Merge branch 'master' into ci-build

pull/5370/head
Aniket 4 years ago committed by GitHub
commit 39abf929bc
  1. 3
      apps/remix-ide/.babelrc
  2. 38
      apps/remix-ide/.circleci/config.yml
  3. 1
      apps/remix-ide/.dockerignore
  4. 4
      apps/remix-ide/.gitignore
  5. 6
      apps/remix-ide/Dockerfile
  6. 28
      apps/remix-ide/Dockerfile.dev
  7. 37
      apps/remix-ide/README.md
  8. BIN
      apps/remix-ide/assets/img/deployAndRun.webp
  9. BIN
      apps/remix-ide/assets/img/fileManager.webp
  10. BIN
      apps/remix-ide/assets/img/ipfs.webp
  11. BIN
      apps/remix-ide/assets/img/localPlugin.webp
  12. BIN
      apps/remix-ide/assets/img/pluginManager.webp
  13. BIN
      apps/remix-ide/assets/img/remixLogo.webp
  14. BIN
      apps/remix-ide/assets/img/settings.webp
  15. BIN
      apps/remix-ide/assets/img/solidity.webp
  16. BIN
      apps/remix-ide/assets/img/staticAnalysis.webp
  17. BIN
      apps/remix-ide/assets/img/swarm.webp
  18. BIN
      apps/remix-ide/assets/img/unitTesting.webp
  19. 181
      apps/remix-ide/assets/js/editor/darkTheme.js
  20. 15
      apps/remix-ide/build.yaml
  21. 6
      apps/remix-ide/ci/browser_tests.sh
  22. 4
      apps/remix-ide/ci/browser_tests_chrome.sh
  23. 4
      apps/remix-ide/ci/browser_tests_firefox.sh
  24. 16
      apps/remix-ide/ci/build_and_publish_docker_images.sh
  25. 18
      apps/remix-ide/docker-compose.yaml
  26. 2
      apps/remix-ide/docs/plugin_manager.md
  27. 12
      apps/remix-ide/docs/run.md
  28. 40
      apps/remix-ide/nginx.conf
  29. 17984
      apps/remix-ide/package-lock.json
  30. 26
      apps/remix-ide/package.json
  31. 53
      apps/remix-ide/src/app.js
  32. 4
      apps/remix-ide/src/app/compiler/compiler-abstract.js
  33. 28
      apps/remix-ide/src/app/compiler/compiler-artefacts.js
  34. 6
      apps/remix-ide/src/app/compiler/compiler-helpers.js
  35. 6
      apps/remix-ide/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js
  36. 54
      apps/remix-ide/src/app/compiler/compiler-utils.js
  37. 2
      apps/remix-ide/src/app/components/local-plugin.js
  38. 2
      apps/remix-ide/src/app/components/plugin-manager-component.js
  39. 17
      apps/remix-ide/src/app/editor/editor.js
  40. 4
      apps/remix-ide/src/app/editor/example-contracts.js
  41. 31
      apps/remix-ide/src/app/files/file-explorer.js
  42. 51
      apps/remix-ide/src/app/files/fileManager.js
  43. 23
      apps/remix-ide/src/app/files/fileProvider.js
  44. 217
      apps/remix-ide/src/app/files/remixDProvider.js
  45. 52
      apps/remix-ide/src/app/files/remixd-handle.js
  46. 6
      apps/remix-ide/src/app/panels/file-panel.js
  47. 11
      apps/remix-ide/src/app/panels/main-view.js
  48. 35
      apps/remix-ide/src/app/panels/styles/terminal-styles.js
  49. 13
      apps/remix-ide/src/app/panels/terminal.js
  50. 2
      apps/remix-ide/src/app/tabs/analysis-tab.js
  51. 63
      apps/remix-ide/src/app/tabs/compile-tab.js
  52. 2
      apps/remix-ide/src/app/tabs/compileTab/compileTab.js
  53. 95
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  54. 14
      apps/remix-ide/src/app/tabs/debugger-tab.js
  55. 93
      apps/remix-ide/src/app/tabs/debugger/debuggerUI.js
  56. 4
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/ButtonNavigator.js
  57. 2
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/Slider.js
  58. 3
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/StepManager.js
  59. 16
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/TxBrowser.js
  60. 48
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/VmDebugger.js
  61. 8
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/styles/basicStyles.js
  62. 13
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/CodeListView.js
  63. 43
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/DropdownPanel.js
  64. 17
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/FunctionPanel.js
  65. 3
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/SolidityState.js
  66. 2
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/StepDetail.js
  67. 2
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/utils/SolidityTypeFormatter.js
  68. 3
      apps/remix-ide/src/app/tabs/runTab/model/dropdownlogic.js
  69. 4
      apps/remix-ide/src/app/tabs/settings-tab.js
  70. 2
      apps/remix-ide/src/app/tabs/staticanalysis/staticAnalysisView.js
  71. 77
      apps/remix-ide/src/app/tabs/test-tab.js
  72. 53
      apps/remix-ide/src/app/tabs/testTab/testTab.js
  73. 10
      apps/remix-ide/src/app/tabs/theme-module.js
  74. 2
      apps/remix-ide/src/app/udapp/make-udapp.js
  75. 2
      apps/remix-ide/src/app/udapp/run-tab.js
  76. 32
      apps/remix-ide/src/app/ui/TreeView.js
  77. 4
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  78. 10
      apps/remix-ide/src/framingService.js
  79. 1
      apps/remix-ide/src/lib/helper.js
  80. 8
      apps/remix-ide/src/lib/offsetToLineColumnConverter.js
  81. 3
      apps/remix-ide/test-browser/commands/clickLaunchIcon.js
  82. 25
      apps/remix-ide/test-browser/commands/getInstalledPlugins.js
  83. 3
      apps/remix-ide/test-browser/commands/goToVMTraceStep.js
  84. 28
      apps/remix-ide/test-browser/commands/noWorkerErrorFor.js
  85. 2
      apps/remix-ide/test-browser/commands/verifyCallReturnValue.js
  86. 14
      apps/remix-ide/test-browser/commands/verifyContracts.js
  87. 27
      apps/remix-ide/test-browser/helpers/init.js
  88. 100
      apps/remix-ide/test-browser/tests/compiler_api.test.js
  89. 18
      apps/remix-ide/test-browser/tests/debugger.test.js
  90. 2
      apps/remix-ide/test-browser/tests/defaultLayout.test.js
  91. 2
      apps/remix-ide/test-browser/tests/editor.test.js
  92. 6
      apps/remix-ide/test-browser/tests/fileExplorer.test.js
  93. 190
      apps/remix-ide/test-browser/tests/fileManager_api.test.js
  94. 21
      apps/remix-ide/test-browser/tests/generalSettings.test.js
  95. 2
      apps/remix-ide/test-browser/tests/gist.test.js
  96. 4
      apps/remix-ide/test-browser/tests/libraryDeployment.test.js
  97. 12
      apps/remix-ide/test-browser/tests/pluginManager.test.js
  98. 9
      apps/remix-ide/test-browser/tests/runAndDeploy.js
  99. 65
      apps/remix-ide/test-browser/tests/solidityUnittests.test.js
  100. 6
      apps/remix-ide/test-browser/tests/specialFunctions.test.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env"]
}

@ -7,7 +7,7 @@ jobs:
remix-ide-chrome: remix-ide-chrome:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/node:10.18.0-browsers - image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images
@ -41,7 +41,7 @@ jobs:
remix-ide-firefox: remix-ide-firefox:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/node:10.18.0-browsers - image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images
@ -81,7 +81,7 @@ jobs:
remix-ide-run-deploy: remix-ide-run-deploy:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/node:10.18.0-browsers - image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images
@ -113,7 +113,7 @@ jobs:
deploy-remix-live: deploy-remix-live:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/node:10.18.0-browsers - image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images
@ -139,10 +139,33 @@ jobs:
- store_artifacts: - store_artifacts:
path: ./reports/screenshots path: ./reports/screenshots
publish-docker:
docker:
# specify the version you desire here
- image: circleci/node:10.19.0-buster
# 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"
- FILES_TO_PACKAGE: "assets background.js build icon.png index.html manifest.json README.md soljson.js package.json"
working_directory: ~/remix-ide
steps:
- checkout
- setup_remote_docker
- run: npm install
- run: npm run build
- run: ./ci/build_and_publish_docker_images.sh
deploy-remix-alpha: deploy-remix-alpha:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/node:10.18.0-browsers - image: circleci/node:9.11.2-browsers
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images
@ -175,6 +198,11 @@ workflows:
- remix-ide-chrome - remix-ide-chrome
- remix-ide-firefox - remix-ide-firefox
- remix-ide-run-deploy - remix-ide-run-deploy
- publish-docker:
requires:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-run-deploy
- deploy-remix-live: - deploy-remix-live:
requires: requires:
- remix-ide-chrome - remix-ide-chrome

@ -1,3 +1,6 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
.idea .idea
.vscode .vscode
build build
@ -12,3 +15,4 @@ remix
contracts contracts
TODO TODO
.tern-port .tern-port
temp_publish_docker

@ -0,0 +1,6 @@
FROM nginx:alpine
WORKDIR /
COPY ./temp_publish_docker/ /usr/share/nginx/html/
EXPOSE 80

@ -0,0 +1,28 @@
# This dockerfile is to build each branch seperately (for dev purpouses)
FROM node:10
# Create Remix user, don't use root!
# RUN yes | adduser --disabled-password remix && mkdir /app
# USER remix
# #Now do remix stuff
# USER remix
WORKDIR /home/remix
COPY ./ ./
RUN npm ci
RUN npm run build
FROM nginx:alpine
WORKDIR /
COPY --from=0 /home/remix/build/ /usr/share/nginx/html/build/
COPY --from=0 /home/remix/index.html /usr/share/nginx/html/index.html
COPY --from=0 /home/remix/nginx.conf /etc/nginx/nginx.conf
COPY --from=0 /home/remix/assets/ /usr/share/nginx/html/assets/
COPY --from=0 /home/remix/icon.png /usr/share/nginx/html/icon.png
COPY --from=0 /home/remix/background.js /usr/share/nginx/html/background.js
COPY --from=0 /home/remix/soljson.js /usr/share/nginx/html/soljson.js
COPY --from=0 /home/remix/package.json /usr/share/nginx/html/package.json
EXPOSE 80

@ -45,6 +45,43 @@ npm run setupremix # only if you plan to link remix and remix-ide repositories
npm start npm start
``` ```
## Docker:
Prerequisites:
* Docker (https://docs.docker.com/desktop/)
* Docker-compose (https://docs.docker.com/compose/install/)
### Run with docker
If you want to run latest changes that are merged into master branch then run:
```
docker pull remixproject/remix-ide:latest
docker run -p 8080:80 remixproject/remix-ide:latest
```
If you want to run latest remix-live release run.
```
docker pull remixproject/remix-ide:remix_live
docker run -p 8080:80 remixproject/remix-ide:remix_live
```
### Run with docker-compose:
To run locally without building you only need docker-compose.yaml file and you can run:
```
docker-compose pull
docker-compose up -d
```
Then go to http://localhost:8080 and you can use you Remix instance.
To fetch docker-compose file without cloning this repo run:
```
curl https://raw.githubusercontent.com/ethereum/remix-ide/master/docker-compose.yaml > docker-compose.yaml
```
## DEVELOPING: ## DEVELOPING:
Run `npm start` and open `http://127.0.0.1:4200` in your browser. Run `npm start` and open `http://127.0.0.1:4200` in your browser.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

@ -0,0 +1,181 @@
/* eslint-disable */
ace.define("ace/theme/remixDark",["require","exports","module","ace/lib/dom"], function(acequire, exports, module) {
exports.isDark = true;
exports.cssClass = "ace-remixDark";
exports.cssText = ".ace-remixDark .ace_gutter {\
background: #2a2c3f;\
color: #8789a1;\
border-right: 1px solid #282828;\
}\
.ace-remixDark .ace_gutter-cell.ace_warning {\
background-image: none;\
background: #FC0;\
border-left: none;\
padding-left: 0;\
color: #000;\
}\
.ace-remixDark .ace_gutter-cell.ace_error {\
background-position: -6px center;\
background-image: none;\
background: #F10;\
border-left: none;\
padding-left: 0;\
color: #000;\
}\
.ace-remixDark .ace_print-margin {\
border-left: 1px solid #555;\
right: 0;\
background: #1D1D1D;\
}\
.ace-remixDark {\
background-color: #222336;\
color: #a2a3bd;\
}\
.ace-remixDark .ace_cursor {\
border-left: 2px solid #FFFFFF;\
}\
.ace-remixDark .ace_cursor.ace_overwrite {\
border-left: 0px;\
border-bottom: 1px solid #FFFFFF;\
}\
.ace-remixDark .ace_marker-layer .ace_selection {\
background: #494836;\
}\
.ace-remixDark .ace_marker-layer .ace_step {\
background: rgb(198, 219, 174);\
}\
.ace-remixDark .ace_marker-layer .ace_bracket {\
margin: -1px 0 0 -1px;\
border: 1px solid #FCE94F;\
}\
.ace-remixDark .ace_marker-layer .ace_active-line {\
background: #363950;\
}\
.ace-remixDark .ace_gutter-active-line {\
background-color: #363950;\
}\
.ace-remixDark .ace_invisible {\
color: #404040;\
}\
.ace-remixDark .ace_rparen {\
color: #d4d7ed;\
}\
.ace-remixDark .ace_lparen {\
color: #d4d7ed;\
}\
.ace-remixDark .ace_keyword {\
color:#ffa76d;\
}\
.ace-remixDark .ace_keyword.ace_operator {\
color:#eceeff;\
}\
.ace-remixDark .ace_constant {\
color:#1EDAFB;\
}\
.ace-remixDark .ace_constant.ace_language {\
color:#FDC251;\
}\
.ace-remixDark .ace_constant.ace_library {\
color:#8DFF0A;\
}\
.ace-remixDark .ace_constant.ace_numeric {\
color:#eceeff;\
}\
.ace-remixDark .ace_invalid {\
color:#FFFFFF;\
background-color:#990000;\
}\
.ace-remixDark .ace_invalid.ace_deprecated {\
color:#FFFFFF;\
background-color:#990000;\
}\
.ace-remixDark .ace_support {\
color: #999;\
}\
.ace-remixDark .ace_support.ace_function {\
color:#3fe2a7;\
}\
.ace-remixDark .ace_function {\
color:#3fe2a7;\
}\
.ace-remixDark .ace_string {\
color:#eceeff;\
}\
.ace-remixDark .ace_comment {\
color:#a7a7a7;\
font-style:italic;\
padding-bottom: 0px;\
}\
.ace-remixDark .ace_type {\
color:#75ceef;\
}]\
.ace-remixDark .ace_visibility (\
color:#f7d777;\
)\
.ace-remixDark .ace_identifier {\
color:#bec1dd;\
}\
.ace-remixDark .ace_modifier {\
color:#efff2f;\
}\
.ace-remixDark .ace-boolean {\
color:#ff86ac;\
}\
.ace-remixDark .ace_statemutability {\
color:#FFCC00;\
}\
.ace-remixDark .ace_variable {\
color:#e0bb83;\
}\
.ace-remixDark .ace_meta.ace_tag {\
color:#BE53E6;\
}\
.ace-remixDark .ace_entity.ace_other.ace_attribute-name {\
color:#4aa8fd;\
}\
.ace-remixDark .ace_markup.ace_underline {\
text-decoration: underline;\
}\
.ace-remixDark .ace_fold-widget {\
text-align: center;\
}\
.ace-remixDark .ace_fold-widget:hover {\
color: #777;\
}\
.ace-remixDark .ace_fold-widget.ace_start,\
.ace-remixDark .ace_fold-widget.ace_end,\
.ace-remixDark .ace_fold-widget.ace_closed{\
background: none;\
border: none;\
box-shadow: none;\
}\
.ace-remixDark .ace_fold-widget.ace_start:after {\
content: '▾'\
}\
.ace-remixDark .ace_fold-widget.ace_end:after {\
content: '▴'\
}\
.ace-remixDark .ace_fold-widget.ace_closed:after {\
content: '‣'\
}\
.ace-remixDark .ace_indent-guide {\
border-right:1px dotted #333;\
margin-right:-1px;\
}\
.ace-remixDark .ace_fold { \
background: #222; \
border-radius: 3px; \
color: #7AF; \
border: none; \
}\
.ace-remixDark .ace_fold:hover {\
background: #CCC; \
color: #000;\
}\
";
var dom = acequire("../lib/dom");
dom.importCssString(exports.cssText, exports.cssClass);
});

@ -0,0 +1,15 @@
version: "3.7"
x-project-base:
&project-base
restart: always
networks:
- remixide
networks:
remixide:
services:
remixide:
build:
context: .
dockerfile: Dockerfile

@ -21,8 +21,10 @@ setupRemixd
sleep 5 sleep 5
npm run nightwatch_parallel || TEST_EXITCODE=1 TESTFILES=$(circleci tests glob "./test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings)
npm run nightwatch_local_runAndDeploy || TEST_EXITCODE=1 for TESTFILE in $TESTFILES; do
./node_modules/.bin/nightwatch --config nightwatch.js --env chrome $TESTFILE || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE" echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ] if [ "$TEST_EXITCODE" -eq 1 ]

@ -22,7 +22,9 @@ setupRemixd
sleep 5 sleep 5
TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings) TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings)
npm run nightwatch_local_chrome $TESTFILES for TESTFILE in $TESTFILES; do
./node_modules/.bin/nightwatch --config nightwatch.js --env chrome $TESTFILE || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE" echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ] if [ "$TEST_EXITCODE" -eq 1 ]

@ -22,7 +22,9 @@ setupRemixd
sleep 5 sleep 5
TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings) TESTFILES=$(circleci tests glob "./apps/remix-ide/test-browser/tests/**/*.test.js" | circleci tests split --split-by=timings)
npm run nightwatch_local_firefox $TESTFILES for TESTFILE in $TESTFILES; do
./node_modules/.bin/nightwatch --config nightwatch.js --env firefox $TESTFILE || TEST_EXITCODE=1
done
echo "$TEST_EXITCODE" echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ] if [ "$TEST_EXITCODE" -eq 1 ]

@ -0,0 +1,16 @@
#!/bin/bash
set -e
export TAG="$CIRCLE_BRANCH"
if [ "$CIRCLE_BRANCH" == "master" ]; then
export TAG="latest";
fi
rm -rf temp_publish_docker
mkdir temp_publish_docker
cp -r $FILES_TO_PACKAGE temp_publish_docker
docker login --username $DOCKER_USER --password $DOCKER_PASS
docker-compose -f docker-compose.yaml -f build.yaml build
docker push remixproject/remix-ide:$TAG

@ -0,0 +1,18 @@
version: "3.7"
x-project-base:
&project-base
restart: always
networks:
- remixide
networks:
remixide:
services:
remixide:
<<: *project-base
image: remixproject/remix-ide:$TAG
container_name: remixide-${TAG}
ports:
- 8080:80
- 65520:65520

@ -4,7 +4,7 @@ Plugin Manager
## Everything is a PLUGIN in Remix ## Everything is a PLUGIN in Remix
In order to integrate new tools made by us and by ...you into Remix, we've now made everything a plugin. In order to integrate new tools made by us and by ...you into Remix, we've now made everything a plugin.
This architcture will also allow Remix or just parts of Remix to be integrated into other projects (your's for example). This architecture will also allow Remix or just parts of Remix to be integrated into other projects (your's for example).
This means that you only load the functionality you need. This means that you only load the functionality you need.

@ -108,16 +108,16 @@ Using the Recorder
The Recorder is a tool used to save a bunch of transactions in a JSON file and The Recorder is a tool used to save a bunch of transactions in a JSON file and
rerun them later either in the same environment or in another. rerun them later either in the same environment or in another.
Saving to the JSON file ( by default its called senario.json) allows one to easily check the transaction list, tweak input parameters, change linked library, etc... Saving to the JSON file ( by default its called scenario.json) allows one to easily check the transaction list, tweak input parameters, change linked library, etc...
There are many use cases for the recorder. There are many use cases for the recorder.
For instance: For instance:
- After having coded and tested contracts in a constrained - After having coded and tested contracts in a constrained
environment (like the JavaScript VM), you could then change the environment and redeploy it to a more realistic environment like a test net with an **injected web3** or to a Geth node. By using the generated **senario.json** file, you will be using all the same settings that you used in the Javascript VM. And this mean that you won't need to click the interface 100 times or whatever to get the state that you achieved originally. So the recorder could be a tool to protect your sanity. environment (like the JavaScript VM), you could then change the environment and redeploy it to a more realistic environment like a test net with an **injected web3** or to a Geth node. By using the generated **scenario.json** file, you will be using all the same settings that you used in the Javascript VM. And this mean that you won't need to click the interface 100 times or whatever to get the state that you achieved originally. So the recorder could be a tool to protect your sanity.
You can also change the settings in the senario.json file to customize the playback. You can also change the settings in the scenario.json file to customize the playback.
- Deploying contract does often require more than creating one - Deploying contract does often require more than creating one
transaction and so the recorder will automate this deployment. transaction and so the recorder will automate this deployment.
@ -127,10 +127,10 @@ For instance:
![](images/a-runtab-recorder.png) ![](images/a-runtab-recorder.png)
### senario.json ### scenario.json
To create this file in the recorder, you first of course need to have run some transactions. In the image above - it has a `0` next to **Transactions Recorded**. So this isn't the right moment to save transactions because - well because there aren't any. Each time you make a transaction, that number will increment. Then when you are ready, click the floppy disk icon and the senario.json file will be created. To create this file in the recorder, you first of course need to have run some transactions. In the image above - it has a `0` next to **Transactions Recorded**. So this isn't the right moment to save transactions because - well because there aren't any. Each time you make a transaction, that number will increment. Then when you are ready, click the floppy disk icon and the scenario.json file will be created.
The JSON file below is an example of the senario.json file. The JSON file below is an example of the scenario.json file.
In it, 3 transactions are executed: In it, 3 transactions are executed:

