Merge remote-tracking branch 'upstream/master'

pull/1/head
Sergii Bomko 6 years ago
commit 19cd3b6176
  1. 11
      .circleci/config.yml
  2. 4
      .travis.yml
  3. 5
      README.md
  4. 26
      ci/browser_tests.sh
  5. 105
      meetings.md
  6. 32
      nightwatch.js
  7. 5
      package.json
  8. 48
      release-process.md
  9. 13
      src/app.js
  10. 4
      src/app/components/plugin-manager-component.js
  11. 19
      src/app/components/side-panel.js
  12. 53
      src/app/components/vertical-icons.js
  13. 9
      src/app/debugger/debuggerUI/TxBrowser.js
  14. 4
      src/app/files/file-explorer.js
  15. 2
      src/app/panels/tab-proxy.js
  16. 26
      src/app/tabs/compile-tab.js
  17. 44
      src/app/tabs/compileTab/compileTab.js
  18. 2
      src/app/tabs/run-tab.js
  19. 2
      src/app/tabs/settings-tab.js
  20. 2
      src/app/tabs/styles/test-tab-styles.js
  21. 73
      src/app/tabs/test-tab.js
  22. 2
      src/app/tabs/testTab/testTab.js
  23. 11
      src/app/ui/landing-page/landing-page.js
  24. 2
      src/app/ui/modaldialog.js
  25. 4
      src/app/ui/tooltip.js
  26. 2
      src/framingService.js
  27. 6
      src/lib/offsetToLineColumnConverter.js
  28. 4
      src/universal-dapp-styles.js
  29. 6
      src/universal-dapp-ui.js
  30. 4
      test-browser/helpers/contracts.js
  31. 8
      test-browser/helpers/init.js
  32. 50
      test-browser/tests/generalTests.js
  33. 10
      test-browser/tests/sauce.js
  34. 18
      test-browser/tests/sharedFolderExplorer.js

@ -7,7 +7,7 @@ jobs:
remix-ide: remix-ide:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/node:9.11.2 - 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
@ -32,7 +32,14 @@ jobs:
key: dep-bundle-29-{{ checksum "package.json" }} key: dep-bundle-29-{{ checksum "package.json" }}
paths: paths:
- ~/repo/node_modules - ~/repo/node_modules
- run: npm run lint && npm run test && npm run make-mock-compiler && npm run build - run: npm run lint && npm run test && npm run make-mock-compiler
- run:
name: Download Selenium
command: wget http://selenium-release.storage.googleapis.com/3.5/selenium-server-standalone-3.5.3.jar
- run:
name: Start Selenium
command: java -jar selenium-server-standalone-3.5.3.jar
background: true
- run: ./ci/browser_tests.sh - run: ./ci/browser_tests.sh
workflows: workflows:

@ -3,7 +3,7 @@ node_js:
- "9" - "9"
branches: branches:
only: only:
- remix_alpha - master
- remix_live - remix_live
script: script:
- npm run lint && npm run test && npm run make-mock-compiler && npm run build - npm run lint && npm run test && npm run make-mock-compiler && npm run build
@ -13,7 +13,7 @@ deploy:
script: ci/deploy_from_travis_remix-alpha.sh script: ci/deploy_from_travis_remix-alpha.sh
skip_cleanup: true skip_cleanup: true
on: on:
branch: remix_alpha branch: master
- provider: script - provider: script
script: ci/deploy_from_travis_remix-live.sh script: ci/deploy_from_travis_remix-live.sh
skip_cleanup: true skip_cleanup: true

@ -37,6 +37,11 @@ Or if you want to clone the github repository (`wget` need to be installed first
```bash ```bash
git clone https://github.com/ethereum/remix-ide.git git clone https://github.com/ethereum/remix-ide.git
git clone https://github.com/ethereum/remix.git # only if you plan to link remix and remix-ide repositories and develop on it. git clone https://github.com/ethereum/remix.git # only if you plan to link remix and remix-ide repositories and develop on it.
cd remix # only if you plan to link remix and remix-ide repositories and develop on it.
npm install # only if you plan to link remix and remix-ide repositories and develop on it.
npm run bootstrap # only if you plan to link remix and remix-ide repositories and develop on it.
cd remix-ide cd remix-ide
npm install npm install
npm run setupremix # only if you plan to link remix and remix-ide repositories and develop on it. npm run setupremix # only if you plan to link remix and remix-ide repositories and develop on it.

@ -11,42 +11,22 @@ setupRemixd () {
cd .. cd ..
} }
if test $(uname -s) = "Darwin"
then
OS="osx"
FILEFORMAT="zip"
else
OS="linux"
FILEFORMAT="tar.gz"
fi
SC_VERSION="4.5.1"
SAUCECONNECT_URL="https://saucelabs.com/downloads/sc-$SC_VERSION-$OS.$FILEFORMAT"
SAUCECONNECT_USERNAME="yanneth"
SAUCECONNECT_ACCESSKEY="1f5a4560-b02b-41aa-b52b-f033aad30870"
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}} BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
SAUCECONNECT_JOBIDENTIFIER="browsersolidity_tests_${BUILD_ID}"
SAUCECONNECT_READYFILE="sc.ready"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run serve & npm run serve &
setupRemixd setupRemixd
wget "$SAUCECONNECT_URL" sleep 5
tar -zxvf sc-"$SC_VERSION"-"$OS"."$FILEFORMAT"
./sc-"$SC_VERSION"-"$OS"/bin/sc -u "$SAUCECONNECT_USERNAME" -k "$SAUCECONNECT_ACCESSKEY" -i "$SAUCECONNECT_JOBIDENTIFIER" --no-ssl-bump-domains all --readyfile "$SAUCECONNECT_READYFILE" &
while [ ! -f "$SAUCECONNECT_READYFILE" ]; do
sleep .5
done
npm run nightwatch_remote_chrome || TEST_EXITCODE=1 npm run nightwatch_remote_chrome || TEST_EXITCODE=1
npm run nightwatch_remote_firefox || TEST_EXITCODE=1 # npm run nightwatch_remote_firefox || TEST_EXITCODE=1
# npm run nightwatch_remote_safari || TEST_EXITCODE=1 # npm run nightwatch_remote_safari || TEST_EXITCODE=1
# npm run nightwatch_remote_ie || TEST_EXITCODE=1 # npm run nightwatch_remote_ie || TEST_EXITCODE=1
# npm run nightwatch_remote_parallel || TEST_EXITCODE=1 # npm run nightwatch_remote_parallel || TEST_EXITCODE=1
node ci/sauceDisconnect.js "$SAUCECONNECT_USERNAME" "$SAUCECONNECT_ACCESSKEY" "$SAUCECONNECT_JOBIDENTIFIER" # node ci/sauceDisconnect.js "$SAUCECONNECT_USERNAME" "$SAUCECONNECT_ACCESSKEY" "$SAUCECONNECT_JOBIDENTIFIER"
echo "$TEST_EXITCODE" echo "$TEST_EXITCODE"
if [ "$TEST_EXITCODE" -eq 1 ] if [ "$TEST_EXITCODE" -eq 1 ]

