Merge branch 'master' into sensitive

pull/2914/head
bunsenstraat 2 years ago committed by GitHub
commit ba81a4c74f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 263
      .circleci/config.yml
  2. 4
      .eslintrc.json
  3. 14
      .github/workflows/publish-action.yml
  4. 20
      .github/workflows/run-sut.yml
  5. 4
      .prettierignore
  6. 3
      .prettierrc
  7. 2
      CONTRIBUTING.md
  8. 190
      README.md
  9. 11
      apps/debugger/src/app/debugger-api.ts
  10. 4
      apps/debugger/src/app/debugger.ts
  11. 13
      apps/etherscan/.babelrc
  12. 16
      apps/etherscan/.browserslistrc
  13. 34
      apps/etherscan/.eslintrc.json
  14. 7
      apps/etherscan/src/app/App.css
  15. 24
      apps/etherscan/src/app/AppContext.tsx
  16. 153
      apps/etherscan/src/app/app.tsx
  17. 160
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  18. 37
      apps/etherscan/src/app/components/SubmitButton.tsx
  19. 2
      apps/etherscan/src/app/components/index.ts
  20. 37
      apps/etherscan/src/app/hooks/useLocalStorage.tsx
  21. 19
      apps/etherscan/src/app/layouts/Default.tsx
  22. 1
      apps/etherscan/src/app/layouts/index.ts
  23. 17
      apps/etherscan/src/app/logo.svg
  24. 51
      apps/etherscan/src/app/routes.tsx
  25. 11
      apps/etherscan/src/app/star.svg
  26. 6
      apps/etherscan/src/app/types/Receipt.ts
  27. 1
      apps/etherscan/src/app/types/ThemeType.ts
  28. 2
      apps/etherscan/src/app/types/index.ts
  29. 1
      apps/etherscan/src/app/utils/index.ts
  30. 32
      apps/etherscan/src/app/utils/utilities.ts
  31. 59
      apps/etherscan/src/app/views/CaptureKeyView.tsx
  32. 31
      apps/etherscan/src/app/views/ErrorView.tsx
  33. 36
      apps/etherscan/src/app/views/HomeView.tsx
  34. 135
      apps/etherscan/src/app/views/ReceiptsView.tsx
  35. 302
      apps/etherscan/src/app/views/VerifyView.tsx
  36. 4
      apps/etherscan/src/app/views/index.ts
  37. 0
      apps/etherscan/src/assets/.gitkeep
  38. 3
      apps/etherscan/src/environments/environment.prod.ts
  39. 6
      apps/etherscan/src/environments/environment.ts
  40. BIN
      apps/etherscan/src/favicon.ico
  41. 14
      apps/etherscan/src/index.html
  42. 7
      apps/etherscan/src/main.tsx
  43. 7
      apps/etherscan/src/polyfills.ts
  44. 1
      apps/etherscan/src/styles.css
  45. 14
      apps/etherscan/tsconfig.app.json
  46. 20
      apps/etherscan/tsconfig.json
  47. 2
      apps/remix-ide-e2e/.eslintrc
  48. 8
      apps/remix-ide-e2e/nightwatch.ts
  49. 44
      apps/remix-ide-e2e/src/buildGroupTests.js
  50. 2
      apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts
  51. 41
      apps/remix-ide-e2e/src/commands/connectToExternalHttpProvider.ts
  52. 18
      apps/remix-ide-e2e/src/commands/currentWorkspaceIs.ts
  53. 4
      apps/remix-ide-e2e/src/commands/executeScriptInTerminal.ts
  54. 19
      apps/remix-ide-e2e/src/commands/getBrowserLogs.ts
  55. 3
      apps/remix-ide-e2e/src/commands/getEditorValue.ts
  56. 4
      apps/remix-ide-e2e/src/commands/goToVMTraceStep.ts
  57. 4
      apps/remix-ide-e2e/src/commands/journalChildIncludes.ts
  58. 11
      apps/remix-ide-e2e/src/commands/openFile.ts
  59. 8
      apps/remix-ide-e2e/src/commands/rightClickCustom.ts
  60. 18
      apps/remix-ide-e2e/src/commands/switchEnvironment.ts
  61. 20
      apps/remix-ide-e2e/src/commands/switchWorkspace.ts
  62. 4
      apps/remix-ide-e2e/src/commands/testConstantFunction.ts
  63. 11
      apps/remix-ide-e2e/src/commands/testFunction.ts
  64. 2
      apps/remix-ide-e2e/src/commands/validateValueInput.ts
  65. 30
      apps/remix-ide-e2e/src/commands/verifyContracts.ts
  66. 173
      apps/remix-ide-e2e/src/examples/editor-test-contracts.ts
  67. 1
      apps/remix-ide-e2e/src/helpers/hardhat_compilation.ts
  68. 113556
      apps/remix-ide-e2e/src/helpers/hardhat_compilation_8a7ab689ec618720f53ce867a3031c03.json
  69. 17
      apps/remix-ide-e2e/src/helpers/init.ts
  70. 11
      apps/remix-ide-e2e/src/select_tests.sh
  71. 84
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  72. 6
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.test.ts
  73. 11
      apps/remix-ide-e2e/src/tests/compiler_api.test.ts
  74. 8
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  75. 8
      apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
  76. 263
      apps/remix-ide-e2e/src/tests/editor.test.ts
  77. 526
      apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts
  78. 244
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  79. 203
      apps/remix-ide-e2e/src/tests/editorReferences.test.ts
  80. 91
      apps/remix-ide-e2e/src/tests/editor_error_marker.test.ts
  81. 76
      apps/remix-ide-e2e/src/tests/editor_line_text.test.ts
  82. 42
      apps/remix-ide-e2e/src/tests/erc721.test.ts
  83. 107
      apps/remix-ide-e2e/src/tests/etherscan_api.ts
  84. 32
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  85. 26
      apps/remix-ide-e2e/src/tests/fileManager_api.test.ts
  86. 125
      apps/remix-ide-e2e/src/tests/file_decorator.test.ts
  87. 4
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  88. 18
      apps/remix-ide-e2e/src/tests/gist.test.ts
  89. 50
      apps/remix-ide-e2e/src/tests/importFromGithub.test.ts
  90. 4
      apps/remix-ide-e2e/src/tests/migrateFileSystem.test.ts
  91. 2
      apps/remix-ide-e2e/src/tests/pluginManager.test.ts
  92. 36
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  93. 27
      apps/remix-ide-e2e/src/tests/providers.test.ts
  94. 287
      apps/remix-ide-e2e/src/tests/proxy.test.ts
  95. 16
      apps/remix-ide-e2e/src/tests/publishContract.test.ts
  96. 146
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  97. 26
      apps/remix-ide-e2e/src/tests/remixd.test.ts
  98. 56
      apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts
  99. 50
      apps/remix-ide-e2e/src/tests/search.test.ts
  100. 10
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,8 +3,12 @@
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2.1
parameters:
run_flaky_tests:
type: boolean
default: false
orbs:
browser-tools: circleci/browser-tools@1.2.3
browser-tools: circleci/browser-tools@1.3.0
jobs:
build:
docker:
@ -25,17 +29,17 @@ jobs:
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm install
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- save_cache:
key: v1-deps-{{ checksum "package-lock.json" }}
key: v1-deps-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run: npm run downloadsolc_assets
- run: yarn run downloadsolc_assets
- run: npx nx build remix-ide
- run: npx nx build remix-ide-e2e-src-local-plugin
- run: npm run build:libs
- run: yarn run build:libs
- run: mkdir persist && zip -r persist/dist.zip dist
- persist_to_workspace:
root: .
@ -60,8 +64,8 @@ jobs:
- checkout
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm install
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Remix Libs Linting
command: ./apps/remix-ide/ci/lint.sh
@ -87,10 +91,10 @@ jobs:
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm i
- run: cd dist/libs/remix-tests && npm install
- run: npm run test:libs
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run: cd dist/libs/remix-tests && yarn install
- run: yarn run test:libs
remix-ide-chrome:
docker:
@ -123,8 +127,8 @@ jobs:
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm install
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
@ -135,6 +139,49 @@ jobs:
- store_artifacts:
path: ./reports/screenshots
flaky-chrome:
docker:
# specify the version you desire here
- image: cimg/node:14.17.6-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
resource_class: xlarge
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
working_directory: ~/remix-project
parallelism: 80
steps:
- browser-tools/install-chrome
- browser-tools/install-chromedriver
- run:
command: |
google-chrome --version
chromedriver --version
java -jar /usr/local/bin/selenium.jar --version
name: Check install
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
background: true
- run: ./apps/remix-ide/ci/flaky.sh chrome
- store_test_results:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
remix-ide-firefox:
docker:
# specify the version you desire here
@ -166,8 +213,8 @@ jobs:
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm install
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
@ -177,6 +224,136 @@ jobs:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
flaky-firefox:
docker:
# specify the version you desire here
- image: cimg/node:14.17.6-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
resource_class: xlarge
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
working_directory: ~/remix-project
parallelism: 80
steps:
- browser-tools/install-firefox
- browser-tools/install-geckodriver
- run:
command: |
firefox --version
geckodriver --version
java -jar /usr/local/bin/selenium.jar --version
name: Check install
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
background: true
- run: ./apps/remix-ide/ci/flaky.sh firefox
- store_test_results:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
remix-ide-vyper-plugin:
docker:
# specify the version you desire here
- image: cimg/node:14.17.6-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
resource_class: xlarge
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
working_directory: ~/remix-project
parallelism: 10
steps:
- browser-tools/install-chrome
- browser-tools/install-chromedriver
- run:
command: |
google-chrome --version
chromedriver --version
java -jar /usr/local/bin/selenium.jar --version
name: Check install
- checkout
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
background: true
- run: npx nx build vyper
- run: ./apps/remix-ide/ci/browser_tests_vyper_plugin.sh
- store_test_results:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
remix-ide-etherscan-plugin:
docker:
# specify the version you desire here
- image: cimg/node:14.17.6-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
resource_class: xlarge
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
working_directory: ~/remix-project
parallelism: 10
steps:
- browser-tools/install-chrome
- browser-tools/install-chromedriver
- run:
command: |
google-chrome --version
chromedriver --version
java -jar /usr/local/bin/selenium.jar --version
name: Check install
- checkout
- checkout
- attach_workspace:
at: .
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
background: true
- run: npx nx build etherscan
- run: ./apps/remix-ide/ci/browser_tests_etherscan_plugin.sh
- store_test_results:
path: ./reports/tests
- store_artifacts:
path: ./reports/screenshots
remix-ide-plugin-api:
docker:
@ -209,8 +386,8 @@ jobs:
- run: unzip ./persist/dist.zip
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- run: npm install
- v1-deps-{{ checksum "yarn.lock" }}
- run: yarn install
- run:
name: Start Selenium
command: java -jar /usr/local/bin/selenium.jar
@ -234,14 +411,14 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/*raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:
- checkout
- run: npm install
- run: npm run downloadsolc_assets
- run: npm run build:production
- run: yarn install
- run: yarn run downloadsolc_assets
- run: yarn run build:production
- run:
name: Deploy
command: |
@ -263,14 +440,14 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/*raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:
- checkout
- run: npm install
- run: npm run downloadsolc_assets
- run: npm run build:production
- run: yarn install
- run: yarn run downloadsolc_assets
- run: yarn run build:production
- run:
name: Deploy
command: |
@ -291,25 +468,35 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/*raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:
- checkout
- run: npm install
- run: npm run build:libs
- run: npm run downloadsolc_assets
- run: npm run build:production
- run: yarn install
- run: yarn run build:libs
- run: yarn run downloadsolc_assets
- run: yarn run build:production
- run:
name: Deploy
command: |
if [ "${CIRCLE_BRANCH}" == "remix_beta" ]; then
./apps/remix-ide/ci/deploy_from_travis_remix-beta.sh;
fi
workflows:
version: 2
run_flaky_tests:
when: << pipeline.parameters.run_flaky_tests >>
jobs:
- build
- flaky-chrome:
requires:
- build
- flaky-firefox:
requires:
- build
build_all:
unless: << pipeline.parameters.run_flaky_tests >>
jobs:
- build
- lint:
@ -321,6 +508,12 @@ workflows:
- remix-ide-plugin-api:
requires:
- build
- remix-ide-vyper-plugin:
requires:
- build
- remix-ide-etherscan-plugin:
requires:
- build
- remix-ide-chrome:
requires:
- build
@ -334,6 +527,8 @@ workflows:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-plugin-api
- remix-ide-vyper-plugin
- remix-ide-etherscan-plugin
filters:
branches:
only: remix_live
@ -344,6 +539,8 @@ workflows:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-plugin-api
- remix-ide-vyper-plugin
- remix-ide-etherscan-plugin
filters:
branches:
only: master
@ -354,6 +551,8 @@ workflows:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-plugin-api
- remix-ide-vyper-plugin
- remix-ide-etherscan-plugin
filters:
branches:
only: remix_beta

@ -29,7 +29,9 @@
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-empty-function": "off",
"eslint-disable-next-line no-empty": "off",
"no-empty": "off"
"no-empty": "off",
"jsx-a11y/anchor-is-valid": "off",
"@typescript-eslint/no-inferrable-types": "off"
}
},
{

@ -9,18 +9,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- run: npm install
- uses: actions/setup-node@v3
with:
node-version: 14.17.6
- run: yarn install
- run: ls
- run: pwd
- run: npm run downloadsolc_assets
- run: npm run build:production
- run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs')" >> $GITHUB_ENV
- run: yarn run downloadsolc_assets
- run: yarn run build:production
- run: echo "action_state=$('./apps/remix-ide/ci/publishIpfs' ${{ secrets.IPFS_PROJET_ID }} ${{ secrets.IPFS_PROJECT_SECRET }})" >> $GITHUB_ENV
- uses: mshick/add-pr-comment@v1
with:
message: |
ipfs://${{ env.action_state }}
https://ipfs.remixproject.org/ipfs/${{ env.action_state }}
https://gateway.ipfs.io/ipfs/${{ env.action_state }}
https://remix-project.infura-ipfs.io/ipfs/${{ env.action_state }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens
allow-repeats: false # This is the default

@ -0,0 +1,20 @@
name: Running Solidity Unit Tests
on: [push]
jobs:
run_sol_contracts_job:
runs-on: ubuntu-latest
name: A job to run solidity unit tests on github actions CI
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Environment Setup
uses: actions/setup-node@v3
with:
node-version: 14.17.6
- name: Run SUT Action
uses: EthereumRemix/sol-test@v1
with:
test-path: 'apps/remix-ide/contracts/tests'
compiler-version: '0.8.15'

@ -1,4 +0,0 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage

@ -1,3 +0,0 @@
{
"singleQuote": true
}

@ -12,6 +12,6 @@ When you add a code in any library, please ensure you add related tests. You can
Please conform to [standard](https://standardjs.com/) for code styles.
## Submitting Pull Request
Please follow Github's standard model of making changes & submitting pull request which is very well explained [here](https://guides.github.com/activities/forking/). Make sure your code works fine locally before submitting a pull request.
Please follow GitHub's standard model of making changes & submitting pull request which is very well explained [here](https://guides.github.com/activities/forking/). Make sure your code works fine locally before submitting a pull request.

@ -1,25 +1,41 @@
[![CircleCI](https://circleci.com/gh/ethereum/remix-project.svg?style=svg)](https://circleci.com/gh/ethereum/remix-project)
[![Documentation Status](https://readthedocs.org/projects/docs/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project)
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green)](https://github.com/ethereum/awesome-remix)
<p align="center">
<img src="./apps/remix-ide/src/assets/img/icon.png" alt="Remix Logo" width="200"/>
</p>
<h3 align="center">Remix Project</h3>
<div align="center">
[![CircleCI](https://img.shields.io/circleci/build/github/ethereum/remix-project?logo=circleci)](https://circleci.com/gh/ethereum/remix-project)
[![Documentation Status](https://readthedocs.org/projects/remix-ide/badge/?version=latest)](https://remix-ide.readthedocs.io/en/latest/index.html)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![GitHub contributors](https://img.shields.io/github/contributors/ethereum/remix-project?style=flat&logo=github)](https://github.com/ethereum/remix-project/blob/master/CONTRIBUTING.md)
[![Awesome Remix](https://img.shields.io/badge/Awesome--Remix-resources-green?logo=awesomelists)](https://github.com/ethereum/awesome-remix)
![GitHub](https://img.shields.io/github/license/ethereum/remix-project)
[![Join the chat at https://gitter.im/ethereum/remix](https://badges.gitter.im/ethereum/remix.svg)](https://gitter.im/ethereum/remix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=social)](https://twitter.com/ethereumremix)
[![Gitter Chat](https://img.shields.io/badge/Gitter%20-chat-brightgreen?style=plastic&logo=gitter)](https://gitter.im/ethereum/remix)
[![Twitter Follow](https://img.shields.io/twitter/follow/ethereumremix?style=flat&logo=twitter&color=green)](https://twitter.com/ethereumremix)
</div>
## Remix Project
**Remix Project** is a rich toolset including Remix IDE, a comprehensive smart contract development tool. The Remix Project also includes Remix Plugin Engine and Remix Libraries which are low-level tools for wider use.
# Remix Project
## Remix IDE
**Remix IDE** is used for the entire journey of contract development by users of any knowledge level. It fosters a fast development cycle and has a rich set of plugins with intuitive GUIs. The IDE comes in 2 flavors and a VSCode extension:
**Remix Project** is a platform for development tools that use a plugin architecture. It encompasses sub-projects including Remix Plugin Engine, Remix Libraries, and of course Remix IDE.
**Remix Online IDE**, see: [https://remix.ethereum.org](https://remix.ethereum.org)
**Remix IDE** is an open source web and desktop application. It fosters a fast development cycle and has a rich set of plugins with intuitive GUIs. Remix is used for the **entire journey of contract development with [Solidity language](https://soliditylang.org/)** as well as a playground for learning and teaching [Ethereum](https://ethereum.org/).
:point_right: Supported browsers: Firefox v100.0.1 & Chrome v101.0.4951.64. No support for Remix's use on tablets or smartphones or telephones.
Start developing using Remix on browser, visit: [https://remix.ethereum.org](https://remix.ethereum.org)
**Remix Desktop IDE**, see releases: [https://github.com/ethereum/remix-desktop/releases](https://github.com/ethereum/remix-desktop/releases)
For desktop version, see releases: [https://github.com/ethereum/remix-desktop/releases](https://github.com/ethereum/remix-desktop/releases)
![Remix screenshot](https://github.com/ethereum/remix-project/raw/master/apps/remix-ide/remix-screenshot-400h.png)
![Remix screenshot](https://github.com/ethereum/remix-project/raw/master/apps/remix-ide/remix_screenshot.png)
**VSCode extension**, see: [Ethereum-Remix](https://marketplace.visualstudio.com/items?itemName=RemixProject.ethereum-remix)
:point_right: **Remix libraries** work as a core of native plugins of Remix IDE. Read more about libraries [here](libs/README.md)
## Remix libraries
Remix libraries are essential for Remix IDE's native plugins. Read more about libraries [here](libs/README.md)
## Offline Usage
@ -30,7 +46,7 @@ Note: It contains the latest supported version of Solidity available at the time
## Setup
* Install **NPM** and **Node.js**. See [Guide](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) <br/>
* Install **Yarn** and **Node.js**. See [Guide for NodeJs](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) and [Yarn install](https://classic.yarnpkg.com/lang/en/docs/install)<br/>
*Supported versions:*
```bash
"engines": {
@ -38,9 +54,9 @@ Note: It contains the latest supported version of Solidity available at the time
"npm": "^6.14.15"
}
```
* Install [Nx CLI](https://nx.dev/react/cli/overview) globally to enable running **nx executable commands**.
* Install [Nx CLI](https://nx.dev/using-nx/nx-cli) globally to enable running **nx executable commands**.
```bash
npm install -g @nrwl/cli
yarn global add nx
```
* Clone the github repository (`wget` need to be installed first):
@ -50,8 +66,8 @@ git clone https://github.com/ethereum/remix-project.git
* Build `remix-project`:
```bash
cd remix-project
npm install
npm run build:libs // Build remix libs
yarn install
yarn run build:libs // Build remix libs
nx build
nx serve
```
@ -63,12 +79,12 @@ Go to your `text editor` and start developing. Browser will automatically refres
## Production Build
To generate react production builds for remix-project.
```bash
npm run build:production
yarn run build:production
```
Build can be found in `remix-project/dist/apps/remix-ide` directory.
```bash
npm run serve:production
yarn run serve:production
```
Production build will be served by default to `http://localhost:8080/` or `http://127.0.0.1:8080/`
@ -111,7 +127,7 @@ curl https://raw.githubusercontent.com/ethereum/remix-project/master/docker-comp
### Troubleshooting
If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/react/cli/overview) is installed globally.
If you have trouble building the project, make sure that you have the correct version of `node`, `npm` and `nvm`. Also ensure [Nx CLI](https://nx.dev/using-nx/nx-cli) is installed globally.
Run:
@ -133,77 +149,137 @@ For example, to run unit tests of `remix-analyzer`, use `nx test remix-analyzer`
To run the Selenium tests via Nightwatch:
- Install Selenium for first time: `npm run selenium-install`
- Run a selenium server: `npm run selenium`
- Install Selenium for first time: `yarn run selenium-install`
- Run a selenium server: `yarn run selenium`
- Build & Serve Remix: `nx serve`
- Run all the end-to-end tests:
for Firefox: `npm run nightwatch_local_firefox`, or
for Firefox: `yarn run nightwatch_local_firefox`, or
for Google Chrome: `npm run nightwatch_local_chrome`
- Run a specific test case instead, use one of following commands:
for Google Chrome: `yarn run nightwatch_local_chrome`
- Run a specific test case instead, use a command like this:
- npm run nightwatch_local_ballot
- yarn run nightwatch_local_ballot
- npm run nightwatch_local_usingWorker
The package.json file contains a list of all the tests you can run.
- npm run nightwatch_local_libraryDeployment
**NOTE:**
- npm run nightwatch_local_solidityImport
- **The `ballot` tests suite** requires to run `ganache-cli` locally.
- npm run nightwatch_local_recorder
- **The `remixd` tests suite** requires to run `remixd` locally.
- npm run nightwatch_local_transactionExecution
- **The `gist` tests suite** requires specifying a github access token in **.env file**.
```
gist_token = <token> // token should have permission to create a gist
```
- npm run nightwatch_local_staticAnalysis
### Using 'select_test' for locally running specific tests
- npm run nightwatch_local_signingMessage
There is a script to allow selecting the browser and a specific test to run:
- npm run nightwatch_local_specialFunctions
```
yarn run select_test
```
- npm run nightwatch_local_solidityUnitTests
You need to have
- npm run nightwatch_local_remixd # remixd needs to be run
- selenium running
- npm run nightwatch_local_terminal
- the IDE running
- npm run nightwatch_local_gist
- optionally have remixd or ganache running
- npm run nightwatch_local_workspace
### Splitting tests with groups
- npm run nightwatch_local_defaultLayout
Groups can be used to group tests in a test file together. The advantage is you can avoid running long test files when you want to focus on a specific set of tests within a test file.x
- npm run nightwatch_local_pluginManager
These groups only apply to the test file, not across all test files. So for example group1 in the ballot is not related to group1 in another test file.
- npm run nightwatch_local_publishContract
Running a group only runs the tests marked as belonging to the group + all the tests in the test file that do not have a group tag. This way you can have tests that run for all groups, for example to peform common actions.
- npm run nightwatch_local_generalSettings
There is no need to number the groups in a certain order. The number of the group is arbitrary.
- npm run nightwatch_local_fileExplorer
A test can have multiple group tags, this means that this test will run in different groups.
- npm run nightwatch_local_debugger
You should write your tests so they can be executed in groups and not depend on other groups.
- npm run nightwatch_local_editor
To do this you need to:
- npm run nightwatch_local_compiler
- Add a group to tag to a test, they are formatted as #group followed by a number: so it becomes #group1, #group220, #group4. Any number will do. You don't have to do it in specific order.
- npm run nightwatch_local_txListener
```
'Should generate test file #group1': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
```
- npm run nightwatch_local_fileManager
- add '@disabled': true to the test file you want to split:
- npm run nightwatch_local_runAndDeploy
```
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done) // , 'http://localhost:8080', false)
},
```
- change package json to locally run all group tests:
```
"nightwatch_local_debugger": "yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/debugger_*.spec.js --env=chrome",
```
**NOTE:**
- run the build script to build the test files if you want to run the locally
- **The `ballot` tests suite** requires to run `ganache-cli` locally.
```
yarn run build:e2e
```
- **The `remixd` tests suite** requires to run `remixd` locally.
### Locally testing group tests
- **The `gist` tests suite** requires specifying a github access token in **.env file**.
You can tag any test with a groupname, for example, #group10 and easily run the test locally.
- make sure you have nx installed globally
- group tests are run like any other test, just specify the correct group number
#### method 1
This script will give you an option menu, just select the test you want
```
gist_token = <token> // token should have permission to create a gist
yarn run select_test
```
#### method 2
```
yarn run group_test --test=debugger --group=10 --env=chromeDesktop
```
- specify chromeDesktop to see the browser action, use 'chrome' to run it headless
### Run the same (flaky) test across all instances in CircleCI
In CircleCI all tests are divided across instances to run in paralel.
You can also run 1 or more tests simultaneously across all instances.
This way the pipeline can easily be restarted to check if a test is flaky.
For example:
```
'Static Analysis run with remixd #group3 #flaky': function (browser) {
```
Now group3 of this test will be executed in firefox and chrome 80 times.
If you mark more groups in other tests they will also be executed.
**CONFIGURATION**
It's important to set a parameter in the .circleci/config.yml, set it to false then the normal tests will run.
Set it to true to run only tests marked with flaky.
```
parameters:
run_flaky_tests:
type: boolean
default: true
```
## Important Links

@ -8,8 +8,6 @@ export const DebuggerApiMixin = (Base) => class extends Base {
initialWeb3
initDebuggerApi () {
this.debugHash = null
const self = this
this.web3Provider = {
sendAsync (payload, callback) {
@ -132,7 +130,6 @@ export const DebuggerApiMixin = (Base) => class extends Base {
} catch (e) {
console.error(e)
}
this.debugHash = hash
if (web3) this._web3 = web3
else this._web3 = this.initialWeb3
remixDebug.init.extendWeb3(this._web3)
@ -154,6 +151,14 @@ export const DebuggerApiMixin = (Base) => class extends Base {
}
showMessage (title: string, message: string) {}
onStartDebugging () {
this.call('layout', 'maximiseSidePanel')
}
onStopDebugging () {
this.call('layout', 'resetSidePanel')
}
}
export class CompilerAbstract implements CompilationOutput { // this is a subset of /remix-ide/src/app/compiler/compiler-abstract.js

@ -12,8 +12,6 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
}
offsetToLineColumnConverter: IDebuggerApi['offsetToLineColumnConverter']
debugHash: string
debugHashRequest: number
removeHighlights: boolean
onBreakpointCleared: (listener: onBreakpointClearedListener) => void
onBreakpointAdded: (listener: onBreakpointAddedListener) => void
@ -26,5 +24,7 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
setFile: (path: string, content: string) => Promise<void>
getDebugWeb3: () => any // returns an instance of web3.js, if applicable (mainet, goerli, ...) it returns a reference to a node from devops (so we are sure debug endpoint is available)
web3: () => any // returns an instance of web3.js
onStartDebugging: () => void // called when debug starts
onStopDebugging: () => void // called when debug stops
}

@ -0,0 +1,13 @@
{
"presets": [
[
"@nrwl/react/babel", {
"runtime": "automatic"
}
]
],
"plugins": [
]
}

@ -0,0 +1,16 @@
# This file is used by:
# 1. autoprefixer to adjust CSS to support the below specified browsers
# 2. babel preset-env to adjust included polyfills
#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
# If you need to support different browsers in production, you may tweak the list below.
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major version
last 2 iOS major versions
Firefox ESR
not IE 9-11 # For IE 9-11 support, remove 'not'.

@ -0,0 +1,34 @@
{
"extends": [
"plugin:@nrwl/nx/react",
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

@ -0,0 +1,7 @@
body {
margin: 0;
}
#root {
padding: 5px;
}

@ -0,0 +1,24 @@
import React from "react"
import { PluginClient } from "@remixproject/plugin"
import { Receipt, ThemeType } from "./types"
export const AppContext = React.createContext({
apiKey: "",
setAPIKey: (value: string) => {
console.log("Set API Key from Context")
},
clientInstance: {} as PluginClient,
receipts: [] as Receipt[],
setReceipts: (receipts: Receipt[]) => {
console.log("Calling Set Receipts")
},
contracts: [] as string[],
setContracts: (contracts: string[]) => {
console.log("Calling Set Contract Names")
},
themeType: "dark" as ThemeType,
setThemeType: (themeType: ThemeType) => {
console.log("Calling Set Theme Type")
},
})

@ -0,0 +1,153 @@
import React, { useState, useEffect, useRef } from "react"
import {
CompilationFileSources,
CompilationResult,
} from "@remixproject/plugin-api"
import { PluginClient } from "@remixproject/plugin";
import { createClient } from "@remixproject/plugin-webview";
import { AppContext } from "./AppContext"
import { DisplayRoutes } from "./routes"
import { useLocalStorage } from "./hooks/useLocalStorage"
import { getReceiptStatus, getEtherScanApi, getNetworkName } from "./utils"
import { Receipt, ThemeType } from "./types"
import "./App.css"
export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts
let result: string[] = []
for (const file of Object.keys(compiledContracts)) {
const newContractNames = Object.keys(compiledContracts[file])
result = [...result, ...newContractNames]
}
return result
}
const App = () => {
const [apiKey, setAPIKey] = useLocalStorage("apiKey", "")
const [clientInstance, setClientInstance] = useState(undefined as any)
const [receipts, setReceipts] = useLocalStorage("receipts", [])
const [contracts, setContracts] = useState([] as string[])
const [themeType, setThemeType] = useState("dark" as ThemeType)
const clientInstanceRef = useRef(clientInstance)
clientInstanceRef.current = clientInstance
const contractsRef = useRef(contracts)
contractsRef.current = contracts
useEffect(() => {
const client = new PluginClient()
createClient(client)
const loadClient = async () => {
await client.onload()
setClientInstance(client)
client.on("solidity",
"compilationFinished",
(
fileName: string,
source: CompilationFileSources,
languageVersion: string,
data: CompilationResult
) => {
const newContractsNames = getNewContractNames(data)
const newContractsToSave: string[] = [
...contractsRef.current,
...newContractsNames,
]
const uniqueContracts: string[] = [...new Set(newContractsToSave)]
setContracts(uniqueContracts)
}
)
//const currentTheme = await client.call("theme", "currentTheme")
//setThemeType(currentTheme.quality)
//client.on("theme", "themeChanged", (theme) => {
// setThemeType(theme.quality)
//})
}
loadClient()
}, [])
useEffect(() => {
if (!clientInstance) {
return
}
const receiptsNotVerified: Receipt[] = receipts.filter((item: Receipt) => {
return item.status !== "Verified"
})
if (receiptsNotVerified.length > 0) {
let timer1 = setInterval(() => {
for (const item in receiptsNotVerified) {
}
receiptsNotVerified.forEach(async (item) => {
if (!clientInstanceRef.current) {
return {}
}
const network = await getNetworkName(clientInstanceRef.current)
if (network === "vm") {
return {}
}
const status = await getReceiptStatus(
item.guid,
apiKey,
getEtherScanApi(network)
)
if (status === "Pass - Verified") {
const newReceipts = receipts.map((currentReceipt: Receipt) => {
if (currentReceipt.guid === item.guid) {
return {
...currentReceipt,
status: "Verified",
}
}
return currentReceipt
})
clearInterval(timer1)
setReceipts(newReceipts)
return () => {
clearInterval(timer1)
}
}
return {}
})
}, 5000)
}
}, [receipts, clientInstance, apiKey, setReceipts])
return (
<AppContext.Provider
value={{
apiKey,
setAPIKey,
clientInstance,
receipts,
setReceipts,
contracts,
setContracts,
themeType,
setThemeType,
}}
>
<DisplayRoutes />
</AppContext.Provider>
)
}
export default App

@ -0,0 +1,160 @@
import React from "react"
import { NavLink } from "react-router-dom"
import { AppContext } from "../AppContext"
import { ThemeType } from "../types"
interface Props {
title?: string
showBackButton?: boolean
from: string
}
interface IconProps {
from: string
themeType: ThemeType
}
const HomeIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-id="home"
data-toggle="tooltip"
data-placement="top"
title="Home"
to={{
pathname: "/"
}}
state={ from }
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-house-door-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M6.5 10.995V14.5a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .146-.354l6-6a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 .146.354v7a.5.5 0 0 1-.5.5h-4a.5.5 0 0 1-.5-.5V11c0-.25-.25-.5-.5-.5H7c-.25 0-.5.25-.5.495z" />
<path
fillRule="evenodd"
d="M13 2.5V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"
/>
</svg>
</NavLink>
)
}
const SettingsIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-toggle="tooltip"
data-placement="top"
title="Settings"
to={{
pathname: "/settings",
}}
state= {from}
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-gear-fill"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"
/>
<path
fillRule="evenodd"
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 0 0-5.86 2.929 2.929 0 0 0 0 5.858z"
/>
</svg>
</NavLink>
)
}
const getStyleFilterIcon = (themeType: ThemeType) => {
const invert = themeType === "dark" ? 1 : 0
const brightness = themeType === "dark" ? "150" : "0" // should be >100 for icons with color
return {
filter: `invert(${invert}) grayscale(1) brightness(${brightness}%)`,
}
}
const ReceiptsIcon: React.FC<IconProps> = ({ from, themeType }: IconProps) => {
return (
<NavLink
data-toggle="tooltip"
data-placement="top"
title="Receipts"
to={{
pathname: "/receipts",
}}
state= { from }
style={isActive => {
return {
...(isActive ? getStyleFilterIcon(themeType) : {}), ...{ marginRight: "0.4em" }
}
}}
>
<svg
style={{ filter: "invert(0.5)" }}
width="1em"
height="1em"
viewBox="0 0 16 16"
className="bi bi-receipt"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M1.92.506a.5.5 0 0 1 .434.14L3 1.293l.646-.647a.5.5 0 0 1 .708 0L5 1.293l.646-.647a.5.5 0 0 1 .708 0L7 1.293l.646-.647a.5.5 0 0 1 .708 0L9 1.293l.646-.647a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .801.13l.5 1A.5.5 0 0 1 15 2v12a.5.5 0 0 1-.053.224l-.5 1a.5.5 0 0 1-.8.13L13 14.707l-.646.647a.5.5 0 0 1-.708 0L11 14.707l-.646.647a.5.5 0 0 1-.708 0L9 14.707l-.646.647a.5.5 0 0 1-.708 0L7 14.707l-.646.647a.5.5 0 0 1-.708 0L5 14.707l-.646.647a.5.5 0 0 1-.708 0L3 14.707l-.646.647a.5.5 0 0 1-.801-.13l-.5-1A.5.5 0 0 1 1 14V2a.5.5 0 0 1 .053-.224l.5-1a.5.5 0 0 1 .367-.27zm.217 1.338L2 2.118v11.764l.137.274.51-.51a.5.5 0 0 1 .707 0l.646.647.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.646.646.646-.646a.5.5 0 0 1 .708 0l.509.509.137-.274V2.118l-.137-.274-.51.51a.5.5 0 0 1-.707 0L12 1.707l-.646.647a.5.5 0 0 1-.708 0L10 1.707l-.646.647a.5.5 0 0 1-.708 0L8 1.707l-.646.647a.5.5 0 0 1-.708 0L6 1.707l-.646.647a.5.5 0 0 1-.708 0L4 1.707l-.646.647a.5.5 0 0 1-.708 0l-.509-.51z"
/>
<path
fillRule="evenodd"
d="M3 4.5a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 1 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm8-6a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5z"
/>
</svg>
</NavLink>
)
}
export const HeaderWithSettings: React.FC<Props> = ({
title = "",
showBackButton = false,
from,
}) => {
return (
<AppContext.Consumer>
{({ themeType }) => (
<div>
<h6>{title}</h6>
<div style={{ float: "right" }}>
<HomeIcon from={from} themeType={themeType} />
<ReceiptsIcon from={from} themeType={themeType} />
<SettingsIcon from={from} themeType={themeType} />
</div>
</div>
)}
</AppContext.Consumer>
)
}

@ -0,0 +1,37 @@
import React from "react"
interface Props {
text: string
isSubmitting?: boolean
dataId?: string
}
export const SubmitButton: React.FC<Props> = ({
text,
dataId,
isSubmitting = false,
}) => {
return (
<button
data-id={dataId}
style={{ padding: "0.25rem 0.4rem", marginRight: "0.5em" }}
type="submit"
className="btn btn-primary"
disabled={isSubmitting}
>
{!isSubmitting && text}
{isSubmitting && (
<div>
<span
className="spinner-border spinner-border-sm"
role="status"
aria-hidden="true"
style={{ marginRight: "0.3em" }}
/>
Verifying... Please wait
</div>
)}
</button>
)
}

@ -0,0 +1,2 @@
export { HeaderWithSettings } from "./HeaderWithSettings"
export { SubmitButton } from "./SubmitButton"

@ -0,0 +1,37 @@
import { useState } from "react"
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key)
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue
} catch (error) {
// If error also return initialValue
console.error(error)
return initialValue
}
})
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value
// Save state
setStoredValue(valueToStore)
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
// A more advanced implementation would handle the error case
console.error(error)
}
}
return [storedValue, setValue]
}

@ -0,0 +1,19 @@
import React, { PropsWithChildren } from "react"
import { HeaderWithSettings } from "../components"
interface Props {
from: string
}
export const DefaultLayout: React.FC<PropsWithChildren<Props>> = ({
children,
from,
}) => {
return (
<div>
<HeaderWithSettings from={from} />
{children}
</div>
)
}

@ -0,0 +1 @@
export { DefaultLayout } from "./Default"

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="262px" height="163px" viewBox="0 0 262 163" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Styles-&amp;-Quick-Wins" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Nx---Quick-Wins" transform="translate(-476.000000, -1284.000000)" fill-rule="nonzero">
<g id="Logos" transform="translate(-11.000000, 782.000000)">
<g id="Nx_Flat_White" transform="translate(487.000000, 502.000000)">
<polygon id="Path" fill="#FFFFFF" points="130.68 104.59 97.49 52.71 97.44 96.3 40.24 0 0 0 0 162.57 39.79 162.57 39.92 66.39 96.53 158.26"></polygon>
<polygon id="Path" fill="#FFFFFF" points="97.5 41.79 137.24 41.79 137.33 41.33 137.33 0 97.54 0 97.49 41.33"></polygon>
<path d="M198.66,86.86 C189.139872,86.6795216 180.538723,92.516445 177.19,101.43 C182.764789,93.0931021 193.379673,89.7432211 202.73,93.37 C207.05,95.13 212.73,97.97 217.23,96.45 C212.950306,90.4438814 206.034895,86.8725952 198.66,86.86 L198.66,86.86 Z" id="Path" fill="#96D8E9"></path>
<path d="M243.75,106.42 C243.75,101.55 241.1,100.42 235.6,98.42 C231.52,97 226.89,95.4 223.52,91 C222.86,90.13 222.25,89.15 221.6,88.11 C220.14382,85.4164099 218.169266,83.037429 215.79,81.11 C212.58,78.75 208.37,77.6 202.91,77.6 C191.954261,77.6076705 182.084192,84.2206169 177.91,94.35 C183.186964,87.0278244 191.956716,83.0605026 200.940147,83.9314609 C209.923578,84.8024193 217.767888,90.3805017 221.54,98.58 C223.424615,101.689762 227.141337,103.174819 230.65,102.22 C236.02,101.07 235.65,106.15 243.76,107.87 L243.75,106.42 Z" id="Path" fill="#48C4E5"></path>
<path d="M261.46,105.38 L261.46,105.27 C261.34,73.03 235.17,45.45 202.91,45.45 C183.207085,45.4363165 164.821777,55.3450614 154,71.81 L153.79,71.45 L137.23,45.45 L97.5,45.4499858 L135.25,104.57 L98.41,162.57 L137,162.57 L153.79,136.78 L170.88,162.57 L209.48,162.57 L174.48,107.49 C173.899005,106.416838 173.583536,105.220114 173.56,104 C173.557346,96.2203871 176.64661,88.7586448 182.147627,83.2576275 C187.648645,77.7566101 195.110387,74.6673462 202.89,74.67 C219.11,74.67 221.82,84.37 225.32,88.93 C232.23,97.93 246.03,93.99 246.03,105.73 L246.03,105.73 C246.071086,108.480945 247.576662,111.001004 249.979593,112.340896 C252.382524,113.680787 255.317747,113.636949 257.679593,112.225896 C260.041438,110.814842 261.471086,108.250945 261.43,105.5 L261.43,105.5 L261.43,105.38 L261.46,105.38 Z" id="Path" fill="#FFFFFF"></path>
<path d="M261.5,113.68 C261.892278,116.421801 261.504116,119.218653 260.38,121.75 C258.18,126.84 254.51,125.14 254.51,125.14 C254.51,125.14 251.35,123.6 253.27,120.65 C255.4,117.36 259.61,117.74 261.5,113.68 Z" id="Path" fill="#FFFFFF"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,51 @@
import React from "react"
import {
HashRouter as Router,
Route,
Routes,
RouteProps,
} from "react-router-dom"
import { ErrorView, HomeView, ReceiptsView, CaptureKeyView } from "./views"
import { DefaultLayout } from "./layouts"
interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component
from: string
}
const RouteWithHeader = ({ component: Component, ...rest }: Props) => {
return (
<Route
{...rest}
>
<DefaultLayout {...rest}>
<Component />
</DefaultLayout>
</Route>
)
}
export const DisplayRoutes = () => (
<Router>
<Routes>
<Route
path="/"
element={<DefaultLayout from="/">
<HomeView />
</DefaultLayout>} />
<Route path="/error"
element={<ErrorView />} />
<Route
path="/receipts"
element={<DefaultLayout from="/receipts">
<ReceiptsView />
</DefaultLayout>} />
<Route
path="/settings"
element={<DefaultLayout from="/settings">
<CaptureKeyView />
</DefaultLayout>} />
</Routes>
</Router>
)

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
className="material-icons"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>

After

Width:  |  Height:  |  Size: 347 B

@ -0,0 +1,6 @@
export type ReceiptStatus = "Verified" | "Queue"
export interface Receipt {
guid: string
status: ReceiptStatus
}

@ -0,0 +1 @@
export type ThemeType = "dark" | "light"

@ -0,0 +1,2 @@
export * from "./Receipt"
export * from "./ThemeType"

@ -0,0 +1 @@
export * from "./utilities"

@ -0,0 +1,32 @@
import { PluginClient } from "@remixproject/plugin"
import axios from 'axios'
type RemixClient = PluginClient
export const getEtherScanApi = (network: string) => {
return network === "main"
? `https://api.etherscan.io/api`
: `https://api-${network}.etherscan.io/api`
}
export const getNetworkName = async (client: RemixClient) => {
const network = await client.call("network", "detectNetwork")
if (!network) {
throw new Error("no known network to verify against")
}
return network.name!.toLowerCase()
}
export const getReceiptStatus = async (
receiptGuid: string,
apiKey: string,
etherscanApi: string
) => {
const params = `guid=${receiptGuid}&module=contract&action=checkverifystatus&apiKey=${apiKey}`
try {
const response = await axios.get(`${etherscanApi}?${params}`)
const { result } = response.data
return result
} catch (error) {
console.error(error)
}
}

@ -0,0 +1,59 @@
import React from "react"
import { Formik, ErrorMessage, Field } from "formik"
import { useNavigate, useLocation } from "react-router-dom"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
export const CaptureKeyView: React.FC = () => {
const location = useLocation()
const navigate = useNavigate()
return (
<AppContext.Consumer>
{({ apiKey, setAPIKey }) => (
<Formik
initialValues={{ apiKey }}
validate={(values) => {
const errors = {} as any
if (!values.apiKey) {
errors.apiKey = "Required"
}
return errors
}}
onSubmit={(values) => {
setAPIKey(values.apiKey)
navigate((location.state as any).from)
}}
>
{({ errors, touched, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="form-group" style={{ marginBottom: "0.5rem" }}>
<label htmlFor="apikey">Please Enter your API key</label>
<Field
className={
errors.apiKey && touched.apiKey
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="apiKey"
placeholder="Example: GM1T20XY6JGSAPWKDCYZ7B2FJXKTJRFVGZ"
/>
<ErrorMessage
className="invalid-feedback"
name="apiKey"
component="div"
/>
</div>
<div>
<SubmitButton text="Save API key" dataId="save-api-key" />
</div>
</form>
)}
</Formik>
)}
</AppContext.Consumer>
)
}

@ -0,0 +1,31 @@
import React from "react"
export const ErrorView: React.FC = () => {
return (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img
style={{ paddingBottom: "2em" }}
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<h5>Sorry, something unexpected happened. </h5>
<h5>
Please raise an issue:{" "}
<a
style={{ color: "red" }}
href="https://github.com/ethereum/remix-project/issues"
>
Here
</a>
</h5>
</div>
)
}

@ -0,0 +1,36 @@
import React from "react"
import { Navigate } from "react-router-dom"
import { AppContext } from "../AppContext"
import { Receipt } from "../types"
import { VerifyView } from "./VerifyView"
export const HomeView: React.FC = () => {
// const [hasError, setHasError] = useState(false)
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, setReceipts, receipts, contracts }) =>
!apiKey ? (
<Navigate
to={{
pathname: "/settings"
}}
/>
) : (
<VerifyView
contracts={contracts}
client={clientInstance}
apiKey={apiKey}
onVerifiedContract={(receipt: Receipt) => {
const newReceipts = [...receipts, receipt]
setReceipts(newReceipts)
}}
/>
)
}
</AppContext.Consumer>
)
}

@ -0,0 +1,135 @@
import React, { useState } from "react"
import { Formik, ErrorMessage, Field } from "formik"
import { getEtherScanApi, getNetworkName, getReceiptStatus } from "../utils"
import { Receipt } from "../types"
import { AppContext } from "../AppContext"
import { SubmitButton } from "../components"
import { Navigate } from "react-router-dom"
interface FormValues {
receiptGuid: string
}
export const ReceiptsView: React.FC = () => {
const [results, setResults] = useState("")
const onGetReceiptStatus = async (
values: FormValues,
clientInstance: any,
apiKey: string
) => {
try {
const network = await getNetworkName(clientInstance)
if (network === "vm") {
setResults("Cannot verify in the selected network")
return
}
const etherscanApi = getEtherScanApi(network)
const result = await getReceiptStatus(
values.receiptGuid,
apiKey,
etherscanApi
)
setResults(result)
} catch (error: any) {
setResults(error.message)
}
}
return (
<AppContext.Consumer>
{({ apiKey, clientInstance, receipts }) =>
!apiKey ? (
<Navigate
to={{
pathname: "/settings"
}}
/>
) : (
<div>
<Formik
initialValues={{ receiptGuid: "" }}
validate={(values) => {
const errors = {} as any
if (!values.receiptGuid) {
errors.receiptGuid = "Required"
}
return errors
}}
onSubmit={(values) =>
onGetReceiptStatus(values, clientInstance, apiKey)
}
>
{({ errors, touched, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div
className="form-group"
style={{ marginBottom: "0.5rem" }}
>
<h6>Get your Receipt GUID status</h6>
<label htmlFor="receiptGuid">Receipt GUID</label>
<Field
className={
errors.receiptGuid && touched.receiptGuid
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="receiptGuid"
/>
<ErrorMessage
className="invalid-feedback"
name="receiptGuid"
component="div"
/>
</div>
<SubmitButton text="Check" />
</form>
)}
</Formik>
<div
style={{
marginTop: "2em",
fontSize: "0.8em",
textAlign: "center",
}}
dangerouslySetInnerHTML={{ __html: results }}
/>
<ReceiptsTable receipts={receipts} />
</div>
)
}
</AppContext.Consumer>
)
}
const ReceiptsTable: React.FC<{ receipts: Receipt[] }> = ({ receipts }) => {
return (
<div className="table-responsive" style={{ fontSize: "0.7em" }}>
<h6>Receipts</h6>
<table className="table table-sm">
<thead>
<tr>
<th scope="col">Guid</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
{receipts &&
receipts.length > 0 &&
receipts.map((item: Receipt, index) => {
return (
<tr key={item.guid}>
<td>{item.guid}</td>
<td>{item.status}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)
}

@ -0,0 +1,302 @@
import React, { useState } from "react"
import {
PluginClient,
} from "@remixproject/plugin"
import { Formik, ErrorMessage, Field } from "formik"
import { getNetworkName, getEtherScanApi, getReceiptStatus } from "../utils"
import { SubmitButton } from "../components"
import { Receipt } from "../types"
import { CompilationResult } from "@remixproject/plugin-api"
import axios from 'axios'
interface Props {
client: PluginClient
apiKey: string
onVerifiedContract: (receipt: Receipt) => void
contracts: string[]
}
interface FormValues {
contractName: string
contractArguments: string
contractAddress: string
}
export const getContractFileName = (
compilationResult: CompilationResult,
contractName: string
) => {
const compiledContracts = compilationResult.contracts
let fileName = ""
for (const file of Object.keys(compiledContracts)) {
for (const contract of Object.keys(compiledContracts[file])) {
if (contract === contractName) {
fileName = file
break
}
}
}
return fileName
}
export const getContractMetadata = (
compilationResult: CompilationResult,
contractName: string
) => {
const compiledContracts = compilationResult.contracts
let contractMetadata = ""
for (const file of Object.keys(compiledContracts)) {
for (const contract of Object.keys(compiledContracts[file])) {
if (contract === contractName) {
contractMetadata = compiledContracts[file][contract].metadata
if (contractMetadata) {
break
}
}
}
}
return contractMetadata
}
export const VerifyView: React.FC<Props> = ({
apiKey,
client,
contracts,
onVerifiedContract,
}) => {
const [results, setResults] = useState("")
const onVerifyContract = async (values: FormValues) => {
const compilationResult = (await client.call(
"solidity",
"getCompilationResult"
)) as any
if (!compilationResult) {
throw new Error("no compilation result available")
}
const contractArguments = values.contractArguments.replace("0x", "")
const verify = async (
apiKeyParam: string,
contractAddress: string,
contractArgumentsParam: string,
contractName: string,
compilationResultParam: any
) => {
const network = await getNetworkName(client)
if (network === "vm") {
return "Cannot verify in the selected network"
}
const etherscanApi = getEtherScanApi(network)
try {
const contractMetadata = getContractMetadata(
compilationResultParam.data,
contractName
)
if (!contractMetadata) {
return "Please recompile contract"
}
const contractMetadataParsed = JSON.parse(contractMetadata)
const fileName = getContractFileName(
compilationResultParam.data,
contractName
)
const jsonInput = {
language: 'Solidity',
sources: compilationResultParam.source.sources,
settings: {
optimizer: {
enabled: contractMetadataParsed.settings.optimizer.enabled,
runs: contractMetadataParsed.settings.optimizer.runs
}
}
}
const data: { [key: string]: string | any } = {
apikey: apiKeyParam, // A valid API-Key is required
module: "contract", // Do not change
action: "verifysourcecode", // Do not change
codeformat: "solidity-standard-json-input",
contractaddress: contractAddress, // Contract Address starts with 0x...
sourceCode: JSON.stringify(jsonInput),
contractname: fileName + ':' + contractName,
compilerversion: `v${contractMetadataParsed.compiler.version}`, // see http://etherscan.io/solcversions for list of support versions
constructorArguements: contractArgumentsParam, // if applicable
}
const body = new FormData()
Object.keys(data).forEach((key) => body.append(key, data[key]))
client.emit("statusChanged", {
key: "loading",
type: "info",
title: "Verifying ...",
})
const response = await axios.post(etherscanApi, body)
const { message, result, status } = await response.data
if (message === "OK" && status === "1") {
resetAfter10Seconds()
const receiptStatus = await getReceiptStatus(
result,
apiKey,
etherscanApi
)
onVerifiedContract({
guid: result,
status: receiptStatus,
})
return `Contract verified correctly <br> Receipt GUID ${result}`
}
if (message === "NOTOK") {
client.emit("statusChanged", {
key: "failed",
type: "error",
title: result,
})
resetAfter10Seconds()
}
return result
} catch (error) {
console.error(error)
setResults("Something wrong happened, try again")
}
}
const resetAfter10Seconds = () => {
setTimeout(() => {
client.emit("statusChanged", { key: "none" })
setResults("")
}, 10000)
}
const verificationResult = await verify(
apiKey,
values.contractAddress,
contractArguments,
values.contractName,
compilationResult
)
setResults(verificationResult)
}
return (
<div>
<Formik
initialValues={{
contractName: "",
contractArguments: "",
contractAddress: "",
}}
validate={(values) => {
const errors = {} as any
if (!values.contractName) {
errors.contractName = "Required"
}
if (!values.contractAddress) {
errors.contractAddress = "Required"
}
if (values.contractAddress.trim() === "") {
errors.contractAddress = "Please enter a valid contract address"
}
return errors
}}
onSubmit={(values) => onVerifyContract(values)}
>
{({ errors, touched, handleSubmit, isSubmitting }) => (
<form onSubmit={handleSubmit}>
<div className="form-group">
<h6>Verify your smart contracts</h6>
<label htmlFor="contractName">Contract</label>
<Field
as="select"
className={
errors.contractName && touched.contractName
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
name="contractName"
>
<option disabled={true} value="">
Select a contract
</option>
{contracts.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</Field>
<ErrorMessage
className="invalid-feedback"
name="contractName"
component="div"
/>
</div>
<div className="form-group">
<label htmlFor="contractArguments">Constructor Arguments</label>
<Field
className={
errors.contractArguments && touched.contractArguments
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="contractArguments"
placeholder="hex encoded"
/>
<ErrorMessage
className="invalid-feedback"
name="contractArguments"
component="div"
/>
</div>
<div className="form-group">
<label htmlFor="contractAddress">Contract Address</label>
<Field
className={
errors.contractAddress && touched.contractAddress
? "form-control form-control-sm is-invalid"
: "form-control form-control-sm"
}
type="text"
name="contractAddress"
placeholder="i.e. 0x11b79afc03baf25c631dd70169bb6a3160b2706e"
/>
<ErrorMessage
className="invalid-feedback"
name="contractAddress"
component="div"
/>
</div>
<SubmitButton dataId="verify-contract" text="Verify Contract" isSubmitting={isSubmitting} />
</form>
)}
</Formik>
<div data-id="verify-result"
style={{ marginTop: "2em", fontSize: "0.8em", textAlign: "center" }}
dangerouslySetInnerHTML={{ __html: results }}
/>
{/* <div style={{ display: "block", textAlign: "center", marginTop: "1em" }}>
<Link to="/receipts">View Receipts</Link>
</div> */}
</div>
)
}