@ -0,0 +1,40 @@
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 300;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
index index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ /index.html;
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "remix-ide", "name": "remix-ide",
"version": "v0.10.1", "version": "v0.10.2",
"description": "Extendable Web IDE for Ethereum", "description": "Extendable Web IDE for Ethereum",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.4.5", "@babel/core": "^7.4.5",
@ -49,20 +49,20 @@
"minixhr": "^3.2.2", "minixhr": "^3.2.2",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"nanohtml": "^1.6.3", "nanohtml": "^1.6.3",
"nightwatch": "^1.3.5", "nightwatch": "^0.9.20",
"notify-error": "^1.2.0", "notify-error": "^1.2.0",
"npm-link-local": "^1.1.0", "npm-link-local": "^1.1.0",
"npm-merge-driver": "^2.3.5", "npm-merge-driver": "^2.3.5",
"npm-run-all": "^4.0.2", "npm-run-all": "^4.0.2",
"onchange": "^3.2.1", "onchange": "^3.2.1",
"remix-analyzer": "0.5.2", "remix-analyzer": "0.5.3",
"remix-debug": "0.4.4", "remix-debug": "0.4.5",
"remix-lib": "0.4.29", "remix-lib": "0.4.30",
"remix-simulator": "0.1.9-beta.5", "remix-simulator": "0.1.9-beta.6",
"remix-solidity": "0.3.30", "remix-solidity": "0.3.31",
"remix-tabs": "1.0.48", "remix-tabs": "1.0.48",
"remix-tests": "0.1.33", "remix-tests": "0.1.34",
"remixd": "0.1.8-alpha.10", "remixd": "0.2.3-alpha.1",
"request": "^2.83.0", "request": "^2.83.0",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
"selenium-standalone": "^6.17.0", "selenium-standalone": "^6.17.0",
@ -79,9 +79,8 @@
"yo-yoify": "^3.7.3" "yo-yoify": "^3.7.3"
}, },
"dependencies": { "dependencies": {
"@remixproject/engine": "^0.2.3", "@remixproject/engine": "^0.2.5",
"http-server": "^0.11.1", "http-server": "^0.11.1",
"remixd": "0.1.8-alpha.10",
"standard": "^8.5.0" "standard": "^8.5.0"
}, },
"repository": { "repository": {
@ -166,6 +165,7 @@
"nightwatch_local_firefox": "nightwatch --config nightwatch.js --env firefox", "nightwatch_local_firefox": "nightwatch --config nightwatch.js --env firefox",
"nightwatch_local_chrome": "nightwatch --config nightwatch.js --env chrome", "nightwatch_local_chrome": "nightwatch --config nightwatch.js --env chrome",
"nightwatch_local_ballot": "nightwatch ./test-browser/tests/ballot.test.js --config nightwatch.js --env chrome ", "nightwatch_local_ballot": "nightwatch ./test-browser/tests/ballot.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_usingWorker": "nightwatch ./test-browser/tests/usingWebWorker.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_libraryDeployment": "nightwatch ./test-browser/tests/libraryDeployment.test.js --config nightwatch.js --env chrome ", "nightwatch_local_libraryDeployment": "nightwatch ./test-browser/tests/libraryDeployment.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_solidityImport": "nightwatch ./test-browser/tests/solidityImport.test.js --config nightwatch.js --env chrome ", "nightwatch_local_solidityImport": "nightwatch ./test-browser/tests/solidityImport.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_recorder": "nightwatch ./test-browser/tests/recorder.test.js --config nightwatch.js --env chrome ", "nightwatch_local_recorder": "nightwatch ./test-browser/tests/recorder.test.js --config nightwatch.js --env chrome ",
@ -185,7 +185,9 @@
"nightwatch_local_fileExplorer": "nightwatch ./test-browser/tests/fileExplorer.test.js --config nightwatch.js --env chrome ", "nightwatch_local_fileExplorer": "nightwatch ./test-browser/tests/fileExplorer.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_debugger": "nightwatch ./test-browser/tests/debugger.test.js --config nightwatch.js --env chrome ", "nightwatch_local_debugger": "nightwatch ./test-browser/tests/debugger.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_editor": "nightwatch ./test-browser/tests/editor.test.js --config nightwatch.js --env chrome ", "nightwatch_local_editor": "nightwatch ./test-browser/tests/editor.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_compiler": "nightwatch ./test-browser/tests/compiler_api.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_txListener": "nightwatch ./test-browser/tests/txListener.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_fileManager": "nightwatch ./test-browser/tests/fileManager_api.test.js --config nightwatch.js --env chrome ",
"nightwatch_local_runAndDeploy": "nightwatch ./test-browser/tests/runAndDeploy.js --config nightwatch.js --env chrome-runAndDeploy ", "nightwatch_local_runAndDeploy": "nightwatch ./test-browser/tests/runAndDeploy.js --config nightwatch.js --env chrome-runAndDeploy ",
"onchange": "onchange build/app.js -- npm-run-all lint", "onchange": "onchange build/app.js -- npm-run-all lint",
"prepublish": "mkdirp build; npm-run-all -ls downloadsolc_root build", "prepublish": "mkdirp build; npm-run-all -ls downloadsolc_root build",

@ -5,7 +5,6 @@ var csjs = require('csjs-inject')
var yo = require('yo-yo') var yo = require('yo-yo')
var remixLib = require('@remix-project/remix-lib') var remixLib = require('@remix-project/remix-lib')
var registry = require('./global/registry') var registry = require('./global/registry')
var Remixd = require('./lib/remixd')
var loadFileFromParent = require('./loadFilesFromParent') var loadFileFromParent = require('./loadFilesFromParent')
var { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConverter') var { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConverter')
var QueryParams = require('./lib/query-params') var QueryParams = require('./lib/query-params')
@ -122,6 +121,7 @@ var css = csjs`
class App { class App {
constructor (api = {}, events = {}, opts = {}) { constructor (api = {}, events = {}, opts = {}) {
var self = this var self = this
self.appManager = new RemixAppManager({})
self._components = {} self._components = {}
self._view = {} self._view = {}
self._view.splashScreen = yo` self._view.splashScreen = yo`
@ -135,7 +135,7 @@ class App {
document.body.appendChild(self._view.splashScreen) document.body.appendChild(self._view.splashScreen)
// setup storage // setup storage
var configStorage = new Storage('config-v0.8:') const configStorage = new Storage('config-v0.8:')
// load app config // load app config
const config = new Config(configStorage) const config = new Config(configStorage)
@ -145,14 +145,7 @@ class App {
self._components.filesProviders = {} self._components.filesProviders = {}
self._components.filesProviders['browser'] = new FileProvider('browser') self._components.filesProviders['browser'] = new FileProvider('browser')
registry.put({api: self._components.filesProviders['browser'], name: 'fileproviders/browser'}) registry.put({api: self._components.filesProviders['browser'], name: 'fileproviders/browser'})
self._components.filesProviders['localhost'] = new RemixDProvider(self.appManager)
var remixd = new Remixd(65520)
registry.put({api: remixd, name: 'remixd'})
remixd.event.register('system', (message) => {
if (message.error) toolTip(message.error)
})
self._components.filesProviders['localhost'] = new RemixDProvider(remixd)
registry.put({api: self._components.filesProviders['localhost'], name: 'fileproviders/localhost'}) registry.put({api: self._components.filesProviders['localhost'], name: 'fileproviders/localhost'})
registry.put({api: self._components.filesProviders, name: 'fileproviders'}) registry.put({api: self._components.filesProviders, name: 'fileproviders'})
@ -235,9 +228,15 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
} }
// APP_MANAGER // APP_MANAGER
const appManager = new RemixAppManager({}) const appManager = self.appManager
const workspace = appManager.pluginLoader.get() const pluginLoader = appManager.pluginLoader
const workspace = pluginLoader.get()
const engine = new Engine(appManager) const engine = new Engine(appManager)
engine.setPluginOption = ({ name, kind }) => {
if (kind === 'provider') return {queueTimeout: 60000 * 4}
if (name === 'LearnEth') return {queueTimeout: 60000}
return {queueTimeout: 10000}
}
await engine.onload() await engine.onload()
// SERVICES // SERVICES
@ -258,7 +257,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile()) editor.event.register('requiringToSaveCurrentfile', () => fileManager.saveCurrentFile())
// ----------------- fileManager servive ---------------------------- // ----------------- fileManager servive ----------------------------
const fileManager = new FileManager(editor) const fileManager = new FileManager(editor, appManager)
registry.put({api: fileManager, name: 'filemanager'}) registry.put({api: fileManager, name: 'filemanager'})
const blockchain = new Blockchain(registry.get('config').api) const blockchain = new Blockchain(registry.get('config').api)
@ -397,22 +396,38 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
await appManager.activatePlugin(['contentImport', 'theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter']) await appManager.activatePlugin(['contentImport', 'theme', 'editor', 'fileManager', 'compilerMetadata', 'compilerArtefacts', 'network', 'web3Provider', 'offsetToLineColumnConverter'])
await appManager.activatePlugin(['mainPanel', 'menuicons']) await appManager.activatePlugin(['mainPanel', 'menuicons'])
await appManager.activatePlugin(['home', 'sidePanel', 'hiddenPanel', 'pluginManager', 'fileExplorers', 'settings', 'contextualListener', 'scriptRunner', 'terminal', 'fetchAndCompile']) await appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await appManager.activatePlugin(['home', 'hiddenPanel', 'pluginManager', 'fileExplorers', 'settings', 'contextualListener', 'scriptRunner', 'terminal', 'fetchAndCompile'])
const queryParams = new QueryParams()
const params = queryParams.get()
// Set workspace after initial activation // Set workspace after initial activation
if (Array.isArray(workspace)) await appManager.activatePlugin(workspace) if (Array.isArray(workspace)) {
try {
await appManager.activatePlugin(workspace)
} catch (e) {
console.error(e)
}
} else {
// activate solidity plugin
appManager.ensureActivated('solidity')
appManager.ensureActivated('udapp')
}
// Load and start the service who manager layout and frame // Load and start the service who manager layout and frame
const framingService = new FramingService(sidePanel, menuicons, mainview, this._components.resizeFeature) const framingService = new FramingService(sidePanel, menuicons, mainview, this._components.resizeFeature)
framingService.start() framingService.start(params)
// If plugins are loaded from the URL params, we focus on the last one.
if (pluginLoader.current === 'queryParams' && Array.isArray(workspace) && workspace.length > 0) menuicons.select(workspace[workspace.length - 1])
// get the file list from the parent iframe // get the file list from the parent iframe
loadFileFromParent(fileManager) loadFileFromParent(fileManager)
// get the file from gist // get the file from gist
const gistHandler = new GistHandler() const gistHandler = new GistHandler()
const queryParams = new QueryParams() const loadedFromGist = gistHandler.loadFromGist(params, fileManager)
const loadedFromGist = gistHandler.loadFromGist(queryParams.get(), fileManager)
if (!loadedFromGist) { if (!loadedFromGist) {
// insert example contracts if there are no files to show // insert example contracts if there are no files to show
self._components.filesProviders['browser'].resolveDirectory('/', (error, filesList) => { self._components.filesProviders['browser'].resolveDirectory('/', (error, filesList) => {
@ -428,4 +443,6 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
if (isElectron()) { if (isElectron()) {
appManager.activatePlugin('remixd') appManager.activatePlugin('remixd')
} }
if (params.embed) framingService.embed()
} }

@ -32,6 +32,10 @@ module.exports = class CompilerAbstract {
getSourceName (fileIndex) { getSourceName (fileIndex) {
if (this.data && this.data.sources) { if (this.data && this.data.sources) {
return Object.keys(this.data.sources)[fileIndex] return Object.keys(this.data.sources)[fileIndex]
} else if (Object.keys(this.source.sources).length === 1) {
// if we don't have ast, we return the only one filename present.
const sourcesArray = Object.keys(this.source.sources)
return sourcesArray[0]
} }
return null return null
} }

@ -14,26 +14,54 @@ module.exports = class CompilerArtefacts extends Plugin {
constructor () { constructor () {
super(profile) super(profile)
this.compilersArtefacts = {} this.compilersArtefacts = {}
this.compilersArtefactsPerFile = {}
} }
clear () { clear () {
this.compilersArtefacts = {} this.compilersArtefacts = {}
this.compilersArtefactsPerFile = {}
} }
onActivation () { onActivation () {
const saveCompilationPerFileResult = (file, source, languageVersion, data) => {
this.compilersArtefactsPerFile[file] = new CompilerAbstract(languageVersion, data, source)
}
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => { this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source) this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
}) })
this.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => { this.on('vyper', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source) this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
}) })
this.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => { this.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source) this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
})
this.on('yulp', 'compilationFinished', (file, source, languageVersion, data) => {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
saveCompilationPerFileResult(file, source, languageVersion, data)
}) })
} }
getAllContractDatas () {
const contractsData = {}
Object.keys(this.compilersArtefactsPerFile).map((targetFile) => {
const contracts = this.compilersArtefactsPerFile[targetFile].getContracts()
Object.keys(contracts).map((file) => { contractsData[file] = contracts[file] })
})
// making sure we save last compilation result in there
if (this.compilersArtefacts['__last']) {
const contracts = this.compilersArtefacts['__last'].getContracts()
Object.keys(contracts).map((file) => { contractsData[file] = contracts[file] })
}
return contractsData
}
addResolvedContract (address, compilerData) { addResolvedContract (address, compilerData) {
this.compilersArtefacts[address] = compilerData this.compilersArtefacts[address] = compilerData
} }

@ -1,5 +1,5 @@
'use strict' 'use strict'
import { canUseWorker } from './compiler-utils' import { canUseWorker, urlFromVersion } from './compiler-utils'
import { Compiler } from '@remix-project/remix-solidity' import { Compiler } from '@remix-project/remix-solidity'
import CompilerAbstract from './compiler-abstract' import CompilerAbstract from './compiler-abstract'
@ -9,9 +9,9 @@ export const compile = async (compilationTargets, settings) => {
const compiler = new Compiler(() => {}) const compiler = new Compiler(() => {})
compiler.set('evmVersion', settings.evmVersion) compiler.set('evmVersion', settings.evmVersion)
compiler.set('optimize', settings.optimize) compiler.set('optimize', settings.optimize)
compiler.loadVersion(canUseWorker(settings.version), settings.compilerUrl) compiler.set('language', settings.language)
compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version))
compiler.event.register('compilationFinished', (success, compilationData, source) => { compiler.event.register('compilationFinished', (success, compilationData, source) => {
if (!success) return reject(compilationData)
resolve(new CompilerAbstract(settings.version, compilationData, source)) resolve(new CompilerAbstract(settings.version, compilationData, source))
}) })
compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, '')) compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, ''))

@ -1,7 +1,6 @@
const ethutil = require('ethereumjs-util') const ethutil = require('ethereumjs-util')
import * as packageJson from '../../../package.json' import * as packageJson from '../../../package.json'
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { urlFromVersion } from './compiler-utils'
import { compile } from './compiler-helpers' import { compile } from './compiler-helpers'
import globalRegistry from '../../global/registry' import globalRegistry from '../../global/registry'
@ -106,10 +105,9 @@ export default class FetchAndCompile extends Plugin {
// compile // compile
const settings = { const settings = {
version: data.metadata.compiler.version, version: data.metadata.compiler.version,
languageName: data.metadata.language, language: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion, evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled, optimize: data.metadata.settings.optimizer.enabled
compilerUrl: urlFromVersion(data.metadata.compiler.version)
} }
try { try {
setTimeout(_ => this.emit('compiling', settings), 0) setTimeout(_ => this.emit('compiling', settings), 0)

@ -1,48 +1,44 @@
const semver = require('semver') const semver = require('semver')
const minixhr = require('minixhr')
/* global Worker */ /* global Worker */
export const baseUrl = 'https://solc-bin.ethereum.org/bin' export const baseURLBin = 'https://solc-bin.ethereum.org/bin'
export const baseURLWasm = 'https://solc-bin.ethereum.org/wasm'
export const pathToURL = {}
/**
* Retrieves the URL of the given compiler version
* @param version is the version of compiler with or without 'soljson-v' prefix and .js postfix
*/
export function urlFromVersion (version) { export function urlFromVersion (version) {
return `${baseUrl}/soljson-v${version}.js` if (!version.startsWith('soljson-v')) version = 'soljson-v' + version
if (!version.endsWith('.js')) version = version + '.js'
return `${pathToURL[version]}/${version}`
} }
/** /**
* Checks if the worker can be used to load a compiler. * Checks if the worker can be used to load a compiler.
* checks a compiler whitelist, browser support and OS. * checks a compiler whitelist, browser support and OS.
*/ */
export function canUseWorker (selectedVersion) { export function canUseWorker (selectedVersion) {
// Following restrictions should be deleted when Solidity will release fixed versions of compilers.
// See https://github.com/ethereum/remix-ide/issues/2461
const isChrome = !!window.chrome
const os = retrieveOS()
// define a whitelist for Linux
const linuxWL = ['0.4.26', '0.5.3', '0.5.4', '0.5.5']
const version = semver.coerce(selectedVersion) const version = semver.coerce(selectedVersion)
// defining whitelist for chrome const isNightly = selectedVersion.includes('nightly')
let isFromWhiteList = false return browserSupportWorker() && (
switch (os) { semver.gt(version, '0.6.3') ||
case 'Windows': semver.gt(version, '0.3.6') && !isNightly
isFromWhiteList = semver.gt(version, '0.5.2') || version === '0.4.26' )
break
case 'Linux':
isFromWhiteList = semver.gt(version, '0.5.13') || linuxWL.includes(version)
break
default :
isFromWhiteList = true
}
return browserSupportWorker() && (!isChrome || (isChrome && isFromWhiteList))
} }
function browserSupportWorker () { function browserSupportWorker () {
return document.location.protocol !== 'file:' && Worker !== undefined return document.location.protocol !== 'file:' && Worker !== undefined
} }
function retrieveOS () { // returns a promise for minixhr
let osName = 'Unknown OS' export function promisedMiniXhr (url) {
if (navigator.platform.indexOf('Win') !== -1) { return new Promise((resolve, reject) => {
osName = 'Windows' minixhr(url, (json, event) => {
} else if (navigator.platform.indexOf('Linux') !== -1) { resolve({ json, event })
osName = 'Linux' })
} })
return osName
} }

@ -37,7 +37,7 @@ module.exports = class LocalPlugin {
*/ */
create () { create () {
const profile = { const profile = {
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMjYyIDEwNzVxLTM3IDEyMS0xMzggMTk1dC0yMjggNzQtMjI4LTc0LTEzOC0xOTVxLTgtMjUgNC00OC41dDM4LTMxLjVxMjUtOCA0OC41IDR0MzEuNSAzOHEyNSA4MCA5Mi41IDEyOS41dDE1MS41IDQ5LjUgMTUxLjUtNDkuNSA5Mi41LTEyOS41cTgtMjYgMzItMzh0NDktNCAzNyAzMS41IDQgNDguNXptLTQ5NC00MzVxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptNTEyIDBxMCA1My0zNy41IDkwLjV0LTkwLjUgMzcuNS05MC41LTM3LjUtMzcuNS05MC41IDM3LjUtOTAuNSA5MC41LTM3LjUgOTAuNSAzNy41IDM3LjUgOTAuNXptMjU2IDI1NnEwLTEzMC01MS0yNDguNXQtMTM2LjUtMjA0LTIwNC0xMzYuNS0yNDguNS01MS0yNDguNSA1MS0yMDQgMTM2LjUtMTM2LjUgMjA0LTUxIDI0OC41IDUxIDI0OC41IDEzNi41IDIwNCAyMDQgMTM2LjUgMjQ4LjUgNTEgMjQ4LjUtNTEgMjA0LTEzNi41IDEzNi41LTIwNCA1MS0yNDguNXptMTI4IDBxMCAyMDktMTAzIDM4NS41dC0yNzkuNSAyNzkuNS0zODUuNSAxMDMtMzg1LjUtMTAzLTI3OS41LTI3OS41LTEwMy0zODUuNSAxMDMtMzg1LjUgMjc5LjUtMjc5LjUgMzg1LjUtMTAzIDM4NS41IDEwMyAyNzkuNSAyNzkuNSAxMDMgMzg1LjV6Ii8+PC9zdmc+', icon: 'assets/img/localPlugin.webp',
methods: [], methods: [],
location: 'sidePanel', location: 'sidePanel',
type: 'iframe', type: 'iframe',

@ -62,7 +62,7 @@ const profile = {
displayName: 'Plugin manager', displayName: 'Plugin manager',
methods: [], methods: [],
events: [], events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNzU1IDQ1M3EzNyAzOCAzNyA5MC41dC0zNyA5MC41bC00MDEgNDAwIDE1MCAxNTAtMTYwIDE2MHEtMTYzIDE2My0zODkuNSAxODYuNXQtNDExLjUtMTAwLjVsLTM2MiAzNjJoLTE4MXYtMTgxbDM2Mi0zNjJxLTEyNC0xODUtMTAwLjUtNDExLjV0MTg2LjUtMzg5LjVsMTYwLTE2MCAxNTAgMTUwIDQwMC00MDFxMzgtMzcgOTEtMzd0OTAgMzcgMzcgOTAuNS0zNyA5MC41bC00MDAgNDAxIDIzNCAyMzQgNDAxLTQwMHEzOC0zNyA5MS0zN3Q5MCAzN3oiLz48L3N2Zz4=', icon: 'assets/img/pluginManager.webp',
description: 'Start/stop services, modules and plugins', description: 'Start/stop services, modules and plugins',
kind: 'settings', kind: 'settings',
location: 'sidePanel', location: 'sidePanel',

@ -20,8 +20,9 @@ require('brace/mode/javascript')
require('brace/mode/python') require('brace/mode/python')
require('brace/mode/json') require('brace/mode/json')
require('brace/mode/rust') require('brace/mode/rust')
require('brace/theme/chaos') require('brace/theme/chrome') // for all light themes
require('brace/theme/chrome') require('brace/theme/chaos') // for all dark themes
require('../../../assets/js/editor/darkTheme') // a custom one for remix 'Dark' theme
const css = csjs` const css = csjs`
.ace-editor { .ace-editor {
@ -62,10 +63,11 @@ class Editor extends Plugin {
this._themes = { this._themes = {
'light': 'chrome', 'light': 'chrome',
'dark': 'chaos' 'dark': 'chaos',
'remixDark': 'remixDark'
} }
themeModule.events.on('themeChanged', (theme) => { themeModule.events.on('themeChanged', (theme) => {
this.setTheme(theme.quality) this.setTheme(theme.name === 'Dark' ? 'remixDark' : theme.quality)
}) })
// Init // Init
@ -324,6 +326,13 @@ class Editor extends Plugin {
* @param {string} content Content of the document or update. * @param {string} content Content of the document or update.
*/ */
open (path, content) { open (path, content) {
/*
we have the following cases:
- URL prepended with "localhost"
- URL prepended with "browser"
- URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL
*/
if (!path.startsWith('localhost') && !path.startsWith('browser')) path = `browser/${path}`
if (!this.sessions[path]) { if (!this.sessions[path]) {
const session = this._createSession(content, this._getMode(path)) const session = this._createSession(content, this._getMode(path))
this.sessions[path] = session this.sessions[path] = session

@ -217,7 +217,7 @@ contract Ballot {
var ballotTest = `pragma solidity >=0.4.22 <0.7.0; var ballotTest = `pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
import "./3_Ballot.sol"; import "../3_Ballot.sol";
contract BallotTest { contract BallotTest {
@ -245,5 +245,5 @@ module.exports = {
storage: { name: '1_Storage.sol', content: storage }, storage: { name: '1_Storage.sol', content: storage },
owner: { name: '2_Owner.sol', content: owner }, owner: { name: '2_Owner.sol', content: owner },
ballot: { name: '3_Ballot.sol', content: ballot }, ballot: { name: '3_Ballot.sol', content: ballot },
ballot_test: { name: '4_Ballot_test.sol', content: ballotTest } ballot_test: { name: 'tests/4_Ballot_test.sol', content: ballotTest }
} }

@ -102,10 +102,10 @@ function fileExplorer (localRegistry, files, menuItems) {
function fileAdded (filepath) { function fileAdded (filepath) {
self.ensureRoot(() => { self.ensureRoot(() => {
var folderpath = filepath.split('/').slice(0, -1).join('/') const folderpath = filepath.split('/').slice(0, -1).join('/')
const currentTree = self.treeView.nodeAt(folderpath)
var currentTree = self.treeView.nodeAt(folderpath) if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
if (currentTree && self.treeView.isExpanded(folderpath)) { if (currentTree) {
self.files.resolveDirectory(folderpath, (error, fileTree) => { self.files.resolveDirectory(folderpath, (error, fileTree) => {
if (error) console.error(error) if (error) console.error(error)
if (!fileTree) return if (!fileTree) return
@ -136,15 +136,20 @@ function fileExplorer (localRegistry, files, menuItems) {
if (!fileTree) return if (!fileTree) return
fileTree = normalize(folderpath, fileTree) fileTree = normalize(folderpath, fileTree)
self.treeView.updateNodeFromJSON(folderpath, fileTree, true) self.treeView.updateNodeFromJSON(folderpath, fileTree, true)
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath)
}) })
}) })
} }
function fileRemoved (filepath) { function fileRemoved (filepath) {
var label = self.treeView.labelAt(filepath) const label = self.treeView.labelAt(filepath)
filepath = filepath.split('/').slice(0, -1).join('/')
if (label && label.parentElement) { if (label && label.parentElement) {
label.parentElement.removeChild(label) label.parentElement.removeChild(label)
} }
self.updatePath(filepath)
} }
function fileRenamed (oldName, newName, isFolder) { function fileRenamed (oldName, newName, isFolder) {
@ -240,10 +245,6 @@ function fileExplorer (localRegistry, files, menuItems) {
if (!removeFolder) { if (!removeFolder) {
tooltip(`failed to remove ${key}. Make sure the directory is empty before removing it.`) tooltip(`failed to remove ${key}. Make sure the directory is empty before removing it.`)
} else {
const { type } = fileManager.currentFileProvider()
self.updatePath(type)
} }
}, () => {}) }, () => {})
} }
@ -286,9 +287,6 @@ function fileExplorer (localRegistry, files, menuItems) {
if (!removeFile) { if (!removeFile) {
tooltip(`Failed to remove file ${key}.`) tooltip(`Failed to remove file ${key}.`)
} else {
const { type } = fileManager.currentFileProvider()
self.updatePath(type)
} }
}, },
() => {} () => {}
@ -438,12 +436,12 @@ fileExplorer.prototype.uploadFile = function (event) {
let files = this.files let files = this.files
function loadFile () { function loadFile () {
var fileReader = new FileReader() var fileReader = new FileReader()
fileReader.onload = function (event) { fileReader.onload = async function (event) {
if (helper.checkSpecialChars(file.name)) { if (helper.checkSpecialChars(file.name)) {
modalDialogCustom.alert('Special characters are not allowed') modalDialogCustom.alert('Special characters are not allowed')
return return
} }
var success = files.set(name, event.target.result) var success = await files.set(name, event.target.result)
if (!success) { if (!success) {
modalDialogCustom.alert('Failed to create file ' + name) modalDialogCustom.alert('Failed to create file ' + name)
} else { } else {
@ -638,7 +636,7 @@ fileExplorer.prototype.renderMenuItems = function () {
<label <label
id=${action} id=${action}
data-id="fileExplorerUploadFile${action}" data-id="fileExplorerUploadFile${action}"
class="${icon} ${css.newFile}" class="${icon} mb-0 ${css.newFile}"
title="${title}" title="${title}"
> >
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onchange=${(event) => { <input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onchange=${(event) => {
@ -654,7 +652,8 @@ fileExplorer.prototype.renderMenuItems = function () {
data-id="fileExplorerNewFile${action}" data-id="fileExplorerNewFile${action}"
onclick=${(event) => { event.stopPropagation(); this[ action ]() }} onclick=${(event) => { event.stopPropagation(); this[ action ]() }}
class="newFile ${icon} ${css.newFile}" class="newFile ${icon} ${css.newFile}"
title=${title}> title=${title}
>
</span> </span>
` `
} }

@ -20,10 +20,10 @@ const profile = {
name: 'fileManager', name: 'fileManager',
displayName: 'File manager', displayName: 'File manager',
description: 'Service - read/write to any files or folders, require giving permissions', description: 'Service - read/write to any files or folders, require giving permissions',
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=', icon: 'assets/img/fileManager.webp',
permission: true, permission: true,
version: packageJson.version, version: packageJson.version,
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'rename', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile'], methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile'],
kind: 'file-system' kind: 'file-system'
} }
const errorMsg = { const errorMsg = {
@ -38,7 +38,7 @@ const createError = (err) => {
} }
class FileManager extends Plugin { class FileManager extends Plugin {
constructor (editor) { constructor (editor, appManager) {
super(profile) super(profile)
this.openedFiles = {} // list all opened files this.openedFiles = {} // list all opened files
this.events = new EventEmitter() this.events = new EventEmitter()
@ -46,6 +46,7 @@ class FileManager extends Plugin {
this._components = {} this._components = {}
this._components.compilerImport = new CompilerImport() this._components.compilerImport = new CompilerImport()
this._components.registry = globalRegistry this._components.registry = globalRegistry
this.appManager = appManager
this.init() this.init()
} }
@ -194,10 +195,24 @@ class FileManager extends Plugin {
* @returns {void} * @returns {void}
*/ */
async rename (oldPath, newPath) { async rename (oldPath, newPath) {
await this.__handleExists(oldPath, `Cannot rename ${oldPath}`) await this._handleExists(oldPath, `Cannot rename ${oldPath}`)
const isFile = await this.isFile(oldPath) const isFile = await this.isFile(oldPath)
const newPathExists = await this.exists(newPath)
const provider = this.fileProviderOf(oldPath)
this.fileRenamedEvent(oldPath, newPath, !isFile) if (isFile) {
if (newPathExists) {
modalDialogCustom.alert('File already exists.')
return
}
return provider.rename(oldPath, newPath, false)
} else {
if (newPathExists) {
modalDialogCustom.alert('Folder already exists.')
return
}
return provider.rename(oldPath, newPath, true)
}
} }
/** /**
@ -479,6 +494,32 @@ class FileManager extends Plugin {
return this._deps.filesProviders['browser'] return this._deps.filesProviders['browser']
} }
// returns the list of directories inside path
dirList (path) {
const dirPaths = []
const collectList = (path) => {
return new Promise((resolve, reject) => {
this.readdir(path).then((ls) => {
const promises = Object.keys(ls).map((item, index) => {
const root = (path.indexOf('/') === -1) ? path : path.substr(0, path.indexOf('/'))
const curPath = `${root}/${item}` // adding 'browser' or 'localhost'
if (ls[item].isDirectory && !dirPaths.includes(curPath)) {
dirPaths.push(curPath)
resolve(dirPaths)
}
return new Promise((resolve, reject) => { resolve() })
})
Promise.all(promises).then(() => { resolve(dirPaths) })
})
})
}
return collectList(path)
}
isRemixDActive () {
return this.appManager.isActive('remixd')
}
saveCurrentFile () { saveCurrentFile () {
var currentFile = this._deps.config.get('currentFile') var currentFile = this._deps.config.get('currentFile')
if (currentFile && this.editor.current()) { if (currentFile && this.editor.current()) {

@ -93,9 +93,13 @@ class FileProvider {
cb = cb || function () {} cb = cb || function () {}
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
var exists = window.remixFileSystem.existsSync(unprefixedpath) var exists = window.remixFileSystem.existsSync(unprefixedpath)
if (exists && window.remixFileSystem.readFileSync(unprefixedpath, 'utf8') === content) return true if (exists && window.remixFileSystem.readFileSync(unprefixedpath, 'utf8') === content) {
cb()
return true
}
if (!exists && unprefixedpath.indexOf('/') !== -1) { if (!exists && unprefixedpath.indexOf('/') !== -1) {
this.createDir(path) // the last element is the filename and we should remove it
this.createDir(path.substr(0, path.lastIndexOf('/')))
} }
try { try {
window.remixFileSystem.writeFileSync(unprefixedpath, content) window.remixFileSystem.writeFileSync(unprefixedpath, content)
@ -113,9 +117,8 @@ class FileProvider {
} }
createDir (path, cb) { createDir (path, cb) {
var unprefixedpath = this.removePrefix(path) const unprefixedpath = this.removePrefix(path)
const paths = unprefixedpath.split('/') const paths = unprefixedpath.split('/')
paths.pop() // last element should the filename
if (paths.length && paths[0] === '') paths.shift() if (paths.length && paths[0] === '') paths.shift()
let currentCheck = '' let currentCheck = ''
paths.forEach((value) => { paths.forEach((value) => {
@ -154,12 +157,13 @@ class FileProvider {
* @param {*} path is the folder to be removed * @param {*} path is the folder to be removed
*/ */
remove (path) { remove (path) {
return new Promise((resolve, reject) => {
path = this.removePrefix(path) path = this.removePrefix(path)
if (window.remixFileSystem.existsSync(path)) { if (window.remixFileSystem.existsSync(path)) {
const stat = window.remixFileSystem.statSync(path) const stat = window.remixFileSystem.statSync(path)
try { try {
if (!stat.isDirectory()) { if (!stat.isDirectory()) {
return this.removeFile(path) resolve(this.removeFile(path))
} else { } else {
const items = window.remixFileSystem.readdirSync(path) const items = window.remixFileSystem.readdirSync(path)
if (items.length !== 0) { if (items.length !== 0) {
@ -176,13 +180,15 @@ class FileProvider {
// folder is empty // folder is empty
window.remixFileSystem.rmdirSync(path, console.log) window.remixFileSystem.rmdirSync(path, console.log)
} }
this.event.trigger('fileRemoved', [this._normalizePath(path)])
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e)
return false return resolve(false)
} }
} }
return true return resolve(true)
})
} }
removeFile (path) { removeFile (path) {
@ -216,10 +222,11 @@ class FileProvider {
window.remixFileSystem.readdir(path, (error, files) => { window.remixFileSystem.readdir(path, (error, files) => {
var ret = {} var ret = {}
if (files) { if (files) {
files.forEach(element => { files.forEach(element => {
const absPath = (path === '/' ? '' : path) + '/' + element const absPath = (path === '/' ? '' : path) + '/' + element
ret[absPath.indexOf('/') === 0 ? absPath.replace('/', '') : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() } ret[absPath.indexOf('/') === 0 ? absPath.substr(1, absPath.length) : absPath] = { isDirectory: window.remixFileSystem.statSync(absPath).isDirectory() }
// ^ ret does not accept path starting with '/' // ^ ret does not accept path starting with '/'
}) })
} }

@ -1,12 +1,10 @@
'use strict' 'use strict'
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
var pathtool = require('path')
module.exports = class RemixDProvider { module.exports = class RemixDProvider {
constructor (remixd) { constructor (appManager) {
this.event = new EventManager() this.event = new EventManager()
this._remixd = remixd this._appManager = appManager
this.remixd = remixapi(remixd, this)
this.type = 'localhost' this.type = 'localhost'
this.error = { 'EEXIST': 'File already exists' } this.error = { 'EEXIST': 'File already exists' }
this._isReady = false this._isReady = false
@ -14,39 +12,34 @@ module.exports = class RemixDProvider {
this._readOnlyMode = false this._readOnlyMode = false
this.filesContent = {} this.filesContent = {}
this.files = {} this.files = {}
}
_registerEvent () {
var remixdEvents = ['connecting', 'connected', 'errored', 'closed'] var remixdEvents = ['connecting', 'connected', 'errored', 'closed']
remixdEvents.forEach((value) => { remixdEvents.forEach((value) => {
remixd.event.register(value, (event) => { this._appManager.on('remixd', value, (event) => {
this.event.trigger(value, [event]) this.event.trigger(value, [event])
}) })
}) })
remixd.event.register('notified', (data) => { this._appManager.on('remixd', 'folderAdded', (path) => {
if (data.scope === 'sharedfolder') { this.event.trigger('folderAdded', [this.addPrefix(path)])
if (data.name === 'created') {
this.init(() => {
this.event.trigger('fileAdded', [this.type + '/' + data.value.path, data.value.isReadOnly, data.value.isFolder])
}) })
} else if (data.name === 'removed') {
this.init(() => { this._appManager.on('remixd', 'fileAdded', (path) => {
this.event.trigger('fileRemoved', [this.type + '/' + data.value.path]) this.event.trigger('fileAdded', [this.addPrefix(path)])
}) })
} else if (data.name === 'changed') {
this._remixd.call('sharedfolder', 'get', {path: data.value}, (error, content) => { this._appManager.on('remixd', 'fileChanged', (path) => {
if (error) { this.event.trigger('fileChanged', [this.addPrefix(path)])
console.log(error)
} else {
var path = this.type + '/' + data.value
this.filesContent[path] = content
this.event.trigger('fileExternallyChanged', [path, content])
}
}) })
} else if (data.name === 'rootFolderChanged') {
// new path has been set, we should reset this._appManager.on('remixd', 'fileRemoved', (path) => {
this.event.trigger('folderAdded', [this.type + '/']) this.event.trigger('fileRemoved', [this.addPrefix(path)])
} })
}
this._appManager.on('remixd', 'fileRenamed', (oldPath, newPath) => {
this.event.trigger('fileRemoved', [this.addPrefix(oldPath), this.addPrefix(newPath)])
}) })
} }
@ -55,38 +48,35 @@ module.exports = class RemixDProvider {
} }
close (cb) { close (cb) {
this.remixd.exit()
this._isReady = false this._isReady = false
cb() cb()
} }
init (cb) { init (cb) {
this._remixd.ensureSocket((error) => { if (this._isReady) return cb && cb()
if (error) return cb(error) this._appManager.call('remixd', 'folderIsReadOnly', {})
this._isReady = !error .then((result) => {
this._remixd.call('sharedfolder', 'folderIsReadOnly', {}, (error, result) => { this._isReady = true
this._readOnlyMode = result this._readOnlyMode = result
cb(error) this._registerEvent()
}) cb && cb()
}).catch((error) => {
cb && cb(error)
}) })
} }
// @TODO: refactor all `this._remixd.call(....)` uses into `this.remixd[api](...)` exists (path, cb) {
// where `api = ...`: if (!this._isReady) return cb && cb('provider not ready')
// this.remixd.read(path, (error, content) => {})
// this.remixd.write(path, content, (error, result) => {})
// this.remixd.rename(path1, path2, (error, result) => {})
// this.remixd.remove(path, (error, result) => {})
// this.remixd.dir(path, (error, filesList) => {})
//
// this.remixd.exists(path, (error, isValid) => {})
async exists (path, cb) {
const unprefixedpath = this.removePrefix(path) const unprefixedpath = this.removePrefix(path)
const callId = await this._remixd.call('sharedfolder', 'exists', {path: unprefixedpath})
const result = await this._remixd.receiveResponse(callId)
return cb(null, result) return this._appManager.call('remixd', 'exists', { path: unprefixedpath })
.then((result) => {
if (cb) return cb(null, result)
return result
}).catch((error) => {
if (cb) return cb(error)
throw new Error(error)
})
} }
getNormalizedName (path) { getNormalizedName (path) {
@ -98,67 +88,75 @@ module.exports = class RemixDProvider {
} }
get (path, cb) { get (path, cb) {
if (!this._isReady) return cb && cb('provider not ready')
var unprefixedpath = this.removePrefix(path) var unprefixedpath = this.removePrefix(path)
this._remixd.call('sharedfolder', 'get', {path: unprefixedpath}, (error, file) => { this._appManager.call('remixd', 'get', { path: unprefixedpath })
if (!error) { .then((file) => {
this.filesContent[path] = file.content this.filesContent[path] = file.content
if (file.readonly) { this._readOnlyFiles[path] = 1 } if (file.readonly) { this._readOnlyFiles[path] = 1 }
cb(error, file.content) cb(null, file.content)
} else { }).catch((error) => {
if (error) console.log(error)
// display the last known content. // display the last known content.
// TODO should perhaps better warn the user that the file is not synced. // TODO should perhaps better warn the user that the file is not synced.
cb(null, this.filesContent[path]) return cb(null, this.filesContent[path])
}
}) })
} }
set (path, content, cb) { async set (path, content, cb) {
var unprefixedpath = this.removePrefix(path) if (!this._isReady) return cb && cb('provider not ready')
this._remixd.call('sharedfolder', 'set', {path: unprefixedpath, content: content}, (error, result) => { const unprefixedpath = this.removePrefix(path)
if (cb) return cb(error, result)
var path = this.type + '/' + unprefixedpath return this._appManager.call('remixd', 'set', { path: unprefixedpath, content: content }).then(async (result) => {
this.event.trigger('fileChanged', [path]) if (cb) return cb(null, result)
}).catch((error) => {
if (cb) return cb(error)
throw new Error(error)
}) })
return true
} }
isReadOnly (path) { isReadOnly (path) {
return this._readOnlyMode || this._readOnlyFiles[path] === 1 return this._readOnlyMode || this._readOnlyFiles[path] === 1
} }
async remove (path) { remove (path) {
var unprefixedpath = this.removePrefix(path) return new Promise((resolve, reject) => {
const callId = await this._remixd.call('sharedfolder', 'remove', {path: unprefixedpath}, (error, result) => { if (!this._isReady) return reject('provider not ready')
if (error) console.log(error) const unprefixedpath = this.removePrefix(path)
var path = this.type + '/' + unprefixedpath this._appManager.call('remixd', 'remove', { path: unprefixedpath })
.then(result => {
const path = this.type + '/' + unprefixedpath
delete this.filesContent[path] delete this.filesContent[path]
this.init(() => { resolve(true)
this.event.trigger('fileRemoved', [path]) this.init()
}).catch(error => {
if (error) console.log(error)
resolve(false)
}) })
}) })
return await this._remixd.receiveResponse(callId)
} }
rename (oldPath, newPath, isFolder) { rename (oldPath, newPath, isFolder) {
var unprefixedoldPath = this.removePrefix(oldPath) const unprefixedoldPath = this.removePrefix(oldPath)
var unprefixednewPath = this.removePrefix(newPath) const unprefixednewPath = this.removePrefix(newPath)
this._remixd.call('sharedfolder', 'rename', {oldPath: unprefixedoldPath, newPath: unprefixednewPath}, (error, result) => { if (!this._isReady) return new Promise((resolve, reject) => reject('provider not ready'))
if (error) { return this._appManager.call('remixd', 'rename', { oldPath: unprefixedoldPath, newPath: unprefixednewPath })
console.log(error) .then(result => {
if (this.error[error.code]) error = this.error[error.code] const newPath = this.type + '/' + unprefixednewPath
this.event.trigger('fileRenamedError', [this.error[error.code]]) const oldPath = this.type + '/' + unprefixedoldPath
} else {
var newPath = this.type + '/' + unprefixednewPath
var oldPath = this.type + '/' + unprefixedoldPath
this.filesContent[newPath] = this.filesContent[oldPath] this.filesContent[newPath] = this.filesContent[oldPath]
delete this.filesContent[oldPath] delete this.filesContent[oldPath]
this.init(() => { this.init(() => {
this.event.trigger('fileRenamed', [oldPath, newPath, isFolder]) this.event.trigger('fileRenamed', [oldPath, newPath, isFolder])
}) })
} return result
}).catch(error => {
console.log(error)
if (this.error[error.code]) error = this.error[error.code]
this.event.trigger('fileRenamedError', [this.error[error.code]])
}) })
return true
} }
isExternalFolder (path) { isExternalFolder (path) {
@ -171,56 +169,33 @@ module.exports = class RemixDProvider {
return path return path
} }
addPrefix (path) {
if (path.indexOf(this.type + '/') === 0) return path
if (path[0] === '/') return 'localhost' + path
return 'localhost/' + path
}
resolveDirectory (path, callback) { resolveDirectory (path, callback) {
var self = this var self = this
if (path[0] === '/') path = path.substring(1) if (path[0] === '/') path = path.substring(1)
if (!path) return callback(null, { [self.type]: { } }) if (!path) return callback(null, { [self.type]: { } })
path = self.removePrefix(path) const unprefixedpath = this.removePrefix(path)
self.remixd.dir(path, callback)
if (!this._isReady) return callback && callback('provider not ready')
this._appManager.call('remixd', 'resolveDirectory', { path: unprefixedpath }).then((result) => {
callback(null, result)
}).catch(callback)
} }
async isDirectory (path) { async isDirectory (path) {
const unprefixedpath = this.removePrefix(path) const unprefixedpath = this.removePrefix(path)
const callId = await this._remixd.call('sharedfolder', 'isDirectory', {path: unprefixedpath}) if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isDirectory', {path: unprefixedpath})
return await this._remixd.receiveResponse(callId)
} }
async isFile (path) { async isFile (path) {
const unprefixedpath = this.removePrefix(path) const unprefixedpath = this.removePrefix(path)
const callId = await this._remixd.call('sharedfolder', 'isFile', {path: unprefixedpath}) if (!this._isReady) throw new Error('provider not ready')
return await this._appManager.call('remixd', 'isFile', { path: unprefixedpath })
return await this._remixd.receiveResponse(callId)
}
}
function remixapi (remixd, self) {
const read = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'get', { path }, (error, content) => callback(error, content))
}
const write = (path, content, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'set', { path, content }, (error, result) => callback(error, result))
}
const rename = (path, newpath, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'rename', { oldPath: path, newPath: newpath }, (error, result) => callback(error, result))
}
const remove = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'remove', { path }, (error, result) => callback(error, result))
}
const dir = (path, callback) => {
path = '' + (path || '')
path = pathtool.join('./', path)
remixd.call('sharedfolder', 'resolveDirectory', { path }, (error, filesList) => callback(error, filesList))
} }
const exit = () => { remixd.close() }
const api = { read, write, rename, remove, dir, exit, event: remixd.event }
return api
} }

@ -1,5 +1,5 @@
import isElectron from 'is-electron' import isElectron from 'is-electron'
import { Plugin } from '@remixproject/engine' import { WebsocketPlugin } from '@remixproject/engine'
import * as packageJson from '../../../package.json' import * as packageJson from '../../../package.json'
var yo = require('yo-yo') var yo = require('yo-yo')
var modalDialog = require('../ui/modaldialog') var modalDialog = require('../ui/modaldialog')
@ -20,14 +20,16 @@ var css = csjs`
const profile = { const profile = {
name: 'remixd', name: 'remixd',
methods: [], displayName: 'RemixD',
url: 'ws://127.0.0.1:65520',
methods: ['folderIsReadOnly', 'resolveDirectory', 'get', 'exists', 'isFile', 'set', 'rename', 'remove', 'isDirectory', 'list'],
events: [], events: [],
description: 'Using Remixd daemon, allow to access file system', description: 'Using Remixd daemon, allow to access file system',
kind: 'other', kind: 'other',
version: packageJson.version version: packageJson.version
} }
export class RemixdHandle extends Plugin { export class RemixdHandle extends WebsocketPlugin {
constructor (fileSystemExplorer, locahostProvider, appManager) { constructor (fileSystemExplorer, locahostProvider, appManager) {
super(profile) super(profile)
this.fileSystemExplorer = fileSystemExplorer this.fileSystemExplorer = fileSystemExplorer
@ -36,16 +38,19 @@ export class RemixdHandle extends Plugin {
} }
deactivate () { deactivate () {
this.fileSystemExplorer.hide()
if (super.socket) super.deactivate()
this.locahostProvider.close((error) => { this.locahostProvider.close((error) => {
if (error) console.log(error) if (error) console.log(error)
}) })
} }
activate () { activate () {
this.fileSystemExplorer.show()
this.connectToLocalhost() this.connectToLocalhost()
} }
canceled () { async canceled () {
this.appManager.ensureDeactivated('remixd') this.appManager.ensureDeactivated('remixd')
} }
@ -55,7 +60,7 @@ export class RemixdHandle extends Plugin {
* *
* @param {String} txHash - hash of the transaction * @param {String} txHash - hash of the transaction
*/ */
connectToLocalhost () { async connectToLocalhost () {
let connection = (error) => { let connection = (error) => {
if (error) { if (error) {
console.log(error) console.log(error)
@ -65,13 +70,22 @@ export class RemixdHandle extends Plugin {
) )
this.canceled() this.canceled()
} else { } else {
this.fileSystemExplorer.ensureRoot() const intervalId = setInterval(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
clearInterval(intervalId)
console.log(error)
modalDialogCustom.alert(
'Connection to remixd terminated' +
'Please make sure remixd is still running in the background.'
)
this.canceled()
}
}, 3000)
this.locahostProvider.init(_ => this.fileSystemExplorer.ensureRoot())
} }
} }
if (this.locahostProvider.isConnected()) { if (this.locahostProvider.isConnected()) {
this.locahostProvider.close((error) => { this.deactivate()
if (error) console.log(error)
})
} else if (!isElectron()) { } else if (!isElectron()) {
// warn the user only if he/she is in the browser context // warn the user only if he/she is in the browser context
modalDialog( modalDialog(
@ -79,7 +93,18 @@ export class RemixdHandle extends Plugin {
remixdDialog(), remixdDialog(),
{ label: 'Connect', { label: 'Connect',
fn: () => { fn: () => {
this.locahostProvider.init((error) => connection(error)) try {
super.activate()
setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
connection(new Error('Connection with daemon failed.'))
} else {
connection()
}
}, 3000)
} catch (error) {
connection(error)
}
} }
}, },
{ label: 'Cancel', { label: 'Cancel',
@ -89,7 +114,12 @@ export class RemixdHandle extends Plugin {
} }
) )
} else { } else {
this.locahostProvider.init((error) => connection(error)) try {
super.activate()
setTimeout(() => { connection() }, 2000)
} catch (error) {
connection(error)
}
} }
} }
} }

