diff --git a/apps/remix-ide-e2e/src/tests/script-runner.test.ts b/apps/remix-ide-e2e/src/tests/script-runner.test.ts new file mode 100644 index 0000000000..493466983d --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/script-runner.test.ts @@ -0,0 +1,116 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', false) + }, + 'Should load default script runner': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('scriptRunnerBridge') + .waitForElementVisible('[data-id="sr-loaded-default"]') + .waitForElementVisible('[data-id="dependency-ethers-^5"]') + .waitForElementVisible('[data-id="sr-toggle-ethers6"]') + }, + 'Should load script runner ethers6': function (browser: NightwatchBrowser) { + browser + .click('[data-id="sr-toggle-ethers6"]') + .waitForElementVisible('[data-id="sr-loaded-ethers6"]') + .waitForElementPresent('[data-id="dependency-ethers-^6"]') + }, + 'Should have config file in .remix/script.config.json': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .waitForElementVisible('[data-path=".remix"]') + .waitForElementVisible('[data-id="treeViewDivDraggableItem.remix/script.config.json"]') + .openFile('.remix/script.config.json') + }, + 'check config file content': function (browser: NightwatchBrowser) { + browser + .getEditorValue((content) => { + console.log(JSON.parse(content)) + const parsed = JSON.parse(content) + browser.assert.ok(parsed.defaultConfig === 'ethers6', 'config file content is correct') + }) + }, + 'execute ethers6 script': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="treeViewUltreeViewMenu"]') // make sure we create the file at the root folder + .addFile('deployWithEthersJs.js', { content: deployWithEthersJs }) + .pause(1000) + .click('[data-id="treeViewDivtreeViewItemcontracts"]') + .openFile('contracts/2_Owner.sol') + .clickLaunchIcon('solidity') + .click('*[data-id="compilerContainerCompileBtn"]') + .executeScriptInTerminal('remix.execute(\'deployWithEthersJs.js\')') + .waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000) + }, + 'switch workspace it should be default again': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .pause(2000) + .waitForElementVisible('*[data-id="workspacesMenuDropdown"]') + .click('*[data-id="workspacesMenuDropdown"]') + .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="create-semaphore"]') + .scrollAndClick('*[data-id="create-semaphore"]') + .modalFooterOKClick('TemplatesSelection') + .clickLaunchIcon('scriptRunnerBridge') + .waitForElementVisible('[data-id="sr-loaded-default"]') + .waitForElementVisible('[data-id="dependency-ethers-^5"]') + .waitForElementVisible('[data-id="sr-toggle-ethers6"]') + }, + 'switch to default workspace that should be on ethers6': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .switchWorkspace('default_workspace') + .clickLaunchIcon('scriptRunnerBridge') + .waitForElementVisible('[data-id="sr-loaded-ethers6"]') + .waitForElementPresent('[data-id="dependency-ethers-^6"]') + } +} + + +const deployWithEthersJs = ` +import { ethers } from 'ethers' + +/** + * Deploy the given contract + * @param {string} contractName name of the contract to deploy + * @param {Array} args list of constructor' parameters + * @param {Number} accountIndex account index from the exposed account + * @return {Contract} deployed contract + * + */ +const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => { + + console.log(\`deploying \${contractName}\`) + // Note that the script needs the ABI which is generated from the compilation artifact. + // Make sure contract is compiled and artifacts are generated + const artifactsPath = \`contracts/artifacts/\${contractName}.json\` // Change this for different path + + const metadata = JSON.parse(await remix.call('fileManager', 'getFile', artifactsPath)) + // 'web3Provider' is a remix global variable object + + const signer = await (new ethers.BrowserProvider(web3Provider)).getSigner(accountIndex) + + const factory = new ethers.ContractFactory(metadata.abi, metadata.data.bytecode.object, signer) + + const contract = await factory.deploy(...args) + + // The contract is NOT deployed yet; we must wait until it is mined + await contract.waitForDeployment() + return contract +} + +(async () => { + try { + const contract = await deploy('Owner', []) + + console.log(\`address: \${await contract.getAddress()}\`) + } catch (e) { + console.log(e.message) + } +})()` \ No newline at end of file diff --git a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json index b3f77de301..5870ea16e0 100644 --- a/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json +++ b/apps/remix-ide/src/app/tabs/locales/en/remixUiTabs.json @@ -7,6 +7,7 @@ "remixUiTabs.tooltipText6": "Enable RemixAI Copilot [BETA]", "remixUiTabs.tooltipText7": "Disable RemixAI Copilot [BETA]", "remixUiTabs.tooltipText8": "Remix AI Tools Documentation", + "remixUiTabs.tooltipText9": "Configure scripting dependencies", "remixUiTabs.tooltipTextDisabledCopilot": "To use RemixAI Copilot, choose a .sol file", "remixUiTabs.zoomOut": "Zoom out", "remixUiTabs.zoomIn": "Zoom in" diff --git a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx index a5424e99ee..9c7374e0b4 100644 --- a/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx +++ b/libs/remix-ui/scriptrunner/src/lib/script-runner-ui.tsx @@ -96,7 +96,7 @@ export const ScriptRunnerUI = (props: ScriptRunnerUIProps) => {