@ -0,0 +1,4 @@
export { HomeView } from "./HomeView"
export { ErrorView } from "./ErrorView"
export { ReceiptsView } from "./ReceiptsView"
export { CaptureKeyView } from "./CaptureKeyView"

@ -0,0 +1,3 @@
export const environment = {
production: true
};

@ -0,0 +1,6 @@
// This file can be replaced during build by using the `fileReplacements` array.
// When building for production, this file is replaced with `environment.prod.ts`.
export const environment = {
production: false
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Etherscan</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,7 @@
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom';
import App from './app/app';
ReactDOM.render(<StrictMode><App /></StrictMode>, document.getElementById('root'));

@ -0,0 +1,7 @@
/**
* Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
*
* See: https://github.com/zloirock/core-js#babel
*/
import 'core-js/stable';
import 'regenerator-runtime/runtime';

@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -0,0 +1,20 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
}
]
}

@ -12,5 +12,5 @@
}
],
"extends": ["../../.eslintrc"],
"ignorePatterns": ["!**/*"]
"ignorePatterns": ["!**/*", "hardhat_compilation.ts"]
}

@ -29,7 +29,13 @@ module.exports = {
javascriptEnabled: true,
acceptSslCerts: true,
'goog:chromeOptions': {
args: ['window-size=2560,1440', 'start-fullscreen', '--no-sandbox', '--headless', '--verbose']
args: ['window-size=2560,1440',
'start-fullscreen',
'--no-sandbox',
'--headless',
'--verbose',
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36",
]
}
}
},

