From 5d6ed35b5354a75765348e1e6adacd73136d645a Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 8 Sep 2023 13:29:59 +0200 Subject: [PATCH 01/11] improve definitionAtPosition --- .../src/app/plugins/parser/code-parser.tsx | 61 +++++++++++++------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx index 2a38f35bf1..9f69ffc1ac 100644 --- a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx +++ b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx @@ -477,40 +477,63 @@ export class CodeParser extends Plugin { */ async definitionAtPosition(position: number) { const nodes = await this.nodesAtPosition(position) - let nodeDefinition: any + let nodeDefinition = { + 'ast': null, + 'parser': null + } let node: genericASTNode if (nodes && nodes.length && !this.errorState) { node = nodes[nodes.length - 1] - nodeDefinition = node + let astNodeDefinition = node if (!isNodeDefinition(node)) { - nodeDefinition = (await this.declarationOf(node)) || node + astNodeDefinition = (await this.declarationOf(node)) || node } if (node.nodeType === 'ImportDirective') { for (const key in this.nodeIndex.flatReferences) { if (this.nodeIndex.flatReferences[key].id === node.sourceUnit) { - nodeDefinition = this.nodeIndex.flatReferences[key] + astNodeDefinition = this.nodeIndex.flatReferences[key] } } } - return nodeDefinition - } else { - const astNodes = await this.antlrService.listAstNodes() - if (astNodes && astNodes.length) { - for (const node of astNodes) { - if (node.range[0] <= position && node.range[1] >= position) { - if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) { - nodeDefinition = node - } - if (!nodeDefinition) nodeDefinition = node + + nodeDefinition.ast = astNodeDefinition + } + const astNodes = await this.antlrService.listAstNodes() + let parserNodeDefinition = null + if (astNodes && astNodes.length) { + for (const node of astNodes) { + if (node.range[0] <= position && node.range[1] >= position) { + if (parserNodeDefinition && parserNodeDefinition.range[0] < node.range[0]) { + parserNodeDefinition = node } + if (!parserNodeDefinition) parserNodeDefinition = node } - if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') { - const nodeForIdentifier = await this.findIdentifier(nodeDefinition) - if (nodeForIdentifier) nodeDefinition = nodeForIdentifier - } - return nodeDefinition } + if (parserNodeDefinition && parserNodeDefinition.type && parserNodeDefinition.type === 'Identifier') { + const nodeForIdentifier = await this.findIdentifier(parserNodeDefinition) + if (nodeForIdentifier) parserNodeDefinition = nodeForIdentifier + } + nodeDefinition.parser = parserNodeDefinition } + + /* if the AST node name & type is the same as the parser node name & type, + / then we can assume that the AST node is the definition, + / because the parser will always return most nodes it can find even with an error in the code + */ + + if (nodeDefinition.ast && nodeDefinition.parser) { + if (nodeDefinition.ast.name === nodeDefinition.parser.name && nodeDefinition.ast.nodeType === nodeDefinition.parser.type) { + return nodeDefinition.ast + } + } + + if (nodeDefinition.ast && !nodeDefinition.parser) { + return nodeDefinition.ast + } + + + return nodeDefinition.parser + } async getContractNodes(contractName: string) { From 8f5b9243bdeb50500f8d52beeca5f8d4c46bb893 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Sat, 9 Sep 2023 11:12:16 +0200 Subject: [PATCH 02/11] lint --- apps/remix-ide/src/app/plugins/parser/code-parser.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx index 9f69ffc1ac..14a1de8400 100644 --- a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx +++ b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx @@ -477,7 +477,7 @@ export class CodeParser extends Plugin { */ async definitionAtPosition(position: number) { const nodes = await this.nodesAtPosition(position) - let nodeDefinition = { + const nodeDefinition = { 'ast': null, 'parser': null } @@ -528,7 +528,7 @@ export class CodeParser extends Plugin { } if (nodeDefinition.ast && !nodeDefinition.parser) { - return nodeDefinition.ast + return nodeDefinition.ast } From a0c055377d9cc9d731ac99c9b52c7e8d2b835879 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Sat, 9 Sep 2023 11:26:06 +0200 Subject: [PATCH 03/11] fix --- apps/remix-ide/src/app/plugins/parser/code-parser.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx index 14a1de8400..c95cb9efca 100644 --- a/apps/remix-ide/src/app/plugins/parser/code-parser.tsx +++ b/apps/remix-ide/src/app/plugins/parser/code-parser.tsx @@ -482,7 +482,7 @@ export class CodeParser extends Plugin { 'parser': null } let node: genericASTNode - if (nodes && nodes.length && !this.errorState) { + if (nodes && nodes.length) { node = nodes[nodes.length - 1] let astNodeDefinition = node if (!isNodeDefinition(node)) { @@ -524,6 +524,11 @@ export class CodeParser extends Plugin { if (nodeDefinition.ast && nodeDefinition.parser) { if (nodeDefinition.ast.name === nodeDefinition.parser.name && nodeDefinition.ast.nodeType === nodeDefinition.parser.type) { return nodeDefinition.ast + }else{ + // if there is a difference and the compiler has compiled correctly assume the ast node is the definition + if(this.compilerService.errorState === false){ + return nodeDefinition.ast + } } } From 06ec22548b0f52e8dd8710b327c11bb1f93fc97e Mon Sep 17 00:00:00 2001 From: filip mertens Date: Sat, 9 Sep 2023 11:58:37 +0200 Subject: [PATCH 04/11] add globals --- .../lib/providers/completion/completionGlobals.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts index 21436a1998..c3b1db62a3 100644 --- a/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts +++ b/libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts @@ -521,6 +521,20 @@ export function GetGlobalVariable(range: monacoTypes.IRange, monaco): monacoType export function GetGlobalFunctions(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] { return [ + { + label: 'fallback', + kind: monaco.languages.CompletionItemKind.Function, + insertText: 'fallback() ${1:external} ${2:payable} { }', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + range + }, + { + label: 'receive', + kind: monaco.languages.CompletionItemKind.Function, + insertText: 'receive() ${1:external} ${2:payable} { }', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + range + }, { detail: 'assert(bool condition): throws if the condition is not met - to be used for internal errors.', insertText: 'assert(${1:condition});', From 64bb57c156b7ddd848957d8b34f4b56c34baa671 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 12 Sep 2023 12:02:45 +0200 Subject: [PATCH 05/11] fix timestamp compiler --- .../parser/services/code-parser-compiler.ts | 9 ++++--- .../services/code-parser-gas-service.ts | 5 +++- libs/remix-solidity/src/compiler/compiler.ts | 27 ++++++++++++------- libs/remix-solidity/src/compiler/types.ts | 2 +- .../editor/src/lib/providers/quickfixes.ts | 18 ++++++++++++- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts index b3a1a9c322..ecd8278ca3 100644 --- a/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts @@ -99,21 +99,24 @@ export default class CodeParserCompiler { await this.clearDecorators(result.getSourceCode().sources) } - if (!data.sources) return if (data.sources && Object.keys(data.sources).length === 0) return this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input) this.errorState = false + this.plugin.nodeIndex = { declarations: {}, flatReferences: {}, nodesPerFile: {}, } - + this.plugin._buildIndex(data, source) // cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository - this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + const extractedFiledNodes = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + if(extractedFiledNodes) { + this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = extractedFiledNodes + } await this.plugin.gasService.showGasEstimates() this.plugin.emit('astFinished') } diff --git a/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts b/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts index 274466fe9c..34e86290c1 100644 --- a/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts +++ b/apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts @@ -42,7 +42,10 @@ export default class CodeParserGasService { } this.plugin.currentFile = await this.plugin.call('fileManager', 'file') // cast from the remix-plugin interface to the solidity one. Should be fixed when remix-plugin move to the remix-project repository - this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + const extractedFiledNodes = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract as unknown as lastCompilationResult) + if(extractedFiledNodes) { + this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = extractedFiledNodes + } const gasEstimates = await this.getGasEstimates(this.plugin.currentFile) diff --git a/libs/remix-solidity/src/compiler/compiler.ts b/libs/remix-solidity/src/compiler/compiler.ts index 77e50a5845..53da05881c 100644 --- a/libs/remix-solidity/src/compiler/compiler.ts +++ b/libs/remix-solidity/src/compiler/compiler.ts @@ -49,6 +49,7 @@ export class Compiler { if (success && this.state.compilationStartTime) { this.event.trigger('compilationDuration', [(new Date().getTime()) - this.state.compilationStartTime]) } + console.log('compilationStartTime to null', this.state.compilationStartTime, data) this.state.compilationStartTime = null }) @@ -81,12 +82,16 @@ export class Compiler { * @param missingInputs missing import file path list */ - internalCompile(files: Source, missingInputs?: string[]): void { + internalCompile(files: Source, missingInputs?: string[], timeStamp?: number): void { + if(timeStamp != this.state.compilationStartTime && this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger ) { + console.log('aborting compilation', timeStamp, this.state.compilationStartTime) + return + } this.gatherImports(files, missingInputs, (error, input) => { if (error) { this.state.lastCompilationResult = null this.event.trigger('compilationFinished', [false, { error: { formattedMessage: error, severity: 'error' } }, files, input, this.state.currentVersion]) - } else if (this.state.compileJSON && input) { this.state.compileJSON(input) } + } else if (this.state.compileJSON && input) { this.state.compileJSON(input, timeStamp) } }) } @@ -99,8 +104,9 @@ export class Compiler { compile(files: Source, target: string): void { this.state.target = target this.state.compilationStartTime = new Date().getTime() + console.log('compiling ' + target + '...', files, this.state.compilationStartTime) this.event.trigger('compilationStarted', []) - this.internalCompile(files) + this.internalCompile(files, null, this.state.compilationStartTime) } /** @@ -157,7 +163,7 @@ export class Compiler { * @param source Source */ - onCompilationFinished(data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget, input?: string, version?: string): void { + onCompilationFinished(data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget, input?: string, version?: string, timeStamp?: number): void { let noFatalErrors = true // ie warnings are ok const checkIfFatalError = (error: CompilationError) => { @@ -173,7 +179,7 @@ export class Compiler { this.event.trigger('compilationFinished', [false, data, source, input, version]) } else if (missingInputs !== undefined && missingInputs.length > 0 && source && source.sources) { // try compiling again with the new set of inputs - this.internalCompile(source.sources, missingInputs) + this.internalCompile(source.sources, missingInputs, timeStamp) } else { data = this.updateInterface(data) if (source) { @@ -183,6 +189,7 @@ export class Compiler { source: source } } + console.log('compilationFinished ', timeStamp, this.state.compilationStartTime) this.event.trigger('compilationFinished', [true, data, source, input, version]) } } @@ -291,7 +298,9 @@ export class Compiler { this.state.worker.addEventListener('message', (msg: Record<'data', MessageFromWorker>) => { const data: MessageFromWorker = msg.data + console.log('incoming message', data.timestamp, data) if (this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger && data.timestamp !== this.state.compilationStartTime) { + console.log('dropping message', data.timestamp, this.state.compilationStartTime) return } switch (data.cmd) { @@ -312,7 +321,7 @@ export class Compiler { sources = jobs[data.job].sources delete jobs[data.job] } - this.onCompilationFinished(result, data.missingInputs, sources, data.input, this.state.currentVersion) + this.onCompilationFinished(result, data.missingInputs, sources, data.input, this.state.currentVersion, data.timestamp) } break } @@ -325,7 +334,7 @@ export class Compiler { this.onCompilationFinished({ error: { formattedMessage } }) }) - this.state.compileJSON = (source: SourceWithTarget) => { + this.state.compileJSON = (source: SourceWithTarget, timeStamp: number) => { if (source && source.sources) { const { optimize, runs, evmVersion, language, useFileConfiguration, configFileContent } = this.state jobs.push({ sources: source }) @@ -342,12 +351,12 @@ export class Compiler { return } - + console.log('posting message with timestamp ', this.state.compilationStartTime, source) this.state.worker.postMessage({ cmd: 'compile', job: jobs.length - 1, input: input, - timestamp: this.state.compilationStartTime + timestamp: timeStamp }) } } diff --git a/libs/remix-solidity/src/compiler/types.ts b/libs/remix-solidity/src/compiler/types.ts index fd1158e7f0..d86e7b0b74 100644 --- a/libs/remix-solidity/src/compiler/types.ts +++ b/libs/remix-solidity/src/compiler/types.ts @@ -160,7 +160,7 @@ export enum CompilerRetriggerMode { } export interface CompilerState { - compileJSON: ((input: SourceWithTarget) => void) | null, + compileJSON: ((input: SourceWithTarget, timeStamp?: number) => void) | null, worker: any, currentVersion: string| null| undefined, compilerLicense: string| null diff --git a/libs/remix-ui/editor/src/lib/providers/quickfixes.ts b/libs/remix-ui/editor/src/lib/providers/quickfixes.ts index 93f289de08..81556e3ef7 100644 --- a/libs/remix-ui/editor/src/lib/providers/quickfixes.ts +++ b/libs/remix-ui/editor/src/lib/providers/quickfixes.ts @@ -170,5 +170,21 @@ export default { title: "Add 'calldata' to param", message: ' calldata ' } + ], + 'SyntaxError: No visibility specified. Did you intend to add "external': [ + { + id: 12, + title: "Add visibility 'external'", + message: 'external ', + nodeType: 'FunctionDefinition' + } + ], + 'DeclarationError: Receive ether function must be payable, but is "nonpayable".': [ + { + id: 13, + title: "Make function 'payable'", + message: 'payable ', + nodeType: 'FunctionDefinition' + } ] -} +} \ No newline at end of file From 12f94c17f21870b068f60d0e4c1c563b1b4ce9c4 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 12 Sep 2023 12:19:36 +0200 Subject: [PATCH 06/11] add test --- .../src/tests/editorAutoComplete.test.ts | 526 ++++++++++++++++++ 1 file changed, 526 insertions(+) create mode 100644 apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts 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..9296a19123 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts @@ -0,0 +1,526 @@ +'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 enable settings': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('settings') + .click('[data-id="settingsAutoCompleteLabel"]') + .click('[data-id="settingsShowGasLabel"]') + .click('[data-id="displayErrorsLabel"]') + }, + + 'Should add test and base files #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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(5000) + .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 #group1': 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 #group1': 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 #group1': 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. #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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. #group1': 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. #group1': 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 #group1': 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 #group1': 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) + }) + } +} \ No newline at end of file From 6814079107f393aaf6001ed96638445df602f9ec Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 12 Sep 2023 12:34:03 +0200 Subject: [PATCH 07/11] fix test --- apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts index 9296a19123..1f366b36e5 100644 --- a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts +++ b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts @@ -12,7 +12,7 @@ module.exports = { before: function (browser: NightwatchBrowser, done: VoidFunction) { init(browser, done, 'http://127.0.0.1:8080', false) }, - + /* 'Should enable settings': function (browser: NightwatchBrowser) { browser .clickLaunchIcon('settings') @@ -20,6 +20,7 @@ module.exports = { .click('[data-id="settingsShowGasLabel"]') .click('[data-id="displayErrorsLabel"]') }, + */ 'Should add test and base files #group1': function (browser: NightwatchBrowser) { browser.addFile(examples.testContract.name, examples.testContract) From 59b59a524d5a7a552165b18bdf658f2b1efc1baa Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 12 Sep 2023 13:10:36 +0200 Subject: [PATCH 08/11] rm autocomplete test --- .../src/tests/editorAutoComplete.test.ts | 527 ------------------ 1 file changed, 527 deletions(-) delete mode 100644 apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts diff --git a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts deleted file mode 100644 index 1f366b36e5..0000000000 --- a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts +++ /dev/null @@ -1,527 +0,0 @@ -'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 enable settings': function (browser: NightwatchBrowser) { - browser - .clickLaunchIcon('settings') - .click('[data-id="settingsAutoCompleteLabel"]') - .click('[data-id="settingsShowGasLabel"]') - .click('[data-id="displayErrorsLabel"]') - }, - */ - - 'Should add test and base files #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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(5000) - .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 #group1': 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 #group1': 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 #group1': 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. #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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 #group1': 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. #group1': 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. #group1': 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 #group1': 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 #group1': 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) - }) - } -} \ No newline at end of file From 5a1082cddc6a68b89eed68918982279a0bf8272a Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 12 Sep 2023 18:54:16 +0200 Subject: [PATCH 09/11] add test --- .../src/tests/editorHoverContext.test.ts | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts b/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts index db86224b11..7e162530e1 100644 --- a/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts +++ b/apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts @@ -17,7 +17,6 @@ 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') @@ -86,7 +85,47 @@ module.exports = { const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]" const expectedContent = 'StructDefinition' checkEditorHoverContent(browser, path, expectedContent) - } + }, + 'Add token file': function (browser: NightwatchBrowser) { + browser.addFile('contracts/mytoken.sol', { + content: myToken + }).useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") + }, + // here we change quickly between files to test the files being parsed correctly when switching between them + 'Should show ERC20 hover over contract in editor #group1': function (browser: NightwatchBrowser) { + browser.scrollToLine(10) + const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]" + const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Metadata, IERC20, Context' + checkEditorHoverContent(browser, path, expectedContent, 25) + }, + 'Go back to ballot file': function (browser: NightwatchBrowser) { + browser.openFile('contracts/3_Ballot.sol') + .useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") + }, + 'Should show hover over function in editor again #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) + }, + 'Open token file': function (browser: NightwatchBrowser) { + browser.openFile('contracts/mytoken.sol') + .useXpath().waitForElementVisible("//*[@class='view-line' and contains(.,'gas')]") + }, + 'Should show ERC20 hover over contract in editor again #group1': function (browser: NightwatchBrowser) { + browser.scrollToLine(10) + const path = "//*[@class='view-line' and contains(.,'MyToken') and contains(.,'Pausable')]//span//span[contains(.,'ERC20Burnable')]" + const expectedContent = 'contract ERC20Burnable is ERC20Burnable, ERC20, IERC20Metadata, IERC20, Context' + checkEditorHoverContent(browser, path, expectedContent, 25) + }, + + + } @@ -233,4 +272,38 @@ contract BallotHoverTest { winnerName_ = proposals[winningProposal()].name; } } +` + +const myToken = ` +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import "@openzeppelin/contracts/security/Pausable.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyToken is ERC20, ERC20Burnable, Pausable, Ownable { + constructor() ERC20("MyToken", "MTK") {} + + function pause() public onlyOwner { + _pause(); + } + + function unpause() public onlyOwner { + _unpause(); + } + + function mint(address to, uint256 amount) public onlyOwner { + _mint(to, amount); + } + + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal + whenNotPaused + override + { + super._beforeTokenTransfer(from, to, amount); + } +} ` \ No newline at end of file From b03e2b11e5b28732a1a8369603fae0af38e4a073 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 12 Sep 2023 18:55:40 +0200 Subject: [PATCH 10/11] rm comments --- libs/remix-solidity/src/compiler/compiler.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/libs/remix-solidity/src/compiler/compiler.ts b/libs/remix-solidity/src/compiler/compiler.ts index 53da05881c..2e20b0c857 100644 --- a/libs/remix-solidity/src/compiler/compiler.ts +++ b/libs/remix-solidity/src/compiler/compiler.ts @@ -49,7 +49,6 @@ export class Compiler { if (success && this.state.compilationStartTime) { this.event.trigger('compilationDuration', [(new Date().getTime()) - this.state.compilationStartTime]) } - console.log('compilationStartTime to null', this.state.compilationStartTime, data) this.state.compilationStartTime = null }) @@ -84,7 +83,6 @@ export class Compiler { internalCompile(files: Source, missingInputs?: string[], timeStamp?: number): void { if(timeStamp != this.state.compilationStartTime && this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger ) { - console.log('aborting compilation', timeStamp, this.state.compilationStartTime) return } this.gatherImports(files, missingInputs, (error, input) => { @@ -104,7 +102,6 @@ export class Compiler { compile(files: Source, target: string): void { this.state.target = target this.state.compilationStartTime = new Date().getTime() - console.log('compiling ' + target + '...', files, this.state.compilationStartTime) this.event.trigger('compilationStarted', []) this.internalCompile(files, null, this.state.compilationStartTime) } @@ -189,7 +186,6 @@ export class Compiler { source: source } } - console.log('compilationFinished ', timeStamp, this.state.compilationStartTime) this.event.trigger('compilationFinished', [true, data, source, input, version]) } } @@ -298,9 +294,8 @@ export class Compiler { this.state.worker.addEventListener('message', (msg: Record<'data', MessageFromWorker>) => { const data: MessageFromWorker = msg.data - console.log('incoming message', data.timestamp, data) if (this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger && data.timestamp !== this.state.compilationStartTime) { - console.log('dropping message', data.timestamp, this.state.compilationStartTime) + // drop message from previous compilation return } switch (data.cmd) { @@ -351,7 +346,6 @@ export class Compiler { return } - console.log('posting message with timestamp ', this.state.compilationStartTime, source) this.state.worker.postMessage({ cmd: 'compile', job: jobs.length - 1, From 96b29690d91b5f2532c49074fcb6b96965a3d989 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Tue, 12 Sep 2023 19:00:07 +0200 Subject: [PATCH 11/11] restore quickfix --- libs/remix-ui/editor/src/lib/providers/quickfixes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-ui/editor/src/lib/providers/quickfixes.ts b/libs/remix-ui/editor/src/lib/providers/quickfixes.ts index 81556e3ef7..89958ed041 100644 --- a/libs/remix-ui/editor/src/lib/providers/quickfixes.ts +++ b/libs/remix-ui/editor/src/lib/providers/quickfixes.ts @@ -187,4 +187,4 @@ export default { nodeType: 'FunctionDefinition' } ] -} \ No newline at end of file +}