@ -32,7 +32,7 @@ const profile = {
displayName: 'File explorers', displayName: 'File explorers',
methods: [], methods: [],
events: [], events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDM4NHE0MCAwIDY4IDI4dDI4IDY4djEyMTZxMCA0MC0yOCA2OHQtNjggMjhoLTk2MHEtNDAgMC02OC0yOHQtMjgtNjh2LTI4OGgtNTQ0cS00MCAwLTY4LTI4dC0yOC02OHYtNjcycTAtNDAgMjAtODh0NDgtNzZsNDA4LTQwOHEyOC0yOCA3Ni00OHQ4OC0yMGg0MTZxNDAgMCA2OCAyOHQyOCA2OHYzMjhxNjgtNDAgMTI4LTQwaDQxNnptLTU0NCAyMTNsLTI5OSAyOTloMjk5di0yOTl6bS02NDAtMzg0bC0yOTkgMjk5aDI5OXYtMjk5em0xOTYgNjQ3bDMxNi0zMTZ2LTQxNmgtMzg0djQxNnEwIDQwLTI4IDY4dC02OCAyOGgtNDE2djY0MGg1MTJ2LTI1NnEwLTQwIDIwLTg4dDQ4LTc2em05NTYgODA0di0xMTUyaC0zODR2NDE2cTAgNDAtMjggNjh0LTY4IDI4aC00MTZ2NjQwaDg5NnoiLz48L3N2Zz4=', icon: 'assets/img/fileManager.webp',
description: ' - ', description: ' - ',
kind: 'fileexplorer', kind: 'fileexplorer',
location: 'sidePanel', location: 'sidePanel',
@ -64,8 +64,8 @@ module.exports = class Filepanel extends ViewPlugin {
const explorers = yo` const explorers = yo`
<div> <div>
<div class=${css.treeview} data-id="filePanelFileExplorerTree">${fileExplorer.init()}</div> <div class="pl-2 ${css.treeview}" data-id="filePanelFileExplorerTree">${fileExplorer.init()}</div>
<div class="filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div> <div class="pl-2 filesystemexplorer ${css.treeview}">${fileSystemExplorer.init()}</div>
</div> </div>
` `

@ -80,7 +80,7 @@ export class MainView {
self.data = { self.data = {
_layout: { _layout: {
top: { top: {
offset: self._deps.config.get('terminal-top-offset') || 150, offset: self._terminalTopOffset(),
show: true show: true
} }
} }
@ -97,6 +97,9 @@ export class MainView {
}) })
} }
} }
_terminalTopOffset () {
return this._deps.config.get('terminal-top-offset') || 150
}
_adjustLayout (direction, delta) { _adjustLayout (direction, delta) {
var limitUp = 0 var limitUp = 0
var limitDown = 32 var limitDown = 32
@ -126,6 +129,12 @@ export class MainView {
self._components.terminal.scroll2bottom() self._components.terminal.scroll2bottom()
} }
} }
minimizeTerminal () {
this._adjustLayout('top')
}
showTerminal (offset) {
this._adjustLayout('top', offset || this._terminalTopOffset())
}
getTerminal () { getTerminal () {
return this._components.terminal return this._components.terminal
} }