@ -15,19 +15,41 @@ fs.readdirSync(testFolder).forEach(file => {
if (!file.includes('group')) {
const content = fs.readFileSync(testFolder + file, 'utf8')
const matches = content.match(/group\d+/g)
if (matches) {
const unique = matches.filter(onlyUnique)
unique.map((group) => {
const rewrite = source.replace('#groupname', group).replace('#file', file.replace('.ts', ''))
const extension = file.split('.')
extension.shift()
const filename = `${testFolder}${file.split('.').shift()}_${group}.${extension.join('.')}`
fs.writeFileSync(filename, rewrite)
})
}
createFlakyTestFiles(file, content)
createFiles(file, matches)
}
})
function onlyUnique (value, index, self) {
function createFiles(file, matches, flaky = false) {
if (matches) {
const unique = matches.filter(onlyUnique)
unique.map((group) => {
const rewrite = source.replace('#groupname', group).replace('#file', file.replace('.ts', ''))
const extension = file.split('.')
extension.shift()
let filename
if (!flaky) {
filename = `${testFolder}${file.split('.').shift()}_${group}.${extension.join('.')}`
} else {
filename = `${testFolder}${file.split('.').shift()}_${group}.flaky.ts`
}
fs.writeFileSync(filename, rewrite)
})
}
}
function onlyUnique(value, index, self) {
return self.indexOf(value) === index
}
function createFlakyTestFiles(file, text) {
const lines = text.split('\n')
lines.forEach((line, index) => {
// if line contains #flaky
if (line.includes('#flaky')) {
const matches = line.match(/group\d+/g)
const unique = matches.filter(onlyUnique)
createFiles(file, matches, true)
}
})
}

@ -21,7 +21,7 @@ function checkFilter (browser: NightwatchBrowser, filter: string, test: string,
const filterClass = '[data-id="terminalInputSearch"]'
browser.setValue(filterClass, filter, function () {
browser.execute(function () {
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test || ''
}, [], function (result) {
browser.clearValue(filterClass).setValue(filterClass, '', function () {
if (!result.value) {

@ -0,0 +1,41 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class ConnectToExternalHttpProvider extends EventEmitter {
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser {
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`,
(result) => {
if (result.status as any === -1) {
console.log("No connection to external provider found. Adding one.", url)
browser
.click({
locateStrategy: 'css selector',
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]',
abortOnFailure: false,
suppressNotFoundErrors: true,
timeout: 5000
})
.switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => { })
.setValue('[data-id="modalDialogCustomPromp"]', url)
.modalFooterOKClick('basic-http-provider')
.perform((done) => {
done()
this.emit('complete')
})
} else {
this.api.perform((done) => {
done()
this.emit('complete')
})
}
}
)
return this
}
}
module.exports = ConnectToExternalHttpProvider

@ -3,15 +3,15 @@ import EventEmitter from 'events'
class CurrentWorkspaceIs extends EventEmitter {
command (this: NightwatchBrowser, name: string): NightwatchBrowser {
this.api
.execute(function () {
const el = document.querySelector('select[data-id="workspacesSelect"]') as HTMLSelectElement
return el.value
}, [], (result) => {
console.log(result)
this.api.assert.equal(result.value, name)
this.emit('complete')
})
const browser = this.api
browser.getText('[data-id="workspacesSelect"]', function (result) {
browser.assert.equal(result.value, name)
})
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}

@ -1,7 +1,7 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class ExecuteScript extends EventEmitter {
class ExecuteScriptInTerminal extends EventEmitter {
command (this: NightwatchBrowser, script: string): NightwatchBrowser {
this.api
.clearEditableContent('*[data-id="terminalCliInput"]')
@ -17,4 +17,4 @@ class ExecuteScript extends EventEmitter {
}
}
module.exports = ExecuteScript
module.exports = ExecuteScriptInTerminal

@ -0,0 +1,19 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class GetBrowserLogs extends EventEmitter {
command(this: NightwatchBrowser): NightwatchBrowser {
this.api.getLog('browser', function (logs) {
if (logs && Array.isArray(logs)) {
logs.forEach(function (log) {
console.log(log)
}
)
}
}).perform(() => {
this.emit('complete')
})
return this
}
}
module.exports = GetBrowserLogs

@ -9,11 +9,10 @@ class GetEditorValue extends EventEmitter {
return elem.currentContent()
}, [], (result) => {
done()
const value = typeof result.value === 'string' ? result.value : null
callback(value)
this.emit('complete')
done()
})
})
return this

@ -10,7 +10,9 @@ class GoToVmTraceStep extends EventEmitter {
}
function goToVMtraceStep (browser: NightwatchBrowser, step: number, incr: number, done: VoidFunction) {
browser.execute(function (step) { (document.getElementById('slider') as HTMLInputElement).value = (step - 1).toString() }, [step])
browser.waitForElementVisible('*[data-id="slider"]')
.waitForElementVisible('#stepdetail')
.execute(function (step) { (document.getElementById('slider') as HTMLInputElement).value = (step - 1).toString() }, [step])
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.execute((step) => {
(document.querySelector('*[data-id="slider"]') as any).internal_onmouseup({ target: { value: step }})

@ -1,4 +1,4 @@
import { NightwatchBrowser } from 'nightwatch'
import { ELEMENT_KEY, NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
/*
@ -11,7 +11,7 @@ class JournalChildIncludes extends EventEmitter {
let occurence = 0
this.api.elements('css selector', '*[data-id="terminalJournal"]', (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
const jsonWebElementId = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
const jsonWebElementId = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
browser.elementIdText(jsonWebElementId, (jsonElement) => {
const text = jsonElement.value

@ -22,9 +22,16 @@ function openFile (browser: NightwatchBrowser, name: string, done: VoidFunction)
browser.element('css selector', '[data-id="verticalIconsKindfilePanel"] img[data-id="selected"]', (result) => {
if (result.status === 0) {
done()
} else browser.clickLaunchIcon('filePanel').perform(done)
} else browser.clickLaunchIcon('filePanel').perform(() => {
done()
})
})
} else browser.clickLaunchIcon('filePanel').perform(done)
} else {
browser.clickLaunchIcon('filePanel').perform(() => {
done()
})
}
})
})
.waitForElementVisible('li[data-id="treeViewLitreeViewItem' + name + '"', 60000)

@ -1,10 +1,10 @@
import EventEmitter from 'events'
import { NightwatchBrowser } from 'nightwatch'
class RightClick extends EventEmitter {
class RightClickCustom extends EventEmitter {
command (this: NightwatchBrowser, cssSelector: string) {
this.api.perform((done) => {
rightClick(this.api, cssSelector, () => {
rightClickCustom(this.api, cssSelector, () => {
done()
this.emit('complete')
})
@ -13,7 +13,7 @@ class RightClick extends EventEmitter {
}
}
function rightClick (browser: NightwatchBrowser, cssSelector: string, callback: VoidFunction) {
function rightClickCustom (browser: NightwatchBrowser, cssSelector: string, callback: VoidFunction) {
browser.execute(function (cssSelector: string) {
const element: any = document.querySelector(cssSelector)
const evt = element.ownerDocument.createEvent('MouseEvents')
@ -34,4 +34,4 @@ function rightClick (browser: NightwatchBrowser, cssSelector: string, callback:
})
}
module.exports = RightClick
module.exports = RightClickCustom

@ -0,0 +1,18 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class switchEnvironment extends EventEmitter {
command (this: NightwatchBrowser, provider: string): NightwatchBrowser {
this.api.useCss().waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`)
.click(`[data-id="dropdown-item-${provider}"]`)
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}
module.exports = switchEnvironment

@ -0,0 +1,20 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class switchWorkspace extends EventEmitter {
command (this: NightwatchBrowser, workspaceName: string): NightwatchBrowser {
this.api.waitForElementVisible('[data-id="workspacesSelect"]')
.click('[data-id="workspacesSelect"]')
.waitForElementVisible(`[data-id="dropdown-item-${workspaceName}"]`)
.pause(2000)
.click(`[data-id="dropdown-item-${workspaceName}"]`)
.pause(3000)
.perform((done) => {
done()
this.emit('complete')
})
return this
}
}
module.exports = switchWorkspace

@ -26,11 +26,11 @@ function testConstantFunction (browser: NightwatchBrowser, address: string, fnFu
done()
})
})
.click('.instance button[title="' + fnFullName + '"]')
.click(`#instance${address} button[title="${fnFullName}"]`)
.pause(1000)
.waitForElementPresent('#instance' + address + ' .udapp_contractActionsContainer .udapp_value')
.scrollInto('#instance' + address + ' .udapp_contractActionsContainer .udapp_value')
.assert.containsText('#instance' + address + ' .udapp_contractActionsContainer .udapp_value', expectedOutput).perform(() => {
.assert.containsText('#instance' + address + ' .udapp_contractActionsContainer', expectedOutput).perform(() => {
cb()
})
}

@ -1,4 +1,4 @@
import { NightwatchBrowser, NightwatchTestFunctionExpectedInput } from 'nightwatch'
import { ELEMENT_KEY, NightwatchBrowser, NightwatchTestFunctionExpectedInput } from 'nightwatch'
import EventEmitter from 'events'
const deepequal = require('deep-equal')
@ -22,14 +22,17 @@ class TestFunction extends EventEmitter {
})
})
.perform((done) => {
browser.waitForElementVisible(`[data-id="block_tx${txHash}"]`, 60000)
browser
.waitForElementVisible(`[data-id="block_tx${txHash}"]`, 60000)
.moveToElement(`[data-id="block_tx${txHash}"]`, 0, 0)
.pause(2000)
.click(`[data-id="block_tx${txHash}"]`)
.pause(3000)
.waitForElementVisible(`*[data-id="txLoggerTable${txHash}"]`, 60000)
// fetch and format transaction logs as key => pair object
.elements('css selector', `*[data-shared="key_${txHash}"]`, (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement) {
const jsonWebElementId: string = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
const jsonWebElementId: string = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
browser.elementIdText(jsonWebElementId, (jsonElement) => {
const key = typeof jsonElement.value === 'string' ? jsonElement.value.trim() : null
@ -40,7 +43,7 @@ class TestFunction extends EventEmitter {
})
.elements('css selector', `*[data-shared="pair_${txHash}"]`, (res) => {
Array.isArray(res.value) && res.value.forEach(function (jsonWebElement, index) {
const jsonWebElementId = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]]
const jsonWebElementId = jsonWebElement[ELEMENT_KEY] || jsonWebElement[Object.keys(jsonWebElement)[0]]
browser.elementIdText(jsonWebElementId, (jsonElement) => {
let value = jsonElement.value

@ -7,7 +7,7 @@ class ValidateValueInput extends EventEmitter {
browser.perform((done) => {
browser.clearValue(selector)
.pause(2000)
.setValue(selector, valueTosSet)
.setValue(selector, valueTosSet).pause(2000)
.execute(function (selector) {
const elem = document.querySelector(selector) as HTMLInputElement
return elem.value

@ -2,7 +2,7 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class VerifyContracts extends EventEmitter {
command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null }): NightwatchBrowser {
command (this: NightwatchBrowser, compiledContractNames: string[], opts = { wait: 1000, version: null, runs: '200' }): NightwatchBrowser {
this.api.perform((done) => {
verifyContracts(this.api, compiledContractNames, opts, () => {
done()
@ -13,13 +13,13 @@ class VerifyContracts extends EventEmitter {
}
}
function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string }, callback: VoidFunction) {
function verifyContracts (browser: NightwatchBrowser, compiledContractNames: string[], opts: { wait: number, version?: string, runs?: string }, callback: VoidFunction) {
browser
.clickLaunchIcon('solidity')
.pause(opts.wait)
.pause(5000)
.waitForElementPresent('*[data-id="compiledContracts"] option', 60000)
.perform((done) => {
.perform(async (done) => {
if (opts.version) {
browser
.click('*[data-id="compilation-details"]')
@ -36,10 +36,28 @@ function verifyContracts (browser: NightwatchBrowser, compiledContractNames: str
done()
callback()
})
} else {
compiledContractNames.forEach((name) => {
browser.waitForElementContainsText('[data-id="compiledContracts"]', name, 60000)
} if (opts.runs) {
browser
.click('*[data-id="compilation-details"]')
.waitForElementVisible('*[data-id="remixui_treeviewitem_metadata"]')
.pause(2000)
.click('*[data-id="remixui_treeviewitem_metadata"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemsettings"]')
.pause(2000)
.click('*[data-id="treeViewDivtreeViewItemsettings"]')
.waitForElementVisible('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.click('*[data-id="treeViewDivtreeViewItemoptimizer"]')
.waitForElementVisible('*[data-id="treeViewDivruns"]')
.assert.containsText('*[data-id="treeViewDivruns"]', `${opts.runs}`)
.click('[data-id="workspacesModalDialog-modal-footer-ok-react"]')
.perform(() => {
done()
callback()
})
} else {
for (const index in compiledContractNames) {
await browser.waitForElementContainsText('[data-id="compiledContracts"]', compiledContractNames[index], 60000)
}
done()
callback()
}

@ -0,0 +1,173 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const testContract = {
name: 'contracts/test.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "contracts/base.sol";
import "contracts/import1.sol";
contract test is base {
string public publicstring;
string private privatestring;
string internal internalstring;
struct TestBookDefinition {
string title;
string author;
uint book_id;
}
TestBookDefinition public mybook;
enum MyEnum{ SMALL, MEDIUM, LARGE }
event MyEvent(uint abc);
importcontract importedcontract;
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
constructor(){
}
function testing() public view {
}
function myprivatefunction() private {
}
function myinternalfunction() internal {
}
function myexternalfunction() external {
}
}`}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const baseContract = {
name: 'contracts/base.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "contracts/baseofbase.sol";
contract base is baseofbase {
event BaseEvent(address indexed _from, uint _value);
enum BaseEnum{ SMALL, MEDIUM, LARGE }
struct Book {
string title;
string author;
uint book_id;
}
Book public book;
}`}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const baseOfBaseContract = {
name: 'contracts/baseofbase.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract baseofbase {
struct BaseBook {
string title;
string author;
uint book_id;
}
BaseBook public basebook;
string private basestring;
string internal internalbasestring;
function privatebase() private {
}
function internalbasefunction() internal {
}
function publicbasefunction() public {
}
function externalbasefunction() external {
}
}`}
const import1Contract = {
name: 'contracts/import1.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "contracts/importbase.sol";
import "contracts/secondimport.sol";
contract importcontract is importbase {
struct ImportedBook {
string title;
string author;
uint book_id;
}
ImportedBook public importedbook;
string private importprivatestring;
string internal internalimportstring;
string public importpublicstring;
function privateimport() private {
}
function internalimport() internal {
}
function publicimport() public {
}
function externalimport() external {
}
}`}
const importbase = {
name: 'contracts/importbase.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract importbase {
string public importbasestring;
}
`}
const secondimport = {
name: 'contracts/secondimport.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract secondimport {
string public secondimportstring;
}
`}
export default {
testContract,
baseContract,
baseOfBaseContract,
import1Contract,
importbase,
secondimport
}

File diff suppressed because one or more lines are too long

@ -2,13 +2,28 @@ import { NightwatchBrowser } from 'nightwatch'
require('dotenv').config()
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true): void {
type LoadPlugin = {
name: string
url: string
}
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, loadPlugin?: LoadPlugin): void {
browser
.url(url || 'http://127.0.0.1:8080')
.pause(6000)
.switchBrowserTab(0)
.waitForElementVisible('[id="remixTourSkipbtn"]')
.click('[id="remixTourSkipbtn"]')
.perform((done) => {
if (!loadPlugin) return done()
browser.execute(function (loadPlugin) { // override a plugin url for testing purpose
localStorage.setItem('test-plugin-name', loadPlugin.name)
localStorage.setItem('test-plugin-url', loadPlugin.url)
}, [loadPlugin])
.refresh()
.pause(6000)
.perform(done())
})
.maximizeWindow()
.fullscreenWindow(() => {
if (preloadPlugins) {

@ -3,7 +3,7 @@
# Bash Menu Script Example
PS3='Select a browser: '
BROWSERS=( "chrome" "firefox" "exit" )
BROWSERS=( "chrome" "chrome headless" "firefox" "exit" )
select opt in "${BROWSERS[@]}"
do
case $opt in
@ -12,6 +12,11 @@ do
BROWSER="chromeDesktop"
break
;;
"chrome headless")
echo "Chrome headless selected"
BROWSER="chrome"
break
;;
"firefox")
echo "Firefox selected"
BROWSER="firefoxDesktop"
@ -24,7 +29,7 @@ do
*) echo "invalid option $REPLY";;
esac
done
npm run build:e2e
yarn run build:e2e
PS3='Select a test or command: '
TESTFILES=( $(grep -IRiL "\'@disabled\': \?true" "dist/apps/remix-ide-e2e/src/tests" | grep "\.spec\|\.test\|plugin_api" | sort ) )
@ -42,6 +47,6 @@ do
done
else
# run the selected test
npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $opt --env=$BROWSER
yarn run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js $opt --env=$BROWSER
fi
done

@ -83,21 +83,15 @@ module.exports = {
browser
.openFile('Untitled.sol')
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]')
.waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]')
.switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(function () {
const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any
const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any
modal.click()
})
.pause(5000)
.execute(function () {
const env: any = document.getElementById('selectExEnvOptions')
return env.value
}, [], function (result) {
browser.assert.ok(result.value === 'web3', 'Web3 Provider not selected')
})
.waitForElementContainsText('#selectExEnvOptions button', 'External Http Provider')
.clickLaunchIcon('solidity')
.clickLaunchIcon('udapp')
.pause(2000)
@ -122,6 +116,41 @@ module.exports = {
})
// Test in Udapp UI , treeViewDiv0 shows returned value on method click
.assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000')
},
'Compile Ballot using config file': function (browser: NightwatchBrowser) {
browser
.addFile('cf.json', {content: configFile})
.clickLaunchIcon('solidity')
.waitForElementVisible('*[data-id="scConfigExpander"]')
.click('*[data-id="scConfigExpander"]')
.waitForElementVisible('*[data-id="scFileConfiguration"]', 10000)
.click('*[data-id="scFileConfiguration"]')
.waitForElementVisible('*[data-id="scConfigChangeFilePath"]', 10000)
.click('*[data-id="scConfigChangeFilePath"]')
.waitForElementVisible('*[data-id="scConfigFilePathInput"]', 10000)
.clearValue('*[data-id="scConfigFilePathInput"]')
.setValue('*[data-id="scConfigFilePathInput"]', 'cf.json')
.sendKeys('*[data-id$="scConfigFilePathInput"]', browser.Keys.ENTER)
.openFile('Untitled.sol')
.verifyContracts(['Ballot'], {wait: 2000, runs: '300'})
},
'Compile and deploy sample yul file': function (browser: NightwatchBrowser) {
browser
.addFile('sample.yul', {content: yulSample})
.clickLaunchIcon('solidity')
.waitForElementVisible('*[data-id="scConfigExpander"]')
.click('*[data-id="scManualConfiguration"]')
.waitForElementVisible('select[id="compilierLanguageSelector"]', 10000)
.click('select[id="compilierLanguageSelector"]')
.click('select[id="compilierLanguageSelector"] option[value=Yul]')
.waitForElementContainsText('[data-id="compiledContracts"]', 'Contract', 60000)
.clickLaunchIcon('udapp')
.click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)
.journalLastChildIncludes('Contract.(constructor)')
.journalLastChildIncludes('data: 0x602...0565b')
.end()
}
}
@ -190,6 +219,7 @@ const stateCheck = {
immutable: false
}
}
const ballotABI = `[
{
"inputs": [
@ -356,3 +386,37 @@ const ballotABI = `[
"type": "function"
}
]`
const configFile = `
{
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": true,
"runs": 300
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["abi", "metadata", "devdoc", "userdoc", "storageLayout", "evm.legacyAssembly", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "evm.gasEstimates", "evm.assembly"]
}
},
"evmVersion": "byzantium"
}
}
`
const yulSample = `
object "Contract" {
code {
function power(base, exponent) -> result
{
result := 1
for { let i := 0 } lt(i, exponent) { i := add(i, 1) }
{
result := mul(result, base)
}
}
}
}
`

@ -78,10 +78,10 @@ module.exports = {
browser
.openFile('Untitled.sol')
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]')
.waitForElementPresent('[data-id="envNotification-modal-footer-ok-react"]')
.switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(function () {
const modal = document.querySelector('[data-id="envNotification-modal-footer-ok-react"]') as any
const modal = document.querySelector('[data-id="basic-http-provider-modal-footer-ok-react"]') as any
modal.click()
})

@ -21,7 +21,7 @@ module.exports = {
'Should compile using "compileWithParamaters" API #group1': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompile.js', { content: jsCompile })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"languageversion":"0.6.8+commit.0bbfe453"', 60000)
.click('*[data-id="terminalClearConsole"]')
},
@ -29,7 +29,7 @@ module.exports = {
'Should compile using "compileWithParamaters" API with optimization On #group2': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompileWithOptimization.js', { content: jsCompileWithOptimization })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '\\"optimizer\\":{\\"enabled\\":true,\\"runs\\":300}', 60000)
.click('*[data-id="terminalClearConsole"]')
},
@ -37,7 +37,7 @@ module.exports = {
'Should compile using "compileWithParamaters" API with optimization off check default runs #group3': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsCompileWithOptimizationDefault.js', { content: jsCompileWithOptimizationDefault })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', '\\"optimizer\\":{\\"enabled\\":false,\\"runs\\":200}', 60000)
.click('*[data-id="terminalClearConsole"]')
},
@ -45,8 +45,8 @@ module.exports = {
'Should update the compiler configuration with "setCompilerConfig" API #group4': function (browser: NightwatchBrowser) {
browser
.addFile('test_updateConfiguration.js', { content: updateConfiguration })
.executeScript('remix.exeCurrent()')
.pause(5000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(15000)
.addFile('test_updateConfiguration.sol', { content: simpleContract })
.verifyContracts(['StorageTestUpdateConfiguration'], { wait: 5000, version: '0.6.8+commit.0bbfe453' })
},
@ -168,3 +168,4 @@ contract DoesNotCompile {
function fStackLimit(uint u1, uint u2, uint u3, uint u4, uint u5, uint u6, uint u7, uint u8, uint u9, uint u10, uint u11, uint u12) public {
}
}`

@ -192,7 +192,7 @@ module.exports = {
'Should call the debugger api: getTrace #group4': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsGetTrace.js', { content: jsGetTrace })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(3000)
.waitForElementContainsText('*[data-id="terminalJournal"]', '{"gas":"0x575f","return":"0x0000000000000000000000000000000000000000000000000000000000000000","structLogs":', 60000)
},
@ -200,11 +200,12 @@ module.exports = {
'Should call the debugger api: debug #group4': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsDebug.js', { content: jsDebug })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(3000)
.clickLaunchIcon('debugger')
.waitForElementVisible('*[data-id="slider"]')
.goToVMTraceStep(154)
.scrollInto('*[data-id="stepdetail"]')
.waitForElementContainsText('*[data-id="stepdetail"]', 'vm trace step:\n154', 60000)
},
@ -214,7 +215,7 @@ module.exports = {
.setSolidityCompilerVersion('soljson-v0.8.7+commit.e28d00a7.js')
.addFile('useDebugNodes.sol', sources[5]['useDebugNodes.sol']) // compile contract
.clickLaunchIcon('udapp')
.click('*[data-id="settingsWeb3Mode"]') // select web3 provider with debug nodes URL
.switchEnvironment('External Http Provider') // select web3 provider with debug nodes URL
.clearValue('*[data-id="modalDialogCustomPromptText"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', 'https://remix-rinkeby.ethdevops.io')
.modalFooterOKClick()
@ -240,6 +241,7 @@ module.exports = {
.clickInstance(0)
.clickFunction('callA - transact (not payable)')
.debugTransaction(1)
.pause(4000)
.goToVMTraceStep(79)
.waitForElementVisible('*[data-id="debugGoToRevert"]', 60000)
.click('*[data-id="debugGoToRevert"]')

@ -17,7 +17,7 @@ module.exports = {
'Loads Side Panel': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORER')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemscripts"]')
@ -40,12 +40,12 @@ module.exports = {
'Toggles Side Panel': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORER')
.clickLaunchIcon('filePanel')
.assert.hidden('div[data-id="remixIdeSidePanel"]')
.assert.not.visible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.assert.visible('div[data-id="remixIdeSidePanel"]')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORER')
},
'Toggles Terminal': function (browser: NightwatchBrowser) {

@ -89,7 +89,7 @@ module.exports = {
.addFile('sourcehighlight.js', sourcehighlightScript)
.addFile('removeAllSourcehighlightScript.js', removeAllSourcehighlightScript)
.openFile('sourcehighlight.js')
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.scrollToLine(32)
.waitForElementPresent('.highlightLine33', 60000)
.checkElementStyle('.highlightLine33', 'background-color', 'rgb(52, 152, 219)')
@ -105,7 +105,7 @@ module.exports = {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.pause(2000)
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts"]')
.click('li[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
@ -119,7 +119,7 @@ module.exports = {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.pause(2000)
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementVisible('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.click('li[data-id="treeViewLitreeViewItemcontracts/3_Ballot.sol"]')
.pause(2000)
@ -128,119 +128,6 @@ module.exports = {
.waitForElementNotPresent('.highlightLine51', 60000)
},
'Should display the context view #group2': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError)
.pause(2000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(17, 16)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'FunctionDefinition')
.waitForElementContainsText('.contextview .name', 'store')
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(18, 12)
}, [], () => {})
.waitForElementContainsText('.contextview .type', 'uint256')
.waitForElementContainsText('.contextview .name', 'number')
.click('.contextview [data-action="previous"]') // declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
.click('.contextview [data-action="next"]') // back to the initial state
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '323')
})
.click('.contextview [data-action="next"]') // next reference
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '489')
})
.click('.contextview [data-action="gotoref"]') // back to the declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
},
'Should display the context view, loop over "Owner" by switching file #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.click('[for="autoCompile"]') // disable auto compile
.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]') // compile
.pause(6000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(14, 6)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'ContractDefinition')
.waitForElementContainsText('.contextview .name', 'Owner')
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '1061')
})
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '153')
})
.currentSelectedFileIs('2_Owner.sol') // make sure the current file has been properly changed
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '211')
})
.click('.contextview [data-action="next"]')
.currentSelectedFileIs('3_Ballot.sol')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '1061')
})
.click('.contextview [data-action="gotoref"]') // go to the declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '153')
})
.end()
}
}
const aceThemes = {
@ -347,148 +234,4 @@ contract Storage {
}
}`
const BallotWithARefToOwner = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "./2_Owner.sol";
/**
* @title Ballot
* @dev Implements voting process along with vote delegation
*/
contract Ballot {
Owner c;
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
struct Proposal {
// If you can limit the length to a certain number of bytes,
// always use one of bytes1 to bytes32 because they are much cheaper
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
/**
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) {
c = new Owner();
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
// 'Proposal({...})' creates a temporary
// Proposal object and 'proposals.push(...)'
// appends it to the end of 'proposals'.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
/**
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'.
* @param voter address of voter
*/
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/**
* @dev Delegate your vote to the voter 'to'.
* @param to address to which vote is delegated
*/
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/**
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'.
* @param proposal index of proposal in the proposals array
*/
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If 'proposal' is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/**
* @dev Computes the winning proposal taking all previous votes into account.
* @return winningProposal_ index of winning proposal in the proposals array
*/
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
/**
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then
* @return winnerName_ the name of the winner
*/
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
`

