Merge branch 'master' of https://github.com/ethereum/remix-project into panels
commit
38758f9171
@ -0,0 +1,35 @@ |
||||
{ |
||||
"root": true, |
||||
"ignorePatterns": ["**/*"], |
||||
"plugins": ["@nrwl/nx"], |
||||
"overrides": [ |
||||
{ |
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], |
||||
"rules": { |
||||
"@nrwl/nx/enforce-module-boundaries": [ |
||||
"error", |
||||
{ |
||||
"enforceBuildableLibDependency": true, |
||||
"allow": [], |
||||
"depConstraints": [ |
||||
{ |
||||
"sourceTag": "*", |
||||
"onlyDependOnLibsWithTags": ["*"] |
||||
} |
||||
] |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
{ |
||||
"files": ["*.ts", "*.tsx"], |
||||
"extends": ["plugin:@nrwl/nx/typescript"], |
||||
"rules": {} |
||||
}, |
||||
{ |
||||
"files": ["*.js", "*.jsx"], |
||||
"extends": ["plugin:@nrwl/nx/javascript"], |
||||
"rules": {} |
||||
} |
||||
] |
||||
} |
@ -1,135 +0,0 @@ |
||||
const helper = require('../../../lib/helper.js') |
||||
const modalDialogCustom = require('../../ui/modal-dialog-custom') |
||||
const remixPath = require('path') |
||||
|
||||
class TestTabLogic { |
||||
constructor (fileManager) { |
||||
this.fileManager = fileManager |
||||
this.currentPath = '/tests' |
||||
} |
||||
|
||||
setCurrentPath (path) { |
||||
if (path.indexOf('/') === 0) return |
||||
this.currentPath = helper.removeMultipleSlashes(helper.removeTrailingSlashes(path)) |
||||
} |
||||
|
||||
generateTestFolder (path) { |
||||
// Todo move this check to File Manager after refactoring
|
||||
// Checking to ignore the value which contains only whitespaces
|
||||
if (!path || !(/\S/.test(path))) return |
||||
path = helper.removeMultipleSlashes(path) |
||||
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0]) |
||||
fileProvider.exists(path).then(res => { |
||||
if (!res) fileProvider.createDir(path) |
||||
}) |
||||
} |
||||
|
||||
async pathExists (path) { |
||||
// Checking to ignore the value which contains only whitespaces
|
||||
if (!path || !(/\S/.test(path))) return |
||||
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0]) |
||||
const res = await fileProvider.exists(path, (e, res) => { return res }) |
||||
return res |
||||
} |
||||
|
||||
generateTestFile () { |
||||
let fileName = this.fileManager.currentFile() |
||||
const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol' |
||||
if (!hasCurrent) fileName = this.currentPath + '/newFile.sol' |
||||
const fileProvider = this.fileManager.fileProviderOf(this.currentPath) |
||||
if (!fileProvider) return |
||||
const splittedFileName = fileName.split('/') |
||||
const fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1] |
||||
helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', (error, newFile) => { |
||||
if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error) |
||||
if (!fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName))) return modalDialogCustom.alert('Failed to create test file ' + newFile) |
||||
this.fileManager.open(newFile) |
||||
this.fileManager.syncEditor(newFile) |
||||
}) |
||||
} |
||||
|
||||
dirList (path) { |
||||
return this.fileManager.dirList(path) |
||||
} |
||||
|
||||
isRemixDActive () { |
||||
return this.fileManager.isRemixDActive() |
||||
} |
||||
|
||||
async getTests (cb) { |
||||
if (!this.currentPath) return cb(null, []) |
||||
const provider = this.fileManager.fileProviderOf(this.currentPath) |
||||
if (!provider) return cb(null, []) |
||||
const tests = [] |
||||
let files = [] |
||||
try { |
||||
if (await this.fileManager.exists(this.currentPath)) files = await this.fileManager.readdir(this.currentPath) |
||||
} catch (e) { |
||||
cb(e.message) |
||||
} |
||||
for (var file in files) { |
||||
const filepath = provider && provider.type ? provider.type + '/' + file : file |
||||
if (/.(_test.sol)$/.exec(file)) tests.push(filepath) |
||||
} |
||||
cb(null, tests, this.currentPath) |
||||
} |
||||
|
||||
// @todo(#2758): If currently selected file is compiled and compilation result is available,
|
||||
// 'contractName' should be <compiledContractName> + '_testSuite'
|
||||
generateTestContractSample (hasCurrent, fileToImport, contractName = 'testSuite') { |
||||
let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport)) |
||||
if (relative === '') relative = '.' |
||||
const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// <import file to test>' |
||||
return `// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
pragma solidity >=0.4.22 <0.9.0; |
||||
|
||||
// This import is automatically injected by Remix
|
||||
import "remix_tests.sol";
|
||||
|
||||
// This import is required to use custom transaction context
|
||||
// Although it may fail compilation in 'Solidity Compiler' plugin
|
||||
// But it will work fine in 'Solidity Unit Testing' plugin
|
||||
import "remix_accounts.sol"; |
||||
${comment} |
||||
|
||||
// File name has to end with '_test.sol', this file can contain more than one testSuite contracts
|
||||
contract ${contractName} { |
||||
|
||||
/// 'beforeAll' runs before all other tests
|
||||
/// More special functions are: 'beforeEach', 'beforeAll', 'afterEach' & 'afterAll'
|
||||
function beforeAll() public { |
||||
// <instantiate contract>
|
||||
Assert.equal(uint(1), uint(1), "1 should be equal to 1"); |
||||
} |
||||
|
||||
function checkSuccess() public { |
||||
// Use 'Assert' methods: https://remix-ide.readthedocs.io/en/latest/assert_library.html
|
||||
Assert.ok(2 == 2, 'should be true'); |
||||
Assert.greaterThan(uint(2), uint(1), "2 should be greater than to 1"); |
||||
Assert.lesserThan(uint(2), uint(3), "2 should be lesser than to 3"); |
||||
} |
||||
|
||||
function checkSuccess2() public pure returns (bool) { |
||||
// Use the return value (true or false) to test the contract
|
||||
return true; |
||||
} |
||||
|
||||
function checkFailure() public { |
||||
Assert.notEqual(uint(1), uint(1), "1 should not be equal to 1"); |
||||
} |
||||
|
||||
/// Custom Transaction Context: https://remix-ide.readthedocs.io/en/latest/unittesting.html#customization
|
||||
/// #sender: account-1
|
||||
/// #value: 100
|
||||
function checkSenderAndValue() public payable { |
||||
// account index varies 0-9, value is in wei
|
||||
Assert.equal(msg.sender, TestsAccounts.getAccount(1), "Invalid sender"); |
||||
Assert.equal(msg.value, 100, "Invalid value"); |
||||
} |
||||
} |
||||
` |
||||
} |
||||
} |
||||
|
||||
module.exports = TestTabLogic |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"presets": [ |
||||
[ |
||||
"@nrwl/react/babel", |
||||
{ |
||||
"runtime": "automatic", |
||||
"useBuiltIns": "usage" |
||||
} |
||||
] |
||||
], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,21 @@ |
||||
{ |
||||
"extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"], |
||||
"ignorePatterns": ["!**/*"], |
||||
"rules": { |
||||
"@typescript-eslint/no-explicit-any": "off" |
||||
}, |
||||
"overrides": [ |
||||
{ |
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], |
||||
"rules": {} |
||||
}, |
||||
{ |
||||
"files": ["*.ts", "*.tsx"], |
||||
"rules": {} |
||||
}, |
||||
{ |
||||
"files": ["*.js", "*.jsx"], |
||||
"rules": {} |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,7 @@ |
||||
# remix-ui-solidity-unit-testing |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-solidity-unit-testing` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1,2 @@ |
||||
export * from './lib/solidity-unit-testing' |
||||
export * from './lib/logic/testTabLogic' |
@ -0,0 +1,45 @@ |
||||
|
||||
.infoBox { |
||||
margin: 5%; |
||||
} |
||||
.testList { |
||||
line-height: 2em; |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin: 5%; |
||||
max-height: 300px; |
||||
overflow-y: auto; |
||||
|
||||
} |
||||
.container { |
||||
margin: 2%; |
||||
padding-bottom: 5%; |
||||
max-height: 300px; |
||||
overflow-y: auto; |
||||
} |
||||
.summaryTitle { |
||||
font-weight: bold; |
||||
} |
||||
.testPass { |
||||
} |
||||
.testLog { |
||||
margin-bottom: 1%; |
||||
border-radius: 4px; |
||||
padding: 1% 1% 1% 5%; |
||||
} |
||||
.title { |
||||
font-size: 1.1em; |
||||
font-weight: bold; |
||||
margin-bottom: 1em; |
||||
} |
||||
.label { |
||||
display: flex; |
||||
align-items: center; |
||||
white-space: nowrap; |
||||
} |
||||
.labelOnBtn { |
||||
border: hidden; |
||||
} |
||||
.inputFolder { |
||||
width: 80%; |
||||
} |
@ -0,0 +1,137 @@ |
||||
import remixPath from 'path' |
||||
|
||||
export class TestTabLogic { |
||||
|
||||
fileManager |
||||
currentPath |
||||
helper |
||||
constructor (fileManager: any, helper: any) { |
||||
this.fileManager = fileManager |
||||
this.helper = helper |
||||
this.currentPath = '/tests' |
||||
} |
||||
|
||||
setCurrentPath (path: string) { |
||||
if (path.indexOf('/') === 0) return |
||||
this.currentPath = this.helper.removeMultipleSlashes(this.helper.removeTrailingSlashes(path)) |
||||
} |
||||
|
||||
generateTestFolder (path:string) { |
||||
// Todo move this check to File Manager after refactoring
|
||||
// Checking to ignore the value which contains only whitespaces
|
||||
if (!path || !(/\S/.test(path))) return |
||||
path = this.helper.removeMultipleSlashes(path) |
||||
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0]) |
||||
fileProvider.exists(path).then((res: any) => { |
||||
if (!res) fileProvider.createDir(path) |
||||
}) |
||||
} |
||||
|
||||
async pathExists (path: string) { |
||||
// Checking to ignore the value which contains only whitespaces
|
||||
if (!path || !(/\S/.test(path))) return |
||||
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0]) |
||||
const res = await fileProvider.exists(path, (e: any, res: any) => { return res }) |
||||
return res |
||||
} |
||||
|
||||
generateTestFile (errorCb: any) { |
||||
let fileName = this.fileManager.currentFile() |
||||
const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol' |
||||
if (!hasCurrent) fileName = this.currentPath + '/newFile.sol' |
||||
const fileProvider = this.fileManager.fileProviderOf(this.currentPath) |
||||
if (!fileProvider) return |
||||
const splittedFileName = fileName.split('/') |
||||
const fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1] |
||||
this.helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', (error: any, newFile: any) => { |
||||
if (error) return errorCb('Failed to create file. ' + newFile + ' ' + error) |
||||
const isFileCreated = fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName)) |
||||
if (!isFileCreated) return errorCb('Failed to create test file ' + newFile) |
||||
this.fileManager.open(newFile) |
||||
this.fileManager.syncEditor(newFile) |
||||
}) |
||||
} |
||||
|
||||
dirList (path:string) { |
||||
return this.fileManager.dirList(path) |
||||
} |
||||
|
||||
isRemixDActive () { |
||||
return this.fileManager.isRemixDActive() |
||||
} |
||||
|
||||
async getTests (cb: any) { |
||||
if (!this.currentPath) return cb(null, []) |
||||
const provider = this.fileManager.fileProviderOf(this.currentPath) |
||||
if (!provider) return cb(null, []) |
||||
const tests = [] |
||||
let files = [] |
||||
try { |
||||
if (await this.fileManager.exists(this.currentPath)) files = await this.fileManager.readdir(this.currentPath) |
||||
} catch (e: any) { |
||||
cb(e.message) |
||||
} |
||||
for (const file in files) { |
||||
const filepath = provider && provider.type ? provider.type + '/' + file : file |
||||
if (/.(_test.sol)$/.exec(file)) tests.push(filepath) |
||||
} |
||||
cb(null, tests, this.currentPath) |
||||
} |
||||
|
||||
// @todo(#2758): If currently selected file is compiled and compilation result is available,
|
||||
// 'contractName' should be <compiledContractName> + '_testSuite'
|
||||
generateTestContractSample (hasCurrent: any, fileToImport: any, contractName = 'testSuite') { |
||||
let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport)) |
||||
if (relative === '') relative = '.' |
||||
const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// <import file to test>' |
||||
return `// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
pragma solidity >=0.4.22 <0.9.0; |
||||
|
||||
// This import is automatically injected by Remix
|
||||
import "remix_tests.sol";
|
||||
|
||||
// This import is required to use custom transaction context
|
||||
// Although it may fail compilation in 'Solidity Compiler' plugin
|
||||
// But it will work fine in 'Solidity Unit Testing' plugin
|
||||
import "remix_accounts.sol"; |
||||
${comment} |
||||
|
||||
// File name has to end with '_test.sol', this file can contain more than one testSuite contracts
|
||||
contract ${contractName} { |
||||
|
||||
/// 'beforeAll' runs before all other tests
|
||||
/// More special functions are: 'beforeEach', 'beforeAll', 'afterEach' & 'afterAll'
|
||||
function beforeAll() public { |
||||
// <instantiate contract>
|
||||
Assert.equal(uint(1), uint(1), "1 should be equal to 1"); |
||||
} |
||||
|
||||
function checkSuccess() public { |
||||
// Use 'Assert' methods: https://remix-ide.readthedocs.io/en/latest/assert_library.html
|
||||
Assert.ok(2 == 2, 'should be true'); |
||||
Assert.greaterThan(uint(2), uint(1), "2 should be greater than to 1"); |
||||
Assert.lesserThan(uint(2), uint(3), "2 should be lesser than to 3"); |
||||
} |
||||
|
||||
function checkSuccess2() public pure returns (bool) { |
||||
// Use the return value (true or false) to test the contract
|
||||
return true; |
||||
} |
||||
|
||||
function checkFailure() public { |
||||
Assert.notEqual(uint(1), uint(1), "1 should not be equal to 1"); |
||||
} |
||||
|
||||
/// Custom Transaction Context: https://remix-ide.readthedocs.io/en/latest/unittesting.html#customization
|
||||
/// #sender: account-1
|
||||
/// #value: 100
|
||||
function checkSenderAndValue() public payable { |
||||
// account index varies 0-9, value is in wei
|
||||
Assert.equal(msg.sender, TestsAccounts.getAccount(1), "Invalid sender"); |
||||
Assert.equal(msg.value, 100, "Invalid value"); |
||||
} |
||||
} |
||||
` |
||||
} |
||||
} |
@ -0,0 +1,734 @@ |
||||
import React, { useState, useRef, useEffect } from 'react' // eslint-disable-line
|
||||
import { eachOfSeries } from 'async' // eslint-disable-line
|
||||
import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity' |
||||
import { Renderer } from '@remix-ui/renderer' // eslint-disable-line
|
||||
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
|
||||
import { format } from 'util' |
||||
import './css/style.css' |
||||
|
||||
const _paq = (window as any)._paq = (window as any)._paq || [] // eslint-disable-line
|
||||
|
||||
/* eslint-disable-next-line */ |
||||
export interface SolidityUnitTestingProps { } |
||||
|
||||
interface TestObject { |
||||
fileName: string |
||||
checked: boolean |
||||
} |
||||
|
||||
export const SolidityUnitTesting = (props: Record<string, any>) => { |
||||
|
||||
const { helper, testTab, initialPath } = props |
||||
const { testTabLogic } = testTab |
||||
|
||||
const [defaultPath, setDefaultPath] = useState('tests') |
||||
const [toasterMsg, setToasterMsg] = useState('') |
||||
|
||||
const [disableCreateButton, setDisableCreateButton] = useState(true) |
||||
const [disableGenerateButton, setDisableGenerateButton] = useState(false) |
||||
const [disableStopButton, setDisableStopButton] = useState(true) |
||||
const [disableRunButton, setDisableRunButton] = useState(false) |
||||
const [runButtonTitle, setRunButtonTitle] = useState('Run tests') |
||||
const [stopButtonLabel, setStopButtonLabel] = useState('Stop') |
||||
|
||||
const [checkSelectAll, setCheckSelectAll] = useState(true) |
||||
const [testsOutput, setTestsOutput] = useState<Element[]>([]) |
||||
|
||||
const [testsExecutionStoppedHidden, setTestsExecutionStoppedHidden] = useState(true) |
||||
const [progressBarHidden, setProgressBarHidden] = useState(true) |
||||
const [testsExecutionStoppedErrorHidden, setTestsExecutionStoppedErrorHidden] = useState(true) |
||||
|
||||
let [testFiles, setTestFiles] = useState<TestObject[]>([]) // eslint-disable-line
|
||||
const [pathOptions, setPathOptions] = useState(['']) |
||||
|
||||
const [inputPathValue, setInputPathValue] = useState('tests') |
||||
|
||||
let [readyTestsNumber, setReadyTestsNumber] = useState(0) // eslint-disable-line
|
||||
let [runningTestsNumber, setRunningTestsNumber] = useState(0) // eslint-disable-line
|
||||
|
||||
const hasBeenStopped = useRef(false) |
||||
const isDebugging = useRef(false) |
||||
const allTests: any = useRef([]) |
||||
const selectedTests: any = useRef([]) |
||||
const currentErrors: any = useRef([]) |
||||
|
||||
|
||||
let areTestsRunning = false |
||||
|
||||
let runningTestFileName: any |
||||
const filesContent: any = {} |
||||
const testsResultByFilename: Record<string, any> = {} |
||||
|
||||
const trimTestDirInput = (input: string) => { |
||||
if (input.includes('/')) return input.split('/').map(e => e.trim()).join('/') |
||||
else return input.trim() |
||||
} |
||||
|
||||
const clearResults = () => { |
||||
setProgressBarHidden(true) |
||||
testTab.call('editor', 'clearAnnotations') |
||||
setTestsOutput([]) |
||||
setTestsExecutionStoppedHidden(true) |
||||
setTestsExecutionStoppedErrorHidden(true) |
||||
} |
||||
|
||||
const updateForNewCurrent = async (file = null) => { |
||||
// Ensure that when someone clicks on compilation error and that opens a new file
|
||||
// Test result, which is compilation error in this case, is not cleared
|
||||
if (currentErrors.current) { |
||||
if (Array.isArray(currentErrors.current) && currentErrors.current.length > 0) { |
||||
const errFiles = currentErrors.current.map((err:any) => { if (err.sourceLocation && err.sourceLocation.file) return err.sourceLocation.file }) // eslint-disable-line
|
||||
if (errFiles.includes(file)) return |
||||
} else if (currentErrors.current.sourceLocation && currentErrors.current.sourceLocation.file && currentErrors.current.sourceLocation.file === file) return |
||||
} |
||||
// if current file is changed while debugging and one of the files imported in test file are opened
|
||||
// do not clear the test results in SUT plugin
|
||||
if (isDebugging.current && testTab.allFilesInvolved.includes(file)) return |
||||
allTests.current = [] |
||||
updateTestFileList() |
||||
clearResults() |
||||
try { |
||||
testTabLogic.getTests(async (error: any, tests: any) => { |
||||
if (error) return setToasterMsg(error)
|
||||
allTests.current = tests |
||||
selectedTests.current = [...allTests.current] |
||||
updateTestFileList() |
||||
if (!areTestsRunning) await updateRunAction(file) |
||||
}) |
||||
} catch (e) { |
||||
console.log(e) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Changes the current path of Unit Testing Plugin |
||||
* @param path - the path from where UT plugin takes _test.sol files to run |
||||
*/ |
||||
const setCurrentPath = async (path: string) => { |
||||
testTabLogic.setCurrentPath(path) |
||||
setInputPathValue(path) |
||||
updateDirList(path) |
||||
await updateForNewCurrent() |
||||
} |
||||
|
||||
useEffect(() => { |
||||
if (initialPath) setCurrentPath(initialPath) |
||||
}, [initialPath]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
useEffect(() => { |
||||
updateDirList('/') |
||||
updateForNewCurrent() |
||||
|
||||
testTab.on('filePanel', 'newTestFileCreated', async (file: string) => { |
||||
try { |
||||
testTabLogic.getTests((error: any, tests: any) => { |
||||
if (error) return setToasterMsg(error)
|
||||
allTests.current = tests |
||||
selectedTests.current = [...allTests.current] |
||||
updateTestFileList() |
||||
}) |
||||
} catch (e) { |
||||
console.log(e) |
||||
allTests.current.push(file) |
||||
selectedTests.current.push(file) |
||||
} |
||||
}) |
||||
|
||||
testTab.on('filePanel', 'setWorkspace', async () => { |
||||
setCurrentPath(defaultPath) |
||||
}) |
||||
|
||||
testTab.fileManager.events.on('noFileSelected', () => { }) // eslint-disable-line
|
||||
testTab.fileManager.events.on('currentFileChanged', (file: any, provider: any) => updateForNewCurrent(file)) |
||||
|
||||
}, []) // eslint-disable-line
|
||||
|
||||
const updateDirList = (path: string) => { |
||||
testTabLogic.dirList(path).then((options: any) => { |
||||
setPathOptions(options) |
||||
}) |
||||
} |
||||
|
||||
const handleTestDirInput = async (e: any) => { |
||||
let testDirInput = trimTestDirInput(e.target.value) |
||||
testDirInput = helper.removeMultipleSlashes(testDirInput) |
||||
if (testDirInput !== '/') testDirInput = helper.removeTrailingSlashes(testDirInput) |
||||
setInputPathValue(testDirInput) |
||||
if (e.key === 'Enter') { |
||||
if (await testTabLogic.pathExists(testDirInput)) { |
||||
testTabLogic.setCurrentPath(testDirInput) |
||||
updateForNewCurrent() |
||||
return |
||||
} |
||||
} |
||||
if (testDirInput) { |
||||
if (testDirInput.endsWith('/') && testDirInput !== '/') { |
||||
testDirInput = helper.removeTrailingSlashes(testDirInput) |
||||
if (testTabLogic.currentPath === testDirInput.substr(0, testDirInput.length - 1)) { |
||||
setDisableCreateButton(true) |
||||
setDisableGenerateButton(true) |
||||
} |
||||
updateDirList(testDirInput) |
||||
} else { |
||||
// If there is no matching folder in the workspace with entered text, enable Create button
|
||||
if (await testTabLogic.pathExists(testDirInput)) { |
||||
setDisableCreateButton(true) |
||||
setDisableGenerateButton(false) |
||||
testTabLogic.setCurrentPath(testDirInput) |
||||
updateForNewCurrent() |
||||
} else { |
||||
// Enable Create button
|
||||
setDisableCreateButton(false) |
||||
// Disable Generate button because dir does not exist
|
||||
setDisableGenerateButton(true) |
||||
} |
||||
} |
||||
} else { |
||||
updateDirList('/') |
||||
} |
||||
} |
||||
|
||||
const handleEnter = async (e: any) => { |
||||
let inputPath = e.target.value |
||||
inputPath = helper.removeMultipleSlashes(trimTestDirInput(inputPath)) |
||||
setInputPathValue(inputPath) |
||||
if (disableCreateButton) { |
||||
if (await testTabLogic.pathExists(inputPath)) { |
||||
testTabLogic.setCurrentPath(inputPath) |
||||
updateForNewCurrent() |
||||
} |
||||
} |
||||
} |
||||
|
||||
const handleCreateFolder = async () => { |
||||
let inputPath = trimTestDirInput(inputPathValue) |
||||
let path = helper.removeMultipleSlashes(inputPath) |
||||
if (path !== '/') path = helper.removeTrailingSlashes(path) |
||||
if (inputPath === '') inputPath = defaultPath |
||||
setInputPathValue(path) |
||||
testTabLogic.generateTestFolder(inputPath) |
||||
setDisableCreateButton(true) |
||||
setDisableGenerateButton(false) |
||||
testTabLogic.setCurrentPath(inputPath) |
||||
await updateRunAction() |
||||
updateForNewCurrent() |
||||
pathOptions.push(inputPath) |
||||
setPathOptions(pathOptions) |
||||
} |
||||
|
||||
const cleanFileName = (fileName: any, testSuite: any) => { |
||||
return fileName ? fileName.replace(/\//g, '_').replace(/\./g, '_') + testSuite : fileName |
||||
} |
||||
|
||||
const startDebug = async (txHash: any, web3: any) => { |
||||
isDebugging.current = true |
||||
if (!await testTab.appManager.isActive('debugger')) await testTab.appManager.activatePlugin('debugger') |
||||
testTab.call('menuicons', 'select', 'debugger') |
||||
testTab.call('debugger', 'debug', txHash, web3) |
||||
} |
||||
|
||||
const printHHLogs = (logsArr: any, testName: any) => { |
||||
let finalLogs = `<b>${testName}:</b>\n` |
||||
for (const log of logsArr) { |
||||
let formattedLog |
||||
// Hardhat implements the same formatting options that can be found in Node.js' console.log,
|
||||
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
|
||||
// For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6'
|
||||
// We check first arg to determine if 'util.format' is needed
|
||||
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) { |
||||
formattedLog = format(log[0], ...log.slice(1)) |
||||
} else { |
||||
formattedLog = log.join(' ') |
||||
} |
||||
finalLogs = finalLogs + ' ' + formattedLog + '\n' |
||||
} |
||||
_paq.push(['trackEvent', 'solidityUnitTesting', 'hardhat', 'console.log']) |
||||
testTab.call('terminal', 'log', { type: 'info', value: finalLogs }) |
||||
} |
||||
|
||||
const discardHighlight = async () => { |
||||
await testTab.call('editor', 'discardHighlight') |
||||
} |
||||
|
||||
const highlightLocation = async (location: string, fileName: string) => { |
||||
if (location) { |
||||
const split = location.split(':') |
||||
const file = split[2] |
||||
const parsedLocation = { |
||||
start: parseInt(split[0]), |
||||
length: parseInt(split[1]) |
||||
} |
||||
const locationToHighlight = testTab.offsetToLineColumnConverter.offsetToLineColumnWithContent( |
||||
parsedLocation, |
||||
parseInt(file), |
||||
filesContent[fileName].content |
||||
) |
||||
await testTab.call('editor', 'discardHighlight') |
||||
await testTab.call('editor', 'highlight', locationToHighlight, fileName, '', { focus: true }) |
||||
} |
||||
} |
||||
|
||||
const renderContract = (filename: any, contract: any, index: number, withoutLabel = false) => { |
||||
if (withoutLabel) { |
||||
const contractCard: any = ( |
||||
<div id={runningTestFileName} data-id="testTabSolidityUnitTestsOutputheader" className="pt-1"> |
||||
<span className="font-weight-bold">{contract ? contract : ''} ({filename})</span> |
||||
</div> |
||||
) |
||||
setTestsOutput(prevCards => ([...prevCards, contractCard])) |
||||
return |
||||
} |
||||
let label |
||||
if (index > -1) { |
||||
const className = "alert-danger d-inline-block mb-1 mr-1 p-1 failed_" + runningTestFileName |
||||
label = (<div |
||||
className={className} |
||||
title="At least one contract test failed" |
||||
> |
||||
FAIL |
||||
</div>) |
||||
} else { |
||||
const className = "alert-success d-inline-block mb-1 mr-1 p-1 passed_" + runningTestFileName |
||||
label = (<div |
||||
className={className} |
||||
title="All contract tests passed" |
||||
> |
||||
PASS |
||||
</div>) |
||||
} |
||||
// show contract and file name with label
|
||||
const ContractCard: any = ( |
||||
<div id={runningTestFileName} data-id="testTabSolidityUnitTestsOutputheader" className="pt-1"> |
||||
{label}<span className="font-weight-bold">{contract} ({filename})</span> |
||||
</div> |
||||
) |
||||
setTestsOutput(prevCards => { |
||||
const index = prevCards.findIndex((card: any) => card.props.id === runningTestFileName) |
||||
prevCards[index] = ContractCard |
||||
return prevCards |
||||
}) |
||||
} |
||||
|
||||
const renderTests = (tests: any, contract: any, filename: any) => { |
||||
const index = tests.findIndex((test: any) => test.type === 'testFailure') |
||||
// show filename and contract
|
||||
renderContract(filename, contract, index) |
||||
// show tests
|
||||
for (const test of tests) { |
||||
if (!test.rendered) { |
||||
let debugBtn |
||||
if (test.debugTxHash) { |
||||
const { web3, debugTxHash } = test |
||||
debugBtn = ( |
||||
<div id={test.value.replaceAll(' ', '_')} className="btn border btn btn-sm ml-1" style={{ cursor: 'pointer' }} title="Start debugging" onClick={() => startDebug(debugTxHash, web3)}> |
||||
<i className="fas fa-bug"></i> |
||||
</div> |
||||
) |
||||
} |
||||
if (test.type === 'testPass') { |
||||
if (test.hhLogs && test.hhLogs.length) printHHLogs(test.hhLogs, test.value) |
||||
const testPassCard: any = ( |
||||
<div |
||||
id={runningTestFileName} |
||||
data-id="testTabSolidityUnitTestsOutputheader" |
||||
className="testPass testLog bg-light mb-2 px-2 text-success border-0" |
||||
onClick={() => discardHighlight()} |
||||
> |
||||
<div className="d-flex my-1 align-items-start justify-content-between"> |
||||
<span > ✓ {test.value}</span> |
||||
{debugBtn} |
||||
</div> |
||||
</div> |
||||
) |
||||
setTestsOutput(prevCards => ([...prevCards, testPassCard])) |
||||
test.rendered = true |
||||
} else if (test.type === 'testFailure') { |
||||
if (test.hhLogs && test.hhLogs.length) printHHLogs(test.hhLogs, test.value) |
||||
if (!test.assertMethod) { |
||||
const testFailCard1: any = (<div |
||||
className="bg-light mb-2 px-2 testLog d-flex flex-column text-danger border-0" |
||||
id={"UTContext" + test.context} |
||||
onClick={() => highlightLocation(test.location, test.filename)} |
||||
> |
||||
<div className="d-flex my-1 align-items-start justify-content-between"> |
||||
<span> ✘ {test.value}</span> |
||||
{debugBtn} |
||||
</div> |
||||
<span className="text-dark">Error Message:</span> |
||||
<span className="pb-2 text-break">"{test.errMsg}"</span> |
||||
</div>) |
||||
setTestsOutput(prevCards => ([...prevCards, testFailCard1])) |
||||
} else { |
||||
const preposition = test.assertMethod === 'equal' || test.assertMethod === 'notEqual' ? 'to' : '' |
||||
const method = test.assertMethod === 'ok' ? '' : test.assertMethod |
||||
const expected = test.assertMethod === 'ok' ? '\'true\'' : test.expected |
||||
const testFailCard2: any = (<div |
||||
className="bg-light mb-2 px-2 testLog d-flex flex-column text-danger border-0" |
||||
id={"UTContext" + test.context} |
||||
onClick={() => highlightLocation(test.location, test.filename)} |
||||
> |
||||
<div className="d-flex my-1 align-items-start justify-content-between"> |
||||
<span> ✘ {test.value}</span> |
||||
{debugBtn} |
||||
</div> |
||||
<span className="text-dark">Error Message:</span> |
||||
<span className="pb-2 text-break">"{test.errMsg}"</span> |
||||
<span className="text-dark">Assertion:</span> |
||||
<div className="d-flex flex-wrap"> |
||||
<span>Expected value should be</span> |
||||
<div className="mx-1 font-weight-bold">{method}</div> |
||||
<div>{preposition} {expected}</div> |
||||
</div> |
||||
<span className="text-dark">Received value:</span> |
||||
<span>{test.returned}</span> |
||||
<span className="text-dark text-sm pb-2">Skipping the remaining tests of the function.</span> |
||||
</div>) |
||||
setTestsOutput(prevCards => ([...prevCards, testFailCard2])) |
||||
} |
||||
test.rendered = true |
||||
} else if (test.type === 'logOnly') { |
||||
if (test.hhLogs && test.hhLogs.length) printHHLogs(test.hhLogs, test.value) |
||||
test.rendered = true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
const showTestsResult = () => { |
||||
const filenames = Object.keys(testsResultByFilename) |
||||
for (const filename of filenames) { |
||||
const fileTestsResult = testsResultByFilename[filename] |
||||
const contracts = Object.keys(fileTestsResult) |
||||
for (const contract of contracts) { |
||||
if (contract && contract !== 'summary' && contract !== 'errors') { |
||||
runningTestFileName = cleanFileName(filename, contract) |
||||
const tests = fileTestsResult[contract] |
||||
if (tests?.length) { |
||||
renderTests(tests, contract, filename) |
||||
} else { |
||||
// show only contract and file name
|
||||
renderContract(filename, contract, -1, true) |
||||
} |
||||
} else if (contract === 'errors' && fileTestsResult['errors']) { |
||||
const errors = fileTestsResult['errors'] |
||||
if (errors && errors.errors) { |
||||
errors.errors.forEach((err: any) => { |
||||
const errorCard: any = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} /> |
||||
setTestsOutput(prevCards => ([...prevCards, errorCard])) |
||||
}) |
||||
} else if (errors && Array.isArray(errors) && (errors[0].message || errors[0].formattedMessage)) { |
||||
errors.forEach((err) => { |
||||
const errorCard: any = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} /> |
||||
setTestsOutput(prevCards => ([...prevCards, errorCard])) |
||||
}) |
||||
} else if (errors && !errors.errors && !Array.isArray(errors)) { |
||||
// To track error like this: https://github.com/ethereum/remix/pull/1438
|
||||
const errorCard: any = <Renderer message={errors.formattedMessage || errors.message} plugin={testTab} opt={{ type: 'error' }} /> |
||||
setTestsOutput(prevCards => ([...prevCards, errorCard])) |
||||
} |
||||
} |
||||
} |
||||
// show summary
|
||||
const testSummary = fileTestsResult['summary'] |
||||
if (testSummary && testSummary.filename && !testSummary.rendered) { |
||||
const summaryCard: any = (<div className="d-flex alert-secondary mb-3 p-3 flex-column"> |
||||
<span className="font-weight-bold">Result for {testSummary.filename}</span> |
||||
<span className="text-success">Passed: {testSummary.passed}</span> |
||||
<span className="text-danger">Failed: {testSummary.failed}</span> |
||||
<span>Time Taken: {testSummary.timeTaken}s</span> |
||||
</div>) |
||||
setTestsOutput(prevCards => ([...prevCards, summaryCard])) |
||||
fileTestsResult['summary']['rendered'] = true |
||||
} |
||||
} |
||||
} |
||||
|
||||
const testCallback = (result: any) => { |
||||
if (result.filename) { |
||||
if (!testsResultByFilename[result.filename]) { |
||||
testsResultByFilename[result.filename] = {} |
||||
testsResultByFilename[result.filename]['summary'] = {} |
||||
} |
||||
if (result.type === 'contract') { |
||||
testsResultByFilename[result.filename][result.value] = {} |
||||
testsResultByFilename[result.filename][result.value] = [] |
||||
} else { |
||||
// Set that this test is not rendered on UI
|
||||
result.rendered = false |
||||
testsResultByFilename[result.filename][result.context].push(result) |
||||
} |
||||
showTestsResult() |
||||
} |
||||
} |
||||
|
||||
const resultsCallback = (_err: any, result: any, cb: any) => { |
||||
// total stats for the test
|
||||
// result.passingNum
|
||||
// result.failureNum
|
||||
// result.timePassed
|
||||
cb() |
||||
} |
||||
|
||||
const updateFinalResult = (_errors: any, result: any, filename: any) => { |
||||
++readyTestsNumber |
||||
setReadyTestsNumber(readyTestsNumber) |
||||
if (!result && (_errors && (_errors.errors || (Array.isArray(_errors) && (_errors[0].message || _errors[0].formattedMessage))))) { |
||||
// show only file name
|
||||
renderContract(filename, null, -1, true) |
||||
currentErrors.current = _errors.errors |
||||
} |
||||
if (result) { |
||||
const totalTime = parseFloat(result.totalTime).toFixed(2) |
||||
const testsSummary = { filename, passed: result.totalPassing, failed: result.totalFailing, timeTaken: totalTime, rendered: false } |
||||
testsResultByFilename[filename]['summary'] = testsSummary |
||||
showTestsResult() |
||||
} else if (_errors) { |
||||
if (!testsResultByFilename[filename]) { |
||||
testsResultByFilename[filename] = {} |
||||
} |
||||
testsResultByFilename[filename]['errors'] = _errors |
||||
setTestsExecutionStoppedErrorHidden(false) |
||||
showTestsResult() |
||||
} |
||||
|
||||
if (hasBeenStopped.current && (readyTestsNumber !== runningTestsNumber)) { |
||||
// if all tests has been through before stopping no need to print this.
|
||||
setTestsExecutionStoppedHidden(false) |
||||
} |
||||
if (_errors || hasBeenStopped.current || readyTestsNumber === runningTestsNumber) { |
||||
// All tests are ready or the operation has been canceled or there was a compilation error in one of the test files.
|
||||
setDisableStopButton(true) |
||||
setStopButtonLabel('Stop') |
||||
if (selectedTests.current?.length !== 0) { |
||||
setDisableRunButton(false) |
||||
} |
||||
areTestsRunning = false |
||||
} |
||||
} |
||||
|
||||
const runTest = (testFilePath: any, callback: any) => { |
||||
isDebugging.current = false |
||||
if (hasBeenStopped.current) { |
||||
updateFinalResult(null, null, null) |
||||
return |
||||
} |
||||
testTab.fileManager.readFile(testFilePath).then((content: any) => { |
||||
const runningTests: any = {} |
||||
runningTests[testFilePath] = { content } |
||||
filesContent[testFilePath] = { content } |
||||
const { currentVersion, evmVersion, optimize, runs, isUrl } = testTab.compileTab.getCurrentCompilerConfig() |
||||
const currentCompilerUrl = isUrl ? currentVersion : urlFromVersion(currentVersion) |
||||
const compilerConfig = { |
||||
currentCompilerUrl, |
||||
evmVersion, |
||||
optimize, |
||||
usingWorker: canUseWorker(currentVersion), |
||||
runs |
||||
} |
||||
const deployCb = async (file: any, contractAddress: any) => { |
||||
const compilerData = await testTab.call('compilerArtefacts', 'getCompilerAbstract', file) |
||||
await testTab.call('compilerArtefacts', 'addResolvedContract', contractAddress, compilerData) |
||||
} |
||||
testTab.testRunner.runTestSources( |
||||
runningTests, |
||||
compilerConfig, |
||||
(result: any) => testCallback(result), |
||||
(_err: any, result: any, cb: any) => resultsCallback(_err, result, cb), |
||||
deployCb, |
||||
(error: any, result: any) => { |
||||
updateFinalResult(error, result, testFilePath) |
||||
callback(error) |
||||
}, (url: any, cb: any) => { |
||||
return testTab.contentImport.resolveAndSave(url).then((result: any) => cb(null, result)).catch((error: any) => cb(error.message)) |
||||
}, { testFilePath } |
||||
) |
||||
}).catch((error: any) => { |
||||
console.log(error) |
||||
if (error) return // eslint-disable-line
|
||||
}) |
||||
} |
||||
|
||||
const runTests = () => { |
||||
areTestsRunning = true |
||||
hasBeenStopped.current = false |
||||
readyTestsNumber = 0 |
||||
setReadyTestsNumber(readyTestsNumber) |
||||
runningTestsNumber = selectedTests.current.length |
||||
setRunningTestsNumber(runningTestsNumber) |
||||
setDisableStopButton(false) |
||||
clearResults() |
||||
setProgressBarHidden(false) |
||||
const tests = selectedTests.current |
||||
if (!tests || !tests.length) return |
||||
else setProgressBarHidden(false) |
||||
_paq.push(['trackEvent', 'solidityUnitTesting', 'runTests']) |
||||
eachOfSeries(tests, (value: any, key: any, callback: any) => { |
||||
if (hasBeenStopped.current) return |
||||
runTest(value, callback) |
||||
}) |
||||
} |
||||
|
||||
const updateRunAction = async (currentFile: any = null) => { |
||||
const isSolidityActive = await testTab.appManager.isActive('solidity') |
||||
if (!isSolidityActive || !selectedTests.current?.length) { |
||||
// setDisableRunButton(true)
|
||||
if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) { |
||||
setRunButtonTitle('No solidity file selected') |
||||
} else { |
||||
setRunButtonTitle('The "Solidity Plugin" should be activated') |
||||
} |
||||
} |
||||
} |
||||
|
||||
const stopTests = () => { |
||||
hasBeenStopped.current = true |
||||
setStopButtonLabel('Stopping') |
||||
setDisableStopButton(true) |
||||
setDisableRunButton(true) |
||||
} |
||||
|
||||
const getCurrentSelectedTests = () => { |
||||
const selectedTestsList: TestObject[] = testFiles.filter(testFileObj => testFileObj.checked) |
||||
return selectedTestsList.map(testFileObj => testFileObj.fileName) |
||||
} |
||||
|
||||
const toggleCheckbox = (eChecked: any, index: any) => { |
||||
testFiles[index].checked = eChecked |
||||
setTestFiles(testFiles) |
||||
selectedTests.current = getCurrentSelectedTests() |
||||
if (eChecked) { |
||||
setCheckSelectAll(true) |
||||
setDisableRunButton(false) |
||||
if ((readyTestsNumber === runningTestsNumber || hasBeenStopped.current) && stopButtonLabel.trim() === 'Stop') { |
||||
setRunButtonTitle('Run tests') |
||||
} |
||||
} else if (!selectedTests.current.length) { |
||||
setCheckSelectAll(false) |
||||
setDisableRunButton(true) |
||||
setRunButtonTitle('No test file selected') |
||||
} else setCheckSelectAll(false) |
||||
} |
||||
|
||||
const checkAll = (event: any) => { |
||||
testFiles.forEach((testFileObj) => testFileObj.checked = event.target.checked) |
||||
setTestFiles(testFiles) |
||||
setCheckSelectAll(event.target.checked) |
||||
if (event.target.checked) { |
||||
selectedTests.current = getCurrentSelectedTests() |
||||
setDisableRunButton(false) |
||||
} else { |
||||
selectedTests.current = [] |
||||
setDisableRunButton(true) |
||||
} |
||||
} |
||||
|
||||
const updateTestFileList = () => { |
||||
if (allTests.current?.length) { |
||||
testFiles = allTests.current.map((testFile: any) => { return { 'fileName': testFile, 'checked': true } }) |
||||
setCheckSelectAll(true) |
||||
} |
||||
else |
||||
testFiles = [] |
||||
setTestFiles(testFiles) |
||||
} |
||||
|
||||
return ( |
||||
<div className="px-2" id="testView"> |
||||
<Toaster message={toasterMsg} /> |
||||
<div className="infoBox"> |
||||
<p className="text-lg"> Test your smart contract in Solidity.</p> |
||||
<p> Select directory to load and generate test files.</p> |
||||
<label>Test directory:</label> |
||||
<div> |
||||
<div className="d-flex p-2"> |
||||
<datalist id="utPathList">{ |
||||
pathOptions.map(function (path) { |
||||
return <option key={path}>{path}</option> |
||||
}) |
||||
} |
||||
</datalist> |
||||
<input |
||||
placeholder={defaultPath} |
||||
list="utPathList" |
||||
className="inputFolder custom-select" |
||||
id="utPath" |
||||
data-id="uiPathInput" |
||||
name="utPath" |
||||
value={inputPathValue} |
||||
title="Press 'Enter' to change the path for test files." |
||||
style={{ backgroundImage: "var(--primary)" }} |
||||
onKeyUp={handleTestDirInput} |
||||
onChange={handleEnter} |
||||
/> |
||||
<button |
||||
className="btn border ml-2" |
||||
data-id="testTabGenerateTestFolder" |
||||
title="Create a test folder" |
||||
disabled={disableCreateButton} |
||||
onClick={handleCreateFolder} |
||||
> |
||||
Create |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<div className="d-flex p-2"> |
||||
<button |
||||
className="btn border w-50" |
||||
data-id="testTabGenerateTestFile" |
||||
title="Generate sample test file." |
||||
disabled={disableGenerateButton} |
||||
onClick={() => { |
||||
testTabLogic.generateTestFile((err:any) => { if (err) setToasterMsg(err)}) |
||||
updateForNewCurrent() |
||||
}} |
||||
> |
||||
Generate |
||||
</button> |
||||
<a className="btn border text-decoration-none pr-0 d-flex w-50 ml-2" title="Check out documentation." target="__blank" href="https://remix-ide.readthedocs.io/en/latest/unittesting.html#test-directory"> |
||||
<label className="btn p-1 ml-2 m-0">How to use...</label> |
||||
</a> |
||||
</div> |
||||
<div className="d-flex p-2"> |
||||
<button id="runTestsTabRunAction" title={runButtonTitle} data-id="testTabRunTestsTabRunAction" className="w-50 btn btn-primary" disabled={disableRunButton} onClick={runTests}> |
||||
<span className="fas fa-play ml-2"></span> |
||||
<label className="labelOnBtn btn btn-primary p-1 ml-2 m-0">Run</label> |
||||
</button> |
||||
<button id="runTestsTabStopAction" data-id="testTabRunTestsTabStopAction" className="w-50 pl-2 ml-2 btn btn-secondary" disabled={disableStopButton} title="Stop running tests" onClick={stopTests}> |
||||
<span className="fas fa-stop ml-2"></span> |
||||
<label className="labelOnBtn btn btn-secondary p-1 ml-2 m-0" id="runTestsTabStopActionLabel">{stopButtonLabel}</label> |
||||
</button> |
||||
</div> |
||||
<div className="d-flex align-items-center mx-3 pb-2 mt-2 border-bottom"> |
||||
<input id="checkAllTests" |
||||
type="checkbox" |
||||
data-id="testTabCheckAllTests" |
||||
onClick={checkAll} |
||||
checked={checkSelectAll} |
||||
onChange={() => { }} // eslint-disable-line
|
||||
/> |
||||
<label className="text-nowrap pl-2 mb-0" htmlFor="checkAllTests"> Select all </label> |
||||
</div> |
||||
<div className="testList py-2 mt-0 border-bottom">{testFiles?.length ? testFiles.map((testFileObj: any, index) => { |
||||
const elemId = `singleTest${testFileObj.fileName}` |
||||
return ( |
||||
<div className="d-flex align-items-center py-1" key={index}> |
||||
<input className="singleTest" id={elemId} onChange={(e) => toggleCheckbox(e.target.checked, index)} type="checkbox" checked={testFileObj.checked} /> |
||||
<label className="singleTestLabel text-nowrap pl-2 mb-0" htmlFor={elemId}>{testFileObj.fileName}</label> |
||||
</div> |
||||
) |
||||
}) |
||||
: "No test file available"} </div> |
||||
<div className="align-items-start flex-column mt-2 mx-3 mb-0"> |
||||
<span className='text-info h6' hidden={progressBarHidden}>Progress: {readyTestsNumber} finished (of {runningTestsNumber})</span> |
||||
<label className="text-warning h6" data-id="testTabTestsExecutionStopped" hidden={testsExecutionStoppedHidden}>The test execution has been stopped</label> |
||||
<label className="text-danger h6" data-id="testTabTestsExecutionStoppedError" hidden={testsExecutionStoppedErrorHidden}>The test execution has been stopped because of error(s) in your test file</label> |
||||
</div> |
||||
<div id="solidityUnittestsOutput" data-id="testTabSolidityUnitTestsOutput">{testsOutput}</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default SolidityUnitTesting |
@ -0,0 +1,20 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"jsx": "react-jsx", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true, |
||||
"forceConsistentCasingInFileNames": true, |
||||
"strict": true, |
||||
"noImplicitReturns": true, |
||||
"noFallthroughCasesInSwitch": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
Loading…
Reference in new issue