@ -1,30 +1,23 @@
var yo = require ( 'yo-yo' )
var async = require ( 'async' )
var helper = require ( '../../lib/helper.js' )
var tooltip = require ( '../ui/tooltip' )
var modalDialogCustom = require ( '../ui/modal-dialog-custom' )
var globalRegistry = require ( '../../global/registry' )
var css = require ( './styles/test-tab-styles' )
var remixTests = require ( 'remix-tests' )
import { ApiFactory } from 'remix-plugin'
const TestTabLogic = require ( './testTab/testTab' )
module . exports = class TestTab extends ApiFactory {
constructor ( localRegistry , compileTab ) {
constructor ( fileManager , filePanel , compileTab ) {
super ( )
// TODO here is a direct reference to compile tab, should be removed
const self = this
self . compileTab = compileTab
self . _view = { el : null }
self . _components = { }
self . _components . registry = localRegistry || globalRegistry
// dependencies
self . _deps = {
fileManager : self . _components . registry . get ( 'filemanager' ) . api ,
filePanel : self . _components . registry . get ( 'filepanel' ) . api
}
self . data = { }
self . testList = yo ` <div class= ${ css . testList } ></div> `
this . compileTab = compileTab
this . _view = { el : null }
this . compileTab = compileTab
this . fileManager = fileManager
this . filePanel = filePanel
this . testTabLogic = new TestTabLogic ( fileManager )
this . data = { }
this . testList = yo ` <div class= ${ css . testList } ></div> `
}
get profile ( ) {
@ -38,164 +31,135 @@ module.exports = class TestTab extends ApiFactory {
}
}
render ( ) {
const self = this
var testsOutput = yo ` <div class=" ${ css . container } border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div> `
var testsSummary = yo ` <div class=" ${ css . container } border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div> `
var testCallback = function ( result ) {
testsOutput . hidden = false
if ( result . type === 'contract' ) {
testsOutput . appendChild ( yo ` <div class=" ${ css . outputTitle } "> ${ result . filename } ( ${ result . value } )</div> ` )
} else if ( result . type === 'testPass' ) {
testsOutput . appendChild ( yo ` <div class=" ${ css . testPass } ${ css . testLog } bg-success">✓ ( ${ result . value } )</div> ` )
} else if ( result . type === 'testFailure' ) {
testsOutput . appendChild ( yo ` <div class=" ${ css . testFailure } ${ css . testLog } bg-danger">✘ ( ${ result . value } )</div> ` )
}
}
var resultsCallback = function ( _err , result , cb ) {
// total stats for the test
// result.passingNum
// result.failureNum
// result.timePassed
cb ( )
}
var updateFinalResult = function ( _err , result , filename ) {
testsSummary . hidden = false
if ( _err ) {
testsSummary . appendChild ( yo ` <div class=" ${ css . testFailureSummary } text-danger" > ${ _err . message } </div> ` )
return
}
testsSummary . appendChild ( yo ` <div class= ${ css . summaryTitle } > ${ filename } </div> ` )
if ( result . totalPassing > 0 ) {
testsSummary . appendChild ( yo ` <div class="text-success"> ${ result . totalPassing } passing ( ${ result . totalTime } s)</div> ` )
testsSummary . appendChild ( yo ` <br> ` )
}
if ( result . totalFailing > 0 ) {
testsSummary . appendChild ( yo ` <div class="text-danger" > ${ result . totalFailing } failing</div> ` )
testsSummary . appendChild ( yo ` <br> ` )
}
result . errors . forEach ( ( error , index ) => {
testsSummary . appendChild ( yo ` <div class="text-danger" > ${ error . context } - ${ error . value } </div> ` )
testsSummary . appendChild ( yo ` <div class=" ${ css . testFailureSummary } text-danger" > ${ error . message } </div> ` )
testsSummary . appendChild ( yo ` <br> ` )
} )
}
function runTest ( testFilePath , callback ) {
self . _deps . fileManager . fileProviderOf ( testFilePath ) . get ( testFilePath , ( error , content ) => {
if ( ! error ) {
var runningTest = { }
runningTest [ testFilePath ] = { content }
remixTests . runTestSources ( runningTest , testCallback , resultsCallback , ( error , result ) => {
updateFinalResult ( error , result , testFilePath )
callback ( error )
} , ( url , cb ) => { self . compileTab . compileTabLogic . importFileCb ( url , cb ) } )
}
} )
}
activate ( ) {
this . listenToEvents ( )
}
function getTests ( self , cb ) {
var path = self . _deps . fileManager . currentPath ( )
if ( ! path ) return cb ( null , [ ] )
var provider = self . _deps . fileManager . fileProviderOf ( path )
if ( ! provider ) return cb ( null , [ ] )
var tests = [ ]
self . _deps . fileManager . getFilesFromPath ( path )
. then ( ( files ) => {
for ( var file in files ) {
if ( /.(_test.sol)$/ . exec ( file ) ) tests . push ( provider . type + '/' + file )
}
cb ( null , tests )
} )
. catch ( err => cb ( err ) )
}
deactivate ( ) {
}
self . _deps . filePanel . event . register ( 'newTestFileCreated' , file => {
var testList = self . view . querySelector ( "[class^='testList']" )
var test = yo ` <label class="singleTestLabel"><input class="singleTest" onchange= ${ ( e ) => toggleCheckbox ( e . target . checked , file ) } type="checkbox" checked="true"> ${ file } </label> `
listenToEvents ( ) {
this . filePanel . event . register ( 'newTestFileCreated' , file => {
var testList = this . view . querySelector ( "[class^='testList']" )
var test = yo ` <label class="singleTestLabel"><input class="singleTest" onchange= ${ ( e ) => this . toggleCheckbox ( e . target . checked , file ) } type="checkbox" checked="true"> ${ file } </label> `
testList . appendChild ( test )
self . data . allTests . push ( file )
self . data . selectedTests . push ( file )
this . data . allTests . push ( file )
this . data . selectedTests . push ( file )
} )
self . _dep s. fileManager . events . on ( 'currentFileChanged' , ( file ) => {
getTests ( self , ( error , tests ) => {
this . fileManager . events . on ( 'currentFileChanged' , ( file , provider ) => {
this . testTabLogic . getTests ( ( error , tests ) => {
if ( error ) return tooltip ( error )
self . data . allTests = tests
self . data . selectedTests = [ ... self . data . allTests ]
if ( ! tests . length ) {
yo . update ( self . testList , yo ` <div class= ${ css . testList } >No test file available</div> ` )
} else {
yo . update ( self . testList , yo ` <div class= ${ css . testList } > ${ listTests ( ) } </div> ` )
}
testsOutput . hidden = true
testsSummary . hidden = true
testsOutput . innerHTML = ''
testsSummary . innerHTML = ''
this . data . allTests = tests
this . data . selectedTests = [ ... this . data . allTests ]
const testsMessage = ( tests . length ? this . listTests ( ) : 'No test file available' )
yo . update ( this . testList , yo ` <div class= ${ css . testList } > ${ testsMessage } </div> ` )
if ( ! this . testsOutput || ! this . testsSummary ) return
this . testsOutput . hidden = true
this . testsSummary . hidden = true
this . testsOutput . innerHTML = ''
this . testsSummary . innerHTML = ''
} )
} )
}
// self._events.filePanel.register('fileRenamed', (oldName, newName, isFolder) => {
// debugger
// self.data.allTests = self.data.allTests.filter(e => e != oldName)
// self.data.selectedTests = self.data.selectedTests.filter(e => e !== oldName)
// if (/.(_test.sol)$/.exec(newName)) self.data.allTests.push(newName)
// })
listTests ( ) {
return this . data . allTests . map ( test => yo ` <label class="singleTestLabel"><input class="singleTest" onchange = ${ ( e ) => this . toggleCheckbox ( e . target . checked , test ) } type="checkbox" checked="true"> ${ test } </label> ` )
}
function listTests ( ) {
var tests = self . data . allTests
return tests . map ( test => yo ` <label class="singleTestLabel"><input class="singleTest" onchange = ${ ( e ) => toggleCheckbox ( e . target . checked , test ) } type="checkbox" checked="true"> ${ test } </label> ` )
toggleCheckbox ( eChecked , test ) {
if ( ! this . data . selectedTests ) {
this . data . selectedTests = this . _view . el . querySelectorAll ( '.singleTest:checked' )
}
function toggleCheckbox ( eChecked , test ) {
if ( ! self . data . selectedTests ) {
self . data . selectedTests = self . _view . el . querySelectorAll ( '.singleTest:checked' )
}
let selectedTests = self . data . selectedTests
selectedTests = eChecked ? [ ... selectedTests , test ] : selectedTests . filter ( el => el !== test )
self . data . selectedTests = selectedTests
let checkAll = self . _view . el . querySelector ( '[id="checkAllTests"]' )
if ( eChecked ) {
checkAll . checked = true
} else if ( ! selectedTests . length ) {
checkAll . checked = false
}
let selectedTests = this . data . selectedTests
selectedTests = eChecked ? [ ... selectedTests , test ] : selectedTests . filter ( el => el !== test )
this . data . selectedTests = selectedTests
let checkAll = this . _view . el . querySelector ( '[id="checkAllTests"]' )
if ( eChecked ) {
checkAll . checked = true
} else if ( ! selectedTests . length ) {
checkAll . checked = false
}
}
function checkAll ( event ) {
let checkBoxes = self . _view . el . querySelectorAll ( '.singleTest' )
const checkboxesLabels = self . _view . el . querySelectorAll ( '.singleTestLabel' )
// checks/unchecks all
for ( let i = 0 ; i < checkBoxes . length ; i ++ ) {
checkBoxes [ i ] . checked = event . target . checked
toggleCheckbox ( event . target . checked , checkboxesLabels [ i ] . innerText )
}
checkAll ( event ) {
let checkBoxes = this . _view . el . querySelectorAll ( '.singleTest' )
const checkboxesLabels = this . _view . el . querySelectorAll ( '.singleTestLabel' )
// checks/unchecks all
for ( let i = 0 ; i < checkBoxes . length ; i ++ ) {
checkBoxes [ i ] . checked = event . target . checked
this . toggleCheckbox ( event . target . checked , checkboxesLabels [ i ] . innerText )
}
}
var runTests = function ( ) {
testsOutput . innerHTML = 'Running tests ...'
var tests = self . data . selectedTests
async . eachOfSeries ( tests , ( value , key , callback ) => { runTest ( value , callback ) } )
testCallback ( result ) {
this . testsOutput . hidden = false
if ( result . type === 'contract' ) {
this . testsOutput . appendChild ( yo ` <div class= ${ css . outputTitle } > ${ result . filename } ( ${ result . value } )</div> ` )
} else if ( result . type === 'testPass' ) {
this . testsOutput . appendChild ( yo ` <div class=" ${ css . testPass } ${ css . testLog } bg-success">✓ ( ${ result . value } )</div> ` )
} else if ( result . type === 'testFailure' ) {
this . testsOutput . appendChild ( yo ` <div class=" ${ css . testFailure } ${ css . testLog } bg-danger">✘ ( ${ result . value } )</div> ` )
}
}
resultsCallback ( _err , result , cb ) {
// total stats for the test
// result.passingNum
// result.failureNum
// result.timePassed
cb ( )
}
var generateTestFile = function ( ) {
var fileManager = self . _deps . fileManager
var path = fileManager . currentPath ( )
var fileProvider = fileManager . fileProviderOf ( path )
if ( fileProvider ) {
helper . createNonClashingNameWithPrefix ( path + '/test.sol' , fileProvider , '_test' , ( error , newFile ) => {
if ( error ) return modalDialogCustom . alert ( 'Failed to create file. ' + newFile + ' ' + error )
if ( ! fileProvider . set ( newFile , testContractSample ) ) {
modalDialogCustom . alert ( 'Failed to create test file ' + newFile )
} else {
fileManager . switchFile ( newFile )
}
} )
}
updateFinalResult ( _err , result , filename ) {
this . testsSummary . hidden = false
if ( _err ) {
this . testsSummary . appendChild ( yo ` <div class=" ${ css . testFailureSummary } text-danger" > ${ _err . message } </div> ` )
return
}
this . testsSummary . appendChild ( yo ` <div class= ${ css . summaryTitle } > ${ filename } </div> ` )
if ( result . totalPassing > 0 ) {
this . testsSummary . appendChild ( yo ` <div class="text-success" > ${ result . totalPassing } passing ( ${ result . totalTime } s)</div> ` )
this . testsSummary . appendChild ( yo ` <br> ` )
}
if ( result . totalFailing > 0 ) {
this . testsSummary . appendChild ( yo ` <div class="text-danger" > ${ result . totalFailing } failing</div> ` )
this . testsSummary . appendChild ( yo ` <br> ` )
}
result . errors . forEach ( ( error , index ) => {
this . testsSummary . appendChild ( yo ` <div class="text-danger" > ${ error . context } - ${ error . value } </div> ` )
this . testsSummary . appendChild ( yo ` <div class=" ${ css . testFailureSummary } text-danger" > ${ error . message } </div> ` )
this . testsSummary . appendChild ( yo ` <br> ` )
} )
}
runTest ( testFilePath , callback ) {
this . fileManager . fileProviderOf ( testFilePath ) . get ( testFilePath , ( error , content ) => {
if ( error ) return
var runningTest = { }
runningTest [ testFilePath ] = { content }
remixTests . runTestSources ( runningTest , ( result ) => { this . testCallback ( result ) } , ( _err , result , cb ) => { this . resultsCallback ( _err , result , cb ) } , ( error , result ) => {
this . updateFinalResult ( error , result , testFilePath )
callback ( error )
} , ( url , cb ) => {
return this . compileTab . compileTabLogic . importFileCb ( url , cb )
} )
} )
}
runTests ( ) {
this . testsOutput . innerHTML = ''
this . testsSummary . innerHTML = ''
var tests = this . data . selectedTests
async . eachOfSeries ( tests , ( value , key , callback ) => { this . runTest ( value , callback ) } )
}
render ( ) {
this . testsOutput = yo ` <div class=" ${ css . container } border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div> `
this . testsSummary = yo ` <div class=" ${ css . container } border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div> `
var el = yo `
< div class = "${css.testTabView} card" id = "testView" >
@ -208,70 +172,28 @@ module.exports = class TestTab extends ApiFactory {
< br / >
For more details , see
How to test smart contracts guide in our documentation .
< br / >
< button class = "${css.generateTestFile} btn btn-primary m-1" onclick = "${generateTestFile}" > Generate test file < / b u t t o n >
< div class = "${css.generateTestFile} btn btn-primary m-1" onclick = "${this.testTabLogic.generateTestFile(this)}" > Generate test file < / d i v >
< / d i v >
< div class = "${css.tests}" >
$ { self . testList }
$ { this . testList }
< div class = "${css.buttons} btn-group" >
< button class = "${css.runButton} btn btn-primary m-1" onclick = "${runTests}" > Run Tests < / b u t t o n >
< div class = "${css.runButton} btn btn-primary m-1" onclick = "${this. runTests.bind(this) }" > Run Tests < / d i v >
< label class = "${css.label}" for = "checkAllTests" >
< input id = "checkAllTests"
type = "checkbox"
onclick = "${function (event) { checkAll(event) }}"
onclick = "${(event) => { this. checkAll(event) }}"
checked = "true"
>
Check / Uncheck all
< / l a b e l >
< / d i v >
$ { testsOutput }
$ { testsSummary }
$ { this . testsOutput }
$ { this . testsSummary }
< / d i v >
< / d i v >
`
if ( ! self . _view . el ) self . _view . el = el
if ( ! this . _view . el ) this . _view . el = el
return el
}
}
var testContractSample = ` pragma solidity >=0.4.0 <0.6.0;
import "remix_tests.sol" ; // this import is automatically injected by Remix.
// file name has to end with '_test.sol'
contract test _1 {
function beforeAll ( ) public {
// here should instantiate tested contract
Assert . equal ( uint ( 4 ) , uint ( 3 ) , "error in before all function" ) ;
}
function check1 ( ) public {
// use 'Assert' to test the contract
Assert . equal ( uint ( 2 ) , uint ( 1 ) , "error message" ) ;
Assert . equal ( uint ( 2 ) , uint ( 2 ) , "error message" ) ;
}
function check2 ( ) public view returns ( bool ) {
// use the return value (true or false) to test the contract
return true ;
}
}
contract test _2 {
function beforeAll ( ) public {
// here should instantiate tested contract
Assert . equal ( uint ( 4 ) , uint ( 3 ) , "error in before all function" ) ;
}
function check1 ( ) public {
// use 'Assert' to test the contract
Assert . equal ( uint ( 2 ) , uint ( 1 ) , "error message" ) ;
Assert . equal ( uint ( 2 ) , uint ( 2 ) , "error message" ) ;
}
function check2 ( ) public view returns ( bool ) {
// use the return value (true or false) to test the contract
return true ;
}
} `