@ -0,0 +1,526 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import examples from '../examples/editor-test-contracts'
const autoCompleteLineElement = (name: string) => {
return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]`
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should add test and base files #group1': function (browser: NightwatchBrowser) {
browser.addFile(examples.testContract.name, examples.testContract)
.addFile(examples.baseContract.name, examples.baseContract)
.addFile(examples.import1Contract.name, examples.import1Contract)
.addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract)
.addFile(examples.secondimport.name, examples.secondimport)
.addFile(examples.importbase.name, examples.importbase)
.openFile(examples.testContract.name)
},
'Should put cursor in the () of the function #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(36)
const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]"
browser.waitForElementVisible('#editorView')
.useXpath()
.click(path).pause(1000)
},
'Should complete variable declaration types in a function definition #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('uint25')
})
.waitForElementPresent(autoCompleteLineElement('uint256'))
.click(autoCompleteLineElement('uint256'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' abc')
.sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow
.sendKeys(', testb')
})
.waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"'))
.click(autoCompleteLineElement('"TestBookDefinition"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' memo')
})
.waitForElementPresent(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('memory'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' btextbook')
.sendKeys(this.Keys.ENTER)
.sendKeys(', BaseB')
})
.waitForElementPresent(autoCompleteLineElement('"BaseBook"'))
.click(autoCompleteLineElement('"BaseBook"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' stor')
})
.waitForElementPresent(autoCompleteLineElement('storage'))
.click(autoCompleteLineElement('storage'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' localbbook')
}).pause(3000)
},
'Should put cursor at the end of function #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]"
browser
.useXpath()
.click(path).pause(1000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
// right arrow key
sendKeys(this.Keys.ARROW_RIGHT).
sendKeys(this.Keys.ARROW_RIGHT)
})
},
'Should autcomplete address types #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('addre')
})
.waitForElementPresent(autoCompleteLineElement('address'))
.click(autoCompleteLineElement('address'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' someaddress;')
.sendKeys(this.Keys.ENTER)
}).pause(2000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('someaddress.')
})
.waitForElementVisible(autoCompleteLineElement('balance'))
.waitForElementVisible(autoCompleteLineElement('send'))
.waitForElementVisible(autoCompleteLineElement('transfer'))
.waitForElementVisible(autoCompleteLineElement('code'))
.click(autoCompleteLineElement('balance'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should autcomplete array types #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('uin')
})
.waitForElementPresent(autoCompleteLineElement('uint'))
.click(autoCompleteLineElement('uint'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('[] mem')
})
.waitForElementVisible(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('memory'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' somearray;')
}
).pause(2000)
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
.sendKeys('somearray.')
})
.waitForElementVisible(autoCompleteLineElement('push'))
.waitForElementVisible(autoCompleteLineElement('pop'))
.waitForElementVisible(autoCompleteLineElement('length'))
.click(autoCompleteLineElement('length'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should see and autocomplete second import because it was imported by the first import #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('secondi')
})
.waitForElementPresent(autoCompleteLineElement('secondimport'))
.click(autoCompleteLineElement('secondimport'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' sec;')
.sendKeys(this.Keys.ENTER)
}).pause(3000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('sec.')
})
.waitForElementVisible(autoCompleteLineElement('secondimportstring'))
.click(autoCompleteLineElement('secondimportstring'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should see and autocomplete imported local class #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('import')
})
.waitForElementPresent(autoCompleteLineElement('importedcontract'))
.click(autoCompleteLineElement('importedcontract'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('externalimport'))
.waitForElementVisible(autoCompleteLineElement('importbasestring'))
.waitForElementVisible(autoCompleteLineElement('importedbook'))
.waitForElementVisible(autoCompleteLineElement('importpublicstring'))
.waitForElementVisible(autoCompleteLineElement('publicimport'))
// no private
.waitForElementNotPresent(autoCompleteLineElement('importprivatestring'))
.waitForElementNotPresent(autoCompleteLineElement('privateimport'))
// no internal
.waitForElementNotPresent(autoCompleteLineElement('importinternalstring'))
.waitForElementNotPresent(autoCompleteLineElement('internalimport'))
.click(autoCompleteLineElement('importbasestring'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should autocomplete derived and local event when not using this. #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('emit base')
})
.waitForElementVisible(autoCompleteLineElement('BaseEvent'))
.click(autoCompleteLineElement('BaseEvent'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys('msg.sender')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium
.sendKeys('3232')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.ENTER)
})
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('emit MyEv')
})
.waitForElementVisible(autoCompleteLineElement('MyEvent'))
.click(autoCompleteLineElement('MyEvent'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys('3232')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.ENTER)
})
},
'Should type and get msg options #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('msg.')
})
.waitForElementVisible(autoCompleteLineElement('sender'))
.waitForElementVisible(autoCompleteLineElement('data'))
.waitForElementVisible(autoCompleteLineElement('value'))
.waitForElementVisible(autoCompleteLineElement('gas'))
.waitForElementVisible(autoCompleteLineElement('sig'))
.click(autoCompleteLineElement('sender'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('balance'))
.waitForElementVisible(autoCompleteLineElement('code'))
.waitForElementVisible(autoCompleteLineElement('codehash'))
.waitForElementVisible(autoCompleteLineElement('send'))
.waitForElementVisible(autoCompleteLineElement('transfer'))
.click(autoCompleteLineElement('balance'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER)
})
},
'Should bo and get book #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('bo')
})
.waitForElementVisible(autoCompleteLineElement('book'))
.click(autoCompleteLineElement('book'))
},
'Should autcomplete derived struct #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should bo and get basebook #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('base')
})
.waitForElementVisible(autoCompleteLineElement('basebook'))
.click(autoCompleteLineElement('basebook'))
},
'Should autcomplete derived struct from base class #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should block scoped localbbook #group1': function (browser: NightwatchBrowser) {
browser.pause(4000).
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('localb')
})
.waitForElementVisible(autoCompleteLineElement('localbbook'))
.click(autoCompleteLineElement('localbbook'))
},
'Should autcomplete derived struct from block localbbook #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should block scoped btextbook #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('btext')
})
.waitForElementVisible(autoCompleteLineElement('btextbook'))
.click(autoCompleteLineElement('btextbook'))
},
'Should autcomplete derived struct from block btextbook #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should find private and internal local functions #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('my')
})
.waitForElementVisible(autoCompleteLineElement('myprivatefunction'))
.waitForElementVisible(autoCompleteLineElement('myinternalfunction'))
.waitForElementVisible(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('myinternalfunction'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER)
})
},
'Should find internal functions and var from base and owner #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('intern')
})
.waitForElementVisible(autoCompleteLineElement('internalbasefunction'))
.waitForElementVisible(autoCompleteLineElement('internalstring'))
.waitForElementVisible(autoCompleteLineElement('internalbasestring'))
// keyword internal
.waitForElementVisible(autoCompleteLineElement('internal keyword'))
.click(autoCompleteLineElement('internalbasefunction'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should not find external functions without this. #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('extern')
})
.waitForElementNotPresent(autoCompleteLineElement('externalbasefunction'))
.waitForElementNotPresent(autoCompleteLineElement('myexternalfunction'))
// keyword internal
.waitForElementVisible(autoCompleteLineElement('external keyword'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
})
},
'Should find external functions using this. #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('this.')
})
.waitForElementVisible(autoCompleteLineElement('externalbasefunction'))
.waitForElementVisible(autoCompleteLineElement('myexternalfunction'))
},
'Should find public functions and vars using this. but not private & other types of nodes #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible(autoCompleteLineElement('"publicbasefunction"'))
.waitForElementVisible(autoCompleteLineElement('"publicstring"'))
.waitForElementVisible(autoCompleteLineElement('"basebook"'))
.waitForElementVisible(autoCompleteLineElement('"mybook"'))
.waitForElementVisible(autoCompleteLineElement('"testing"'))
// but no private functions or vars or other types of nodes
.waitForElementNotPresent(autoCompleteLineElement('"private"'))
.waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"'))
.waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"'))
.waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"'))
.click(autoCompleteLineElement('"publicbasefunction"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should autocomplete local and derived ENUMS #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('BaseEnum.')
})
.waitForElementVisible(autoCompleteLineElement('SMALL'))
.waitForElementVisible(autoCompleteLineElement('MEDIUM'))
.waitForElementVisible(autoCompleteLineElement('LARGE'))
.click(autoCompleteLineElement('SMALL'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
.sendKeys('MyEnum.')
})
.waitForElementVisible(autoCompleteLineElement('SMALL'))
.waitForElementVisible(autoCompleteLineElement('MEDIUM'))
.waitForElementVisible(autoCompleteLineElement('LARGE'))
.click(autoCompleteLineElement('SMALL'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
}
}

@ -0,0 +1,244 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const checkEditorHoverContent = (browser: NightwatchBrowser, path: string, expectedContent: string, offsetLeft: number = 0) => {
browser.useXpath()
.useXpath()
.moveToElement('//body', 0, 0) // always move away from the hover before the next test in case we hover in the same line on a different element
.waitForElementVisible(path)
.moveToElement(path, offsetLeft, 0)
.useCss()
.waitForElementContainsText('.monaco-hover-content', expectedContent).pause(1000)
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(4000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[contains(text(),'BallotHoverTest')]"
checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest')
checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0')
checkEditorHoverContent(browser, path, '@title Ballot')
},
'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'chairperson') and contains(.,'address') and contains(.,'public')]//span//span[contains(.,'chairperson')]"
const expectedContent = 'address public chairperson'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over constructor in editor #group1': function (browser: NightwatchBrowser) {
const path: string = "//*[@class='view-line' and contains(.,'constructor') and contains(.,'bytes32') and contains(.,'memory')]//span//span[contains(.,'constructor')]"
const expectedContent = 'Estimated creation cost: infinite gas Estimated code deposit cost:'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over function in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(58)
const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]"
let expectedContent = 'Estimated execution cost'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'"
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over var components in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(37)
let path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'voters')]"
let expectedContent = 'mapping(address => struct BallotHoverTest.Voter) public voters'
checkEditorHoverContent(browser, path, expectedContent, 15)
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'chairperson')]"
expectedContent = 'address public chairperson'
checkEditorHoverContent(browser, path, expectedContent, 3)
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'weight')]"
expectedContent = 'uint256 internal weight'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over new contract creation in editor #group1': function (browser: NightwatchBrowser) {
let path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'cowner')]"
let expectedContent = 'contract Owner internal cowner'
checkEditorHoverContent(browser, path, expectedContent, 10)
path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'Owner')]"
expectedContent = 'contract Owner is Owner'
checkEditorHoverContent(browser, path, expectedContent, 10)
},
'Should show hover over external class member in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
let expectedContent = 'function getOwner () external view returns (address internal )'
checkEditorHoverContent(browser, path, expectedContent, 0)
expectedContent = 'contracts/2_Owner.sol'
checkEditorHoverContent(browser, path, expectedContent, 0)
expectedContent = '@dev Return owner address'
checkEditorHoverContent(browser, path, expectedContent, 0)
},
'Should show hover over struct definition in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(5)
const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]"
const expectedContent = 'StructDefinition'
checkEditorHoverContent(browser, path, expectedContent)
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "./2_Owner.sol";
/**
* @title Ballot
* @dev Implements voting process along with vote delegation
*/
contract BallotHoverTest {
Owner cowner;
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
struct Proposal {
// If you can limit the length to a certain number of bytes,
// always use one of bytes1 to bytes32 because they are much cheaper
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
/**
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) {
cowner = new Owner();
cowner.getOwner();
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
// 'Proposal({...})' creates a temporary
// Proposal object and 'proposals.push(...)'
// appends it to the end of 'proposals'.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
/**
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'.
* @param voter address of voter
*/
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/**
* @dev Delegate your vote to the voter 'to'.
* @param to address to which vote is delegated
*/
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/**
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'.
* @param proposal index of proposal in the proposals array
*/
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If 'proposal' is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/**
* @dev Computes the winning proposal taking all previous votes into account.
* @return winningProposal_ index of winning proposal in the proposals array
*/
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
/**
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then
* @return winnerName_ the name of the winner
*/
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
`