File diff suppressed because one or more lines are too long

@ -147,11 +147,11 @@ class Terminal extends Plugin {
self._view.icon = yo` self._view.icon = yo`
<i onmouseenter=${hover} onmouseleave=${hover} onmousedown=${minimize} <i onmouseenter=${hover} onmouseleave=${hover} onmousedown=${minimize}
class="btn btn-secondary btn-sm align-items-center ${css.toggleTerminal} fas fa-angle-double-down" data-id="terminalToggleIcon"></i>` class="mx-2 ${css.toggleTerminal} fas fa-angle-double-down" data-id="terminalToggleIcon"></i>`
self._view.dragbar = yo` self._view.dragbar = yo`
<div onmousedown=${mousedown} class=${css.dragbarHorizontal}></div>` <div onmousedown=${mousedown} class=${css.dragbarHorizontal}></div>`
self._view.pendingTxCount = yo`<div class=${css.pendingTx} title='Pending Transactions'>0</div>` self._view.pendingTxCount = yo`<div class="mx-2" title='Pending Transactions'>0</div>`
self._view.inputSearch = yo`<input self._view.inputSearch = yo`<input
spellcheck="false" spellcheck="false"
type="text" type="text"
@ -166,21 +166,22 @@ class Terminal extends Plugin {
${self._view.dragbar} ${self._view.dragbar}
<div class="${css.menu} border-top border-dark bg-light" data-id="terminalToggleMenu"> <div class="${css.menu} border-top border-dark bg-light" data-id="terminalToggleMenu">
${self._view.icon} ${self._view.icon}
<div class=${css.clear} id="clearConsole" data-id="terminalClearConsole" onclick=${clear}> <div class="mx-2" id="clearConsole" data-id="terminalClearConsole" onclick=${clear}>
<i class="fas fa-ban" aria-hidden="true" title="Clear console" <i class="fas fa-ban" aria-hidden="true" title="Clear console"
onmouseenter=${hover} onmouseleave=${hover}></i> onmouseenter=${hover} onmouseleave=${hover}></i>
</div> </div>
${self._view.pendingTxCount} ${self._view.pendingTxCount}
<div class=${css.verticalLine}></div> <div class=${css.verticalLine}></div>
<div class="form-check"> <div class="mx-2 d-flex pb-1 align-items-center ${css.listenOnNetwork} custom-control custom-checkbox">
<input <input
class="pb-0 form-check-input custom-control-input"
id="listenNetworkCheck" id="listenNetworkCheck"
onchange=${listenOnNetwork} onchange=${listenOnNetwork}
type="checkbox" class="form-check-input " type="checkbox"
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you" title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
> >
<label <label
class="${css.listenOnNetworkLabel} form-check-label" class="pt-1 form-check-label custom-control-label text-nowrap""
title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you" title="If checked Remix will listen on all transactions mined in the current environment and not only transactions created by you"
for="listenNetworkCheck" for="listenNetworkCheck"
> >

@ -11,7 +11,7 @@ const profile = {
displayName: 'Solidity static analysis', displayName: 'Solidity static analysis',
methods: [], methods: [],
events: [], events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMjA0OCIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMjA0OCAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0yMDQ4IDE1MzZ2MTI4aC0yMDQ4di0xNTM2aDEyOHYxNDA4aDE5MjB6bS0xMjgtMTI0OHY0MzVxMCAyMS0xOS41IDI5LjV0LTM1LjUtNy41bC0xMjEtMTIxLTYzMyA2MzNxLTEwIDEwLTIzIDEwdC0yMy0xMGwtMjMzLTIzMy00MTYgNDE2LTE5Mi0xOTIgNTg1LTU4NXExMC0xMCAyMy0xMHQyMyAxMGwyMzMgMjMzIDQ2NC00NjQtMTIxLTEyMXEtMTYtMTYtNy41LTM1LjV0MjkuNS0xOS41aDQzNXExNCAwIDIzIDl0OSAyM3oiLz48L3N2Zz4=', icon: 'assets/img/staticAnalysis.webp',
description: 'Checks the contract code for security vulnerabilities and bad practices.', description: 'Checks the contract code for security vulnerabilities and bad practices.',
kind: 'analysis', kind: 'analysis',
location: 'sidePanel', location: 'sidePanel',

File diff suppressed because one or more lines are too long

@ -68,7 +68,7 @@ class CompileTab {
const sources = { [target]: { content } } const sources = { [target]: { content } }
this.event.emit('startingCompilation') this.event.emit('startingCompilation')
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation') // setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
setTimeout(() => this.compiler.compile(sources, target), 100) setTimeout(() => { this.compiler.compile(sources, target); resolve() }, 100)
}) })
}) })
} }

