diff --git a/ci/browser_tests.sh b/ci/browser_tests.sh index da6092f4b9..e0e42fc9e1 100755 --- a/ci/browser_tests.sh +++ b/ci/browser_tests.sh @@ -30,11 +30,11 @@ while [ ! -f "$SAUCECONNECT_READYFILE" ]; do sleep .5 done -# npm run nightwatch_remote_chrome || TEST_EXITCODE=1 -# npm run nightwatch_remote_firefox || TEST_EXITCODE=1 -# npm run nightwatch_remote_safari || TEST_EXITCODE=1 +npm run nightwatch_remote_chrome || TEST_EXITCODE=1 +npm run nightwatch_remote_firefox || TEST_EXITCODE=1 +npm run nightwatch_remote_safari || 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" diff --git a/contracts/src/gmbh/company.sol b/contracts/src/gmbh/company.sol index 1b9da1a0cf..3161bf74dd 100644 --- a/contracts/src/gmbh/company.sol +++ b/contracts/src/gmbh/company.sol @@ -1,5 +1,6 @@ +import "./contract.sol"; contract Assets { - + uint[] proposals; function add(uint8 _numProposals) { proposals.length = _numProposals; } diff --git a/contracts/src/gmbh/contract.sol b/contracts/src/gmbh/contract.sol index 27ea2cb127..011523e562 100644 --- a/contracts/src/gmbh/contract.sol +++ b/contracts/src/gmbh/contract.sol @@ -1,5 +1,5 @@ contract gmbh { - + uint[] proposals; function register(uint8 _numProposals) { proposals.length = _numProposals; } diff --git a/src/app.js b/src/app.js index 68d4a36b2a..1874ab6a2e 100644 --- a/src/app.js +++ b/src/app.js @@ -3,6 +3,7 @@ var $ = require('jquery') var csjs = require('csjs-inject') var yo = require('yo-yo') +var async = require('async') var remixLib = require('remix-lib') var EventManager = remixLib.EventManager @@ -214,10 +215,17 @@ Please make a backup of your contracts and start using http://remix.ethereum.org // ----------------- Compiler ----------------- var compiler = new Compiler((url, cb) => { var provider = fileManager.fileProviderOf(url) - if (provider && provider.exists(url)) { - return provider.get(url, cb) - } - handleImports.import(url, + if (provider) { + provider.exists(url, (error, exist) => { + if (error) return cb(error) + if (exist) { + return provider.get(url, cb) + } else { + return cb('Unable to import "' + url + '": File not found') + } + }) + } else { + handleImports.import(url, (loadingMsg) => { $('#output').append($('
').append($('
').text(loadingMsg)))
       },
@@ -229,6 +237,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
           cb(error)
         }
       })
+    }
   })
   var offsetToLineColumnConverter = new OffsetToLineColumnConverter(compiler.event)
 
@@ -386,14 +395,26 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
   this._components.contextView = new ContextView({
     contextualListener: this._components.contextualListener,
     jumpTo: (position) => {
+      function jumpToLine (lineColumn) {
+        if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) {
+          editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
+        }
+      }
       if (compiler.lastCompilationResult && compiler.lastCompilationResult.data) {
         var lineColumn = offsetToLineColumnConverter.offsetToLineColumn(position, position.file, compiler.lastCompilationResult)
         var filename = compiler.getSourceName(position.file)
-        if (filename !== config.get('currentFile') && (filesProviders['browser'].exists(filename) || filesProviders['localhost'].exists(filename))) {
-          fileManager.switchFile(filename)
-        }
-        if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) {
-          editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
+        // TODO: refactor with rendererAPI.errorClick
+        if (filename !== config.get('currentFile')) {
+          var provider = fileManager.fileProviderOf(filename)
+          if (provider) {
+            provider.exists(filename, (error, exist) => {
+              if (error) return console.log(error)
+              fileManager.switchFile(filename)
+              jumpToLine(lineColumn)
+            })
+          }
+        } else {
+          jumpToLine(lineColumn)
         }
       }
     }
@@ -443,15 +464,21 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
   function loadFiles (filesSet, fileProvider) {
     if (!fileProvider) fileProvider = 'browser'
 
-    for (var f in filesSet) {
-      var name = helper.createNonClashingName(f, filesProviders[fileProvider])
-      if (helper.checkSpecialChars(name)) {
-        modalDialogCustom.alert('Special characters are not allowed')
-        return
-      }
-      filesProviders[fileProvider].set(name, filesSet[f].content)
-    }
-    fileManager.switchFile()
+    async.each(Object.keys(filesSet), (file, callback) => {
+      helper.createNonClashingName(file, filesProviders[fileProvider],
+      (error, name) => {
+        if (error) {
+          modalDialogCustom.alert('Unexpected error loading the file ' + error)
+        } else if (helper.checkSpecialChars(name)) {
+          modalDialogCustom.alert('Special characters are not allowed')
+        } else {
+          filesProviders[fileProvider].set(name, filesSet[file].content)
+        }
+        callback()
+      })
+    }, (error) => {
+      if (!error) fileManager.switchFile()
+    })
   }
 
   // Replace early callback with instant response
@@ -541,10 +568,19 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
       }
     },
     errorClick: (errFile, errLine, errCol) => {
-      if (errFile !== config.get('currentFile') && (filesProviders['browser'].exists(errFile) || filesProviders['localhost'].exists(errFile))) {
-        fileManager.switchFile(errFile)
+      if (errFile !== config.get('currentFile')) {
+        // TODO: refactor with this._components.contextView.jumpTo
+        var provider = fileManager.fileProviderOf(errFile)
+        if (provider) {
+          provider.exists(errFile, (error, exist) => {
+            if (error) return console.log(error)
+            fileManager.switchFile(errFile)
+            editor.gotoLine(errLine, errCol)
+          })
+        }
+      } else {
+        editor.gotoLine(errLine, errCol)
       }
-      editor.gotoLine(errLine, errCol)
     }
   }
   var renderer = new Renderer(rendererAPI)
