Merge branch 'master' of github.com:ethereum/remix-project

pull/1861/head
Joseph Izang 3 years ago
commit 1bca4ce3c6
  1. 4
      apps/remix-ide-e2e/src/tests/importFromGithub.test.ts
  2. 4
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  3. 46
      apps/remix-ide/src/app/files/fileManager.ts
  4. 2
      apps/remix-ide/src/app/panels/file-panel.js
  5. 113
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  6. 1
      apps/remix-ide/tsconfig.json
  7. 2
      libs/remix-core-plugin/src/lib/compiler-content-imports.ts
  8. 1
      libs/remix-ui/app/src/index.ts
  9. 1
      libs/remix-ui/app/src/lib/remix-app/components/modals/dialogViewPlugin.tsx
  10. 4
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  11. 1
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  12. 6
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  13. 10
      libs/remix-ui/modal-dialog/src/lib/remix-ui-modal-dialog.tsx
  14. 3
      libs/remix-ui/solidity-unit-testing/.eslintrc.json
  15. 14
      libs/remix-ui/solidity-unit-testing/src/lib/logic/testTabLogic.ts
  16. 178
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  17. 6
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  18. 2
      package.json

@ -31,8 +31,6 @@ module.exports = {
'Display Error Message For Invalid GitHub URL Modal': function (browser: NightwatchBrowser) { 'Display Error Message For Invalid GitHub URL Modal': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings')
.clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]') .scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]') .waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
.execute(() => { .execute(() => {
@ -48,8 +46,6 @@ module.exports = {
'Import From Github For Valid URL': function (browser: NightwatchBrowser) { 'Import From Github For Valid URL': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('settings')
.clickLaunchIcon('filePanel')
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]') .scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]') .waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]')
.clearValue('*[data-id="homeTabModalDialogCustomPromptText"]') .clearValue('*[data-id="homeTabModalDialogCustomPromptText"]')

@ -125,9 +125,9 @@ function startRemixd (browser: NightwatchBrowser) {
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.clickLaunchIcon('pluginManager') .clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager *[data-id="pluginManagerComponentActivateButtonremixd"]') .scrollAndClick('#pluginManager *[data-id="pluginManagerComponentActivateButtonremixd"]')
.waitForElementVisible('#modal-footer-ok', 2000) .waitForElementVisible('*[data-id="remixdConnect-modal-footer-ok-react"]', 2000)
.pause(2000) .pause(2000)
.click('#modal-footer-ok') .click('*[data-id="remixdConnect-modal-footer-ok-react"]')
// .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]') // .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]')
} }

@ -5,9 +5,9 @@ import async from 'async'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry' import Registry from '../state/registry'
const EventEmitter = require('events') import { EventEmitter } from 'events'
import { RemixAppManager } from '../../../../../libs/remix-ui/plugin-manager/src/types'
const toaster = require('../ui/tooltip') const toaster = require('../ui/tooltip')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const helper = require('../../lib/helper.js') const helper = require('../../lib/helper.js')
/* /*
@ -37,6 +37,18 @@ const createError = (err) => {
} }
class FileManager extends Plugin { class FileManager extends Plugin {
mode: string
openedFiles: any
events: EventEmitter
editor: any
_components: any
appManager: RemixAppManager
_deps: any
getCurrentFile: () => any
getFile: (path: any) => Promise<unknown>
getFolder: (path: any) => Promise<unknown>
setFile: (path: any, data: any) => Promise<unknown>
switchFile: (path: any) => Promise<void>
constructor (editor, appManager) { constructor (editor, appManager) {
super(profile) super(profile)
this.mode = 'browser' this.mode = 'browser'
@ -70,7 +82,7 @@ class FileManager extends Plugin {
* @param {string} path path of the file/directory * @param {string} path path of the file/directory
* @param {string} message message to display if path doesn't exist. * @param {string} message message to display if path doesn't exist.
*/ */
async _handleExists (path, message) { async _handleExists (path: string, message?:string) {
const exists = await this.exists(path) const exists = await this.exists(path)
if (!exists) { if (!exists) {
@ -96,7 +108,7 @@ class FileManager extends Plugin {
* @param {string} path path of the file/directory * @param {string} path path of the file/directory
* @param {string} message message to display if path is not a directory. * @param {string} message message to display if path is not a directory.
*/ */
async _handleIsDir (path, message) { async _handleIsDir (path: string, message?: string) {
const isDir = await this.isDirectory(path) const isDir = await this.isDirectory(path)
if (!isDir) { if (!isDir) {
@ -305,13 +317,19 @@ class FileManager extends Plugin {
if (isFile) { if (isFile) {
if (newPathExists) { if (newPathExists) {
modalDialogCustom.alert('File already exists.') this.call('modal', 'alert', {
id: 'fileManagerAlert',
message: 'File already exists'
})
return return
} }
return provider.rename(oldPath, newPath, false) return provider.rename(oldPath, newPath, false)
} else { } else {
if (newPathExists) { if (newPathExists) {
modalDialogCustom.alert('Folder already exists.') this.call('modal', 'alert', {
id: 'fileManagerAlert',
message: 'Directory already exists'
})
return return
} }
return provider.rename(oldPath, newPath, true) return provider.rename(oldPath, newPath, true)
@ -612,7 +630,7 @@ class FileManager extends Plugin {
this.events.emit('noFileSelected') this.events.emit('noFileSelected')
} }
async openFile (file) { async openFile (file?: string) {
if (!file) { if (!file) {
this.emit('noFileSelected') this.emit('noFileSelected')
this.events.emit('noFileSelected') this.events.emit('noFileSelected')
@ -639,7 +657,7 @@ class FileManager extends Plugin {
// TODO: Only keep `this.emit` (issue#2210) // TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file) this.emit('currentFileChanged', file)
this.events.emit('currentFileChanged', file) this.events.emit('currentFileChanged', file)
resolve() resolve(true)
} }
}) })
}) })
@ -698,7 +716,7 @@ class FileManager extends Plugin {
dirPaths.push(item) dirPaths.push(item)
resolve(dirPaths) resolve(dirPaths)
} }
return new Promise((resolve, reject) => { resolve() }) return new Promise((resolve, reject) => { resolve(true) })
}) })
Promise.all(promises).then(() => { resolve(dirPaths) }) Promise.all(promises).then(() => { resolve(dirPaths) })
}) })
@ -760,9 +778,15 @@ class FileManager extends Plugin {
helper.createNonClashingName(file, self._deps.filesProviders[fileProvider], helper.createNonClashingName(file, self._deps.filesProviders[fileProvider],
(error, name) => { (error, name) => {
if (error) { if (error) {
modalDialogCustom.alert('Unexpected error loading the file ' + error) this.call('modal', 'alert', {
id: 'fileManagerAlert',
message: 'Unexpected error loading file ' + file + ': ' + error
})
} else if (helper.checkSpecialChars(name)) { } else if (helper.checkSpecialChars(name)) {
modalDialogCustom.alert('Special characters are not allowed') this.call('modal', 'alert', {
id: 'fileManagerAlert',
message: 'Special characters are not allowed in file names.'
})
} else { } else {
try { try {
self._deps.filesProviders[fileProvider].set(name, filesSet[file].content) self._deps.filesProviders[fileProvider].set(name, filesSet[file].content)

@ -5,7 +5,7 @@ import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line import { FileSystemProvider } from '@remix-ui/workspace' // eslint-disable-line
import Registry from '../state/registry' import Registry from '../state/registry'
const { RemixdHandle } = require('../files/remixd-handle.js') import { RemixdHandle } from '../plugins/remixd-handle'
const { GitHandle } = require('../files/git-handle.js') const { GitHandle } = require('../files/git-handle.js')
const { HardhatHandle } = require('../files/hardhat-handle.js') const { HardhatHandle } = require('../files/hardhat-handle.js')
const { SlitherHandle } = require('../files/slither-handle.js') const { SlitherHandle } = require('../files/slither-handle.js')

@ -1,24 +1,13 @@
/* eslint-disable no-unused-vars */
import React, { useRef, useState, useEffect } from 'react' // eslint-disable-line
import isElectron from 'is-electron' import isElectron from 'is-electron'
import { WebsocketPlugin } from '@remixproject/engine-web' import { WebsocketPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json' import * as packageJson from '../../../../../package.json'
import { version as remixdVersion } from '../../../../../libs/remixd/package.json' import { version as remixdVersion } from '../../../../../libs/remixd/package.json'
var yo = require('yo-yo') import { PluginManager } from '@remixproject/engine'
var modalDialog = require('../ui/modaldialog') import { AppModal, AlertModal } from '@remix-ui/app'
var modalDialogCustom = require('../ui/modal-dialog-custom') import { CopyToClipboard } from '@remix-ui/clipboard'
var copyToClipboard = require('../ui/copy-to-clipboard')
var csjs = require('csjs-inject')
var css = csjs`
.dialog {
display: flex;
flex-direction: column;
}
.dialogParagraph {
margin-bottom: 2em;
word-break: break-word;
}
`
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
const profile = { const profile = {
@ -32,29 +21,37 @@ const profile = {
version: packageJson.version version: packageJson.version
} }
enum State {
ok,
cancel,
new
}
export class RemixdHandle extends WebsocketPlugin { export class RemixdHandle extends WebsocketPlugin {
localhostProvider: any
appManager: PluginManager
state: State
constructor (localhostProvider, appManager) { constructor (localhostProvider, appManager) {
super(profile) super(profile)
this.localhostProvider = localhostProvider this.localhostProvider = localhostProvider
this.appManager = appManager this.appManager = appManager
} }
deactivate () { async deactivate () {
if (super.socket) super.deactivate() if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342 // this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
if (this.appManager.actives.includes('hardhat')) this.appManager.deactivatePlugin('hardhat') if (this.appManager.isActive('hardhat')) this.appManager.deactivatePlugin('hardhat')
if (this.appManager.actives.includes('slither')) this.appManager.deactivatePlugin('slither') if (this.appManager.isActive('slither')) this.appManager.deactivatePlugin('slither')
this.localhostProvider.close((error) => { this.localhostProvider.close((error) => {
if (error) console.log(error) if (error) console.log(error)
}) })
} }
activate () { async activate () {
this.connectToLocalhost() await this.connectToLocalhost()
} }
async canceled () { async canceled () {
// await this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
await this.appManager.deactivatePlugin('remixd') await this.appManager.deactivatePlugin('remixd')
} }
@ -65,23 +62,25 @@ export class RemixdHandle extends WebsocketPlugin {
* @param {String} txHash - hash of the transaction * @param {String} txHash - hash of the transaction
*/ */
async connectToLocalhost () { async connectToLocalhost () {
const connection = (error) => { const connection = (error?:any) => {
if (error) { if (error) {
console.log(error) console.log(error)
modalDialogCustom.alert( const alert:AlertModal = {
'Cannot connect to the remixd daemon. ' + id: 'connectionAlert',
'Please make sure you have the remixd running in the background.' message: 'Cannot connect to the remixd daemon. Please make sure you have the remixd running in the background.'
) }
this.call('modal', 'alert', alert)
this.canceled() this.canceled()
} else { } else {
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
clearInterval(intervalId) clearInterval(intervalId)
console.log(error) console.log(error)
modalDialogCustom.alert( const alert:AlertModal = {
'Connection to remixd terminated. ' + id: 'connectionAlert',
'Please make sure remixd is still running in the background.' message: 'Connection to remixd terminated.Please make sure remixd is still running in the background.'
) }
this.call('modal', 'alert', alert)
this.canceled() this.canceled()
} }
}, 3000) }, 3000)
@ -96,12 +95,13 @@ export class RemixdHandle extends WebsocketPlugin {
this.deactivate() this.deactivate()
} else if (!isElectron()) { } else if (!isElectron()) {
// warn the user only if he/she is in the browser context // warn the user only if he/she is in the browser context
modalDialog( this.state = State.new
'Connect to localhost', const mod:AppModal = {
remixdDialog(), id: 'remixdConnect',
{ title: 'Connect to localhost',
label: 'Connect', message: remixdDialog(),
fn: () => { okFn: () => {
this.state = State.ok
try { try {
this.localhostProvider.preInit() this.localhostProvider.preInit()
super.activate() super.activate()
@ -115,15 +115,18 @@ export class RemixdHandle extends WebsocketPlugin {
} catch (error) { } catch (error) {
connection(error) connection(error)
} }
}
}, },
{ cancelFn: async () => {
label: 'Cancel', this.state = State.cancel
fn: () => { await this.canceled()
this.canceled() },
okLabel: 'Connect',
cancelLabel: 'Cancel',
hideFn: async () => {
if (this.state === State.new) await this.canceled()
} }
} }
) await this.call('modal', 'modal', mod)
} else { } else {
try { try {
super.activate() super.activate()
@ -137,31 +140,31 @@ export class RemixdHandle extends WebsocketPlugin {
function remixdDialog () { function remixdDialog () {
const commandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>' const commandText = 'remixd -s <path-to-the-shared-folder> -u <remix-ide-instance-URL>'
return yo` return (<>
<div class=${css.dialog}> <div className=''>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.<br/><br/> Access your local file system from Remix IDE using <a target="_blank" href="https://www.npmjs.com/package/@remix-project/remixd">Remixd NPM package</a>.<br/><br/>
Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a>. Remixd needs to be running in the background to load the files in localhost workspace. For more info, please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a>.
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
If you are just looking for the remixd command, here it is: If you are just looking for the remixd command, here it is:
<br><br><b>${commandText}</b> <br></br><br></br><b>${commandText}</b>
<span class="">${copyToClipboard(() => commandText)}</span> <CopyToClipboard data-id='remixdCopyCommand' content={commandText}></CopyToClipboard>
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
When connected, a session will be started between <em>${window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>. When connected, a session will be started between <em>${window.location.origin}</em> and your local file system at <i>ws://127.0.0.1:65520</i>.
The shared folder will be in the "File Explorers" workspace named "localhost". The shared folder will be in the "File Explorers" workspace named "localhost".
<br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a> <br/>Read more about other <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#ports-usage">Remixd ports usage</a>
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
This feature is still in Alpha. We recommend to keep a backup of the shared folder. This feature is still in Alpha. We recommend to keep a backup of the shared folder.
</div> </div>
<div class=${css.dialogParagraph}> <div className='mb-2 text-break'>
<h6 class="text-danger"> <h6 className="text-danger">
Before using, make sure remixd version is latest i.e. <b>${remixdVersion}</b> Before using, make sure remixd version is latest i.e. <b>${remixdVersion}</b>
<br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a> <br></br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a>
</h6> </h6>
</div> </div>
</div> </div>
` </>)
} }

@ -7,6 +7,7 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"types": ["node", "jest"], "types": ["node", "jest"],
"module": "es6", "module": "es6",
"resolveJsonModule": true
}, },
"files": [ "files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",

@ -146,6 +146,8 @@ export class CompilerImports extends Plugin {
const splitted = /([^/]+)\/(.*)$/g.exec(url) const splitted = /([^/]+)\/(.*)$/g.exec(url)
const possiblePaths = ['localhost/installed_contracts/' + url] const possiblePaths = ['localhost/installed_contracts/' + url]
// pick remix-tests library contracts from '.deps'
if (url.startsWith('remix_')) possiblePaths.push('localhost/.deps/remix-tests/' + url)
if (splitted) possiblePaths.push('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2]) if (splitted) possiblePaths.push('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2])
possiblePaths.push('localhost/node_modules/' + url) possiblePaths.push('localhost/node_modules/' + url)
if (splitted) possiblePaths.push('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2]) if (splitted) possiblePaths.push('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2])

@ -2,3 +2,4 @@ export { default as RemixApp } from './lib/remix-app/remix-app'
export { dispatchModalContext } from './lib/remix-app/context/context' export { dispatchModalContext } from './lib/remix-app/context/context'
export { ModalProvider } from './lib/remix-app/context/provider' export { ModalProvider } from './lib/remix-app/context/provider'
export { AppModal } from './lib/remix-app/interface/index' export { AppModal } from './lib/remix-app/interface/index'
export { AlertModal } from './lib/remix-app/interface/index'

@ -7,7 +7,6 @@ const DialogViewPlugin = () => {
const app = useContext(AppContext) const app = useContext(AppContext)
useEffect(() => { useEffect(() => {
console.log(modal, app)
app.modal.setDispatcher({ modal, alert, toast }) app.modal.setDispatcher({ modal, alert, toast })
}, []) }, [])
return <></> return <></>

@ -10,10 +10,10 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState)
const modal = (data: AppModal) => { const modal = (data: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue } = data const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn } = data
dispatch({ dispatch({
type: modalActionTypes.setModal, type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue } payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn }
}) })
} }

@ -12,6 +12,7 @@ export interface AppModal {
cancelFn: () => void, cancelFn: () => void,
modalType?: ModalTypes, modalType?: ModalTypes,
defaultValue?: string defaultValue?: string
hideFn?: () => void
} }
export interface AlertModal { export interface AlertModal {

@ -18,7 +18,8 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
cancelLabel: modalList[0].cancelLabel, cancelLabel: modalList[0].cancelLabel,
cancelFn: modalList[0].cancelFn, cancelFn: modalList[0].cancelFn,
modalType: modalList[0].modalType, modalType: modalList[0].modalType,
defaultValue: modalList[0].defaultValue defaultValue: modalList[0].defaultValue,
hideFn: modalList[0].hideFn
} }
modalList = modalList.slice() modalList = modalList.slice()
@ -28,6 +29,9 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
return { ...state, modals: modalList } return { ...state, modals: modalList }
} }
case modalActionTypes.handleHideModal: case modalActionTypes.handleHideModal:
if (state.focusModal.hideFn) {
state.focusModal.hideFn()
}
state.focusModal = { ...state.focusModal, hide: true, message: null } state.focusModal = { ...state.focusModal, hide: true, message: null }
return { ...state } return { ...state }

@ -12,12 +12,15 @@ export const ModalDialog = (props: ModalDialogProps) => {
const [state, setState] = useState({ const [state, setState] = useState({
toggleBtn: true toggleBtn: true
}) })
const calledHideFunctionOnce = useRef<boolean>()
const modal = useRef(null) const modal = useRef(null)
const handleHide = () => { const handleHide = () => {
props.handleHide() if (!calledHideFunctionOnce.current) { props.handleHide() }
calledHideFunctionOnce.current = true
} }
useEffect(() => { useEffect(() => {
calledHideFunctionOnce.current = props.hide
modal.current.focus() modal.current.focus()
}, [props.hide]) }, [props.hide])
@ -32,13 +35,10 @@ export const ModalDialog = (props: ModalDialogProps) => {
} }
if (modal.current) { if (modal.current) {
modal.current.addEventListener('blur', handleBlur) modal.current.addEventListener('blur', handleBlur)
}
return () => { return () => {
if (modal.current) {
modal.current.removeEventListener('blur', handleBlur) modal.current.removeEventListener('blur', handleBlur)
} }
}
}
}, [modal.current]) }, [modal.current])
const modalKeyEvent = (keyCode) => { const modalKeyEvent = (keyCode) => {

@ -1,9 +1,6 @@
{ {
"extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"], "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"], "ignorePatterns": ["!**/*"],
"rules": {
"@typescript-eslint/no-explicit-any": "off"
},
"overrides": [ "overrides": [
{ {
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],

@ -5,6 +5,7 @@ export class TestTabLogic {
fileManager fileManager
currentPath currentPath
helper helper
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor (fileManager: any, helper: any) { constructor (fileManager: any, helper: any) {
this.fileManager = fileManager this.fileManager = fileManager
this.helper = helper this.helper = helper
@ -26,7 +27,7 @@ export class TestTabLogic {
if (!path || !(/\S/.test(path))) return if (!path || !(/\S/.test(path))) return
path = this.helper.removeMultipleSlashes(path) path = this.helper.removeMultipleSlashes(path)
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0]) const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0])
fileProvider.exists(path).then((res: any) => { fileProvider.exists(path).then((res: boolean) => {
if (!res) fileProvider.createDir(path) if (!res) fileProvider.createDir(path)
}) })
} }
@ -35,11 +36,12 @@ export class TestTabLogic {
// Checking to ignore the value which contains only whitespaces // Checking to ignore the value which contains only whitespaces
if (!path || !(/\S/.test(path))) return if (!path || !(/\S/.test(path))) return
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0]) const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0])
const res = await fileProvider.exists(path, (e: any, res: any) => { return res }) const res = await fileProvider.exists(path, (e: Error, res: boolean) => { return res })
return res return res
} }
generateTestFile (errorCb: any) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
generateTestFile (errorCb:any) {
let fileName = this.fileManager.currentFile() let fileName = this.fileManager.currentFile()
const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol' const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol'
if (!hasCurrent) fileName = this.currentPath + '/newFile.sol' if (!hasCurrent) fileName = this.currentPath + '/newFile.sol'
@ -47,7 +49,7 @@ export class TestTabLogic {
if (!fileProvider) return if (!fileProvider) return
const splittedFileName = fileName.split('/') const splittedFileName = fileName.split('/')
const fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1] const fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1]
this.helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', (error: any, newFile: any) => { this.helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', (error: Error, newFile: string) => {
if (error) return errorCb('Failed to create file. ' + newFile + ' ' + error) if (error) return errorCb('Failed to create file. ' + newFile + ' ' + error)
const isFileCreated = fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName)) const isFileCreated = fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName))
if (!isFileCreated) return errorCb('Failed to create test file ' + newFile) if (!isFileCreated) return errorCb('Failed to create test file ' + newFile)
@ -72,7 +74,7 @@ export class TestTabLogic {
let files = [] let files = []
try { try {
if (await this.fileManager.exists(this.currentPath)) files = await this.fileManager.readdir(this.currentPath) if (await this.fileManager.exists(this.currentPath)) files = await this.fileManager.readdir(this.currentPath)
} catch (e: any) { } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
throw e.message throw e.message
} }
for (const file in files) { for (const file in files) {
@ -84,7 +86,7 @@ export class TestTabLogic {
// @todo(#2758): If currently selected file is compiled and compilation result is available, // @todo(#2758): If currently selected file is compiled and compilation result is available,
// 'contractName' should be <compiledContractName> + '_testSuite' // 'contractName' should be <compiledContractName> + '_testSuite'
generateTestContractSample (hasCurrent: any, fileToImport: any, contractName = 'testSuite') { generateTestContractSample (hasCurrent: boolean, fileToImport: string, contractName = 'testSuite') {
let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport)) let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport))
if (relative === '') relative = '.' if (relative === '') relative = '.'
const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// <import file to test>' const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// <import file to test>'

@ -1,62 +1,84 @@
import React, { useState, useRef, useEffect } from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, ReactElement } from 'react' // eslint-disable-line
import { eachOfSeries } from 'async' // eslint-disable-line import { eachOfSeries } from 'async' // eslint-disable-line
import type Web3 from 'web3'
import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity' import { canUseWorker, urlFromVersion } from '@remix-project/remix-solidity'
import { Renderer } from '@remix-ui/renderer' // eslint-disable-line import { Renderer } from '@remix-ui/renderer' // eslint-disable-line
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
import { format } from 'util' import { format } from 'util'
import './css/style.css' import './css/style.css'
const _paq = (window as any)._paq = (window as any)._paq || [] // eslint-disable-line const _paq = (window as any)._paq = (window as any)._paq || [] // eslint-disable-line @typescript-eslint/no-explicit-any
/* eslint-disable-next-line */
export interface SolidityUnitTestingProps { }
interface TestObject { interface TestObject {
fileName: string fileName: string
checked: boolean checked: boolean
} }
export const SolidityUnitTesting = (props: Record<string, any>) => { interface TestResultInterface {
type: string
value: any // eslint-disable-line @typescript-eslint/no-explicit-any
time?: number
context?: string
errMsg?: string
filename: string
assertMethod?: string
returned?: string | number
expected?: string | number
location?: string
hhLogs?: []
web3?: Web3
debugTxHash?: string
rendered?: boolean
}
interface FinalResult {
totalPassing: number,
totalFailing: number,
totalTime: any, // eslint-disable-line @typescript-eslint/no-explicit-any
errors: any[], // eslint-disable-line @typescript-eslint/no-explicit-any
}
export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-disable-line @typescript-eslint/no-explicit-any
const { helper, testTab, initialPath } = props const { helper, testTab, initialPath } = props
const { testTabLogic } = testTab const { testTabLogic } = testTab
const [toasterMsg, setToasterMsg] = useState('') const [toasterMsg, setToasterMsg] = useState<string>('')
const [disableCreateButton, setDisableCreateButton] = useState(true) const [disableCreateButton, setDisableCreateButton] = useState<boolean>(true)
const [disableGenerateButton, setDisableGenerateButton] = useState(false) const [disableGenerateButton, setDisableGenerateButton] = useState<boolean>(false)
const [disableStopButton, setDisableStopButton] = useState(true) const [disableStopButton, setDisableStopButton] = useState<boolean>(true)
const [disableRunButton, setDisableRunButton] = useState(false) const [disableRunButton, setDisableRunButton] = useState<boolean>(false)
const [runButtonTitle, setRunButtonTitle] = useState('Run tests') const [runButtonTitle, setRunButtonTitle] = useState<string>('Run tests')
const [stopButtonLabel, setStopButtonLabel] = useState('Stop') const [stopButtonLabel, setStopButtonLabel] = useState<string>('Stop')
const [checkSelectAll, setCheckSelectAll] = useState(true) const [checkSelectAll, setCheckSelectAll] = useState<boolean>(true)
const [testsOutput, setTestsOutput] = useState<Element[]>([]) const [testsOutput, setTestsOutput] = useState<ReactElement[]>([])
const [testsExecutionStoppedHidden, setTestsExecutionStoppedHidden] = useState(true) const [testsExecutionStoppedHidden, setTestsExecutionStoppedHidden] = useState<boolean>(true)
const [progressBarHidden, setProgressBarHidden] = useState(true) const [progressBarHidden, setProgressBarHidden] = useState<boolean>(true)
const [testsExecutionStoppedErrorHidden, setTestsExecutionStoppedErrorHidden] = useState(true) const [testsExecutionStoppedErrorHidden, setTestsExecutionStoppedErrorHidden] = useState<boolean>(true)
let [testFiles, setTestFiles] = useState<TestObject[]>([]) // eslint-disable-line let [testFiles, setTestFiles] = useState<TestObject[]>([]) // eslint-disable-line
const [pathOptions, setPathOptions] = useState(['']) const [pathOptions, setPathOptions] = useState<string[]>([''])
const [inputPathValue, setInputPathValue] = useState('tests') const [inputPathValue, setInputPathValue] = useState<string>('tests')
let [readyTestsNumber, setReadyTestsNumber] = useState(0) // eslint-disable-line let [readyTestsNumber, setReadyTestsNumber] = useState<number>(0) // eslint-disable-line
let [runningTestsNumber, setRunningTestsNumber] = useState(0) // eslint-disable-line let [runningTestsNumber, setRunningTestsNumber] = useState<number>(0) // eslint-disable-line
const hasBeenStopped = useRef(false) const hasBeenStopped = useRef<boolean>(false)
const isDebugging = useRef(false) const isDebugging = useRef<boolean>(false)
const allTests: any = useRef([]) const allTests = useRef<string[]>([])
const selectedTests: any = useRef([]) const selectedTests = useRef<string[]>([])
const currentErrors: any = useRef([]) const currentErrors:any = useRef([]) // eslint-disable-line @typescript-eslint/no-explicit-any
const defaultPath = 'tests' const defaultPath = 'tests'
let areTestsRunning = false let areTestsRunning = false
let runningTestFileName: any let runningTestFileName: string
const filesContent: any = {} const filesContent: Record<string, Record<string, string>> = {}
const testsResultByFilename: Record<string, any> = {} const testsResultByFilename: Record<string, Record<string, Record<string, any>>> = {} // eslint-disable-line @typescript-eslint/no-explicit-any
const trimTestDirInput = (input: string) => { const trimTestDirInput = (input: string) => {
if (input.includes('/')) return input.split('/').map(e => e.trim()).join('/') if (input.includes('/')) return input.split('/').map(e => e.trim()).join('/')
@ -71,7 +93,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
setTestsExecutionStoppedErrorHidden(true) setTestsExecutionStoppedErrorHidden(true)
} }
const updateForNewCurrent = async (file = null) => { const updateForNewCurrent = async (file: string | null = null) => {
// Ensure that when someone clicks on compilation error and that opens a new file // 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 // Test result, which is compilation error in this case, is not cleared
if (currentErrors.current) { if (currentErrors.current) {
@ -92,7 +114,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
selectedTests.current = [...allTests.current] selectedTests.current = [...allTests.current]
updateTestFileList() updateTestFileList()
if (!areTestsRunning) await updateRunAction(file) if (!areTestsRunning) await updateRunAction(file)
} catch (e: any) { } catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
console.log(e) console.log(e)
setToasterMsg(e) setToasterMsg(e)
} }
@ -132,17 +154,17 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
}) })
testTab.fileManager.events.on('noFileSelected', () => { }) // eslint-disable-line testTab.fileManager.events.on('noFileSelected', () => { }) // eslint-disable-line
testTab.fileManager.events.on('currentFileChanged', async (file: any, provider: any) => await updateForNewCurrent(file)) testTab.fileManager.events.on('currentFileChanged', async (file: string) => await updateForNewCurrent(file))
}, []) // eslint-disable-line }, []) // eslint-disable-line
const updateDirList = (path: string) => { const updateDirList = (path: string) => {
testTabLogic.dirList(path).then((options: any) => { testTabLogic.dirList(path).then((options: string[]) => {
setPathOptions(options) setPathOptions(options)
}) })
} }
const handleTestDirInput = async (e: any) => { const handleTestDirInput = async (e: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
let testDirInput = trimTestDirInput(e.target.value) let testDirInput = trimTestDirInput(e.target.value)
testDirInput = helper.removeMultipleSlashes(testDirInput) testDirInput = helper.removeMultipleSlashes(testDirInput)
if (testDirInput !== '/') testDirInput = helper.removeTrailingSlashes(testDirInput) if (testDirInput !== '/') testDirInput = helper.removeTrailingSlashes(testDirInput)
@ -183,7 +205,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} }
} }
const handleEnter = async (e: any) => { const handleEnter = async (e: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
let inputPath = e.target.value let inputPath = e.target.value
inputPath = helper.removeMultipleSlashes(trimTestDirInput(inputPath)) inputPath = helper.removeMultipleSlashes(trimTestDirInput(inputPath))
setInputPathValue(inputPath) setInputPathValue(inputPath)
@ -210,18 +232,18 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
setPathOptions(pathOptions) setPathOptions(pathOptions)
} }
const cleanFileName = (fileName: any, testSuite: any) => { const cleanFileName = (fileName: string, testSuite: string) => {
return fileName ? fileName.replace(/\//g, '_').replace(/\./g, '_') + testSuite : fileName return fileName ? fileName.replace(/\//g, '_').replace(/\./g, '_') + testSuite : fileName
} }
const startDebug = async (txHash: any, web3: any) => { const startDebug = async (txHash: string, web3: Web3) => {
isDebugging.current = true isDebugging.current = true
if (!await testTab.appManager.isActive('debugger')) await testTab.appManager.activatePlugin('debugger') if (!await testTab.appManager.isActive('debugger')) await testTab.appManager.activatePlugin('debugger')
testTab.call('menuicons', 'select', 'debugger') testTab.call('menuicons', 'select', 'debugger')
testTab.call('debugger', 'debug', txHash, web3) testTab.call('debugger', 'debug', txHash, web3)
} }
const printHHLogs = (logsArr: any, testName: any) => { const printHHLogs = (logsArr: Record<string, any>[], testName: string) => { // eslint-disable-line @typescript-eslint/no-explicit-any
let finalLogs = `<b>${testName}:</b>\n` let finalLogs = `<b>${testName}:</b>\n`
for (const log of logsArr) { for (const log of logsArr) {
let formattedLog let formattedLog
@ -262,9 +284,9 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} }
} }
const renderContract = (filename: any, contract: any, index: number, withoutLabel = false) => { const renderContract = (filename: string, contract: string|null, index: number, withoutLabel = false) => {
if (withoutLabel) { if (withoutLabel) {
const contractCard: any = ( const contractCard: ReactElement = (
<div id={runningTestFileName} data-id="testTabSolidityUnitTestsOutputheader" className="pt-1"> <div id={runningTestFileName} data-id="testTabSolidityUnitTestsOutputheader" className="pt-1">
<span className="font-weight-bold">{contract ? contract : ''} ({filename})</span> <span className="font-weight-bold">{contract ? contract : ''} ({filename})</span>
</div> </div>
@ -291,20 +313,20 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
</div>) </div>)
} }
// show contract and file name with label // show contract and file name with label
const ContractCard: any = ( const ContractCard: ReactElement = (
<div id={runningTestFileName} data-id="testTabSolidityUnitTestsOutputheader" className="pt-1"> <div id={runningTestFileName} data-id="testTabSolidityUnitTestsOutputheader" className="pt-1">
{label}<span className="font-weight-bold">{contract} ({filename})</span> {label}<span className="font-weight-bold">{contract} ({filename})</span>
</div> </div>
) )
setTestsOutput(prevCards => { setTestsOutput(prevCards => {
const index = prevCards.findIndex((card: any) => card.props.id === runningTestFileName) const index = prevCards.findIndex((card: ReactElement) => card.props.id === runningTestFileName)
prevCards[index] = ContractCard prevCards[index] = ContractCard
return prevCards return prevCards
}) })
} }
const renderTests = (tests: any, contract: any, filename: any) => { const renderTests = (tests: TestResultInterface[], contract: string, filename: string) => {
const index = tests.findIndex((test: any) => test.type === 'testFailure') const index = tests.findIndex((test: TestResultInterface) => test.type === 'testFailure')
// show filename and contract // show filename and contract
renderContract(filename, contract, index) renderContract(filename, contract, index)
// show tests // show tests
@ -321,7 +343,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} }
if (test.type === 'testPass') { if (test.type === 'testPass') {
if (test.hhLogs && test.hhLogs.length) printHHLogs(test.hhLogs, test.value) if (test.hhLogs && test.hhLogs.length) printHHLogs(test.hhLogs, test.value)
const testPassCard: any = ( const testPassCard: ReactElement = (
<div <div
id={runningTestFileName} id={runningTestFileName}
data-id="testTabSolidityUnitTestsOutputheader" data-id="testTabSolidityUnitTestsOutputheader"
@ -339,10 +361,10 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} else if (test.type === 'testFailure') { } else if (test.type === 'testFailure') {
if (test.hhLogs && test.hhLogs.length) printHHLogs(test.hhLogs, test.value) if (test.hhLogs && test.hhLogs.length) printHHLogs(test.hhLogs, test.value)
if (!test.assertMethod) { if (!test.assertMethod) {
const testFailCard1: any = (<div const testFailCard1: ReactElement = (<div
className="bg-light mb-2 px-2 testLog d-flex flex-column text-danger border-0" className="bg-light mb-2 px-2 testLog d-flex flex-column text-danger border-0"
id={"UTContext" + test.context} id={"UTContext" + test.context}
onClick={() => highlightLocation(test.location, test.filename)} onClick={() => { if(test.location) highlightLocation(test.location, test.filename)}}
> >
<div className="d-flex my-1 align-items-start justify-content-between"> <div className="d-flex my-1 align-items-start justify-content-between">
<span> {test.value}</span> <span> {test.value}</span>
@ -356,10 +378,10 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
const preposition = test.assertMethod === 'equal' || test.assertMethod === 'notEqual' ? 'to' : '' const preposition = test.assertMethod === 'equal' || test.assertMethod === 'notEqual' ? 'to' : ''
const method = test.assertMethod === 'ok' ? '' : test.assertMethod const method = test.assertMethod === 'ok' ? '' : test.assertMethod
const expected = test.assertMethod === 'ok' ? '\'true\'' : test.expected const expected = test.assertMethod === 'ok' ? '\'true\'' : test.expected
const testFailCard2: any = (<div const testFailCard2: ReactElement = (<div
className="bg-light mb-2 px-2 testLog d-flex flex-column text-danger border-0" className="bg-light mb-2 px-2 testLog d-flex flex-column text-danger border-0"
id={"UTContext" + test.context} id={"UTContext" + test.context}
onClick={() => highlightLocation(test.location, test.filename)} onClick={() => { if(test.location) highlightLocation(test.location, test.filename)}}
> >
<div className="d-flex my-1 align-items-start justify-content-between"> <div className="d-flex my-1 align-items-start justify-content-between">
<span> {test.value}</span> <span> {test.value}</span>
@ -396,7 +418,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
for (const contract of contracts) { for (const contract of contracts) {
if (contract && contract !== 'summary' && contract !== 'errors') { if (contract && contract !== 'summary' && contract !== 'errors') {
runningTestFileName = cleanFileName(filename, contract) runningTestFileName = cleanFileName(filename, contract)
const tests = fileTestsResult[contract] const tests = fileTestsResult[contract] as TestResultInterface[]
if (tests?.length) { if (tests?.length) {
renderTests(tests, contract, filename) renderTests(tests, contract, filename)
} else { } else {
@ -406,18 +428,18 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} else if (contract === 'errors' && fileTestsResult['errors']) { } else if (contract === 'errors' && fileTestsResult['errors']) {
const errors = fileTestsResult['errors'] const errors = fileTestsResult['errors']
if (errors && errors.errors) { if (errors && errors.errors) {
errors.errors.forEach((err: any) => { errors.errors.forEach((err: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
const errorCard: any = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} /> const errorCard: ReactElement = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} />
setTestsOutput(prevCards => ([...prevCards, errorCard])) setTestsOutput(prevCards => ([...prevCards, errorCard]))
}) })
} else if (errors && Array.isArray(errors) && (errors[0].message || errors[0].formattedMessage)) { } else if (errors && Array.isArray(errors) && (errors[0].message || errors[0].formattedMessage)) {
errors.forEach((err) => { errors.forEach((err) => {
const errorCard: any = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} /> const errorCard: ReactElement = <Renderer message={err.formattedMessage || err.message} plugin={testTab} opt={{ type: err.severity, errorType: err.type }} />
setTestsOutput(prevCards => ([...prevCards, errorCard])) setTestsOutput(prevCards => ([...prevCards, errorCard]))
}) })
} else if (errors && !errors.errors && !Array.isArray(errors)) { } else if (errors && !errors.errors && !Array.isArray(errors)) {
// To track error like this: https://github.com/ethereum/remix/pull/1438 // 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' }} /> const errorCard: ReactElement = <Renderer message={errors.formattedMessage || errors.message} plugin={testTab} opt={{ type: 'error' }} />
setTestsOutput(prevCards => ([...prevCards, errorCard])) setTestsOutput(prevCards => ([...prevCards, errorCard]))
} }
} }
@ -425,7 +447,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
// show summary // show summary
const testSummary = fileTestsResult['summary'] const testSummary = fileTestsResult['summary']
if (testSummary && testSummary.filename && !testSummary.rendered) { if (testSummary && testSummary.filename && !testSummary.rendered) {
const summaryCard: any = (<div className="d-flex alert-secondary mb-3 p-3 flex-column"> const summaryCard: ReactElement = (<div className="d-flex alert-secondary mb-3 p-3 flex-column">
<span className="font-weight-bold">Result for {testSummary.filename}</span> <span className="font-weight-bold">Result for {testSummary.filename}</span>
<span className="text-success">Passed: {testSummary.passed}</span> <span className="text-success">Passed: {testSummary.passed}</span>
<span className="text-danger">Failed: {testSummary.failed}</span> <span className="text-danger">Failed: {testSummary.failed}</span>
@ -437,7 +459,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} }
} }
const testCallback = (result: any) => { const testCallback = (result: Record<string, any>) => { // eslint-disable-line @typescript-eslint/no-explicit-any
if (result.filename) { if (result.filename) {
if (!testsResultByFilename[result.filename]) { if (!testsResultByFilename[result.filename]) {
testsResultByFilename[result.filename] = {} testsResultByFilename[result.filename] = {}
@ -455,7 +477,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} }
} }
const resultsCallback = (_err: any, result: any, cb: any) => { const resultsCallback = (_err: any, result: any, cb: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
// total stats for the test // total stats for the test
// result.passingNum // result.passingNum
// result.failureNum // result.failureNum
@ -463,7 +485,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
cb() cb()
} }
const updateFinalResult = (_errors: any, result: any, filename: any) => { const updateFinalResult = (_errors: any, result: FinalResult|null, filename: string) => { // eslint-disable-line @typescript-eslint/no-explicit-any
++readyTestsNumber ++readyTestsNumber
setReadyTestsNumber(readyTestsNumber) setReadyTestsNumber(readyTestsNumber)
if (!result && (_errors && (_errors.errors || (Array.isArray(_errors) && (_errors[0].message || _errors[0].formattedMessage))))) { if (!result && (_errors && (_errors.errors || (Array.isArray(_errors) && (_errors[0].message || _errors[0].formattedMessage))))) {
@ -500,14 +522,14 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} }
} }
const runTest = (testFilePath: any, callback: any) => { const runTest = (testFilePath: string, callback: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
isDebugging.current = false isDebugging.current = false
if (hasBeenStopped.current) { if (hasBeenStopped.current) {
updateFinalResult(null, null, null) updateFinalResult(null, null, testFilePath)
return return
} }
testTab.fileManager.readFile(testFilePath).then((content: any) => { testTab.fileManager.readFile(testFilePath).then((content: string) => {
const runningTests: any = {} const runningTests: Record<string, Record<string, string>> = {}
runningTests[testFilePath] = { content } runningTests[testFilePath] = { content }
filesContent[testFilePath] = { content } filesContent[testFilePath] = { content }
const { currentVersion, evmVersion, optimize, runs, isUrl } = testTab.compileTab.getCurrentCompilerConfig() const { currentVersion, evmVersion, optimize, runs, isUrl } = testTab.compileTab.getCurrentCompilerConfig()
@ -519,24 +541,24 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
usingWorker: canUseWorker(currentVersion), usingWorker: canUseWorker(currentVersion),
runs runs
} }
const deployCb = async (file: any, contractAddress: any) => { const deployCb = async (file: string, contractAddress: string) => {
const compilerData = await testTab.call('compilerArtefacts', 'getCompilerAbstract', file) const compilerData = await testTab.call('compilerArtefacts', 'getCompilerAbstract', file)
await testTab.call('compilerArtefacts', 'addResolvedContract', contractAddress, compilerData) await testTab.call('compilerArtefacts', 'addResolvedContract', contractAddress, compilerData)
} }
testTab.testRunner.runTestSources( testTab.testRunner.runTestSources(
runningTests, runningTests,
compilerConfig, compilerConfig,
(result: any) => testCallback(result), (result: Record<string, any>) => testCallback(result), // eslint-disable-line @typescript-eslint/no-explicit-any
(_err: any, result: any, cb: any) => resultsCallback(_err, result, cb), (_err: any, result: any, cb: any) => resultsCallback(_err, result, cb), // eslint-disable-line @typescript-eslint/no-explicit-any
deployCb, deployCb,
(error: any, result: any) => { (error: any, result: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
updateFinalResult(error, result, testFilePath) updateFinalResult(error, result, testFilePath)
callback(error) callback(error)
}, (url: any, cb: any) => { }, (url: string, cb: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
return testTab.contentImport.resolveAndSave(url).then((result: any) => cb(null, result)).catch((error: any) => cb(error.message)) return testTab.contentImport.resolveAndSave(url).then((result: any) => cb(null, result)).catch((error: Error) => cb(error.message)) // eslint-disable-line @typescript-eslint/no-explicit-any
}, { testFilePath } }, { testFilePath }
) )
}).catch((error: any) => { }).catch((error: Error) => {
console.log(error) console.log(error)
if (error) return // eslint-disable-line if (error) return // eslint-disable-line
}) })
@ -552,17 +574,17 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
setDisableStopButton(false) setDisableStopButton(false)
clearResults() clearResults()
setProgressBarHidden(false) setProgressBarHidden(false)
const tests = selectedTests.current const tests: string[] = selectedTests.current
if (!tests || !tests.length) return if (!tests || !tests.length) return
else setProgressBarHidden(false) else setProgressBarHidden(false)
_paq.push(['trackEvent', 'solidityUnitTesting', 'runTests']) _paq.push(['trackEvent', 'solidityUnitTesting', 'runTests'])
eachOfSeries(tests, (value: any, key: any, callback: any) => { eachOfSeries(tests, (value: string, key: string, callback: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
if (hasBeenStopped.current) return if (hasBeenStopped.current) return
runTest(value, callback) runTest(value, callback)
}) })
} }
const updateRunAction = async (currentFile: any = null) => { const updateRunAction = async (currentFile: any = null) => { // eslint-disable-line @typescript-eslint/no-explicit-any
const isSolidityActive = await testTab.appManager.isActive('solidity') const isSolidityActive = await testTab.appManager.isActive('solidity')
if (!isSolidityActive || !selectedTests.current?.length) { if (!isSolidityActive || !selectedTests.current?.length) {
// setDisableRunButton(true) // setDisableRunButton(true)
@ -586,7 +608,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
return selectedTestsList.map(testFileObj => testFileObj.fileName) return selectedTestsList.map(testFileObj => testFileObj.fileName)
} }
const toggleCheckbox = (eChecked: any, index: any) => { const toggleCheckbox = (eChecked: boolean, index: number) => {
testFiles[index].checked = eChecked testFiles[index].checked = eChecked
setTestFiles(testFiles) setTestFiles(testFiles)
selectedTests.current = getCurrentSelectedTests() selectedTests.current = getCurrentSelectedTests()
@ -603,7 +625,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
} else setCheckSelectAll(false) } else setCheckSelectAll(false)
} }
const checkAll = (event: any) => { const checkAll = (event: any) => { // eslint-disable-line @typescript-eslint/no-explicit-any
testFiles.forEach((testFileObj) => testFileObj.checked = event.target.checked) testFiles.forEach((testFileObj) => testFileObj.checked = event.target.checked)
setTestFiles(testFiles) setTestFiles(testFiles)
setCheckSelectAll(event.target.checked) setCheckSelectAll(event.target.checked)
@ -618,7 +640,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
const updateTestFileList = () => { const updateTestFileList = () => {
if (allTests.current?.length) { if (allTests.current?.length) {
testFiles = allTests.current.map((testFile: any) => { return { 'fileName': testFile, 'checked': true } }) testFiles = allTests.current.map((testFile: string) => { return { 'fileName': testFile, 'checked': true } })
setCheckSelectAll(true) setCheckSelectAll(true)
} }
else else
@ -674,7 +696,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
title="Generate sample test file." title="Generate sample test file."
disabled={disableGenerateButton} disabled={disableGenerateButton}
onClick={async () => { onClick={async () => {
testTabLogic.generateTestFile((err:any) => { if (err) setToasterMsg(err)}) testTabLogic.generateTestFile((err:any) => { if (err) setToasterMsg(err)}) // eslint-disable-line @typescript-eslint/no-explicit-any
await updateForNewCurrent() await updateForNewCurrent()
}} }}
> >
@ -704,7 +726,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => {
/> />
<label className="text-nowrap pl-2 mb-0" htmlFor="checkAllTests"> Select all </label> <label className="text-nowrap pl-2 mb-0" htmlFor="checkAllTests"> Select all </label>
</div> </div>
<div className="testList py-2 mt-0 border-bottom">{testFiles?.length ? testFiles.map((testFileObj: any, index) => { <div className="testList py-2 mt-0 border-bottom">{testFiles?.length ? testFiles.map((testFileObj: TestObject, index) => {
const elemId = `singleTest${testFileObj.fileName}` const elemId = `singleTest${testFileObj.fileName}`
return ( return (
<div className="d-flex align-items-center py-1" key={index}> <div className="d-flex align-items-center py-1" key={index}>

@ -672,9 +672,11 @@ const fetchDirectoryContent = (state: BrowserState, payload: { fileTree, path: s
return files return files
} }
} else { } else {
if (payload.path === state.mode || payload.path === '/') { if (payload.path === '/') {
const files = normalize(payload.fileTree, payload.path, payload.type)
return { [state.mode]: files }
} else if (payload.path === state.mode) {
let files = normalize(payload.fileTree, payload.path, payload.type) let files = normalize(payload.fileTree, payload.path, payload.type)
files = _.merge(files, state[state.mode].files[state.mode]) files = _.merge(files, state[state.mode].files[state.mode])
if (deletePath) delete files[deletePath] if (deletePath) delete files[deletePath]
return { [state.mode]: files } return { [state.mode]: files }

@ -45,7 +45,7 @@
"workspace-schematic": "nx workspace-schematic", "workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs", "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs", "publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs",

Loading…
Cancel
Save