@ -1,12 +1,11 @@
const yo = require('yo-yo') const yo = require('yo-yo')
const minixhr = require('minixhr')
const helper = require('../../../lib/helper') const helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip') const addTooltip = require('../../ui/tooltip')
const semver = require('semver') const semver = require('semver')
const modalDialogCustom = require('../../ui/modal-dialog-custom') const modalDialogCustom = require('../../ui/modal-dialog-custom')
const css = require('../styles/compile-tab-styles') const css = require('../styles/compile-tab-styles')
import { canUseWorker } from '../../compiler/compiler-utils' import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '../../compiler/compiler-utils'
class CompilerContainer { class CompilerContainer {
@ -24,8 +23,7 @@ class CompilerContainer {
timeout: 300, timeout: 300,
allversions: null, allversions: null,
selectedVersion: null, selectedVersion: null,
defaultVersion: 'soljson-v0.6.6+commit.6c089d02.js', // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler defaultVersion: 'soljson-v0.6.6+commit.6c089d02.js' // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler
baseurl: 'https://solc-bin.ethereum.org/bin'
} }
} }
@ -320,17 +318,21 @@ class CompilerContainer {
this.compileIfAutoCompileOn() this.compileIfAutoCompileOn()
} }
/*
The following functions are handlers for internal events.
*/
onchangeOptimize () { onchangeOptimize () {
this.compileTabLogic.setOptimize(!!this._view.optimize.checked) this.compileTabLogic.setOptimize(!!this._view.optimize.checked)
this.compileIfAutoCompileOn() this.compileIfAutoCompileOn()
} }
onchangeLanguage (event) { onchangeLanguage () {
this.compileTabLogic.setLanguage(event.target.value) this.compileTabLogic.setLanguage(this._view.languageSelector.value)
this.compileIfAutoCompileOn() this.compileIfAutoCompileOn()
} }
onchangeEvmVersion (_) { onchangeEvmVersion () {
let s = this._view.evmVersionSelector let s = this._view.evmVersionSelector
let v = s.value let v = s.value
if (v === 'default') { if (v === 'default') {
@ -340,12 +342,44 @@ class CompilerContainer {
this.compileIfAutoCompileOn() this.compileIfAutoCompileOn()
} }
onchangeLoadVersion (event) { onchangeLoadVersion () {
this.data.selectedVersion = this._view.versionSelector.value this.data.selectedVersion = this._view.versionSelector.value
this._updateVersionSelector() this._updateVersionSelector()
this._updateLanguageSelector() this._updateLanguageSelector()
} }
/*
The following functions map with the above event handlers.
They are an external API for modifying the compiler configuration.
*/
setConfiguration (settings) {
this.setLanguage(settings.language)
this.setEvmVersion(settings.evmVersion)
this.setOptimize(settings.optimize)
this.setVersion(settings.version)
}
setOptimize (enabled) {
this._view.optimize.checked = enabled
this.onchangeOptimize()
}
setLanguage (lang) {
this._view.languageSelector.value = lang
this.onchangeLanguage()
}
setEvmVersion (version) {
this._view.evmVersionSelector.value = version || 'default'
this.onchangeEvmVersion()
}
setVersion (version) {
this._view.versionSelector.value = `soljson-v${version}.js`
this.onchangeLoadVersion()
}
_shouldBeAdded (version) { _shouldBeAdded (version) {
return !version.includes('nightly') || return !version.includes('nightly') ||
(version.includes('nightly') && this._view.includeNightlies.checked) (version.includes('nightly') && this._view.includeNightlies.checked)
@ -383,13 +417,13 @@ class CompilerContainer {
if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) { if (this.data.selectedVersion.indexOf('soljson') !== 0 || helper.checkSpecialChars(this.data.selectedVersion)) {
return console.log('loading ' + this.data.selectedVersion + ' not allowed') return console.log('loading ' + this.data.selectedVersion + ' not allowed')
} }
url = `${this.data.baseurl}/${this.data.selectedVersion}` url = `${urlFromVersion(this.data.selectedVersion)}`
} }
// Workers cannot load js on "file:"-URLs and we get a // Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium, // "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case. // resort to non-worker version in that case.
if (canUseWorker(this.data.selectedVersion)) { if (this.data.selectedVersion !== 'builtin' && canUseWorker(this.data.selectedVersion)) {
this.compileTabLogic.compiler.loadVersion(true, url) this.compileTabLogic.compiler.loadVersion(true, url)
this.setVersionText('(loading using worker)') this.setVersionText('(loading using worker)')
} else { } else {
@ -413,27 +447,42 @@ class CompilerContainer {
if (this._view.version) this._view.version.innerText = text if (this._view.version) this._view.version.innerText = text
} }
fetchAllVersion (callback) { // fetching both normal and wasm builds and creating a [version, baseUrl] map
minixhr(`${this.data.baseurl}/list.json`, (json, event) => { async fetchAllVersion (callback) {
// @TODO: optimise and cache results to improve app loading times #2461 let allVersions, selectedVersion, allVersionsWasm
var allversions, selectedVersion // fetch normal builds
if (event.type !== 'error') { const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`)
// fetch wasm builds
const wasmRes = await promisedMiniXhr(`${baseURLWasm}/list.json`)
if (binRes.event.type === 'error' && wasmRes.event.type === 'error') {
allVersions = [{ path: 'builtin', longVersion: 'latest local version' }]
selectedVersion = 'builtin'
callback(allVersions, selectedVersion)
}
try { try {
const data = JSON.parse(json) allVersions = JSON.parse(binRes.json).builds.slice().reverse()
allversions = data.builds.slice().reverse()
selectedVersion = this.data.defaultVersion selectedVersion = this.data.defaultVersion
if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version if (this.queryParams.get().version) selectedVersion = this.queryParams.get().version
} catch (e) { if (wasmRes.event.type !== 'error') {
addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload.') allVersionsWasm = JSON.parse(wasmRes.json).builds.slice().reverse()
} }
} catch (e) {
addTooltip('Cannot load compiler version list. It might have been blocked by an advertisement blocker. Please try deactivating any of them from this page and reload. Error: ' + e)
}
// replace in allVersions those compiler builds which exist in allVersionsWasm with new once
if (allVersionsWasm && allVersions) {
allVersions.forEach((compiler, index) => {
const wasmIndex = allVersionsWasm.findIndex(wasmCompiler => { return wasmCompiler.longVersion === compiler.longVersion })
if (wasmIndex !== -1) {
allVersions[index] = allVersionsWasm[wasmIndex]
pathToURL[compiler.path] = baseURLWasm
} else { } else {
allversions = [{ path: 'builtin', longVersion: 'latest local version' }] pathToURL[compiler.path] = baseURLBin
selectedVersion = 'builtin'
} }
callback(allversions, selectedVersion)
}) })
} }
callback(allVersions, selectedVersion)
}
scheduleCompilation () { scheduleCompilation () {
if (!this.config.get('autoCompile')) return if (!this.config.get('autoCompile')) return
if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout) if (this.data.compileTimeout) window.clearTimeout(this.data.compileTimeout)

@ -11,7 +11,7 @@ const profile = {
displayName: 'Debugger', displayName: 'Debugger',
methods: ['debug', 'getTrace'], methods: ['debug', 'getTrace'],
events: [], events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xNjk2IDk2MHEwIDI2LTE5IDQ1dC00NSAxOWgtMjI0cTAgMTcxLTY3IDI5MGwyMDggMjA5cTE5IDE5IDE5IDQ1dC0xOSA0NXEtMTggMTktNDUgMTl0LTQ1LTE5bC0xOTgtMTk3cS01IDUtMTUgMTN0LTQyIDI4LjUtNjUgMzYuNS04MiAyOS05NyAxM3YtODk2aC0xMjh2ODk2cS01MSAwLTEwMS41LTEzLjV0LTg3LTMzLTY2LTM5LTQzLjUtMzIuNWwtMTUtMTQtMTgzIDIwN3EtMjAgMjEtNDggMjEtMjQgMC00My0xNi0xOS0xOC0yMC41LTQ0LjV0MTUuNS00Ni41bDIwMi0yMjdxLTU4LTExNC01OC0yNzRoLTIyNHEtMjYgMC00NS0xOXQtMTktNDUgMTktNDUgNDUtMTloMjI0di0yOTRsLTE3My0xNzNxLTE5LTE5LTE5LTQ1dDE5LTQ1IDQ1LTE5IDQ1IDE5bDE3MyAxNzNoODQ0bDE3My0xNzNxMTktMTkgNDUtMTl0NDUgMTkgMTkgNDUtMTkgNDVsLTE3MyAxNzN2Mjk0aDIyNHEyNiAwIDQ1IDE5dDE5IDQ1em0tNDgwLTU3NmgtNjQwcTAtMTMzIDkzLjUtMjI2LjV0MjI2LjUtOTMuNSAyMjYuNSA5My41IDkzLjUgMjI2LjV6Ii8+PC9zdmc+', icon: 'assets/img/debuggerLogo.webp',
description: 'Debug transactions', description: 'Debug transactions',
kind: 'debugging', kind: 'debugging',
location: 'sidePanel', location: 'sidePanel',
@ -56,19 +56,25 @@ class DebuggerTab extends ViewPlugin {
}) })
this.debuggerUI = new DebuggerUI( this.debuggerUI = new DebuggerUI(
this,
this.el.querySelector('#debugger'), this.el.querySelector('#debugger'),
this.blockchain,
(address, receipt) => { (address, receipt) => {
const target = (address && remixLib.helpers.trace.isContractCreation(address)) ? receipt.contractAddress : address const target = (address && remixLib.helpers.trace.isContractCreation(address)) ? receipt.contractAddress : address
return this.call('fetchAndCompile', 'resolve', target || receipt.contractAddress || receipt.to, '.debug', this.blockchain.web3()) return this.call('fetchAndCompile', 'resolve', target || receipt.contractAddress || receipt.to, '.debug', this.blockchain.web3())
}) }
)
this.call('manager', 'activatePlugin', 'source-verification') this.call('manager', 'activatePlugin', 'source-verification').catch(e => console.log(e.message))
// this.call('manager', 'activatePlugin', 'udapp') // this.call('manager', 'activatePlugin', 'udapp')
return this.el return this.el
} }
deactivate () {
this.debuggerUI.deleteHighlights()
super.deactivate()
}
debug (hash) { debug (hash) {
if (this.debuggerUI) this.debuggerUI.debug(hash) if (this.debuggerUI) this.debuggerUI.debug(hash)
} }

@ -22,17 +22,12 @@ var css = csjs`
.statusMessage { .statusMessage {
margin-left: 15px; margin-left: 15px;
} }
.innerShift {
padding: 2px;
margin-left: 10px;
}
` `
class DebuggerUI { class DebuggerUI {
constructor (container, blockchain, fetchContractAndCompile) { constructor (debuggerModule, component, fetchContractAndCompile) {
this.registry = globalRegistry this.debuggerModule = debuggerModule
this.blockchain = blockchain
this.fetchContractAndCompile = fetchContractAndCompile this.fetchContractAndCompile = fetchContractAndCompile
this.event = new EventManager() this.event = new EventManager()
@ -48,62 +43,62 @@ class DebuggerUI {
this.view this.view
container.appendChild(this.render()) component.appendChild(this.render())
this.setEditor() this.setEditor()
} }
setEditor () { setEditor () {
const self = this this.editor = globalRegistry.get('editor').api
this.editor = this.registry.get('editor').api
self.editor.event.register('breakpointCleared', (fileName, row) => { this.editor.event.register('breakpointCleared', (fileName, row) => {
if (self.debugger) self.debugger.breakPointManager.remove({fileName: fileName, row: row}) if (this.debugger) this.debugger.breakPointManager.remove({fileName: fileName, row: row})
}) })
self.editor.event.register('breakpointAdded', (fileName, row) => { this.editor.event.register('breakpointAdded', (fileName, row) => {
if (self.debugger) self.debugger.breakPointManager.add({fileName: fileName, row: row}) if (this.debugger) this.debugger.breakPointManager.add({fileName: fileName, row: row})
}) })
self.editor.event.register('contentChanged', function () { this.editor.event.register('contentChanged', () => {
if (self.debugger) self.debugger.unload() if (this.debugger) this.debugger.unload()
}) })
} }
listenToEvents () { listenToEvents () {
const self = this if (!this.debugger) return
if (!self.debugger) return
this.debugger.event.register('debuggerStatus', function (isActive) { this.debugger.event.register('debuggerStatus', async (isActive) => {
self.sourceHighlighter.currentSourceLocation(null) await this.debuggerModule.call('editor', 'discardHighlight')
self.isActive = isActive this.isActive = isActive
}) })
this.debugger.event.register('newSourceLocation', async function (lineColumnPos, rawLocation) { this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation) => {
const contracts = await self.fetchContractAndCompile( const contracts = await this.fetchContractAndCompile(
self.currentReceipt.contractAddress || self.currentReceipt.to, this.currentReceipt.contractAddress || this.currentReceipt.to,
self.currentReceipt) this.currentReceipt)
if (contracts) { if (contracts) {
const path = contracts.getSourceName(rawLocation.file) const path = contracts.getSourceName(rawLocation.file)
if (path) self.sourceHighlighter.currentSourceLocationFromfileName(lineColumnPos, path) if (path) {
await this.debuggerModule.call('editor', 'discardHighlight')
await this.debuggerModule.call('editor', 'highlight', lineColumnPos, path)
}
} }
}) })
this.debugger.event.register('debuggerUnloaded', self.unLoad.bind(this)) this.debugger.event.register('debuggerUnloaded', () => this.unLoad())
} }
startTxBrowser () { startTxBrowser () {
const self = this
let txBrowser = new TxBrowser() let txBrowser = new TxBrowser()
this.txBrowser = txBrowser this.txBrowser = txBrowser
txBrowser.event.register('requestDebug', function (blockNumber, txNumber, tx) { txBrowser.event.register('requestDebug', (blockNumber, txNumber, tx) => {
if (self.debugger) self.debugger.unload() if (this.debugger) this.debugger.unload()
self.startDebugging(blockNumber, txNumber, tx) this.startDebugging(blockNumber, txNumber, tx)
}) })
txBrowser.event.register('unloadRequested', this, function (blockNumber, txIndex, tx) { txBrowser.event.register('unloadRequested', this, (blockNumber, txIndex, tx) => {
if (self.debugger) self.debugger.unload() if (this.debugger) this.debugger.unload()
}) })
} }
@ -113,13 +108,13 @@ class DebuggerUI {
getDebugWeb3 () { getDebugWeb3 () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.blockchain.detectNetwork((error, network) => { this.debuggerModule.blockchain.detectNetwork((error, network) => {
let web3 let web3
if (error || !network) { if (error || !network) {
web3 = init.web3DebugNode(this.blockchain.web3()) web3 = init.web3DebugNode(this.debuggerModule.blockchain.web3())
} else { } else {
const webDebugNode = init.web3DebugNode(network.name) const webDebugNode = init.web3DebugNode(network.name)
web3 = !webDebugNode ? this.blockchain.web3() : webDebugNode web3 = !webDebugNode ? this.debuggerModule.blockchain.web3() : webDebugNode
} }
init.extendWeb3(web3) init.extendWeb3(web3)
resolve(web3) resolve(web3)
@ -134,7 +129,7 @@ class DebuggerUI {
this.currentReceipt = await web3.eth.getTransactionReceipt(txNumber) this.currentReceipt = await web3.eth.getTransactionReceipt(txNumber)
this.debugger = new Debugger({ this.debugger = new Debugger({
web3, web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api, offsetToLineColumnConverter: globalRegistry.get('offsettolinecolumnconverter').api,
compilationResult: async (address) => { compilationResult: async (address) => {
try { try {
return await this.fetchContractAndCompile(address, this.currentReceipt) return await this.fetchContractAndCompile(address, this.currentReceipt)
@ -164,7 +159,7 @@ class DebuggerUI {
this.currentReceipt = await web3.eth.getTransactionReceipt(hash) this.currentReceipt = await web3.eth.getTransactionReceipt(hash)
const debug = new Debugger({ const debug = new Debugger({
web3, web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api, offsetToLineColumnConverter: globalRegistry.get('offsettolinecolumnconverter').api,
compilationResult: async (address) => { compilationResult: async (address) => {
try { try {
return await this.fetchContractAndCompile(address, this.currentReceipt) return await this.fetchContractAndCompile(address, this.currentReceipt)
@ -186,26 +181,28 @@ class DebuggerUI {
} }
render () { render () {
this.debuggerPanelsView = yo`<div class="${css.innerShift}"></div>` this.debuggerPanelsView = yo`<div class="px-2"></div>`
this.debuggerHeadPanelsView = yo`<div class="${css.innerShift}"></div>` this.debuggerHeadPanelsView = yo`<div class="px-2"></div>`
this.stepManagerView = yo`<div class="${css.innerShift}"></div>` this.stepManagerView = yo`<div class="px-2"></div>`
var view = yo`<div> var view = yo`
<div class="${css.innerShift}"> <div>
<div class="px-2">
${this.txBrowser.render()} ${this.txBrowser.render()}
${this.debuggerHeadPanelsView}
${this.stepManagerView} ${this.stepManagerView}
${this.debuggerHeadPanelsView}
</div> </div>
<div class="${css.statusMessage}">${this.statusMessage}</div> <div class="${css.statusMessage}">${this.statusMessage}</div>
${this.debuggerPanelsView} ${this.debuggerPanelsView}
</div>` </div>
`
if (!this.view) { if (!this.view) {
this.view = view this.view = view
} }
return view return view
} }
unLoad () { async unLoad () {
yo.update(this.debuggerHeadPanelsView, yo`<div></div>`) yo.update(this.debuggerHeadPanelsView, yo`<div></div>`)
yo.update(this.debuggerPanelsView, yo`<div></div>`) yo.update(this.debuggerPanelsView, yo`<div></div>`)
yo.update(this.stepManagerView, yo`<div></div>`) yo.update(this.stepManagerView, yo`<div></div>`)
@ -218,6 +215,10 @@ class DebuggerUI {
this.event.trigger('traceUnloaded') this.event.trigger('traceUnloaded')
} }
async deleteHighlights () {
await this.debuggerModule.call('editor', 'discardHighlight')
}
renderDebugger () { renderDebugger () {
yo.update(this.debuggerHeadPanelsView, this.vmDebugger.renderHead()) yo.update(this.debuggerHeadPanelsView, this.vmDebugger.renderHead())
yo.update(this.debuggerPanelsView, this.vmDebugger.render()) yo.update(this.debuggerPanelsView, this.vmDebugger.render())

@ -45,14 +45,14 @@ function ButtonNavigator () {
ButtonNavigator.prototype.render = function () { ButtonNavigator.prototype.render = function () {
var self = this var self = this
var view = yo`<div class="${css.buttons}"> var view = yo`<div class="${css.buttons}">
<div class="${css.stepButtons} btn-group p-1"> <div class="${css.stepButtons} btn-group py-1">
<button id='overback' class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-reply' title='Step over back' onclick=${function () { self.event.trigger('stepOverBack') }} disabled=${this.overBackDisabled} ></button> <button id='overback' class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-reply' title='Step over back' onclick=${function () { self.event.trigger('stepOverBack') }} disabled=${this.overBackDisabled} ></button>
<button id='intoback' data-id="buttonNavigatorIntoBack" class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-level-up-alt' title='Step back' onclick=${function () { self.event.trigger('stepIntoBack') }} disabled=${this.intoBackDisabled} ></button> <button id='intoback' data-id="buttonNavigatorIntoBack" class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-level-up-alt' title='Step back' onclick=${function () { self.event.trigger('stepIntoBack') }} disabled=${this.intoBackDisabled} ></button>
<button id='intoforward' data-id="buttonNavigatorIntoForward" class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-level-down-alt' title='Step into' onclick=${function () { self.event.trigger('stepIntoForward') }} disabled=${this.intoForwardDisabled} ></button> <button id='intoforward' data-id="buttonNavigatorIntoForward" class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-level-down-alt' title='Step into' onclick=${function () { self.event.trigger('stepIntoForward') }} disabled=${this.intoForwardDisabled} ></button>
<button id='overforward' class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-share' title='Step over forward'onclick=${function () { self.event.trigger('stepOverForward') }} disabled=${this.overForwardDisabled} ></button> <button id='overforward' class='btn btn-primary btn-sm ${css.navigator} ${css.stepButton} fas fa-share' title='Step over forward'onclick=${function () { self.event.trigger('stepOverForward') }} disabled=${this.overForwardDisabled} ></button>
</div> </div>
<div class="${css.jumpButtons} btn-group p-1"> <div class="${css.jumpButtons} btn-group py-1">
<button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-step-backward' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" title='Jump to the previous breakpoint' onclick=${function () { self.event.trigger('jumpPreviousBreakpoint') }} disabled=${this.jumpPreviousBreakpointDisabled} ></button> <button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-step-backward' id='jumppreviousbreakpoint' data-id="buttonNavigatorJumpPreviousBreakpoint" title='Jump to the previous breakpoint' onclick=${function () { self.event.trigger('jumpPreviousBreakpoint') }} disabled=${this.jumpPreviousBreakpointDisabled} ></button>
<button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-eject' id='jumpout' title='Jump out' onclick=${function () { self.event.trigger('jumpOut') }} disabled=${this.jumpOutDisabled} ></button> <button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-eject' id='jumpout' title='Jump out' onclick=${function () { self.event.trigger('jumpOut') }} disabled=${this.jumpOutDisabled} ></button>
<button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-step-forward' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" title='Jump to the next breakpoint' onclick=${function () { self.event.trigger('jumpNextBreakpoint') }} disabled=${this.jumpNextBreakpointDisabled} ></button> <button class='btn btn-primary btn-sm ${css.navigator} ${css.jumpButton} fas fa-step-forward' id='jumpnextbreakpoint' data-id="buttonNavigatorJumpNextBreakpoint" title='Jump to the next breakpoint' onclick=${function () { self.event.trigger('jumpNextBreakpoint') }} disabled=${this.jumpNextBreakpointDisabled} ></button>

@ -43,7 +43,7 @@ class Slider {
render () { render () {
var self = this var self = this
var view = yo`<div> var view = yo`<div>
<input id='slider' data-id="slider" style='width: 100%' type='range' min=0 max=${this.max} value=0 <input id='slider' data-id="slider" class='w-100 my-0' type='range' min=0 max=${this.max} value=0
onchange=${function () { self.onChange() }} oninput=${function () { self.onChange() }} disabled=${this.disabled} /> onchange=${function () { self.onChange() }} oninput=${function () { self.onChange() }} disabled=${this.disabled} />
</div>` </div>`
if (!this.view) { if (!this.view) {

@ -49,7 +49,8 @@ StepManager.prototype.remove = function () {
} }
StepManager.prototype.render = function () { StepManager.prototype.render = function () {
return yo`<div> return yo`
<div class="py-1">
${this.slider.render()} ${this.slider.render()}
${this.buttonNavigator.render()} ${this.buttonNavigator.render()}
</div>` </div>`

@ -11,11 +11,6 @@ var css = csjs`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.txinputs {
width: 100%;
display: flex;
justify-content: center;
}
.txinput { .txinput {
width: inherit; width: inherit;
font-size: small; font-size: small;
@ -23,16 +18,9 @@ var css = csjs`
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.txbuttons {
width: 100%;
display: flex;
justify-content: center;
}
.txbutton { .txbutton {
width: inherit; width: inherit;
} }
.txbuttonstart {
}
.txbutton:hover { .txbutton:hover {
} }
.vmargin { .vmargin {
@ -110,10 +98,10 @@ TxBrowser.prototype.render = function () {
var view = yo` var view = yo`
<div class="${css.container}"> <div class="${css.container}">
<div class="${css.txContainer}"> <div class="${css.txContainer}">
<div class="${css.txinputs} p-1 input-group"> <div class=" py-1 d-flex justify-content-center w-100 input-group">
${this.state.txNumberInput} ${this.state.txNumberInput}
</div> </div>
<div class="${css.txbuttons} btn-group p-1"> <div class="d-flex justify-content-center w-100 btn-group py-1">
${txButton} ${txButton}
</div> </div>
</div> </div>

@ -1,11 +1,11 @@
'use strict' 'use strict'
var csjs = require('csjs-inject')
var yo = require('yo-yo') var yo = require('yo-yo')
var CodeListView = require('./vmDebugger/CodeListView') var CodeListView = require('./vmDebugger/CodeListView')
var CalldataPanel = require('./vmDebugger/CalldataPanel') var CalldataPanel = require('./vmDebugger/CalldataPanel')
var MemoryPanel = require('./vmDebugger/MemoryPanel') var MemoryPanel = require('./vmDebugger/MemoryPanel')
var CallstackPanel = require('./vmDebugger/CallstackPanel') var CallstackPanel = require('./vmDebugger/CallstackPanel')
var FunctionPanel = require('./vmDebugger/FunctionPanel')
var StackPanel = require('./vmDebugger/StackPanel') var StackPanel = require('./vmDebugger/StackPanel')
var StoragePanel = require('./vmDebugger/StoragePanel') var StoragePanel = require('./vmDebugger/StoragePanel')
var StepDetail = require('./vmDebugger/StepDetail') var StepDetail = require('./vmDebugger/StepDetail')
@ -14,18 +14,6 @@ var SolidityLocals = require('./vmDebugger/SolidityLocals')
var FullStoragesChangesPanel = require('./vmDebugger/FullStoragesChanges') var FullStoragesChangesPanel = require('./vmDebugger/FullStoragesChanges')
var DropdownPanel = require('./vmDebugger/DropdownPanel') var DropdownPanel = require('./vmDebugger/DropdownPanel')
var css = csjs`
.asmCode {
width: 100%;
}
.stepDetail {
width: 100%;
}
.vmheadView {
margin-top:10px;
}
`
function VmDebugger (vmDebuggerLogic) { function VmDebugger (vmDebuggerLogic) {
var self = this var self = this
this.view this.view
@ -48,6 +36,16 @@ function VmDebugger (vmDebuggerLogic) {
this.stackPanel = new StackPanel() this.stackPanel = new StackPanel()
this.vmDebuggerLogic.event.register('traceManagerStackUpdate', this.stackPanel.update.bind(this.stackPanel)) this.vmDebuggerLogic.event.register('traceManagerStackUpdate', this.stackPanel.update.bind(this.stackPanel))
this.functionPanel = new FunctionPanel()
this.vmDebuggerLogic.event.register('functionsStackUpdate', (stack) => {
if (stack === null) return
let functions = []
for (let func of stack) {
functions.push(func.functionDefinition.attributes.name + '(' + func.inputs.join(', ') + ')')
}
this.functionPanel.update(functions)
})
this.storagePanel = new StoragePanel() this.storagePanel = new StoragePanel()
this.vmDebuggerLogic.event.register('traceManagerStorageUpdate', this.storagePanel.update.bind(this.storagePanel)) this.vmDebuggerLogic.event.register('traceManagerStorageUpdate', this.storagePanel.update.bind(this.storagePanel))
@ -107,27 +105,39 @@ function VmDebugger (vmDebuggerLogic) {
self.asmCode.basicPanel.show() self.asmCode.basicPanel.show()
self.stackPanel.basicPanel.show() self.stackPanel.basicPanel.show()
self.functionPanel.basicPanel.show()
self.storagePanel.basicPanel.show() self.storagePanel.basicPanel.show()
self.memoryPanel.basicPanel.show() self.memoryPanel.basicPanel.show()
self.stepDetail.basicPanel.show()
self.calldataPanel.basicPanel.show() self.calldataPanel.basicPanel.show()
self.callstackPanel.basicPanel.show() self.callstackPanel.basicPanel.show()
}) })
this.vmDebuggerLogic.event.register('newCallTree', () => { this.vmDebuggerLogic.event.register('newCallTree', () => {
if (!self.view) return if (!self.view) return
self.functionPanel.basicPanel.show()
self.solidityLocals.basicPanel.show() self.solidityLocals.basicPanel.show()
self.solidityState.basicPanel.show() self.solidityState.basicPanel.show()
self.solidityPanel.hidden = false
}) })
this.vmDebuggerLogic.start() this.vmDebuggerLogic.start()
} }
VmDebugger.prototype.renderHead = function () { VmDebugger.prototype.renderHead = function () {
this.solidityPanel = yo`
<div class="w-100" hidden>
${this.functionPanel.render()}
${this.solidityLocals.render()}
${this.solidityState.render()}
</div>
`
const headView = yo` const headView = yo`
<div id="vmheadView" class="${css.vmheadView} container"> <div id="vmheadView" class="mt-1 px-0">
<div class="row" > <div class="d-flex flex-column">
<div class="${css.asmCode} column">${this.asmCode.render()}</div> ${this.solidityPanel}
<div class="${css.stepDetail} column">${this.stepDetail.render()}</div> <div class="w-100">${this.asmCode.render()}</div>
<div class="w-100">${this.stepDetail.render()}</div>
</div> </div>
</div> </div>
` `
@ -144,10 +154,8 @@ VmDebugger.prototype.remove = function () {
VmDebugger.prototype.render = function () { VmDebugger.prototype.render = function () {
const view = yo` const view = yo`
<div id="vmdebugger" class="pl-2"> <div id="vmdebugger" class="px-2">
<div> <div>
${this.solidityLocals.render()}
${this.solidityState.render()}
${this.stackPanel.render()} ${this.stackPanel.render()}
${this.memoryPanel.render()} ${this.memoryPanel.render()}
${this.storagePanel.render()} ${this.storagePanel.render()}

@ -9,14 +9,6 @@ module.exports = {
font: { font: {
'font-family': 'arial,sans-serif' 'font-family': 'arial,sans-serif'
}, },
innerShift: {
'padding': '2px',
'margin-left': '10px'
},
container: {
'margin': '10px',
'padding': '5px'
},
statusMessage: { statusMessage: {
'margin-left': '15px' 'margin-left': '15px'
}, },

@ -8,7 +8,7 @@ var csjs = require('csjs-inject')
var css = csjs` var css = csjs`
.instructions { .instructions {
overflow-y: scroll; overflow-y: scroll;
max-height: 150px; max-height: 130px;
} }
` `
function CodeListView () { function CodeListView () {
@ -41,12 +41,9 @@ CodeListView.prototype.indexChanged = function (index) {
} }
let codeView = this.view.querySelector('#asmitems') let codeView = this.view.querySelector('#asmitems')
this.itemSelected = codeView.children[index] this.itemSelected = codeView.children[index]
this.itemSelected.style.setProperty('background-color', 'var(--info)') this.itemSelected.style.setProperty('border-color', 'var(--primary)')
this.itemSelected.style.setProperty('color', 'var(--light)') this.itemSelected.style.setProperty('border-style', 'solid')
this.itemSelected.setAttribute('selected', 'selected') this.itemSelected.setAttribute('selected', 'selected')
if (this.itemSelected.firstChild) {
this.itemSelected.firstChild.setAttribute('style', 'margin-left: 2px')
}
codeView.scrollTop = this.itemSelected.offsetTop - parseInt(codeView.offsetTop) codeView.scrollTop = this.itemSelected.offsetTop - parseInt(codeView.offsetTop)
} }
@ -67,9 +64,9 @@ CodeListView.prototype.changed = function (code, address, index) {
CodeListView.prototype.renderAssemblyItems = function () { CodeListView.prototype.renderAssemblyItems = function () {
if (this.code) { if (this.code) {
var codeView = this.code.map(function (item, i) { var codeView = this.code.map(function (item, i) {
return yo`<div key=${i} value=${i}><span>${item}</span></div>` return yo`<div class="px-1" key=${i} value=${i}><span>${item}</span></div>`
}) })
return yo`<div class=${css.instructions} id='asmitems' ref='itemsList'> return yo`<div class="pl-2 my-1 small ${css.instructions}" id='asmitems' ref='itemsList'>
${codeView} ${codeView}
</div>` </div>`
} }

@ -1,12 +1,12 @@
'use strict' 'use strict'
var yo = require('yo-yo') const yo = require('yo-yo')
const copy = require('copy-text-to-clipboard') const copyToClipboard = require('../../../../ui/copy-to-clipboard')
var EventManager = require('../../../../../lib/events') const EventManager = require('../../../../../lib/events')
var TreeView = require('../../../../ui/TreeView') // TODO setup a direct reference to the UI components const TreeView = require('../../../../ui/TreeView') // TODO setup a direct reference to the UI components
var csjs = require('csjs-inject') const csjs = require('csjs-inject')
var css = csjs` const css = csjs`
.title { .title {
display: flex; display: flex;
align-items: center; align-items: center;
@ -24,8 +24,6 @@ var css = csjs`
.eyeButton { .eyeButton {
margin: 3px; margin: 3px;
} }
.eyeButton:hover {
}
.dropdownpanel { .dropdownpanel {
width: 100%; width: 100%;
word-break: break-word; word-break: break-word;
@ -87,7 +85,7 @@ DropdownPanel.prototype.update = function (_data, _header) {
this.view.querySelector('.dropdownpanel .dropdowncontent').style.display = 'block' this.view.querySelector('.dropdownpanel .dropdowncontent').style.display = 'block'
this.view.querySelector('.dropdownpanel .dropdownrawcontent').innerText = JSON.stringify(_data, null, '\t') this.view.querySelector('.dropdownpanel .dropdownrawcontent').innerText = JSON.stringify(_data, null, '\t')
if (!this.displayContentOnly) { if (!this.displayContentOnly) {
this.view.querySelector('.title div.btn').style.display = 'block' this.view.querySelector('.title i.fa-copy').style.display = 'block'
this.view.querySelector('.title span').innerText = _header || ' ' this.view.querySelector('.title span').innerText = _header || ' '
} }
this.message('') this.message('')
@ -101,6 +99,11 @@ DropdownPanel.prototype.setContent = function (node) {
yo.update(this.view, this.render(null, node)) yo.update(this.view, this.render(null, node))
} }
DropdownPanel.prototype.copyClipboard = function () {
const content = this.view.querySelector('.dropdownpanel .dropdownrawcontent')
return content.innerText ? content.innerText : content.textContent
}
DropdownPanel.prototype.render = function (overridestyle, node) { DropdownPanel.prototype.render = function (overridestyle, node) {
var content = yo`<div>Empty</div>` var content = yo`<div>Empty</div>`
if (this.json) { if (this.json) {
@ -108,20 +111,21 @@ DropdownPanel.prototype.render = function (overridestyle, node) {
} }
overridestyle === undefined ? {} : overridestyle overridestyle === undefined ? {} : overridestyle
var self = this var self = this
var title = !self.displayContentOnly ? yo`<div class="${css.title} title"> var contentNode = yo`
<div class="${css.icon} fas fa-caret-right" onclick=${function () { self.toggle() }} ></div> <div class='dropdownpanel ${css.dropdownpanel}' style='display:none'>
<div class="${css.name}" onclick=${function () { self.toggle() }} >${this.name}</div><span class="${css.nameDetail}" onclick=${function () { self.toggle() }} ></span>
<div onclick=${function () { self.copyClipboard() }} title='raw' class="${css.eyeButton} btn far fa-clipboard"></div>
</div>` : yo`<div></div>`
var contentNode = yo`<div class='dropdownpanel ${css.dropdownpanel}' style='display:none'>
<i class="${css.refresh} fas fa-sync" aria-hidden="true"></i> <i class="${css.refresh} fas fa-sync" aria-hidden="true"></i>
<div class='dropdowncontent'>${node || content}</div> <div class='dropdowncontent'>${node || content}</div>
<div class='dropdownrawcontent' style='display:none'></div> <div class='dropdownrawcontent' style='display:none'></div>
<div class='message' style='display:none'></div> <div class='message' style='display:none'></div>
</div>` </div>`
var title = !self.displayContentOnly ? yo`<div class="${css.title} py-0 px-1 title">
<div class="${css.icon} fas fa-caret-right" onclick=${function () { self.toggle() }} ></div>
<div class="${css.name}" onclick=${function () { self.toggle() }} >${this.name}</div><span class="${css.nameDetail}" onclick=${function () { self.toggle() }} ></span>
${copyToClipboard(() => this.copyClipboard())}
</div>` : yo`<div></div>`
var view = yo` var view = yo`
<div class="border rounded p-1 m-1 bg-light"> <div class="border rounded px-1 mt-1 bg-light">
<style> <style>
@-moz-keyframes spin { @-moz-keyframes spin {
to { -moz-transform: rotate(359deg); } to { -moz-transform: rotate(359deg); }
@ -143,11 +147,6 @@ DropdownPanel.prototype.render = function (overridestyle, node) {
return view return view
} }
DropdownPanel.prototype.copyClipboard = function () {
var content = this.view.querySelector('.dropdownpanel .dropdownrawcontent')
if (content) copy(content.innerText ? content.innerText : content.textContent)
}
DropdownPanel.prototype.toggle = function () { DropdownPanel.prototype.toggle = function () {
var el = this.view.querySelector('.dropdownpanel') var el = this.view.querySelector('.dropdownpanel')
var caret = this.view.querySelector('.title').firstElementChild var caret = this.view.querySelector('.title').firstElementChild

@ -0,0 +1,17 @@
'use strict'
var DropdownPanel = require('./DropdownPanel')
var yo = require('yo-yo')
function FunctionPanel () {
this.basicPanel = new DropdownPanel('Function Stack', {json: true, displayContentOnly: false})
}
FunctionPanel.prototype.update = function (calldata) {
this.basicPanel.update(calldata)
}
FunctionPanel.prototype.render = function () {
return yo`<div id="FunctionPanel">${this.basicPanel.render()}</div>`
}
module.exports = FunctionPanel

@ -26,7 +26,8 @@ SolidityState.prototype.setUpdating = function () {
SolidityState.prototype.render = function () { SolidityState.prototype.render = function () {
if (this.view) return if (this.view) return
this.view = yo`<div id='soliditystate' > this.view = yo`
<div id='soliditystate' >
${this.basicPanel.render()} ${this.basicPanel.render()}
</div>` </div>`
return this.view return this.view

@ -2,7 +2,7 @@ var yo = require('yo-yo')
var DropdownPanel = require('./DropdownPanel') var DropdownPanel = require('./DropdownPanel')
function StepDetail () { function StepDetail () {
this.basicPanel = new DropdownPanel('Step detail', {json: true, displayContentOnly: true}) this.basicPanel = new DropdownPanel('Step details', {json: true, displayContentOnly: false})
this.detail = { 'vm trace step': '-', 'execution step': '-', 'add memory': '', 'gas': '', 'remaining gas': '-', 'loaded address': '-' } this.detail = { 'vm trace step': '-', 'execution step': '-', 'add memory': '', 'gas': '', 'remaining gas': '-', 'loaded address': '-' }
} }

@ -12,7 +12,7 @@ function formatSelf (key, data) {
if (data.type === 'string') { if (data.type === 'string') {
data.self = JSON.stringify(data.self) data.self = JSON.stringify(data.self)
} }
return yo `<label style='${keyStyle};white-space:pre-wrap;'> ${' ' + key}:<label style=${style}>${' ' + data.self}</label><label style='font-style:italic'> ${data.isProperty || !data.type ? '' : ' ' + data.type}</label></label>` return yo `<label class="mb-0" style='${keyStyle};white-space:pre-wrap;'> ${' ' + key}:<label class="mb-0" style=${style}>${' ' + data.self}</label><label style='font-style:italic'> ${data.isProperty || !data.type ? '' : ' ' + data.type}</label></label>`
} }
function extractData (item, parent, key) { function extractData (item, parent, key) {

@ -34,6 +34,9 @@ class DropdownLogic {
this.runView.on('lexon', 'compilationFinished', (file, source, languageVersion, data) => this.runView.on('lexon', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data) broadcastCompilationResult(file, source, languageVersion, data)
) )
this.runView.on('yulp', 'compilationFinished', (file, source, languageVersion, data) =>
broadcastCompilationResult(file, source, languageVersion, data)
)
} }
loadContractFromAddress (address, confirmCb, cb) { loadContractFromAddress (address, confirmCb, cb) {

@ -12,7 +12,7 @@ const profile = {
displayName: 'Settings', displayName: 'Settings',
methods: ['getGithubAccessToken'], methods: ['getGithubAccessToken'],
events: [], events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik0xMTUyIDg5NnEwLTEwNi03NS0xODF0LTE4MS03NS0xODEgNzUtNzUgMTgxIDc1IDE4MSAxODEgNzUgMTgxLTc1IDc1LTE4MXptNTEyLTEwOXYyMjJxMCAxMi04IDIzdC0yMCAxM2wtMTg1IDI4cS0xOSA1NC0zOSA5MSAzNSA1MCAxMDcgMTM4IDEwIDEyIDEwIDI1dC05IDIzcS0yNyAzNy05OSAxMDh0LTk0IDcxcS0xMiAwLTI2LTlsLTEzOC0xMDhxLTQ0IDIzLTkxIDM4LTE2IDEzNi0yOSAxODYtNyAyOC0zNiAyOGgtMjIycS0xNCAwLTI0LjUtOC41dC0xMS41LTIxLjVsLTI4LTE4NHEtNDktMTYtOTAtMzdsLTE0MSAxMDdxLTEwIDktMjUgOS0xNCAwLTI1LTExLTEyNi0xMTQtMTY1LTE2OC03LTEwLTctMjMgMC0xMiA4LTIzIDE1LTIxIDUxLTY2LjV0NTQtNzAuNXEtMjctNTAtNDEtOTlsLTE4My0yN3EtMTMtMi0yMS0xMi41dC04LTIzLjV2LTIyMnEwLTEyIDgtMjN0MTktMTNsMTg2LTI4cTE0LTQ2IDM5LTkyLTQwLTU3LTEwNy0xMzgtMTAtMTItMTAtMjQgMC0xMCA5LTIzIDI2LTM2IDk4LjUtMTA3LjV0OTQuNS03MS41cTEzIDAgMjYgMTBsMTM4IDEwN3E0NC0yMyA5MS0zOCAxNi0xMzYgMjktMTg2IDctMjggMzYtMjhoMjIycTE0IDAgMjQuNSA4LjV0MTEuNSAyMS41bDI4IDE4NHE0OSAxNiA5MCAzN2wxNDItMTA3cTktOSAyNC05IDEzIDAgMjUgMTAgMTI5IDExOSAxNjUgMTcwIDcgOCA3IDIyIDAgMTItOCAyMy0xNSAyMS01MSA2Ni41dC01NCA3MC41cTI2IDUwIDQxIDk4bDE4MyAyOHExMyAyIDIxIDEyLjV0OCAyMy41eiIvPjwvc3ZnPg==', icon: 'assets/img/settings.webp',
description: 'Remix-IDE settings', description: 'Remix-IDE settings',
kind: 'settings', kind: 'settings',
location: 'sidePanel', location: 'sidePanel',
@ -69,7 +69,7 @@ module.exports = class SettingsTab extends ViewPlugin {
// Gist settings // Gist settings
const token = this.config.get('settings/gist-access-token') const token = this.config.get('settings/gist-access-token')
const gistAccessToken = yo`<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" class="border form-control-sm mb-2 ${css.inline}" placeholder="Token">` const gistAccessToken = yo`<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" class="form-control border form-control-sm mb-2 ${css.inline}" placeholder="Token">`
if (token) gistAccessToken.value = token if (token) gistAccessToken.value = token
const removeToken = () => { self.config.set('settings/gist-access-token', ''); gistAccessToken.value = ''; tooltip('Access token removed') } const removeToken = () => { self.config.set('settings/gist-access-token', ''); gistAccessToken.value = ''; tooltip('Access token removed') }
const saveToken = () => { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') } const saveToken = () => { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }

@ -169,7 +169,7 @@ staticAnalysisView.prototype.run = function () {
<span class="d-flex flex-column"> <span class="d-flex flex-column">
<span class="h6 font-weight-bold">${result.name}</span> <span class="h6 font-weight-bold">${result.name}</span>
${item.warning} ${item.warning}
${item.more ? yo`<span><a href="${item.more}" target="blank">more</a></span>` : yo`<span></span>`} ${item.more ? yo`<span><a href="${item.more}" target="_blank">more</a></span>` : yo`<span></span>`}
<span class="" title="Position in ${fileName}">Pos: ${locationString}</span> <span class="" title="Position in ${fileName}">Pos: ${locationString}</span>
</span>` </span>`
self._components.renderer.error( self._components.renderer.error(

@ -4,7 +4,7 @@ var tooltip = require('../ui/tooltip')
var css = require('./styles/test-tab-styles') var css = require('./styles/test-tab-styles')
var remixTests = require('@remix-project/remix-tests') var remixTests = require('@remix-project/remix-tests')
import { ViewPlugin } from '@remixproject/engine' import { ViewPlugin } from '@remixproject/engine'
import { canUseWorker, baseUrl } from '../compiler/compiler-utils' import { canUseWorker, urlFromVersion } from '../compiler/compiler-utils'
const TestTabLogic = require('./testTab/testTab') const TestTabLogic = require('./testTab/testTab')
@ -13,7 +13,7 @@ const profile = {
displayName: 'Solidity unit testing', displayName: 'Solidity unit testing',
methods: ['testFromPath', 'testFromSource'], methods: ['testFromPath', 'testFromSource'],
events: [], events: [],
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wUDEhQZ0zbrmQAAAfNJREFUWMPF17lrFVEUx/EPaIKovfAScSndjUtULFQSYhHF0r/Dwsa/RywUiTaWgvaChWsiKkSMZte4o7G5A49x7r0zLy/PA6eZOef3PXebuYfu2xCmcQ9b9NgOYw6rwR9ia6/gR7HQBi/8PjavN/w4FivghV9bT/gwlhLwHzjTVPQ8rqAvE3ciA/+O8abwy/gVBG4lijiJ5czIL64FXvhNbCzFnaoBv9AUPo7fEcEb2BDiTuNTAv4NYxX6u/EIM7GZuZoQXcX1sJk+J2K+YrRCexfetsX9xKVyUB9uZ4r4k3j3BSMR+JvIMv2zQfsxkSkiBj9XAd8ZgRf+vmop+nGnAXwlcs534HUm93FsQ9YtIjby7XiVyZ3BntSpyBWxgrMR+FQG/gF76xzNftxtMO1rgo+G5AdBqLBN4d9eCCyHD1En8Oi0j4UPSBE4hcFSERN4Fz7BZRvEZKcjHynBC5/EQI1lGqgJ3xcTmE4kvswUMRBiUvCPKTg8zQi8QKsirxXe5eD7c1N4ALMZoeelIlrhWSpnNmjXsoM1iihmYhueZGIXcKTp7/hQ6UZb5c+Cp2LmglZHVqeIlC+G2/GarNMiFnGsWzfdpkV0Fd7e5czXgC+FvmDdWq35/wVvbzbnI/DhXvV9Q6W+r6fw9hZsKnjX4H8B0Aamri7CrBsAAAAASUVORK5CYII=', icon: 'assets/img/unitTesting.webp',
description: 'Fast tool to generate unit tests for your contracts', description: 'Fast tool to generate unit tests for your contracts',
location: 'sidePanel', location: 'sidePanel',
documentation: 'https://remix-ide.readthedocs.io/en/latest/unittesting.html' documentation: 'https://remix-ide.readthedocs.io/en/latest/unittesting.html'
@ -33,11 +33,13 @@ module.exports = class TestTab extends ViewPlugin {
this.runningTestsNumber = 0 this.runningTestsNumber = 0
this.readyTestsNumber = 0 this.readyTestsNumber = 0
this.areTestsRunning = false this.areTestsRunning = false
this.defaultPath = 'browser/tests'
appManager.event.on('activate', (name) => { appManager.event.on('activate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile()) if (name === 'solidity') this.updateRunAction()
}) })
appManager.event.on('deactivate', (name) => { appManager.event.on('deactivate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile()) if (name === 'solidity') this.updateRunAction()
}) })
} }
@ -56,9 +58,6 @@ module.exports = class TestTab extends ViewPlugin {
}) })
this.fileManager.events.on('noFileSelected', () => { this.fileManager.events.on('noFileSelected', () => {
this.updateGenerateFileAction()
this.updateRunAction()
this.updateTestFileList()
}) })
this.fileManager.events.on('currentFileChanged', (file, provider) => this.updateForNewCurrent(file)) this.fileManager.events.on('currentFileChanged', (file, provider) => this.updateForNewCurrent(file))
@ -86,6 +85,7 @@ module.exports = class TestTab extends ViewPlugin {
} }
listTests () { listTests () {
if (!this.data.allTests) return []
return this.data.allTests.map( return this.data.allTests.map(
testFile => this.createSingleTest(testFile) testFile => this.createSingleTest(testFile)
) )
@ -301,7 +301,7 @@ module.exports = class TestTab extends ViewPlugin {
let runningTest = {} let runningTest = {}
runningTest[path] = { content } runningTest[path] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig() const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = baseUrl + '/' + currentVersion const currentCompilerUrl = urlFromVersion(currentVersion)
const compilerConfig = { const compilerConfig = {
currentCompilerUrl, currentCompilerUrl,
evmVersion, evmVersion,
@ -327,7 +327,7 @@ module.exports = class TestTab extends ViewPlugin {
const runningTest = {} const runningTest = {}
runningTest[testFilePath] = { content } runningTest[testFilePath] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig() const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = baseUrl + '/' + currentVersion const currentCompilerUrl = urlFromVersion(currentVersion)
const compilerConfig = { const compilerConfig = {
currentCompilerUrl, currentCompilerUrl,
evmVersion, evmVersion,
@ -351,6 +351,13 @@ module.exports = class TestTab extends ViewPlugin {
}) })
} }
updateCurrentPath (e) {
const newValue = e.target.value === '' ? this.defaultPath : e.target.value
this.testTabLogic.setCurrentPath(newValue)
this.updateRunAction()
this.updateForNewCurrent()
}
runTests () { runTests () {
this.areTestsRunning = true this.areTestsRunning = true
this.hasBeenStopped = false this.hasBeenStopped = false
@ -386,21 +393,16 @@ module.exports = class TestTab extends ViewPlugin {
} }
updateGenerateFileAction (currentFile) { updateGenerateFileAction (currentFile) {
let el = yo`<button let el = yo`
<button
class="btn border w-50" class="btn border w-50"
data-id="testTabGenerateTestFile" data-id="testTabGenerateTestFile"
title="Generate sample test file." title="Generate sample test file."
onclick="${this.testTabLogic.generateTestFile.bind(this.testTabLogic)}" onclick="${this.testTabLogic.generateTestFile.bind(this.testTabLogic)}"
> >
Generate Generate
</button>` </button>
if ( `
!currentFile ||
(currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')
) {
el.setAttribute('disabled', 'disabled')
el.setAttribute('title', 'No solidity file selected')
}
if (!this.generateFileActionElement) { if (!this.generateFileActionElement) {
this.generateFileActionElement = el this.generateFileActionElement = el
} else { } else {
@ -416,8 +418,8 @@ module.exports = class TestTab extends ViewPlugin {
<label class="${css.labelOnBtn} btn btn-primary p-1 ml-2 m-0">Run</label> <label class="${css.labelOnBtn} btn btn-primary p-1 ml-2 m-0">Run</label>
</button> </button>
` `
const isSolidityActive = this.appManager.actives.includes('solidity') const isSolidityActive = this.appManager.isActive('solidity')
if (!currentFile || !isSolidityActive || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) { if (!isSolidityActive || !this.listTests().length) {
el.setAttribute('disabled', 'disabled') el.setAttribute('disabled', 'disabled')
if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) { if (!currentFile || (currentFile && currentFile.split('.').pop().toLowerCase() !== 'sol')) {
el.setAttribute('title', 'No solidity file selected') el.setAttribute('title', 'No solidity file selected')
@ -451,6 +453,7 @@ module.exports = class TestTab extends ViewPlugin {
} else { } else {
yo.update(this.testFilesListElement, el) yo.update(this.testFilesListElement, el)
} }
this.updateRunAction()
return this.testFilesListElement return this.testFilesListElement
} }
@ -482,11 +485,40 @@ module.exports = class TestTab extends ViewPlugin {
return yo`<span class='text-info h6'>Progress: ${ready} finished (of ${this.runningTestsNumber})</span>` return yo`<span class='text-info h6'>Progress: ${ready} finished (of ${this.runningTestsNumber})</span>`
} }
updateDirList () {
for (var o of this.uiPathList.querySelectorAll('option')) o.remove()
this.uiPathList.appendChild(yo`<option>browser</option>`)
if (this.testTabLogic.isRemixDActive()) this.uiPathList.appendChild(yo`<option>localhost</option>`)
if (!this._view.el) return
this.testTabLogic.dirList(this.inputPath.value).then((options) => {
options.forEach((path) => this.uiPathList.appendChild(yo`<option>${path}</option>`))
})
}
render () { render () {
this.onActivationInternal() this.onActivationInternal()
this.testsOutput = yo`<div class="mx-3 mb-2 pb-4 border-top border-primary" hidden='true' id="solidityUnittestsOutput" data-id="testTabSolidityUnitTestsOutput"></a>` this.testsOutput = yo`<div class="mx-3 mb-2 pb-4 border-top border-primary" hidden='true' id="solidityUnittestsOutput" data-id="testTabSolidityUnitTestsOutput"></a>`
this.testsExecutionStopped = yo`<label class="text-warning h6" data-id="testTabTestsExecutionStopped">The test execution has been stopped</label>` this.testsExecutionStopped = yo`<label class="text-warning h6" data-id="testTabTestsExecutionStopped">The test execution has been stopped</label>`
this.testsExecutionStoppedError = yo`<label class="text-danger h6" data-id="testTabTestsExecutionStoppedError">The test execution has been stopped because of error(s) in your test file</label>` this.testsExecutionStoppedError = yo`<label class="text-danger h6" data-id="testTabTestsExecutionStoppedError">The test execution has been stopped because of error(s) in your test file</label>`
this.uiPathList = yo`<datalist id="utPathList"></datalist>`
this.inputPath = yo`<input
placeholder=${this.defaultPath}
list="utPathList"
class="custom-select"
id="utPath"
data-id="uiPathInput"
name="utPath"
style="background-image: var(--primary);"
onkeydown=${(e) => { if (e.keyCode === 191) this.updateDirList() }}
onchange=${(e) => this.updateCurrentPath(e)}/>`
const availablePaths = yo`
<div>
${this.inputPath}
${this.uiPathList}
</div>
`
this.updateDirList()
this.testsExecutionStopped.hidden = true this.testsExecutionStopped.hidden = true
this.testsExecutionStoppedError.hidden = true this.testsExecutionStoppedError.hidden = true
this.resultStatistics = this.createResultLabel() this.resultStatistics = this.createResultLabel()
@ -495,7 +527,9 @@ module.exports = class TestTab extends ViewPlugin {
<div class="${css.testTabView} px-2" id="testView"> <div class="${css.testTabView} px-2" id="testView">
<div class="${css.infoBox}"> <div class="${css.infoBox}">
<p class="text-lg"> Test your smart contract in Solidity.</p> <p class="text-lg"> Test your smart contract in Solidity.</p>
<p> Click on "Generate" to generate a sample test file.</p> <p> Select directory to load and generate test files.</p>
<label>Test directory:</label>
${availablePaths}
</div> </div>
<div class="${css.tests}"> <div class="${css.tests}">
<div class="d-flex p-2"> <div class="d-flex p-2">
@ -518,6 +552,7 @@ module.exports = class TestTab extends ViewPlugin {
</div> </div>
` `
this._view.el = el this._view.el = el
this.testTabLogic.setCurrentPath(this.defaultPath)
this.updateForNewCurrent(this.fileManager.currentFile()) this.updateForNewCurrent(this.fileManager.currentFile())
return el return el
} }

@ -1,52 +1,71 @@
const helper = require('../../../lib/helper.js') const helper = require('../../../lib/helper.js')
const modalDialogCustom = require('../../ui/modal-dialog-custom') const modalDialogCustom = require('../../ui/modal-dialog-custom')
const remixPath = require('path')
class TestTabLogic { class TestTabLogic {
constructor (fileManager) { constructor (fileManager) {
this.fileManager = fileManager this.fileManager = fileManager
this.currentPath = 'browser/tests'
}
setCurrentPath (path) {
if (path.indexOf('/') === 0) return
this.currentPath = path
const fileProvider = this.fileManager.fileProviderOf(path.split('/')[0])
fileProvider.exists(path, (e, res) => { if (!res) fileProvider.createDir(path) })
} }
generateTestFile () { generateTestFile () {
const path = this.fileManager.currentPath() let fileName = this.fileManager.currentFile()
const fileName = this.fileManager.currentFile() const hasCurrent = !!fileName && this.fileManager.currentFile().split('.').pop().toLowerCase() === 'sol'
const fileProvider = this.fileManager.fileProviderOf(path) if (!hasCurrent) fileName = this.currentPath + '/newFile.sol'
const fileProvider = this.fileManager.fileProviderOf(this.currentPath)
if (!fileProvider) return if (!fileProvider) return
helper.createNonClashingNameWithPrefix(fileName, fileProvider, '_test', (error, newFile) => {
if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error)
const splittedFileName = fileName.split('/') const splittedFileName = fileName.split('/')
// This is fine for now because test file is created on same path where file to be tested is. let fileNameToImport = (!hasCurrent) ? fileName : this.currentPath + '/' + splittedFileName[splittedFileName.length - 1]
// This should be updated to pass complete path, if test file comes from different directory/path helper.createNonClashingNameWithPrefix(fileNameToImport, fileProvider, '_test', (error, newFile) => {
const fileNameToImport = splittedFileName[splittedFileName.length - 1] if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error)
if (!fileProvider.set(newFile, this.generateTestContractSample(fileNameToImport))) return modalDialogCustom.alert('Failed to create test file ' + newFile) if (!fileProvider.set(newFile, this.generateTestContractSample(hasCurrent, fileName))) return modalDialogCustom.alert('Failed to create test file ' + newFile)
this.fileManager.open(newFile) this.fileManager.open(newFile)
this.fileManager.syncEditor(newFile)
}) })
} }
dirList (path) {
return this.fileManager.dirList(path)
}
isRemixDActive () {
return this.fileManager.isRemixDActive()
}
async getTests (cb) { async getTests (cb) {
const path = this.fileManager.currentPath() if (!this.currentPath) return cb(null, [])
if (!path) return cb(null, []) const provider = this.fileManager.fileProviderOf(this.currentPath)
const provider = this.fileManager.fileProviderOf(path)
if (!provider) return cb(null, []) if (!provider) return cb(null, [])
const tests = [] const tests = []
let files let files
try { try {
files = await this.fileManager.readdir(path) files = await this.fileManager.readdir(this.currentPath)
} catch (e) { } catch (e) {
cb(e.message) cb(e.message)
} }
for (var file in files) { for (var file in files) {
if (/.(_test.sol)$/.exec(file)) tests.push(provider.type + '/' + file) if (/.(_test.sol)$/.exec(file)) tests.push(provider.type + '/' + file)
} }
cb(null, tests, path) cb(null, tests, this.currentPath)
} }
// @todo(#2758): If currently selected file is compiled and compilation result is available, // @todo(#2758): If currently selected file is compiled and compilation result is available,
// 'contractName' should be <compiledContractName> + '_testSuite' // 'contractName' should be <compiledContractName> + '_testSuite'
generateTestContractSample (fileToImport, contractName = 'testSuite') { generateTestContractSample (hasCurrent, fileToImport, contractName = 'testSuite') {
let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport))
if (relative === '') relative = '.'
const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// Import here the file to test.'
return `pragma solidity >=0.4.22 <0.7.0; return `pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
import "./${fileToImport}"; ${comment}
// File name has to end with '_test.sol', this file can contain more than one testSuite contracts // File name has to end with '_test.sol', this file can contain more than one testSuite contracts
contract ${contractName} { contract ${contractName} {
@ -65,7 +84,7 @@ contract ${contractName} {
Assert.notEqual(uint(2), uint(3), "2 should not be equal to 3"); Assert.notEqual(uint(2), uint(3), "2 should not be equal to 3");
} }
function checkSuccess2() public view returns (bool) { function checkSuccess2() public pure returns (bool) {
// Use the return value (true or false) to test the contract // Use the return value (true or false) to test the contract
return true; return true;
} }

@ -1,11 +1,12 @@
import { Plugin } from '@remixproject/engine' import { Plugin } from '@remixproject/engine'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import QueryParams from '../../lib/query-params'
import * as packageJson from '../../../package.json' import * as packageJson from '../../../package.json'
import yo from 'yo-yo' import yo from 'yo-yo'
const themes = [ const themes = [
{name: 'Dark', quality: 'dark', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1584965247/remix-dark_tmkdla.css'}, {name: 'Dark', quality: 'dark', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1584965247/remix-dark_tmkdla.css'},
{name: 'Light', quality: 'light', url: 'https://res.cloudinary.com/dvtmp0niu/raw/upload/v1584966540/remix-light_t0c780.css'}, {name: 'Light', quality: 'light', url: 'https://res.cloudinary.com/remix/raw/upload/v1594059208/remix-themes/remix-light_csxus2.css'},
{name: 'Cerulean', quality: 'light', url: 'https://bootswatch.com/4/cerulean/bootstrap.min.css'}, {name: 'Cerulean', quality: 'light', url: 'https://bootswatch.com/4/cerulean/bootstrap.min.css'},
{name: 'Flatly', quality: 'light', url: 'https://bootswatch.com/4/flatly/bootstrap.min.css'}, {name: 'Flatly', quality: 'light', url: 'https://bootswatch.com/4/flatly/bootstrap.min.css'},
@ -17,7 +18,6 @@ const themes = [
{name: 'Yeti', quality: 'light', url: 'https://bootswatch.com/4/yeti/bootstrap.min.css'}, {name: 'Yeti', quality: 'light', url: 'https://bootswatch.com/4/yeti/bootstrap.min.css'},
{name: 'Cyborg', quality: 'dark', url: 'https://bootswatch.com/4/cyborg/bootstrap.min.css'}, {name: 'Cyborg', quality: 'dark', url: 'https://bootswatch.com/4/cyborg/bootstrap.min.css'},
{name: 'Darkly', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/darkly/bootstrap.min.css'}, {name: 'Darkly', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/darkly/bootstrap.min.css'},
{name: 'Slate', quality: 'light', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/slate/bootstrap.min.css'},
{name: 'Superhero', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/superhero/bootstrap.min.css'} {name: 'Superhero', quality: 'dark', url: 'https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/superhero/bootstrap.min.css'}
] ]
@ -38,7 +38,9 @@ export class ThemeModule extends Plugin {
config: registry.get('config').api config: registry.get('config').api
} }
this.themes = themes.reduce((acc, theme) => ({ ...acc, [theme.name]: theme }), {}) this.themes = themes.reduce((acc, theme) => ({ ...acc, [theme.name]: theme }), {})
this.active = this._deps.config.get('settings/theme') ? this._deps.config.get('settings/theme') : 'Dark' const theme = (new QueryParams()).get().theme
this.active = theme || this._deps.config.get('settings/theme') || 'Dark'
this.forced = theme !== undefined
} }
/** Return the active theme */ /** Return the active theme */
@ -76,7 +78,7 @@ export class ThemeModule extends Plugin {
} }
const next = themeName || this.active // Name const next = themeName || this.active // Name
const nextTheme = this.themes[next] // Theme const nextTheme = this.themes[next] // Theme
this._deps.config.set('settings/theme', next) if (!this.forced) this._deps.config.set('settings/theme', next)
document.getElementById('theme-link').setAttribute('href', nextTheme.url) document.getElementById('theme-link').setAttribute('href', nextTheme.url)
document.documentElement.style.setProperty('--theme', nextTheme.quality) document.documentElement.style.setProperty('--theme', nextTheme.quality)
if (themeName) this.active = themeName if (themeName) this.active = themeName

@ -32,7 +32,7 @@ export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) {
const txlistener = blockchain.getTxListener({ const txlistener = blockchain.getTxListener({
api: { api: {
contracts: function () { contracts: function () {
if (compilersArtefacts['__last']) return compilersArtefacts['__last'].getContracts() if (compilersArtefacts['__last']) return compilersArtefacts.getAllContractDatas()
return null return null
}, },
resolveReceipt: function (tx, cb) { resolveReceipt: function (tx, cb) {

@ -20,7 +20,7 @@ const UniversalDAppUI = require('../ui/universal-dapp-ui')
const profile = { const profile = {
name: 'udapp', name: 'udapp',
displayName: 'Deploy & run transactions', displayName: 'Deploy & run transactions',
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzFfY29weSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4Ig0KCSB5PSIwcHgiIHdpZHRoPSI3NDIuNTQ1cHgiIGhlaWdodD0iNjc2Ljg4NnB4IiB2aWV3Qm94PSIwIC0wLjIwNCA3NDIuNTQ1IDY3Ni44ODYiDQoJIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAtMC4yMDQgNzQyLjU0NSA2NzYuODg2IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwb2x5Z29uIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjI5NS45MTEsMC43MTEgNDg4LjkxMSwzMDQuMTg2IDQ4OC45MTEsMzk3LjE4MSAyOTMuOTExLDY3Ni41NTYgDQoJCTc0MS43ODYsMzQ5Ljk0MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4Myw0MDYuNTg5IDIwOS43OTEsNTE5LjQ5NCAxLjg0Niw0MDYuMjM0IDIwOS43OTEsNjc1Ljg2MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4MywzMTguNzA3IDIwOS43OTEsMC43MTEgMS44NDYsMzE4LjQyOCAyMDkuNzkxLDQzMS42ODkgCSIvPg0KPC9nPg0KPC9zdmc+DQo=', icon: 'assets/img/deployAndRun.webp',
description: 'execute and save transactions', description: 'execute and save transactions',
kind: 'udapp', kind: 'udapp',
location: 'sidePanel', location: 'sidePanel',

@ -9,7 +9,6 @@ var css = csjs`
-webkit-margin-start: 0px; -webkit-margin-start: 0px;
-webkit-margin-end: 0px; -webkit-margin-end: 0px;
-webkit-padding-start: 0px; -webkit-padding-start: 0px;
margin-left: 10px;
} }
.ul_tv { .ul_tv {
list-style-type: none; list-style-type: none;
@ -22,13 +21,19 @@ var css = csjs`
.caret_tv { .caret_tv {
width: 10px; width: 10px;
flex-shrink: 0; flex-shrink: 0;
padding-right: 5px;
} }
.label_tv { .label_item {
align-items: center; word-break: break-all;
} }
.label_tv>span { .label_key {
min-width: 15%;
max-width: 80%;
word-break: break-word; word-break: break-word;
} }
.label_value {
min-width: 10%;
}
` `
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
@ -75,22 +80,22 @@ class TreeView {
var children = Object.keys(json).map((innerkey) => { var children = Object.keys(json).map((innerkey) => {
return this.renderObject(json[innerkey], json, innerkey, expand, innerkey) return this.renderObject(json[innerkey], json, innerkey, expand, innerkey)
}) })
return yo`<ul key=${key} data-id="treeViewUl${key}" class="${css.ul_tv}">${children}</ul>` return yo`<ul key=${key} data-id="treeViewUl${key}" class="${css.ul_tv} ml-0 px-2">${children}</ul>`
} }
formatData (key, data, children, expand, keyPath) { formatData (key, data, children, expand, keyPath) {
var self = this var self = this
var li = yo`<li key=${keyPath} data-id="treeViewLi${keyPath}" class=${css.li_tv}></li>` var li = yo`<li key=${keyPath} data-id="treeViewLi${keyPath}" class=${css.li_tv}></li>`
var caret = yo`<div class="fas fa-caret-right caret ${css.caret_tv}"></div>` var caret = yo`<div class="px-1 fas fa-caret-right caret ${css.caret_tv}"></div>`
var label = yo` var label = yo`
<div key=${keyPath} data-id="treeViewDiv${keyPath}" class=${css.label_tv}> <div key=${keyPath} data-id="treeViewDiv${keyPath}" class="d-flex flex-row align-items-center">
${caret} ${caret}
<span>${self.formatSelf(key, data, li)}</span> <span class="w-100">${self.formatSelf(key, data, li)}</span>
</div>` </div>`
const expanded = self.expandPath.includes(keyPath) const expanded = self.expandPath.includes(keyPath)
li.appendChild(label) li.appendChild(label)
if (data.children) { if (data.children) {
var list = yo`<ul key=${keyPath} data-id="treeViewUlList${keyPath}" class=${css.ul_tv}>${children}</ul>` var list = yo`<ul key=${keyPath} data-id="treeViewUlList${keyPath}" class="pl-2 ${css.ul_tv}">${children}</ul>`
list.style.display = expanded ? 'block' : 'none' list.style.display = expanded ? 'block' : 'none'
caret.className = list.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}` caret.className = list.style.display === 'none' ? `fas fa-caret-right caret ${css.caret_tv}` : `fas fa-caret-down caret ${css.caret_tv}`
caret.setAttribute('data-id', `treeViewToggle${keyPath}`) caret.setAttribute('data-id', `treeViewToggle${keyPath}`)
@ -99,7 +104,7 @@ class TreeView {
if (self.isExpanded(keyPath)) { if (self.isExpanded(keyPath)) {
if (!self.expandPath.includes(keyPath)) self.expandPath.push(keyPath) if (!self.expandPath.includes(keyPath)) self.expandPath.push(keyPath)
} else { } else {
self.expandPath = self.expandPath.filter(path => path !== keyPath) self.expandPath = self.expandPath.filter(path => !path.startsWith(keyPath))
} }
} }
label.oncontextmenu = function (event) { label.oncontextmenu = function (event) {
@ -164,7 +169,12 @@ class TreeView {
} }
formatSelfDefault (key, data) { formatSelfDefault (key, data) {
return yo`<label>${key}: ${data.self}</label>` return yo`
<div class="d-flex mb-1 flex-row ${css.label_item}">
<label class="small font-weight-bold pr-1 ${css.label_key}">${key}:</label>
<label class="m-0 ${css.label_value}">${data.self}</label>
</div>
`
} }
extractDataDefault (item, parent, key) { extractDataDefault (item, parent, key) {

File diff suppressed because one or more lines are too long

@ -8,7 +8,7 @@ export class FramingService {
this.resizeFeature = resizeFeature this.resizeFeature = resizeFeature
} }
start () { start (params) {
this.sidePanel.events.on('toggle', () => { this.sidePanel.events.on('toggle', () => {
this.resizeFeature.panel.clientWidth !== 0 ? this.resizeFeature.hidePanel() : this.resizeFeature.showPanel() this.resizeFeature.panel.clientWidth !== 0 ? this.resizeFeature.hidePanel() : this.resizeFeature.showPanel()
}) })
@ -33,5 +33,13 @@ export class FramingService {
e.preventDefault() e.preventDefault()
} }
}) })
if (params.minimizeterminal) this.mainView.minimizeTerminal()
if (params.minimizesidepanel) this.resizeFeature.hidePanel()
}
embed () {
this.mainView.minimizeTerminal()
this.resizeFeature.hidePanel()
} }
} }

@ -84,4 +84,3 @@ function find (args, query) {
}) })
return isMatch return isMatch
} }

@ -19,14 +19,20 @@ export class OffsetToLineColumnConverter extends Plugin {
offsetToLineColumn (rawLocation, file, sources, asts) { offsetToLineColumn (rawLocation, file, sources, asts) {
if (!this.lineBreakPositionsByContent[file]) { if (!this.lineBreakPositionsByContent[file]) {
const sourcesArray = Object.keys(sources)
if (!asts && file === 0 && sourcesArray.length === 1) {
// if we don't have ast, we process the only one available content
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content)
} else {
for (var filename in asts) { for (var filename in asts) {
var source = asts[filename] const source = asts[filename]
if (source.id === file) { if (source.id === file) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content) this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
break break
} }
} }
} }
}
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
} }

@ -2,7 +2,8 @@ const EventEmitter = require('events')
class ClickLaunchIcon extends EventEmitter { class ClickLaunchIcon extends EventEmitter {
command (icon) { command (icon) {
this.api.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]').click('#icon-panel div[plugin="' + icon + '"]').perform(() => { this.api.waitForElementVisible('#icon-panel div[plugin="' + icon + '"]').click('#icon-panel div[plugin="' + icon + '"]').perform((done) => {
done()
this.emit('complete') this.emit('complete')
}) })
return this return this

@ -3,26 +3,23 @@ const EventEmitter = require('events')
class GetInstalledPlugins extends EventEmitter { class GetInstalledPlugins extends EventEmitter {
command (cb) { command (cb) {
const browser = this.api const browser = this.api
const plugins = []
browser.click('*[data-id="remixIdeIconPanel"]') browser.waitForElementPresent('[plugin]:not([plugin=""]')
.waitForElementPresent('[plugin]:not([plugin=""])') .perform((done) => {
.elements('css selector', '[plugin]:not([plugin=""])', (res) => { browser.execute(() => {
res.value.forEach(function (jsonWebElement) { const pluginNames = []
const jsonWebElementId = jsonWebElement.ELEMENT || jsonWebElement[Object.keys(jsonWebElement)[0]] const plugins = document.querySelectorAll('[plugin]:not([plugin=""]')
browser.elementIdAttribute(jsonWebElementId, 'plugin', (jsonElement) => {
const attribute = jsonElement.value
plugins.push(attribute) plugins.forEach(plugin => {
}) pluginNames.push(plugin.getAttribute('plugin'))
}) })
}) return pluginNames
.perform((done) => { }, [], (result) => {
done() done()
cb(plugins) cb(result.value)
this.emit('complete') this.emit('complete')
}) })
})
return this return this
} }
} }

@ -17,10 +17,9 @@ function goToVMtraceStep (browser, step, incr, done) {
browser.execute(function (step) { browser.execute(function (step) {
return document.querySelector('#stepdetail').innerHTML return document.querySelector('#stepdetail').innerHTML
}, [step], function (result) { }, [step], function (result) {
if (result.value.indexOf('vm trace step: ' + step) !== -1) { if (result.value.indexOf('vm trace step:') !== -1 && result.value.indexOf(step) !== -1) {
done() done()
} else if (incr > 1000) { } else if (incr > 1000) {
console.log(result)
browser.assert.fail('goToVMtraceStep fails', 'info about error', '') browser.assert.fail('goToVMtraceStep fails', 'info about error', '')
done() done()
} else { } else {

@ -0,0 +1,28 @@
const EventEmitter = require('events')
class NoWorkerErrorFor extends EventEmitter {
command (version) {
this.api.perform((done) => {
noWorkerErrorFor(this.api, version, () => {
done()
this.emit('complete')
})
})
return this
}
}
function noWorkerErrorFor (browser, version, callback) {
browser
.setSolidityCompilerVersion(version)
.click('*[data-id="compilerContainerCompileBtn"]')
.waitForElementPresent('*[data-id="compilationFinishedWith_' + version + '"]', 10000)
.notContainsText('*[data-id="compiledErrors"]', 'worker error:undefined')
.notContainsText('*[data-id="compiledErrors"]', 'Uncaught RangeError: Maximum call stack size exceeded')
.notContainsText('*[data-id="compiledErrors"]', 'RangeError: Maximum call stack size exceeded')
.perform(() => {
callback()
})
}
module.exports = NoWorkerErrorFor

@ -24,7 +24,7 @@ function verifyCallReturnValue (browser, address, checks, done) {
}, [address], function (result) { }, [address], function (result) {
console.log('verifyCallReturnValue', result) console.log('verifyCallReturnValue', result)
for (var k in checks) { for (var k in checks) {
browser.assert.equal(result.value[k], checks[k]) browser.assert.equal(result.value[k].trim(), checks[k].trim())
} }
done() done()
}) })

@ -1,7 +1,7 @@
const EventEmitter = require('events') const EventEmitter = require('events')
class VerifyContracts extends EventEmitter { class VerifyContracts extends EventEmitter {
command (compiledContractNames, opts = { wait: 1000 }) { command (compiledContractNames, opts = { wait: 1000, version: null }) {
this.api.perform((done) => { this.api.perform((done) => {
verifyContracts(this.api, compiledContractNames, opts, () => { verifyContracts(this.api, compiledContractNames, opts, () => {
done() done()
@ -17,6 +17,18 @@ function getCompiledContracts (browser, opts, callback) {
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.pause(opts.wait) .pause(opts.wait)
.waitForElementPresent('*[data-id="compiledContracts"] option') .waitForElementPresent('*[data-id="compiledContracts"] option')
.perform((done) => {
if (opts.version) {
browser
.click('*[data-id="compilation-details"]')
.waitForElementVisible('*[data-id="treeViewDivcompiler"]')
.pause(2000)
.click('*[data-id="treeViewDivcompiler"]')
.waitForElementVisible('*[data-id="treeViewLicompiler/version"]')
.assert.containsText('*[data-id="treeViewLicompiler/version"]', `version:\n${opts.version}`)
.perform(done)
} else done()
})
.execute(function () { .execute(function () {
var contracts = document.querySelectorAll('*[data-id="compiledContracts"] option') var contracts = document.querySelectorAll('*[data-id="compiledContracts"] option')
if (!contracts) { if (!contracts) {

@ -11,28 +11,27 @@ module.exports = function (browser, callback, url, preloadPlugins = true) {
initModules(browser, () => { initModules(browser, () => {
browser.clickLaunchIcon('solidity') browser.clickLaunchIcon('solidity')
.pause(2000) .pause(2000)
.click('*[for="autoCompile"]') .execute(() => {
.perform(function () { document.getElementById('autoCompile').click()
callback()
}) })
}) })
} else callback() }
}) })
}) })
.perform(() => {
callback()
})
} }
function initModules (browser, callback) { function initModules (browser, callback) {
browser.pause(5000) browser.pause(5000)
.click('#icon-panel div[plugin="pluginManager"]') .click('[data-id="verticalIconsKindpluginManager"]')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidity"] button') .scrollAndClick('[data-id="pluginManagerComponentActivateButtonsolidityStaticAnalysis"]')
.pause(5000) .scrollAndClick('[data-id="pluginManagerComponentActivateButtondebugger"]')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_udapp"] button') .scrollAndClick('[data-id="verticalIconsKindfileExplorers"]')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_solidityStaticAnalysis"] button')
.scrollAndClick('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.scrollAndClick('#icon-panel div[plugin="fileExplorers"]')
.clickLaunchIcon('settings') .clickLaunchIcon('settings')
.setValue('#gistaccesstoken', process.env.gist_token) .setValue('[data-id="settingsTabGistAccessToken"]', process.env.gist_token)
.click('#savegisttoken') .click('[data-id="settingsTabSaveGistToken"]')
.click('#settingsView #Flatly') // e2e tests were initially developed with Flatly. Some tests are failing with the default one (Dark), because the dark theme put uppercase everywhere. .click('[data-id="settingsTabThemeFlatly"]') // e2e tests were initially developed with Flatly. Some tests are failing with the default one (Dark), because the dark theme put uppercase everywhere.
.perform(() => { callback() }) .perform(() => { callback() })
} }

@ -0,0 +1,100 @@
'use strict'
var examples = require('../../src/app/editor/example-contracts')
var init = require('../helpers/init')
var sauce = require('./sauce')
var sources = [
{'browser/Untitled.sol': {content: examples.ballot.content}}
]
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Should compile using "compileWithParamaters" API': function (browser) {
browser
.addFile('test_jsCompile.js', { content: jsCompile })
.executeScript('remix.exeCurrent()')
.pause(5000)
.journalChildIncludes('"languageversion": "0.6.8+commit.0bbfe453"')
},
'Should update the compiler configuration with "setCompilerConfig" API': function (browser) {
browser
.addFile('test_updateConfiguration.js', { content: updateConfiguration })
.executeScript('remix.exeCurrent()')
.pause(5000)
.addFile('test_updateConfiguration.sol', { content: simpleContract })
.verifyContracts(['StorageTestUpdateConfiguration'], {wait: 5000, version: '0.6.8+commit.0bbfe453'})
.end()
},
tearDown: sauce
}
const simpleContract = `pragma solidity >=0.4.22 <0.7.0;
/**
* @title Storage
* @dev Store & retreive value in a variable
*/
contract StorageTestUpdateConfiguration {
uint256 number;
/**
* @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 retreive() public view returns (uint256){
return number;
}
}
`
const jsCompile = `(async () => {
try {
const contract = {
"storage.sol": {content : \`${simpleContract}\` }
}
console.log('compile')
const params = {
optimize: false,
evmVersion: null,
language: 'Solidity',
version: '0.6.8+commit.0bbfe453'
}
const result = await remix.call('solidity', 'compileWithParameters', contract, params)
console.log('result ', result)
} catch (e) {
console.log(e.message)
}
})()`
const updateConfiguration = `(async () => {
try {
const params = {
optimize: false,
evmVersion: null,
language: 'Solidity',
version: '0.6.8+commit.0bbfe453'
}
await remix.call('solidity', 'setCompilerConfig', params)
} catch (e) {
console.log(e.message)
}
})()`

@ -43,7 +43,7 @@ module.exports = {
.setValue('*[data-id="slider"]', 50) .setValue('*[data-id="slider"]', 50)
.pause(2000) .pause(2000)
.assert.containsText('*[data-id="solidityLocals"]', 'no locals') .assert.containsText('*[data-id="solidityLocals"]', 'no locals')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 92') .assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n92')
}, },
'Should step back and forward transaction': function (browser) { 'Should step back and forward transaction': function (browser) {
@ -51,12 +51,12 @@ module.exports = {
.waitForElementPresent('*[data-id="buttonNavigatorIntoBack"]') .waitForElementPresent('*[data-id="buttonNavigatorIntoBack"]')
.scrollAndClick('*[data-id="buttonNavigatorIntoBack"]') .scrollAndClick('*[data-id="buttonNavigatorIntoBack"]')
.pause(2000) .pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 91') .assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n91')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 91') .assert.containsText('*[data-id="stepdetail"]', 'execution step:\n91')
.click('*[data-id="buttonNavigatorIntoForward"]') .click('*[data-id="buttonNavigatorIntoForward"]')
.pause(2000) .pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 92') .assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n92')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 92') .assert.containsText('*[data-id="stepdetail"]', 'execution step:\n92')
}, },
'Should jump through breakpoints': function (browser) { 'Should jump through breakpoints': function (browser) {
@ -66,12 +66,12 @@ module.exports = {
.waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]') .waitForElementVisible('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]') .click('*[data-id="buttonNavigatorJumpPreviousBreakpoint"]')
.pause(2000) .pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 0') .assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n0')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 0') .assert.containsText('*[data-id="stepdetail"]', 'execution step:\n0')
.click('*[data-id="buttonNavigatorJumpNextBreakpoint"]') .click('*[data-id="buttonNavigatorJumpNextBreakpoint"]')
.pause(2000) .pause(2000)
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step: 140') .assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n184')
.assert.containsText('*[data-id="stepdetail"]', 'execution step: 140') .assert.containsText('*[data-id="stepdetail"]', 'execution step:\n184')
.end() .end()
}, },

@ -18,7 +18,7 @@ module.exports = {
browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]') browser.waitForElementVisible('div[data-id="remixIdeSidePanel"]')
.assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS') .assert.containsText('h6[data-id="sidePanelSwapitTitle"]', 'FILE EXPLORERS')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]') .waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.waitForElementVisible('li[key="browser/4_Ballot_test.sol"]') .waitForElementVisible('li[key="browser/3_Ballot.sol"]')
}, },
'Loads Main View': function (browser) { 'Loads Main View': function (browser) {

@ -66,10 +66,12 @@ module.exports = {
.click('*[data-id="settingsTabThemeDark"]') .click('*[data-id="settingsTabThemeDark"]')
.pause(2000) .pause(2000)
.waitForElementVisible('*[data-id="editorInput"]') .waitForElementVisible('*[data-id="editorInput"]')
/* @todo(#2863) ch for class and not colors
.checkElementStyle('.ace_keyword', 'color', aceThemes.dark.keyword) .checkElementStyle('.ace_keyword', 'color', aceThemes.dark.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.dark.comment) .checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.dark.comment)
.checkElementStyle('.ace_function', 'color', aceThemes.dark.function) .checkElementStyle('.ace_function', 'color', aceThemes.dark.function)
.checkElementStyle('.ace_variable', 'color', aceThemes.dark.variable) .checkElementStyle('.ace_variable', 'color', aceThemes.dark.variable)
*/
}, },
'Should highlight source code': function (browser) { 'Should highlight source code': function (browser) {

@ -22,8 +22,7 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="modalDialogContainer"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', '5_New_contract.sol') .setValue('*[data-id="modalDialogCustomPromptText"]', '5_New_contract.sol')
.modalFooterOKClick() .modalFooterOKClick()
.pause(2000) .waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]', 7000)
.waitForElementVisible('*[data-id="treeViewLibrowser/5_New_contract.sol"]')
}, },
'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser) { 'Should rename `5_New_contract.sol` to 5_Renamed_Contract.sol': function (browser) {
@ -82,8 +81,7 @@ module.exports = {
.click('*[data-id="fileExplorerNewFilepublishToGist"]') .click('*[data-id="fileExplorerNewFilepublishToGist"]')
.waitForElementVisible('*[data-id="modalDialogContainer"]') .waitForElementVisible('*[data-id="modalDialogContainer"]')
.modalFooterOKClick() .modalFooterOKClick()
.pause(10000) .waitForElementVisible('*[data-id="modalDialogContainer"]', 7000)
.waitForElementVisible('*[data-id="modalDialogContainer"]')
.modalFooterOKClick() .modalFooterOKClick()
.pause(2000) .pause(2000)
.perform((done) => { .perform((done) => {

@ -0,0 +1,190 @@
'use strict'
var init = require('../helpers/init')
var sauce = require('./sauce')
module.exports = {
before: function (browser, done) {
init(browser, done)
},
'Should execute `file` api from file manager external api': function (browser) {
browser
.addFile('file.js', { content: executeFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('browser/file.js')
},
'Should execute `exists` api from file manager external api': function (browser) {
browser
.addFile('exists.js', { content: executeExists })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalChildIncludes('browser/exists.js true')
.journalChildIncludes('browser/non-exists.js false')
},
'Should execute `open` api from file manager external api': function (browser) {
browser
.addFile('open.js', { content: executeOpen })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('browser/3_Ballot.sol')
},
'Should execute `writeFile` api from file manager external api': function (browser) {
browser
.addFile('writeFile.js', { content: executeWriteFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.openFile('browser/new_contract.sol')
.assert.containsText('[data-id="editorInput"]', 'pragma solidity ^0.6.0')
},
'Should execute `readFile` api from file manager external api': function (browser) {
browser
.addFile('readFile.js', { content: executeReadFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('pragma solidity ^0.6.0')
},
'Should execute `copyFile` api from file manager external api': function (browser) {
browser
.addFile('copyFile.js', { content: executeCopyFile })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('pragma solidity >=0.4.22 <0.7.0;')
},
'Should execute `rename` api from file manager external api': function (browser) {
browser
.addFile('renameFile.js', { content: executeRename })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/old_contract.sol"]')
},
'Should execute `mkdir` api from file manager external api': function (browser) {
browser
.addFile('mkdirFile.js', { content: executeMkdir })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementPresent('[data-id="treeViewLibrowser/Test_Folder"]')
},
'Should execute `readdir` api from file manager external api': function (browser) {
browser
.addFile('readdirFile.js', { content: executeReaddir })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.journalLastChildIncludes('Test_Folder isDirectory true')
},
'Should execute `remove` api from file manager external api': function (browser) {
browser
.addFile('removeFile.js', { content: executeRemove })
.executeScript(`remix.exeCurrent()`)
.pause(2000)
.waitForElementNotPresent('[data-id="treeViewLibrowser/old_contract.sol"]')
.end()
},
tearDown: sauce
}
const executeFile = `
const run = async () => {
const result = await remix.call('fileManager', 'file')
console.log(result)
}
run()
`
const executeExists = `
const run = async () => {
const result1 = await remix.call('fileManager', 'exists', 'browser/exists.js')
const result2 = await remix.call('fileManager', 'exists', 'browser/non-exists.js')
console.log('browser/exists.js ' + result1)
console.log('browser/non-exists.js ' + result2)
}
run()
`
const executeOpen = `
const run = async () => {
await remix.call('fileManager', 'open', 'browser/3_Ballot.sol')
const result = await remix.call('fileManager', 'file')
console.log(result)
}
run()
`
const executeWriteFile = `
const run = async () => {
await remix.call('fileManager', 'writeFile', 'browser/new_contract.sol', 'pragma solidity ^0.6.0')
}
run()
`
const executeReadFile = `
const run = async () => {
const result = await remix.call('fileManager', 'readFile', 'browser/new_contract.sol')
console.log(result)
}
run()
`
const executeCopyFile = `
const run = async () => {
await remix.call('fileManager', 'copyFile', 'browser/3_Ballot.sol', 'browser/new_contract.sol')
const result = await remix.call('fileManager', 'readFile', 'browser/new_contract.sol')
console.log(result)
}
run()
`
const executeRename = `
const run = async () => {
await remix.call('fileManager', 'rename', 'browser/new_contract.sol', 'browser/old_contract.sol')
}
run()
`
const executeMkdir = `
const run = async () => {
await remix.call('fileManager', 'mkdir', 'browser/Test_Folder/')
}
run()
`
const executeReaddir = `
const run = async () => {
const result = await remix.call('fileManager', 'readdir', 'browser/')
console.log('Test_Folder isDirectory ', result["Test_Folder"].isDirectory)
}
run()
`
const executeRemove = `
const run = async () => {
await remix.call('fileManager', 'remove', 'browser/old_contract.sol')
}
run()
`

@ -37,7 +37,6 @@ module.exports = {
.click('*[data-id="compilerContainerCompileBtn"]') .click('*[data-id="compilerContainerCompileBtn"]')
.pause(3000) .pause(3000)
.click('*[data-id="verticalIconsKindfileExplorers"]') .click('*[data-id="verticalIconsKindfileExplorers"]')
.openFile('browser/artifacts')
.openFile('browser/artifacts/Ballot.json') .openFile('browser/artifacts/Ballot.json')
}, },
@ -209,18 +208,6 @@ module.exports = {
.checkElementStyle(':root', '--danger', remixIdeThemes.darkly.danger) .checkElementStyle(':root', '--danger', remixIdeThemes.darkly.danger)
}, },
'Should load Slate theme': function (browser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.click('*[data-id="settingsTabThemeSlate"]')
.pause(2000)
.checkElementStyle(':root', '--primary', remixIdeThemes.slate.primary)
.checkElementStyle(':root', '--secondary', remixIdeThemes.slate.secondary)
.checkElementStyle(':root', '--success', remixIdeThemes.slate.success)
.checkElementStyle(':root', '--info', remixIdeThemes.slate.info)
.checkElementStyle(':root', '--warning', remixIdeThemes.slate.warning)
.checkElementStyle(':root', '--danger', remixIdeThemes.slate.danger)
},
'Should load Superhero theme': function (browser) { 'Should load Superhero theme': function (browser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000) browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.click('*[data-id="settingsTabThemeSuperhero"]') .click('*[data-id="settingsTabThemeSuperhero"]')
@ -334,14 +321,6 @@ var remixIdeThemes = {
warning: '#F39C12', warning: '#F39C12',
danger: '#E74C3C' danger: '#E74C3C'
}, },
slate: {
primary: '#3A3F44',
secondary: '#7A8288',
success: '#62c462',
info: '#5bc0de',
warning: '#f89406',
danger: '#ee5f5b'
},
superhero: { superhero: {
primary: '#DF691A', primary: '#DF691A',
secondary: '#4E5D6C', secondary: '#4E5D6C',

@ -45,7 +45,6 @@ module.exports = {
.modalFooterCancelClick() .modalFooterCancelClick()
.executeScript(`remix.loadgist('${gistid}')`) .executeScript(`remix.loadgist('${gistid}')`)
.perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('browser/gists') } done() }) .perform((done) => { if (runtimeBrowser === 'chrome') { browser.openFile('browser/gists') } done() })
.openFile(`browser/gists/${gistid}`)
.openFile(`browser/gists/${gistid}/1_Storage.sol`) .openFile(`browser/gists/${gistid}/1_Storage.sol`)
.perform(done) .perform(done)
} }
@ -86,7 +85,6 @@ module.exports = {
.waitForElementVisible('*[data-id="modalDialogCustomPromptText"]') .waitForElementVisible('*[data-id="modalDialogCustomPromptText"]')
.setValue('*[data-id="modalDialogCustomPromptText"]', testData.validGistId) .setValue('*[data-id="modalDialogCustomPromptText"]', testData.validGistId)
.modalFooterOKClick() .modalFooterOKClick()
.openFile(`browser/gists/${testData.validGistId}`)
.openFile(`browser/gists/${testData.validGistId}/ApplicationRegistry`) .openFile(`browser/gists/${testData.validGistId}/ApplicationRegistry`)
.waitForElementVisible(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry']`) .waitForElementVisible(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry']`)
.assert.containsText(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry'] > span`, 'ApplicationRegistry') .assert.containsText(`div[title='browser/gists/${testData.validGistId}/ApplicationRegistry'] > span`, 'ApplicationRegistry')

@ -26,7 +26,7 @@ module.exports = {
.waitForElementPresent('.instance:nth-of-type(2)') .waitForElementPresent('.instance:nth-of-type(2)')
.click('.instance:nth-of-type(2) > div > button') .click('.instance:nth-of-type(2) > div > button')
.perform((done) => { .perform((done) => {
browser.testConstantFunction(addressRef, 'get - call', '', '0: uint256: 45').perform(() => { browser.testConstantFunction(addressRef, 'get - call', '', '0:\nuint256: 45').perform(() => {
done() done()
}) })
}) })
@ -96,7 +96,7 @@ function checkDeployShouldSucceed (browser, address, callback) {
.click('.instance:nth-of-type(3) > div > button') .click('.instance:nth-of-type(3) > div > button')
.perform(() => { .perform(() => {
browser browser
.testConstantFunction(addressRef, 'get - call', '', '0: uint256: 45') .testConstantFunction(addressRef, 'get - call', '', '0:\nuint256: 45')
.perform(() => { callback() }) .perform(() => { callback() })
}) })
} }

@ -27,8 +27,8 @@ module.exports = {
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]') .waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.clearValue('*[data-id="pluginManagerComponentSearchInput"]') .clearValue('*[data-id="pluginManagerComponentSearchInput"]')
.click('*[data-id="pluginManagerComponentSearchInput"]') .click('*[data-id="pluginManagerComponentSearchInput"]')
.keys('Deploy & run transactions') .keys('Vyper')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonudapp"]') .waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonvyper"]')
.clearValue('*[data-id="pluginManagerComponentSearchInput"]') .clearValue('*[data-id="pluginManagerComponentSearchInput"]')
.click('*[data-id="pluginManagerComponentSearchInput"]') .click('*[data-id="pluginManagerComponentSearchInput"]')
.keys('ZoKrates') .keys('ZoKrates')
@ -43,8 +43,8 @@ module.exports = {
.click('*[data-id="pluginManagerComponentPluginManager"]') .click('*[data-id="pluginManagerComponentPluginManager"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtondebugger"]') .scrollAndClick('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]') .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtondebugger"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonudapp"]') .scrollAndClick('*[data-id="pluginManagerComponentActivateButtonvyper"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonudapp"]') .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonvyper"]')
.scrollAndClick('*[data-id="pluginManagerComponentActivateButtonZoKrates"]') .scrollAndClick('*[data-id="pluginManagerComponentActivateButtonZoKrates"]')
.waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]') .waitForElementVisible('*[data-id="pluginManagerComponentDeactivateButtonZoKrates"]')
}, },
@ -54,8 +54,8 @@ module.exports = {
.click('*[data-id="pluginManagerComponentPluginManager"]') .click('*[data-id="pluginManagerComponentPluginManager"]')
.scrollAndClick('*[data-id="pluginManagerComponentDeactivateButtondebugger"]') .scrollAndClick('*[data-id="pluginManagerComponentDeactivateButtondebugger"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]') .waitForElementVisible('*[data-id="pluginManagerComponentActivateButtondebugger"]')
.scrollAndClick('*[data-id="pluginManagerComponentDeactivateButtonudapp"]') .scrollAndClick('*[data-id="pluginManagerComponentDeactivateButtonvyper"]')
.waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonudapp"]') .waitForElementVisible('*[data-id="pluginManagerComponentActivateButtonvyper"]')
}, },
/* /*

@ -31,8 +31,7 @@ module.exports = {
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgHash"]')
.assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .assert.elementNotPresent('*[data-id="settingsRemixRunSignMsgSignature"]')
.modalFooterOKClick() .modalFooterOKClick()
.pause(2000) .waitForElementPresent('*[data-id="modalDialogContainer"]', 12000)
.waitForElementPresent('*[data-id="modalDialogContainer"]')
.assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]') .assert.elementPresent('*[data-id="settingsRemixRunSignMsgHash"]')
.assert.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]') .assert.elementPresent('*[data-id="settingsRemixRunSignMsgSignature"]')
.modalFooterOKClick() .modalFooterOKClick()
@ -145,8 +144,8 @@ module.exports = {
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]') .waitForElementPresent('*[data-id="Deploy - transact (not payable)"]')
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.pause(2000) .waitForElementPresent('*[data-id="modalDialogContainer"]', 15000)
.waitForElementPresent('*[data-id="modalDialogContainer"]') .pause(10000)
.assert.containsText('*[data-id="modalDialogModalBody"]', 'You are creating a transaction on the main network. Click confirm if you are sure to continue.') .assert.containsText('*[data-id="modalDialogModalBody"]', 'You are creating a transaction on the main network. Click confirm if you are sure to continue.')
.modalFooterCancelClick() .modalFooterCancelClick()
}, },
@ -179,7 +178,7 @@ module.exports = {
.setValue('*[data-id="debuggerTransactionInput"]', '0x959371506b8f6223d71c709ac2eb2d0158104dca2d76ca949f1662712cf0e6db') // debug tx .setValue('*[data-id="debuggerTransactionInput"]', '0x959371506b8f6223d71c709ac2eb2d0158104dca2d76ca949f1662712cf0e6db') // debug tx
.click('*[data-id="debuggerTransactionStartButton"]') .click('*[data-id="debuggerTransactionStartButton"]')
.waitForElementVisible('*[data-id="treeViewDivto"]', 30000) .waitForElementVisible('*[data-id="treeViewDivto"]', 30000)
.assert.containsText('*[data-id="stepdetail"]', 'loaded address: 0x3c943Fb816694d7D1f4C738e3e7823818a88DD6C') .assert.containsText('*[data-id="stepdetail"]', 'loaded address:\n0x3c943Fb816694d7D1f4C738e3e7823818a88DD6C')
.assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1') .assert.containsText('*[data-id="solidityLocals"]', 'to: 0x6C3CCC7FBA111707D5A1AAF2758E9D4F4AC5E7B1')
.end() .end()
}, },

@ -31,14 +31,16 @@ module.exports = {
.click('*[data-id="verticalIconsKindsolidityUnitTesting"]') .click('*[data-id="verticalIconsKindsolidityUnitTesting"]')
.waitForElementPresent('*[data-id="testTabGenerateTestFile"]') .waitForElementPresent('*[data-id="testTabGenerateTestFile"]')
.click('*[data-id="testTabGenerateTestFile"]') .click('*[data-id="testTabGenerateTestFile"]')
.waitForElementPresent('*[title="browser/simple_storage_test.sol"]') .waitForElementPresent('*[title="browser/tests/simple_storage_test.sol"]')
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.removeFile('browser/simple_storage_test.sol') .pause(10000)
.openFile('browser/tests/simple_storage_test.sol')
.removeFile('browser/tests/simple_storage_test.sol')
}, },
'Should run simple unit test `simple_storage_test.sol` ': function (browser) { 'Should run simple unit test `simple_storage_test.sol` ': function (browser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]') browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.addFile('simple_storage_test.sol', sources[0]['browser/simple_storage_test.sol']) .addFile('tests/simple_storage_test.sol', sources[0]['browser/tests/simple_storage_test.sol'])
.click('*[data-id="verticalIconsKindsolidityUnitTesting"]') .click('*[data-id="verticalIconsKindsolidityUnitTesting"]')
.waitForElementPresent('*[data-id="testTabCheckAllTests"]') .waitForElementPresent('*[data-id="testTabCheckAllTests"]')
.click('*[data-id="testTabCheckAllTests"]') .click('*[data-id="testTabCheckAllTests"]')
@ -46,19 +48,19 @@ module.exports = {
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]') .scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 80000) .waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 80000)
.pause(5000) .pause(5000)
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'MyTest (browser/simple_storage_test.sol)') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'MyTest (browser/tests/simple_storage_test.sol)')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✓ Initial value should be100') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✓ Initial value should be100')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✓ Value is set200') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✓ Value is set200')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✘ Should fail for wrong value200') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✘ Should fail for wrong value200')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'Passing: 2') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'Passing: 2')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'Failing: 1') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'Failing: 1')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'FAIL MyTest (browser/simple_storage_test.sol)') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'FAIL MyTest (browser/tests/simple_storage_test.sol)')
}, },
'Should run advance unit test using natspec and experimental ABIEncoderV2 `ks2b_test.sol` ': function (browser) { 'Should run advance unit test using natspec and experimental ABIEncoderV2 `ks2b_test.sol` ': function (browser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]') browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.addFile('ks2b_test.sol', sources[0]['browser/ks2b_test.sol']) .addFile('tests/ks2b_test.sol', sources[0]['browser/tests/ks2b_test.sol'])
.click('*[data-id="verticalIconsKindsolidityUnitTesting"]') .click('*[data-id="verticalIconsKindsolidityUnitTesting"]')
.waitForElementPresent('*[data-id="testTabCheckAllTests"]') .waitForElementPresent('*[data-id="testTabCheckAllTests"]')
.click('*[data-id="testTabCheckAllTests"]') .click('*[data-id="testTabCheckAllTests"]')
@ -66,7 +68,7 @@ module.exports = {
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]') .scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000) .waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000)
.pause(5000) .pause(5000)
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/ks2b_test.sol') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/tests/ks2b_test.sol')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✓ Check project exists') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✓ Check project exists')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✘ Check wrong project owner') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✘ Check wrong project owner')
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✘ Check wrong sender') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', '✘ Check wrong sender')
@ -86,21 +88,21 @@ module.exports = {
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]') .scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.pause(5000) .pause(5000)
.click('*[data-id="testTabRunTestsTabStopAction"]') .click('*[data-id="testTabRunTestsTabStopAction"]')
.pause(2000) .pause(1000)
.assert.containsText('*[data-id="testTabRunTestsTabStopAction"]', 'Stopping') .assert.containsText('*[data-id="testTabRunTestsTabStopAction"]', 'Stopping')
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000) .waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000)
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/ks2b_test.sol') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/tests/ks2b_test.sol')
.notContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/4_Ballot_test.sol') .notContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/tests/4_Ballot_test.sol')
.notContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/simple_storage_test.sol') .notContainsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/tests/simple_storage_test.sol')
.pause(6000) .pause(7000)
.assert.containsText('*[data-id="testTabTestsExecutionStopped"]', 'The test execution has been stopped') .assert.containsText('*[data-id="testTabTestsExecutionStopped"]', 'The test execution has been stopped')
}, },
'Should fail on compilation': function (browser) { 'Should fail on compilation': function (browser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]') browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.addFile('compilationError_test.sol', sources[0]['browser/compilationError_test.sol']) .addFile('tests/compilationError_test.sol', sources[0]['browser/compilationError_test.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.openFile('browser/compilationError_test.sol') .openFile('browser/tests/compilationError_test.sol')
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.click('*[data-id="testTabCheckAllTests"]') .click('*[data-id="testTabCheckAllTests"]')
.clickElementAtPosition('.singleTestLabel', 3) .clickElementAtPosition('.singleTestLabel', 3)
@ -113,9 +115,9 @@ module.exports = {
'Should fail on deploy': function (browser) { 'Should fail on deploy': function (browser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]') browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.addFile('deployError_test.sol', sources[0]['browser/deployError_test.sol']) .addFile('tests/deployError_test.sol', sources[0]['browser/tests/deployError_test.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.openFile('browser/deployError_test.sol') .openFile('browser/tests/deployError_test.sol')
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.click('*[data-id="testTabCheckAllTests"]') .click('*[data-id="testTabCheckAllTests"]')
.clickElementAtPosition('.singleTestLabel', 4) .clickElementAtPosition('.singleTestLabel', 4)
@ -127,9 +129,9 @@ module.exports = {
'Should fail when parameters are to method in test contract': function (browser) { 'Should fail when parameters are to method in test contract': function (browser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]') browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.addFile('methodFailure_test.sol', sources[0]['browser/methodFailure_test.sol']) .addFile('tests/methodFailure_test.sol', sources[0]['browser/tests/methodFailure_test.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.openFile('browser/methodFailure_test.sol') .openFile('browser/tests/methodFailure_test.sol')
.clickLaunchIcon('solidityUnitTesting') .clickLaunchIcon('solidityUnitTesting')
.click('*[data-id="testTabCheckAllTests"]') .click('*[data-id="testTabCheckAllTests"]')
.clickElementAtPosition('.singleTestLabel', 5) .clickElementAtPosition('.singleTestLabel', 5)
@ -139,6 +141,19 @@ module.exports = {
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', `Method 'add' can not have parameters inside a test contract`) .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', `Method 'add' can not have parameters inside a test contract`)
}, },
'Changing current path': function (browser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfileExplorers"]')
.addFile('myTests/simple_storage_test.sol', sources[0]['browser/tests/simple_storage_test.sol'])
.clickLaunchIcon('solidityUnitTesting')
.setValue('*[data-id="uiPathInput"]', 'browser/myTests')
.clickElementAtPosition('.singleTestLabel', 0)
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000)
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutput"]')
.clearValue('*[data-id="uiPathInput"]')
.setValue('*[data-id="uiPathInput"]', 'browser/tests')
},
'Solidity Unittests': function (browser) { 'Solidity Unittests': function (browser) {
runTests(browser) runTests(browser)
}, },
@ -157,7 +172,7 @@ function runTests (browser) {
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000) .waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000)
.pause(5000) .pause(5000)
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]') .waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]')
.assert.containsText('#solidityUnittestsOutput', 'browser/4_Ballot_test.sol') .assert.containsText('#solidityUnittestsOutput', 'browser/tests/4_Ballot_test.sol')
.assert.containsText('#solidityUnittestsOutput', '✓ Check winning proposal') .assert.containsText('#solidityUnittestsOutput', '✓ Check winning proposal')
.assert.containsText('#solidityUnittestsOutput', '✓ Check winnin proposal with return value') .assert.containsText('#solidityUnittestsOutput', '✓ Check winnin proposal with return value')
.end() .end()
@ -186,11 +201,11 @@ var sources = [
} }
` `
}, },
'browser/simple_storage_test.sol': { 'browser/tests/simple_storage_test.sol': {
content: ` content: `
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.7.0;
import "remix_tests.sol"; import "remix_tests.sol";
import "./simple_storage.sol"; import "../simple_storage.sol";
contract MyTest { contract MyTest {
SimpleStorage foo; SimpleStorage foo;
@ -269,14 +284,14 @@ var sources = [
} }
` `
}, },
'browser/ks2b_test.sol': { 'browser/tests/ks2b_test.sol': {
content: ` content: `
pragma solidity >=0.4.22 <0.6.0; pragma solidity >=0.4.22 <0.6.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
import "remix_accounts.sol"; import "remix_accounts.sol";
import "./ks2a.sol"; import "../ks2a.sol";
contract kickstarterTest { contract kickstarterTest {
enum State { Started, Completed } enum State { Started, Completed }
@ -339,7 +354,7 @@ var sources = [
} }
` `
}, },
'browser/deployError_test.sol': { 'browser/tests/deployError_test.sol': {
content: ` content: `
pragma solidity ^0.6.0; pragma solidity ^0.6.0;
@ -350,7 +365,7 @@ var sources = [
} }
` `
}, },
'browser/methodFailure_test.sol': { 'browser/tests/methodFailure_test.sol': {
content: ` content: `
pragma solidity ^0.6.0; pragma solidity ^0.6.0;

@ -4,6 +4,12 @@ var sauce = require('./sauce')
module.exports = { module.exports = {
before: function (browser, done) { before: function (browser, done) {
// this test suite also contribute testing https://github.com/ethereum/remix/pull/1497 and https://github.com/ethereum/remix-ide/pull/2898
// quick explanation:
// the goal of https://github.com/ethereum/remix-ide/pull/2898 is to keep track of all the compiled contracts an not only the last one.
// this introduce an issue: if 2 compiled contracts have the same name, the second one override the first which is not wanted.
// fix's delivered by https://github.com/ethereum/remix/pull/1497: instead of getting contract by name,
// which result in name clashing we process the whole contract object (which contain bytecode, deployedbytecode, ...)
init(browser, done) init(browser, done)
}, },
'@sources': function () { '@sources': function () {

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

Loading…
Cancel
Save