diff --git a/apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts b/apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts index 2ebc4cd1f1..ed8fa54cbd 100644 --- a/apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts +++ b/apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts @@ -21,7 +21,7 @@ function checkFilter (browser: NightwatchBrowser, filter: string, test: string, const filterClass = '[data-id="terminalInputSearch"]' browser.setValue(filterClass, filter, function () { browser.execute(function () { - return document.querySelector('[data-id="terminalJournal"]').innerHTML === test + return document.querySelector('[data-id="terminalJournal"]').innerHTML === test || '' }, [], function (result) { browser.clearValue(filterClass).setValue(filterClass, '', function () { if (!result.value) { diff --git a/apps/remix-ide-e2e/src/commands/executeScriptInTerminal.ts b/apps/remix-ide-e2e/src/commands/executeScriptInTerminal.ts new file mode 100644 index 0000000000..0638011291 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/executeScriptInTerminal.ts @@ -0,0 +1,20 @@ +import { NightwatchBrowser } from 'nightwatch' +import EventEmitter from 'events' + +class ExecuteScriptInTerminal extends EventEmitter { + command (this: NightwatchBrowser, script: string): NightwatchBrowser { + this.api + .clearEditableContent('*[data-id="terminalCliInput"]') + .click('*[data-id="terminalCli"]') + .setValue('*[data-id="terminalCliInput"]', [this.api.Keys.CONTROL, 'a', this.api.Keys.DELETE]) + .sendKeys('*[data-id="terminalCliInput"]', script) + .sendKeys('*[data-id="terminalCliInput"]', this.api.Keys.ENTER) + .sendKeys('*[data-id="terminalCliInput"]', this.api.Keys.ENTER) + .perform(() => { + this.emit('complete') + }) + return this + } +} + +module.exports = ExecuteScriptInTerminal diff --git a/apps/remix-ide-e2e/src/commands/openFile.ts b/apps/remix-ide-e2e/src/commands/openFile.ts index f8191e874f..ad5ec6d06e 100644 --- a/apps/remix-ide-e2e/src/commands/openFile.ts +++ b/apps/remix-ide-e2e/src/commands/openFile.ts @@ -5,6 +5,7 @@ class OpenFile extends EventEmitter { command (this: NightwatchBrowser, name: string) { this.api.perform((done) => { openFile(this.api, name, () => { + console.log('doing done') done() this.emit('complete') }) @@ -22,9 +23,12 @@ function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction) browser.element('css selector', '[data-id="verticalIconsKindfilePanel"] img[data-id="selected"]', (result) => { if (result.status === 0) { done() - } else browser.clickLaunchIcon('filePanel').perform(done) + } else browser.clickLaunchIcon('filePanel').perform(done()) }) - } else browser.clickLaunchIcon('filePanel').perform(done) + } else { + browser.clickLaunchIcon('filePanel').perform(done()) + } + }) }) .waitForElementVisible('li[data-id="treeViewLitreeViewItem' + name + '"', 60000) diff --git a/apps/remix-ide-e2e/src/commands/rightClickCustom.ts b/apps/remix-ide-e2e/src/commands/rightClickCustom.ts new file mode 100644 index 0000000000..f98ff7ea93 --- /dev/null +++ b/apps/remix-ide-e2e/src/commands/rightClickCustom.ts @@ -0,0 +1,37 @@ +import EventEmitter from 'events' +import { NightwatchBrowser } from 'nightwatch' + +class RightClickCustom extends EventEmitter { + command (this: NightwatchBrowser, cssSelector: string) { + this.api.perform((done) => { + rightClickCustom(this.api, cssSelector, () => { + done() + this.emit('complete') + }) + }) + return this + } +} + +function rightClickCustom (browser: NightwatchBrowser, cssSelector: string, callback: VoidFunction) { + browser.execute(function (cssSelector: string) { + const element: any = document.querySelector(cssSelector) + const evt = element.ownerDocument.createEvent('MouseEvents') + const RIGHT_CLICK_BUTTON_CODE = 2 + + evt.initMouseEvent('contextmenu', true, true, + element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, + false, false, false, RIGHT_CLICK_BUTTON_CODE, null) + if (Object.prototype.hasOwnProperty.call(document, 'createEventObject')) { + // dispatch for IE + return element.fireEvent('onclick', evt) + } else { + // dispatch for firefox + others + return !element.dispatchEvent(evt) + } + }, [cssSelector], function () { + callback() + }) +} + +module.exports = RightClickCustom diff --git a/apps/remix-ide-e2e/src/commands/validateValueInput.ts b/apps/remix-ide-e2e/src/commands/validateValueInput.ts index dd7f653ad7..61e90a80dc 100644 --- a/apps/remix-ide-e2e/src/commands/validateValueInput.ts +++ b/apps/remix-ide-e2e/src/commands/validateValueInput.ts @@ -7,7 +7,7 @@ class ValidateValueInput extends EventEmitter { browser.perform((done) => { browser.clearValue(selector) .pause(2000) - .setValue(selector, valueTosSet) + .setValue(selector, valueTosSet).pause(2000) .execute(function (selector) { const elem = document.querySelector(selector) as HTMLInputElement return elem.value diff --git a/apps/remix-ide-e2e/src/examples/editor-test-contracts.ts b/apps/remix-ide-e2e/src/examples/editor-test-contracts.ts new file mode 100644 index 0000000000..86992350e4 --- /dev/null +++ b/apps/remix-ide-e2e/src/examples/editor-test-contracts.ts @@ -0,0 +1,173 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const testContract = { + name: 'contracts/test.sol', + content: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.7.0 <0.9.0; + + import "contracts/base.sol"; + import "contracts/import1.sol"; + + contract test is base { + string public publicstring; + string private privatestring; + string internal internalstring; + + struct TestBookDefinition { + string title; + string author; + uint book_id; + } + TestBookDefinition public mybook; + enum MyEnum{ SMALL, MEDIUM, LARGE } + event MyEvent(uint abc); + importcontract importedcontract; + + modifier costs(uint price) { + if (msg.value >= price) { + _; + } + } + constructor(){ + + } + + function testing() public view { + + } + + function myprivatefunction() private { + + } + + function myinternalfunction() internal { + + } + + function myexternalfunction() external { + + } + }`} + + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const baseContract = { + name: 'contracts/base.sol', + content: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.7.0 <0.9.0; + + import "contracts/baseofbase.sol"; + + contract base is baseofbase { + event BaseEvent(address indexed _from, uint _value); + enum BaseEnum{ SMALL, MEDIUM, LARGE } + struct Book { + string title; + string author; + uint book_id; + } + Book public book; + }`} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const baseOfBaseContract = { + name: 'contracts/baseofbase.sol', + content: ` + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.7.0 <0.9.0; + + contract baseofbase { + + struct BaseBook { + string title; + string author; + uint book_id; + } + BaseBook public basebook; + + string private basestring; + string internal internalbasestring; + + function privatebase() private { + + } + + function internalbasefunction() internal { + + } + + function publicbasefunction() public { + + } + function externalbasefunction() external { + } + }`} + +const import1Contract = { + name: 'contracts/import1.sol', + content: ` + // SPDX-License-Identifier: MIT + pragma solidity >=0.7.0 <0.9.0; + + import "contracts/importbase.sol"; + import "contracts/secondimport.sol"; + +contract importcontract is importbase { + struct ImportedBook { + string title; + string author; + uint book_id; + } + ImportedBook public importedbook; + + string private importprivatestring; + string internal internalimportstring; + string public importpublicstring; + + function privateimport() private { + + } + + function internalimport() internal { + + } + + function publicimport() public { + + } + + function externalimport() external { + } +}`} + +const importbase = { + name: 'contracts/importbase.sol', + content: ` + // SPDX-License-Identifier: MIT +pragma solidity >=0.7.0 <0.9.0; + +contract importbase { + string public importbasestring; +} +`} + +const secondimport = { + name: 'contracts/secondimport.sol', + content: ` + // SPDX-License-Identifier: MIT +pragma solidity >=0.7.0 <0.9.0; + +contract secondimport { + string public secondimportstring; +} +`} + +export default { + testContract, + baseContract, + baseOfBaseContract, + import1Contract, + importbase, + secondimport +} \ No newline at end of file diff --git a/apps/remix-ide-e2e/src/tests/compiler_api.test.ts b/apps/remix-ide-e2e/src/tests/compiler_api.test.ts index 0646b83f80..17d8a96815 100644 --- a/apps/remix-ide-e2e/src/tests/compiler_api.test.ts +++ b/apps/remix-ide-e2e/src/tests/compiler_api.test.ts @@ -21,7 +21,7 @@ module.exports = { 'Should compile using "compileWithParamaters" API #group1': function (browser: NightwatchBrowser) { browser .addFile('test_jsCompile.js', { content: jsCompile }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', '"languageversion":"0.6.8+commit.0bbfe453"', 60000) .click('*[data-id="terminalClearConsole"]') }, @@ -29,7 +29,7 @@ module.exports = { 'Should compile using "compileWithParamaters" API with optimization On #group2': function (browser: NightwatchBrowser) { browser .addFile('test_jsCompileWithOptimization.js', { content: jsCompileWithOptimization }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', '\\"optimizer\\":{\\"enabled\\":true,\\"runs\\":300}', 60000) .click('*[data-id="terminalClearConsole"]') }, @@ -37,7 +37,7 @@ module.exports = { 'Should compile using "compileWithParamaters" API with optimization off check default runs #group3': function (browser: NightwatchBrowser) { browser .addFile('test_jsCompileWithOptimizationDefault.js', { content: jsCompileWithOptimizationDefault }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', '\\"optimizer\\":{\\"enabled\\":false,\\"runs\\":200}', 60000) .click('*[data-id="terminalClearConsole"]') }, @@ -45,7 +45,7 @@ module.exports = { 'Should update the compiler configuration with "setCompilerConfig" API #group4': function (browser: NightwatchBrowser) { browser .addFile('test_updateConfiguration.js', { content: updateConfiguration }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(15000) .addFile('test_updateConfiguration.sol', { content: simpleContract }) .verifyContracts(['StorageTestUpdateConfiguration'], { wait: 5000, version: '0.6.8+commit.0bbfe453' }) diff --git a/apps/remix-ide-e2e/src/tests/debugger.test.ts b/apps/remix-ide-e2e/src/tests/debugger.test.ts index c3541d2163..19918de0c4 100644 --- a/apps/remix-ide-e2e/src/tests/debugger.test.ts +++ b/apps/remix-ide-e2e/src/tests/debugger.test.ts @@ -192,7 +192,7 @@ module.exports = { 'Should call the debugger api: getTrace #group4': function (browser: NightwatchBrowser) { browser .addFile('test_jsGetTrace.js', { content: jsGetTrace }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(3000) .waitForElementContainsText('*[data-id="terminalJournal"]', '{"gas":"0x575f","return":"0x0000000000000000000000000000000000000000000000000000000000000000","structLogs":', 60000) }, @@ -200,7 +200,7 @@ module.exports = { 'Should call the debugger api: debug #group4': function (browser: NightwatchBrowser) { browser .addFile('test_jsDebug.js', { content: jsDebug }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(3000) .clickLaunchIcon('debugger') .waitForElementVisible('*[data-id="slider"]') diff --git a/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts b/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts index d79515020f..8f303bf593 100644 --- a/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts +++ b/apps/remix-ide-e2e/src/tests/defaultLayout.test.ts @@ -42,7 +42,7 @@ module.exports = { browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]') .assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORER') .clickLaunchIcon('filePanel') - .assert.hidden('div[data-id="remixIdeSidePanel"]') + .assert.not.visible('div[data-id="remixIdeSidePanel"]') .clickLaunchIcon('filePanel') .assert.visible('div[data-id="remixIdeSidePanel"]') .assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORER') diff --git a/apps/remix-ide-e2e/src/tests/editor.test.ts b/apps/remix-ide-e2e/src/tests/editor.test.ts index 9a83da9b81..771b59b93a 100644 --- a/apps/remix-ide-e2e/src/tests/editor.test.ts +++ b/apps/remix-ide-e2e/src/tests/editor.test.ts @@ -89,7 +89,7 @@ module.exports = { .addFile('sourcehighlight.js', sourcehighlightScript) .addFile('removeAllSourcehighlightScript.js', removeAllSourcehighlightScript) .openFile('sourcehighlight.js') - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .scrollToLine(32) .waitForElementPresent('.highlightLine33', 60000) .checkElementStyle('.highlightLine33', 'background-color', 'rgb(52, 152, 219)') @@ -105,7 +105,7 @@ module.exports = { browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]') .click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]') .pause(2000) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]') .click('li[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') @@ -119,7 +119,7 @@ module.exports = { browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]') .click('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]') .pause(2000) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') .click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]') .pause(2000) @@ -128,119 +128,6 @@ module.exports = { .waitForElementNotPresent('.highlightLine51', 60000) }, - 'Should display the context view #group2': function (browser: NightwatchBrowser) { - browser - .openFile('contracts') - .openFile('contracts/1_Storage.sol') - .waitForElementVisible('#editorView') - .setEditorValue(storageContractWithError) - .pause(2000) - .execute(() => { - (document.getElementById('editorView') as any).gotoLine(17, 16) - }, [], () => {}) - .waitForElementVisible('.contextview') - .waitForElementContainsText('.contextview .type', 'FunctionDefinition') - .waitForElementContainsText('.contextview .name', 'store') - .execute(() => { - (document.getElementById('editorView') as any).gotoLine(18, 12) - }, [], () => {}) - .waitForElementContainsText('.contextview .type', 'uint256') - .waitForElementContainsText('.contextview .name', 'number') - .click('.contextview [data-action="previous"]') // declaration - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '180') - }) - .click('.contextview [data-action="next"]') // back to the initial state - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '323') - }) - .click('.contextview [data-action="next"]') // next reference - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '489') - }) - .click('.contextview [data-action="gotoref"]') // back to the declaration - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '180') - }) - }, - - 'Should display the context view, loop over "Owner" by switching file #group2': function (browser: NightwatchBrowser) { - browser - .clickLaunchIcon('solidity') - .click('[for="autoCompile"]') // disable auto compile - .openFile('contracts') - .openFile('contracts/3_Ballot.sol') - .waitForElementVisible('#editorView') - .setEditorValue(BallotWithARefToOwner) - .clickLaunchIcon('solidity') - .click('*[data-id="compilerContainerCompileBtn"]') // compile - .pause(6000) - .execute(() => { - (document.getElementById('editorView') as any).gotoLine(14, 6) - }, [], () => {}) - .waitForElementVisible('.contextview') - .waitForElementContainsText('.contextview .type', 'ContractDefinition') - .waitForElementContainsText('.contextview .name', 'Owner') - .click('.contextview [data-action="next"]') - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '1061') - }) - .click('.contextview [data-action="next"]') - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '153') - }) - .currentSelectedFileIs('2_Owner.sol') // make sure the current file has been properly changed - .click('.contextview [data-action="next"]') - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '211') - }) - .click('.contextview [data-action="next"]') - .currentSelectedFileIs('3_Ballot.sol') - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '1061') - }) - .click('.contextview [data-action="gotoref"]') // go to the declaration - .pause(1000) - .execute(() => { - return (document.getElementById('editorView') as any).getCursorPosition() - }, [], (result) => { - console.log('result', result) - browser.assert.equal(result.value, '153') - }) - .end() - } } const aceThemes = { @@ -347,148 +234,4 @@ contract Storage { } }` -const BallotWithARefToOwner = ` - - -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity >=0.7.0 <0.9.0; - -import "./2_Owner.sol"; - -/** - * @title Ballot - * @dev Implements voting process along with vote delegation - */ -contract Ballot { - Owner c; - struct Voter { - uint weight; // weight is accumulated by delegation - bool voted; // if true, that person already voted - address delegate; // person delegated to - uint vote; // index of the voted proposal - } - - struct Proposal { - // If you can limit the length to a certain number of bytes, - // always use one of bytes1 to bytes32 because they are much cheaper - bytes32 name; // short name (up to 32 bytes) - uint voteCount; // number of accumulated votes - } - - address public chairperson; - - mapping(address => Voter) public voters; - - Proposal[] public proposals; - - /** - * @dev Create a new ballot to choose one of 'proposalNames'. - * @param proposalNames names of proposals - */ - constructor(bytes32[] memory proposalNames) { - c = new Owner(); - chairperson = msg.sender; - voters[chairperson].weight = 1; - - for (uint i = 0; i < proposalNames.length; i++) { - // 'Proposal({...})' creates a temporary - // Proposal object and 'proposals.push(...)' - // appends it to the end of 'proposals'. - proposals.push(Proposal({ - name: proposalNames[i], - voteCount: 0 - })); - } - } - - /** - * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. - * @param voter address of voter - */ - function giveRightToVote(address voter) public { - require( - msg.sender == chairperson, - "Only chairperson can give right to vote." - ); - require( - !voters[voter].voted, - "The voter already voted." - ); - require(voters[voter].weight == 0); - voters[voter].weight = 1; - } - - /** - * @dev Delegate your vote to the voter 'to'. - * @param to address to which vote is delegated - */ - function delegate(address to) public { - Voter storage sender = voters[msg.sender]; - require(!sender.voted, "You already voted."); - require(to != msg.sender, "Self-delegation is disallowed."); - - while (voters[to].delegate != address(0)) { - to = voters[to].delegate; - // We found a loop in the delegation, not allowed. - require(to != msg.sender, "Found loop in delegation."); - } - sender.voted = true; - sender.delegate = to; - Voter storage delegate_ = voters[to]; - if (delegate_.voted) { - // If the delegate already voted, - // directly add to the number of votes - proposals[delegate_.vote].voteCount += sender.weight; - } else { - // If the delegate did not vote yet, - // add to her weight. - delegate_.weight += sender.weight; - } - } - - /** - * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. - * @param proposal index of proposal in the proposals array - */ - function vote(uint proposal) public { - Voter storage sender = voters[msg.sender]; - require(sender.weight != 0, "Has no right to vote"); - require(!sender.voted, "Already voted."); - sender.voted = true; - sender.vote = proposal; - - // If 'proposal' is out of the range of the array, - // this will throw automatically and revert all - // changes. - proposals[proposal].voteCount += sender.weight; - } - - /** - * @dev Computes the winning proposal taking all previous votes into account. - * @return winningProposal_ index of winning proposal in the proposals array - */ - function winningProposal() public view - returns (uint winningProposal_) - { - uint winningVoteCount = 0; - for (uint p = 0; p < proposals.length; p++) { - if (proposals[p].voteCount > winningVoteCount) { - winningVoteCount = proposals[p].voteCount; - winningProposal_ = p; - } - } - } - - /** - * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then - * @return winnerName_ the name of the winner - */ - function winnerName() public view - returns (bytes32 winnerName_) - { - winnerName_ = proposals[winningProposal()].name; - } -} -` diff --git a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts new file mode 100644 index 0000000000..8dc7bd4440 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts @@ -0,0 +1,518 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' +import examples from '../examples/editor-test-contracts' + +const autoCompleteLineElement = (name: string) => { + return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` +} + +module.exports = { + '@disabled': true, + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', false) + }, + 'Should add test and base files #group2': function (browser: NightwatchBrowser) { + browser.addFile(examples.testContract.name, examples.testContract) + .addFile(examples.baseContract.name, examples.baseContract) + .addFile(examples.import1Contract.name, examples.import1Contract) + .addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract) + .addFile(examples.secondimport.name, examples.secondimport) + .addFile(examples.importbase.name, examples.importbase) + .openFile(examples.testContract.name) + }, + 'Should put cursor in the () of the function #group2': function (browser: NightwatchBrowser) { + browser.scrollToLine(36) + const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]" + browser.waitForElementVisible('#editorView') + .useXpath() + .click(path).pause(1000) + }, + 'Should complete variable declaration types in a function definition #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('uint25') + }) + .waitForElementPresent(autoCompleteLineElement('uint256')) + .click(autoCompleteLineElement('uint256')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' abc') + .sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow + .sendKeys(', testb') + }) + .waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"')) + .click(autoCompleteLineElement('"TestBookDefinition"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' memo') + }) + .waitForElementPresent(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('memory')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' btextbook') + .sendKeys(this.Keys.ENTER) + .sendKeys(', BaseB') + }) + .waitForElementPresent(autoCompleteLineElement('"BaseBook"')) + .click(autoCompleteLineElement('"BaseBook"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' stor') + }) + .waitForElementPresent(autoCompleteLineElement('storage')) + .click(autoCompleteLineElement('storage')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' localbbook') + }).pause(3000) + }, + 'Should put cursor at the end of function #group2': function (browser: NightwatchBrowser) { + + const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]" + browser + .useXpath() + .click(path).pause(1000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + // right arrow key + sendKeys(this.Keys.ARROW_RIGHT). + sendKeys(this.Keys.ARROW_RIGHT) + }) + }, + 'Should autcomplete address types': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('addre') + }) + .waitForElementPresent(autoCompleteLineElement('address')) + .click(autoCompleteLineElement('address')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' someaddress;') + .sendKeys(this.Keys.ENTER) + }).pause(2000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('someaddress.') + }) + .waitForElementVisible(autoCompleteLineElement('balance')) + .waitForElementVisible(autoCompleteLineElement('send')) + .waitForElementVisible(autoCompleteLineElement('transfer')) + .waitForElementVisible(autoCompleteLineElement('code')) + .click(autoCompleteLineElement('balance')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should autcomplete array types': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('uin') + }) + .waitForElementPresent(autoCompleteLineElement('uint')) + .click(autoCompleteLineElement('uint')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('[] mem') + }) + .waitForElementVisible(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('memory')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' somearray;') + } + ).pause(2000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + .sendKeys('somearray.') + }) + .waitForElementVisible(autoCompleteLineElement('push')) + .waitForElementVisible(autoCompleteLineElement('pop')) + .waitForElementVisible(autoCompleteLineElement('length')) + .click(autoCompleteLineElement('length')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should see and autocomplete second import because it was imported by the first import #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('secondi') + }) + .waitForElementPresent(autoCompleteLineElement('secondimport')) + .click(autoCompleteLineElement('secondimport')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' sec;') + .sendKeys(this.Keys.ENTER) + }).pause(3000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('sec.') + }) + .waitForElementVisible(autoCompleteLineElement('secondimportstring')) + .click(autoCompleteLineElement('secondimportstring')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + + }, + 'Should see and autocomplete imported local class #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('import') + }) + .waitForElementPresent(autoCompleteLineElement('importedcontract')) + .click(autoCompleteLineElement('importedcontract')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('externalimport')) + .waitForElementVisible(autoCompleteLineElement('importbasestring')) + .waitForElementVisible(autoCompleteLineElement('importedbook')) + .waitForElementVisible(autoCompleteLineElement('importpublicstring')) + .waitForElementVisible(autoCompleteLineElement('publicimport')) + // no private + .waitForElementNotPresent(autoCompleteLineElement('importprivatestring')) + .waitForElementNotPresent(autoCompleteLineElement('privateimport')) + // no internal + .waitForElementNotPresent(autoCompleteLineElement('importinternalstring')) + .waitForElementNotPresent(autoCompleteLineElement('internalimport')) + .click(autoCompleteLineElement('importbasestring')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + + }, + 'Should autocomplete derived and local event when not using this. #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('emit base') + }) + .waitForElementVisible(autoCompleteLineElement('BaseEvent')) + .click(autoCompleteLineElement('BaseEvent')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys('msg.sender') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium + .sendKeys('3232') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.ENTER) + }) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('emit MyEv') + }) + .waitForElementVisible(autoCompleteLineElement('MyEvent')) + .click(autoCompleteLineElement('MyEvent')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys('3232') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.ENTER) + }) + }, + + 'Should type and get msg options #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('msg.') + }) + .waitForElementVisible(autoCompleteLineElement('sender')) + .waitForElementVisible(autoCompleteLineElement('data')) + .waitForElementVisible(autoCompleteLineElement('value')) + .waitForElementVisible(autoCompleteLineElement('gas')) + .waitForElementVisible(autoCompleteLineElement('sig')) + .click(autoCompleteLineElement('sender')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('balance')) + .waitForElementVisible(autoCompleteLineElement('code')) + .waitForElementVisible(autoCompleteLineElement('codehash')) + .waitForElementVisible(autoCompleteLineElement('send')) + .waitForElementVisible(autoCompleteLineElement('transfer')) + .click(autoCompleteLineElement('balance')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER) + }) + }, + 'Should bo and get book #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('bo') + }) + .waitForElementVisible(autoCompleteLineElement('book')) + .click(autoCompleteLineElement('book')) + }, + 'Should autcomplete derived struct #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should bo and get basebook #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('base') + }) + .waitForElementVisible(autoCompleteLineElement('basebook')) + .click(autoCompleteLineElement('basebook')) + }, + 'Should autcomplete derived struct from base class #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should block scoped localbbook #group2': function (browser: NightwatchBrowser) { + browser.pause(4000). + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('localb') + }) + .waitForElementVisible(autoCompleteLineElement('localbbook')) + .click(autoCompleteLineElement('localbbook')) + }, + 'Should autcomplete derived struct from block localbbook #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should block scoped btextbook #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('btext') + }) + .waitForElementVisible(autoCompleteLineElement('btextbook')) + .click(autoCompleteLineElement('btextbook')) + }, + 'Should autcomplete derived struct from block btextbook #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should find private and internal local functions #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('my') + }) + .waitForElementVisible(autoCompleteLineElement('myprivatefunction')) + .waitForElementVisible(autoCompleteLineElement('myinternalfunction')) + .waitForElementVisible(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('myinternalfunction')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER) + }) + }, + 'Should find internal functions and var from base and owner #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('intern') + }) + .waitForElementVisible(autoCompleteLineElement('internalbasefunction')) + .waitForElementVisible(autoCompleteLineElement('internalstring')) + .waitForElementVisible(autoCompleteLineElement('internalbasestring')) + // keyword internal + .waitForElementVisible(autoCompleteLineElement('internal keyword')) + .click(autoCompleteLineElement('internalbasefunction')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + + 'Should not find external functions without this. #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('extern') + }) + .waitForElementNotPresent(autoCompleteLineElement('externalbasefunction')) + .waitForElementNotPresent(autoCompleteLineElement('myexternalfunction')) + // keyword internal + .waitForElementVisible(autoCompleteLineElement('external keyword')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + }) + }, + 'Should find external functions using this. #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('this.') + }) + .waitForElementVisible(autoCompleteLineElement('externalbasefunction')) + .waitForElementVisible(autoCompleteLineElement('myexternalfunction')) + }, + 'Should find public functions and vars using this. but not private & other types of nodes #group2': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible(autoCompleteLineElement('"publicbasefunction"')) + .waitForElementVisible(autoCompleteLineElement('"publicstring"')) + .waitForElementVisible(autoCompleteLineElement('"basebook"')) + .waitForElementVisible(autoCompleteLineElement('"mybook"')) + .waitForElementVisible(autoCompleteLineElement('"testing"')) + // but no private functions or vars or other types of nodes + .waitForElementNotPresent(autoCompleteLineElement('"private"')) + .waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"')) + .waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"')) + .waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"')) + .click(autoCompleteLineElement('"publicbasefunction"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should autocomplete local and derived ENUMS #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('BaseEnum.') + }) + .waitForElementVisible(autoCompleteLineElement('SMALL')) + .waitForElementVisible(autoCompleteLineElement('MEDIUM')) + .waitForElementVisible(autoCompleteLineElement('LARGE')) + .click(autoCompleteLineElement('SMALL')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + .sendKeys('MyEnum.') + }) + .waitForElementVisible(autoCompleteLineElement('SMALL')) + .waitForElementVisible(autoCompleteLineElement('MEDIUM')) + .waitForElementVisible(autoCompleteLineElement('LARGE')) + .click(autoCompleteLineElement('SMALL')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + } +} + diff --git a/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts b/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts new file mode 100644 index 0000000000..db86224b11 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts @@ -0,0 +1,236 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +const checkEditorHoverContent = (browser: NightwatchBrowser, path: string, expectedContent: string, offsetLeft: number = 0) => { + browser.useXpath() + .useXpath() + .moveToElement('//body', 0, 0) // always move away from the hover before the next test in case we hover in the same line on a different element + .waitForElementVisible(path) + .moveToElement(path, offsetLeft, 0) + .useCss() + .waitForElementContainsText('.monaco-hover-content', expectedContent).pause(1000) +} + +module.exports = { + '@disabled': true, + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', false) + }, + + 'Should load the test file': function (browser: NightwatchBrowser) { + browser.openFile('contracts') + .openFile('contracts/3_Ballot.sol') + .waitForElementVisible('#editorView') + .setEditorValue(BallotWithARefToOwner) + .pause(4000) // wait for the compiler to finish + .scrollToLine(37) + }, + 'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) { + const path = "//*[contains(text(),'BallotHoverTest')]" + checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest') + checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0') + checkEditorHoverContent(browser, path, '@title Ballot') + }, + 'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) { + const path = "//*[@class='view-line' and contains(.,'chairperson') and contains(.,'address') and contains(.,'public')]//span//span[contains(.,'chairperson')]" + const expectedContent = 'address public chairperson' + checkEditorHoverContent(browser, path, expectedContent) + }, + 'Should show hover over constructor in editor #group1': function (browser: NightwatchBrowser) { + const path: string = "//*[@class='view-line' and contains(.,'constructor') and contains(.,'bytes32') and contains(.,'memory')]//span//span[contains(.,'constructor')]" + const expectedContent = 'Estimated creation cost: infinite gas Estimated code deposit cost:' + checkEditorHoverContent(browser, path, expectedContent) + }, + 'Should show hover over function in editor #group1': function (browser: NightwatchBrowser) { + browser.scrollToLine(58) + const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]" + let expectedContent = 'Estimated execution cost' + checkEditorHoverContent(browser, path, expectedContent) + expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()' + checkEditorHoverContent(browser, path, expectedContent) + expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'" + checkEditorHoverContent(browser, path, expectedContent) + }, + 'Should show hover over var components in editor #group1': function (browser: NightwatchBrowser) { + browser.scrollToLine(37) + let path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'voters')]" + let expectedContent = 'mapping(address => struct BallotHoverTest.Voter) public voters' + checkEditorHoverContent(browser, path, expectedContent, 15) + path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'chairperson')]" + expectedContent = 'address public chairperson' + checkEditorHoverContent(browser, path, expectedContent, 3) + path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'weight')]" + expectedContent = 'uint256 internal weight' + checkEditorHoverContent(browser, path, expectedContent) + }, + 'Should show hover over new contract creation in editor #group1': function (browser: NightwatchBrowser) { + let path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'cowner')]" + let expectedContent = 'contract Owner internal cowner' + checkEditorHoverContent(browser, path, expectedContent, 10) + path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'Owner')]" + expectedContent = 'contract Owner is Owner' + checkEditorHoverContent(browser, path, expectedContent, 10) + }, + 'Should show hover over external class member in editor #group1': function (browser: NightwatchBrowser) { + const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" + let expectedContent = 'function getOwner () external view returns (address internal )' + checkEditorHoverContent(browser, path, expectedContent, 0) + expectedContent = 'contracts/2_Owner.sol' + checkEditorHoverContent(browser, path, expectedContent, 0) + expectedContent = '@dev Return owner address' + checkEditorHoverContent(browser, path, expectedContent, 0) + }, + 'Should show hover over struct definition in editor #group1': function (browser: NightwatchBrowser) { + browser.scrollToLine(5) + const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]" + const expectedContent = 'StructDefinition' + checkEditorHoverContent(browser, path, expectedContent) + } +} + + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +import "./2_Owner.sol"; + +/** + * @title Ballot + * @dev Implements voting process along with vote delegation + */ +contract BallotHoverTest { + Owner cowner; + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + struct Proposal { + // If you can limit the length to a certain number of bytes, + // always use one of bytes1 to bytes32 because they are much cheaper + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + mapping(address => Voter) public voters; + + Proposal[] public proposals; + + /** + * @dev Create a new ballot to choose one of 'proposalNames'. + * @param proposalNames names of proposals + */ + constructor(bytes32[] memory proposalNames) { + cowner = new Owner(); + cowner.getOwner(); + chairperson = msg.sender; + voters[chairperson].weight = 1; + + for (uint i = 0; i < proposalNames.length; i++) { + // 'Proposal({...})' creates a temporary + // Proposal object and 'proposals.push(...)' + // appends it to the end of 'proposals'. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + /** + * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. + * @param voter address of voter + */ + function giveRightToVote(address voter) public { + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /** + * @dev Delegate your vote to the voter 'to'. + * @param to address to which vote is delegated + */ + function delegate(address to) public { + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + require(to != msg.sender, "Self-delegation is disallowed."); + + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + sender.voted = true; + sender.delegate = to; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /** + * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. + * @param proposal index of proposal in the proposals array + */ + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If 'proposal' is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /** + * @dev Computes the winning proposal taking all previous votes into account. + * @return winningProposal_ index of winning proposal in the proposals array + */ + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + /** + * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then + * @return winnerName_ the name of the winner + */ + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; + } +} +` \ No newline at end of file diff --git a/apps/remix-ide-e2e/src/tests/editorReferences.test.ts b/apps/remix-ide-e2e/src/tests/editorReferences.test.ts new file mode 100644 index 0000000000..86856a9822 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editorReferences.test.ts @@ -0,0 +1,196 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +const openReferences = (browser: NightwatchBrowser, path: string) => { + (browser as any).useXpath() + .useXpath() + .waitForElementVisible(path) + .click(path) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + keyDown(this.Keys.SHIFT). + sendKeys(this.Keys.F12) + }) +} + +module.exports = { + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', false) + }, + + 'Should load the test file': function (browser: NightwatchBrowser) { + browser.openFile('contracts') + .openFile('contracts/3_Ballot.sol') + .waitForElementVisible('#editorView') + .setEditorValue(BallotWithARefToOwner) + .pause(10000) // wait for the compiler to finish + .scrollToLine(37) + }, + 'Should show local references': function (browser: NightwatchBrowser) { + browser.scrollToLine(48) + const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]" + openReferences(browser, path) + browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]") + .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]") + .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]") + .keys(browser.Keys.ESCAPE) + }, + 'Should show references of getOwner': function (browser: NightwatchBrowser) { + browser.scrollToLine(39) + const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]" + openReferences(browser, path) + browser.useXpath() + .waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]") + .waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]") + .waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]") + .waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]") + .keys(browser.Keys.ESCAPE) + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +import "./2_Owner.sol"; + +/** + * @title Ballot + * @dev Implements voting process along with vote delegation + */ +contract BallotHoverTest { + Owner cowner; + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + struct Proposal { + // If you can limit the length to a certain number of bytes, + // always use one of bytes1 to bytes32 because they are much cheaper + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + mapping(address => Voter) public voters; + + Proposal[] public proposals; + + /** + * @dev Create a new ballot to choose one of 'proposalNames'. + * @param proposalNames names of proposals + */ + constructor(bytes32[] memory proposalNames) { + cowner = new Owner(); + cowner.getOwner(); + chairperson = msg.sender; + voters[chairperson].weight = 1; + + for (uint i = 0; i < proposalNames.length; i++) { + // 'Proposal({...})' creates a temporary + // Proposal object and 'proposals.push(...)' + // appends it to the end of 'proposals'. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + /** + * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. + * @param voter address of voter + */ + function giveRightToVote(address voter) public { + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /** + * @dev Delegate your vote to the voter 'to'. + * @param to address to which vote is delegated + */ + function delegate(address to) public { + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + require(to != msg.sender, "Self-delegation is disallowed."); + + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + sender.voted = true; + sender.delegate = to; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /** + * @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'. + * @param proposal index of proposal in the proposals array + */ + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If 'proposal' is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /** + * @dev Computes the winning proposal taking all previous votes into account. + * @return winningProposal_ index of winning proposal in the proposals array + */ + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + /** + * @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then + * @return winnerName_ the name of the winner + */ + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; + } +} +` \ No newline at end of file diff --git a/apps/remix-ide-e2e/src/tests/fileExplorer.test.ts b/apps/remix-ide-e2e/src/tests/fileExplorer.test.ts index 7b26d84679..0a15009a89 100644 --- a/apps/remix-ide-e2e/src/tests/fileExplorer.test.ts +++ b/apps/remix-ide-e2e/src/tests/fileExplorer.test.ts @@ -10,12 +10,12 @@ const testData = { } module.exports = { - + '@disabled': true, before: function (browser: NightwatchBrowser, done: VoidFunction) { init(browser, done) }, - 'Should create a new file `5_New_contract.sol` in file explorer': function (browser: NightwatchBrowser) { + 'Should create a new file `5_New_contract.sol` in file explorer #group1': function (browser: NightwatchBrowser) { browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]') .clickLaunchIcon('filePanel') .assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORER') @@ -28,7 +28,7 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]', 7000) }, - 'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser: NightwatchBrowser) { + 'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]') .click('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]') @@ -36,14 +36,14 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItem5_Renamed_Contract.sol"]') }, - 'Should delete file `5_Renamed_Contract.sol` from file explorer': function (browser: NightwatchBrowser) { + 'Should delete file `5_Renamed_Contract.sol` from file explorer #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="treeViewLitreeViewItem5_Renamed_Contract.sol"]') .removeFile('5_Renamed_Contract.sol', 'default_workspace') .waitForElementNotPresent('*[data-id="treeViewLitreeViewItem5_Renamed_Contract.sol"') }, - 'Should create a new folder': function (browser: NightwatchBrowser) { + 'Should create a new folder #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.txt"]') .click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory @@ -55,7 +55,7 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]') }, - 'Should rename Browser_Tests folder to Browser_E2E_Tests': function (browser: NightwatchBrowser) { + 'Should rename Browser_Tests folder to Browser_E2E_Tests #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]') .click('*[data-id="treeViewLitreeViewItemBrowser_Tests"]') @@ -63,10 +63,10 @@ module.exports = { .waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]') }, - 'Should delete Browser_E2E_Tests folder': function (browser: NightwatchBrowser) { + 'Should delete Browser_E2E_Tests folder #group1': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]') - .rightClick('[data-path="Browser_E2E_Tests"]') + .rightClickCustom('[data-path="Browser_E2E_Tests"]') .click('*[id="menuitemdelete"]') .waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok', 60000) .pause(2000) @@ -99,9 +99,12 @@ module.exports = { }) }, - 'Should open local filesystem explorer': function (browser: NightwatchBrowser) { - browser.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]') + 'Should open local filesystem explorer #group2': !function (browser: NightwatchBrowser) { + browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]') + .clickLaunchIcon('filePanel') + .waitForElementVisible('*[data-id="filePanelFileExplorerTree"]') .click('[data-id="remixUIWorkspaceExplorer"]') + .waitForElementPresent('*[data-id="fileExplorerFileUpload"]') .setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1) .setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2) .setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3) diff --git a/apps/remix-ide-e2e/src/tests/fileManager_api.test.ts b/apps/remix-ide-e2e/src/tests/fileManager_api.test.ts index 32da1f4828..26a2e7cffd 100644 --- a/apps/remix-ide-e2e/src/tests/fileManager_api.test.ts +++ b/apps/remix-ide-e2e/src/tests/fileManager_api.test.ts @@ -11,7 +11,7 @@ module.exports = { 'Should execute `file` api from file manager external api #group1': function (browser: NightwatchBrowser) { browser .addFile('file.js', { content: executeFile }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(1000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'file.js', 60000) }, @@ -19,7 +19,7 @@ module.exports = { 'Should execute `exists` api from file manager external api #group1': function (browser: NightwatchBrowser) { browser .addFile('exists.js', { content: executeExists }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', 'exists.js true', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'non-exists.js false', 60000) }, @@ -27,14 +27,14 @@ module.exports = { 'Should execute `open` api from file manager external api #group1': function (browser: NightwatchBrowser) { browser .addFile('open.js', { content: executeOpen }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', 'contracts/3_Ballot.sol', 60000) }, 'Should execute `writeFile` api from file manager external api #group1': function (browser: NightwatchBrowser) { browser .addFile('writeFile.js', { content: executeWriteFile }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(2000) .openFile('new_contract.sol') .getEditorValue((content) => { @@ -45,23 +45,23 @@ module.exports = { 'Should execute `readFile` api from file manager external api #group2': function (browser: NightwatchBrowser) { browser .addFile('writeFile.js', { content: executeWriteFile }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .addFile('readFile.js', { content: executeReadFile }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', 'pragma solidity ^0.6.0', 60000) }, 'Should execute `copyFile` api from file manager external api #group2': function (browser: NightwatchBrowser) { browser .addFile('copyFile.js', { content: executeCopyFile }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', 'pragma solidity >=0.7.0 <0.9.0;', 60000) }, 'Should execute `rename` api from file manager external api #group2': function (browser: NightwatchBrowser) { browser .addFile('renameFile.js', { content: executeRename }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(2000) .waitForElementPresent('[data-id="treeViewLitreeViewItemold_contract.sol"]', 60000) }, @@ -69,7 +69,7 @@ module.exports = { 'Should execute `mkdir` api from file manager external api #group3': function (browser: NightwatchBrowser) { browser .addFile('mkdirFile.js', { content: executeMkdir }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(2000) .waitForElementPresent('[data-id="treeViewLitreeViewItemTest_Folder"]', 80000) }, @@ -77,7 +77,7 @@ module.exports = { 'Should execute `readdir` api from file manager external api #group3': function (browser: NightwatchBrowser) { browser .addFile('readdirFile.js', { content: executeReaddir }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .waitForElementContainsText('*[data-id="terminalJournal"]', 'Test_Folder isDirectory', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'true', 5000) }, @@ -86,7 +86,7 @@ module.exports = { browser .addFile('old_contract.sol', { content: 'test' }) .addFile('removeFile.js', { content: executeRemove }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(2000) .waitForElementNotPresent('[data-id="treeViewLitreeViewItemold_contract.sol"]', 60000) }, @@ -95,7 +95,7 @@ module.exports = { 'Should execute `remove` api from file manager external api on a folder #group4': function (browser: NightwatchBrowser) { browser .addFile('test_jsRemoveFolder.js', { content: executeRemoveOnFolder }) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(2000) .waitForElementNotPresent('[data-id="treeViewLitreeViewItemcontracts"]', 60000) .end() diff --git a/apps/remix-ide-e2e/src/tests/gist.test.ts b/apps/remix-ide-e2e/src/tests/gist.test.ts index 0fd6efa234..5bf3b34d5d 100644 --- a/apps/remix-ide-e2e/src/tests/gist.test.ts +++ b/apps/remix-ide-e2e/src/tests/gist.test.ts @@ -34,7 +34,7 @@ module.exports = { .sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER) .waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]') .addFile('File.sol', { content: '' }) - .executeScript(`remix.loadgist('${gistid}')`) + .executeScriptInTerminal(`remix.loadgist('${gistid}')`) // .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() }) .waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`) .click(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`) @@ -58,7 +58,7 @@ module.exports = { const gistid = id[1] browser .click('[data-id="default_workspace-modal-footer-cancel-react"]') - .executeScript(`remix.loadgist('${gistid}')`) + .executeScriptInTerminal(`remix.loadgist('${gistid}')`) // .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() }) .waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`) .click(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`) diff --git a/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts b/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts index bf4936a2d4..cf163dbb94 100644 --- a/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts +++ b/apps/remix-ide-e2e/src/tests/importFromGithub.test.ts @@ -37,8 +37,8 @@ module.exports = { .setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.invalidURL) .waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]') .click('[data-id="homeTab-modal-footer-ok-react"]') // submitted - .waitForElementVisible('*[data-shared="tooltipPopup"]') - .assert.containsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL) + //.waitForElementVisible('*[data-shared="tooltipPopup"]') + //.waitForElementContainsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL) }, 'Import From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) { diff --git a/apps/remix-ide-e2e/src/tests/plugin_api.ts b/apps/remix-ide-e2e/src/tests/plugin_api.ts index c7ef41a231..a05197f4b6 100644 --- a/apps/remix-ide-e2e/src/tests/plugin_api.ts +++ b/apps/remix-ide-e2e/src/tests/plugin_api.ts @@ -214,7 +214,7 @@ module.exports = { } }) .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') - .rightClick('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]').useXpath().waitForElementVisible('//*[@id="menuitemtestcommand"]').click('//*[@id="menuitemtestcommand"]', async () => { + .rightClickCustom('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]').useXpath().waitForElementVisible('//*[@id="menuitemtestcommand"]').click('//*[@id="menuitemtestcommand"]', async () => { // @ts-ignore browser.click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => { await clickAndCheckLog(browser, null, { id: 'localPlugin', name: 'testCommand', label: 'testCommand', type: [], extension: ['.sol'], path: ['contracts/1_Storage.sol'], pattern: [] }, null, null) @@ -321,14 +321,14 @@ module.exports = { // DGIT 'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'dgit') - await clickAndCheckLog(browser, 'dGitProvider:status', [['README.txt', 0, 2, 0], ['contracts/1_Storage.sol', 0, 2, 0], ['contracts/2_Owner.sol', 0, 2, 0], ['contracts/3_Ballot.sol', 0, 2, 0], ['scripts/deploy_with_ethers.ts', 0, 2, 0], ['scripts/deploy_with_web3.ts', 0, 2, 0], ['tests/Ballot_test.sol', 0, 2, 0]], ['tests/storage.test.js', 0, 2, 0], null, null) + await clickAndCheckLog(browser, 'dGitProvider:status', [["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,0],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null) }, 'Should stage contract #group3': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'dGitProvider:add', null, null, { filepath: 'contracts/1_Storage.sol' }) - await clickAndCheckLog(browser, 'dGitProvider:status', [['README.txt', 0, 2, 0], ['contracts/1_Storage.sol', 0, 2, 2], ['contracts/2_Owner.sol', 0, 2, 0], ['contracts/3_Ballot.sol', 0, 2, 0], ['scripts/deploy_with_ethers.ts', 0, 2, 0], ['scripts/deploy_with_web3.ts', 0, 2, 0], ['tests/Ballot_test.sol', 0, 2, 0]], ['tests/storage.test.js', 0, 2, 0], null, null) + await clickAndCheckLog(browser, 'dGitProvider:status', [["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,2],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null) }, 'Should commit changes #group3': async function (browser: NightwatchBrowser) { await clickAndCheckLog(browser, 'dGitProvider:commit', null, null, { author: { name: 'Remix', email: 'Remix' }, message: 'commit-message' }) @@ -417,7 +417,7 @@ module.exports = { .frameParent() .useCss() .addFile('test_modal.js', { content: testModalToasterApi }) - .executeScript('remix.execute(\'test_modal.js\')') + .executeScriptInTerminal('remix.execute(\'test_modal.js\')') .useCss() .waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]', 60000) .assert.containsText('*[data-id="test_id_1_ModalDialogModalBody-react"]', 'message 1') diff --git a/apps/remix-ide-e2e/src/tests/remixd.test.ts b/apps/remix-ide-e2e/src/tests/remixd.test.ts index 4e0772bf5f..6cf256e2cc 100644 --- a/apps/remix-ide-e2e/src/tests/remixd.test.ts +++ b/apps/remix-ide-e2e/src/tests/remixd.test.ts @@ -102,7 +102,7 @@ module.exports = { 'Run git status': '' + function (browser) { browser - .executeScript('git status') + .executeScriptInTerminal('git status') .pause(3000) .journalLastChildIncludes('On branch ') }, diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index 6d87899d79..dceb54a420 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -39,8 +39,8 @@ module.exports = { .waitForElementVisible('*[data-id="signMessageTextarea"]', 120000) .click('*[data-id="signMessageTextarea"]') .setValue('*[data-id="signMessageTextarea"]', 'Remix is cool!') - .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]') - .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]') + .assert.not.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]') + .assert.not.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .pause(2000) .waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]') .click('[data-id="udappNotify-modal-footer-ok-react"]') @@ -200,7 +200,7 @@ module.exports = { 'Call web3.eth.getAccounts() using Injected Provider (Metamask)': '' + function (browser: NightwatchBrowser) { browser - .executeScript('web3.eth.getAccounts()') + .executeScriptInTerminal('web3.eth.getAccounts()') .pause(2000) .journalLastChildIncludes('[ "0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f" ]') .end() diff --git a/apps/remix-ide-e2e/src/tests/search.test.ts b/apps/remix-ide-e2e/src/tests/search.test.ts index c8f90c5978..c91531d587 100644 --- a/apps/remix-ide-e2e/src/tests/search.test.ts +++ b/apps/remix-ide-e2e/src/tests/search.test.ts @@ -12,7 +12,7 @@ module.exports = { browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') .click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]') .waitForElementVisible('*[id="search_include"]') - .setValue('*[id="search_include"]', ', *.txt').pause(2000) + .setValue('*[id="search_include"]', ', *.*').pause(2000) .setValue('*[id="search_input"]', 'read').sendKeys('*[id="search_input"]', browser.Keys.ENTER) .pause(1000) .waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000) @@ -100,7 +100,7 @@ module.exports = { browser.click('*[data-id="confirm_replace_label"]').pause(500) .clearValue('*[id="search_input"]') .setValue('*[id="search_input"]', 'replacing').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(1000) - .setValue('*[id="search_replace"]', '2').pause(1000) + .setValue('*[id="search_replace"]', 'replacing2').pause(1000) .waitForElementVisible('*[data-id="contracts/2_Owner.sol-33-71"]') .moveToElement('*[data-id="contracts/2_Owner.sol-33-71"]', 10, 10) .waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-33-71"]') diff --git a/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts b/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts index bcdc3c4977..ad41980081 100644 --- a/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts +++ b/apps/remix-ide-e2e/src/tests/specialFunctions.test.ts @@ -33,7 +33,7 @@ module.exports = { .journalLastChildIncludes('to: CheckSpecials.(fallback)') .journalLastChildIncludes('value: 0 wei') .journalLastChildIncludes('data: 0xaa') - .perform(done) + .perform(done()) }) }) }, @@ -46,7 +46,7 @@ module.exports = { .pause(1000) .waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`) .assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, 'The calldata should be a valid hexadecimal value with size of at least one byte.') - .perform(done) + .perform(done()) }) }) }, @@ -58,7 +58,7 @@ module.exports = { .pause(1000) .waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`) .assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, 'The calldata should be a valid hexadecimal value.') - .perform(done) + .perform(done()) }) }) }, @@ -71,7 +71,7 @@ module.exports = { .journalLastChildIncludes('to: CheckSpecials.(receive)') .journalLastChildIncludes('value: 1 wei') .journalLastChildIncludes('data: 0x') - .perform(done) + .perform(done()) }) }) }, @@ -83,7 +83,7 @@ module.exports = { .pause(1000) .journalLastChildIncludes('to CheckSpecials.(fallback) errored:') .journalLastChildIncludes('The called function should be payable if you send value') - .perform(done) + .perform(done()) }) }) }, @@ -101,7 +101,7 @@ module.exports = { .journalLastChildIncludes('to: CheckSpecials.(receive)') .journalLastChildIncludes('value: 1 wei') .journalLastChildIncludes('data: 0x') - .perform(done) + .perform(done()) }) }) }, @@ -113,7 +113,7 @@ module.exports = { .pause(1000) .waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`) .assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, '\'Fallback\' function is not defined') - .perform(done) + .perform(done()) }) }) }, @@ -131,7 +131,7 @@ module.exports = { .journalLastChildIncludes('to: CheckSpecials.(fallback)') .journalLastChildIncludes('value: 1 wei') .journalLastChildIncludes('data: 0x') - .perform(done) + .perform(done()) }) }) }, @@ -144,7 +144,7 @@ module.exports = { .journalLastChildIncludes('to: CheckSpecials.(fallback)') .journalLastChildIncludes('value: 1 wei') .journalLastChildIncludes('data: 0xaa') - .perform(done) + .perform(done()) }) }) }, @@ -161,7 +161,7 @@ module.exports = { .pause(1000) .waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`) .assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, 'should have either \'receive\' or payable \'fallback\'') - .perform(done) + .perform(done()) }) }) }, @@ -184,7 +184,7 @@ module.exports = { .journalLastChildIncludes('to: CheckSpecials.(fallback)') .journalLastChildIncludes('value: 999999998765257135 wei') .journalLastChildIncludes('data: 0xaa') - .perform(done) + .perform(done()) }) }) }, @@ -196,7 +196,7 @@ module.exports = { .journalLastChildIncludes('to: CheckSpecials.(receive)') .journalLastChildIncludes('value: 1 wei') .journalLastChildIncludes('data: 0x') - .perform(done) + .perform(done()) }) }) }, @@ -217,7 +217,7 @@ module.exports = { .pause(1000) .waitForElementVisible(`#instance${address} label[id="deployAndRunLLTxError"]`) .assert.containsText(`#instance${address} label[id="deployAndRunLLTxError"]`, 'Both \'receive\' and \'fallback\' functions are not defined') - .perform(done) + .perform(done()) }) }) .end() diff --git a/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts b/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts index 2a2b39b523..d254d15eee 100644 --- a/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts +++ b/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts @@ -42,7 +42,7 @@ module.exports = { .waitForElementPresent('#staticanalysisresult .warning', 5000) .assert.containsText('#verticalIconsKindsolidityStaticAnalysis .remixui_status', '1') // Check warning count .verify.elementPresent('input[name="showLibWarnings"]') - .verify.elementNotPresent('input[name="showLibWarnings"]:checked') + .verify.not.elementPresent('input[name="showLibWarnings"]:checked') .verify.elementPresent('label[id="headingshowLibWarnings"]') .click('label[id="headingshowLibWarnings"]') .pause(1000) @@ -62,7 +62,7 @@ function runTests (browser: NightwatchBrowser) { .testContracts('Untitled.sol', sources[0]['Untitled.sol'], ['TooMuchGas', 'test1', 'test2']) .clickLaunchIcon('solidityStaticAnalysis') .click('#staticanalysisButton button') - .waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () { + .waitForElementPresent('#staticanalysisresult .warning', 2000, 500, true, function () { listSelectorContains(['Use of tx.origin', 'Fallback function of contract TooMuchGas requires too much gas', 'TooMuchGas.() : Variables have very similar names "test" and "test1".', diff --git a/apps/remix-ide-e2e/src/tests/terminal.test.ts b/apps/remix-ide-e2e/src/tests/terminal.test.ts index 79305cd22d..e7d76869e0 100644 --- a/apps/remix-ide-e2e/src/tests/terminal.test.ts +++ b/apps/remix-ide-e2e/src/tests/terminal.test.ts @@ -10,7 +10,7 @@ module.exports = { 'Should execution a simple console command #group1 #group999': function (browser: NightwatchBrowser) { browser .waitForElementVisible('*[data-id="terminalCli"]', 10000) - .executeScript('console.log(1 + 1)') + .executeScriptInTerminal('console.log(1 + 1)') .pause(2000) .waitForElementContainsText('*[data-id="terminalJournal"]', '2', 60000) }, @@ -26,7 +26,7 @@ module.exports = { 'Async/Await Script #group1': function (browser: NightwatchBrowser) { browser .addFile('asyncAwait.js', { content: asyncAwait }) - .executeScript('remix.execute("asyncAwait.js")') + .executeScriptInTerminal('remix.execute("asyncAwait.js")') .waitForElementContainsText('*[data-id="terminalJournal"]', 'Waiting Promise', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'result - ', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'Promise Resolved', 60000) @@ -35,13 +35,13 @@ module.exports = { 'Call Remix File Manager from a script #group2': function (browser: NightwatchBrowser) { browser .addFile('asyncAwaitWithFileManagerAccess.js', { content: asyncAwaitWithFileManagerAccess }) - .executeScript('remix.execute(\'asyncAwaitWithFileManagerAccess.js\')') + .executeScriptInTerminal('remix.execute(\'asyncAwaitWithFileManagerAccess.js\')') .waitForElementContainsText('*[data-id="terminalJournal"]', 'contract Ballot {', 60000) }, 'Call web3.eth.getAccounts() using Remix VM #group2': function (browser: NightwatchBrowser) { browser - .executeScript('web3.eth.getAccounts()') + .executeScriptInTerminal('web3.eth.getAccounts()') .waitForElementContainsText('*[data-id="terminalJournal"]', '["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4","0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2","0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db","0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB","0x617F2E2fD72FD9D5503197092aC168c91465E7f2","0x17F6AD8Ef982297579C203069C1DbfFE4348c372","0x5c6B0f7Bf3E7ce046039Bd8FABdfD3f9F5021678","0x03C6FcED478cBbC9a4FAB34eF9f40767739D1Ff7","0x1aE0EA34a72D944a8C7603FfB3eC30a6669E454C","0x0A098Eda01Ce92ff4A4CCb7A4fFFb5A43EBC70DC","0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c","0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C","0x4B0897b0513fdC7C541B6d9D7E929C4e5364D2dB","0x583031D1113aD414F02576BD6afaBfb302140225","0xdD870fA1b7C4700F2BD7f44238821C26f7392148"]') }, @@ -51,7 +51,7 @@ module.exports = { .clickLaunchIcon('udapp') .switchEnvironment('External Http Provider') .modalFooterOKClick('basic-http-provider') - .executeScript('web3.eth.getAccounts()') + .executeScriptInTerminal('web3.eth.getAccounts()') .waitForElementContainsText('*[data-id="terminalJournal"]', '["', 60000) // we check if an array is present, don't need to check for the content .waitForElementContainsText('*[data-id="terminalJournal"]', '"]', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '","', 60000) @@ -63,7 +63,7 @@ module.exports = { .addFile('resolveExternalUrlAndSave.js', { content: resolveExternalUrlAndSave }) .openFile('resolveExternalUrlAndSave.js') .pause(1000) - .executeScript('remix.execute(\'resolveExternalUrlAndSave.js\')') + .executeScriptInTerminal('remix.execute(\'resolveExternalUrlAndSave.js\')') .waitForElementContainsText('*[data-id="terminalJournal"]', 'Implementation of the {IERC20} interface.', 60000) .openFile('.deps/github/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol') }, @@ -74,7 +74,7 @@ module.exports = { .addFile('resolveUrl.js', { content: resolveUrl }) .openFile('resolveUrl.js') .pause(1000) - .executeScript('remix.execute(\'resolveUrl.js\')') + .executeScriptInTerminal('remix.execute(\'resolveUrl.js\')') .waitForElementContainsText('*[data-id="terminalJournal"]', 'contract Ballot {', 60000) }, @@ -84,7 +84,7 @@ module.exports = { .addFile('resolveExternalUrlAndSaveToaPath.js', { content: resolveExternalUrlAndSaveToaPath }) .openFile('resolveExternalUrlAndSaveToaPath.js') .pause(1000) - .executeScript('remix.execute(\'resolveExternalUrlAndSaveToaPath.js\')') + .executeScriptInTerminal('remix.execute(\'resolveExternalUrlAndSaveToaPath.js\')') .waitForElementContainsText('*[data-id="terminalJournal"]', 'abstract contract ERC20Burnable', 60000) .openFile('.deps/github/newFile.sol') @@ -105,7 +105,7 @@ module.exports = { .openFile('contracts/2_Owner.sol') .clickLaunchIcon('solidity') .click('*[data-id="compilerContainerCompileBtn"]').pause(5000) // compile Owner - .executeScript('remix.execute(\'deployWithEthersJs.js\')') + .executeScriptInTerminal('remix.execute(\'deployWithEthersJs.js\')') .waitForElementContainsText('*[data-id="terminalJournal"]', 'Contract Address:', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', '0xd9145CCE52D386f254917e481eB44e9943F39138', 60000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'Deployment successful.', 60000) @@ -127,7 +127,7 @@ module.exports = { .clickLaunchIcon('solidity') .click('*[data-id="compilerContainerCompileBtn"]') .pause(1000) // compile Storage - .executeScript('remix.execute(\'scripts/storage.test.js\')') + .executeScriptInTerminal('remix.execute(\'scripts/storage.test.js\')') .pause(1000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'Running tests....') .waitForElementContainsText('*[data-id="terminalJournal"]', 'storage contract Address:') @@ -149,7 +149,7 @@ module.exports = { .clickLaunchIcon('solidity') .click('*[data-id="compilerContainerCompileBtn"]') .pause(1000) // compile StorageWithLib - .executeScript('remix.execute(\'scripts/storageWithLib.test.js\')') + .executeScriptInTerminal('remix.execute(\'scripts/storageWithLib.test.js\')') .pause(1000) .waitForElementContainsText('*[data-id="terminalJournal"]', 'Running tests....') .waitForElementContainsText('*[data-id="terminalJournal"]', 'Storage with lib') diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index 760d3eb6ae..832dca69d0 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -227,7 +227,7 @@ module.exports = { // default values .assert.containsText('#evmVersionSelector option[data-id="selected"]', 'default') .verify.elementPresent('#optimize') - .assert.elementNotPresent('#optimize:checked') + .assert.not.elementPresent('#optimize:checked') .verify.elementPresent('#runs:disabled') .click('[for="optimize"') .verify.attributeEquals('#runs', 'value', '200') diff --git a/apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.ts b/apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.ts index 63a98743a6..3cbe497dd7 100644 --- a/apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.ts +++ b/apps/remix-ide-e2e/src/tests/verticalIconsPanel.test.ts @@ -13,7 +13,7 @@ module.exports = { .click('*[data-id="verticalIconsKindpluginManager"]') .scrollAndClick('*[data-id="pluginManagerComponentActivateButtondebugger"]') .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]', 7000) - .rightClick('[data-id="verticalIconsKinddebugger"]') + .rightClickCustom('[data-id="verticalIconsKinddebugger"]') .waitForElementVisible('*[id="menuitemdeactivate"]') .waitForElementVisible('*[id="menuitemdocumentation"]') .click('*[data-id="remixIdeIconPanel"]') @@ -23,7 +23,7 @@ module.exports = { browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000) .waitForElementVisible('*[data-id="verticalIconsKinddebugger"]', 7000) .pause(5000) - .rightClick('[data-id="verticalIconsKinddebugger"]') + .rightClickCustom('[data-id="verticalIconsKinddebugger"]') .click('*[id="menuitemdeactivate"]') .click('*[data-id="verticalIconsKindsettings"]') .click('*[data-id="verticalIconsKindpluginManager"]') diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index c45c1d4d3d..4f5568e773 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -21,14 +21,14 @@ declare module 'nightwatch' { modalFooterOKClick(id?: string): NightwatchBrowser, clickInstance(index: number): NightwatchBrowser, journalLastChildIncludes(val: string): NightwatchBrowser, - executeScript(script: string): NightwatchBrowser, + executeScriptInTerminal(script: string): NightwatchBrowser, clearEditableContent(cssSelector: string): NightwatchBrowser, journalChildIncludes(val: string, opts = { shouldHaveOnlyOneOccurence: boolean }): NightwatchBrowser, debugTransaction(index: number): NightwatchBrowser, checkElementStyle(cssSelector: string, styleProperty: string, expectedResult: string): NightwatchBrowser, openFile(name: string): NightwatchBrowser, renamePath(path: string, newFileName: string, renamedPath: string): NightwatchBrowser, - rightClick(cssSelector: string): NightwatchBrowser, + rightClickCustom(cssSelector: string): NightwatchBrowser, scrollToLine(line: number): NightwatchBrowser, waitForElementContainsText(id: string, value: string, timeout?: number): NightwatchBrowser, getModalBody(callback: (value: string, cb: VoidFunction) => void): NightwatchBrowser, diff --git a/package.json b/package.json index 37cbe00ad6..a0a8f0f101 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,7 @@ "@ethereumjs/vm": "^5.5.3", "@ethersphere/bee-js": "^3.2.0", "@isomorphic-git/lightning-fs": "^4.4.1", - "@monaco-editor/react": "^4.3.1", + "@monaco-editor/react": "4.4.5", "@remixproject/engine": "^0.3.31", "@remixproject/engine-web": "^0.3.31", "@remixproject/plugin": "^0.3.31", @@ -161,6 +161,7 @@ "@remixproject/plugin-utils": "^0.3.31", "@remixproject/plugin-webview": "^0.3.31", "@remixproject/plugin-ws": "^0.3.31", + "@types/nightwatch": "^2.0.9", "ansi-gray": "^0.1.1", "async": "^2.6.2", "axios": ">=0.26.0", @@ -245,7 +246,6 @@ "@types/jest": "^27.0.2", "@types/lodash": "^4.14.172", "@types/mocha": "^7.0.2", - "@types/nightwatch": "1.3.4", "@types/node": "~8.9.4", "@types/react": "^17.0.24", "@types/react-beautiful-dnd": "^13.1.2", @@ -307,7 +307,7 @@ "mkdirp": "^0.5.1", "mocha": "^8.0.1", "nanohtml": "^1.6.3", - "nightwatch": "^1.7.11", + "nightwatch": "^2.2.2", "nodemon": "^2.0.4", "notify-error": "^1.2.0", "npm-link-local": "^1.1.0",