@ -0,0 +1,105 @@
# Team meeting 04/06/2019
## documentation
#### doc update for release
For each remix IDE PR, check whether the documentation needs to be updated.
If so, either
- update the doc with and link to the PR
- create an issue with the label "documentation"
Before releasing (better a few days before), we take time to address documentation issues that *needs* to be done. @rob is committing to checking and organize our work on this.
#### monthly doc update
We setup a monthly call where we read through the documentation and check what should be added / updated / improved
#### move to remix IDE
we move the documentation to the remix-ide repository
## medium post policy
Any post that relates to Ethereum could be put in the remix plublication.
Although that is not mandatory and left up to the writter.
## guided tour
we still need to validate the framework but "shepherd" seems to be the one.
It will work as a native plugin, started by default.
Each other native plugin can request a guided tour with:
`this.call('guidedtour', 'start', 'debugger')`
Other type of plugin may be able to the native plugin guided tour but we won't push this if the integration is not working out of the box.
We rather update the remix-plugin doc saying that `guided tour framework name` is the prefferred one.
## web site
we commit to have a public web site for general info about us.
It won't be a branded web site.
We are asking a designer for improving the Liana's logo.
We don't need to have a framework (hugo, hexo) if that's too much overhead.
@liana is testing out the easiest solution.
## solidity tutorial framework
it will be set of file:
- md
- solidity contract
- test contract
we only support md for now and move to supporting other format if needded.
It requires the "test" native plugin to extend its API.
@rob/@francois are managing that.
## remix workshops repository
the current workshop should either be removed or converted to the md tutorial framework.
@rob/@francois are managing that
## coding best practices
Use html "id" only if needed.
"id" has to be explicit enough so the node can be targeted without using nested css target.
## file manager / file explorer / file provider
They are going to be refactored out to a stand alone ts repo / npm module
## npm module structure
will likely looks like:
RemixProject/component:
/Treeview
/tab
/tabs
/dialog
/fs
RemixProject/libs:
/Solidity
/Tests
/FsProvider
RemixProject/IDE
## browser test
a PR (https://github.com/ethereum/remix-ide/pull/1961) is waiting for merging @yann
It iwll be followed by other PRs, aiming to improve process of writting browser tests.
## desktop version
we will propose an offline version using electron (@yann)
first steps :
- put all the public link to the local package
- basic electron wrapper
## out reach beyond community
We agree it is something interesting to explore,
It is not 100% dev tool nor remix so we should organize call with other people from EF at least

@ -1,7 +1,5 @@
'use strict' 'use strict'
var buildId = process.env.CIRCLE_BUILD_NUM || process.env.TRAVIS_JOB_NUMBER
module.exports = { module.exports = {
'src_folders': ['test-browser/tests'], 'src_folders': ['test-browser/tests'],
'output_folder': 'reports', 'output_folder': 'reports',
@ -12,13 +10,8 @@ module.exports = {
'test_settings': { 'test_settings': {
'default': { 'default': {
'launch_url': 'http://ondemand.saucelabs.com:80', 'selenium_port': 4444,
'selenium_host': 'ondemand.saucelabs.com', 'selenium_host': 'localhost',
'selenium_port': 80,
'silent': true,
'username': 'yanneth',
'access_key': '1f5a4560-b02b-41aa-b52b-f033aad30870',
'use_ssl': false,
'globals': { 'globals': {
'waitForConditionTimeout': 10000, 'waitForConditionTimeout': 10000,
'asyncHookTimeout': 100000 'asyncHookTimeout': 100000
@ -30,9 +23,7 @@ module.exports = {
'desiredCapabilities': { 'desiredCapabilities': {
'browserName': 'firefox', 'browserName': 'firefox',
'javascriptEnabled': true, 'javascriptEnabled': true,
'acceptSslCerts': true, 'acceptSslCerts': true
'build': 'build-' + buildId,
'tunnel-identifier': 'browsersolidity_tests_' + buildId
} }
}, },
@ -41,8 +32,6 @@ module.exports = {
'browserName': 'chrome', 'browserName': 'chrome',
'javascriptEnabled': true, 'javascriptEnabled': true,
'acceptSslCerts': true, 'acceptSslCerts': true,
'build': 'build-' + buildId,
'tunnel-identifier': 'browsersolidity_tests_' + buildId,
'chromeOptions': { 'chromeOptions': {
'args': ['window-size=2560,1440', 'start-fullscreen'] 'args': ['window-size=2560,1440', 'start-fullscreen']
} }
@ -53,11 +42,7 @@ module.exports = {
'desiredCapabilities': { 'desiredCapabilities': {
'browserName': 'safari', 'browserName': 'safari',
'javascriptEnabled': true, 'javascriptEnabled': true,
'platform': 'macOS 10.13', 'acceptSslCerts': true
'version': '11.0',
'acceptSslCerts': true,
'build': 'build-' + buildId,
'tunnel-identifier': 'browsersolidity_tests_' + buildId
} }
}, },
@ -65,18 +50,11 @@ module.exports = {
'desiredCapabilities': { 'desiredCapabilities': {
'browserName': 'internet explorer', 'browserName': 'internet explorer',
'javascriptEnabled': true, 'javascriptEnabled': true,
'platform': 'Windows 10', 'acceptSslCerts': true
'acceptSslCerts': true,
'version': '11.103',
'build': 'build-' + buildId,
'tunnel-identifier': 'browsersolidity_tests_' + buildId
} }
}, },
'local': { 'local': {
'launch_url': 'http://localhost:8080',
'selenium_port': 4444,
'selenium_host': 'localhost',
'desiredCapabilities': { 'desiredCapabilities': {
'browserName': 'chrome', 'browserName': 'chrome',
'javascriptEnabled': true, 'javascriptEnabled': true,

@ -1,6 +1,6 @@
{ {
"name": "remix-ide", "name": "remix-ide",
"version": "v0.8.0", "version": "v0.8.1",
"description": "Minimalistic browser-based Solidity IDE", "description": "Minimalistic browser-based Solidity IDE",
"devDependencies": { "devDependencies": {
"@fortawesome/fontawesome-free": "^5.8.1", "@fortawesome/fontawesome-free": "^5.8.1",
@ -68,7 +68,7 @@
}, },
"dependencies": { "dependencies": {
"http-server": "^0.11.1", "http-server": "^0.11.1",
"remix-plugin": "0.0.2-alpha.9", "remix-plugin": "0.0.2-alpha.10",
"remixd": "0.1.8-alpha.6" "remixd": "0.1.8-alpha.6"
}, },
"repository": { "repository": {
@ -173,6 +173,7 @@
"minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false", "minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false",
"nightwatch_local": "nightwatch --config nightwatch.js --env local", "nightwatch_local": "nightwatch --config nightwatch.js --env local",
"nightwatch_local_general": "nightwatch ./test-browser/tests/generalTests.js --config nightwatch.js --env local ", "nightwatch_local_general": "nightwatch ./test-browser/tests/generalTests.js --config nightwatch.js --env local ",
"nightwatch_local_sharedFolderExplorer": "nightwatch ./test-browser/tests/sharedFolderExplorer.js --config nightwatch.js --env local ",
"nightwatch_local_debugger": "nightwatch --config nightwatch_debugger.js --env local", "nightwatch_local_debugger": "nightwatch --config nightwatch_debugger.js --env local",
"nightwatch_remote_chrome": "nightwatch --config nightwatch.js --env chrome", "nightwatch_remote_chrome": "nightwatch --config nightwatch.js --env chrome",
"nightwatch_remote_firefox": "nightwatch --config nightwatch.js --env default", "nightwatch_remote_firefox": "nightwatch --config nightwatch.js --env default",

@ -3,56 +3,18 @@ This document includes:
- how to update remix-alpha.ethereum.org. - how to update remix-alpha.ethereum.org.
- how to release remix IDE. - how to release remix IDE.
# remix-* release (npm release, github release)
- For a specifix module (lib/core/debug/ide/solidity/tests)
- In a new branch, bump the version in package.json, push it and create PR.
- Wait for tests completion.
- merge PR
- build the branch ( `npm run build` for remix-ide ).
- execute `npm publish`.
- create new `tag` ( e.g `git tag v0.6.1-alpha.2` ).
- push the tag ( `git push --tag` ).
- execute `gren changelog --generate -t <new tag>..<previous tag> --data-source=prs`.
- in `changelog.md` remove the closed and non merged PR.
- publish a release in github using the changelog.
# remix.ethereum.org update # remix.ethereum.org update
This is not strictly speaking a release. Updating the remix site is done through the Travis build: This is not strictly speaking a release. Updating the remix site is done through the Travis build:
- In remix-ide repository - switch to the remix_live
- Switch to the branch `remix_live` - git reset --hard <master-commit-hash>
- Rebase the branch against master - git push -f origin remix_live
- Force push Travis will build automaticaly and update remix.ethereum.org
- https://travis-ci.org/ethereum/remix-ide
- Click `More options`
- Click `Trigger build`
- Select `remix_live`
- Click `Trigger custom build`
- Once the build is finished (can take a while) and successful, check remix.ethereum.org is updated accordingly
# remix-alpha.ethereum.org update # remix-alpha.ethereum.org update
This is not strictly speaking a release. Updating the remix-alpha site is done through the Travis build: remix-alpha.ethereum.org is automaticaly updated every time commits are pushed to master
- https://travis-ci.org/ethereum/remix-ide
- Click `More options`
- Click `Trigger build`
- Select `Master`
- Click `Trigger custom build`
- Once the build is finished (can take a while) and successful, check remix-alpha.ethereum.org is updated accordingly
# beta testing remix
We publish a new release roughly every month and greatly appreciate support on beta testing.
By giving report, beta testers help to:
- verify viabilty (in term core and UX design) of new features
- track possible regression
- propose new update
- contribute on reviewing / building Pull Request
# remix IDE release # remix IDE release

@ -263,10 +263,14 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
return 'Are you sure you want to leave?' return 'Are you sure you want to leave?'
} }
let appStore = new EntityStore('module', 'name')
const appManager = new RemixAppManager(appStore)
registry.put({api: appManager, name: 'appmanager'})
registry.put({api: msg => self._components.mainview.logHtmlMessage(msg), name: 'logCallback'}) registry.put({api: msg => self._components.mainview.logHtmlMessage(msg), name: 'logCallback'})
// helper for converting offset to line/column // helper for converting offset to line/column
var offsetToLineColumnConverter = new OffsetToLineColumnConverter() var offsetToLineColumnConverter = new OffsetToLineColumnConverter(appManager)
registry.put({api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter'}) registry.put({api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter'})
// json structure for hosting the last compilattion result // json structure for hosting the last compilattion result
@ -318,10 +322,6 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
// TODO: There are still a lot of dep between mainview and filemanager // TODO: There are still a lot of dep between mainview and filemanager
let appStore = new EntityStore('module', 'name')
const appManager = new RemixAppManager(appStore)
registry.put({api: appManager, name: 'appmanager'})
// ----------------- file manager ---------------------------- // ----------------- file manager ----------------------------
self._components.fileManager = new FileManager() self._components.fileManager = new FileManager()
const fileManager = self._components.fileManager const fileManager = self._components.fileManager
@ -412,7 +412,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
let test = new TestTab( let test = new TestTab(
registry.get('filemanager').api, registry.get('filemanager').api,
registry.get('filepanel').api, registry.get('filepanel').api,
compileTab compileTab,
appStore
) )
let sourceHighlighters = registry.get('editor').api.sourceHighlighters let sourceHighlighters = registry.get('editor').api.sourceHighlighters

@ -91,7 +91,7 @@ class PluginManagerComponent extends BaseApi {
</button>` </button>`
return yo` return yo`
<article class="list-group-item py-1" title="${name}" > <article id="remixPluginManagerListItem_${name}" class="list-group-item py-1" title="${displayName}" >
<div class="${css.row} justify-content-between align-items-center"> <div class="${css.row} justify-content-between align-items-center">
<h6 class="${css.displayName}">${displayName}</h6> <h6 class="${css.displayName}">${displayName}</h6>
${activationButton} ${activationButton}
@ -155,7 +155,7 @@ class PluginManagerComponent extends BaseApi {
? yo` ? yo`
<nav class="navbar navbar-expand-lg navbar-light bg-light justify-content-between align-items-center"> <nav class="navbar navbar-expand-lg navbar-light bg-light justify-content-between align-items-center">
<span class="navbar-brand">Inactive Modules</span> <span class="navbar-brand">Inactive Modules</span>
<span class="badge badge-pill badge-primary">${inactives.length}</span> <span class="badge badge-pill badge-primary" style = "cursor: default;">${inactives.length}</span>
</nav>` </nav>`
: '' : ''

@ -10,6 +10,8 @@ const css = csjs`
.swapitTitle { .swapitTitle {
text-transform: uppercase; text-transform: uppercase;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.swapitTitle i{ .swapitTitle i{
padding-left: 6px; padding-left: 6px;
@ -21,6 +23,7 @@ const css = csjs`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
justify-content: flex-start;
} }
.swapitHeader h6 { .swapitHeader h6 {
margin: 0; margin: 0;
@ -33,6 +36,9 @@ const css = csjs`
height: calc(100% - 35px); height: calc(100% - 35px);
overflow: auto; overflow: auto;
} }
.titleInfo {
padding-left: 10px;
}
` `
const options = { const options = {
@ -56,7 +62,7 @@ export class SidePanel extends AbstractPanel {
yo.update(this.header, this.renderHeader()) yo.update(this.header, this.renderHeader())
} }
/** The header of the swap panel */ /** The header of the side panel */
renderHeader () { renderHeader () {
let name = ' - ' let name = ' - '
let docLink = '' let docLink = ''
@ -64,14 +70,15 @@ export class SidePanel extends AbstractPanel {
const { profile } = this.store.getOne(this.active) const { profile } = this.store.getOne(this.active)
name = profile.displayName ? profile.displayName : profile.name name = profile.displayName ? profile.displayName : profile.name
const docsRoot = 'https://remix.readthedocs.io/en/latest/' const docsRoot = 'https://remix.readthedocs.io/en/latest/'
docLink = profile.documentation ? yo`<a href="${docsRoot}${profile.documentation}" title="link to documentation" target="_blank"><i aria-hidden="true" class="fas fa-book"></i></a>` : '' docLink = profile.documentation ? yo`<a href="${docsRoot}${profile.documentation}" class="${css.titleInfo}" title="link to documentation" target="_blank"><i aria-hidden="true" class="fas fa-book"></i></a>` : ''
} }
return yo` return yo`
<header class="${css.swapitHeader}"> <header class="${css.swapitHeader}">
<h6 class="${css.swapitTitle}">${name}${docLink}</h6> <h6 class="${css.swapitTitle}">${name}</h6>
</div> ${docLink}
</header>` </header>
`
} }
render () { render () {

@ -10,7 +10,7 @@ export class VerticalIcons {
constructor (name, appStore, homeProfile) { constructor (name, appStore, homeProfile) {
this.store = appStore this.store = appStore
this.homeProfile = homeProfile this.homeProfile = homeProfile.profile
this.events = new EventEmitter() this.events = new EventEmitter()
this.icons = {} this.icons = {}
this.iconKind = {} this.iconKind = {}
@ -74,12 +74,13 @@ export class VerticalIcons {
* @param {ModuleProfile} profile The profile of the module * @param {ModuleProfile} profile The profile of the module
*/ */
addIcon ({kind, name, icon, displayName, tooltip}) { addIcon ({kind, name, icon, displayName, tooltip}) {
let title = (displayName || name)// + (tooltip ? tooltip : "") let title = (tooltip || displayName || name)
this.icons[name] = yo` this.icons[name] = yo`
<div <div
class="${css.icon}" class="${css.icon}"
onclick="${() => { this._iconClick(name) }}" onclick="${() => { this.toggle(name) }}"
plugin="${name}" title="${title}" > plugin="${name}"
title="${title}">
<img class="image" src="${icon}" alt="${name}" /> <img class="image" src="${icon}" alt="${name}" />
</div>` </div>`
this.iconKind[kind || 'other'].appendChild(this.icons[name]) this.iconKind[kind || 'other'].appendChild(this.icons[name])
@ -195,9 +196,22 @@ export class VerticalIcons {
* @param {string} name Name of profile of the module to activate * @param {string} name Name of profile of the module to activate
*/ */
select (name) { select (name) {
this.updateActivations(name)
this.events.emit('showContent', name)
}
/**
* Toggles the side panel for plugin
* @param {string} name Name of profile of the module to activate
*/
toggle (name) {
this.updateActivations(name)
this.events.emit('toggleContent', name)
}
updateActivations (name) {
this.removeActive() this.removeActive()
this.addActive(name) this.addActive(name)
this.events.emit('showContent', name)
} }
onThemeChanged (themeType) { onThemeChanged (themeType) {
@ -209,10 +223,11 @@ export class VerticalIcons {
} }
} }
_iconClick (name) { /**
this.removeActive() * Show the home page
this.addActive(name) */
this.events.emit('toggleContent', name) showHome () {
globalRegistry.get('appmanager').api.ensureActivated('home')
} }
render () { render () {
@ -220,7 +235,7 @@ export class VerticalIcons {
<div <div
class="${css.homeIcon}" class="${css.homeIcon}"
onclick="${(e) => { onclick="${(e) => {
globalRegistry.get('appmanager').api.ensureActivated('home') this.showHome()
}}" }}"
plugin="${this.homeProfile.name}" title="${this.homeProfile.displayName}" plugin="${this.homeProfile.name}" title="${this.homeProfile.displayName}"
> >
@ -344,15 +359,15 @@ export class VerticalIcons {
this.view = yo` this.view = yo`
<div class=${css.icons}> <div class=${css.icons}>
${home} ${home}
${this.iconKind['fileexplorer']} ${this.iconKind['fileexplorer']}
${this.iconKind['compile']} ${this.iconKind['compile']}
${this.iconKind['run']} ${this.iconKind['run']}
${this.iconKind['testing']} ${this.iconKind['testing']}
${this.iconKind['analysis']} ${this.iconKind['analysis']}
${this.iconKind['debugging']} ${this.iconKind['debugging']}
${this.iconKind['other']} ${this.iconKind['other']}
${this.iconKind['settings']} ${this.iconKind['settings']}
</div> </div>
` `
return this.view return this.view

@ -62,6 +62,9 @@ TxBrowser.prototype.submit = function () {
TxBrowser.prototype.updateTxN = function (ev) { TxBrowser.prototype.updateTxN = function (ev) {
this.state.txNumber = ev.target.value this.state.txNumber = ev.target.value
if (this.view) {
yo.update(this.view, this.render())
}
} }
TxBrowser.prototype.load = function (txHash, tx) { TxBrowser.prototype.load = function (txHash, tx) {
@ -81,6 +84,7 @@ TxBrowser.prototype.setState = function (state) {
TxBrowser.prototype.render = function () { TxBrowser.prototype.render = function () {
var self = this var self = this
let action = yo`<button class='btn btn-primary btn-sm ${css.txbutton}' id='load' title='${this.state.debugging ? 'Stop' : 'Start'} debugging' onclick=${function () { self.submit() }}>${this.state.debugging ? 'Stop' : 'Start'} debugging</button>`
var view = yo`<div class="${css.container}"> var view = yo`<div class="${css.container}">
<div class="${css.txContainer}"> <div class="${css.txContainer}">
<div class="${css.txinputs} p-1 input-group"> <div class="${css.txinputs} p-1 input-group">
@ -94,7 +98,7 @@ TxBrowser.prototype.render = function () {
/> />
</div> </div>
<div class="${css.txbuttons} btn-group p-1"> <div class="${css.txbuttons} btn-group p-1">
<button class='btn btn-primary btn-sm ${css.txbutton}' id='load' title='${this.state.debugging ? 'Stop' : 'Start'} debugging' onclick=${function () { self.submit() }}>${this.state.debugging ? 'Stop' : 'Start'} debugging</button> ${action}
</div> </div>
</div> </div>
<span id='error'></span> <span id='error'></span>
@ -102,6 +106,9 @@ TxBrowser.prototype.render = function () {
if (this.state.debugging) { if (this.state.debugging) {
view.querySelectorAll('input').forEach(element => { element.setAttribute('disabled', '') }) view.querySelectorAll('input').forEach(element => { element.setAttribute('disabled', '') })
} }
if (!this.state.txNumber) {
action.setAttribute('disabled', 'disabled')
}
if (!this.view) { if (!this.view) {
this.view = view this.view = view
} }

@ -522,12 +522,12 @@ fileExplorer.prototype.renderMenuItems = function () {
items = this.menuItems.map(({action, title, icon}) => { items = this.menuItems.map(({action, title, icon}) => {
if (action === 'uploadFile') { if (action === 'uploadFile') {
return yo` return yo`
<label class="${icon} ${css.newFile}" title="${title}"> <span class="${icon} ${css.newFile}" title="${title}">
<input type="file" onchange=${(event) => { <input type="file" onchange=${(event) => {
event.stopPropagation() event.stopPropagation()
this.uploadFile(event) this.uploadFile(event)
}} multiple /> }} multiple />
</label> </span>
` `
} else { } else {
return yo` return yo`

@ -117,7 +117,7 @@ export class TabProxy {
id: name, id: name,
title, title,
icon, icon,
tooltip: name tooltip: title
}) })
this._handlers[name] = { switchTo, close } this._handlers[name] = { switchTo, close }
} }

@ -132,7 +132,7 @@ class CompileTab extends CompilerApi {
// Update contract Selection // Update contract Selection
let contractMap = {} let contractMap = {}
if (success) this.compiler.visitContracts((contract) => { contractMap[contract.name] = contract }) if (success) this.compiler.visitContracts((contract) => { contractMap[contract.name] = contract })
let contractSelection = this.contractSelection(Object.keys(contractMap) || [], source.target) let contractSelection = this.contractSelection(contractMap)
yo.update(this._view.contractSelection, contractSelection) yo.update(this._view.contractSelection, contractSelection)
if (data['error']) { if (data['error']) {
@ -173,6 +173,11 @@ class CompileTab extends CompilerApi {
return this.compileTabLogic.compiler.lastCompilationResult return this.compileTabLogic.compiler.lastCompilationResult
} }
// This function is used by remix-plugin
compile (fileName) {
return this.compileTabLogic.compileFile(fileName)
}
/********* /*********
* SUB-COMPONENTS * SUB-COMPONENTS
*/ */
@ -181,13 +186,22 @@ class CompileTab extends CompilerApi {
* Section to select the compiled contract * Section to select the compiled contract
* @param {string[]} contractList Names of the compiled contracts * @param {string[]} contractList Names of the compiled contracts
*/ */
contractSelection (contractList = [], sourceFile) { contractSelection (contractMap) {
// Return the file name of a path: ex "browser/ballot.sol" -> "ballot.sol"
const getFileName = (path) => {
const part = path.split('/')
return part[part.length - 1]
}
const contractList = contractMap ? Object.keys(contractMap).map((key) => ({
name: key,
file: getFileName(contractMap[key].file)
})) : []
let selectEl = yo` let selectEl = yo`
<select <select
onchange="${e => this.selectContract(e.target.value)}" onchange="${e => this.selectContract(e.target.value)}"
id="compiledContracts" class="custom-select" id="compiledContracts" class="custom-select"
> >
${contractList.map((name) => yo`<option value="${name}">${name}</option>`)} ${contractList.map(({name, file}) => yo`<option value="${name}">${name} (${file})</option>`)}
</select> </select>
` `
let result = contractList.length let result = contractList.length
@ -207,14 +221,14 @@ class CompileTab extends CompilerApi {
Compilation Details Compilation Details
</button> </button>
<!-- Copy to Clipboard --> <!-- Copy to Clipboard -->
<div class="${css.contractHelperButtons} btn-secondary"> <div class="${css.contractHelperButtons}">
<div class="input-group"> <div class="input-group">
<div class="btn-group" role="group" aria-label="Copy to Clipboard"> <div class="btn-group" role="group" aria-label="Copy to Clipboard">
<button class="btn btn-secondary" title="Copy ABI to clipboard" onclick="${() => { this.copyABI() }}"> <button class="btn" title="Copy ABI to clipboard" onclick="${() => { this.copyABI() }}">
<i class="${css.copyIcon} far fa-clipboard" aria-hidden="true"></i> <i class="${css.copyIcon} far fa-clipboard" aria-hidden="true"></i>
<span>ABI</span> <span>ABI</span>
</button> </button>
<button class="btn btn-secondary" title="Copy Bytecode to clipboard" onclick="${() => { this.copyBytecode() }}"> <button class="btn" title="Copy Bytecode to clipboard" onclick="${() => { this.copyBytecode() }}">
<i class="${css.copyIcon} far fa-clipboard" aria-hidden="true"></i> <i class="${css.copyIcon} far fa-clipboard" aria-hidden="true"></i>
<span>Bytecode</span> <span>Bytecode</span>
</button> </button>

@ -33,28 +33,38 @@ class CompileTab {
this.compiler.setOptimize(this.optimize) this.compiler.setOptimize(this.optimize)
} }
runCompiler () { /**
this.fileManager.saveCurrentFile() * Compile a specific file of the file manager
this.editor.clearAnnotations() * @param {string} target the path to the file to compile
var currentFile = this.config.get('currentFile') */
if (!currentFile) return compileFile (target) {
if (!/\.sol/.exec(currentFile)) return if (!target) throw new Error('No target provided for compiliation')
if (!/\.sol/.exec(target)) throw new Error(`${target} is not a solidity file. It cannot be compiled with solidity compiler`)
// only compile *.sol file. // only compile *.sol file.
var target = currentFile const provider = this.fileManager.fileProviderOf(target)
var sources = {} if (!provider) throw new Error(`cannot compile ${target}. Does not belong to any explorer`)
var provider = this.fileManager.fileProviderOf(currentFile) return new Promise((resolve, reject) => {
if (!provider) return console.log('cannot compile ' + currentFile + '. Does not belong to any explorer') provider.get(target, (error, content) => {
provider.get(target, (error, content) => { if (error) return reject(error)
if (error) return console.log(error) const sources = { [target]: { content } }
sources[target] = { content } this.event.emit('startingCompilation')
this.event.emit('startingCompilation')
setTimeout(() => {
// setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation') // setTimeout fix the animation on chrome... (animation triggered by 'staringCompilation')
this.compiler.compile(sources, target) setTimeout(() => this.compiler.compile(sources, target), 100)
}, 100) })
}) })
} }
runCompiler () {
try {
this.fileManager.saveCurrentFile()
this.editor.clearAnnotations()
var currentFile = this.config.get('currentFile')
return this.compileFile(currentFile)
} catch (err) {
console.error(err)
}
}
importExternal (url, cb) { importExternal (url, cb) {
this.compilerImport.import(url, this.compilerImport.import(url,

@ -18,7 +18,7 @@ import { BaseApi } from 'remix-plugin'
const profile = { const profile = {
name: 'run', name: 'run',
displayName: 'Deploy and run transactions', displayName: 'Deploy & run transactions',
methods: [], methods: [],
events: [], events: [],
icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzFfY29weSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4Ig0KCSB5PSIwcHgiIHdpZHRoPSI3NDIuNTQ1cHgiIGhlaWdodD0iNjc2Ljg4NnB4IiB2aWV3Qm94PSIwIC0wLjIwNCA3NDIuNTQ1IDY3Ni44ODYiDQoJIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAtMC4yMDQgNzQyLjU0NSA2NzYuODg2IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwb2x5Z29uIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjI5NS45MTEsMC43MTEgNDg4LjkxMSwzMDQuMTg2IDQ4OC45MTEsMzk3LjE4MSAyOTMuOTExLDY3Ni41NTYgDQoJCTc0MS43ODYsMzQ5Ljk0MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4Myw0MDYuNTg5IDIwOS43OTEsNTE5LjQ5NCAxLjg0Niw0MDYuMjM0IDIwOS43OTEsNjc1Ljg2MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4MywzMTguNzA3IDIwOS43OTEsMC43MTEgMS44NDYsMzE4LjQyOCAyMDkuNzkxLDQzMS42ODkgCSIvPg0KPC9nPg0KPC9zdmc+DQo=', icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzFfY29weSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4Ig0KCSB5PSIwcHgiIHdpZHRoPSI3NDIuNTQ1cHgiIGhlaWdodD0iNjc2Ljg4NnB4IiB2aWV3Qm94PSIwIC0wLjIwNCA3NDIuNTQ1IDY3Ni44ODYiDQoJIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAtMC4yMDQgNzQyLjU0NSA2NzYuODg2IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwb2x5Z29uIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjI5NS45MTEsMC43MTEgNDg4LjkxMSwzMDQuMTg2IDQ4OC45MTEsMzk3LjE4MSAyOTMuOTExLDY3Ni41NTYgDQoJCTc0MS43ODYsMzQ5Ljk0MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4Myw0MDYuNTg5IDIwOS43OTEsNTE5LjQ5NCAxLjg0Niw0MDYuMjM0IDIwOS43OTEsNjc1Ljg2MyAJIi8+DQoJPHBvbHlnb24gc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iNDE3LjA4MywzMTguNzA3IDIwOS43OTEsMC43MTEgMS44NDYsMzE4LjQyOCAyMDkuNzkxLDQzMS42ODkgCSIvPg0KPC9nPg0KPC9zdmc+DQo=',

@ -71,7 +71,7 @@ module.exports = class SettingsTab extends BaseApi {
var gistAddToken = yo`<input class="${css.savegisttoken} btn btn-sm btn-primary" id="savegisttoken" onclick=${() => { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }} value="Save" type="button">` var gistAddToken = yo`<input class="${css.savegisttoken} btn btn-sm btn-primary" id="savegisttoken" onclick=${() => { this.config.set('settings/gist-access-token', gistAccessToken.value); tooltip('Access token saved') }} value="Save" type="button">`
var gistRemoveToken = yo`<input class="btn btn-sm btn-primary" id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; this.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">` var gistRemoveToken = yo`<input class="btn btn-sm btn-primary" id="removegisttoken" onclick=${() => { gistAccessToken.value = ''; this.config.set('settings/gist-access-token', ''); tooltip('Access token removed') }} value="Remove" type="button">`
this._view.gistToken = yo`<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => this.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div>` this._view.gistToken = yo`<div class="${css.checkboxText}">${gistAccessToken}${copyToClipboard(() => this.config.get('settings/gist-access-token'))}${gistAddToken}${gistRemoveToken}</div>`
this._view.optionVM = yo`<input onchange=${onchangeOption} checked class="align-middle form-check-input" id="alwaysUseVM" type="checkbox">` this._view.optionVM = yo`<input onchange=${onchangeOption} class="align-middle form-check-input" id="alwaysUseVM" type="checkbox">`
if (this.config.get('settings/always-use-vm') === undefined) this.config.set('settings/always-use-vm', true) if (this.config.get('settings/always-use-vm') === undefined) this.config.set('settings/always-use-vm', true)
if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '') if (this.config.get('settings/always-use-vm')) this._view.optionVM.setAttribute('checked', '')
this._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="align-middle form-check-input">` this._view.personal = yo`<input onchange=${onchangePersonal} id="personal" type="checkbox" class="align-middle form-check-input">`

@ -45,6 +45,7 @@ var css = csjs`
align-items: center; align-items: center;
} }
.runButton { .runButton {
white-space: nowrap;
} }
.generateTestFile { .generateTestFile {
margin-top: 20px; margin-top: 20px;
@ -58,6 +59,7 @@ var css = csjs`
.label { .label {
display: flex; display: flex;
align-items: center; align-items: center;
white-space: nowrap;
} }
` `
module.exports = css module.exports = css

@ -19,16 +19,22 @@ const profile = {
} }
module.exports = class TestTab extends BaseApi { module.exports = class TestTab extends BaseApi {
constructor (fileManager, filePanel, compileTab) { constructor (fileManager, filePanel, compileTab, appStore) {
super(profile) super(profile)
this.compileTab = compileTab this.compileTab = compileTab
this._view = { el: null } this._view = { el: null }
this.compileTab = compileTab this.compileTab = compileTab
this.fileManager = fileManager this.fileManager = fileManager
this.filePanel = filePanel this.filePanel = filePanel
this.appStore = appStore
this.testTabLogic = new TestTabLogic(fileManager) this.testTabLogic = new TestTabLogic(fileManager)
this.data = {} this.data = {}
this.testList = yo`<div class=${css.testList}></div>` this.appStore.event.on('activate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile())
})
this.appStore.event.on('deactivate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile())
})
} }
activate () { activate () {
@ -47,15 +53,20 @@ module.exports = class TestTab extends BaseApi {
this.data.selectedTests.push(file) this.data.selectedTests.push(file)
}) })
this.fileManager.events.on('noFileSelected', () => {
this.updateGenerateFileAction()
this.updateRunAction()
this.updateTestFileList()
})
this.fileManager.events.on('currentFileChanged', (file, provider) => { this.fileManager.events.on('currentFileChanged', (file, provider) => {
this.updateGenerateFileAction(file)
this.updateRunAction(file)
this.testTabLogic.getTests((error, tests) => { this.testTabLogic.getTests((error, tests) => {
if (error) return tooltip(error) if (error) return tooltip(error)
this.data.allTests = tests this.data.allTests = tests
this.data.selectedTests = [...this.data.allTests] this.data.selectedTests = [...this.data.allTests]
this.updateTestFileList(tests)
const testsMessage = (tests.length ? this.listTests() : 'No test file available')
yo.update(this.testList, yo`<div class=${css.testList}>${testsMessage}</div>`)
if (!this.testsOutput || !this.testsSummary) return if (!this.testsOutput || !this.testsSummary) return
}) })
}) })
@ -148,13 +159,55 @@ module.exports = class TestTab extends BaseApi {
} }
runTests () { runTests () {
this.loading.hidden = false
this.testsOutput.innerHTML = '' this.testsOutput.innerHTML = ''
this.testsSummary.innerHTML = '' this.testsSummary.innerHTML = ''
var tests = this.data.selectedTests var tests = this.data.selectedTests
if (!tests) return
this.loading.hidden = tests.length === 0
async.eachOfSeries(tests, (value, key, callback) => { this.runTest(value, callback) }) async.eachOfSeries(tests, (value, key, callback) => { this.runTest(value, callback) })
} }
updateGenerateFileAction (currentFile) {
let el = yo`<button class="${css.generateTestFile} btn btn-primary" onclick="${this.testTabLogic.generateTestFile.bind(this.testTabLogic)}">Generate test file</button>`
if (!currentFile) {
el.setAttribute('disabled', 'disabled')
el.setAttribute('title', 'No file selected')
}
if (!this.generateFileActionElement) {
this.generateFileActionElement = el
} else {
yo.update(this.generateFileActionElement, el)
}
return this.generateFileActionElement
}
updateRunAction (currentFile) {
let el = yo`<button class="${css.runButton} btn btn-primary" onclick="${this.runTests.bind(this)}">Run Tests</button>`
const isSolidityActive = this.appStore.isActive('solidity')
if (!currentFile || !isSolidityActive) {
el.setAttribute('disabled', 'disabled')
if (!currentFile) el.setAttribute('title', 'No file selected')
if (!isSolidityActive) el.setAttribute('title', 'The solidity module should be activated')
}
if (!this.runActionElement) {
this.runActionElement = el
} else {
yo.update(this.runActionElement, el)
}
return this.runActionElement
}
updateTestFileList (tests) {
const testsMessage = (tests && tests.length ? this.listTests() : 'No test file available')
let el = yo`<div class=${css.testList}>${testsMessage}</div>`
if (!this.testFilesListElement) {
this.testFilesListElement = el
} else {
yo.update(this.testFilesListElement, el)
}
return this.testFilesListElement
}
render () { render () {
this.testsOutput = yo`<div class="${css.container} m-3 border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>` this.testsOutput = yo`<div class="${css.container} m-3 border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>`
this.testsSummary = yo`<div class="${css.container} border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>` this.testsSummary = yo`<div class="${css.container} border border-primary border-right-0 border-left-0 border-bottom-0" hidden='true' id="tests"></div>`
@ -172,11 +225,11 @@ module.exports = class TestTab extends BaseApi {
For more details, see For more details, see
How to test smart contracts guide in our documentation. How to test smart contracts guide in our documentation.
<br/> <br/>
<div class="${css.generateTestFile} btn btn-secondary" onclick="${this.testTabLogic.generateTestFile.bind(this.testTabLogic)}">Generate test file</div> ${this.updateGenerateFileAction()}
</div> </div>
<div class="${css.tests}"> <div class="${css.tests}">
<div class="${css.buttons}"> <div class="${css.buttons}">
<div class="${css.runButton} btn btn-primary" onclick="${this.runTests.bind(this)}">Run Tests</div> ${this.updateRunAction()}
<label class="${css.label} mx-4 m-2" for="checkAllTests"> <label class="${css.label} mx-4 m-2" for="checkAllTests">
<input id="checkAllTests" <input id="checkAllTests"
type="checkbox" type="checkbox"
@ -186,7 +239,7 @@ module.exports = class TestTab extends BaseApi {
Check/Uncheck all Check/Uncheck all
</label> </label>
</div> </div>
${this.testList} ${this.updateTestFileList()}
<hr> <hr>
<div class="${css.buttons}" ><h6>Results:${this.loading}</h6></div> <div class="${css.buttons}" ><h6>Results:${this.loading}</h6></div>
${this.testsOutput} ${this.testsOutput}

@ -33,7 +33,7 @@ class TestTabLogic {
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) cb(null, tests, path)
} }
generateTestContractSample () { generateTestContractSample () {

@ -96,9 +96,12 @@ export class LandingPage extends BaseApi {
let load = function (service, item, examples, info) { let load = function (service, item, examples, info) {
let compilerImport = new CompilerImport() let compilerImport = new CompilerImport()
let fileProviders = globalRegistry.get('fileproviders').api let fileProviders = globalRegistry.get('fileproviders').api
const msg = yo`<div class="p-2"><span>Enter the ${item} you would like to load.</span> const msg = yo`
<div>${info}</div> <div class="p-2">
<div>e.g ${examples.map((url) => { return yo`<div class="p-1"><a>${url}</a></div>` })}</div></div>` <span>Enter the ${item} you would like to load.</span>
<div>${info}</div>
<div>e.g ${examples.map((url) => { return yo`<div class="p-1"><a>${url}</a></div>` })}</div>
</div>`
modalDialogCustom.prompt(`Import from ${service}`, msg, null, (target) => { modalDialogCustom.prompt(`Import from ${service}`, msg, null, (target) => {
if (target !== '') { if (target !== '') {
@ -222,7 +225,7 @@ export class LandingPage extends BaseApi {
<p class="mb-1">Import From:</p> <p class="mb-1">Import From:</p>
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-sm btn-secondary" onclick=${() => { importFromGist() }}>Gist</button> <button class="btn btn-sm btn-secondary" onclick=${() => { importFromGist() }}>Gist</button>
<button class="btn btn-sm btn-secondary" onclick=${() => { load('Github', 'github URL', ['https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/crowdsale/Crowdsale.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol', 'github:OpenZeppelin/openzeppelin-solidity/contracts/ownership/Ownable.sol#v2.1.2']) }}>Github</button> <button class="btn btn-sm btn-secondary" onclick=${() => { load('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol', 'github:OpenZeppelin/openzeppelin-solidity/contracts/ownership/Ownable.sol#v2.1.2']) }}>GitHub</button>
<button class="btn btn-sm btn-secondary" onclick=${() => { load('Swarm', 'bzz-raw URL', ['bzz-raw://<swarm-hash>']) }}>Swarm</button> <button class="btn btn-sm btn-secondary" onclick=${() => { load('Swarm', 'bzz-raw URL', ['bzz-raw://<swarm-hash>']) }}>Swarm</button>
<button class="btn btn-sm btn-secondary" onclick=${() => { load('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>']) }}>Ipfs</button> <button class="btn btn-sm btn-secondary" onclick=${() => { load('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>']) }}>Ipfs</button>
<button class="btn btn-sm btn-secondary" onclick=${() => { load('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/master/contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol']) }}>https</button> <button class="btn btn-sm btn-secondary" onclick=${() => { load('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/master/contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol']) }}>https</button>

@ -3,7 +3,7 @@ var css = require('./styles/modaldialog-styles')
module.exports = (title, content, ok, cancel, focusSelector, opts) => { module.exports = (title, content, ok, cancel, focusSelector, opts) => {
let agreed = true let agreed = true
let footerIsActive = true let footerIsActive = false
opts = opts || {} opts = opts || {}
var container = document.querySelector(`.modal`) var container = document.querySelector(`.modal`)
if (!container) { if (!container) {

@ -23,7 +23,7 @@ class Toaster {
}, 2000) }, 2000)
animation(this.tooltip, css.animateTop.className) animation(this.tooltip, css.animateTop.className)
} }
render (tooltipText, action, opts) { render (tooltipText, actionElement, opts) {
opts = defaultOptions(opts) opts = defaultOptions(opts)
let canShorten = true let canShorten = true
if (tooltipText instanceof Element) { if (tooltipText instanceof Element) {
@ -53,7 +53,7 @@ class Toaster {
<span class="px-2"> <span class="px-2">
${shortTooltipText} ${shortTooltipText}
${button} ${button}
${action} ${actionElement}
</span> </span>
<span style="align-self: baseline;"> <span style="align-self: baseline;">
<button class="fas fa-times btn-info mx-1 p-0" onclick=${() => { <button class="fas fa-times btn-info mx-1 p-0" onclick=${() => {

@ -11,7 +11,7 @@ export default {
}) })
verticalIcon.select('fileExplorers') verticalIcon.select('fileExplorers')
mainPanel.showContent('home') verticalIcon.showHome()
document.addEventListener('keypress', (e) => { document.addEventListener('keypress', (e) => {
if (e.shiftKey && e.ctrlKey) { if (e.shiftKey && e.ctrlKey) {

@ -1,10 +1,12 @@
'use strict' 'use strict'
var SourceMappingDecoder = require('remix-lib').SourceMappingDecoder var SourceMappingDecoder = require('remix-lib').SourceMappingDecoder
function offsetToColumnConverter () { function offsetToColumnConverter (appManager) {
this.lineBreakPositionsByContent = {} this.lineBreakPositionsByContent = {}
this.sourceMappingDecoder = new SourceMappingDecoder() this.sourceMappingDecoder = new SourceMappingDecoder()
// we don't listen anymore on compilation result for clearing the cache appManager.data.proxy.event.register('sendCompilationResult', () => {
this.clear()
})
} }
offsetToColumnConverter.prototype.offsetToLineColumn = function (rawLocation, file, sources, asts) { offsetToColumnConverter.prototype.offsetToLineColumn = function (rawLocation, file, sources, asts) {

@ -8,10 +8,9 @@ var css = csjs`
.title { .title {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
font-size: 11px; font-size: 11px;
/* height: 30px; */ /* height: 30px; */
/* width: 97%; */ width: 100%;
overflow: hidden; overflow: hidden;
word-break: break-word; word-break: break-word;
line-height: initial; line-height: initial;
@ -49,6 +48,7 @@ var css = csjs`
padding: 5px 7px; padding: 5px 7px;
} }
.nameNbuts { .nameNbuts {
display: contents;
flex-wrap: nowrap; flex-wrap: nowrap;
width: 100%; width: 100%;
} }

@ -68,17 +68,17 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
var shortAddress = helper.shortenAddress(address) var shortAddress = helper.shortenAddress(address)
var title = yo` var title = yo`
<div class="${css.title} alert alert-secondary"> <div class="${css.title} alert alert-secondary p-2">
<button class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}"><i class="fas fa-angle-right" aria-hidden="true"></i></button> <button class="btn ${css.titleExpander}" onclick="${(e) => { toggleClass(e) }}"><i class="fas fa-angle-right" aria-hidden="true"></i></button>
<div class="input-group ${css.nameNbuts}"> <div class="input-group ${css.nameNbuts}">
<div class="${css.titleText} input-group-prepend"><span class="input-group-text ${css.spanTitleText}"> ${contractName} at ${shortAddress} (${context})</span></div> <div class="${css.titleText} input-group-prepend"><span class="input-group-text ${css.spanTitleText}"> ${contractName} at ${shortAddress} (${context})</span></div>
<div class="btn-group"> <div class="btn-group">
<button class="btn p-2 btn-secondary">${copyToClipboard(() => address)}</button> <button class="btn p-1 btn-secondary">${copyToClipboard(() => address)}</button>
</div> </div>
</div> </div>
</div>` </div>`
var close = yo`<button class="${css.udappClose} p-2 btn btn-secondary" onclick=${remove}><i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i></button>` var close = yo`<button class="${css.udappClose} p-1 btn btn-secondary" onclick=${remove} title="Remove from the list"><i class="${css.closeIcon} fas fa-times" aria-hidden="true"></i></button>`
title.querySelector('.btn-group').appendChild(close) title.querySelector('.btn-group').appendChild(close)
var contractActionsWrapper = yo` var contractActionsWrapper = yo`

@ -42,7 +42,7 @@ function getCompiledContracts (browser, compiled, callback) {
} else { } else {
var ret = [] var ret = []
for (var c = 0; c < contracts.length; c++) { for (var c = 0; c < contracts.length; c++) {
ret.push(contracts[c].innerHTML) ret.push(contracts[c].value)
} }
return ret return ret
} }
@ -116,6 +116,7 @@ function clickFunction (fnFullName, expectedInput) {
} }
function verifyCallReturnValue (browser, address, checks, done) { function verifyCallReturnValue (browser, address, checks, done) {
console.log('verifyCallReturnValue address', address)
browser.execute(function (address) { browser.execute(function (address) {
var nodes = document.querySelectorAll('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]') var nodes = document.querySelectorAll('#instance' + address + ' div[class^="contractActionsContainer"] div[class^="value"]')
var ret = [] var ret = []
@ -125,6 +126,7 @@ function verifyCallReturnValue (browser, address, checks, done) {
} }
return ret return ret
}, [address], function (result) { }, [address], function (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], checks[k])
} }

@ -21,10 +21,10 @@ function initModules (browser, callback) {
.execute(function () { .execute(function () {
document.querySelector('div[id="pluginManager"]').scrollTop = document.querySelector('div[id="pluginManager"]').scrollHeight document.querySelector('div[id="pluginManager"]').scrollTop = document.querySelector('div[id="pluginManager"]').scrollHeight
}, [], function () { }, [], function () {
browser.click('#pluginManager article[title="solidity"] button') browser.click('#pluginManager article[id="remixPluginManagerListItem_solidity"] button')
.click('#pluginManager article[title="run"] button') .click('#pluginManager article[id="remixPluginManagerListItem_run"] button')
.click('#pluginManager article[title="solidityStaticAnalysis"] button') .click('#pluginManager article[id="remixPluginManagerListItem_solidityStaticAnalysis"] button')
.click('#pluginManager article[title="debugger"] button') .click('#pluginManager article[id="remixPluginManagerListItem_debugger"] button')
.click('#icon-panel div[plugin="fileExplorers"]') .click('#icon-panel div[plugin="fileExplorers"]')
.perform(() => { callback() }) .perform(() => { callback() })
}) })

@ -164,32 +164,30 @@ function checkDeployShouldSucceed (browser, address, callback) {
function testSignature (browser, callback) { function testSignature (browser, callback) {
let hash, signature let hash, signature
browser.perform((client, done) => { contractHelper.signMsg(browser, 'test message', (h, s) => {
contractHelper.signMsg(browser, 'test message', (h, s) => { hash = h
hash = h signature = s
signature = s browser.assert.ok(typeof hash.value === 'string', 'type of hash.value must be String')
browser.assert.ok(typeof hash.value === 'string', 'type of hash.value must be String') browser.assert.ok(typeof signature.value === 'string', 'type of signature.value must be String')
browser.assert.ok(typeof signature.value === 'string', 'type of signature.value must be String') contractHelper.addFile(browser, 'signMassage.sol', sources[6]['browser/signMassage.sol'], () => {
contractHelper.addFile(browser, 'signMassage.sol', sources[6]['browser/signMassage.sol'], () => { contractHelper.switchFile(browser, 'browser/signMassage.sol', () => {
contractHelper.switchFile(browser, 'browser/signMassage.sol', () => { contractHelper.selectContract(browser, 'ECVerify', () => { // deploy lib
contractHelper.selectContract(browser, 'ECVerify', () => { // deploy lib contractHelper.createContract(browser, '', () => {
contractHelper.createContract(browser, '', () => { const instanceSelector = '.instance:nth-of-type(4)'
const instanceSelector = '.instance:nth-of-type(4)' browser.waitForElementPresent(instanceSelector)
browser.waitForElementPresent(instanceSelector) .click(instanceSelector + ' > div > button')
.click(instanceSelector + ' > div > button') .getAttribute(instanceSelector, 'id', (result) => {
.getAttribute(instanceSelector, 'id', (result) => { // skip 'instance' part of e.g. 'instance0x692a70d2e424a56d2c6c27aa97d1a86395877b3a'
// skip 'instance' part of e.g. 'instance0x692a70d2e424a56d2c6c27aa97d1a86395877b3a' const address = result.value.slice('instance'.length)
const address = result.value.slice('instance'.length) browser.clickFunction('ecrecovery - call', {types: 'bytes32 hash, bytes sig', values: `"${hash.value}","${signature.value}"`}).perform(
browser.clickFunction('ecrecovery - call', {types: 'bytes32 hash, bytes sig', values: `"${hash.value}","${signature.value}"`}).perform( () => {
() => { contractHelper.verifyCallReturnValue(
contractHelper.verifyCallReturnValue( browser,
browser, address,
address, ['0: address: 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'],
['0: address: 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c'], () => { callback(null, browser) }
() => { callback(null, browser) } )
) })
})
})
}) })
}) })
}) })

@ -1,17 +1,14 @@
const https = require('https') // const https = require('https')
module.exports = function sauce (callback) { module.exports = function sauce (callback) {
return callback()
/*
const currentTest = this.client.currentTest const currentTest = this.client.currentTest
const username = this.client.options.username const username = this.client.options.username
const sessionId = this.client.capabilities['webdriver.remote.sessionid'] const sessionId = this.client.capabilities['webdriver.remote.sessionid']
const accessKey = this.client.options.accessKey const accessKey = this.client.options.accessKey
if (!this.client.launch_url.match(/saucelabs/)) {
console.log('Not saucelabs ...')
return callback()
}
if (!username || !accessKey || !sessionId) { if (!username || !accessKey || !sessionId) {
console.log(this.client) console.log(this.client)
console.log('No username, accessKey or sessionId') console.log('No username, accessKey or sessionId')
@ -59,4 +56,5 @@ module.exports = function sauce (callback) {
console.log('Error', error) console.log('Error', error)
callback() callback()
} }
*/
} }

@ -59,24 +59,19 @@ function runTests (browser, testData) {
browser.end() browser.end()
return return
} }
if (browserName === 'chrome') {
console.log('do not run remixd test for ' + browserName + ': TODO to reenable later')
browser.end()
return
}
if (browserName === 'firefox') { if (browserName === 'firefox') {
console.log('do not run remixd test for ' + browserName + ': TODO to reenable later') console.log('do not run remixd test for ' + browserName + ': TODO to reenable later')
browser.end() browser.end()
return return
} }
browser browser
.waitForElementVisible('#icon-panel', 10000) .waitForElementVisible('#icon-panel', 2000)
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.click('.websocketconn') .clickLaunchIcon('pluginManager')
.waitForElementVisible('#modal-footer-ok', 10000) .click('#pluginManager article[id="remixPluginManagerListItem_remixd"] button')
.waitForElementVisible('#modal-footer-ok', 2000)
.click('#modal-footer-ok') .click('#modal-footer-ok')
.waitForElementVisible('[data-path="localhost"]', 100000) .clickLaunchIcon('fileExplorers')
.click('[data-path="localhost"]')
.waitForElementVisible('[data-path="localhost/folder1"]') .waitForElementVisible('[data-path="localhost/folder1"]')
.click('[data-path="localhost/folder1"]') .click('[data-path="localhost/folder1"]')
.waitForElementVisible('[data-path="localhost/contract1.sol"]') .waitForElementVisible('[data-path="localhost/contract1.sol"]')
@ -142,7 +137,8 @@ function runTests (browser, testData) {
.waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '.sol"]') // check if renamed (old) file is not present .waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '.sol"]') // check if renamed (old) file is not present
.waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '_toremove.sol"]') // check if removed (old) file is not present .waitForElementNotPresent('[data-path="localhost/folder1/contract_' + browserName + '_toremove.sol"]') // check if removed (old) file is not present
.click('[data-path="localhost/folder1/renamed_contract_' + browserName + '.sol"]') .click('[data-path="localhost/folder1/renamed_contract_' + browserName + '.sol"]')
.click('.websocketconn') .clickLaunchIcon('pluginManager')
.click('#pluginManager article[id="remixPluginManagerListItem_remixd"] button')
.end() .end()
}) })
} }

Loading…
Cancel
Save