Dependencies:

    {config.dependencies.map((dep, depIndex) => ( -
  • +
  • {dep.name} (v{dep.version})
  • ))} diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index b4f59c61e3..7572db392c 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -1,7 +1,7 @@ import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' import { CustomTooltip } from '@remix-ui/helper' import { Plugin } from '@remixproject/engine' -import React, {useState, useRef, useEffect, useReducer} from 'react' // eslint-disable-line +import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import { FormattedMessage } from 'react-intl' import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import './remix-ui-tabs.css' @@ -51,19 +51,19 @@ const initialTabsState: ITabsState = { const tabsReducer = (state: ITabsState, action: ITabsAction) => { switch (action.type) { - case 'SELECT_INDEX': - return { - ...state, - currentExt: action.ext, - selectedIndex: action.payload - } - case 'SET_FILE_DECORATIONS': - return { - ...state, - fileDecorations: action.payload as fileDecoration[] - } - default: - return state + case 'SELECT_INDEX': + return { + ...state, + currentExt: action.ext, + selectedIndex: action.payload + } + case 'SET_FILE_DECORATIONS': + return { + ...state, + fileDecorations: action.payload as fileDecoration[] + } + default: + return state } } @@ -86,10 +86,10 @@ export const TabsUI = (props: TabsUIProps) => { } }, [tabsState.selectedIndex]) - const getAI = async() => { + const getAI = async () => { try { return await props.plugin.call('settings', 'getCopilotSetting') - } catch (e){ + } catch (e) { return false } } @@ -226,14 +226,32 @@ export const TabsUI = (props: TabsUIProps) => { + {(tabsState.currentExt === 'ts' || tabsState.currentExt === 'js') -
    + && + + + }> + } +
    - {tabsState.currentExt === 'sol'? ( + {tabsState.currentExt === 'sol' ? ( ) : ( @@ -265,7 +283,7 @@ export const TabsUI = (props: TabsUIProps) => { tooltipId="overlay-tooltip-copilot" tooltipText={ - { tabsState.currentExt === 'sol'? ( + {tabsState.currentExt === 'sol' ? ( !ai_switch ? ( ) : () @@ -279,7 +297,7 @@ export const TabsUI = (props: TabsUIProps) => { data-id="remix_ai_switch" id='remix_ai_switch' className="btn ai-switch text-ai pl-2 pr-0 py-0" - disabled={!(tabsState.currentExt === 'sol' )} + disabled={!(tabsState.currentExt === 'sol')} onClick={async () => { await props.plugin.call('settings', 'updateCopilotChoice', !ai_switch) setAI_switch(!ai_switch) @@ -291,7 +309,7 @@ export const TabsUI = (props: TabsUIProps) => {
    -
    +
    }> props.onZoomOut()}>