diff --git a/.circleci/config.yml b/.circleci/config.yml index 9f0cfcb8227..d1e53fd6945 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,7 +14,7 @@ parameters: default: "" run_file_tests_keyword: type: enum - enum: ["", "ai_panel", "ballot", "ballot_0_4_14", "blockchain", "bottom-bar", "circom", "code_format", "compile_run_widget", "compiler_api", "contract_flattener", "contract_verification", "debugger", "defaultLayout", "deploy_vefiry", "dgit_github", "dgit_local", "editor", "editorHoverContext", "editorReferences", "editor_error_marker", "editor_line_text", "eip1153", "eip7702", "environment-account", "erc721", "etherscan_api", "expandAllFolders", "fileExplorer", "fileManager_api", "file_decorator", "file_explorer_context_menu", "file_explorer_dragdrop", "file_explorer_multiselect", "generalSettings", "gist", "homeTab", "importFromGithub", "layout", "learneth", "libraryDeployment", "matomo-bot-detection", "matomo-consent", "mcp_all_resources", "mcp_all_tools", "mcp_server_complete", "mcp_server_connection", "mcp_server_lifecycle", "mcp_workflow_integration", "metamask", "migrateFileSystem", "noir", "pinned_contracts", "pinned_plugin", "pluginManager", "plugin_api", "providers", "proxy_oz_v4", "proxy_oz_v5", "proxy_oz_v5_non_shanghai_runtime", "publishContract", "quickDapp_metamask", "recorder", "remixd", "runAndDeploy", "script-runner", "search", "signingMessage", "sol2uml", "solidityImport", "solidityUnittests", "specialFunctions", "staticAnalysis", "stressEditor", "templates", "terminal", "transactionExecution", "txListener", "uniswap_v4_core", "url", "usingWebWorker", "verticalIconsPanel", "vm_state", "vyper_api", "walkthrough", "workspace", "workspace_git"] + enum: ["", "ai_panel", "ballot", "ballot_0_4_14", "blockchain", "bottom-bar", "circom", "code_format", "compile_run_widget", "compiler_api", "contract_flattener", "contract_verification", "debugger", "defaultLayout", "deploy_vefiry", "dgit_github", "dgit_local", "editor", "editorHoverContext", "editorReferences", "editor_error_marker", "editor_line_text", "eip1153", "eip7702", "environment-account", "erc721", "etherscan_api", "expandAllFolders", "fileExplorer", "fileManager_api", "file_decorator", "file_explorer_context_menu", "file_explorer_dragdrop", "file_explorer_multiselect", "generalSettings", "gist", "homeTab", "importFromGithub", "layout", "learneth", "libraryDeployment", "matomo-bot-detection", "matomo-consent", "mcp_all_resources", "mcp_all_tools", "mcp_server_complete", "mcp_server_connection", "mcp_server_lifecycle", "mcp_workflow_integration", "metamask", "migrateFileSystem", "noir", "pinned_contracts", "pinned_plugin", "pluginManager", "plugin_api", "providers", "proxy_oz_v4", "proxy_oz_v5", "proxy_oz_v5_non_shanghai_runtime", "publishContract", "quickDapp_metamask", "recorder", "remixd", "runAndDeploy", "script-runner", "search", "signingMessage", "sol2uml", "solidityImport", "solidityUnittests", "specialFunctions", "staticAnalysis", "stressEditor", "template_exp_modal", "terminal", "transactionExecution", "txListener", "uniswap_v4_core", "url", "usingWebWorker", "verticalIconsPanel", "vm_state", "vyper_api", "walkthrough", "workspace", "workspace_git"] default: "" run_flaky_tests: type: boolean diff --git a/apps/remix-ide-e2e/src/tests/ballot.test.ts b/apps/remix-ide-e2e/src/tests/ballot.test.ts index ac8323fb447..292d8c79a92 100644 --- a/apps/remix-ide-e2e/src/tests/ballot.test.ts +++ b/apps/remix-ide-e2e/src/tests/ballot.test.ts @@ -96,16 +96,23 @@ module.exports = { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') - .scrollAndClick('*[data-id="create-remixDefault"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' }) - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="workspace-details-section"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'workspace_remix_default') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="default-workspace-name-span"]') + .assert.textContains('*[data-id="default-workspace-name-span"]', 'WORKSPACE_REMIX_DEFAULT', 'Workspace name is correct') .pause(1000) + .click('*[data-id="validateWorkspaceButton"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .addFile('contracts/lib/storage/src/Storage.sol', { content: storageContract}) .addFile('remappings.txt', { content: 'storage=contracts/lib/storage/src' }) diff --git a/apps/remix-ide-e2e/src/tests/circom.test.ts b/apps/remix-ide-e2e/src/tests/circom.test.ts index 5fda944b711..12039a8686c 100644 --- a/apps/remix-ide-e2e/src/tests/circom.test.ts +++ b/apps/remix-ide-e2e/src/tests/circom.test.ts @@ -13,10 +13,16 @@ module.exports = { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-semaphore"]') - .scrollAndClick('*[data-id="create-semaphore"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .scrollInto('*[data-id="template-category-Circom ZKP"]') + .waitForElementVisible('*[data-id="template-card-semaphore-0"]') + .click('*[data-id="template-card-semaphore-0"]') + .click('*[data-id="validate-semaphoreworkspace-button"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') @@ -33,8 +39,8 @@ module.exports = { 'Should compile a simple circuit using editor play button #group1': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]') - .waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]') - .waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]') + .waitForElementPresent('[data-path="circuits/simple.circom"]') + .waitForElementVisible('[data-path="circuits/simple.circom"]') .waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]') .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') .click('[data-id="compile-action"]') @@ -65,8 +71,8 @@ module.exports = { 'Should compile a simple circuit using compile button in circom plugin #group2': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]') - .waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]') - .waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]') + .waitForElementPresent('[data-path="circuits/simple.circom"]') + .waitForElementVisible('[data-path="circuits/simple.circom"]') .clickLaunchIcon('circuit-compiler') .frame(0) .waitForElementPresent('button[data-id="compile_circuit_btn"]') @@ -114,8 +120,8 @@ module.exports = { 'Should compile a simple circuit using CTRL + S from the editor #group3': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]') - .waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]') - .waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]') + .waitForElementPresent('[data-path="circuits/simple.circom"]') + .waitForElementVisible('[data-path="circuits/simple.circom"]') .waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]') .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') .perform(function () { @@ -134,8 +140,8 @@ module.exports = { 'Should display warnings for compiled circuit without pragma version #group4': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemcircuits/simple.circom"]') - .waitForElementPresent('[data-path="Semaphore - 1/circuits/simple.circom"]') - .waitForElementVisible('[data-path="Semaphore - 1/circuits/simple.circom"]') + .waitForElementPresent('[data-path="circuits/simple.circom"]') + .waitForElementVisible('[data-path="circuits/simple.circom"]') .setEditorValue(warningCircuit) .clickLaunchIcon('circuit-compiler') .frame(0) @@ -184,9 +190,9 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-hashchecker"]') - .scrollAndClick('*[data-id="create-hashchecker"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementPresent('*[data-id="template-card-hashchecker-1"]') + .click('*[data-id="template-card-hashchecker-1"]') + .click('*[data-id="validate-hashcheckerworkspace-button"]') .pause(100) .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]') @@ -204,8 +210,8 @@ module.exports = { 'Should run groth16 trusted setup script for hash checker #group5': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemscripts/groth16/groth16_trusted_setup.ts"]') - .waitForElementPresent('[data-path="Hash Checker - 1/scripts/groth16/groth16_trusted_setup.ts"]') - .waitForElementVisible('[data-path="Hash Checker - 1/scripts/groth16/groth16_trusted_setup.ts"]') + .waitForElementPresent('[data-path="scripts/groth16/groth16_trusted_setup.ts"]') + .waitForElementVisible('[data-path="scripts/groth16/groth16_trusted_setup.ts"]') .waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]') .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') .click('[data-id="compile-action"]') @@ -218,8 +224,8 @@ module.exports = { 'Should run groth16 zkproof script for hash checker #group5': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemscripts/groth16/groth16_zkproof.ts"]') - .waitForElementPresent('[data-path="Hash Checker - 1/scripts/groth16/groth16_zkproof.ts"]') - .waitForElementVisible('[data-path="Hash Checker - 1/scripts/groth16/groth16_zkproof.ts"]') + .waitForElementPresent('[data-path="scripts/groth16/groth16_zkproof.ts"]') + .waitForElementVisible('[data-path="scripts/groth16/groth16_zkproof.ts"]') .waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]') .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') .click('[data-id="compile-action"]') @@ -238,8 +244,8 @@ module.exports = { 'Should run plonk trusted setup script for hash checker #group6': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemscripts/plonk/plonk_trusted_setup.ts"]') - .waitForElementPresent('[data-path="Hash Checker - 1/scripts/plonk/plonk_trusted_setup.ts"]') - .waitForElementVisible('[data-path="Hash Checker - 1/scripts/plonk/plonk_trusted_setup.ts"]') + .waitForElementPresent('[data-path="scripts/plonk/plonk_trusted_setup.ts"]') + .waitForElementVisible('[data-path="scripts/plonk/plonk_trusted_setup.ts"]') .waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]') .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') .click('[data-id="compile-action"]') @@ -252,8 +258,8 @@ module.exports = { 'Should run plonk zkproof script for hash checker #group6': function (browser: NightwatchBrowser) { browser .click('[data-id="treeViewLitreeViewItemscripts/plonk/plonk_zkproof.ts"]') - .waitForElementPresent('[data-path="Hash Checker - 1/scripts/plonk/plonk_zkproof.ts"]') - .waitForElementVisible('[data-path="Hash Checker - 1/scripts/plonk/plonk_zkproof.ts"]') + .waitForElementPresent('[data-path="scripts/plonk/plonk_zkproof.ts"]') + .waitForElementVisible('[data-path="scripts/plonk/plonk_zkproof.ts"]') .waitForElementPresent('[data-id="verticalIconsKindcircuit-compiler"]') .waitForElementVisible('[data-id="verticalIconsKindcircuit-compiler"]') .click('[data-id="compile-action"]') diff --git a/apps/remix-ide-e2e/src/tests/erc721.test.ts b/apps/remix-ide-e2e/src/tests/erc721.test.ts index 9f24075bb6d..5a5245c5a22 100644 --- a/apps/remix-ide-e2e/src/tests/erc721.test.ts +++ b/apps/remix-ide-e2e/src/tests/erc721.test.ts @@ -15,16 +15,18 @@ module.exports = { 'Deploy SampleERC721 whose bytecode is very similar to ERC721': function (browser: NightwatchBrowser) { browser.clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - // create contract - .waitForElementPresent('*[data-id="create-hashchecker"]') - .scrollAndClick('*[data-id="create-ozerc721"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' }) - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .click('*[data-id="contract-wizard-contract-type-dropdown"]') + .click('*[data-id="contract-wizard-contract-type-dropdown-item-erc721"]') + .click('*[data-id="contract-wizard-validate-workspace-button"]') .pause(100) .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') diff --git a/apps/remix-ide-e2e/src/tests/noir.test.ts b/apps/remix-ide-e2e/src/tests/noir.test.ts index 6544ebeb487..a5e5262f665 100644 --- a/apps/remix-ide-e2e/src/tests/noir.test.ts +++ b/apps/remix-ide-e2e/src/tests/noir.test.ts @@ -14,9 +14,9 @@ module.exports = { .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-multNr"]') - .scrollAndClick('*[data-id="create-multNr"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementPresent('*[data-id="template-card-multNr-0"]') + .scrollAndClick('*[data-id="template-card-multNr-0"]') + .click('*[data-id="validate-multNrworkspace-button"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc/main.nr"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') diff --git a/apps/remix-ide-e2e/src/tests/pinned_contracts.test.ts b/apps/remix-ide-e2e/src/tests/pinned_contracts.test.ts index aca6c2f6797..e3885b180cb 100644 --- a/apps/remix-ide-e2e/src/tests/pinned_contracts.test.ts +++ b/apps/remix-ide-e2e/src/tests/pinned_contracts.test.ts @@ -43,15 +43,23 @@ module.exports = { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') - .scrollAndClick('*[data-id="create-remixDefault"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' }) - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="workspace-details-section"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'workspace_remix_default') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="default-workspace-name-span"]') + .assert.textContains('*[data-id="default-workspace-name-span"]', 'WORKSPACE_REMIX_DEFAULT', 'Workspace name is correct') + .pause(1000) + .click('*[data-id="validateWorkspaceButton"]') .clickLaunchIcon('udapp') .assert.elementPresent('*[data-id="deployedContracts"]') .assert.textContains('*[data-id="deployedContractsBadge"]', '0') diff --git a/apps/remix-ide-e2e/src/tests/script-runner.test.ts b/apps/remix-ide-e2e/src/tests/script-runner.test.ts index 3359533d413..5a57f164b62 100644 --- a/apps/remix-ide-e2e/src/tests/script-runner.test.ts +++ b/apps/remix-ide-e2e/src/tests/script-runner.test.ts @@ -72,10 +72,16 @@ const tests = { .pause(2000) .waitForElementVisible('*[data-id="workspacesSelect"]') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-semaphore"]') - .scrollAndClick('*[data-id="create-semaphore"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .scrollInto('*[data-id="template-category-Circom ZKP"]') + .waitForElementVisible('*[data-id="template-card-semaphore-0"]') + .click('*[data-id="template-card-semaphore-0"]') + .click('*[data-id="validate-semaphoreworkspace-button"]') // .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]') .waitForElementVisible({ locateStrategy: 'xpath', @@ -95,9 +101,9 @@ const tests = { .click('*[data-id="workspacesSelect"]') .click('*[data-id="workspacecreate"]') // .click('*[data-id="workspacesSelect"]') - .waitForElementVisible('*[data-id="create-introToEIP7702"]') - .click('*[data-id="create-introToEIP7702"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-card-simpleEip7702-2"]') + .click('*[data-id="template-card-simpleEip7702-2"]') + .click('*[data-id="validate-simpleEip7702workspace-button"]') // .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Example7702.sol"]') .waitForElementVisible({ locateStrategy: 'xpath', @@ -107,8 +113,8 @@ const tests = { locateStrategy: 'xpath', selector: "//li[@data-id='UIScriptRunner' and @role='tab']" }) - .waitForElementVisible('[data-id="sr-notloaded-default"]') - .waitForElementVisible('[data-id="sr-loaded-ethers6"]') + .scrollAndClick('*[data-id="sr-notloaded-ethers6"]') + .scrollInto('[data-id="sr-notloaded-default"]') }, 'reset to default after template': function (browser: NightwatchBrowser) { browser diff --git a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts index a2307cc2b68..c6a6f383171 100644 --- a/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts +++ b/apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts @@ -179,18 +179,21 @@ module.exports = { .clickLaunchIcon('filePanel') // creating a new workspace .click('*[data-id="workspacesSelect"]') - .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') - .scrollAndClick('*[data-id="create-remixDefault"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .click('input[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_new') .pause(2000) - .getValue('input[data-id="modalDialogCustomPromptTextCreate"]', (result) => { - console.log(result) - browser.assert.equal(result.value, 'workspace_new') - }) - .modalFooterOKClick('TemplatesSelection') + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'workspace_new') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="default-workspace-name-span"]') + .assert.textContains('*[data-id="default-workspace-name-span"]', 'WORKSPACE_NEW', 'Workspace name is correct') + .click('*[data-id="validateWorkspaceButton"]') .pause(3000) .currentWorkspaceIs('workspace_new') .expandAllFolders() diff --git a/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts b/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts index 97c2554d22f..fe21ddc4df0 100644 --- a/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts +++ b/apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts @@ -59,11 +59,11 @@ module.exports = { .waitForElementVisible('span#ssaRemixtab') .click('span#ssaRemixtab') .waitForElementContainsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '384') - + .click('label[id="headingshowLibWarnings"]') .pause(1000) .waitForElementContainsText('span#ssaRemixtab > *[data-id="RemixStaticAnalysisErrorCount', '3') - + .end() } } diff --git a/apps/remix-ide-e2e/src/tests/stressEditor.test.ts b/apps/remix-ide-e2e/src/tests/stressEditor.test.ts index 524c441d77d..b8060916570 100644 --- a/apps/remix-ide-e2e/src/tests/stressEditor.test.ts +++ b/apps/remix-ide-e2e/src/tests/stressEditor.test.ts @@ -22,166 +22,166 @@ module.exports = { }) } const fillContent = (content, mul) => { - let localContent = content - mul = 3 * mul - for (let k = 0 ; k < mul; k++) { - localContent += content - } - return localContent + let localContent = content + mul = 3 * mul + for (let k = 0 ; k < mul; k++) { + localContent += content + } + return localContent } browser.clickLaunchIcon('filePanel') - .perform((done) => { + .perform((done) => { const i = 0 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 1 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 2 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 3 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 4 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 5 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 6 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 7 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 8 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }).perform((done) => { + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }).perform((done) => { const i = 9 const localContent = fillContent(content, i) contents[i] = localContent const name = 'test_' + i + '.sol' browser.click('[data-id="fileExplorerNewFilecreateNewFile"]') - .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) - .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) - .getText('.remix_ui_terminal_block', (result) => { - console.log(result) - }) - .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) - .setEditorValue(localContent) - done() - }) - .pause(10000) + .waitForElementContainsText('*[data-id$="fileExplorerTreeItemInput"]', '', 60000) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', name) + .sendKeys('*[data-id$="fileExplorerTreeItemInput"]', browser.Keys.ENTER) + .getText('.remix_ui_terminal_block', (result) => { + console.log(result) + }) + .waitForElementVisible(`li[data-id="treeViewLitreeViewItem${name}"]`, 60000) + .setEditorValue(localContent) + done() + }) + .pause(10000) .refreshPage() .perform(done => checkContent(0, done)) .perform(done => checkContent(1, done)) @@ -202,12 +202,12 @@ const content = ` pragma solidity >=0.7.0 <0.9.0; -/** +/** * @title Ballot * @dev Implements voting process along with vote delegation| */ contract Ballot { - + struct Voter { uint weight; // weight is accumulated by delegation bool voted; // if true, that person already voted @@ -216,7 +216,7 @@ contract Ballot { } struct Proposal { - // If you can limit the length to a certain number of bytes, + // 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 @@ -230,7 +230,7 @@ contract Ballot { function () test { - /** + /** * @dev Create a new ballot to choose one of 'proposalNames'. * @param proposalNames names of proposals */ @@ -248,8 +248,8 @@ function () test { })); } } - - /** + + /** * @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'. * @param voter address of voter */ @@ -312,7 +312,7 @@ function () test { 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 */ @@ -328,7 +328,7 @@ function () test { } } - /** + /** * @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 */ diff --git a/apps/remix-ide-e2e/src/tests/template_exp_modal.test.ts b/apps/remix-ide-e2e/src/tests/template_exp_modal.test.ts new file mode 100644 index 00000000000..04b6330db72 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/template_exp_modal.test.ts @@ -0,0 +1,370 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done) + }, + 'Create blank workspace': function (browser: NightwatchBrowser) { + browser + .refreshPage() + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-card-blank-1"]') + .click('*[data-id="template-card-blank-1"]') + .waitForElementVisible('*[data-id="workspace-name-blank-input"]') + .click('*[data-id="workspace-name-blank-input"]') + .pause(1000) + .setValue('*[data-id="workspace-name-blank-input"]', 'Test Blank Workspace') + .click('*[data-id="validate-blankworkspace-button"]') + .pause(1000) + .assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test Blank Workspace', 'Workspace name is correct') + .isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]') + .isVisible('*[data-id="treeViewDivDraggableItem.prettierrc.json"]') + .execute(function () { + const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]') + return fileList.getElementsByTagName('li').length; + }, [], function (result) { + browser.assert.equal(result.value, 3, 'Incorrect number of files in workspace'); + }); + }, + 'Create Pectra 7702 based workspace': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-card-simpleEip7702-2"]') + .click('*[data-id="template-card-simpleEip7702-2"]') + .waitForElementVisible('*[data-id="workspace-name-simpleEip7702-input"]') + .click('*[data-id="workspace-name-simpleEip7702-input"]') + .setValue('*[data-id="workspace-name-simpleEip7702-input"]', 'Test Pectra 7702 Workspace') + .click('*[data-id="validate-simpleEip7702workspace-button"]') + .pause(1000) + .assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test Pectra 7702 Workspace', 'Workspace name is correct') + .isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]') + .waitForElementVisible('*[data-id="treeViewDivDraggableItemcontracts"]') + .isVisible('*[data-id="treeViewDivDraggableItemcontracts/Example7702.sol"]') + .waitForElementNotPresent('*[data-id="treeViewDivDraggableItemtests"]') + }, + 'Create Semaphore based workspace': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .scrollInto('*[data-id="template-category-Circom ZKP"]') + .waitForElementVisible('*[data-id="template-card-semaphore-0"]') + .click('*[data-id="template-card-semaphore-0"]') + .waitForElementVisible('*[data-id="workspace-name-semaphore-input"]') + .click('*[data-id="workspace-name-semaphore-input"]') + .setValue('*[data-id="workspace-name-semaphore-input"]', 'Test Semaphore Workspace') + .click('*[data-id="validate-semaphoreworkspace-button"]') + .pause(1000) + .assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test Semaphore Workspace', 'Workspace name is correct') + .isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]') + .waitForElementVisible('*[data-id="treeViewDivDraggableItemcircuits"]') + .isVisible('*[data-id="treeViewDivDraggableItemcircuits/semaphore.circom"]') + .waitForElementNotPresent('*[data-id="treeViewDivDraggableItemtests"]') + .click('*[data-id="treeViewDivDraggableItemcircuits/semaphore.circom"]') + .waitForElementVisible('*[data-id="compile-action"]') + .click('*[data-id="compile-action"]') + .pause(3000) + .waitForElementContainsText('*[data-id="terminalJournal"]', 'Everything went okay', 60000) + .clickLaunchIcon('filePanel') + .waitForElementVisible('*[data-id="treeViewDivDraggableItemcircuits/.bin/semaphore_js"]') + }, + 'Search for Noir Simple Multiplier template': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-search-input"]') + .click('*[data-id="template-explorer-search-input"]') + .setValue('*[data-id="template-explorer-search-input"]', 'Simple Multiplier') + .pause(1000) + .waitForElementVisible('*[data-id="template-card-multNr-0"]') + .click('*[data-id="template-card-multNr-0"]') + .waitForElementVisible('*[data-id="workspace-name-multNr-input"]') + .click('*[data-id="workspace-name-multNr-input"]') + .setValue('*[data-id="workspace-name-multNr-input"]', 'Test Simple Multiplier Workspace') + .click('*[data-id="validate-multNrworkspace-button"]') + .waitForElementVisible('*[data-id="treeViewDivDraggableItemNargo.toml"]') + .isVisible('*[data-id="treeViewDivDraggableItemsrc"]') + .isVisible('*[data-id="treeViewDivDraggableItemsrc/main.nr"]') + .click('*[data-id="treeViewDivDraggableItemsrc/main.nr"]') + .waitForElementVisible('*[data-id="compile-action"]') + }, + 'Create OpenZeppelin ERC20 template with Contract Wizard': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .waitForElementVisible('*[data-id="contract-wizard-token-name-input"]') + .setValue('*[data-id="contract-wizard-token-name-input"]', 'TestToken') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .waitForElementVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/TestToken.sol"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/TestToken.sol"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`contract TestToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable {`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') + .click('*[data-id="compile-action"]') + .waitForElementVisible('#verticalIconsKindsolidity > i.remixui_status.fas.fa-check-circle.text-success.remixui_statusCheck') + .pause(2000) + }, + 'Create OpenZeppelin ERC721 template with Contract Wizard': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('filePanel') + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .waitForElementVisible('*[data-id="contract-wizard-token-name-input"]') + .setValue('*[data-id="contract-wizard-token-name-input"]', 'Test721Token') + .click('*[data-id="contract-wizard-contract-type-dropdown"]') + .click('*[data-id="contract-wizard-contract-type-dropdown-item-erc721"]') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .pause(1000) + .perform(function () { + browser.isVisible('*[data-id="treeViewUltreeViewMenu"]', function (result) { + if (result.value === false) { + browser.clickLaunchIcon('filePanel') + } + }) + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemremix.config.json"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/Test721Token.sol"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/Test721Token.sol"]') + .pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`contract Test721Token is ERC721, ERC721Pausable, Ownable, ERC721Burnable {`) !== -1, + 'Incorrect content') + }) + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') + .click('*[data-id="compile-action"]') + .waitForElementVisible('#verticalIconsKindsolidity > i.remixui_status.fas.fa-check-circle.text-success.remixui_statusCheck') + .clickLaunchIcon('solidity') + .isVisible('*[data-id="compilation-details"]') + }, + 'Use default workspace and add github actions template': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="home"]') + .click('*[data-id="landingPageImportFromTemplate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .click('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'Test Default Workspace') + .click('*[data-id="initGitRepositoryLabel"') + .click('*[data-id="validateWorkspaceButton"]') + .pause(1000) + .assert.textContains('*[data-id="workspacesSelect-togglerText"]', 'Test Default Workspace', 'Workspace name is correct') + .perform(function () { + browser.isVisible('*[data-id="treeViewUltreeViewMenu"]', function (result) { + if (result.value === false) { + browser.clickLaunchIcon('filePanel') + } + }) + }) + .isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') + .click('*[data-id="workspacesSelect"]') + .click('*[data-id="workspacecreate"]') + .waitForElementPresent('*[data-id="template-card-runSolidityUnittestingAction-1"]') + .scrollInto('*[data-id="template-card-runSolidityUnittestingAction-1"]') + .click('*[data-id="template-card-runSolidityUnittestingAction-1"]') + .pause(3000) + .waitForElementVisible('*[data-id="treeViewDivtreeViewItem.github"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItem.github/workflows"]') + .click('*[data-id="treeViewLitreeViewItem.github/workflows"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItem.github/workflows/run-solidity-unittesting.yml"]') + .click('*[data-id="treeViewLitreeViewItem.github/workflows/run-solidity-unittesting.yml"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`name: Running Solidity Unit Tests`) !== -1, + 'Correct content') + }) + }, + 'Add Mocha Chai Test Workflow template': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="workspacesSelect"]') + .click('*[data-id="workspacecreate"]') + .scrollInto('*[data-id="template-category-GitHub Actions"]') + .waitForElementVisible('*[data-id="template-card-runJsTestAction-0"]') + .click('*[data-id="template-card-runJsTestAction-0"]') + .waitForElementVisible('*[data-id="treeViewDivtreeViewItem.github"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItem.github/workflows"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItem.github/workflows/run-js-test.yml"]') + .click('*[data-id="treeViewLitreeViewItem.github/workflows/run-js-test.yml"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`name: Running Mocha Chai Solidity Unit Tests`) !== -1, + 'Correct content') + }) + }, + 'Add Slither Workflow template': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="workspacesSelect"]') + .click('*[data-id="workspacecreate"]') + .scrollInto('*[data-id="template-category-GitHub Actions"]') + .waitForElementVisible('*[data-id="template-card-runSlitherAction-2"]') + .click('*[data-id="template-card-runSlitherAction-2"]') + .waitForElementVisible('*[data-id="treeViewDivtreeViewItem.github"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItem.github/workflows"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItem.github/workflows/run-slither-action.yml"]') + .click('*[data-id="treeViewLitreeViewItem.github/workflows/run-slither-action.yml"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`name: Slither Analysis`) !== -1, + 'Correct content') + }) + }, + 'Add Create2 Solidity Factory template': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="workspacesSelect"]') + .click('*[data-id="workspacecreate"]') + .scrollInto('*[data-id="template-category-Solidity CREATE2"]') + .waitForElementVisible('*[data-id="template-card-contractCreate2Factory-0"]') + .click('*[data-id="template-card-contractCreate2Factory-0"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/libs"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/libs/create2-factory.sol"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`contract Create2Factory {`) !== -1, + 'Correct content') + }) + }, + 'Add Contract Deployer Scripts template': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="workspacesSelect"]') + .click('*[data-id="workspacecreate"]') + .scrollInto('*[data-id="template-category-Solidity CREATE2"]') + .waitForElementVisible('*[data-id="template-card-contractDeployerScripts-1"]') + .click('*[data-id="template-card-contractDeployerScripts-1"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/contract-deployer/basic-contract-deploy.ts"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`export const deploy = async (contractName: string, args: Array, accountIndex?: number): Promise => {`) !== -1, + 'Correct content') + }) + }, + 'Add Etherscan scripts template': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="workspacesSelect"]') + .click('*[data-id="workspacecreate"]') + .scrollInto('*[data-id="template-category-Contract Verification"]') + .waitForElementVisible('*[data-id="template-card-etherscanScripts-0"]') + .click('*[data-id="template-card-etherscanScripts-0"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/etherscan"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/etherscan/receiptGuidScript.ts"]') + .click('*[data-id="treeViewLitreeViewItemscripts/etherscan/receiptGuidScript.ts"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`export const receiptStatus = async (apikey: string, guid: string, isProxyContract?: boolean) => {`) !== -1, + 'Correct content') + }) + }, + 'Confirm that editing workspace name works': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="validateWorkspaceButton"]') + .pause(1000) + .waitForElementVisible('*[data-id="landingPageImportFromTemplate"]') + .click('*[data-id="landingPageImportFromTemplate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="default-workspace-name-span"]') + .waitForElementVisible('*[data-id="default-workspace-name-span"]') + .assert.textEquals('*[data-id="default-workspace-name-span"]', 'BASIC - 1', 'Workspace name is correct') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .click('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'ChangedWorkspaceName ') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="default-workspace-name-span"]') + .click('*[data-id="validateWorkspaceButton"]') + .currentWorkspaceIs('ChangedWorkspaceName') + .switchWorkspace('Basic') + }, + 'Creating a workspace with the same name as an existing one should show an error': function (browser: NightwatchBrowser) { + browser + .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .click('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'Basic') + .click('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="validateWorkspaceButton"]') + .pause(1000) + .waitForElementVisible('*[data-id="workspaceAlreadyExistsErrorModalDialogModalTitle-react"]') + .click('*[data-id="workspaceAlreadyExistsError-modal-footer-ok-react"]') + .end() + } +} diff --git a/apps/remix-ide-e2e/src/tests/templates.test.ts b/apps/remix-ide-e2e/src/tests/templates.test.ts deleted file mode 100644 index 5b6c13eb237..00000000000 --- a/apps/remix-ide-e2e/src/tests/templates.test.ts +++ /dev/null @@ -1,265 +0,0 @@ -'use strict' - -import { NightwatchBrowser } from 'nightwatch' -import init from '../helpers/init' - -const templatesToCheck = [ - { - value: "remixDefault", - displayName: "Basic", - checkSelectors: [ - '*[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]', - '*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]' - ] - }, - { - value: "blank", - displayName: "Blank", - checkSelectors: ['*[data-id="treeViewLitreeViewItem.prettierrc.json"]'] - }, - { - value: "simpleEip7702", - displayName: "Simple EIP-7702", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcontracts/Example7702.sol"]'] - }, - { - value: "introToEIP7702", - displayName: "Intro to EIP-7702", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcontracts/Spender.sol"]'] - }, - { - value: "accountAbstraction", - displayName: "Account Abstraction", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemfunding.json"]'] - }, - { - value: "ozerc20", - displayName: "ERC20", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]'], - clickOk: true - }, - { - value: "ozerc721", - displayName: "ERC721", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]'], - clickOk: true - }, - { - value: "ozerc1155", - displayName: "ERC1155", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]'], - clickOk: true - }, - { - value: "zeroxErc20", - displayName: "ERC20", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcontracts/SampleERC20.sol"]'] - }, - { - value: "gnosisSafeMultisig", - displayName: "MultiSig", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcontracts/MultisigWallet.sol"]'] - }, - { - value: "semaphore", - displayName: "Semaphore", - checkSelectors: ['*[data-id="treeViewLitreeViewItemcircuits/semaphore.circom"]'] - }, - { - value: "hashchecker", - displayName: "Hash", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemcircuits/calculate_hash.circom"]'] - }, - { - value: "rln", - displayName: "Rate", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemcircuits/rln.circom"]'] - }, - { - value: "multNr", - displayName: "Multiplier", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemsrc/main.nr"]'] - }, - { - value: "sindriScripts", - displayName: "Add Sindri ZK scripts", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemscripts/sindri/run_compile.ts"]'] - }, - { - value: "uniswapV4Template", - displayName: "v4 Template", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemfoundry.toml"]'] - }, - { - value: "contractCreate2Factory", - displayName: "Add Create2 Solidity factory", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemcontracts/libs/create2-factory.sol"]'] - }, - { - value: "contractDeployerScripts", - displayName: "Add contract deployer scripts", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemscripts/contract-deployer/basic-contract-deploy.ts"]'] - }, - { - value: "etherscanScripts", - displayName: "Add Etherscan scripts", - checkSelectors: ['*[data-id="treeViewDivtreeViewItemscripts/etherscan/receiptGuidScript.ts"]'] - }, - { - value: "runJsTestAction", - displayName: "Mocha Chai Test Workflow", - checkSelectors: ['*[data-id="treeViewDivtreeViewItem.github/workflows/run-js-test.yml"]'] - }, - { - value: "runSolidityUnittestingAction", - displayName: "Solidity Test Workflow", - checkSelectors: ['*[data-id="treeViewDivtreeViewItem.github/workflows/run-solidity-unittesting.yml"]'] - }, - { - value: "runSlitherAction", - displayName: "Slither Workflo", - checkSelectors: ['*[data-id="treeViewDivtreeViewItem.github/workflows/run-slither-action.yml"]'] - } -] - -function setTemplateOptions(browser: NightwatchBrowser, opts: { [key: string]: any }) { - if (opts.mintable) browser.click('*[data-id="featureTypeMintable"]') - if (opts.burnable) browser.click('*[data-id="featureTypeBurnable"]') - if (opts.pausable) browser.click('*[data-id="featureTypePausable"]') - if (opts.upgradeability === 'transparent') browser.click('*[data-id="upgradeTypeTransparent"]') - if (opts.upgradeability === 'uups') browser.click('*[data-id="upgradeTypeUups"]') -} - -function openTemplatesExplorer(browser: NightwatchBrowser) { - browser - .click('*[data-id="workspacesSelect"]') - .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') -} - -function runTemplateChecks( - browser: NightwatchBrowser, - start: number, - end: number, - mode: 'create' | 'add' = 'create', -) { - templatesToCheck.slice(start, end).forEach(({ value, displayName, checkSelectors, clickOk }) => { - console.log(`Checking template: ${value} in ${mode} mode`) - openTemplatesExplorer(browser) - - if (mode === 'create') { - browser - .waitForElementVisible(`[data-id="create-${value}"]`, 5000) - .click(`[data-id="create-${value}"]`) - } else { - browser - .waitForElementVisible(`[data-id="create-blank"]`, 5000) - .click(`[data-id="create-blank"]`) - } - - browser - .waitForElementVisible('*[data-id="TemplatesSelection-modal-footer-ok-react"]', 2000) - .click('*[data-id="TemplatesSelection-modal-footer-ok-react"]') - .pause(1000) - - if (mode === 'add') { - browser.element('css selector', `[data-id="add-${value}"]`, result => { - console.log(`Element add-${value} status: ${result.status}`) - if (result.status == 0) { - openTemplatesExplorer(browser) - browser - .waitForElementVisible(`[data-id="add-${value}"]`, 5000) - .click(`[data-id="add-${value}"]`) - if (clickOk) { - browser - .waitForElementVisible('*[data-id="TemplatesSelection-modal-footer-ok-react"]', 2000) - .click('*[data-id="TemplatesSelection-modal-footer-ok-react"]') - } - - checkSelectors.forEach(selector => { - console.log(`Checking selector: ${selector}`) - browser.waitForElementVisible(selector, 30000) - }) - } - }) - } else { - browser - .useXpath() - .waitForElementVisible(`//div[contains(@data-id, "dropdown-content") and contains(., "${displayName}")]`, 10000) - .useCss() - - checkSelectors.forEach(selector => { - console.log(`Checking selector: ${selector}`) - browser.waitForElementVisible(selector, 30000) - }) - } - }) -} - -function testTemplateOptions(browser: NightwatchBrowser, mode: 'create' | 'add') { - openTemplatesExplorer(browser) - - const selector = mode === 'create' ? '[data-id="create-ozerc20"]' : '[data-id="add-ozerc20"]' - - browser - .waitForElementVisible(selector, 5000) - .click(selector) - - browser - .waitForElementVisible('*[data-id="TemplatesSelection-modal-footer-ok-react"]', 2000) - - // Simulate user selecting options - setTemplateOptions(browser, { mintable: true, burnable: true, upgradeability: 'uups' }) - - // Confirm selection - browser - .click('*[data-id="TemplatesSelection-modal-footer-ok-react"]') - - // Verify expected file was created - browser - .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]', 10000) - .click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') - .pause(1000) - .getEditorValue(editorValue => { - const expected = 'contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable' - if (editorValue.includes(expected)) { - console.log(`✅ Template with options applied successfully (${mode})`) - browser.assert.ok(true, `Template with options applied successfully (${mode})`) - } else { - browser.assert.fail(`❌ Template with options was not applied correctly (${mode})`) - } - }) -} - -const tests = { - '@disabled': true, - before: function (browser: NightwatchBrowser, done: VoidFunction) { - init(browser, done) - }, - openFilePanel: function (browser: NightwatchBrowser) { - browser.clickLaunchIcon('filePanel') - }, - 'Loop through templates and click create #group1': function (browser) { - runTemplateChecks(browser, 0, templatesToCheck.length, 'create') - }, - 'Loop through templates and click add buttons #group1': function (browser) { - runTemplateChecks(browser, 0, templatesToCheck.length, 'add') - }, - 'Test template options with create #group2': function (browser: NightwatchBrowser) { - testTemplateOptions(browser, 'create') - }, - - 'Test template options with add #group2': function (browser: NightwatchBrowser) { - openTemplatesExplorer(browser) - browser - .waitForElementVisible(`[data-id="create-remixDefault"]`, 5000) - .click(`[data-id="create-remixDefault"]`) - .waitForElementVisible('*[data-id="TemplatesSelection-modal-footer-ok-react"]', 2000) - .click('*[data-id="TemplatesSelection-modal-footer-ok-react"]') - .pause(1000) - - testTemplateOptions(browser, 'add') - } -} - -module.exports = {} // browser.browserName.includes('chrome') ? {} : tests diff --git a/apps/remix-ide-e2e/src/tests/workspace.test.ts b/apps/remix-ide-e2e/src/tests/workspace.test.ts index b266bfb781d..7ec9469b6ed 100644 --- a/apps/remix-ide-e2e/src/tests/workspace.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace.test.ts @@ -35,20 +35,25 @@ module.exports = { 'Should create Remix default workspace with files #group1': function (browser: NightwatchBrowser) { browser - .waitForElementVisible('*[data-id="workspacesSelect"]') + .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') - .waitForElementVisible('*[data-id="workspacecreate"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') - .scrollAndClick('*[data-id="create-remixDefault"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_remix_default') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_remix_default' }) - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="workspace-details-section"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'workspace_remix_default') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="default-workspace-name-span"]') + .assert.textContains('*[data-id="default-workspace-name-span"]', 'WORKSPACE_REMIX_DEFAULT', 'Workspace name is correct') .pause(1000) - .clickLaunchIcon('filePanel') + .click('*[data-id="validateWorkspaceButton"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/2_Owner.sol"]') @@ -92,19 +97,20 @@ module.exports = { 'Should create blank workspace with no files #group1': function (browser: NightwatchBrowser) { browser .click('*[data-id="workspacesSelect"]') - .waitForElementVisible('*[data-id="workspacecreate"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementVisible('*[data-id="create-blank"]') - .click('*[data-id="create-blank"]') - .waitForElementPresent('*[data-id="TemplatesSelectionModalDialogModalTitle-react"]') - .assert.containsText('*[data-id="TemplatesSelectionModalDialogModalTitle-react"]', 'Create Workspace Using Template') - // .scrollAndClick('*[data-id="create-blank"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank') - // eslint-disable-next-line dot-notation - // .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' }) - .click('*[data-id="TemplatesSelection-modal-footer-ok-react"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-card-blank-1"]') + .click('*[data-id="template-card-blank-1"]') + .waitForElementVisible('*[data-id="generic-template-section-blank"]') + .waitForElementVisible('*[data-id="workspace-name-blank-input"]') + .click('*[data-id="workspace-name-blank-input"]') + .setValue('*[data-id="workspace-name-blank-input"]', 'workspace_blank') + .assert.valueEquals('*[data-id="workspace-name-blank-input"]', 'workspace_blank', 'Workspace name is correct') + .pause(1000) + .click('*[data-id="validate-blankworkspace-button"]') .currentWorkspaceIs('workspace_blank') .waitForElementPresent('*[data-id="treeViewUltreeViewMenu"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]') @@ -113,7 +119,7 @@ module.exports = { const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]') return fileList.getElementsByTagName('li').length; }, [], function (result) { - browser.assert.equal(result.value, 2, 'Incorrect number of files'); + browser.assert.equal(result.value, 3, 'Incorrect number of files in workspace'); }); }, @@ -121,22 +127,42 @@ module.exports = { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') - .waitForElementVisible('*[data-id="workspacecreate"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-ozerc20"]') - .scrollAndClick('*[data-id="create-ozerc20"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc20') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc20' }) - .modalFooterOKClick('TemplatesSelection') - .pause(100) - .clickLaunchIcon('filePanel') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .waitForElementVisible('*[data-id="contract-wizard-token-name-input"]') + .click('*[data-id="contract-wizard-token-name-input"]') + .setValue('*[data-id="contract-wizard-token-name-input"]', 'TestToken') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .perform(function () { + browser.isVisible('*[data-id="treeViewUltreeViewMenu"]', function (result) { + browser.assert.not.ok(result.value as any, 'Scripts folder is not visible') + .clickLaunchIcon('filePanel') + }) + }) + .isVisible('*[data-id="treeViewLitreeViewItemremix.config.json"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') - .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') + .isVisible('*[data-id="treeViewLitreeViewItemcontracts/TestToken.sol"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/TestToken.sol"]') + .pause(1000) + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`contract TestToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable {`) !== -1, + 'Correct content') + }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') - + .click('*[data-id="compile-action"]') + .waitForElementVisible('#verticalIconsKindsolidity > i.remixui_status.fas.fa-check-circle.text-success.remixui_statusCheck') + .pause(1000) + // check js and ts files are not transformed .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') .waitForElementPresent({ @@ -158,38 +184,54 @@ module.exports = { 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') - .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/MyToken_test.sol"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/TestToken_test.sol"]') }, 'Should create ERC721 workspace with files #group1': function (browser: NightwatchBrowser) { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') - .waitForElementVisible('*[data-id="workspacecreate"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-ozerc721"]') - .scrollAndClick('*[data-id="create-ozerc721"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc721') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' }) - .modalFooterOKClick('TemplatesSelection') - .pause(100) - .clickLaunchIcon('filePanel') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .waitForElementVisible('*[data-id="contract-wizard-token-name-input"]') + .setValue('*[data-id="contract-wizard-token-name-input"]', 'Test721Token') + .click('*[data-id="contract-wizard-contract-type-dropdown"]') + .click('*[data-id="contract-wizard-contract-type-dropdown-item-erc721"]') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .perform(function() { + browser.isVisible('*[data-id="treeViewUltreeViewMenu"]', function (result) { + browser.assert.not.ok(result.value as any, 'Scripts folder is not visible') + .clickLaunchIcon('filePanel') + }) + }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') - .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') + .isVisible('*[data-id="treeViewDivDraggableItemremix.config.json"]') + .isVisible('*[data-id="treeViewLitreeViewItemcontracts/Test721Token.sol"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]') + .click('*[data-id="treeViewLitreeViewItem.prettierrc.json"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/Test721Token.sol"]') + .getEditorValue((content) => { + browser.assert.ok(content.indexOf(`contract Test721Token is ERC721, ERC721Pausable, Ownable, ERC721Burnable {`) !== -1, + 'Incorrect content') + }) + .pause(300) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') - .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') + // .waitForElementVisible('*[data-id="treeViewDivtreeViewItemscripts/ethers-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') - .waitForElementPresent({ - selector: "//div[contains(@class, 'view-line') and contains(.//span, './ethers-lib')]", - locateStrategy: 'xpath' - }) .getEditorValue((content) => { browser.assert.ok(content.indexOf(`import { deploy } from './ethers-lib'`) !== -1, - 'Incorrect content') + 'Correct content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/ethers-lib.ts"]') @@ -202,26 +244,42 @@ module.exports = { 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') - .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/MyToken_test.sol"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests/Test721Token_test.sol"]') }, 'Should create ERC1155 workspace with files #group1': function (browser: NightwatchBrowser) { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-ozerc1155"]') - .scrollAndClick('*[data-id="create-ozerc1155"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_erc1155') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc1155' }) - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .click('*[data-id="contract-wizard-contract-type-dropdown"]') + .click('*[data-id="contract-wizard-contract-type-dropdown-item-erc1155"]') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-upgradability-uups-checkbox"]') .pause(100) - .clickLaunchIcon('filePanel') + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .perform(function() { + browser.isVisible('*[data-id="treeViewUltreeViewMenu"]', function (result) { + browser.assert.not.ok(result.value as any, 'Scripts folder is not visible') + .clickLaunchIcon('filePanel') + }) + }) + .pause(1000) .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') + .click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') + .pause(1000) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') .click('*[data-id="treeViewLitreeViewItemscripts/deploy_with_ethers.ts"]') @@ -251,25 +309,37 @@ module.exports = { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - // .waitForElementPresent(`*[data-id='create-ozerc1155{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}']`) - // .scrollAndClick(`*[data-id='create-ozerc1155{"upgradeable":"uups","mintable":true,"burnable":true,"pausable":true}']`) - .waitForElementPresent('*[data-id="create-ozerc1155"]') - .scrollAndClick('*[data-id="create-ozerc1155"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .click('*[data-id="featureTypeMintable"]') - .click('*[data-id="featureTypeBurnable"]') - .click('*[data-id="featureTypePausable"]') - .click('*[data-id="upgradeTypeUups"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .click('*[data-id="contract-wizard-contract-type-dropdown"]') + .click('*[data-id="contract-wizard-contract-type-dropdown-item-erc1155"]') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-upgradability-uups-checkbox"]') .pause(100) - .clickLaunchIcon('filePanel') + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .perform(function() { + browser.isVisible('*[data-id="treeViewUltreeViewMenu"]', function (result) { + browser.assert.not.ok(result.value as any, 'Scripts folder is not visible') + .clickLaunchIcon('filePanel') + }) + }) + .pause(1000) .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') .click('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') .pause(1000) .getEditorValue((content) => { - browser.assert.ok(content.indexOf(`contract MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable, ERC1155PausableUpgradeable, ERC1155BurnableUpgradeable, UUPSUpgradeable {`) !== -1, + browser.assert.ok(content.indexOf(`contract MyToken is Initializable, ERC1155Upgradeable, ERC1155PausableUpgradeable, OwnableUpgradeable, ERC1155BurnableUpgradeable, UUPSUpgradeable {`) !== -1, 'Incorrect content') }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemscripts"]') @@ -298,11 +368,18 @@ module.exports = { 'Should create circom zkp hashchecker workspace #group1': function (browser: NightwatchBrowser) { browser .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-hashchecker"]') - .scrollAndClick('*[data-id="create-hashchecker"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .scrollInto('*[data-id="template-category-Circom ZKP"]') + .waitForElementVisible('*[data-id="template-card-semaphore-0"]') + .waitForElementPresent('*[data-id="template-card-hashchecker-1"]') + .click('*[data-id="template-card-hashchecker-1"]') + .waitForElementVisible('*[data-id="workspace-name-hashchecker-input"') + .setValue('*[data-id="workspace-name-hashchecker-input"]', 'Test Hashchecker Workspace') + .click('*[data-id="validate-hashcheckerworkspace-button"]') .pause(100) .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcircuits/calculate_hash.circom"]') @@ -335,14 +412,19 @@ module.exports = { 'Should create two workspace and switch to the first one #group1': function (browser: NightwatchBrowser) { browser .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') - .click('*[data-id="create-remixDefault"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .click('input[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name') - // .modalFooterOKClick('TemplatesSelection') - .click('*[data-id="TemplatesSelection-modal-footer-ok-react"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="workspace-details-section"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'workspace_name') + .click('*[data-id="validateWorkspaceButton"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') .click('*[data-id="treeViewLitreeViewItemtests"]') .addFile('test.sol', { content: 'test' }) @@ -352,14 +434,19 @@ module.exports = { locateStrategy: 'xpath' }) .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') - .scrollAndClick('*[data-id="create-remixDefault"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .click('input[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('input[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_name_1') - // .modalFooterOKClick('TemplatesSelection') - .click('*[data-id="TemplatesSelection-modal-footer-ok-react"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="workspace-details-section"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .waitForElementVisible('*[data-id="workspace-name-input"]') + .setValue('*[data-id="workspace-name-input"]', 'workspace_name_1') + .click('*[data-id="validateWorkspaceButton"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') .waitForElementNotPresent('*[data-id="treeViewLitreeViewItemtest.sol"]') .switchWorkspace('workspace_name') @@ -404,32 +491,35 @@ module.exports = { .click('*[data-id="topbarModalModalDialogModalFooter-react"] .modal-ok') .waitForElementVisible('*[data-id="workspacesSelect"]') .click('*[data-id="workspacesSelect"]') - .waitForElementVisible('*[data-id="dropdown-item-workspace_name"]') - .click('*[data-id="dropdown-item-workspace_name"]') + .waitForElementVisible('*[data-id="dropdown-item-workspace_name_renamed"]') + .click('*[data-id="dropdown-item-workspace_name_renamed"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .click('*[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('*[data-id="workspacesSelect"]') .click('*[data-id="workspacesSelect"]') + .click('*[data-id="dropdown-item-ERC1155 - 1"]') + .click('*[data-id="workspacesSelect"]') .waitForElementNotPresent(`[data-id="dropdown-item-workspace_name_1"]`) .end() }, 'Should create workspace for test #group2': function (browser: NightwatchBrowser) { browser - .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-ozerc1155"]') - .scrollAndClick('*[data-id="create-ozerc1155"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'sometestworkspace') - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'sometestworkspace' }) - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .clickLaunchIcon('filePanel') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]') - .pause(2000) }, 'Should change the current workspace in localstorage to a non existent value, reload the page and see the workspace created #group2': function (browser: NightwatchBrowser) { @@ -439,21 +529,37 @@ module.exports = { }) .refreshPage() .clickLaunchIcon('filePanel') - .currentWorkspaceIs('sometestworkspace') + .currentWorkspaceIs('default_workspace') }, 'Should create workspace for next test #group2': function (browser: NightwatchBrowser) { browser .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-ozerc1155"]') - .scrollAndClick('*[data-id="create-ozerc1155"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_db_test') - // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_db_test' }) - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-ozerc1155-2"]') + .scrollAndClick('*[data-id="template-card-ozerc1155-2"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .click('*[data-id="contract-wizard-contract-type-dropdown"]') + .click('*[data-id="contract-wizard-contract-type-dropdown-item-erc1155"]') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-upgradability-uups-checkbox"]') + .pause(1000) + .click('*[data-id="contract-wizard-validate-workspace-button"]') + .perform(function () { + browser.isVisible('*[data-id="treeViewUltreeViewMenu"]', function (result) { + console.log(result) + if (result.value === false) { + browser.clickLaunchIcon('filePanel') + } + }) + }) .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItem.prettierrc.json"]') @@ -475,7 +581,11 @@ module.exports = { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') .waitForElementPresent('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]') .scrollAndClick('*[data-id="create-uniswapV4HookBookMultiSigSwapHook"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') @@ -498,6 +608,11 @@ module.exports = { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) + .click('*[data-id="workspacecreate"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') .click('*[data-id="workspaceaddcreate2solidityfactory"]') .getEditorValue((content) => { browser.assert.ok(content.indexOf(`contract Create2FactoryAssembly {`) !== -1, diff --git a/apps/remix-ide-e2e/src/tests/workspace_git.test.ts b/apps/remix-ide-e2e/src/tests/workspace_git.test.ts index 3c8e2966a9c..fce169619ce 100644 --- a/apps/remix-ide-e2e/src/tests/workspace_git.test.ts +++ b/apps/remix-ide-e2e/src/tests/workspace_git.test.ts @@ -9,29 +9,24 @@ module.exports = { before: function (browser: NightwatchBrowser, done: VoidFunction) { init(browser, done) }, - 'Should not be able to create GIT without credentials #group1': function (browser: NightwatchBrowser) { + 'Should be able to create GIT without credentials #group1': function (browser: NightwatchBrowser) { browser .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') - .scrollAndClick('*[data-id="create-remixDefault"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .waitForElementVisible({ - selector: "//*[@class='text-warning' and contains(.,'add username and email')]", - locateStrategy: 'xpath' - }) - .waitForElementPresent({ - selector: '//*[@data-id="initGitRepository"][@disabled]', - locateStrategy: 'xpath' - }) - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="default-workspace-name-edit-icon"]') + .click('*[data-id="default-workspace-name-edit-icon"]') + .setValue('*[data-id="workspace-name-input"]', 'workspace_blank') .click('[data-id="initGitRepositoryLabel"]') - .modalFooterOKClick('TemplatesSelection') - .pause(100) + .click('*[data-id="validateWorkspaceButton"]') .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') - .waitForElementNotPresent('*[data-id="treeViewLitreeViewItem.git"]') + .waitForElementPresent('*[data-id="treeViewLitreeViewItem.git"]') .waitForElementVisible('[data-id="treeViewDivtreeViewItemREADME.txt"]') }, 'Should add credentials #group1 #group2 #group3': function (browser: NightwatchBrowser) { @@ -47,16 +42,19 @@ module.exports = { browser // .waitForElementNotVisible('[data-id="workspaceGitPanel"]') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-blank"]') - .scrollAndClick('*[data-id="create-blank"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'workspace_blank') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-blank-1"]') + .scrollAndClick('*[data-id="template-card-blank-1"]') + .waitForElementVisible('*[data-id="workspace-name-blank-input"]') + .click('*[data-id="workspace-name-blank-input"]') + .setValue('*[data-id="workspace-name-blank-input"]', 'workspace_blank_git') + .click('[data-id="initializeAsGitRepo-blank"]') + .click('*[data-id="validate-blankworkspace-button"]') // eslint-disable-next-line dot-notation - .execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_blank' }) - .click('[data-id="initGitRepositoryLabel"]') - .modalFooterOKClick('TemplatesSelection') .pause(100) .waitForElementVisible('[data-id="workspaceGitPanel"]') .waitForElementContainsText('[data-id="workspaceGitBranchesDropdown"]', 'main') @@ -452,11 +450,18 @@ module.exports = { 'Should create a git workspace (uniswapV4Template) #group4': function (browser: NightwatchBrowser) { browser .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-uniswapV4Template"]') - .scrollAndClick('*[data-id="create-uniswapV4Template"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-uniswapV4Template-0"]') + .scrollAndClick('*[data-id="template-card-uniswapV4Template-0"]') + .waitForElementVisible('*[data-id="generic-template-section-uniswapV4Template"]') + .waitForElementVisible('*[data-id="workspace-name-uniswapV4Template-input"]') + .click('*[data-id="workspace-name-uniswapV4Template-input"]') + .setValue('*[data-id="workspace-name-uniswapV4Template-input"]', 'uniswapv4') + .click('*[data-id="validate-uniswapV4Templateworkspace-button"]') .pause(100) .waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]') .openFile('src') @@ -470,19 +475,28 @@ module.exports = { 'Should create Remix default workspace with files #group5': function (browser: NightwatchBrowser) { browser - .waitForElementVisible('*[data-id="workspacesSelect"]') .click('*[data-id="workspacesSelect"]') - .waitForElementVisible('*[data-id="workspacecreate"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-ozerc20"]') - .scrollAndClick('*[data-id="create-ozerc20"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .scrollAndClick('*[data-id="modalDialogCustomPromptTextCreate"]') - .setValue('*[data-id="modalDialogCustomPromptTextCreate"]', 'new_workspace') - .modalFooterOKClick('TemplatesSelection') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .waitForElementVisible('*[data-id="contract-wizard-topcard"]') + .click('*[data-id="contract-wizard-topcard"]') + .waitForElementVisible('*[data-id="contract-wizard-container"]') + .waitForElementVisible('*[data-id="contract-wizard-edit-icon"]') + .click('*[data-id="contract-wizard-edit-icon"]') + .waitForElementVisible('*[data-id="contract-wizard-token-name-input"]') + .setValue('*[data-id="contract-wizard-token-name-input"]', 'MyToken_test') + .click('*[data-id="contract-wizard-mintable-checkbox"]') + .click('*[data-id="contract-wizard-burnable-checkbox"]') + .click('*[data-id="contract-wizard-pausable-checkbox"]') + .assert.selected('*[data-id="contract-wizard-access-ownable-radio"]', 'checked') + .click('*[data-id="contract-wizard-validate-workspace-button"]') .clickLaunchIcon('filePanel') .pause(1000) - .waitForElementVisible('*[data-id="treeViewDivDraggableItemtests/MyToken_test.sol"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken_test.sol"]') }, 'Update settings for git #group5': function (browser: NightwatchBrowser) { browser. @@ -503,7 +517,7 @@ module.exports = { .waitForElementVisible('*[data-id="sourcecontrol-panel"]') .click('*[data-id="sourcecontrol-panel"]') .waitForElementVisible({ - selector: "//*[@data-status='new-untracked' and @data-file='/tests/MyToken_test.sol']", + selector: "//*[@data-status='new-untracked' and @data-file='/tests/MyToken_test_test.sol']", locateStrategy: 'xpath' }) }, @@ -532,14 +546,22 @@ module.exports = { }, 'Should create a git workspace (uniswapV4Template) #group5': function (browser: NightwatchBrowser) { browser - .clickLaunchIcon('filePanel') .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-uniswapV4Template"]') - .scrollAndClick('*[data-id="create-uniswapV4Template"]') - .waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]') - .modalFooterOKClick('TemplatesSelection') - .pause(100) + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-uniswapV4Template-0"]') + .scrollAndClick('*[data-id="template-card-uniswapV4Template-0"]') + .waitForElementVisible('*[data-id="generic-template-section-uniswapV4Template"]') + .waitForElementVisible('*[data-id="workspace-name-uniswapV4Template-input"]') + .click('*[data-id="workspace-name-uniswapV4Template-input"]') + .setValue('*[data-id="workspace-name-uniswapV4Template-input"]', 'uniswapv4-workspace') + .click('*[data-id="initializeAsGitRepo-uniswapV4Template"]') + .click('*[data-id="validate-uniswapV4Templateworkspace-button"]') + .pause(2000) + .clickLaunchIcon('filePanel') .waitForElementVisible('*[data-id="treeViewLitreeViewItemsrc"]') .openFile('src') .openFile('src/Counter.sol') diff --git a/apps/remix-ide-e2e/src/types/index.d.ts b/apps/remix-ide-e2e/src/types/index.d.ts index 66c95fb6747..505b2fdc746 100644 --- a/apps/remix-ide-e2e/src/types/index.d.ts +++ b/apps/remix-ide-e2e/src/types/index.d.ts @@ -9,6 +9,7 @@ declare module 'nightwatch' { switchBrowserTab(indexOrTitle: number | string, forceReload?: boolean): NightwatchBrowser scrollAndClick(target: string): NightwatchBrowser scrollInto(target: string): NightwatchBrowser + openTemplateExplorer(): NightwatchBrowser testContracts(fileName: string, contractCode: NightwatchContractContent, compiledContractNames: string[]): NightwatchBrowser setEditorValue(value: string, callback?: () => void): NightwatchBrowser addFile(name: string, content: NightwatchContractContent, readMeFile?: string): NightwatchBrowser diff --git a/apps/remix-ide/src/app.ts b/apps/remix-ide/src/app.ts index ba81e89962a..3897896dfc0 100644 --- a/apps/remix-ide/src/app.ts +++ b/apps/remix-ide/src/app.ts @@ -111,6 +111,7 @@ import Terminal from './app/panels/terminal' import TabProxy from './app/panels/tab-proxy.js' import { Plugin } from '@remixproject/engine' import BottomBarPanel from './app/components/bottom-bar-panel' +import { TemplateExplorerModalPlugin } from './app/plugins/template-explorer-modal' // Tracking now handled by this.track() method using MatomoManager @@ -157,6 +158,7 @@ class AppComponent { popupPanel: PopupPanel statusBar: StatusBar topBar: Topbar + templateExplorerModal: TemplateExplorerModalPlugin settings: SettingsTab params: any desktopClientMode: boolean @@ -261,6 +263,7 @@ class AppComponent { } } + this.templateExplorerModal = new TemplateExplorerModalPlugin() // SERVICES // ----------------- gist service --------------------------------- this.gistHandler = new GistHandler() @@ -399,6 +402,8 @@ class AppComponent { const templateSelection = new TemplatesSelectionPlugin() + const templateExplorerModal = this.templateExplorerModal + const walletConnect = new WalletConnect() this.engine.register([ @@ -526,7 +531,7 @@ class AppComponent { const bottomBarPanel = new BottomBarPanel() - this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, this.statusBar, this.topBar, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel, this.popupPanel, bottomBarPanel]) + this.engine.register([this.menuicons, landingPage, this.hiddenPanel, this.sidePanel, this.statusBar, filePanel, pluginManagerComponent, this.settings, this.pinnedPanel, this.popupPanel, bottomBarPanel]) // CONTENT VIEWS & DEFAULT PLUGINS const openZeppelinProxy = new OpenZeppelinProxy(blockchain) @@ -568,6 +573,7 @@ class AppComponent { openZeppelinProxy, run.recorder ]) + this.engine.register([templateExplorerModal, this.topBar]) this.layout.panels = { tabs: { plugin: tabProxy, active: true }, @@ -606,8 +612,9 @@ class AppComponent { ]) await this.appManager.activatePlugin(['mainPanel', 'menuicons', 'tabs']) - await this.appManager.activatePlugin(['topbar']) + await this.appManager.activatePlugin(['topbar', 'templateexplorermodal']) await this.appManager.activatePlugin(['statusBar']) + // await this.appManager.activatePlugin(['remix-template-explorer-modal']) await this.appManager.activatePlugin(['bottomBar']) await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await this.appManager.activatePlugin(['pinnedPanel']) diff --git a/apps/remix-ide/src/app/components/status-bar.tsx b/apps/remix-ide/src/app/components/status-bar.tsx index adbb937009f..402419230fa 100644 --- a/apps/remix-ide/src/app/components/status-bar.tsx +++ b/apps/remix-ide/src/app/components/status-bar.tsx @@ -81,6 +81,7 @@ export class StatusBar extends Plugin implements StatusBarI this.on('settings', 'copilotChoiceChanged', (isAiActive: boolean) => { this.isAiActive = isAiActive }) + this.renderComponent() } diff --git a/apps/remix-ide/src/app/components/top-bar.tsx b/apps/remix-ide/src/app/components/top-bar.tsx index 6b2faebd6f0..6c06f2dfcb3 100644 --- a/apps/remix-ide/src/app/components/top-bar.tsx +++ b/apps/remix-ide/src/app/components/top-bar.tsx @@ -22,6 +22,7 @@ const TopBarProfile = { description: '', version: packageJson.version, icon: '', + location: 'none', methods: ['getWorkspaces', 'createWorkspace', 'renameWorkspace', 'deleteWorkspace', 'getCurrentWorkspace', 'setWorkspace'], events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'], } diff --git a/apps/remix-ide/src/app/plugins/notification.tsx b/apps/remix-ide/src/app/plugins/notification.tsx index 2d0455a31de..c37b74d0a61 100644 --- a/apps/remix-ide/src/app/plugins/notification.tsx +++ b/apps/remix-ide/src/app/plugins/notification.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @nrwl/nx/enforce-module-boundaries */ import { Plugin } from '@remixproject/engine' import { LibraryProfile, MethodApi, StatusEvents } from '@remixproject/plugin-utils' import { AppModal } from '@remix-ui/app' @@ -10,6 +11,7 @@ interface INotificationApi { modal: (args: AppModal) => void alert: (args: AlertModal) => void toast: (message: string) => void + } } diff --git a/apps/remix-ide/src/app/plugins/remix-ai-assistant.tsx b/apps/remix-ide/src/app/plugins/remix-ai-assistant.tsx index 5593f036bca..a6767c67036 100644 --- a/apps/remix-ide/src/app/plugins/remix-ai-assistant.tsx +++ b/apps/remix-ide/src/app/plugins/remix-ai-assistant.tsx @@ -18,7 +18,7 @@ const profile = { maintainedBy: 'Remix', permission: true, events: [], - methods: ['chatPipe'] + methods: ['chatPipe', 'handleExternalMessage', 'getProfile'] } export class RemixAIAssistant extends ViewPlugin { @@ -28,6 +28,7 @@ export class RemixAIAssistant extends ViewPlugin { event: any chatRef: React.RefObject history: ChatMessage[] = [] + externalMessage: string constructor() { super(profile) @@ -38,6 +39,10 @@ export class RemixAIAssistant extends ViewPlugin { ;(window as any).remixAIChat = this.chatRef } + getProfile() { + return profile + } + async onActivation() { if (!localStorage.getItem('remixaiassistant_firstload_flag')) { this.call('sidePanel', 'pinView', this.profile) @@ -88,6 +93,11 @@ export class RemixAIAssistant extends ViewPlugin { this.renderComponent() } + handleExternalMessage = (message: string) => { + this.externalMessage = message + this.renderComponent() + } + onReady() { console.log('RemixAiAssistant onReady') } diff --git a/apps/remix-ide/src/app/plugins/remix-templates.ts b/apps/remix-ide/src/app/plugins/remix-templates.ts index 7e3c3810ac6..db327b43f22 100644 --- a/apps/remix-ide/src/app/plugins/remix-templates.ts +++ b/apps/remix-ide/src/app/plugins/remix-templates.ts @@ -7,7 +7,7 @@ const profile = { name: 'remix-templates', displayName: 'remix-templates', description: 'Remix Templates plugin', - methods: ['getTemplate', 'loadTemplateInNewWindow', 'addToCurrentElectronFolder', 'loadFilesInNewWindow'], + methods: ['getTemplate', 'loadTemplateInNewWindow', 'addToCurrentElectronFolder', 'loadFilesInNewWindow', 'getTemplateReadMeFile'], } export class TemplatesPlugin extends Plugin { @@ -27,6 +27,13 @@ export class TemplatesPlugin extends Plugin { const files = await templateWithContent[template](opts, this) return files } + + async getTemplateReadMeFile(templateName: string) { + const files = typeof templateWithContent[templateName] === 'function' ? await templateWithContent[templateName]({}, this) : { 'README.md': `# ${templateName} template` } + const readMe = files?.['README.md'] || files?.['README.txt'] || 'No ReadMe file found' + return { readMe, type: files['README.md'] ? 'md' : files['README.txt'] ? 'txt' : 'none' } + } + // electron only method async addToCurrentElectronFolder(template: string, opts?: any) { diff --git a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx index c4b1e67f5f0..ea0f9b70d60 100644 --- a/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx +++ b/apps/remix-ide/src/app/plugins/remixAIPlugin.tsx @@ -2,6 +2,7 @@ import * as packageJson from '../../../../../package.json' import { Plugin } from '@remixproject/engine'; import { trackMatomoEvent } from '@remix-api' import { IModel, RemoteInferencer, IRemoteModel, IParams, GenerationParams, AssistantParams, CodeExplainAgent, SecurityAgent, CompletionParams, OllamaInferencer, isOllamaAvailable, getBestAvailableModel } from '@remix/remix-ai-core'; +//@ts-ignore import { CodeCompletionAgent, ContractAgent, workspaceAgent, IContextType, mcpDefaultServersConfig } from '@remix/remix-ai-core'; import { MCPInferencer } from '@remix/remix-ai-core'; import { IMCPServer, IMCPConnectionStatus } from '@remix/remix-ai-core'; @@ -29,7 +30,7 @@ const profile = { icon: 'assets/img/remix-logo-blue.png', description: 'RemixAI provides AI services to Remix IDE.', kind: '', - location: 'popupPanel', + location: 'none', documentation: 'https://remix-ide.readthedocs.io/en/latest/ai.html', version: packageJson.version, maintainedBy: 'Remix' diff --git a/apps/remix-ide/src/app/plugins/remixGuide.tsx b/apps/remix-ide/src/app/plugins/remixGuide.tsx index 381526f9b0a..e6b52167ea2 100644 --- a/apps/remix-ide/src/app/plugins/remixGuide.tsx +++ b/apps/remix-ide/src/app/plugins/remixGuide.tsx @@ -120,7 +120,7 @@ export class RemixGuidePlugin extends ViewPlugin { > { section.cells.map((cell, index) => { return = () => { } + event: any + appStateDispatch: any + constructor() { + super(pluginProfile) + this.element = document.createElement('div') + this.element.setAttribute('id', 'template-explorer-modal') + this.dispatch = () => { } + this.event = new EventEmitter() + } + + async onActivation(): Promise { + + } + + async addArtefactsToWorkspace(workspaceTemplateName: WorkspaceTemplate, opts: any, isEmpty: boolean, cb: (err: Error) => void) { + this.emit('addTemplateToWorkspaceReducerEvent', workspaceTemplateName, opts, isEmpty, (err: Error) => { + if (err) { + console.error(err) + } + }) + } + + onDeactivation(): void { + + } + + setDispatch(dispatch: React.Dispatch) { + this.dispatch = dispatch + this.renderComponent() + } + + setAppStateDispatch(appStateDispatch: React.Dispatch) { + this.appStateDispatch = appStateDispatch + } + + render() { + return ( +
+ +
+ ) + } + + renderComponent(): void { + this.dispatch({ + ...this + }) + } + + updateComponent(state: any) { + return ( + + ) + } +} diff --git a/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx b/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx index fbeb2ac4007..cbfc5fa72ac 100644 --- a/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx +++ b/apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx @@ -269,7 +269,7 @@ export class TemplatesSelectionPlugin extends ViewPlugin { if (!item.opts) { return ( { - browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) - }) - }) - }, - 'compile storage': function (browser: NightwatchBrowser) { - browser - .clickLaunchIcon('solidity') - .pause(1000) - .waitForElementVisible('*[data-id="compilerContainerCompileBtn"]') - .click('[data-id="compilerContainerCompileBtn"]') - .clickLaunchIcon('filePanel') - .clickLaunchIcon('solidity') - .pause(5000) - .waitForElementPresent('*[data-id="compiledContracts"] option', 60000) - .click('*[data-id="compilation-details"]') - .waitForElementVisible('*[data-id="remixui_treeviewitem_metadata"]') - } -} \ No newline at end of file + browser + .pause(3000) + .windowHandles(function (result) { + console.log(result.value) + browser.hideToolTips().switchWindow(result.value[1]) + .hideToolTips() + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') + .click('*[data-id="treeViewLitreeViewItemtests"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') + .openFile('contracts/1_Storage.sol') + .waitForElementVisible('*[id="editorView"]', 10000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) + }) + }) + }, + 'compile storage': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('solidity') + .pause(1000) + .waitForElementVisible('*[data-id="compilerContainerCompileBtn"]') + .click('[data-id="compilerContainerCompileBtn"]') + .clickLaunchIcon('filePanel') + .clickLaunchIcon('solidity') + .pause(5000) + .waitForElementPresent('*[data-id="compiledContracts"] option', 60000) + .click('*[data-id="compilation-details"]') + .waitForElementVisible('*[data-id="remixui_treeviewitem_metadata"]') + } +} diff --git a/apps/remixdesktop/test/tests/app/search.test.ts b/apps/remixdesktop/test/tests/app/search.test.ts index 952d9751446..7627bc1054d 100644 --- a/apps/remixdesktop/test/tests/app/search.test.ts +++ b/apps/remixdesktop/test/tests/app/search.test.ts @@ -3,279 +3,284 @@ import { NightwatchBrowser } from 'nightwatch' function openTemplatesExplorer(browser: NightwatchBrowser) { browser .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="workspace-details-section"]') } module.exports = { - before: function (browser: NightwatchBrowser, done: VoidFunction) { - browser.hideToolTips() - done() - }, - 'open default template': function (browser: NightwatchBrowser) { - browser - .hideToolTips() - .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) + before: function (browser: NightwatchBrowser, done: VoidFunction) { + browser.hideToolTips() + done() + }, + 'open default template': function (browser: NightwatchBrowser) { + browser + .hideToolTips() + .waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) - openTemplatesExplorer(browser) + openTemplatesExplorer(browser) + browser + .pause(3000) + .windowHandles(function (result) { + console.log(result.value) browser - .scrollAndClick('*[data-id="create-remixDefault"]') - .pause(3000) - .windowHandles(function (result) { - console.log(result.value) - browser - .hideToolTips() - .switchWindow(result.value[1]) - .hideToolTips() - .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') - .click('*[data-id="treeViewLitreeViewItemtests"]') - .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') - .click('*[data-id="treeViewLitreeViewItemcontracts"]') - .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') - .openFile('contracts/1_Storage.sol') - .waitForElementVisible('*[id="editorView"]', 10000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) - }) - }) - }, - 'Should find text #group1': function (browser: NightwatchBrowser) { - browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') - .click('*[plugin="search"]') - .waitForElementVisible('*[id="search_input"]') - .click('*[id="search_include"]') - .waitForElementVisible('*[id="search_include"]') - .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) - .waitForElementContainsText('*[data-id="search_results"]', 'contracts', 60000) - .waitForElementContainsText('*[data-id="search_results"]', 'README.TXT', 60000) - .waitForElementContainsText('*[data-id="search_results"]', 'file must') - .waitForElementContainsText('*[data-id="search_results"]', 'be compiled') - .waitForElementContainsText('*[data-id="search_results"]', 'that person al') - .waitForElementContainsText('*[data-id="search_results"]', 'sender.voted') - .waitForElementContainsText('*[data-id="search_results"]', 'read') - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 7) - }) - }, 'Should find text with exclude #group1': function (browser: NightwatchBrowser) { - browser - .clearValue('*[id="search_input"]') - .setValue('*[id="search_input"]', 'contract').pause(1000) - .clearValue('*[id="search_include"]').pause(2000) - .setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000) - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 61) - }) - .setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000) - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 54) - }) - .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt') - .clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*') - }, - 'Should find regex #group1': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible('*[id="search_input"]') - .clearValue('*[id="search_input"]').pause(2000) - .setValue('*[id="search_input"]', '^contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(3000) - .waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]').pause(3000) - .waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000) - .waitForElementContainsText('*[data-id="search_results"]', '2_OWNER.SOL', 60000) - .waitForElementContainsText('*[data-id="search_results"]', '1_STORAGE.SOL', 60000) - .waitForElementContainsText('*[data-id="search_results"]', 'BALLOT_TEST.SOL', 60000) - .waitForElementContainsText('*[data-id="search_results"]', 'tests', 60000) - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 4) - }) - }, - 'Should find matchcase #group1': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]') - .waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]').pause(4000) - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 0) - }) - .clearValue('*[id="search_input"]') - .setValue('*[id="search_input"]', 'Contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(3000) - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 3) - }) - .waitForElementContainsText('*[data-id="search_results"]', 'STORAGE.TEST.JS', 60000) - }, - 'Should find matchword #group1': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]') - .waitForElementVisible('*[data-id="search_whole_word"]').click('*[data-id="search_whole_word"]').pause(2000) - .clearValue('*[id="search_input"]') - .setValue('*[id="search_input"]', 'contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(4000) - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 16) - }) - }, - 'Should replace text #group1': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]') - .waitForElementVisible('*[id="search_replace"]') - .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', 'contracts/2_*.sol') - .setValue('*[id="search_replace"]', 'replacing').sendKeys('*[id="search_include"]', browser.Keys.ENTER).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"]') - .click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000). - modalFooterOKClick('confirmreplace').pause(2000). - getEditorValue((content) => { - browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok') - }) - }, - 'Should replace text without confirmation #group1': function (browser: NightwatchBrowser) { - 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"]', '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"]') - .click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000). - getEditorValue((content) => { - browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok') - }) - }, - 'Should replace all & undo #group1': function (browser: NightwatchBrowser) { - browser - .clearValue('*[id="search_input"]') - .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', 'contracts/1_*.sol') - .setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_include"]', browser.Keys.ENTER) - .clearValue('*[id="search_replace"]') - .setValue('*[id="search_replace"]', '123test').pause(1000) - .waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]') - .click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('contract 123test'), 'should replace text ok') - browser.assert.ok(content.includes('title 123test'), 'should replace text ok') - }) - .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]') - .click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('contract Storage'), 'should undo text ok') - browser.assert.ok(content.includes('title Storage'), 'should undo text ok') - }) - }, - 'Should replace all & undo & switch between files #group1': function (browser: NightwatchBrowser) { - browser.waitForElementVisible('*[id="search_input"]') - .clearValue('*[id="search_input"]') - .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt') - .setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_include"]', browser.Keys.ENTER) - .clearValue('*[id="search_replace"]') - .setValue('*[id="search_replace"]', '123test').pause(1000) - .waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]') - .click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('contract 123test'), 'should replace text ok') - browser.assert.ok(content.includes('title 123test'), 'should replace text ok') - }) - .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]') - .openFile('README.txt') - .click('*[plugin="search"]').pause(2000) - .waitForElementNotPresent('*[data-id="undo-replace-contracts/1_Storage.sol"]') - .waitForElementVisible('*[data-id="replace-all-README.txt"]') - .click('*[data-id="replace-all-README.txt"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes("123test' contract"), 'should replace text ok') - }) - .waitForElementVisible('*[data-id="undo-replace-README.txt"]') - .click('div[data-path="/contracts/1_Storage.sol"]').pause(2000) - .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]') - .click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('contract Storage'), 'should undo text ok') - browser.assert.ok(content.includes('title Storage'), 'should undo text ok') - }) - .click('div[data-path="/README.txt"]').pause(2000) - .waitForElementVisible('*[data-id="undo-replace-README.txt"]') - .click('*[data-id="undo-replace-README.txt"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes("Storage' contract"), 'should replace text ok') - }) - }, - 'Should hide button when edited content is the same #group2': function (browser: NightwatchBrowser) { - browser.refresh() - .hideToolTips() - .waitForElementVisible('*[data-id="remixIdeSidePanel"]') - .addFile('test.sol', { content: '123' }) - .pause(4000) - .click('*[plugin="search"]') - .waitForElementVisible('*[id="search_input"]') - .waitForElementVisible('*[data-id="toggle_replace"]') - .click('*[data-id="toggle_replace"]') - .clearValue('*[id="search_input"]') - .setValue('*[id="search_input"]', '123') - .sendKeys('*[id="search_input"]', browser.Keys.ENTER) - .waitForElementVisible('*[id="search_replace"]') - .clearValue('*[id="search_replace"]') - .setValue('*[id="search_replace"]', '456').pause(1000) - .click('*[data-id="confirm_replace_label"]').pause(500) - .waitForElementVisible('*[data-id="replace-all-test.sol"]') - .click('*[data-id="replace-all-test.sol"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('456'), 'should replace text ok') - } - ) - .setEditorValue('123') - .getEditorValue((content) => { - browser.assert.ok(content.includes('123'), 'should have text ok') - } - ).pause(5000) - .waitForElementNotPresent('*[data-id="undo-replace-test.sol"]') - }, - 'Should disable/enable button when edited content changed #group2': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible('*[id="search_input"]') - .clearValue('*[id="search_input"]') - .clearValue('*[id="search_input"]') - .setValue('*[id="search_input"]', '123').sendKeys('*[id="search_input"]', browser.Keys.ENTER) - .clearValue('*[id="search_replace"]') - .setValue('*[id="search_replace"]', 'replaced').pause(1000) - .waitForElementVisible('*[data-id="replace-all-test.sol"]') - .click('*[data-id="replace-all-test.sol"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('replaced'), 'should replace text ok') - } - ) - .setEditorValue('changed') - .getEditorValue((content) => { - browser.assert.ok(content.includes('changed'), 'should have text ok') - } - ).pause(5000) - .waitForElementVisible('*[data-id="undo-replace-test.sol"]') - .getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => { - browser.assert.equal(result.value, 'true', 'should be disabled') - }) - .setEditorValue('replaced') - .getEditorValue((content) => { - browser.assert.ok(content.includes('replaced'), 'should have text ok') - } - ).pause(1000) - .waitForElementVisible('*[data-id="undo-replace-test.sol"]') - .getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => { - browser.assert.equal(result.value, null, 'should not be disabled') - }) - .click('*[data-id="undo-replace-test.sol"]').pause(2000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('123'), 'should have text ok') - }) - .waitForElementNotPresent('*[data-id="undo-replace-test.sol"]') - }, + .hideToolTips() + .switchWindow(result.value[1]) + .hideToolTips() + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') + .click('*[data-id="treeViewLitreeViewItemtests"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') + .openFile('contracts/1_Storage.sol') + .waitForElementVisible('*[id="editorView"]', 10000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) + }) + }) + }, + 'Should find text #group1': function (browser: NightwatchBrowser) { + browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') + .click('*[plugin="search"]') + .waitForElementVisible('*[id="search_input"]') + .click('*[id="search_include"]') + .waitForElementVisible('*[id="search_include"]') + .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) + .waitForElementContainsText('*[data-id="search_results"]', 'contracts', 60000) + .waitForElementContainsText('*[data-id="search_results"]', 'README.TXT', 60000) + .waitForElementContainsText('*[data-id="search_results"]', 'file must') + .waitForElementContainsText('*[data-id="search_results"]', 'be compiled') + .waitForElementContainsText('*[data-id="search_results"]', 'that person al') + .waitForElementContainsText('*[data-id="search_results"]', 'sender.voted') + .waitForElementContainsText('*[data-id="search_results"]', 'read') + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 7) + }) + }, 'Should find text with exclude #group1': function (browser: NightwatchBrowser) { + browser + .clearValue('*[id="search_input"]') + .setValue('*[id="search_input"]', 'contract').pause(1000) + .clearValue('*[id="search_include"]').pause(2000) + .setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000) + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 61) + }) + .setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000) + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 54) + }) + .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt') + .clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*') + }, + 'Should find regex #group1': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[id="search_input"]') + .clearValue('*[id="search_input"]').pause(2000) + .setValue('*[id="search_input"]', '^contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(3000) + .waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]').pause(3000) + .waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000) + .waitForElementContainsText('*[data-id="search_results"]', '2_OWNER.SOL', 60000) + .waitForElementContainsText('*[data-id="search_results"]', '1_STORAGE.SOL', 60000) + .waitForElementContainsText('*[data-id="search_results"]', 'BALLOT_TEST.SOL', 60000) + .waitForElementContainsText('*[data-id="search_results"]', 'tests', 60000) + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 4) + }) + }, + 'Should find matchcase #group1': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]') + .waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]').pause(4000) + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 0) + }) + .clearValue('*[id="search_input"]') + .setValue('*[id="search_input"]', 'Contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(3000) + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 3) + }) + .waitForElementContainsText('*[data-id="search_results"]', 'STORAGE.TEST.JS', 60000) + }, + 'Should find matchword #group1': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]') + .waitForElementVisible('*[data-id="search_whole_word"]').click('*[data-id="search_whole_word"]').pause(2000) + .clearValue('*[id="search_input"]') + .setValue('*[id="search_input"]', 'contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(4000) + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 16) + }) + }, + 'Should replace text #group1': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]') + .waitForElementVisible('*[id="search_replace"]') + .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', 'contracts/2_*.sol') + .setValue('*[id="search_replace"]', 'replacing').sendKeys('*[id="search_include"]', browser.Keys.ENTER).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"]') + .click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000). + modalFooterOKClick('confirmreplace').pause(2000). + getEditorValue((content) => { + browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok') + }) + }, + 'Should replace text without confirmation #group1': function (browser: NightwatchBrowser) { + 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"]', '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"]') + .click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000). + getEditorValue((content) => { + browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok') + }) + }, + 'Should replace all & undo #group1': function (browser: NightwatchBrowser) { + browser + .clearValue('*[id="search_input"]') + .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', 'contracts/1_*.sol') + .setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_include"]', browser.Keys.ENTER) + .clearValue('*[id="search_replace"]') + .setValue('*[id="search_replace"]', '123test').pause(1000) + .waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]') + .click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('contract 123test'), 'should replace text ok') + browser.assert.ok(content.includes('title 123test'), 'should replace text ok') + }) + .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]') + .click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('contract Storage'), 'should undo text ok') + browser.assert.ok(content.includes('title Storage'), 'should undo text ok') + }) + }, + 'Should replace all & undo & switch between files #group1': function (browser: NightwatchBrowser) { + browser.waitForElementVisible('*[id="search_input"]') + .clearValue('*[id="search_input"]') + .clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt') + .setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_include"]', browser.Keys.ENTER) + .clearValue('*[id="search_replace"]') + .setValue('*[id="search_replace"]', '123test').pause(1000) + .waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]') + .click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('contract 123test'), 'should replace text ok') + browser.assert.ok(content.includes('title 123test'), 'should replace text ok') + }) + .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]') + .openFile('README.txt') + .click('*[plugin="search"]').pause(2000) + .waitForElementNotPresent('*[data-id="undo-replace-contracts/1_Storage.sol"]') + .waitForElementVisible('*[data-id="replace-all-README.txt"]') + .click('*[data-id="replace-all-README.txt"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes("123test' contract"), 'should replace text ok') + }) + .waitForElementVisible('*[data-id="undo-replace-README.txt"]') + .click('div[data-path="/contracts/1_Storage.sol"]').pause(2000) + .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]') + .click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('contract Storage'), 'should undo text ok') + browser.assert.ok(content.includes('title Storage'), 'should undo text ok') + }) + .click('div[data-path="/README.txt"]').pause(2000) + .waitForElementVisible('*[data-id="undo-replace-README.txt"]') + .click('*[data-id="undo-replace-README.txt"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes("Storage' contract"), 'should replace text ok') + }) + }, + 'Should hide button when edited content is the same #group2': function (browser: NightwatchBrowser) { + browser.refresh() + .hideToolTips() + .waitForElementVisible('*[data-id="remixIdeSidePanel"]') + .addFile('test.sol', { content: '123' }) + .pause(4000) + .click('*[plugin="search"]') + .waitForElementVisible('*[id="search_input"]') + .waitForElementVisible('*[data-id="toggle_replace"]') + .click('*[data-id="toggle_replace"]') + .clearValue('*[id="search_input"]') + .setValue('*[id="search_input"]', '123') + .sendKeys('*[id="search_input"]', browser.Keys.ENTER) + .waitForElementVisible('*[id="search_replace"]') + .clearValue('*[id="search_replace"]') + .setValue('*[id="search_replace"]', '456').pause(1000) + .click('*[data-id="confirm_replace_label"]').pause(500) + .waitForElementVisible('*[data-id="replace-all-test.sol"]') + .click('*[data-id="replace-all-test.sol"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('456'), 'should replace text ok') + } + ) + .setEditorValue('123') + .getEditorValue((content) => { + browser.assert.ok(content.includes('123'), 'should have text ok') + } + ).pause(5000) + .waitForElementNotPresent('*[data-id="undo-replace-test.sol"]') + }, + 'Should disable/enable button when edited content changed #group2': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[id="search_input"]') + .clearValue('*[id="search_input"]') + .clearValue('*[id="search_input"]') + .setValue('*[id="search_input"]', '123').sendKeys('*[id="search_input"]', browser.Keys.ENTER) + .clearValue('*[id="search_replace"]') + .setValue('*[id="search_replace"]', 'replaced').pause(1000) + .waitForElementVisible('*[data-id="replace-all-test.sol"]') + .click('*[data-id="replace-all-test.sol"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('replaced'), 'should replace text ok') + } + ) + .setEditorValue('changed') + .getEditorValue((content) => { + browser.assert.ok(content.includes('changed'), 'should have text ok') + } + ).pause(5000) + .waitForElementVisible('*[data-id="undo-replace-test.sol"]') + .getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => { + browser.assert.equal(result.value, 'true', 'should be disabled') + }) + .setEditorValue('replaced') + .getEditorValue((content) => { + browser.assert.ok(content.includes('replaced'), 'should have text ok') + } + ).pause(1000) + .waitForElementVisible('*[data-id="undo-replace-test.sol"]') + .getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => { + browser.assert.equal(result.value, null, 'should not be disabled') + }) + .click('*[data-id="undo-replace-test.sol"]').pause(2000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('123'), 'should have text ok') + }) + .waitForElementNotPresent('*[data-id="undo-replace-test.sol"]') + }, - 'should clear search #group2': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible('*[id="search_input"]') - .setValue('*[id="search_input"]', 'nodata').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(1000) - .elements('css selector', '.search_plugin_search_line', (res) => { - Array.isArray(res.value) && browser.assert.equal(res.value.length, 0) - }) - } + 'should clear search #group2': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[id="search_input"]') + .setValue('*[id="search_input"]', 'nodata').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(1000) + .elements('css selector', '.search_plugin_search_line', (res) => { + Array.isArray(res.value) && browser.assert.equal(res.value.length, 0) + }) + } -} \ No newline at end of file +} diff --git a/apps/remixdesktop/test/tests/app/slitherlinux.test.ts b/apps/remixdesktop/test/tests/app/slitherlinux.test.ts index 8b8bfbdb35e..412335575d7 100644 --- a/apps/remixdesktop/test/tests/app/slitherlinux.test.ts +++ b/apps/remixdesktop/test/tests/app/slitherlinux.test.ts @@ -1,18 +1,24 @@ -import {NightwatchBrowser} from 'nightwatch' +import { NightwatchBrowser } from 'nightwatch' import { ChildProcess, spawn, execSync } from 'child_process' import { homedir } from 'os' function openTemplatesExplorer(browser: NightwatchBrowser) { browser .click('*[data-id="workspacesSelect"]') + .pause(2000) .click('*[data-id="workspacecreate"]') - .waitForElementPresent('*[data-id="create-remixDefault"]') + .waitForElementVisible('*[data-id="template-explorer-modal-react"]') + .waitForElementVisible('*[data-id="template-explorer-template-container"]') + .click('*[data-id="template-explorer-template-container"]') + .waitForElementPresent('*[data-id="template-card-remixDefault-0"]') + .click('*[data-id="template-card-remixDefault-0"]') + .waitForElementVisible('*[data-id="workspace-details-section"]') } const tests = { before: function (browser: NightwatchBrowser, done: VoidFunction) { browser.hideToolTips() - done() + done() }, open: function (browser: NightwatchBrowser) { browser.hideToolTips().waitForElementVisible('*[data-id="openFolderButton"]', 10000).click('*[data-id="openFolderButton"]') @@ -25,22 +31,21 @@ const tests = { openTemplatesExplorer(browser) browser - .scrollAndClick('*[data-id="create-remixDefault"]') .pause(3000) .windowHandles(function (result) { console.log(result.value) - browser.hideToolTips().switchWindow(result.value[1]) - .hideToolTips() - .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') - .click('*[data-id="treeViewLitreeViewItemtests"]') - .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') - .click('*[data-id="treeViewLitreeViewItemcontracts"]') - .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') - .openFile('contracts/1_Storage.sol') - .waitForElementVisible('*[id="editorView"]', 10000) - .getEditorValue((content) => { - browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) - }) + browser.hideToolTips().switchWindow(result.value[1]) + .hideToolTips() + .waitForElementVisible('*[data-id="treeViewLitreeViewItemtests"]') + .click('*[data-id="treeViewLitreeViewItemtests"]') + .waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]') + .click('*[data-id="treeViewLitreeViewItemcontracts"]') + .waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]') + .openFile('contracts/1_Storage.sol') + .waitForElementVisible('*[id="editorView"]', 10000) + .getEditorValue((content) => { + browser.assert.ok(content.includes('function retrieve() public view returns (uint256){')) + }) }) }, 'Should install slither #group6': function (browser: NightwatchBrowser) { @@ -51,65 +56,62 @@ const tests = { }, 'run slither': function (browser: NightwatchBrowser) { browser - .click('[data-id="verticalIconsKindpluginManager"]') - .scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityStaticAnalysis"]') - .clickLaunchIcon('solidity').click('*[data-id="compilerContainerCompileBtn"]') - .pause(1000) - .clickLaunchIcon('solidityStaticAnalysis') - .useXpath() - .click('//*[@id="staticAnalysisRunBtn"]') - .waitForElementPresent('//*[@id="staticanalysisresult"]', 5000) - .waitForElementVisible({ - selector: "//*[@data-id='nolibslitherwarnings'][contains(text(), '1')]", - locateStrategy: 'xpath', - timeout: 5000 - }) - .waitForElementVisible({ - selector: "//div[@data-id='block']/span[contains(text(), '1 warnings found.')]", - locateStrategy: 'xpath', - timeout: 5000 - }) + .click('[data-id="verticalIconsKindpluginManager"]') + .scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityStaticAnalysis"]') + .clickLaunchIcon('solidity').click('*[data-id="compilerContainerCompileBtn"]') + .pause(1000) + .clickLaunchIcon('solidityStaticAnalysis') + .useXpath() + .click('//*[@id="staticAnalysisRunBtn"]') + .waitForElementPresent('//*[@id="staticanalysisresult"]', 5000) + .waitForElementVisible({ + selector: "//*[@data-id='nolibslitherwarnings'][contains(text(), '1')]", + locateStrategy: 'xpath', + timeout: 5000 + }) + .waitForElementVisible({ + selector: "//div[@data-id='block']/span[contains(text(), '1 warnings found.')]", + locateStrategy: 'xpath', + timeout: 5000 + }) }, - + after: function (browser: NightwatchBrowser) { browser.end() }, } async function installSlither(): Promise { - console.log('installSlither', process.cwd()) + console.log('installSlither', process.cwd()) + try { try { - try { - const solcVersion = '0.8.15' - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: requires Python3.6+ (pip3) to be installed on your system`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc-select will be installed along with Slither to set different solc compiler versions.`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: checking pip3 availability ...`) - const pip3OP = execSync('pip3 --version') - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: pip3 found: ${pip3OP.toString()}`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing slither...`) - const slitherOP = execSync('pip3 install slither-analyzer') - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: slither installation output: ${slitherOP.toString()}`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing solc-select...`) - const solcSelectOP = execSync('pip3 install solc-select') - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc-select installation output: ${solcSelectOP.toString()}`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing solc ${solcVersion}...`) - const solcInstallOP = execSync(`solc-select install ${solcVersion}`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc installation output: ${solcInstallOP.toString()}`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: setting solc version to ${solcVersion}...`) - const solcUseOP = execSync(`solc-select use ${solcVersion}`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc setting installation output: ${solcUseOP.toString()}`) - console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: Slither is ready to use!`) - } catch (err) { - console.log('\x1b[31m%s\x1b[0m', `[Slither Installation]: Error occurred: ${err}`) - } - } catch (e) { - console.log(e) + const solcVersion = '0.8.15' + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: requires Python3.6+ (pip3) to be installed on your system`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc-select will be installed along with Slither to set different solc compiler versions.`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: checking pip3 availability ...`) + const pip3OP = execSync('pip3 --version') + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: pip3 found: ${pip3OP.toString()}`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing slither...`) + const slitherOP = execSync('pip3 install slither-analyzer') + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: slither installation output: ${slitherOP.toString()}`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing solc-select...`) + const solcSelectOP = execSync('pip3 install solc-select') + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc-select installation output: ${solcSelectOP.toString()}`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: installing solc ${solcVersion}...`) + const solcInstallOP = execSync(`solc-select install ${solcVersion}`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc installation output: ${solcInstallOP.toString()}`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: setting solc version to ${solcVersion}...`) + const solcUseOP = execSync(`solc-select use ${solcVersion}`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: solc setting installation output: ${solcUseOP.toString()}`) + console.log('\x1b[32m%s\x1b[0m', `[Slither Installation]: Slither is ready to use!`) + } catch (err) { + console.log('\x1b[31m%s\x1b[0m', `[Slither Installation]: Error occurred: ${err}`) } + } catch (e) { + console.log(e) } - - - +} module.exports = { - ...process.platform.startsWith('linux')?tests:{} -} \ No newline at end of file + ...process.platform.startsWith('linux')?tests:{} +} diff --git a/libs/remix-ai-core/src/agents/contractAgent.ts b/libs/remix-ai-core/src/agents/contractAgent.ts index 0d3d7c871df..66e7970bc8e 100644 --- a/libs/remix-ai-core/src/agents/contractAgent.ts +++ b/libs/remix-ai-core/src/agents/contractAgent.ts @@ -62,7 +62,6 @@ export class ContractAgent { } // check if file already exists await this.plugin.call('fileManager', 'writeFile', file.fileName, file.content) - await this.plugin.call('codeFormatter', 'format', file.fileName) } return "New workspace created: **" + this.workspaceName + "**\nUse the Hamburger menu to select it!" } diff --git a/libs/remix-api/src/lib/plugins/matomo/core/categories.ts b/libs/remix-api/src/lib/plugins/matomo/core/categories.ts index 910556a7510..d7b8bce9dd5 100644 --- a/libs/remix-api/src/lib/plugins/matomo/core/categories.ts +++ b/libs/remix-api/src/lib/plugins/matomo/core/categories.ts @@ -1,6 +1,6 @@ /** * Matomo Category Constants - * + * * Single source of truth for all Matomo event categories and actions. * These are used for type-safe event creation. */ @@ -8,7 +8,7 @@ // Type-Safe Constants - Access categories and actions via types instead of string literals export const MatomoCategories = { FILE_EXPLORER: 'fileExplorer' as const, - COMPILER: 'compiler' as const, + COMPILER: 'compiler' as const, HOME_TAB: 'hometab' as const, AI: 'AI' as const, UDAPP: 'udapp' as const, @@ -24,6 +24,7 @@ export const MatomoCategories = { LEARNETH: 'learneth' as const, REMIX_GUIDE: 'remixGuide' as const, TEMPLATE_SELECTION: 'template-selection' as const, + TEMPLATE_EXPLORER_MODAL: 'templateExplorerModal' as const, SOLIDITY_UML_GEN: 'solidityumlgen' as const, SOLIDITY_SCRIPT: 'SolidityScript' as const, SCRIPT_EXECUTOR: 'ScriptExecutor' as const, @@ -35,7 +36,7 @@ export const MatomoCategories = { // Common action constants used across multiple categories export const FileExplorerActions = { CONTEXT_MENU: 'contextMenu' as const, - WORKSPACE_MENU: 'workspaceMenu' as const, + WORKSPACE_MENU: 'workspaceMenu' as const, FILE_ACTION: 'fileAction' as const, DRAG_DROP: 'dragDrop' as const } @@ -44,4 +45,4 @@ export const CompilerActions = { COMPILED: 'compiled' as const, ERROR: 'error' as const, WARNING: 'warning' as const -} \ No newline at end of file +} diff --git a/libs/remix-api/src/lib/plugins/matomo/events/tools-events.ts b/libs/remix-api/src/lib/plugins/matomo/events/tools-events.ts index f5eea9bb9ee..f54e346fd5c 100644 --- a/libs/remix-api/src/lib/plugins/matomo/events/tools-events.ts +++ b/libs/remix-api/src/lib/plugins/matomo/events/tools-events.ts @@ -1,6 +1,6 @@ /** * Tools Events - Developer tools and utilities tracking events - * + * * This file contains events for debugger, editor, testing, and other developer tools. */ @@ -8,7 +8,7 @@ import { MatomoEventBase } from '../core/base-types'; export interface DebuggerEvent extends MatomoEventBase { category: 'debugger'; - action: + action: | 'start' | 'step' | 'breakpoint' @@ -17,7 +17,7 @@ export interface DebuggerEvent extends MatomoEventBase { export interface EditorEvent extends MatomoEventBase { category: 'editor'; - action: + action: | 'open' | 'save' | 'format' @@ -31,7 +31,7 @@ export interface EditorEvent extends MatomoEventBase { export interface SolidityUnitTestingEvent extends MatomoEventBase { category: 'solidityUnitTesting'; - action: + action: | 'runTest' | 'generateTest' | 'testPassed' @@ -41,21 +41,21 @@ export interface SolidityUnitTestingEvent extends MatomoEventBase { export interface SolidityStaticAnalyzerEvent extends MatomoEventBase { category: 'solidityStaticAnalyzer'; - action: + action: | 'analyze' | 'warningFound'; } export interface DesktopDownloadEvent extends MatomoEventBase { category: 'desktopDownload'; - action: + action: | 'download' | 'click'; } export interface GridViewEvent extends MatomoEventBase { category: 'gridView'; - action: + action: | 'toggle' | 'resize' | 'rearrange' @@ -64,14 +64,14 @@ export interface GridViewEvent extends MatomoEventBase { export interface XTERMEvent extends MatomoEventBase { category: 'xterm'; - action: + action: | 'terminal' | 'command'; } export interface SolidityScriptEvent extends MatomoEventBase { category: 'SolidityScript'; - action: + action: | 'execute' | 'deploy' | 'run' @@ -80,7 +80,7 @@ export interface SolidityScriptEvent extends MatomoEventBase { export interface RemixGuideEvent extends MatomoEventBase { category: 'remixGuide'; - action: + action: | 'start' | 'step' | 'complete' @@ -91,16 +91,40 @@ export interface RemixGuideEvent extends MatomoEventBase { export interface TemplateSelectionEvent extends MatomoEventBase { category: 'template-selection'; - action: + action: | 'selectTemplate' | 'createWorkspace' | 'cancel' | 'addToCurrentWorkspace'; } +export interface TemplateExplorerModalEvent extends MatomoEventBase { + category: 'templateExplorerModal'; + action: + | 'openModal' + | 'closeModal' + | 'search' + | 'addScriptsToWorkspace' + | 'selectWorkspaceTemplate' + | 'createWorkspaceFromTemplate' + | 'createWorkspaceWithAiRequestSent' + | 'createWorkspaceWithAiFailed' + | 'createWorkspaceWithAiSucceeded' + | 'topCardImportProject' + | 'topCardContractWizard' + | 'topCardCreateWithAi' + | 'topCardCreateBlank' + | 'createWorkspaceWithBasicTemplate' + | 'contractTypeSelectedInContractWizard' + | 'initializeAsGitRepoSelectedInContractWizard' + | 'initializeAsGitRepoSelectedInOtherTemplates' + | 'createWorkspaceWithContractWizard' + | 'createWorkspaceWithGenericTemplate' +} + export interface ScriptExecutorEvent extends MatomoEventBase { category: 'ScriptExecutor'; - action: + action: | 'execute' | 'deploy' | 'run' diff --git a/libs/remix-api/src/lib/plugins/matomo/events/ui-events.ts b/libs/remix-api/src/lib/plugins/matomo/events/ui-events.ts index 17913944a7f..933c0fe1fcc 100644 --- a/libs/remix-api/src/lib/plugins/matomo/events/ui-events.ts +++ b/libs/remix-api/src/lib/plugins/matomo/events/ui-events.ts @@ -1,6 +1,6 @@ /** * UI Events - User interface and navigation tracking events - * + * * This file contains UI-related events like home tab, topbar, and navigation. */ @@ -8,7 +8,7 @@ import { MatomoEventBase } from '../core/base-types'; export interface HomeTabEvent extends MatomoEventBase { category: 'hometab'; - action: + action: | 'header' | 'filesSection' | 'scamAlert' @@ -25,39 +25,39 @@ export interface HomeTabEvent extends MatomoEventBase { export interface TopbarEvent extends MatomoEventBase { category: 'topbar'; - action: + action: | 'GIT' | 'header'; } export interface LayoutEvent extends MatomoEventBase { category: 'layout'; - action: + action: | 'pinToRight' | 'pinToLeft'; } export interface SettingsEvent extends MatomoEventBase { category: 'settings'; - action: + action: | 'change'; } export interface ThemeEvent extends MatomoEventBase { category: 'theme'; - action: + action: | 'switchThemeTo'; } export interface LocaleEvent extends MatomoEventBase { category: 'locale'; - action: + action: | 'switchTo'; } export interface LandingPageEvent extends MatomoEventBase { category: 'landingPage'; - action: + action: | 'welcome' | 'getStarted' | 'tutorial' @@ -68,7 +68,7 @@ export interface LandingPageEvent extends MatomoEventBase { export interface StatusBarEvent extends MatomoEventBase { category: 'statusBar'; - action: + action: | 'initNewRepo'; } @@ -98,6 +98,5 @@ export interface StatusBarEvent extends MatomoEventBase { - diff --git a/libs/remix-api/src/lib/plugins/matomo/index.ts b/libs/remix-api/src/lib/plugins/matomo/index.ts index f14766dbde4..ee5145d6667 100644 --- a/libs/remix-api/src/lib/plugins/matomo/index.ts +++ b/libs/remix-api/src/lib/plugins/matomo/index.ts @@ -1,8 +1,8 @@ /** * Matomo Events - Modular Event System * - * This is the main index for the split Matomo event system. - * It re-exports all events to maintain backward compatibility while + * This is the main index for the split Matomo event system. + * It re-exports all events to maintain backward compatibility while * organizing the code into manageable modules. * * Usage: @@ -34,7 +34,7 @@ import type { HomeTabEvent, TopbarEvent, LayoutEvent, SettingsEvent, ThemeEvent, import type { FileExplorerEvent, WorkspaceEvent, StorageEvent, BackupEvent } from './events/file-events'; import type { BlockchainEvent, UdappEvent, RunEvent } from './events/blockchain-events'; import type { PluginEvent, ManagerEvent, PluginManagerEvent, AppEvent, MatomoManagerEvent, PluginPanelEvent, MigrateEvent } from './events/plugin-events'; -import type { DebuggerEvent, EditorEvent, SolidityUnitTestingEvent, SolidityStaticAnalyzerEvent, DesktopDownloadEvent, XTERMEvent, SolidityScriptEvent, RemixGuideEvent, TemplateSelectionEvent, ScriptExecutorEvent, GridViewEvent, SolidityUMLGenEvent, ScriptRunnerPluginEvent, CircuitCompilerEvent, NoirCompilerEvent, ContractVerificationEvent, LearnethEvent } from './events/tools-events'; +import type { DebuggerEvent, EditorEvent, SolidityUnitTestingEvent, SolidityStaticAnalyzerEvent, DesktopDownloadEvent, XTERMEvent, SolidityScriptEvent, RemixGuideEvent, TemplateSelectionEvent, ScriptExecutorEvent, GridViewEvent, SolidityUMLGenEvent, ScriptRunnerPluginEvent, CircuitCompilerEvent, NoirCompilerEvent, ContractVerificationEvent, LearnethEvent, TemplateExplorerModalEvent } from './events/tools-events'; // Union type of all Matomo events - includes base properties for compatibility export type MatomoEvent = ( @@ -59,7 +59,7 @@ export type MatomoEvent = ( | LocaleEvent | LandingPageEvent | StatusBarEvent - + // File Management events | FileExplorerEvent | WorkspaceEvent @@ -90,6 +90,7 @@ export type MatomoEvent = ( | SolidityScriptEvent | RemixGuideEvent | TemplateSelectionEvent + | TemplateExplorerModalEvent | ScriptExecutorEvent | GridViewEvent | SolidityUMLGenEvent @@ -111,7 +112,7 @@ export type MatomoEvent = ( // 2351-line file into appropriate category modules: // // - blockchain-events.ts (BlockchainEvent, UdappEvent) -// - file-events.ts (FileExplorerEvent, WorkspaceEvent) +// - file-events.ts (FileExplorerEvent, WorkspaceEvent) // - plugin-events.ts (PluginEvent, ManagerEvent, etc.) // - app-events.ts (AppEvent, StorageEvent, etc.) // - debug-events.ts (DebuggerEvent, MatomoManagerEvent) @@ -120,32 +121,32 @@ export type MatomoEvent = ( // - learneth-events.ts (LearnethEvent) // - desktop-events.ts (DesktopDownloadEvent) // - editor-events.ts (EditorEvent) -// +// // Each would follow the same pattern: // 1. Define the TypeScript interface // 2. Export type-safe builder functions // 3. Keep files focused and manageable (~200-400 lines each) -// For backward compatibility, the original matomo-events.ts file would +// For backward compatibility, the original matomo-events.ts file would // be replaced with just: // export * from './matomo'; // Example of how other files would be structured: -/* +/* // blockchain-events.ts export interface BlockchainEvent extends MatomoEventBase { - category: 'blockchain'; + category: 'blockchain'; action: 'providerChanged' | 'networkChanged' | 'accountChanged'; } export const BlockchainEvents = { providerChanged: (name?: string, value?: string | number): BlockchainEvent => ({ category: 'blockchain', - action: 'providerChanged', + action: 'providerChanged', name, value, isClick: true }) } as const; -*/ \ No newline at end of file +*/ diff --git a/libs/remix-ui/app/src/lib/remix-app/actions/app.ts b/libs/remix-ui/app/src/lib/remix-app/actions/app.ts index 6cb33532c92..e4135dd9940 100644 --- a/libs/remix-ui/app/src/lib/remix-app/actions/app.ts +++ b/libs/remix-ui/app/src/lib/remix-app/actions/app.ts @@ -1,4 +1,5 @@ import { branch, desktopConnection, GitHubUser } from '@remix-api'; +import { GenericModal } from '../interface'; type ActionMap = { [Key in keyof M]: M[Key] extends undefined @@ -18,6 +19,8 @@ export const enum appActionTypes { setCanUseGit = 'SET_CAN_USE_GIT', setShowPopupPanel = 'SET_SHOW_POPUP_PANEL', setConnectedToDesktop = 'SET_CONNECTED_TO_DESKTOP', + showGenericModal = 'SHOW_GENERIC_MODAL', + closeGenericModal = 'CLOSE_GENERIC_MODAL', } type AppPayload = { @@ -26,7 +29,9 @@ type AppPayload = { [appActionTypes.setNeedsGitInit]: boolean, [appActionTypes.setCanUseGit]: boolean, [appActionTypes.setShowPopupPanel]: boolean, - [appActionTypes.setConnectedToDesktop]: desktopConnection + [appActionTypes.setConnectedToDesktop]: desktopConnection, + [appActionTypes.showGenericModal]: boolean, + [appActionTypes.closeGenericModal]: boolean } export type AppAction = ActionMap[keyof ActionMap< diff --git a/libs/remix-ui/app/src/lib/remix-app/actions/modals.ts b/libs/remix-ui/app/src/lib/remix-app/actions/modals.ts index 4c2e70764d9..c4fed0d70e9 100644 --- a/libs/remix-ui/app/src/lib/remix-app/actions/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/actions/modals.ts @@ -1,4 +1,4 @@ -import { AppModal } from '../interface' +import { AppModal, GenericModal } from '../interface' type ActionMap = { [Key in keyof M]: M[Key] extends undefined @@ -16,7 +16,8 @@ export const enum modalActionTypes { setToast = 'SET_TOAST', processQueue = 'PROCESS_QUEUEU', handleHideModal = 'HANDLE_HIDE_MODAL', - handleToaster = 'HANDLE_HIDE_TOAST' + handleToaster = 'HANDLE_HIDE_TOAST', + setTemplateExplorer = 'SET_TEMPLATE_EXPLORER' } type ModalPayload = { @@ -24,7 +25,8 @@ type ModalPayload = { [modalActionTypes.handleHideModal]: any [modalActionTypes.setToast]: { message: string | JSX.Element, timestamp: number } [modalActionTypes.handleToaster]: any, - [modalActionTypes.processQueue]: any + [modalActionTypes.processQueue]: any, + [modalActionTypes.setTemplateExplorer]: GenericModal } export type ModalAction = ActionMap[keyof ActionMap< diff --git a/libs/remix-ui/app/src/lib/remix-app/components/modals/aiworkspace-generation.tsx b/libs/remix-ui/app/src/lib/remix-app/components/modals/aiworkspace-generation.tsx new file mode 100644 index 00000000000..f58548c6dfd --- /dev/null +++ b/libs/remix-ui/app/src/lib/remix-app/components/modals/aiworkspace-generation.tsx @@ -0,0 +1,25 @@ +import React, { useContext, useEffect, useState } from 'react' +import { ModalDialog } from '@remix-ui/modal-dialog' +import { useDialogDispatchers } from '../../context/provider' +import { AppContext } from '../../context/context' + +export function AiWorkspaceGeneration() { + const { alert } = useDialogDispatchers() + const [content, setContent] = useState(null) + const { isAiWorkspaceBeingGenerated } = useContext(AppContext) + + useEffect(() => { + if (isAiWorkspaceBeingGenerated){ + setContent(`Your workspace is being generated. Please wait while I generate the workspace for you. It won't be long.`) + } + }, []) + + useEffect(() => { + if (content) { + alert({ id: 'aiWorkspaceGeneration', title: null, message: content }) + } + }, [content]) + + return <> +} + diff --git a/libs/remix-ui/app/src/lib/remix-app/context/context.tsx b/libs/remix-ui/app/src/lib/remix-app/context/context.tsx index 48aa1b391e1..f7ef62c987b 100644 --- a/libs/remix-ui/app/src/lib/remix-app/context/context.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/context/context.tsx @@ -11,6 +11,8 @@ export type appProviderContextType = { modal: any appState: AppState appStateDispatch: React.Dispatch + isAiWorkspaceBeingGenerated: boolean + setIsAiWorkspaceBeingGenerated: (isAiWorkspaceBeingGenerated: boolean) => void } export enum appPlatformTypes { diff --git a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx index baa7931ce85..4d68896c30f 100644 --- a/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/context/provider.tsx @@ -6,7 +6,6 @@ import { modalReducer } from '../reducer/modals' import { ModalInitialState } from '../state/modals' import { ModalTypes } from '../types' import { AppContext, dispatchModalContext, modalContext, platformContext, onLineContext } from './context' - declare global { interface Window { _intl: IntlShape @@ -14,7 +13,7 @@ declare global { } export const ModalProvider = ({ children = [], reducer = modalReducer, initialState = ModalInitialState } = {}) => { - const [{ modals, toasters, focusModal, focusToaster }, dispatch] = useReducer(reducer, initialState) + const [{ modals, toasters, focusModal, focusToaster, focusTemplateExplorer }, dispatch] = useReducer(reducer, initialState) const onNextFn = async () => { dispatch({ @@ -86,7 +85,9 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt return ( - {children} + + {children} + ) } @@ -109,3 +110,11 @@ export const useDialogs = () => { export const useDialogDispatchers = () => { return React.useContext(dispatchModalContext) } + +export const defaultFocusTemplateExplorer = () => { + return ( + <> +

Template Explorer

+ + ) +} diff --git a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts index 084795e3212..72d8643f219 100644 --- a/libs/remix-ui/app/src/lib/remix-app/interface/index.ts +++ b/libs/remix-ui/app/src/lib/remix-app/interface/index.ts @@ -1,5 +1,7 @@ +/* eslint-disable @nrwl/nx/enforce-module-boundaries */ import { branch, desktopConnection, GitHubUser } from '@remix-api' import { AppModalCancelTypes, ModalTypes } from '../types' +import { Template, TemplateGroup, TemplateOption } from 'libs/remix-ui/workspace/src/lib/utils/constants' export type ValidationResult = { valid: boolean, @@ -40,7 +42,8 @@ export interface ModalState { modals: AppModal[], toasters: {message: (string | JSX.Element), timestamp: number }[], focusModal: AppModal, - focusToaster: {message: (string | JSX.Element), timestamp: number } + focusToaster: {message: (string | JSX.Element), timestamp: number }, + focusTemplateExplorer: GenericModal } export interface forceChoiceModal { @@ -49,6 +52,47 @@ export interface forceChoiceModal { message: string | JSX.Element, } +export interface TemplateExplorerGenericData { + workspaceName: string, + modifyWorkspaceName: boolean, + workspaceDescription: string, + workspaceTemplateOptions: TemplateOption, + workspaceTemplateGroup: TemplateGroup, + workspaceTemplate: Template, + workspaceTags: string[] + searchTerm?: string + modifyWorkspace?: boolean +} + +export interface GenericModal { + id?: string + title?: JSX.Element, + message?: JSX.Element, + footer?: JSX.Element, + genericData?: any, + timestamp?: number + hide?: boolean + showModal?: boolean + validationFn?: (value: string) => ValidationResult + // eslint-disable-next-line no-undef + okLabel?: string | JSX.Element + okFn?: (value?:any) => void + cancelLabel?: string | JSX.Element + cancelFn?: (reason?: AppModalCancelTypes) => void, + modalType?: ModalTypes, + modalParentClass?: string + defaultValue?: string + hideFn?: () => void, + resolve?: (value?:any) => void, + next?: () => void, + data?: any, + showCancelIcon?: boolean, + preventBlur?: boolean + placeholderText?: string + width?: string + height?: string +} + export interface AppState { gitHubUser: GitHubUser currentBranch: branch @@ -57,5 +101,6 @@ export interface AppState { showPopupPanel: boolean connectedToDesktop: desktopConnection desktopClientConnected: desktopConnection + genericModalState?: GenericModal } diff --git a/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts b/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts index 2110a638997..f047fdb2f47 100644 --- a/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts +++ b/libs/remix-ui/app/src/lib/remix-app/reducer/app.ts @@ -42,5 +42,12 @@ export const appReducer = (state: AppState, action: AppAction): AppState => { connectedToDesktop: action.payload } } + + case appActionTypes.showGenericModal: { + return { + ...state, + genericModalState: { ...state.genericModalState, showModal: action.payload } + } + } } -} \ No newline at end of file +} diff --git a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts index 9be604ce80f..4e26a6073af 100644 --- a/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts @@ -83,5 +83,9 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda return { ...state, toasters: []} } } + + case modalActionTypes.setTemplateExplorer: { + return { ...state, focusTemplateExplorer: action.payload } + } } } diff --git a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx index 8829ef0d8c0..6261354602c 100644 --- a/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx +++ b/libs/remix-ui/app/src/lib/remix-app/remix-app.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @nrwl/nx/enforce-module-boundaries */ import React, { useEffect, useReducer, useRef, useState } from 'react' import './style/remix-app.css' import { RemixUIMainPanel } from '@remix-ui/panel' @@ -14,6 +15,9 @@ import { appReducer } from './reducer/app' import { appInitialState } from './state/app' import isElectron from 'is-electron' import { desktopConnectionType } from '@remix-api' +import { RemixUiTemplateExplorerModal } from 'libs/remix-ui/template-explorer-modal/src/lib/remix-ui-template-explorer-modal' +import { TemplateExplorerProvider } from 'libs/remix-ui/template-explorer-modal/context/template-explorer-context' +import { AiWorkspaceGeneration } from './components/modals/aiworkspace-generation' interface IRemixAppUi { app: any @@ -40,8 +44,22 @@ const RemixApp = (props: IRemixAppUi) => { const [appState, appStateDispatch] = useReducer(appReducer, { ...appInitialState, showPopupPanel: !window.localStorage.getItem('did_show_popup_panel') && !isElectron(), - connectedToDesktop: props.app.desktopClientMode ? desktopConnectionType .disconnected : desktopConnectionType .disabled + connectedToDesktop: props.app.desktopClientMode ? desktopConnectionType .disconnected : desktopConnectionType .disabled, + genericModalState: { + id: '', + title:
Default Title
, + message:
Default Message
, + footer:
Default Footer
, + okLabel: 'Default Ok Label', + okFn: () => { }, + cancelLabel: 'Default Cancel Label', + cancelFn: () => { }, + width: '720px', + height: '720px', + showModal: false + } }) + const [isAiWorkspaceBeingGenerated, setIsAiWorkspaceBeingGenerated] = useState(false) useEffect(() => { if (props.app.params && props.app.params.activate && props.app.params.activate.split(',').includes('desktopClient')){ @@ -148,7 +166,9 @@ const RemixApp = (props: IRemixAppUi) => { showEnter: props.app.showEnter, modal: props.app.notification, appState: appState, - appStateDispatch: appStateDispatch + appStateDispatch: appStateDispatch, + isAiWorkspaceBeingGenerated: isAiWorkspaceBeingGenerated, + setIsAiWorkspaceBeingGenerated: setIsAiWorkspaceBeingGenerated } return ( @@ -216,6 +236,8 @@ const RemixApp = (props: IRemixAppUi) => { + {appState.genericModalState.showModal && props.app.templateExplorerModal.render() + } diff --git a/libs/remix-ui/app/src/lib/remix-app/state/app.ts b/libs/remix-ui/app/src/lib/remix-app/state/app.ts index 4c0eae622ee..d7b7fc4085b 100644 --- a/libs/remix-ui/app/src/lib/remix-app/state/app.ts +++ b/libs/remix-ui/app/src/lib/remix-app/state/app.ts @@ -9,4 +9,4 @@ export const appInitialState: AppState = { showPopupPanel: false, connectedToDesktop: desktopConnectionType.disabled, desktopClientConnected: desktopConnectionType.disabled -} \ No newline at end of file +} diff --git a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts index 56993cc7ec9..41189f69e88 100644 --- a/libs/remix-ui/app/src/lib/remix-app/state/modals.ts +++ b/libs/remix-ui/app/src/lib/remix-app/state/modals.ts @@ -1,4 +1,6 @@ +import { Template, TemplateGroup } from '@remix-ui/workspace' import { ModalState } from '../interface' +import { defaultFocusTemplateExplorer } from '../context/provider' export const ModalInitialState: ModalState = { modals: [], @@ -15,5 +17,19 @@ export const ModalInitialState: ModalState = { cancelFn: () => { }, showCancelIcon: false }, - focusToaster: { message: '', timestamp: 0 } + focusToaster: { message: '', timestamp: 0 }, + focusTemplateExplorer: { + id: '', + hide: true, + validationFn: () => { return { valid: true, message: '' } }, + okLabel: '', + okFn: () => { }, + cancelLabel: '', + cancelFn: () => { }, + showModal: false, + showCancelIcon: false, + preventBlur: false, + placeholderText: '', + genericData: {} + } } diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index 3839f2e805d..0f583f89269 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -5,6 +5,7 @@ import { isArray } from 'lodash' import Editor, { DiffEditor, loader, Monaco } from '@monaco-editor/react' import { AppContext, AppModal } from '@remix-ui/app' import { MatomoEvent, EditorEvent, AIEvent } from '@remix-api' +//@ts-ignore import { TrackingContext } from '@remix-ide/tracking' import { ConsoleLogs, EventManager, QueryParams } from '@remix-project/remix-lib' import { reducerActions, reducerListener, initialState } from './actions/editor' @@ -160,6 +161,7 @@ const contextMenuEvent = new EventManager() export const EditorUI = (props: EditorUIProps) => { const intl = useIntl() const appContext = useContext(AppContext) + //@ts-ignore const { trackMatomoEvent: baseTrackEvent } = useContext(TrackingContext) const trackMatomoEvent = (event: T) => { baseTrackEvent?.(event) diff --git a/libs/remix-ui/environment-explorer/src/lib/components/environment-explorer-ui.tsx b/libs/remix-ui/environment-explorer/src/lib/components/environment-explorer-ui.tsx index 138c09b718c..ce2d36a44f4 100644 --- a/libs/remix-ui/environment-explorer/src/lib/components/environment-explorer-ui.tsx +++ b/libs/remix-ui/environment-explorer/src/lib/components/environment-explorer-ui.tsx @@ -96,7 +96,7 @@ export const EnvironmentExplorerUI = (props: environmentExplorerUIProps) => { > {section.providers.map(provider => ( + {text} + + ) + } + return ( +
+ {language && ( +
+ {language} + +
+ )} + {!language && ( + + )} +
+                {text}
+              
+
+ ) + }, + // Paragraphs + p: ({ node, ...props }) => ( +

+ ), + // Headings + h1: ({ node, ...props }) => ( +

+ ), + h2: ({ node, ...props }) => ( +

+ ), + h3: ({ node, ...props }) => ( +

+ ), + h4: ({ node, ...props }) => ( +

+ ), + h5: ({ node, ...props }) => ( +

+ ), + h6: ({ node, ...props }) => ( +
+ ), + // Lists + ul: ({ node, ...props }) => ( +