@ -0,0 +1,203 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const openReferences = (browser: NightwatchBrowser, path: string) => {
(browser as any).useXpath()
.useXpath()
.waitForElementVisible(path)
.click(path)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
keyDown(this.Keys.SHIFT).
sendKeys(this.Keys.F12)
})
}
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(10000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show local references': function (browser: NightwatchBrowser) {
browser.scrollToLine(48)
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]"
openReferences(browser, path)
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]")
.keys(browser.Keys.ESCAPE)
},
'Should show references of getOwner': function (browser: NightwatchBrowser) {
browser.scrollToLine(39)
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
openReferences(browser, path)
browser.useXpath()
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]")
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]")
.keys(browser.Keys.ESCAPE)
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "./2_Owner.sol";
/**
* @title Ballot
* @dev Implements voting process along with vote delegation
*/
contract BallotHoverTest {
Owner cowner;
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
struct Proposal {
// If you can limit the length to a certain number of bytes,
// always use one of bytes1 to bytes32 because they are much cheaper
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
/**
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) {
cowner = new Owner();
cowner.getOwner();
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
// 'Proposal({...})' creates a temporary
// Proposal object and 'proposals.push(...)'
// appends it to the end of 'proposals'.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
/**
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'.
* @param voter address of voter
*/
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/**
* @dev Delegate your vote to the voter 'to'.
* @param to address to which vote is delegated
*/
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/**
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'.
* @param proposal index of proposal in the proposals array
*/
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If 'proposal' is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/**
* @dev Computes the winning proposal taking all previous votes into account.
* @return winningProposal_ index of winning proposal in the proposals array
*/
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
/**
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then
* @return winnerName_ the name of the winner
*/
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
`

@ -0,0 +1,91 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should add error marker': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.addFile('scripts/adderror.ts', {content: addErrorMarker})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementVisible("//*[@class='cdr squiggly-error']")
.waitForElementVisible("//*[@class='cdr squiggly-warning']")
},
'Should clear error marker': function (browser: NightwatchBrowser) {
browser
.useCss()
.addFile('scripts/clear.ts', {content: clearMarkers})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementNotPresent("//*[@class='cdr squiggly-error']")
.waitForElementNotPresent("//*[@class='cdr squiggly-warning']")
}
}
const clearMarkers =`
(async () => {
await remix.call('editor', 'clearErrorMarkers' as any, ['contracts/1_Storage.sol'])
})()`
const addErrorMarker = `
(async () => {
let errors = [
{
position: {
start: {
line: 10,
column: 1,
},
end: {
line: 10,
column: 10
}
},
message: 'testing',
severity: 'error',
file: 'contracts/1_Storage.sol'
},
{
position: {
start: {
line: 18,
column: 1,
},
end: {
line: 18,
column: 10
}
},
message: 'testing2',
severity: 'warning',
file: 'contracts/1_Storage.sol'
},
]
await remix.call('editor', 'addErrorMarker' as any, errors)
})()`

@ -0,0 +1,76 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should add line texts': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.addFile('scripts/addlinetext.ts', {content: addLineText})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementVisible("//*[@class='view-line' and contains(.,'contract')]//span//span[contains(.,'mylinetext1')]")
.waitForElementVisible("//*[@class='view-line' and contains(.,'function')]//span//span[contains(.,'mylinetext2')]")
}
}
const addLineText = `
(async () => {
await remix.call('editor', 'discardLineTexts' as any)
let linetext = {
content: 'mylinetext1',
position: {
start: {
line: 9,
column: 1,
}
},
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
hoverMessage: [{
value: 'hovering1',
},
],
}
await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol')
linetext = {
content: 'mylinetext2',
position: {
start: {
line: 17,
column: 1,
}
},
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
hoverMessage: [{
value: 'hovering2',
},
],
}
await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol')
})()`

@ -0,0 +1,42 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const sources = []
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Deploy SampleERC721 whose bytecode is very similar to ERC721': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('filePanel')
.click('*[data-id="workspaceCreate"]')
// create contract
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc721]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/MyToken.sol"]')
.openFile('contracts/MyToken.sol')
.verifyContracts(['MyToken'])
// deploy contract
.clickLaunchIcon('udapp')
.selectContract('MyToken')
.createContract('')
.testFunction('last',
{
status: 'true Transaction mined and execution succeed',
'decoded input': {}
}).end()
}
}

@ -0,0 +1,107 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
declare global {
interface Window { testplugin: { name: string, url: string }; }
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, null, true, { name: 'etherscan', url: 'http://127.0.0.1:5003'})
},
'Should load etherscan plugin #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('pluginManager')
.scrollAndClick('[data-id="pluginManagerComponentActivateButtonetherscan"]')
.clickLaunchIcon('etherscan')
.pause(5000)
// @ts-ignore
.frame(0)
.waitForElementVisible('input[name="apiKey"]')
.setValue('input[name="apiKey"]', '2HKUX5ZVASZIKWJM8MIQVCRUVZ6JAWT531')
.click('[data-id="save-api-key"]')
},
'Should verify a contract (contract is already verified) #group1': function (browser: NightwatchBrowser) {
browser
.frameParent()
.clickLaunchIcon('udapp') // switch to Goerli
.switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => {})
.setValue('[data-id="modalDialogCustomPromp"]', 'https://remix-goerli.ethdevops.io')
.modalFooterOKClick('basic-http-provider')
.clickLaunchIcon('solidity') // compile
.testContracts('Owner_1.sol', { content: verifiedContract }, ['Owner'])
.clickLaunchIcon('etherscan') // start etherscan verification
// @ts-ignore
.frame(0)
.click('[data-id="home"]')
.setValue('select[name="contractName"]', 'Owner')
.setValue('*[name="contractAddress"]', '0x9981c9d00103da481c3c65b22a79582a3e3ff50b')
.click('[data-id="verify-contract"]')
.waitForElementVisible('[data-id="verify-result"]')
.waitForElementContainsText('[data-id="verify-result"]', 'Contract source code already verified')
}
}
const verifiedContract = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Owner
* @dev Set & change owner
*/
contract Owner {
address private owner;
// event for EVM logging
event OwnerSet(address indexed oldOwner, address indexed newOwner);
// modifier to check if caller is owner
modifier isOwner() {
// If the first argument of 'require' evaluates to 'false', execution terminates and all
// changes to the state and to Ether balances are reverted.
// This used to consume all gas in old EVM versions, but not anymore.
// It is often a good idea to use 'require' to check if functions are called correctly.
// As a second argument, you can also provide an explanation about what went wrong.
require(msg.sender == owner, "Caller is not owner");
_;
}
function getInt() public returns (uint) {
return 123498;
}
/**
* @dev Set contract deployer as owner
*/
constructor() {
owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
emit OwnerSet(address(0), owner);
}
/**
* @dev Change owner
* @param newOwner address of new owner
*/
function changeOwner(address newOwner) public isOwner {
emit OwnerSet(owner, newOwner);
owner = newOwner;
}
/**
* @dev Return owner address
* @return address of owner
*/
function getOwner() external view returns (address) {
return owner;
}
}`

@ -10,15 +10,15 @@ const testData = {
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Should create a new file `5_New_contract.sol` in file explorer': function (browser: NightwatchBrowser) {
'Should create a new file `5_New_contract.sol` in file explorer #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORER')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
.click('*[data-id="fileExplorerNewFilecreateNewFile"]')
.pause(1000)
@ -28,7 +28,7 @@ module.exports = {
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]', 7000)
},
'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser: NightwatchBrowser) {
'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]')
.click('*[data-id="treeViewLitreeViewItem5_New_contract.sol"]')
@ -36,14 +36,14 @@ module.exports = {
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_Renamed_Contract.sol"]')
},
'Should delete file `5_Renamed_Contract.sol` from file explorer': function (browser: NightwatchBrowser) {
'Should delete file `5_Renamed_Contract.sol` from file explorer #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItem5_Renamed_Contract.sol"]')
.removeFile('5_Renamed_Contract.sol', 'default_workspace')
.waitForElementNotPresent('*[data-id="treeViewLitreeViewItem5_Renamed_Contract.sol"')
},
'Should create a new folder': function (browser: NightwatchBrowser) {
'Should create a new folder #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItemREADME.txt"]')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"]') // focus on root directory
@ -55,7 +55,7 @@ module.exports = {
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
},
'Should rename Browser_Tests folder to Browser_E2E_Tests': function (browser: NightwatchBrowser) {
'Should rename Browser_Tests folder to Browser_E2E_Tests #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
.click('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
@ -63,10 +63,10 @@ module.exports = {
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]')
},
'Should delete Browser_E2E_Tests folder': function (browser: NightwatchBrowser) {
'Should delete Browser_E2E_Tests folder #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_E2E_Tests"]')
.rightClick('[data-path="Browser_E2E_Tests"]')
.rightClickCustom('[data-path="Browser_E2E_Tests"]')
.click('*[id="menuitemdelete"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok', 60000)
.pause(2000)
@ -99,12 +99,16 @@ module.exports = {
})
},
'Should open local filesystem explorer': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
'Should open local filesystem explorer #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('*[data-id="filePanelFileExplorerTree"]')
.click('[data-id="remixUIWorkspaceExplorer"]')
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3)
.waitForElementPresent('*[data-id="fileExplorerFileUpload"]')
.uploadFile('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.uploadFile('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.uploadFile('*[data-id="fileExplorerFileUpload"]', testData.testFile3)
.waitForElementVisible('[data-id="treeViewLitreeViewItemeditor.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemfileExplorer.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemgeneralSettings.test.js"]')

@ -11,7 +11,7 @@ module.exports = {
'Should execute `file` api from file manager external api #group1': function (browser: NightwatchBrowser) {
browser
.addFile('file.js', { content: executeFile })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(1000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'file.js', 60000)
},
@ -19,7 +19,7 @@ module.exports = {
'Should execute `exists` api from file manager external api #group1': function (browser: NightwatchBrowser) {
browser
.addFile('exists.js', { content: executeExists })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'exists.js true', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'non-exists.js false', 60000)
},
@ -27,14 +27,14 @@ module.exports = {
'Should execute `open` api from file manager external api #group1': function (browser: NightwatchBrowser) {
browser
.addFile('open.js', { content: executeOpen })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'contracts/3_Ballot.sol', 60000)
},
'Should execute `writeFile` api from file manager external api #group1': function (browser: NightwatchBrowser) {
browser
.addFile('writeFile.js', { content: executeWriteFile })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(2000)
.openFile('new_contract.sol')
.getEditorValue((content) => {
@ -45,23 +45,23 @@ module.exports = {
'Should execute `readFile` api from file manager external api #group2': function (browser: NightwatchBrowser) {
browser
.addFile('writeFile.js', { content: executeWriteFile })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.addFile('readFile.js', { content: executeReadFile })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'pragma solidity ^0.6.0', 60000)
},
'Should execute `copyFile` api from file manager external api #group2': function (browser: NightwatchBrowser) {
browser
.addFile('copyFile.js', { content: executeCopyFile })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'pragma solidity >=0.7.0 <0.9.0;', 60000)
},
'Should execute `rename` api from file manager external api #group2': function (browser: NightwatchBrowser) {
browser
.addFile('renameFile.js', { content: executeRename })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(2000)
.waitForElementPresent('[data-id="treeViewLitreeViewItemold_contract.sol"]', 60000)
},
@ -69,15 +69,15 @@ module.exports = {
'Should execute `mkdir` api from file manager external api #group3': function (browser: NightwatchBrowser) {
browser
.addFile('mkdirFile.js', { content: executeMkdir })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(2000)
.waitForElementPresent('[data-id="treeViewLitreeViewItemTest_Folder"]', 60000)
.waitForElementPresent('[data-id="treeViewLitreeViewItemTest_Folder"]', 80000)
},
'Should execute `readdir` api from file manager external api #group3': function (browser: NightwatchBrowser) {
browser
.addFile('readdirFile.js', { content: executeReaddir })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Test_Folder isDirectory', 60000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'true', 5000)
},
@ -86,7 +86,7 @@ module.exports = {
browser
.addFile('old_contract.sol', { content: 'test' })
.addFile('removeFile.js', { content: executeRemove })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(2000)
.waitForElementNotPresent('[data-id="treeViewLitreeViewItemold_contract.sol"]', 60000)
},
@ -95,7 +95,7 @@ module.exports = {
'Should execute `remove` api from file manager external api on a folder #group4': function (browser: NightwatchBrowser) {
browser
.addFile('test_jsRemoveFolder.js', { content: executeRemoveOnFolder })
.executeScript('remix.exeCurrent()')
.executeScriptInTerminal('remix.exeCurrent()')
.pause(2000)
.waitForElementNotPresent('[data-id="treeViewLitreeViewItemcontracts"]', 60000)
.end()

@ -0,0 +1,125 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Test decorators with script': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/2_Owner.sol')
.openFile('contracts/1_Storage.sol')
.openFile('contracts/3_Ballot.sol')
.addFile('scripts/decorators.ts', { content: testScriptSet })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.useXpath()
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2')
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U')
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2')
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0, 0)
.waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]')
.waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner')
},
'clear ballot decorator': function (browser: NightwatchBrowser) {
browser
.useCss()
.addFile('scripts/clearballot.ts', { content: testScriptClearBallot })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000)
},
'clear all decorators': function (browser: NightwatchBrowser) {
browser
.addFile('scripts/clearall.ts', { content: testScriptClear })
.pause(2000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.waitForElementNotPresent('[data-id="file-decoration-error-contracts/2_Owner.sol"]', 10000)
.waitForElementNotPresent('[data-id="file-decoration-warning-contracts/1_Storage.sol"]', 10000)
}
}
const testScriptSet = `
(async () => {
remix.call('fileDecorator' as any, 'clearFileDecorators')
let decorator: any = {
path: 'contracts/2_Owner.sol',
isDirectory: false,
fileStateType: 'ERROR',
fileStateLabelClass: 'text-danger',
fileStateIconClass: '',
fileStateIcon: '',
text: '2',
bubble: true,
comment: 'error on owner',
}
let decorator2: any = {
path: 'contracts/2_Owner.sol',
isDirectory: false,
fileStateType: 'CUSTOM',
fileStateLabelClass: 'text-success',
fileStateIconClass: 'text-success',
fileStateIcon: 'U',
text: '',
bubble: true,
comment: 'modified',
}
await remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2])
decorator = {
path: 'contracts/1_Storage.sol',
isDirectory: false,
fileStateType: 'WARNING',
fileStateLabelClass: 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: '2',
bubble: true,
comment: 'warning on storage',
}
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
decorator = {
path: 'contracts/3_Ballot.sol',
isDirectory: false,
fileStateType: 'CUSTOM',
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: 'customtext',
text: 'with text',
bubble: true,
comment: 'custom comment',
}
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator)
})()`
const testScriptClearBallot = `
(async () => {
await remix.call('fileDecorator' as any, 'clearFileDecorators', 'contracts/3_Ballot.sol')
})()`
const testScriptClear = `
(async () => {
await remix.call('fileDecorator' as any, 'clearAllFileDecorators')
})()`

@ -41,7 +41,7 @@ module.exports = {
.setValue('*[data-id="settingsTabGistAccessToken"]', '**********')
.click('*[data-id="settingsTabSaveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Access token has been saved')
.assert.containsText('*[data-shared="tooltipPopup"]', 'Credentials updated')
.pause(3000)
},
@ -59,7 +59,7 @@ module.exports = {
.pause(1000)
.click('*[data-id="settingsTabRemoveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'Access token removed')
.assert.containsText('*[data-shared="tooltipPopup"]', 'Credentials removed')
.assert.containsText('*[data-id="settingsTabGistAccessToken"]', '')
},

@ -9,10 +9,11 @@ const testData = {
// 99266d6da54cc12f37f11586e8171546c7700d67
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
UploadToGists: function (browser: NightwatchBrowser) {
'UploadToGists #group1': function (browser: NightwatchBrowser) {
/*
- set the access token
- publish to gist
@ -34,7 +35,7 @@ module.exports = {
.sendKeys('*[data-id$="/blank"] .remixui_items', browser.Keys.ENTER)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemBrowser_Tests"]')
.addFile('File.sol', { content: '' })
.executeScript(`remix.loadgist('${gistid}')`)
.executeScriptInTerminal(`remix.loadgist('${gistid}')`)
// .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() })
.waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.click(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
@ -58,7 +59,7 @@ module.exports = {
const gistid = id[1]
browser
.click('[data-id="default_workspace-modal-footer-cancel-react"]')
.executeScript(`remix.loadgist('${gistid}')`)
.executeScriptInTerminal(`remix.loadgist('${gistid}')`)
// .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('gists') } done() })
.waitForElementVisible(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
.click(`[data-id="treeViewLitreeViewItemgist-${gistid}"]`)
@ -68,7 +69,7 @@ module.exports = {
*/
},
'Load Gist Modal': function (browser: NightwatchBrowser) {
'Load Gist Modal #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
@ -84,7 +85,7 @@ module.exports = {
.modalFooterCancelClick('gisthandler')
},
'Display Error Message For Invalid Gist ID': function (browser: NightwatchBrowser) {
'Display Error Message For Invalid Gist ID #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -101,7 +102,7 @@ module.exports = {
.modalFooterOKClick('gisthandler')
},
'Display Error Message For Missing Gist Token When Publishing': function (browser: NightwatchBrowser) {
'Display Error Message For Missing Gist Token When Publishing #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -125,9 +126,9 @@ module.exports = {
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
},
'Import From Gist For Valid Gist ID': function (browser: NightwatchBrowser) {
'Import From Gist For Valid Gist ID #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 15000)
.clickLaunchIcon('settings')
.click('*[data-id="settingsTabGenerateContractMetadataLabel"]')
.setValue('[data-id="settingsTabGistAccessToken"]', process.env.gist_token)
@ -140,6 +141,7 @@ module.exports = {
})
.setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId)
.modalFooterOKClick('gisthandler')
.pause(10000)
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')

@ -9,71 +9,71 @@ const testData = {
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'Import from GitHub Modal': function (browser: NightwatchBrowser) {
'Import from GitHub Modal #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
.click('div[title="home"]')
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]')
.pause(1000)
.scrollAndClick('button[data-id="landingPageImportFromGitHubButton"]')
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogModalTitle-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from Github')
.assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from GitHub')
.waitForElementVisible('*[data-id="homeTabModalDialogModalBody-react"]')
.assert.containsText('*[data-id="homeTabModalDialogModalBody-react"]', 'Enter the github URL you would like to load.')
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]')
.refresh()
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
},
'Display Error Message For Invalid GitHub URL Modal': function (browser: NightwatchBrowser) {
'Display Error Message For Invalid GitHub URL Modal #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
.execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus()
}, [], () => {})
}, [], () => { })
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.invalidURL)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.scrollAndClick('[data-id="homeTab-modal-footer-ok-react"]') // submitted
.waitForElementVisible('*[data-shared="tooltipPopup"]')
.assert.containsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL)
.click('[data-id="homeTab-modal-footer-ok-react"]') // submitted
//.waitForElementVisible('*[data-shared="tooltipPopup"]')
//.waitForElementContainsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL)
},
'Import From Github For Valid URL': function (browser: NightwatchBrowser) {
'Import From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]')
.clearValue('*[data-id="homeTabModalDialogCustomPromptText"]')
.clickLaunchIcon('filePanel')
.click('div[title="home"]')
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]').pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
.execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus()
}, [], () => {})
}, [], () => { })
.clearValue('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000)
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.validURL)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.scrollAndClick('[data-id="homeTab-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]')
.openFile('github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol')
.waitForElementVisible("div[title='default_workspace/github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol'")
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('library Roles {') !== -1, 'content does contain "library Roles {"')
})
},
'Import JSON From Github For Valid URL': function (browser: NightwatchBrowser) {
'Import JSON From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) {
browser
.click('div[title="home"]')
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]')
.clearValue('*[data-id="homeTabModalDialogCustomPromptText"]')
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000)
.execute(() => {
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus()
}, [], () => {})
}, [], () => { })
.clearValue('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000)
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.JSON)
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.scrollAndClick('[data-id="homeTab-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]')
.openFile('github/ethereum/remix-project/package.json')
.waitForElementVisible("div[title='default_workspace/github/ethereum/remix-project/package.json'")
.getEditorValue((content) => {

@ -81,7 +81,7 @@ module.exports = {
// these are test data entries
'Should have a workspace_test #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="workspacesSelect"] option[value="workspace_test"]')
.switchWorkspace('workspace_test')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemtest_contracts"]')
},
'Should have a sol file with test data #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) {
@ -103,7 +103,7 @@ module.exports = {
},
'Should have a empty workspace #group1 #group3 #group5 #group7': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]', 5000)
.click('*[data-id="workspacesSelect"] option[value="emptyspace"]')
.switchWorkspace('emptyspace')
},
// end of test data entries
'Should load with all storage blocked #group4': function (browser: NightwatchBrowser) {

@ -109,7 +109,7 @@ module.exports = {
'Should load back installed plugins after reload': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.waitForElementVisible('*[data-id="remixIdeSidePanel"]',3000)
.waitForElementVisible('*[data-id="pluginManagerComponentPluginManager"]')
.getInstalledPlugins((plugins) => {
browser.refresh()

@ -188,7 +188,7 @@ module.exports = {
.frameParent()
.useCss()
.clickLaunchIcon('udapp')
.waitForElementContainsText('#selectExEnvOptions option:checked', 'JavaScript VM (Berlin)')
.waitForElementContainsText('#selectExEnvOptions button', 'Remix VM (Berlin)')
.clickLaunchIcon('localPlugin')
.useXpath()
// @ts-ignore
@ -214,7 +214,7 @@ module.exports = {
}
})
.waitForElementVisible('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]')
.rightClick('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]').useXpath().waitForElementVisible('//*[@id="menuitemtestcommand"]').click('//*[@id="menuitemtestcommand"]', async () => {
.rightClickCustom('[data-id="treeViewLitreeViewItemcontracts/1_Storage.sol"]').useXpath().waitForElementVisible('//*[@id="menuitemtestcommand"]').click('//*[@id="menuitemtestcommand"]', async () => {
// @ts-ignore
browser.click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => {
await clickAndCheckLog(browser, null, { id: 'localPlugin', name: 'testCommand', label: 'testCommand', type: [], extension: ['.sol'], path: ['contracts/1_Storage.sol'], pattern: [] }, null, null)
@ -230,7 +230,12 @@ module.exports = {
},
'Should get current files #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, tests: { isDirectory: true }, scripts: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, '/')
await clickAndCheckLog(browser, 'fileManager:readdir', {
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
}, null, '/')
},
'Should throw error on current file #group7': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'fileManager:getCurrentFile', 'Error from IDE : Error: No such file or directory No file selected', null, null)
@ -285,40 +290,45 @@ module.exports = {
'Should create workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'testspace')
await clickAndCheckLog(browser, 'filePanel:getCurrentWorkspace', { name: 'testspace', isLocalhost: false, absolutePath: '.workspaces/testspace' }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', { contracts: { isDirectory: true }, tests: { isDirectory: true }, scripts: { isDirectory: true }, 'README.txt': { isDirectory: false } }, null, null)
await clickAndCheckLog(browser, 'fileManager:readdir', {
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
}, null, '/')
},
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['default_workspace', 'emptyworkspace', 'testspace'], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false}, {name:"emptyworkspace",isGitRepo:false}, {name:"testspace",isGitRepo:false}], null, null)
},
'Should have set workspace event #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, { event: 'setWorkspace', args: [{ name: 'newspace', isLocalhost: false }] }, 'newspace')
},
'Should have event when switching workspace #group2': async function (browser: NightwatchBrowser) {
// @ts-ignore
browser.frameParent().useCss().clickLaunchIcon('filePanel').click('*[data-id="workspacesSelect"] option[value="default_workspace"]').useXpath().click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => {
browser.frameParent().useCss().clickLaunchIcon('filePanel').switchWorkspace('default_workspace').useXpath().click('//*[@data-id="verticalIconsKindlocalPlugin"]').frame(0, async () => {
await clickAndCheckLog(browser, null, null, { event: 'setWorkspace', args: [{ name: 'default_workspace', isLocalhost: false }] }, null)
})
},
'Should rename workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:renameWorkspace', null, null, ['default_workspace', 'renamed'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'testspace', 'newspace', 'renamed'], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"testspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null)
},
'Should delete workspace #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:deleteWorkspace', null, null, ['testspace'])
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', ['emptyworkspace', 'newspace', 'renamed'], null, null)
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"emptyworkspace",isGitRepo:false},{name:"newspace",isGitRepo:false},{name:"renamed",isGitRepo:false}], null, null)
},
// DGIT
'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'dgit')
await clickAndCheckLog(browser, 'dGitProvider:status', [['README.txt', 0, 2, 0], ['contracts/1_Storage.sol', 0, 2, 0], ['contracts/2_Owner.sol', 0, 2, 0], ['contracts/3_Ballot.sol', 0, 2, 0], ['scripts/deploy_with_ethers.ts', 0, 2, 0], ['scripts/deploy_with_web3.ts', 0, 2, 0], ['tests/Ballot_test.sol', 0, 2, 0]], ['tests/storage.test.js', 0, 2, 0], null, null)
await clickAndCheckLog(browser, 'dGitProvider:status', [["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,0],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
},
'Should stage contract #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:add', null, null, {
filepath: 'contracts/1_Storage.sol'
})
await clickAndCheckLog(browser, 'dGitProvider:status', [['README.txt', 0, 2, 0], ['contracts/1_Storage.sol', 0, 2, 2], ['contracts/2_Owner.sol', 0, 2, 0], ['contracts/3_Ballot.sol', 0, 2, 0], ['scripts/deploy_with_ethers.ts', 0, 2, 0], ['scripts/deploy_with_web3.ts', 0, 2, 0], ['tests/Ballot_test.sol', 0, 2, 0]], ['tests/storage.test.js', 0, 2, 0], null, null)
await clickAndCheckLog(browser, 'dGitProvider:status', [["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,2],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
},
'Should commit changes #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:commit', null, null, { author: { name: 'Remix', email: 'Remix' }, message: 'commit-message' })
@ -381,7 +391,7 @@ module.exports = {
.useCss()
.clickLaunchIcon('pluginManager')
.clickLaunchIcon('udapp')
.click('*[data-id="Hardhat Provider"]')
.switchEnvironment('Hardhat Provider')
.modalFooterOKClick('hardhat-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom') // e.g Custom (1337) network
.clickLaunchIcon('localPlugin')
@ -407,9 +417,9 @@ module.exports = {
.frameParent()
.useCss()
.addFile('test_modal.js', { content: testModalToasterApi })
.executeScript('remix.execute(\'test_modal.js\')')
.executeScriptInTerminal('remix.execute(\'test_modal.js\')')
.useCss()
.waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]')
.waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]', 60000)
.assert.containsText('*[data-id="test_id_1_ModalDialogModalBody-react"]', 'message 1')
.modalFooterOKClick('test_id_1_')
// check the script runner notifications

@ -10,7 +10,7 @@ module.exports = {
'Should switch to ganache provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('udapp')
.click('*[data-id="Ganache Provider"]')
.switchEnvironment('Ganache Provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="ganache-providerModalDialogModalBody-react"] input') as any).focus()
@ -25,9 +25,32 @@ module.exports = {
},
'Should switch to ganache provider, use the default ganache URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.click('*[data-id="Ganache Provider"]')
browser.switchEnvironment('Ganache Provider')
.waitForElementVisible('*[data-id="ganache-providerModalDialogModalBody-react"]')
.modalFooterOKClick('ganache-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
},
'Should switch to foundry provider, set a custom URL and fail to connect': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="remixIdeIconPanel"]', 10000)
.switchEnvironment('Foundry Provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.execute(() => {
(document.querySelector('*[data-id="foundry-providerModalDialogModalBody-react"] input') as any).focus()
}, [], () => {})
.clearValue('*[data-id="foundry-providerModalDialogModalBody-react"] input')
.setValue('*[data-id="foundry-providerModalDialogModalBody-react"] input', 'http://127.0.0.1:8084')
.modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="foundry-providerModalDialogModalBody-react"]', 'Error while connecting to the provider')
.modalFooterOKClick('foundry-provider')
.waitForElementNotVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.pause(1000)
},
'Should switch to foundry provider, use the default foundry URL and succeed to connect': function (browser: NightwatchBrowser) {
browser.switchEnvironment('Foundry Provider')
.waitForElementVisible('*[data-id="foundry-providerModalDialogModalBody-react"]')
.modalFooterOKClick('foundry-provider')
.waitForElementContainsText('*[data-id="settingsNetworkEnv"]', 'Custom (')
}
}