diff --git a/src/app/files/basicReadOnlyExplorer.js b/src/app/files/basicReadOnlyExplorer.js
index de0f62a041..f5d3ab41dd 100644
--- a/src/app/files/basicReadOnlyExplorer.js
+++ b/src/app/files/basicReadOnlyExplorer.js
@@ -21,10 +21,10 @@ class BasicReadOnlyExplorer {
     this.files = {}
   }
 
-  exists (path) {
-    if (!this.files) return false
+  exists (path, cb) {
+    if (!this.files) return cb(null, false)
     var unprefixedPath = this.removePrefix(path)
-    return this.files[unprefixedPath] !== undefined
+    cb(null, this.files[unprefixedPath] !== undefined)
   }
 
   get (path, cb) {
diff --git a/src/app/files/browser-files.js b/src/app/files/browser-files.js
index ce645a083c..e0a25e6db8 100644
--- a/src/app/files/browser-files.js
+++ b/src/app/files/browser-files.js
@@ -8,7 +8,11 @@ function Files (storage) {
   var readonly = {}
   this.type = 'browser'
 
-  this.exists = function (path) {
+  this.exists = function (path, cb) {
+    cb(null, this._exists(path))
+  }
+
+  this._exists = function (path) {
     var unprefixedpath = this.removePrefix(path)
     // NOTE: ignore the config file
     if (path === '.remix.config') return false
@@ -75,7 +79,7 @@ function Files (storage) {
 
   this.remove = function (path) {
     var unprefixedpath = this.removePrefix(path)
-    if (!this.exists(unprefixedpath)) {
+    if (!this._exists(unprefixedpath)) {
       return false
     }
 
@@ -133,7 +137,7 @@ function Files (storage) {
   }
 
   // rename .browser-solidity.json to .remix.config
-  if (this.exists('.browser-solidity.json')) {
+  if (this._exists('.browser-solidity.json')) {
     this.rename('.browser-solidity.json', '.remix.config')
   }
 }
diff --git a/src/app/files/file-explorer.js b/src/app/files/file-explorer.js
index 6613ab420b..82ec08c670 100644
--- a/src/app/files/file-explorer.js
+++ b/src/app/files/file-explorer.js
@@ -221,16 +221,21 @@ function fileExplorer (appAPI, files) {
       } else if (helper.checkSpecialChars(label.innerText)) {
         modalDialogCustom.alert('Special characters are not allowed')
         label.innerText = textUnderEdit
-      } else if (!files.exists(newPath)) {
-        files.rename(label.dataset.path, newPath, isFolder)
       } else {
-        modalDialogCustom.alert('File already exists.')
-        label.innerText = textUnderEdit
+        files.exists(newPath, (error, exist) => {
+          if (error) return modalDialogCustom.alert('Unexpected error while renaming: ' + error)
+          if (!exist) {
+            files.rename(label.dataset.path, newPath, isFolder)
+          } else {
+            modalDialogCustom.alert('File already exists.')
+            label.innerText = textUnderEdit
+          }
+        })
       }
     }
 
     if (event.which === 13) event.preventDefault()
-    if (event.type === 'blur' || event.which === 13 && label.getAttribute('contenteditable')) {
+    if ((event.type === 'blur' || event.which === 13) && label.getAttribute('contenteditable')) {
       var isFolder = label.className.indexOf('folder') !== -1
       var save = textUnderEdit !== label.innerText
       if (save) {
diff --git a/src/app/files/shared-folder.js b/src/app/files/shared-folder.js
index 00e87011f8..f57752952d 100644
--- a/src/app/files/shared-folder.js
+++ b/src/app/files/shared-folder.js
@@ -65,13 +65,11 @@ module.exports = class SharedFolder {
   //
   // this.remixd.exists(path, (error, isValid) => {})
 
-  exists (path) {
-    // @TODO: add new remixd.exists() method
-    // we remove the this.files = null at the beggining
-    // modify the exists() (cause it is using the this.files) to use remixd
-    // yes for the exists I think you might need another remixd function
-    if (!this.files) return false
-    return this.files[path] !== undefined
+  exists (path, cb) {
+    var unprefixedpath = this.removePrefix(path)
+    this._remixd.call('sharedfolder', 'exists', {path: unprefixedpath}, (error, result) => {
+      cb(error, result)
+    })
   }
 
   get (path, cb) {
diff --git a/src/app/panels/file-panel.js b/src/app/panels/file-panel.js
index 51803bf3bf..1e3910f708 100644
--- a/src/app/panels/file-panel.js
+++ b/src/app/panels/file-panel.js
@@ -169,17 +169,20 @@ function filepanel (appAPI, filesProvider) {
           }
           var success = files.set(name, event.target.result)
           if (!success) modalDialogCustom.alert('Failed to create file ' + name)
-          else self.events.trigger('focus', [name])
+          else self.event.trigger('focus', [name])
         }
         fileReader.readAsText(file)
       }
 
       var name = files.type + '/' + file.name
-      if (!files.exists(name)) {
-        loadFile()
-      } else {
-        modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
-      }
+      files.exists(name, (error, exist) => {
+        if (error) console.log(error)
+        if (!exist) {
+          loadFile()
+        } else {
+          modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() })
+        }
+      })
     })
   }
 
@@ -221,12 +224,14 @@ function filepanel (appAPI, filesProvider) {
 
   function createNewFile () {
     modalDialogCustom.prompt(null, 'File Name', 'Untitled.sol', (input) => {
-      var newName = filesProvider['browser'].type + '/' + helper.createNonClashingName(input, filesProvider['browser'])
-      if (!filesProvider['browser'].set(newName, '')) {
-        modalDialogCustom.alert('Failed to create file ' + newName)
-      } else {
-        appAPI.switchFile(newName)
-      }
+      helper.createNonClashingName(input, filesProvider['browser'], (error, newName) => {
+        if (error) return modalDialogCustom.alert('Failed to create file ' + newName + ' ' + error)
+        if (!filesProvider['browser'].set(newName, '')) {
+          modalDialogCustom.alert('Failed to create file ' + newName)
+        } else {
+          appAPI.switchFile(filesProvider['browser'].type + '/' + newName)
+        }
+      })
     })
   }
 
diff --git a/src/app/tabs/run-tab.js b/src/app/tabs/run-tab.js
index 96147bdacc..5b0cef0291 100644
--- a/src/app/tabs/run-tab.js
+++ b/src/app/tabs/run-tab.js
@@ -161,12 +161,14 @@ function makeRecorder (events, appAPI, appEvents) {
       var fileProvider = appAPI.fileProviderOf(path)
       if (fileProvider) {
         var newFile = path + input
-        newFile = helper.createNonClashingName(newFile, fileProvider, '.json')
-        if (!fileProvider.set(newFile, txJSON)) {
-          modalDialogCustom.alert('Failed to create file ' + newFile)
-        } else {
-          appAPI.switchFile(newFile)
-        }
+        helper.createNonClashingName(newFile, fileProvider, (error, newFile) => {
+          if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error)
+          if (!fileProvider.set(newFile, txJSON)) {
+            modalDialogCustom.alert('Failed to create file ' + newFile)
+          } else {
+            appAPI.switchFile(newFile)
+          }
+        })
       }
     })
   }
diff --git a/src/app/ui/contextMenu.js b/src/app/ui/contextMenu.js
index 6491725446..15ede4ce2d 100644
--- a/src/app/ui/contextMenu.js
+++ b/src/app/ui/contextMenu.js
@@ -45,7 +45,7 @@ module.exports = (event, items) => {
   event.preventDefault()
 
   function hide (event, force) {
-    if (force || (event.target !== container)) {
+    if (container && container.parentElement && (force || (event.target !== container))) {
       container.parentElement.removeChild(container)
     }
     window.removeEventListener('click', hide)
diff --git a/src/lib/helper.js b/src/lib/helper.js
index d577643f4a..b186dc4ffb 100644
--- a/src/lib/helper.js
+++ b/src/lib/helper.js
@@ -1,3 +1,5 @@
+var async = require('async')
+
 module.exports = {
   shortenAddress: function (address, etherBalance) {
     var len = address.length
@@ -9,7 +11,7 @@ module.exports = {
     var len = data.length
     return data.slice(0, 5) + '...' + data.slice(len - 5, len)
   },
-  createNonClashingName (name, fileProvider) {
+  createNonClashingName (name, fileProvider, cb) {
     var counter = ''
     var ext = 'sol'
     var reg = /(.*)\.([^.]+)/g
@@ -18,10 +20,22 @@ module.exports = {
       name = split[1]
       ext = split[2]
     }
-    while (fileProvider.exists(name + counter + '.' + ext)) {
-      counter = (counter | 0) + 1
-    }
-    return name + counter + '.' + ext
+    var exist = true
+    async.whilst(
+      () => { return exist },
+      (callback) => {
+        fileProvider.exists(name + counter + '.' + ext, (error, currentExist) => {
+          if (error) {
+            callback(error)
+          } else {
+            exist = currentExist
+            if (exist) counter = (counter | 0) + 1
+            callback()
+          }
+        })
+      },
+      (error) => { cb(error, name + counter + '.' + ext) }
+    )
   },
   checkSpecialChars (name) {
     return name.match(/[/:*?"<>\\'|]/) != null
diff --git a/test-browser/tests/sharedFolderExplorer.js b/test-browser/tests/sharedFolderExplorer.js
index 47bd572672..9eb553dd1e 100644
--- a/test-browser/tests/sharedFolderExplorer.js
+++ b/test-browser/tests/sharedFolderExplorer.js
@@ -3,9 +3,33 @@ var contractHelper = require('../helpers/contracts')
 var init = require('../helpers/init')
 var sauce = require('./sauce')
 
+var assetsTestContract = `import "./contract.sol";
+contract Assets {
+    uint[] proposals;
+    function add(uint8 _numProposals) {
+        proposals.length = _numProposals;
+    }
+}
+`
+
+var gmbhTestContract = `
+contract gmbh {
+    uint[] proposals;
+    function register(uint8 _numProposals) {
+        proposals.length = _numProposals;
+    }
+}
+`
 var sources = [
   {
     'localhost/folder1/contract2.sol': {content: 'contract test2 { function get () returns (uint) { return 11; }}'}
+  },
+  {
+    'localhost/src/gmbh/company.sol': {content: assetsTestContract}
+  },
+  {
+    'localhost/src/gmbh/company.sol': {content: assetsTestContract},
+    'localhost/src/gmbh/contract.sol': {content: gmbhTestContract}
   }
 ]
 
@@ -92,6 +116,9 @@ function runTests (browser, testData) {
         done()
       })
     })
+    .perform(function (done) {
+      testImportFromRemixd(browser, () => { done() })
+    })
     .perform(function () {
       browser.click('[data-path="localhost"]') // collapse and expand
         .waitForElementNotVisible('[data-path="localhost/folder1"]')
@@ -107,3 +134,19 @@ function runTests (browser, testData) {
         .end()
     })
 }
+
+function testImportFromRemixd (browser, callback) {
+  browser
+    .waitForElementVisible('[data-path="localhost/src"]', 100000)
+    .click('[data-path="localhost/src"]')
+    .waitForElementVisible('[data-path="localhost/src/gmbh"]', 100000)
+    .click('[data-path="localhost/src/gmbh"]')
+    .waitForElementVisible('[data-path="localhost/src/gmbh/company.sol"]', 100000)
+    .click('[data-path="localhost/src/gmbh/company.sol"]')
+    .pause(1000)
+    .perform(() => {
+      contractHelper.verifyContract(browser, ['Assets', 'gmbh'], function () {
+        callback()
+      })
+    })
+}