@ -0,0 +1,287 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
let firstProxyAddress: string
let lastProxyAddress: string
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should show deploy proxy option for UUPS upgradeable contract': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV1.sol', sources[0]['myTokenV1.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should show upgrade proxy option for child contract inheriting UUPS parent contract': function (browser: NightwatchBrowser) {
browser
.addFile('myTokenV2.sol', sources[1]['myTokenV2.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
},
'Should deploy proxy without initialize parameters': function (browser: NightwatchBrowser) {
browser
.openFile('myTokenV1.sol')
.clickLaunchIcon('solidity')
.pause(2000)
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
},
'Should interact with deployed contract via ERC1967 (proxy)': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
firstProxyAddress = address
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'name - call', null, '0:\nstring: MyToken').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'symbol - call', null, '0:\nstring: MTK').perform(() => {
done()
})
})
},
'Should deploy proxy with initialize parameters': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.addFile('initializeProxy.sol', sources[2]['initializeProxy.sol'])
.clickLaunchIcon('solidity')
.pause(2000)
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyInitializedToken]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyInitializedToken]')
.waitForElementPresent('[data-id="contractGUIDeployWithProxyLabel"]')
.click('[data-id="contractGUIDeployWithProxyLabel"]')
.waitForElementPresent('input[title="tokenName"]')
.waitForElementPresent('input[title="tokenSymbol"]')
.setValue('input[title="tokenName"]', 'Remix')
.setValue('input[title="tokenSymbol"]', "R")
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Proxy (ERC1967)')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Deploy Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
},
'Should interact with initialized contract to verify parameters': function (browser: NightwatchBrowser) {
browser
.getAddressAtPosition(1, (address) => {
lastProxyAddress = address
})
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'name - call', null, '0:\nstring: Remix').perform(() => {
done()
})
})
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'symbol - call', null, '0:\nstring: R').perform(() => {
done()
})
})
},
'Should upgrade contract using last deployed proxy address (MyTokenV1 to MyTokenV2)': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.pause(2000)
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.click('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]')
.click('[data-id="contractGUIProxyAddressLabel"]')
.waitForElementPresent('[data-id="lastDeployedERC1967Address"]')
.assert.containsText('[data-id="lastDeployedERC1967Address"]', lastProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
},
'Should interact with upgraded function in contract MyTokenV2': function (browser: NightwatchBrowser) {
browser
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(lastProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
},
'Should upgrade contract by providing proxy address in input field (MyTokenV1 to MyTokenV2)': function (browser: NightwatchBrowser) {
browser
.waitForElementPresent('[data-id="deployAndRunClearInstances"]')
.click('[data-id="deployAndRunClearInstances"]')
.openFile('myTokenV2.sol')
.clickLaunchIcon('solidity')
.pause(2000)
.click('[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('select[id="compiledContracts"] option[value=MyTokenV2]', 60000)
.clickLaunchIcon('udapp')
.click('select.udapp_contractNames')
.click('select.udapp_contractNames option[value=MyTokenV2]')
.waitForElementPresent('[data-id="contractGUIUpgradeImplementationLabel"]')
.waitForElementPresent('[data-id="contractGUIProxyAddressLabel"]')
.click('[data-id="contractGUIProxyAddressLabel"]')
.waitForElementPresent('[data-id="ERC1967AddressInput"]')
.setValue('[data-id="ERC1967AddressInput"]', firstProxyAddress)
.createContract('')
.waitForElementContainsText('[data-id="udappNotifyModalDialogModalTitle-react"]', 'Deploy Implementation & Update Proxy')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
.waitForElementContainsText('[data-id="confirmProxyDeploymentModalDialogModalTitle-react"]', 'Confirm Update Proxy (ERC1967)')
.waitForElementVisible('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.click('[data-id="confirmProxyDeployment-modal-footer-ok-react"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander0"]')
.waitForElementPresent('[data-id="universalDappUiTitleExpander1"]')
},
'Should interact with upgraded contract through provided proxy address': function (browser: NightwatchBrowser) {
browser
.clickInstance(1)
.perform((done) => {
browser.testConstantFunction(firstProxyAddress, 'version - call', null, '0:\nstring: MyTokenV2!').perform(() => {
done()
})
})
.end()
}
}
const sources = [
{
'myTokenV1.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() initializer public {
__ERC721_init("MyToken", "MTK");
__Ownable_init();
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}, {
'myTokenV2.sol': {
content: `
import "./myTokenV1.sol";
contract MyTokenV2 is MyToken {
function version () public view returns (string memory) {
return "MyTokenV2!";
}
}
`
}
}, {
'initializeProxy.sol': {
content: `
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyInitializedToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(string memory tokenName, string memory tokenSymbol) initializer public {
__ERC721_init(tokenName, tokenSymbol);
__Ownable_init();
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
onlyOwner
override
{}
}
`
}
}
]

@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
@ -19,17 +20,22 @@ module.exports = {
.openFile('contracts/3_Ballot.sol')
.verifyContracts(['Ballot'])
.click('#publishOnIpfs')
.pause(2000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
.pause(8000)
.waitForElementVisible('[data-id="publishToStorageModalDialogModalBody-react"]', 60000)
.getText('[data-id="publishToStorageModalDialogModalBody-react"]', (result) => {
const value = <string>(result.value)
browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
done()
})
})
.click('[data-id="publishToStorage-modal-footer-ok-react"]')
.openFile('ipfs/QmSUodhSvoorFL5m5CNqve8YvmuBpjCq17NbTf4GUX8ydw')
.openFile('ipfs/QmXYUS1ueS22EqNVRaKuZa31EgHLjKZ8uTM8vWhQLxa3pw')
},
/* Disableing the test untill refactoring and the new swarm usage
@ -41,7 +47,7 @@ module.exports = {
const value = <string>(result.value)
browser.perform((done) => {
if (value.indexOf('Metadata of "ballot" was published successfully.') === -1) browser.assert.fail('swarm deploy failed')
if (value.indexOf('Metadata and sources of "ballot" were published successfully.') === -1) browser.assert.fail('swarm deploy failed')
if (value.indexOf('bzz') === -1) browser.assert.fail('swarm deploy failed')
done()
})
@ -61,11 +67,13 @@ module.exports = {
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000)
.waitForElementVisible('[data-id="udappModalDialogModalBody-react"]')
.waitForElementVisible('[data-id="udappModalDialogModalBody-react"]', 60000)
.modalFooterOKClick('udapp')
.pause(8000)
.getText('[data-id="udappModalDialogModalBody-react"]', (result) => {
const value = typeof result.value === 'string' ? result.value : null
if (value.indexOf('Metadata of "storage" was published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
if (value.indexOf('Metadata and sources of "storage" were published successfully.') === -1) browser.assert.fail('ipfs deploy failed')
})
.modalFooterOKClick('udapp')
},

@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
@ -11,13 +12,13 @@ module.exports = {
return sources
},
'Run Scenario': function (browser: NightwatchBrowser) {
'Run Scenario #group1': function (browser: NightwatchBrowser) {
let addressRef
browser.addFile('scenario.json', { content: records })
.pause(5000)
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('[data-id="udapp_arrow"]')
.click('[data-id="udappRecorderTitleExpander"]')
.click('[data-id="runtransaction"]')
.clickInstance(0)
.clickInstance(1)
@ -36,13 +37,13 @@ module.exports = {
.click('*[data-id="deployAndRunClearInstances"]')
},
'Save scenario': function (browser: NightwatchBrowser) {
'Save scenario #group1': function (browser: NightwatchBrowser) {
browser.testContracts('testRecorder.sol', sources[0]['testRecorder.sol'], ['testRecorder'])
.clickLaunchIcon('udapp')
.createContract('12')
.clickInstance(0)
.clickFunction('set - transact (not payable)', { types: 'uint256 _p', values: '34' })
.click('i.savetransaction')
.click('.savetransaction')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {
const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
@ -64,7 +65,7 @@ module.exports = {
})
},
'Record more than one contract': function (browser: NightwatchBrowser) {
'Record more than one contract #group1': function (browser: NightwatchBrowser) {
// deploy 2 contracts (2 different ABIs), save the record, reexecute and test one of the function.
browser
.click('*[data-id="deployAndRunClearInstances"]')
@ -77,13 +78,14 @@ module.exports = {
.selectContract('t2est')
.pause(1000)
.createContract('')
.click('i.savetransaction')
.click('.savetransaction')
.waitForElementVisible('[data-id="udappNotify-modal-footer-ok-react"]')
.execute(function () {
const modalOk = document.querySelector('[data-id="udappNotify-modal-footer-ok-react"]') as any
modalOk.click()
})
.pause(1000)
.click('*[data-id="deployAndRunClearInstances"]') // clear udapp
.click('*[data-id="terminalClearConsole"]') // clear terminal
.click('[data-id="runtransaction"]')
@ -95,6 +97,50 @@ module.exports = {
status: 'true Transaction mined and execution succeed',
'decoded input': { 'uint256 _po': '10' }
})
},
'Run with live "mode" #group1': function (browser: NightwatchBrowser) {
let addressRef: string
browser.addFile('scenario_live_mode.json', { content: JSON.stringify(liveModeScenario, null, '\t') })
.addFile('scenario_live_mode_storage.sol', { content: testStorageForLiveMode })
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]')
.openFile('scenario_live_mode.json')
.clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]')
.click('*[data-id="runtabLivemodeInput"]')
.click('.runtransaction')
.pause(1000)
.clickInstance(0)
.getAddressAtPosition(0, (address) => {
addressRef = address
})
.clickFunction('retrieve - call')
.perform((done) => {
browser.verifyCallReturnValue(addressRef, ['0:uint256: 350'])
.perform(() => done())
})
// change the init state and recompile the same contract.
.openFile('scenario_live_mode_storage.sol')
.setEditorValue(testStorageForLiveMode.replace('number = 350', 'number = 300'))
.pause(5000)
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]')
.openFile('scenario_live_mode.json')
.clickLaunchIcon('udapp')
.click('*[data-id="deployAndRunClearInstances"]')
.click('.runtransaction')
.pause(5000)
.clickInstance(0)
.getAddressAtPosition(0, (address) => {
addressRef = address
})
.clickFunction('retrieve - call')
.perform((done) => {
browser.verifyCallReturnValue(addressRef, ['0:uint256: 300'])
.perform(() => done())
})
.end()
}
}
@ -364,3 +410,91 @@ const scenario = {
]
}
}
const liveModeScenario = {
"accounts": {
"account{0}": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
},
"linkReferences": {},
"transactions": [
{
"timestamp": 1656329164297,
"record": {
"value": "0",
"parameters": [],
"abi": "0x8b8c9c14c8e1442e90dd6ff82bb9889ccfe5a54d88ef30776f11047ecce5fedb",
"contractName": "Storage",
"bytecode": "608060405234801561001057600080fd5b5060c88061001f6000396000f3fe6080604052348015600f57600080fd5b5060043610604e577c010000000000000000000000000000000000000000000000000000000060003504632e64cec1811460535780636057361d146068575b600080fd5b60005460405190815260200160405180910390f35b60786073366004607a565b600055565b005b600060208284031215608b57600080fd5b503591905056fea264697066735822122091f1bc250ccda7caf2b0d9f67b0314d92233fdb5952b72cece72bd2a5d43cfc264736f6c63430008070033",
"linkReferences": {},
"name": "",
"inputs": "()",
"type": "constructor",
"from": "account{0}"
}
}
],
"abis": {
"0x8b8c9c14c8e1442e90dd6ff82bb9889ccfe5a54d88ef30776f11047ecce5fedb": [
{
"inputs": [
{
"internalType": "uint256",
"name": "num",
"type": "uint256"
}
],
"name": "store",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "retrieve",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
}
}
const testStorageForLiveMode = `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/**
* @title Storage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy_with_ethers.ts
*/
contract Storage {
uint256 number;
constructor () {
number = 350;
}
/**
* @dev Store value in variable
* @param num value to store
*/
function store(uint256 num) public {
number = num;
}
/**
* @dev Return value
* @return value of 'number'
*/
function retrieve() public view returns (uint256){
return number;
}
}`

@ -1,6 +1,8 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import { writeFileSync } from 'fs'
import init from '../helpers/init'
import * as hardhatCompilation from '../helpers/hardhat_compilation_8a7ab689ec618720f53ce867a3031c03.json'
const assetsTestContract = `import "./contract.sol";
contract Assets {
@ -55,6 +57,7 @@ module.exports = {
},
'start Remixd': function (browser) {
startRemixd(browser)
},
'run Remixd tests #group4': function (browser) {
runTests(browser)
@ -84,6 +87,7 @@ module.exports = {
'Static Analysis run with remixd #group3': function (browser) {
browser.testContracts('test_static_analysis_with_remixd_and_hardhat.sol', sources[5]['test_static_analysis_with_remixd_and_hardhat.sol'], ['test5']).pause(2000)
.clickLaunchIcon('solidityStaticAnalysis')
/*
.click('#staticanalysisButton button').pause(4000)
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
browser
@ -96,11 +100,12 @@ module.exports = {
'code has not been loaded')
})
})
*/
},
'Run git status': '' + function (browser) {
browser
.executeScript('git status')
.executeScriptInTerminal('git status')
.pause(3000)
.journalLastChildIncludes('On branch ')
},
@ -109,8 +114,22 @@ module.exports = {
browser
.clickLaunchIcon('pluginManager')
.scrollAndClick('#pluginManager *[data-id="pluginManagerComponentDeactivateButtonremixd"]')
.end()
}
},
'Should listen on compilation result from hardhat #group5': function (browser: NightwatchBrowser) {
browser.perform((done) => {
console.log('working directory', process.cwd())
writeFileSync('./apps/remix-ide/contracts/artifacts/build-info/c7062fdd360381a85af23eeef31c98f8.json', JSON.stringify(hardhatCompilation))
done()
})
.expect.element('*[data-id="terminalJournal"]').text.to.contain('received compilation result from hardhat').before(60000)
browser.clickLaunchIcon('udapp')
.assert.textContains('*[data-id="udappCompiledBy"]', 'Compiled by hardhat')
.selectContract('Lock')
.createContract('1')
.expect.element('*[data-id="terminalJournal"]').text.to.contain('Unlock time should be in the future').before(60000)
}
}
function startRemixd (browser: NightwatchBrowser) {
@ -129,6 +148,7 @@ function startRemixd (browser: NightwatchBrowser) {
.waitForElementVisible('*[data-id="remixdConnect-modal-footer-ok-react"]', 2000)
.pause(2000)
.click('*[data-id="remixdConnect-modal-footer-ok-react"]')
.pause(10000)
// .click('*[data-id="workspacesModalDialog-modal-footer-ok-react"]')
}

@ -32,15 +32,15 @@ module.exports = {
'Should sign message using account key #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="settingsRemixRunSignMsg"]')
.click('select[id="selectExEnvOptions"] option[value="vm-berlin"]')
.switchEnvironment('vm-berlin')
.pause(2000)
.click('*[data-id="settingsRemixRunSignMsg"]')
.pause(2000)
.waitForElementVisible('*[data-id="signMessageTextarea"]', 120000)
.click('*[data-id="signMessageTextarea"]')
.setValue('*[data-id="signMessageTextarea"]', 'Remix is cool!')
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]')
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]')
.assert.not.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]')
.assert.not.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]')
.pause(2000)
.waitForElementPresent('[data-id="udappNotify-modal-footer-ok-react"]')
.click('[data-id="udappNotify-modal-footer-ok-react"]')
@ -65,6 +65,29 @@ module.exports = {
})
},
'Should show and update balance for deployed contract on JavascriptVM #group3': function (browser: NightwatchBrowser) {
let instanceAddress
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.clickLaunchIcon('filePanel')
.addFile('checkBalance.sol', sources[0]['checkBalance.sol'])
.clickLaunchIcon('udapp')
.setValue('*[data-id="dandrValue"]', '111')
.waitForElementVisible('*[data-id="Deploy - transact (payable)"]', 45000)
.click('*[data-id="Deploy - transact (payable)"]')
.pause(1000)
.clickInstance(1)
.pause(1000)
.getAddressAtPosition(1, (address) => {
instanceAddress = address
browser
.waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH')
.clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' })
.pause(1000)
.assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000109 ETH')
})
},
'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.clickInstance(0)
@ -80,7 +103,7 @@ module.exports = {
.end()
},
'Should connect to Goerli Test Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should connect to Goerli Test Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.setupMetamask(passphrase, password)
.click('.network-indicator__down-arrow')
@ -101,7 +124,7 @@ module.exports = {
.switchBrowserTab(0)
},
'Should deploy contract on Goerli Test Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should deploy contract on Goerli Test Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="runTabSelectAccount"] option')
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
@ -118,7 +141,7 @@ module.exports = {
.switchBrowserTab(0)
},
'Should run low level interaction (fallback function) on Goerli Test Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should run low level interaction (fallback function) on Goerli Test Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.waitForElementPresent('*[data-id="universalDappUiTitleExpander"]')
.click('*[data-id="universalDappUiTitleExpander"]')
@ -135,7 +158,7 @@ module.exports = {
.end()
},
'Should connect to Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should connect to Ethereum Main Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.switchBrowserTab(2)
.waitForElementPresent('.network-indicator__down-arrow')
@ -153,7 +176,7 @@ module.exports = {
.assert.containsText('*[data-id="settingsNetworkEnv"]', 'Main (1) network')
},
'Should deploy contract on Ethereum Main Network using MetaMask': '' + function (browser: NightwatchBrowser) {
'Should deploy contract on Ethereum Main Network using MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="runTabSelectAccount"] option')
.clickLaunchIcon('filePanel')
.openFile('Greet.sol')
@ -173,7 +196,7 @@ module.exports = {
* - Ropsten node for retrieving the trace and storage
*
*/
'Should debug Ropsten transaction with source highlighting using the source verifier service and MetaMask': '' + function (browser: NightwatchBrowser) {
'Should debug Ropsten transaction with source highlighting using the source verifier service and MetaMask': !function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.switchBrowserTab(2)
@ -198,9 +221,9 @@ module.exports = {
.assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1')
},
'Call web3.eth.getAccounts() using Injected web3 (Metamask)': '' + function (browser: NightwatchBrowser) {
'Call web3.eth.getAccounts() using Injected Provider (Metamask)': !function (browser: NightwatchBrowser) {
browser
.executeScript('web3.eth.getAccounts()')
.executeScriptInTerminal('web3.eth.getAccounts()')
.pause(2000)
.journalLastChildIncludes('[ "0x76a3ABb5a12dcd603B52Ed22195dED17ee82708f" ]')
.end()
@ -224,6 +247,17 @@ const sources = [
message = _message;
}
}`
},
'checkBalance.sol': {
content: `pragma solidity ^0.8.0;
contract checkBalance {
constructor () payable {}
function sendSomeEther(uint256 num) public {
payable(msg.sender).transfer(num);
}
}`
}
}
]

@ -4,14 +4,15 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should find text': function (browser: NightwatchBrowser) {
'Should find text #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.waitForElementVisible('*[id="search_include"]')
.setValue('*[id="search_include"]', ', *.txt').pause(2000)
.setValue('*[id="search_include"]', ', *.*').pause(2000)
.setValue('*[id="search_input"]', 'read').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.pause(1000)
.waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000)
@ -26,23 +27,23 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 6)
})
},
'Should find text with exclude': function (browser: NightwatchBrowser) {
'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, 48)
Array.isArray(res.value) && browser.assert.equal(res.value.length, 62)
})
.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, 42)
Array.isArray(res.value) && browser.assert.equal(res.value.length, 56)
})
.clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt')
.clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*')
},
'Should find regex': function (browser: NightwatchBrowser) {
'Should find regex #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]').pause(2000)
@ -57,7 +58,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 4)
})
},
'Should find matchcase': function (browser: NightwatchBrowser) {
'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)
@ -71,17 +72,17 @@ module.exports = {
})
.waitForElementContainsText('*[data-id="search_results"]', 'STORAGE.TEST.JS', 60000)
},
'Should find matchword': function (browser: NightwatchBrowser) {
'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, 13)
Array.isArray(res.value) && browser.assert.equal(res.value.length, 15)
})
},
'Should replace text': function (browser: NightwatchBrowser) {
'Should replace text #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]')
.waitForElementVisible('*[id="search_replace"]')
@ -95,11 +96,11 @@ module.exports = {
browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok')
})
},
'Should replace text without confirmation': function (browser: NightwatchBrowser) {
'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"]', '2').pause(1000)
.setValue('*[id="search_replace"]', 'replacing2').pause(1000)
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-33-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-33-71"]', 10, 10)
.waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-33-71"]')
@ -108,7 +109,7 @@ module.exports = {
browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok')
})
},
'Should replace all & undo': function (browser: NightwatchBrowser) {
'Should replace all & undo #group1': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
@ -127,7 +128,7 @@ module.exports = {
browser.assert.ok(content.includes('title Storage'), 'should undo text ok')
})
},
'Should replace all & undo & switch between files': function (browser: NightwatchBrowser) {
'Should replace all & undo & switch between files #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
@ -163,14 +164,20 @@ module.exports = {
browser.assert.ok(content.includes("Storage' contract"), 'should replace text ok')
})
},
'Should hide button when edited content is the same': function (browser: NightwatchBrowser) {
'Should hide button when edited content is the same #group2': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.addFile('test.sol', { content: '123' })
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.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)
.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) => {
@ -181,13 +188,14 @@ module.exports = {
.getEditorValue((content) => {
browser.assert.ok(content.includes('123'), 'should have text ok')
}
).pause(1000)
).pause(5000)
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
},
'Should disable/enable button when edited content changed': function (browser: NightwatchBrowser) {
'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)
@ -201,7 +209,7 @@ module.exports = {
.getEditorValue((content) => {
browser.assert.ok(content.includes('changed'), 'should have text ok')
}
).pause(1000)
).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')
@ -222,7 +230,7 @@ module.exports = {
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
},
'should clear search': function (browser: NightwatchBrowser) {
'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)

@ -30,7 +30,7 @@ module.exports = {
.assert.containsText('#compileTabView .error pre', 'not found Untitled11.sol')
},
'Test Github Import - from master branch #group1': function (browser: NightwatchBrowser) {
'Test GitHub Import - from master branch #group1': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.addFile('Untitled4.sol', sources[3]['Untitled4.sol'])
@ -38,7 +38,7 @@ module.exports = {
.verifyContracts(['test7', 'ERC20'], { wait: 10000 })
},
'Test Github Import - from other branch #group2': function (browser: NightwatchBrowser) {
'Test GitHub Import - from other branch #group2': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.5.0+commit.1d4f565a.js') // switch back to 0.5.0 : release-v2.3.0 branch is not solidity 0.6 compliant
.addFile('Untitled5.sol', sources[4]['Untitled5.sol'])
@ -46,7 +46,7 @@ module.exports = {
.verifyContracts(['test8', 'ERC20', 'SafeMath'], { wait: 10000 })
},
'Test Github Import - no branch specified #group2': function (browser: NightwatchBrowser) {
'Test GitHub Import - no branch specified #group2': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.8.0+commit.c7dfd78e.js') // open-zeppelin moved to pragma ^0.8.0 (master branch)
.clickLaunchIcon('filePanel')
@ -56,7 +56,7 @@ module.exports = {
.verifyContracts(['test10', 'ERC20'], { wait: 10000 })
},
'Test Github Import - raw URL #group4': function (browser: NightwatchBrowser) {
'Test GitHub Import - raw URL #group4': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"')
@ -65,7 +65,7 @@ module.exports = {
.verifyContracts(['test11', 'ERC20'], { wait: 10000 })
},
'Test switch to a github import from a solidity warning #group3': function (browser: NightwatchBrowser) {
'Test switch to a GitHub import from a solidity warning #group3': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js')
.clickLaunchIcon('filePanel')

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save