diff options
author | Thomas Huang <tmashuang@users.noreply.github.com> | 2018-05-31 07:04:02 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-31 07:04:02 +0800 |
commit | dc5477be3cc62dff912a9447c702edab66200f02 (patch) | |
tree | 4c4c4293bfbc2a80812d231af9c7e22877cebfbd /test | |
parent | 5fc24930a7febd919ec6a8f6e9c14f2bac0ef2b2 (diff) | |
parent | e59f606adb65de85484b0fb258980543967ee5e1 (diff) | |
download | tangerine-wallet-browser-dc5477be3cc62dff912a9447c702edab66200f02.tar tangerine-wallet-browser-dc5477be3cc62dff912a9447c702edab66200f02.tar.gz tangerine-wallet-browser-dc5477be3cc62dff912a9447c702edab66200f02.tar.bz2 tangerine-wallet-browser-dc5477be3cc62dff912a9447c702edab66200f02.tar.lz tangerine-wallet-browser-dc5477be3cc62dff912a9447c702edab66200f02.tar.xz tangerine-wallet-browser-dc5477be3cc62dff912a9447c702edab66200f02.tar.zst tangerine-wallet-browser-dc5477be3cc62dff912a9447c702edab66200f02.zip |
Merge pull request #4408 from MetaMask/v4.7.0rc2
Version 4.7.0 - rc2
Diffstat (limited to 'test')
52 files changed, 2561 insertions, 1950 deletions
diff --git a/test/base.conf.js b/test/base.conf.js index e2e9d44ba..956dce011 100644 --- a/test/base.conf.js +++ b/test/base.conf.js @@ -6,6 +6,9 @@ module.exports = function(config) { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: process.cwd(), + // Uncomment to allow for longer timeouts + // browserNoActivityTimeout: 100000000, + browserConsoleLogOptions: { terminal: false, }, diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js new file mode 100644 index 000000000..e07d4a99e --- /dev/null +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -0,0 +1,406 @@ +const path = require('path') +const assert = require('assert') +const webdriver = require('selenium-webdriver') +const { By, Key } = webdriver +const { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, +} = require('../func') +const { + checkBrowserForConsoleErrors, + loadExtension, + verboseReportOnFailure, +} = require('./helpers') + +describe('Using MetaMask with an existing account', function () { + let extensionId + let driver + let tokenAddress + + const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const testAddress = '0xE18035BF8712672935FDB4e5e431b1a0183d2DFC' + const regularDelayMs = 1000 + const largeDelayMs = regularDelayMs * 2 + const waitingNewPageDelayMs = regularDelayMs * 10 + + this.timeout(0) + this.bail(true) + + before(async function () { + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + const extensionPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extensionPath) + extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/popup.html`) + await delay(regularDelayMs) + break + } + case 'firefox': { + const extensionPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver() + await installWebExt(driver, extensionPath) + await delay(regularDelayMs) + extensionId = await getExtensionIdFirefox(driver) + await driver.get(`moz-extension://${extensionId}/popup.html`) + await delay(regularDelayMs) + break + } + } + }) + + afterEach(async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + const errors = await checkBrowserForConsoleErrors(driver) + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + console.error(new Error(errorMessage)) + } + } + if (this.currentTest.state === 'failed') { + await verboseReportOnFailure(driver, this.currentTest) + } + }) + + after(async function () { + await driver.quit() + }) + + describe('New UI setup', async function () { + it('switches to first tab', async function () { + const [firstTab] = await driver.getAllWindowHandles() + await driver.switchTo().window(firstTab) + await delay(regularDelayMs) + }) + + it('use the local network', async function () { + const [networkSelector] = await driver.findElements(By.css('#network_component')) + await networkSelector.click() + await delay(regularDelayMs) + + const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`)) + await localhost.click() + await delay(regularDelayMs) + }) + + it('selects the new UI option', async () => { + const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]")) + await button.click() + await delay(regularDelayMs) + + // Close all other tabs + const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles() + await driver.switchTo().window(oldUi) + await driver.close() + await driver.switchTo().window(infoPage) + await driver.close() + await driver.switchTo().window(newUi) + await delay(regularDelayMs) + + const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button')) + await continueBtn.click() + await delay(regularDelayMs) + }) + }) + + describe('First time flow starting from an existing seed phrase', () => { + it('imports a seed phrase', async () => { + const [seedPhrase] = await driver.findElements(By.xpath(`//a[contains(text(), 'Import with seed phrase')]`)) + await seedPhrase.click() + await delay(regularDelayMs) + + const [seedTextArea] = await driver.findElements(By.css('textarea.import-account__secret-phrase')) + await seedTextArea.sendKeys(testSeedPhrase) + await delay(regularDelayMs) + + const [password] = await driver.findElements(By.id('password')) + await password.sendKeys('correct horse battery staple') + const [confirmPassword] = await driver.findElements(By.id('confirm-password')) + confirmPassword.sendKeys('correct horse battery staple') + + const [importButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Import')]`)) + await importButton.click() + await delay(regularDelayMs) + }) + + it('clicks through the privacy notice', async () => { + const [nextScreen] = await driver.findElements(By.css('.tou button')) + await nextScreen.click() + await delay(regularDelayMs) + + const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() + assert.equal(canClickThrough, false, 'disabled continue button') + const element = await driver.findElement(By.linkText('Attributions')) + await driver.executeScript('arguments[0].scrollIntoView(true)', element) + await delay(regularDelayMs) + + const [acceptTos] = await driver.findElements(By.css('.tou button')) + await acceptTos.click() + await delay(regularDelayMs) + }) + }) + + describe('Show account information', () => { + it('shows the correct account address', async () => { + await driver.findElement(By.css('.wallet-view__details-button')).click() + await driver.findElement(By.css('.qr-wrapper')).isDisplayed() + await delay(regularDelayMs) + + const [address] = await driver.findElements(By.css('input.qr-ellip-address')) + assert.equal(await address.getAttribute('value'), testAddress) + + await driver.executeScript("document.querySelector('.account-modal-close').click()") + await delay(largeDelayMs) + }) + + it('shows a QR code for the account', async () => { + await driver.findElement(By.css('.wallet-view__details-button')).click() + await driver.findElement(By.css('.qr-wrapper')).isDisplayed() + await delay(regularDelayMs) + + await driver.executeScript("document.querySelector('.account-modal-close').click()") + await delay(regularDelayMs) + }) + }) + + describe('Log out and log back in', () => { + it('logs out of the account', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) + assert.equal(await logoutButton.getText(), 'Log out') + await logoutButton.click() + await delay(regularDelayMs) + }) + + it('accepts the account password after lock', async () => { + await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') + await driver.findElement(By.id('password')).sendKeys(Key.ENTER) + await delay(largeDelayMs) + }) + }) + + describe('Add an account', () => { + it('choose Create Account from the account menu', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`)) + await createAccount.click() + await delay(regularDelayMs) + }) + + it('set account name', async () => { + const [accountName] = await driver.findElements(By.css('.new-account-create-form input')) + await accountName.sendKeys('2nd account') + await delay(regularDelayMs) + + const [createButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`)) + await createButton.click() + await delay(regularDelayMs) + }) + + it('should show the correct account name', async () => { + const [accountName] = await driver.findElements(By.css('.account-name')) + assert.equal(await accountName.getText(), '2nd account') + await delay(regularDelayMs) + }) + }) + + describe('Switch back to original account', () => { + it('chooses the original account from the account menu', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [originalAccountMenuItem] = await driver.findElements(By.css('.account-menu__name')) + await originalAccountMenuItem.click() + await delay(regularDelayMs) + }) + }) + + describe('Send ETH from inside MetaMask', () => { + it('starts to send a transaction', async function () { + const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`)) + await sendButton.click() + await delay(regularDelayMs) + + const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]')) + const [inputAmount] = await driver.findElements(By.css('.currency-display__input')) + await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await inputAmount.sendKeys('1') + + // Set the gas limit + const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button')) + await configureGas.click() + await delay(regularDelayMs) + + const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`)) + await save.click() + await delay(regularDelayMs) + + // Continue to next screen + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('confirms the transaction', async function () { + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + }) + + it('finds the transaction in the transactions list', async function () { + const transactions = await driver.findElements(By.css('.tx-list-item')) + assert.equal(transactions.length, 1) + + const txValues = await driver.findElements(By.css('.tx-list-value')) + assert.equal(txValues.length, 1) + assert.equal(await txValues[0].getText(), '1 ETH') + }) + }) + + describe('Send ETH from Faucet', () => { + it('starts a send transaction inside Faucet', async () => { + await driver.executeScript('window.open("https://faucet.metamask.io")') + await delay(waitingNewPageDelayMs) + + const [extension, faucet] = await driver.getAllWindowHandles() + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + + const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`)) + await send1eth.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + await driver.close() + await delay(regularDelayMs) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + }) + + describe('Add existing token using search', () => { + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('picks an existing token', async () => { + const [tokenSearch] = await driver.findElements(By.css('input.add-token__input')) + await tokenSearch.sendKeys('BAT') + await delay(regularDelayMs) + + const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]")) + await token.click() + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(largeDelayMs) + }) + + it('renders the balance for the new token', async () => { + const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '0BAT') + await delay(regularDelayMs) + }) + }) + + describe('Add a custom token from TokenFactory', () => { + it('creates a new token', async () => { + await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")') + await delay(waitingNewPageDelayMs) + + const [extension, tokenFactory] = await driver.getAllWindowHandles() + await driver.switchTo().window(tokenFactory) + const [ + totalSupply, + tokenName, + tokenDecimal, + tokenSymbol, + ] = await driver.findElements(By.css('input')) + + await totalSupply.sendKeys('100') + await tokenName.sendKeys('Test') + await tokenDecimal.sendKeys('0') + await tokenSymbol.sendKeys('TST') + + const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`)) + await createToken.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(),'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(tokenFactory) + await delay(regularDelayMs) + const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)')) + tokenAddress = await tokenContactAddress.getText() + await driver.close() + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('picks the new Test token', async () => { + const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]")) + await addCustomToken.click() + await delay(regularDelayMs) + + const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input')) + await newTokenAddress.sendKeys(tokenAddress) + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(regularDelayMs) + }) + + it('renders the balance for the new token', async () => { + const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '100TST') + await delay(regularDelayMs) + }) + }) +}) diff --git a/test/e2e/beta/helpers.js b/test/e2e/beta/helpers.js new file mode 100644 index 000000000..8307fdc50 --- /dev/null +++ b/test/e2e/beta/helpers.js @@ -0,0 +1,55 @@ +const fs = require('fs') +const mkdirp = require('mkdirp') +const pify = require('pify') + +module.exports = { + checkBrowserForConsoleErrors, + loadExtension, + verboseReportOnFailure, +} + +async function loadExtension (driver, extensionId) { + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + await driver.get(`chrome-extension://${extensionId}/home.html`) + break + } + case 'firefox': { + await driver.get(`moz-extension://${extensionId}/home.html`) + break + } + } +} + +async function checkBrowserForConsoleErrors (driver) { + const ignoredLogTypes = ['WARNING'] + const ignoredErrorMessages = [ + // React throws error warnings on "dataset", but still sets the data-* properties correctly + 'Warning: Unknown prop `dataset` on ', + // Third-party Favicon 404s show up as errors + 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', + // React Development build - known issue blocked by test build sys + 'Warning: It looks like you\'re using a minified copy of the development build of React.', + // Redux Development build - known issue blocked by test build sys + 'This means that you are running a slower development build of Redux.', + ] + const browserLogs = await driver.manage().logs().get('browser') + const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) + const errorObjects = errorEntries.map(entry => entry.toJSON()) + return errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message))) +} + +async function verboseReportOnFailure (driver, test) { + let artifactDir + if (process.env.SELENIUM_BROWSER === 'chrome') { + artifactDir = `./test-artifacts/chrome/${test.title}` + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + artifactDir = `./test-artifacts/firefox/${test.title}` + } + const filepathBase = `${artifactDir}/test-failure` + await pify(mkdirp)(artifactDir) + const screenshot = await driver.takeScreenshot() + await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) + const htmlSource = await driver.getPageSource() + await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) +} diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js new file mode 100644 index 000000000..00863e3b3 --- /dev/null +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -0,0 +1,491 @@ +const path = require('path') +const assert = require('assert') +const webdriver = require('selenium-webdriver') +const { By, Key } = webdriver +const { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, +} = require('../func') +const { + checkBrowserForConsoleErrors, + loadExtension, + verboseReportOnFailure, +} = require('./helpers') + +describe('MetaMask', function () { + let extensionId + let driver + let tokenAddress + + const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const tinyDelayMs = 500 + const regularDelayMs = tinyDelayMs * 2 + const largeDelayMs = regularDelayMs * 2 + const waitingNewPageDelayMs = regularDelayMs * 10 + + this.timeout(0) + this.bail(true) + + before(async function () { + switch (process.env.SELENIUM_BROWSER) { + case 'chrome': { + const extPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extPath) + extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/popup.html`) + break + } + case 'firefox': { + const extPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver() + await installWebExt(driver, extPath) + await delay(700) + extensionId = await getExtensionIdFirefox(driver) + await driver.get(`moz-extension://${extensionId}/popup.html`) + } + } + }) + + afterEach(async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + const errors = await checkBrowserForConsoleErrors(driver) + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + console.error(new Error(errorMessage)) + } + } + if (this.currentTest.state === 'failed') { + await verboseReportOnFailure(this.currentTest) + } + }) + + after(async function () { + await driver.quit() + }) + + describe('New UI setup', async function () { + it('switches to first tab', async function () { + const [firstTab] = await driver.getAllWindowHandles() + await driver.switchTo().window(firstTab) + await delay(regularDelayMs) + }) + + it('use the local network', async function () { + const [networkSelector] = await driver.findElements(By.css('#network_component')) + await networkSelector.click() + await delay(regularDelayMs) + + const [localhost] = await driver.findElements(By.xpath(`//li[contains(text(), 'Localhost')]`)) + await localhost.click() + await delay(regularDelayMs) + }) + + it('selects the new UI option', async () => { + const button = await driver.findElement(By.xpath("//p[contains(text(), 'Try Beta Version')]")) + await button.click() + await delay(regularDelayMs) + + // Close all other tabs + const [oldUi, infoPage, newUi] = await driver.getAllWindowHandles() + await driver.switchTo().window(oldUi) + await driver.close() + await driver.switchTo().window(infoPage) + await driver.close() + await driver.switchTo().window(newUi) + await delay(regularDelayMs) + + const [continueBtn] = await driver.findElements(By.css('.welcome-screen__button')) + await continueBtn.click() + await delay(regularDelayMs) + }) + }) + + describe('Going through the first time flow', () => { + it('accepts a secure password', async () => { + const [passwordBox] = await driver.findElements(By.css('.create-password #create-password')) + const [passwordBoxConfirm] = await driver.findElements(By.css('.create-password #confirm-password')) + const [button] = await driver.findElements(By.css('.create-password button')) + + await passwordBox.sendKeys('correct horse battery staple') + await passwordBoxConfirm.sendKeys('correct horse battery staple') + await button.click() + await delay(regularDelayMs) + }) + + it('clicks through the unique image screen', async () => { + const [nextScreen] = await driver.findElements(By.css('.unique-image button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('clicks through the privacy notice', async () => { + const [nextScreen] = await driver.findElements(By.css('.tou button')) + await nextScreen.click() + await delay(regularDelayMs) + + const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() + assert.equal(canClickThrough, false, 'disabled continue button') + const [bottomOfTos] = await driver.findElements(By.linkText('Attributions')) + await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) + await delay(regularDelayMs) + + const [acceptTos] = await driver.findElements(By.css('.tou button')) + await acceptTos.click() + await delay(regularDelayMs) + }) + + let seedPhrase + + it('reveals the seed phrase', async () => { + const [revealSeedPhrase] = await driver.findElements(By.css('.backup-phrase__secret-blocker')) + await revealSeedPhrase.click() + await delay(regularDelayMs) + + seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() + assert.equal(seedPhrase.split(' ').length, 12) + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.css('.backup-phrase button')) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('can retype the seed phrase', async () => { + const words = seedPhrase.split(' ') + + const [word0] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[0]}')]`)) + await word0.click() + await delay(tinyDelayMs) + + const [word1] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[1]}')]`)) + await word1.click() + await delay(tinyDelayMs) + + const [word2] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[2]}')]`)) + await word2.click() + await delay(tinyDelayMs) + + const [word3] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[3]}')]`)) + await word3.click() + await delay(tinyDelayMs) + + const [word4] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[4]}')]`)) + await word4.click() + await delay(tinyDelayMs) + + const [word5] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[5]}')]`)) + await word5.click() + await delay(tinyDelayMs) + + const [word6] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[6]}')]`)) + await word6.click() + await delay(tinyDelayMs) + + const [word7] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[7]}')]`)) + await word7.click() + await delay(tinyDelayMs) + + const [word8] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[8]}')]`)) + await word8.click() + await delay(tinyDelayMs) + + const [word9] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[9]}')]`)) + await word9.click() + await delay(tinyDelayMs) + + const [word10] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[10]}')]`)) + await word10.click() + await delay(tinyDelayMs) + + const [word11] = await driver.findElements(By.xpath(`//button[contains(text(), '${words[11]}')]`)) + await word11.click() + await delay(tinyDelayMs) + + const [confirm] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirm.click() + await delay(regularDelayMs) + }) + + it('clicks through the deposit modal', async () => { + const [closeModal] = await driver.findElements(By.css('.page-container__header-close')) + await closeModal.click() + await delay(regularDelayMs) + }) + }) + + describe('Show account information', () => { + it('shows the QR code for the account', async () => { + await driver.findElement(By.css('.wallet-view__details-button')).click() + await driver.findElement(By.css('.qr-wrapper')).isDisplayed() + await delay(regularDelayMs) + + await driver.executeScript("document.querySelector('.account-modal-close').click()") + await delay(regularDelayMs * 4) + }) + }) + + describe('Log out an log back in', () => { + it('logs out of the account', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) + assert.equal(await logoutButton.getText(), 'Log out') + await logoutButton.click() + await delay(regularDelayMs) + }) + + it('accepts the account password after lock', async () => { + await driver.findElement(By.id('password')).sendKeys('correct horse battery staple') + await driver.findElement(By.id('password')).sendKeys(Key.ENTER) + await delay(regularDelayMs * 4) + }) + }) + + describe('Add account', () => { + it('choose Create Account from the account menu', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [createAccount] = await driver.findElements(By.xpath(`//div[contains(text(), 'Create Account')]`)) + await createAccount.click() + await delay(regularDelayMs) + }) + + it('set account name', async () => { + const [accountName] = await driver.findElements(By.css('.new-account-create-form input')) + await accountName.sendKeys('2nd account') + await delay(regularDelayMs) + + const [create] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create')]`)) + await create.click() + await delay(regularDelayMs) + }) + + it('should correct account name', async () => { + const [accountName] = await driver.findElements(By.css('.account-name')) + assert.equal(await accountName.getText(), '2nd account') + await delay(regularDelayMs) + }) + }) + + describe('Import seed phrase', () => { + it('logs out of the vault', async () => { + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const [logoutButton] = await driver.findElements(By.css('.account-menu__logout-button')) + assert.equal(await logoutButton.getText(), 'Log out') + await logoutButton.click() + await delay(regularDelayMs) + }) + + it('imports seed phrase', async () => { + const [restoreSeedLink] = await driver.findElements(By.css('.unlock-page__link--import')) + assert.equal(await restoreSeedLink.getText(), 'Import using account seed phrase') + await restoreSeedLink.click() + await delay(regularDelayMs) + + const [seedTextArea] = await driver.findElements(By.css('textarea')) + await seedTextArea.sendKeys(testSeedPhrase) + await delay(regularDelayMs) + + await driver.findElement(By.id('password-box')).sendKeys('correct horse battery staple') + await driver.findElement(By.id('password-box-confirm')).sendKeys('correct horse battery staple') + await driver.findElement(By.css('button:nth-child(2)')).click() + await delay(regularDelayMs) + }) + + it('balance renders', async () => { + const balance = await driver.findElement(By.css('.balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '100.000 ETH') + await delay(regularDelayMs) + }) + }) + + describe('Send ETH from inside MetaMask', () => { + it('starts to send a transaction', async function () { + const [sendButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Send')]`)) + await sendButton.click() + await delay(regularDelayMs) + + const [inputAddress] = await driver.findElements(By.css('input[placeholder="Recipient Address"]')) + const [inputAmount] = await driver.findElements(By.css('.currency-display__input')) + await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await inputAmount.sendKeys('1') + + // Set the gas limit + const [configureGas] = await driver.findElements(By.css('.send-v2__gas-fee-display button')) + await configureGas.click() + await delay(regularDelayMs) + + const [save] = await driver.findElements(By.xpath(`//button[contains(text(), 'Save')]`)) + await save.click() + await delay(regularDelayMs) + + // Continue to next screen + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + }) + + it('confirms the transaction', async function () { + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + }) + + it('finds the transaction in the transactions list', async function () { + const transactions = await driver.findElements(By.css('.tx-list-item')) + assert.equal(transactions.length, 1) + + const txValues = await driver.findElements(By.css('.tx-list-value')) + assert.equal(txValues.length, 1) + assert.equal(await txValues[0].getText(), '1 ETH') + }) + }) + + describe('Send ETH from Faucet', () => { + it('starts a send transaction inside Faucet', async () => { + await driver.executeScript('window.open("https://faucet.metamask.io")') + await delay(waitingNewPageDelayMs) + + const [extension, faucet] = await driver.getAllWindowHandles() + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + + const [send1eth] = await driver.findElements(By.xpath(`//button[contains(text(), '10 ether')]`)) + await send1eth.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(faucet) + await delay(regularDelayMs) + await driver.close() + await delay(regularDelayMs) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + }) + + describe('Add existing token using search', () => { + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('can pick a token from the existing options', async () => { + const [tokenSearch] = await driver.findElements(By.css('input.add-token__input')) + await tokenSearch.sendKeys('BAT') + await delay(regularDelayMs) + + const [token] = await driver.findElements(By.xpath("//div[contains(text(), 'BAT')]")) + await token.click() + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(largeDelayMs) + }) + + it('renders the balance for the chosen token', async () => { + const balance = await driver.findElement(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '0BAT') + await delay(regularDelayMs) + }) + }) + + describe('Add a custom token from TokenFactory', () => { + it('creates a new token', async () => { + await driver.executeScript('window.open("https://tokenfactory.surge.sh/#/factory")') + await delay(waitingNewPageDelayMs) + + const [extension, tokenFactory] = await driver.getAllWindowHandles() + await driver.switchTo().window(tokenFactory) + const [ + totalSupply, + tokenName, + tokenDecimal, + tokenSymbol, + ] = await driver.findElements(By.css('input')) + + await totalSupply.sendKeys('100') + await tokenName.sendKeys('Test') + await tokenDecimal.sendKeys('0') + await tokenSymbol.sendKeys('TST') + + const [createToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Create Token')]`)) + await createToken.click() + await delay(regularDelayMs) + + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + + const [confirmButton] = await driver.findElements(By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + + await driver.switchTo().window(tokenFactory) + await delay(regularDelayMs) + const tokenContactAddress = await driver.findElement(By.css('div > div > div:nth-child(2) > span:nth-child(3)')) + tokenAddress = await tokenContactAddress.getText() + await driver.close() + await driver.switchTo().window(extension) + await loadExtension(driver, extensionId) + await delay(regularDelayMs) + }) + + it('clicks on the Add Token button', async () => { + const [addToken] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Token')]`)) + await addToken.click() + await delay(regularDelayMs) + }) + + it('picks the newly created Test token', async () => { + const [addCustomToken] = await driver.findElements(By.xpath("//div[contains(text(), 'Custom Token')]")) + await addCustomToken.click() + await delay(regularDelayMs) + + const [newTokenAddress] = await driver.findElements(By.css('.add-token__add-custom-form input')) + await newTokenAddress.sendKeys(tokenAddress) + await delay(regularDelayMs) + + const [nextScreen] = await driver.findElements(By.xpath(`//button[contains(text(), 'Next')]`)) + await nextScreen.click() + await delay(regularDelayMs) + + const [addTokens] = await driver.findElements(By.xpath(`//button[contains(text(), 'Add Tokens')]`)) + await addTokens.click() + await delay(regularDelayMs) + }) + + it('renders the balance for the new token', async () => { + const [balance] = await driver.findElements(By.css('.tx-view .balance-display .token-amount')) + const tokenAmount = await balance.getText() + assert.equal(tokenAmount, '100TST') + await delay(regularDelayMs) + }) + }) +}) diff --git a/test/e2e/beta/run-all.sh b/test/e2e/beta/run-all.sh new file mode 100755 index 000000000..5916d5614 --- /dev/null +++ b/test/e2e/beta/run-all.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e +set -u +set -o pipefail + +export PATH="$PATH:./node_modules/.bin" + +shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec' +shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec' diff --git a/test/e2e/func.js b/test/e2e/func.js index 733225565..9f06e7f37 100644 --- a/test/e2e/func.js +++ b/test/e2e/func.js @@ -1,18 +1,63 @@ require('chromedriver') +require('geckodriver') +const fs = require('fs') +const os = require('os') +const path = require('path') const webdriver = require('selenium-webdriver') +const Command = require('selenium-webdriver/lib/command').Command +const By = webdriver.By -exports.delay = function delay (time) { - return new Promise(resolve => setTimeout(resolve, time)) +module.exports = { + delay, + buildChromeWebDriver, + buildFirefoxWebdriver, + installWebExt, + getExtensionIdChrome, + getExtensionIdFirefox, } +function delay (time) { + return new Promise(resolve => setTimeout(resolve, time)) +} -exports.buildWebDriver = function buildWebDriver (extPath) { +function buildChromeWebDriver (extPath) { + const tmpProfile = path.join(os.tmpdir(), fs.mkdtempSync('mm-chrome-profile')); return new webdriver.Builder() .withCapabilities({ chromeOptions: { - args: [`load-extension=${extPath}`], + args: [ + `load-extension=${extPath}`, + `user-data-dir=${tmpProfile}`, + ], + binary: process.env.SELENIUM_CHROME_BINARY, }, }) - .forBrowser('chrome') .build() } + +function buildFirefoxWebdriver () { + return new webdriver.Builder().build() +} + +async function getExtensionIdChrome (driver) { + await driver.get('chrome://extensions') + const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")') + return extensionId +} + +async function getExtensionIdFirefox (driver) { + await driver.get('about:debugging#addons') + const extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText() + return extensionId +} + +async function installWebExt (driver, extension) { + const cmd = await new Command('moz-install-web-ext') + .setParameter('path', path.resolve(extension)) + .setParameter('temporary', true) + + await driver.getExecutor() + .defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install') + + return await driver.schedule(cmd, 'installWebExt(' + extension + ')') +} diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index e0ff2a57e..a08a34d96 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -4,26 +4,44 @@ const path = require('path') const assert = require('assert') const pify = require('pify') const webdriver = require('selenium-webdriver') -const By = webdriver.By -const { delay, buildWebDriver } = require('./func') +const { By, Key } = webdriver +const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func') describe('Metamask popup page', function () { - let driver - this.seedPhase - this.accountAddress + let driver, accountAddress, tokenAddress, extensionId + this.timeout(0) before(async function () { - const extPath = path.resolve('dist/chrome') - driver = buildWebDriver(extPath) - await driver.get('chrome://extensions-frame') - const elems = await driver.findElements(By.css('.extension-list-item-wrapper')) - const extensionId = await elems[1].getAttribute('id') - await driver.get(`chrome-extension://${extensionId}/popup.html`) - await delay(500) + if (process.env.SELENIUM_BROWSER === 'chrome') { + const extPath = path.resolve('dist/chrome') + driver = buildChromeWebDriver(extPath) + extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/popup.html`) + + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + const extPath = path.resolve('dist/firefox') + driver = buildFirefoxWebdriver() + await installWebExt(driver, extPath) + await delay(700) + extensionId = await getExtensionIdFirefox(driver) + await driver.get(`moz-extension://${extensionId}/popup.html`) + } }) afterEach(async function () { + // logs command not supported in firefox + // https://github.com/SeleniumHQ/selenium/issues/2910 + if (process.env.SELENIUM_BROWSER === 'chrome') { + // check for console errors + const errors = await checkBrowserForConsoleErrors() + if (errors.length) { + const errorReports = errors.map(err => err.message) + const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}` + this.test.error(new Error(errorMessage)) + } + } + // gather extra data if test failed if (this.currentTest.state === 'failed') { await verboseReportOnFailure(this.currentTest) } @@ -33,105 +51,301 @@ describe('Metamask popup page', function () { await driver.quit() }) - describe('#onboarding', () => { - it('should open Metamask.io', async function () { - const tabs = await driver.getAllWindowHandles() - await driver.switchTo().window(tabs[0]) + describe('Setup', function () { + + it('switches to Chrome extensions list', async function () { await delay(300) - await setProviderType('localhost') + const windowHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(windowHandles[0]) + }) + + it('sets provider type to localhost', async function () { await delay(300) + await setProviderType('localhost') }) - it('should match title', async () => { + }) + + describe('Account Creation', () => { + + it('matches MetaMask title', async () => { const title = await driver.getTitle() assert.equal(title, 'MetaMask', 'title matches MetaMask') }) - it('should show privacy notice', async () => { + it('shows privacy notice', async () => { + await delay(300) const privacy = await driver.findElement(By.css('.terms-header')).getText() assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice') - driver.findElement(By.css('button')).click() + await driver.findElement(By.css('button')).click() await delay(300) }) - it('should show terms of use', async () => { - await delay(300) + it('show terms of use', async () => { const terms = await driver.findElement(By.css('.terms-header')).getText() assert.equal(terms, 'TERMS OF USE', 'shows terms of use') - await delay(300) + delay(300) }) - it('should be unable to continue without scolling throught the terms of use', async () => { + it('checks if the TOU button is disabled', async () => { const button = await driver.findElement(By.css('button')).isEnabled() assert.equal(button, false, 'disabled continue button') - const element = driver.findElement(By.linkText( - 'Attributions' - )) + const element = await driver.findElement(By.linkText('Attributions')) await driver.executeScript('arguments[0].scrollIntoView(true)', element) - await delay(300) + await delay(700) }) - it('should be able to continue when scrolled to the bottom of terms of use', async () => { - const button = await driver.findElement(By.css('button')) - const buttonEnabled = await button.isEnabled() - await delay(500) - assert.equal(buttonEnabled, true, 'enabled continue button') + it('allows the button to be clicked when scrolled to the bottom of TOU', async () => { + const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button')) await button.click() - await delay(300) }) - it('should accept password with length of eight', async () => { + it('accepts password with length of eight', async () => { const passwordBox = await driver.findElement(By.id('password-box')) const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm')) - const button = driver.findElement(By.css('button')) + const button = await driver.findElements(By.css('button')) - passwordBox.sendKeys('123456789') - passwordBoxConfirm.sendKeys('123456789') + await passwordBox.sendKeys('123456789') + await passwordBoxConfirm.sendKeys('123456789') + await button[0].click() await delay(500) - await button.click() }) - it('should show value was created and seed phrase', async () => { - await delay(700) - this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText() - const continueAfterSeedPhrase = await driver.findElement(By.css('button')) + it('shows value was created and seed phrase', async () => { + await delay(300) + const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText() + assert.equal(seedPhrase.split(' ').length, 12) + const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)')) + assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`) await continueAfterSeedPhrase.click() await delay(300) }) - it('should show lock account', async () => { + it('adds a second account', async function () { + await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div')).click() + await delay(300) + await driver.findElement(By.css('#app-content > div > div.full-width > div > div:nth-child(2) > span > div > div > span > div > li:nth-child(3) > span')).click() + }) + + it('shows account address', async function () { + await delay(300) + accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText() + }) + + it('logs out of the vault', async () => { await driver.findElement(By.css('.sandwich-expando')).click() await delay(500) - await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click() + const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) + assert.equal(await logoutButton.getText(), 'Log Out') + await logoutButton.click() }) - it('should accept account password after lock', async () => { + it('accepts account password after lock', async () => { await delay(500) await driver.findElement(By.id('password-box')).sendKeys('123456789') - await driver.findElement(By.css('button')).click() + await driver.findElement(By.id('password-box')).sendKeys(Key.ENTER) await delay(500) }) - it('should show QR code option', async () => { + it('shows QR code option', async () => { await delay(300) await driver.findElement(By.css('.fa-ellipsis-h')).click() await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() await delay(300) }) - it('should show the account address', async () => { - this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText() + it('checks QR code address is the same as account details address', async () => { + const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText() + assert.equal(accountAddress.toLowerCase(), QRaccountAddress) await driver.findElement(By.css('.fa-arrow-left')).click() await delay(500) }) }) - async function setProviderType(type) { + describe('Import Ganache seed phrase', function () { + + it('logs out', async function () { + await driver.findElement(By.css('.sandwich-expando')).click() + await delay(200) + const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')) + assert.equal(await logOut.getText(), 'Log Out') + await logOut.click() + await delay(300) + }) + + it('restores from seed phrase', async function () { + const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p')) + assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase') + await restoreSeedLink.click() + await delay(100) + }) + + it('adds seed phrase', async function () { + const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent' + const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea')) + await seedTextArea.sendKeys(testSeedPhrase) + + await driver.findElement(By.id('password-box')).sendKeys('123456789') + await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789') + await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click() + await delay(500) + }) + + it('balance renders', async function () { + await delay(200) + const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)')) + assert.equal(await balance.getText(), '100.000') + await delay(200) + }) + + it('sends transaction', async function () { + const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)')) + assert.equal(await sendButton.getText(), 'SEND') + await sendButton.click() + await delay(200) + }) + + it('adds recipient address and amount', async function () { + const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText() + assert.equal(sendTranscationScreen, 'SEND TRANSACTION') + const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input')) + const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input')) + await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970') + await inputAmmount.sendKeys('10') + await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click() + await delay(300) + }) + + it('confirms transaction', async function () { + await delay(300) + await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click() + await delay(500) + }) + + it('finds the transaction in the transactions list', async function () { + const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)')) + assert.equal(await tranasactionAmount.getText(), '10.0') + }) + }) + + describe('Token Factory', function () { + + it('navigates to token factory', async function () { + await driver.get('http://tokenfactory.surge.sh/') + }) + + it('navigates to create token contract link', async function () { + const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a')) + await createToken.click() + }) + + it('adds input for token', async function () { + const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input')) + const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input')) + const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input')) + const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input')) + const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button')) + + await totalSupply.sendKeys('100') + await tokenName.sendKeys('Test') + await tokenDecimal.sendKeys('0') + await tokenSymbol.sendKeys('TST') + await createToken.click() + await delay(1000) + }) + + // There is an issue with blank confirmation window in Firefox, but the button is still there and the driver is able to clicked (?.?) + it('confirms transaction in MetaMask popup', async function () { + const windowHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(windowHandles[windowHandles.length - 1]) + const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')) + await metamaskSubmit.click() + await delay(1000) + }) + + it('switches back to Token Factory to grab the token contract address', async function () { + const windowHandles = await driver.getAllWindowHandles() + await driver.switchTo().window(windowHandles[0]) + const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)')) + tokenAddress = await tokenContactAddress.getText() + await delay(500) + }) + + it('navigates back to MetaMask popup in the tab', async function () { + if (process.env.SELENIUM_BROWSER === 'chrome') { + await driver.get(`chrome-extension://${extensionId}/popup.html`) + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + await driver.get(`moz-extension://${extensionId}/popup.html`) + } + await delay(700) + }) + }) + + describe('Add Token', function () { + + it('switches to the add token screen', async function () { + const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer')) + assert.equal(await tokensTab.getText(), 'TOKENS') + await tokensTab.click() + await delay(300) + }) + + it('navigates to the add token screen', async function () { + const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button')) + assert.equal(await addTokenButton.getText(), 'ADD TOKEN') + await addTokenButton.click() + }) + + it('checks add token screen rendered', async function () { + const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2')) + assert.equal(await addTokenScreen.getText(), 'ADD TOKEN') + }) + + it('adds token parameters', async function () { + const tokenContractAddress = await driver.findElement(By.css('#token-address')) + await tokenContractAddress.sendKeys(tokenAddress) + await delay(300) + await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click() + await delay(200) + }) + + it('checks the token balance', async function () { + const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3')) + assert.equal(await tokenBalance.getText(), '100 TST') + }) + }) + + async function setProviderType (type) { await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) } - async function verboseReportOnFailure(test) { - const artifactDir = `./test-artifacts/${test.title}` + async function checkBrowserForConsoleErrors() { + const ignoredLogTypes = ['WARNING'] + const ignoredErrorMessages = [ + // React throws error warnings on "dataset", but still sets the data-* properties correctly + 'Warning: Unknown prop `dataset` on ', + // Third-party Favicon 404s show up as errors + 'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)', + // React Development build - known issue blocked by test build sys + 'Warning: It looks like you\'re using a minified copy of the development build of React.', + // Redux Development build - known issue blocked by test build sys + 'This means that you are running a slower development build of Redux.', + ] + const browserLogs = await driver.manage().logs().get('browser') + const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString())) + const errorObjects = errorEntries.map(entry => entry.toJSON()) + // ignore all errors that contain a message in `ignoredErrorMessages` + const matchedErrorObjects = errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message))) + return matchedErrorObjects + } + + async function verboseReportOnFailure (test) { + let artifactDir + if (process.env.SELENIUM_BROWSER === 'chrome') { + artifactDir = `./test-artifacts/chrome/${test.title}` + } else if (process.env.SELENIUM_BROWSER === 'firefox') { + artifactDir = `./test-artifacts/firefox/${test.title}` + } const filepathBase = `${artifactDir}/test-failure` await pify(mkdirp)(artifactDir) // capture screenshot diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js index 1840bdd39..e51c854d2 100644 --- a/test/integration/lib/add-token.js +++ b/test/integration/lib/add-token.js @@ -22,6 +22,11 @@ async function runAddTokenFlowTest (assert, done) { selectState.val('add token') reactTriggerChange(selectState[0]) + // Used to set values on TextField input component + const nativeInputValueSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, 'value' + ).set + // Check that no tokens have been added assert.ok($('.token-list-item').length === 0, 'no tokens added') @@ -31,14 +36,14 @@ async function runAddTokenFlowTest (assert, done) { addTokenButton[0].click() // Verify Add Token screen - let addTokenWrapper = await queryAsync($, '.add-token__wrapper') + let addTokenWrapper = await queryAsync($, '.page-container') assert.ok(addTokenWrapper[0], 'add token wrapper renders') - let addTokenTitle = await queryAsync($, '.add-token__header__title') + let addTokenTitle = await queryAsync($, '.page-container__title') assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') // Cancel Add Token - const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.add-token__cancel-button') + const cancelAddTokenButton = await queryAsync($, 'button.btn-secondary--lg.page-container__footer-button') assert.ok(cancelAddTokenButton[0], 'cancel add token button present') cancelAddTokenButton.click() @@ -50,20 +55,22 @@ async function runAddTokenFlowTest (assert, done) { addTokenButton[0].click() // Verify Add Token Screen - addTokenWrapper = await queryAsync($, '.add-token__wrapper') - addTokenTitle = await queryAsync($, '.add-token__header__title') + addTokenWrapper = await queryAsync($, '.page-container') + addTokenTitle = await queryAsync($, '.page-container__title') assert.ok(addTokenWrapper[0], 'add token wrapper renders') assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct') // Search for token - const searchInput = await queryAsync($, 'input.add-token__input') - searchInput.val('a') - reactTriggerChange(searchInput[0]) + const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0] + searchInput.focus() + await timeout(1000) + nativeInputValueSetter.call(searchInput, 'a') + searchInput.dispatchEvent(new Event('input', { bubbles: true})) // Click token to add - const tokenWrapper = await queryAsync($, 'div.add-token__token-wrapper') + const tokenWrapper = await queryAsync($, 'div.token-list__token') assert.ok(tokenWrapper[0], 'token found') - const tokenImageProp = tokenWrapper.find('.add-token__token-icon').css('background-image') + const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image') const tokenImageUrl = tokenImageProp.slice(5, -2) tokenWrapper[0].click() @@ -73,11 +80,8 @@ async function runAddTokenFlowTest (assert, done) { nextButton[0].click() // Confirm Add token - assert.equal( - $('.add-token__description')[0].textContent, - 'Token balance(s)', - 'confirm add token rendered' - ) + const confirmAddToken = await queryAsync($, '.confirm-add-token') + assert.ok(confirmAddToken[0], 'confirm add token rendered') assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') $('button.btn-primary--lg')[0].click() @@ -91,39 +95,46 @@ async function runAddTokenFlowTest (assert, done) { assert.ok(addTokenButton[0], 'add token button present') addTokenButton[0].click() - const addTokenTabs = await queryAsync($, '.add-token__header__tabs__tab') + addTokenWrapper = await queryAsync($, '.page-container') + const addTokenTabs = await queryAsync($, '.page-container__tab') assert.equal(addTokenTabs.length, 2, 'expected number of tabs') assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present') assert.ok(addTokenTabs[1], 'add custom token tab present') addTokenTabs[1].click() + await timeout(1000) // Input token contract address - const customInput = await queryAsync($, 'input.add-token__add-custom-input') - customInput.val('0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') - reactTriggerChange(customInput[0]) + const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0] + customInput.focus() + await timeout(1000) + nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c') + customInput.dispatchEvent(new Event('input', { bubbles: true})) + // Click Next button - nextButton = await queryAsync($, 'button.btn-primary--lg') - assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') - nextButton[0].click() + // nextButton = await queryAsync($, 'button.btn-primary--lg') + // assert.equal(nextButton[0].textContent, 'Next', 'next button rendered') + // nextButton[0].click() - // Verify symbol length error since contract address won't return symbol - const errorMessage = await queryAsync($, '.add-token__add-custom-error-message') + // // Verify symbol length error since contract address won't return symbol + const errorMessage = await queryAsync($, '#custom-symbol-helper-text') assert.ok(errorMessage[0], 'error rendered') $('button.btn-secondary--lg')[0].click() - // // Confirm Add token + // await timeout(100000) + + // Confirm Add token // assert.equal( - // $('.add-token__description')[0].textContent, + // $('.page-container__subtitle')[0].textContent, // 'Would you like to add these tokens?', // 'confirm add token rendered' // ) // assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found') // $('button.btn-primary--lg')[0].click() - // // Verify added token image - // heroBalance = await queryAsync($, '.hero-balance') - // assert.ok(heroBalance, 'rendered hero balance') - // assert.ok(heroBalance.find('.identicon')[0], 'token added') + // Verify added token image + heroBalance = await queryAsync($, '.hero-balance') + assert.ok(heroBalance, 'rendered hero balance') + assert.ok(heroBalance.find('.identicon')[0], 'token added') } diff --git a/test/integration/lib/mascara-first-time.js b/test/integration/lib/mascara-first-time.js index 5e07ab0b4..f43a30c74 100644 --- a/test/integration/lib/mascara-first-time.js +++ b/test/integration/lib/mascara-first-time.js @@ -1,5 +1,4 @@ const PASSWORD = 'password123' -const reactTriggerChange = require('react-trigger-change') const { timeout, findAsync, @@ -11,6 +10,11 @@ async function runFirstTimeUsageTest (assert, done) { const app = await queryAsync($, '#app-content') + // Used to set values on TextField input component + const nativeInputValueSetter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, 'value' + ).set + await skipNotices(app) const welcomeButton = (await findAsync(app, '.welcome-screen__button'))[0] @@ -21,12 +25,14 @@ async function runFirstTimeUsageTest (assert, done) { assert.equal(title, 'Create Password', 'create password screen') // enter password - const pwBox = (await findAsync(app, '.first-time-flow__input'))[0] - const confBox = (await findAsync(app, '.first-time-flow__input'))[1] - pwBox.value = PASSWORD - confBox.value = PASSWORD - reactTriggerChange(pwBox) - reactTriggerChange(confBox) + const pwBox = (await findAsync(app, '#create-password'))[0] + const confBox = (await findAsync(app, '#confirm-password'))[0] + + nativeInputValueSetter.call(pwBox, PASSWORD) + pwBox.dispatchEvent(new Event('input', { bubbles: true})) + + nativeInputValueSetter.call(confBox, PASSWORD) + confBox.dispatchEvent(new Event('input', { bubbles: true})) // Create Password const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0] @@ -71,10 +77,16 @@ async function runFirstTimeUsageTest (assert, done) { assert.ok(lock, 'Lock menu item found') lock.click() - const pwBox2 = (await findAsync(app, '#password-box'))[0] - pwBox2.value = PASSWORD + await timeout(1000) + + const pwBox2 = (await findAsync(app, '#password'))[0] + pwBox2.focus() + await timeout(1000) + + nativeInputValueSetter.call(pwBox2, PASSWORD) + pwBox2.dispatchEvent(new Event('input', { bubbles: true})) - const createButton2 = (await findAsync(app, 'button.primary'))[0] + const createButton2 = (await findAsync(app, 'button[type="submit"]'))[0] createButton2.click() const detail2 = (await findAsync(app, '.wallet-view'))[0] diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js index 0c0c5a77f..4856b3852 100644 --- a/test/integration/lib/tx-list-items.js +++ b/test/integration/lib/tx-list-items.js @@ -21,7 +21,7 @@ async function runTxListItemsTest(assert, done) { selectState.val('tx list items') reactTriggerChange(selectState[0]) - const metamaskLogo = await queryAsync($, '.left-menu-wrapper') + const metamaskLogo = await queryAsync($, '.app-header__logo-container') assert.ok(metamaskLogo[0], 'metamask logo present') metamaskLogo[0].click() @@ -46,7 +46,7 @@ async function runTxListItemsTest(assert, done) { const failedTx = txListItems[4] const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status') assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label') - + const shapeShiftTx = txListItems[5] const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)') assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status') diff --git a/test/screens/new-ui.js b/test/screens/new-ui.js index 91b3a9633..65a542e49 100644 --- a/test/screens/new-ui.js +++ b/test/screens/new-ui.js @@ -5,29 +5,43 @@ const mkdirp = require('mkdirp') const rimraf = require('rimraf') const webdriver = require('selenium-webdriver') const endOfStream = require('end-of-stream') +const clipboardy = require('clipboardy') +const Ethjs = require('ethjs') const GIFEncoder = require('gifencoder') const pngFileStream = require('png-file-stream') const sizeOfPng = require('image-size/lib/types/png') const By = webdriver.By -const { delay, buildWebDriver } = require('./func') const localesIndex = require('../../app/_locales/index.json') +const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('../e2e/func') + +const eth = new Ethjs(new Ethjs.HttpProvider('http://localhost:8545')) let driver +let screenshotCount = 0 + +captureAllScreens() +.then(async () => { + // build screenshots into gif + console.log('building gif...') + await generateGif() -captureAllScreens().catch((err) => { + await driver.quit() + process.exit() +}) +.catch(async (err) => { try { console.error(err) - verboseReportOnFailure() - driver.quit() + verboseReportOnFailure({ title: 'something broke' }) } catch (err) { console.error(err) } + + await driver.quit() process.exit(1) }) -async function captureAllScreens() { - let screenshotCount = 0 +async function captureAllScreens() { // common names let button let tabs @@ -35,12 +49,10 @@ async function captureAllScreens() { await cleanScreenShotDir() - // setup selenium and install extension const extPath = path.resolve('dist/chrome') - driver = buildWebDriver(extPath) - await driver.get('chrome://extensions-frame') - const elems = await driver.findElements(By.css('.extension-list-item-wrapper')) - const extensionId = await elems[1].getAttribute('id') + driver = buildChromeWebDriver(extPath) + const extensionId = await getExtensionIdChrome(driver) + await driver.get(`chrome-extension://${extensionId}/home.html`) await delay(500) tabs = await driver.getAllWindowHandles() @@ -75,10 +87,11 @@ async function captureAllScreens() { await driver.findElement(By.css('button')).click() await captureLanguageScreenShots('create password') - const passwordBox = await driver.findElement(By.css('input[type=password]:nth-of-type(1)')) - const passwordBoxConfirm = await driver.findElement(By.css('input[type=password]:nth-of-type(2)')) - passwordBox.sendKeys('123456789') - passwordBoxConfirm.sendKeys('123456789') + const password = '123456789' + const passwordBox = await driver.findElement(By.css('input#create-password')) + const passwordBoxConfirm = await driver.findElement(By.css('input#confirm-password')) + passwordBox.sendKeys(password) + passwordBoxConfirm.sendKeys(password) await delay(500) await captureLanguageScreenShots('choose-password-filled') @@ -112,109 +125,123 @@ async function captureAllScreens() { await delay(300) await captureLanguageScreenShots('secret backup phrase - reveal') + const seedPhrase = await driver.findElement(By.css('.backup-phrase__secret-words')).getText() + const seedPhraseWords = seedPhrase.split(' ') await driver.findElement(By.css('button')).click() await delay(300) await captureLanguageScreenShots('confirm secret backup phrase') - // finish up - console.log('building gif...') - await generateGif() - await driver.quit() - return - - // - // await button.click() - // await delay(700) - // this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText() - // await captureScreenShot('seed phrase') - // - // const continueAfterSeedPhrase = await driver.findElement(By.css('button')) - // await continueAfterSeedPhrase.click() - // await delay(300) - // await captureScreenShot('main screen') - // - // await driver.findElement(By.css('.sandwich-expando')).click() - // await delay(500) - // await captureScreenShot('menu') - - // await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click() - // await captureScreenShot('main screen') - // it('should accept account password after lock', async () => { - // await delay(500) - // await driver.findElement(By.id('password-box')).sendKeys('123456789') - // await driver.findElement(By.css('button')).click() - // await delay(500) - // }) - // - // it('should show QR code option', async () => { - // await delay(300) - // await driver.findElement(By.css('.fa-ellipsis-h')).click() - // await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click() - // await delay(300) - // }) - // - // it('should show the account address', async () => { - // this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText() - // await driver.findElement(By.css('.fa-arrow-left')).click() - // await delay(500) - // }) - - async function captureLanguageScreenShots(label) { - const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en') - // take english shot - await captureScreenShot(`${label} (en)`) - for (let localeMeta of nonEnglishLocales) { - // set locale and take shot - await setLocale(localeMeta.code) - await delay(300) - await captureScreenShot(`${label} (${localeMeta.code})`) - } - // return locale to english - await setLocale('en') - await delay(300) + // enter seed phrase + const seedPhraseButtons = await driver.findElements(By.css('.backup-phrase__confirm-seed-options > button')) + const seedPhraseButtonWords = await Promise.all(seedPhraseButtons.map(button => button.getText())) + for (let targetWord of seedPhraseWords) { + const wordIndex = seedPhraseButtonWords.indexOf(targetWord) + if (wordIndex === -1) throw new Error(`Captured seed phrase word "${targetWord}" not in found seed phrase button options ${seedPhraseButtonWords.join(' ')}`) + await driver.findElement(By.css(`.backup-phrase__confirm-seed-options > button:nth-child(${wordIndex+1})`)).click() + await delay(100) } + await captureLanguageScreenShots('confirm secret backup phrase - words selected correctly') - async function setLocale(code) { - await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code) - } + await driver.findElement(By.css('.backup-phrase__content-wrapper .first-time-flow__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask post-initialize greeter screen deposit ether') - async function setProviderType(type) { - await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) - } + await driver.findElement(By.css('.page-container__header-close')).click() + await delay(300) + await captureLanguageScreenShots('metamask account main screen') - // cleanup - await driver.quit() + // account details + export private key + await driver.findElement(By.css('.wallet-view__name-container > .wallet-view__details-button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail screen') - async function cleanScreenShotDir() { - await pify(rimraf)(`./test-artifacts/screens/`) - } + await driver.findElement(By.css('.account-modal__button:nth-of-type(2)')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - initial') - async function captureScreenShot(label) { - const shotIndex = screenshotCount.toString().padStart(4, '0') - screenshotCount++ - const artifactDir = `./test-artifacts/screens/` - await pify(mkdirp)(artifactDir) - // capture screenshot - const screenshot = await driver.takeScreenshot() - await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' }) - } + await driver.findElement(By.css('.private-key-password > input')).sendKeys(password) + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - password entered') + + await driver.findElement(By.css('.btn-primary--lg.export-private-key__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - reveal key') + + await driver.findElement(By.css('.export-private-key__button')).click() + await delay(300) + await captureLanguageScreenShots('metamask account detail export private key screen - done') + + // get eth from Ganache + // const viewAddressButton = await driver.findElement(By.css('.wallet-view__address')) + // await driver.actions({ bridge: true }).move({ origin: viewAddressButton }).perform() + // console.log('driver.actions', driver.actions({ bridge: true })) + // await delay(300) + // await captureLanguageScreenShots('metamask home - hover copy address') + + await driver.findElement(By.css('.wallet-view__address')).click() + await delay(100) + await captureLanguageScreenShots('metamask home - hover copy address') + + const primaryAddress = clipboardy.readSync() + await requestEther(primaryAddress) + // wait for block polling + await delay(10000) + await captureLanguageScreenShots('metamask home - has ether') - async function generateGif(){ - // calculate screenshot size - const screenshot = await driver.takeScreenshot() - const pngBuffer = Buffer.from(screenshot, 'base64') - const size = sizeOfPng.calculate(pngBuffer) +} - // read only the english pngs into gif - const encoder = new GIFEncoder(size.width, size.height) - const stream = pngFileStream('./test-artifacts/screens/* (en).png') - .pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 })) - .pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif')) - // wait for end - await pify(endOfStream)(stream) +async function captureLanguageScreenShots(label) { + const nonEnglishLocales = localesIndex.filter(localeMeta => localeMeta.code !== 'en') + // take english shot + await captureScreenShot(`${label} (en)`) + for (let localeMeta of nonEnglishLocales) { + // set locale and take shot + await setLocale(localeMeta.code) + await delay(300) + await captureScreenShot(`${label} (${localeMeta.code})`) } + // return locale to english + await setLocale('en') + await delay(300) +} +async function setLocale(code) { + await driver.executeScript('window.metamask.updateCurrentLocale(arguments[0])', code) +} + +async function setProviderType(type) { + await driver.executeScript('window.metamask.setProviderType(arguments[0])', type) +} + +async function cleanScreenShotDir() { + await pify(rimraf)(`./test-artifacts/screens/`) +} + +async function captureScreenShot(label) { + const shotIndex = screenshotCount.toString().padStart(4, '0') + screenshotCount++ + const artifactDir = `./test-artifacts/screens/` + await pify(mkdirp)(artifactDir) + // capture screenshot + const screenshot = await driver.takeScreenshot() + await pify(fs.writeFile)(`${artifactDir}/${shotIndex} - ${label}.png`, screenshot, { encoding: 'base64' }) +} + +async function generateGif(){ + // calculate screenshot size + const screenshot = await driver.takeScreenshot() + const pngBuffer = Buffer.from(screenshot, 'base64') + const size = sizeOfPng.calculate(pngBuffer) + + // read only the english pngs into gif + const encoder = new GIFEncoder(size.width, size.height) + const stream = pngFileStream('./test-artifacts/screens/* (en).png') + .pipe(encoder.createWriteStream({ repeat: 0, delay: 1000, quality: 10 })) + .pipe(fs.createWriteStream('./test-artifacts/screens/walkthrough (en).gif')) + + // wait for end + await pify(endOfStream)(stream) } async function verboseReportOnFailure(test) { @@ -228,3 +255,8 @@ async function verboseReportOnFailure(test) { const htmlSource = await driver.getPageSource() await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) } + +async function requestEther(address) { + const accounts = await eth.accounts() + await eth.sendTransaction({ from: accounts[0], to: address, value: 1 * 1e18, data: '0x0' }) +} diff --git a/test/stub/blacklist.json b/test/stub/blacklist.json deleted file mode 100644 index 6a3230b2f..000000000 --- a/test/stub/blacklist.json +++ /dev/null @@ -1,1374 +0,0 @@ -{ - "version": 2, - "tolerance": 2, - "fuzzylist": [ - "metamask.io", - "myetherwallet.com", - "cryptokitties.co", - "mycrypto.com" - ], - "whitelist": [ - "crypto.pro", - "ocrypto.org", - "wecrypto.net", - "iccrypto.io", - "crypto.kred", - "ohmycrypto.io", - "spcrypto.net", - "melcrypto.com", - "zzcrypto.org", - "zzcrypto.net", - "crypto.bg", - "mycrypto24.online", - "acrypto.io", - "mycrypto.ca", - "scrypto.io", - "mycrypto.dk", - "mvzcrypto.com", - "ambcrypto.com", - "crypto.bi", - "crypto.jobs", - "crypto.help", - "my.crypt.observer", - "crypt.observer", - "ucrypto.com", - "cryptojobslist.com", - "crypto.review", - "crypto.me", - "b3crypto.com", - "mycrypto.ninja", - "jkcrypto.com", - "crypto.cr", - "mycrypto.live", - "yocrypto.io", - "crypto.ba", - "zacrypto.info", - "mycrypto.com", - "remix.ethereum.org", - "metahash.io", - "metahash.net", - "metahash.org", - "cryptotitties.com", - "cryptocities.net", - "cryptoshitties.co", - "cryptotitties.fun", - "cryptokitties.forsale", - "cryptokitties.care", - "metamate.cc", - "metamesh.tech", - "ico.nexus.social", - "metamesh.org", - "metatask.io", - "metmask.com", - "metarasa.com", - "metapack.com", - "metacase.com", - "metafas.nl", - "metamako.com", - "metamast.com", - "metamax.ru", - "metadesk.io", - "metadisk.com", - "metallsk.ru", - "metamag.fr", - "metamaks.ru", - "metamap.ru", - "metamaps.cc", - "metamats.com", - "metamax.by", - "metamax.com", - "metamax.io", - "metamuse.net", - "metarank.com", - "metaxas.com", - "megamas2.ru", - "metamask.io", - "myetherwallet.com", - "myethlerwallet.com", - "ethereum.org", - "myetheroll.com", - "myetherapi.com", - "ledgerwallet.com", - "databrokerdao.com", - "etherscan.io", - "etherid.org", - "ether.cards", - "etheroll.com", - "ethnews.com", - "ethex.market", - "ethereumdev.io", - "ethereumdev.kr", - "dether.io", - "ethermine.org", - "slaask.com", - "etherbtc.io", - "ethereal.capital", - "etherisc.com", - "m.famalk.net", - "etherecho.com", - "ethereum.os.tc", - "theethereum.wiki", - "metajack.im", - "etherhub.io", - "ethereum.network", - "ethereum.link", - "ethereum.com", - "prethereum.org", - "ethereumj.io", - "etheraus.com", - "ethereum.dev", - "1ethereum.ru", - "ethereum.nz", - "nethereum.com", - "metabank.com", - "metamas.com", - "aventus.io", - "metabase.com", - "etherdelta.com", - "metabase.one", - "cryptokitties.co", - "remme.io", - "jibrel.network" - ], - "blacklist": [ - "xn--myethrwalle-jb9e19a.com", - "xn--myetheralle-7b9ezl.com", - "iconfoundation.co", - "fundrequest.info", - "xn--myetherwale-os8e7x.com", - "remme-ico.eu", - "gonetwork.live", - "token.gonetwork.pro", - "gonetwork.pro", - "gonetwork.eu", - "nucleus-vision.cc", - "jibreltoken.in", - "dock.so", - "dock.promo", - "xn--mycrypt-r0a.com", - "xn--mycrypt-g1a.com", - "xn--mycrpto-y2a.com", - "ethexploit.org", - "remme.in", - "remme.ws", - "remme.com.ng", - "nyeitthervvallet.com", - "xn--myeerhwailet-ooc.com", - "myeterhwaliot.com", - "remme.live", - "xn--yethewalle-to2exkhi.com", - "myetherwallet.custom-token.com", - "custom-token.com", - "sale-earn.com", - "bankera.live", - "originprotocol.io", - "trx.foundation", - "tokensale.adhive.net", - "adhive.net", - "decentral.market", - "cryptoexploite.com", - "blockclain.net", - "xn--blckchin-5za9o.info", - "xn--blkhain-m0a4pb.info", - "xn--blocchal-gmb8m.info", - "xn--blocchaln-orb.info", - "xn--blocchan-gmb7c.info", - "xn--blockaden-lsen-5pb.com", - "xn--blockchai-3vb.info", - "xn--blockchai-jvb.info", - "xn--blockchal-3vb.info", - "xn--blockcham-ipb.info", - "xn--blockchan-2pb.com", - "xn--blockchan-75a.com", - "xn--blockchan-7sb.info", - "xn--blockchan-d5a.net", - "xn--blockchan-dob.info", - "xn--blockchan-ipb.com", - "xn--blockchan-ipb.info", - "xn--blockchan-nk7d.com", - "xn--blockchan-xub.info", - "xn--blockchann-4ub.com", - "xn--blockchi-n7a50e.info", - "xn--blockchi-o8a54d.info", - "xn--blockchi-p99co8a.com", - "xn--blockchim-hdb.info", - "xn--blockchin-1xb.info", - "xn--blockchin-61a.info", - "xn--blockchin-61a.net", - "xn--blockchin-6ib.info", - "xn--blockchin-ccb.info", - "xn--blockchin-h4a.com", - "xn--blockchin-h4a.info", - "xn--blockchin-hdb.info", - "xn--blockchin-hhb.info", - "xn--blockchin-mib.net", - "xn--blockchin-wcb.com", - "xn--blockchn-fza4j.com", - "xn--blockchn-fza4j.info", - "xn--blockchn-n7a43b.info", - "xn--blockchn-p0a.info", - "xn--blockchn-tx0d4p.com", - "xn--blockclai-3vb.info", - "xn--blockclin-hdb.com", - "xn--blockclin-hdb.info", - "xn--blockclin-hdb.org", - "xn--blockflte-kirchrode-w6b.de", - "xn--blockfltenquartett-windspiel-81c.de", - "xn--blockhai-obb78c.info", - "xn--blockhain-4eb.com", - "xn--blockhain-pfb.com", - "xn--blockhain-pfb.info", - "xn--blockhain-zdb.info", - "xn--blockhan-obb65a.info", - "xn--blockhas-d6a.com", - "xn--blockwallt-j7a.com", - "xn--blokchai-fqb.info", - "xn--blokchain-nfb.info", - "xn--blokhain-28ab.info", - "xn--bockclnain-eyb.info", - "xn--mymoeo-zt7bzf.com", - "xn--mymoer-nqc1368c.com", - "xn--mymoero-c13c.com", - "xn--mymoero-s13c.com", - "xn--mymoneo-f63c.com", - "xn--mymoneo-v63c.com", - "xn--mymoneo-y53c.com", - "xn--mymoner-j0a.com", - "xn--mymoner-j5b.com", - "xn--mymoner-r0a.com", - "xn--mymoner-z0a.com", - "xn--mymoner-z2c.com", - "xn--mymonro-fya.com", - "xn--mymonro-x8a.com", - "xn--myetheallet-l58emu.com", - "xn--myetheraet-9k2ea77h.com", - "xn--myetheralet-ms8e21b.com", - "xn--myetheralle-7b9exm.com", - "xn--myetherallet-5s5f.com", - "xn--myetherallet-fs5f.com", - "xn--myetherewalle-1t1g.com", - "xn--myetherllet-pl9e6k.com", - "xn--myethervvalle-8vc.com", - "xn--myetherwaet-61ea.com", - "xn--myetherwaet-8eda.com", - "xn--myetherwaet-ns8ea.com", - "xn--myetherwale-ns8e8x.com", - "xn--myetherwalet-0fb.com", - "xn--myetherwalet-0z4f.com", - "xn--myetherwalet-814f.com", - "xn--myetherwalet-d9b.com", - "xn--myetherwalet-h14f.com", - "xn--myetherwalle-9me.com", - "xn--myetherwalle-ek5f.com", - "xn--myetherwalle-fqc.com", - "xn--myetherwalle-opc.com", - "xn--myetherwalle-q05f.com", - "xn--myetherwllet-wob.com", - "xn--myetherwllt-r7a0i.com", - "xn--myethewaliet-9d5f.com", - "xn--myethewalle-3ic0947g.com", - "xn--myethewallet-0e5f.com", - "xn--myethewallet-1kc.com", - "xn--myethewallet-bkc.com", - "xn--myethewallet-vof.com", - "xn--myethewalliet-nm1g.com", - "xn--myethewallt-kbb3019g.com", - "xn--myethewallt-w48ew7b.com", - "xn--myethrwalet-6qb6408g.com", - "xn--myethrwalet-ms8e83d.com", - "xn--myethrwallet-1db.com", - "xn--myethrwallt-29af.com", - "xn--myethrwallt-29as.com", - "xn--myethrwllet-q7a31e.com", - "xn--myethrwllet-r8a3c.com", - "fintrux.eu", - "refereum-ico.eu", - "arcblock-ico.org", - "xn--fuson-1sa.org", - "refereum-token.com", - "fintrux.co", - "ico-ton.org", - "xn--mytherwallt-cbbv.com", - "xmoneta.co", - "data-wallet.co", - "tokensale.data-wallet.co", - "xn--myeerhwallot-ooc.com", - "xn--myeterwalet-cm8epi.com", - "xn--myeterwalle-cm8ev6a.com", - "rnyetherumwallet.com", - "republic-protocol.net", - "nyeihitervvallatt.com", - "arcblock.eu", - "republicprotocol.eu", - "tokensale-fusion.com", - "myetherwalletjoin.com", - "medicalchian.com", - "myeahteirwaliet.com", - "myenhtersvvailct.com", - "trinity-token.com", - "xn--eo-yzs.com", - "zilliqa.in", - "sparc.pro", - "myetherwallet.import-tokens.com", - "token-gram.org", - "xn--shapshift-e4a.com", - "xn--shapshift-y4a.com", - "xn--shpeshift-c2a.com", - "xn--shpeshift-r1a.com", - "xn--shapshift-o4a.com", - "xn--shpeshift-w2a.com", - "xn--shapeshft-w5a.com", - "tokensale-fusion.org", - "fusion-ico.com", - "beetolen.com", - "tokencrowdsale.online", - "fusion.tokencrowdsale.online", - "beetokem.com", - "block.chaiins.in", - "origintrail.in", - "bit-z.ru", - "xn--myetherallet-nu5f.com", - "xn--mytherwalet-3qb08c.com", - "xn--myeterwllet-cm8et1d.com", - "xn--mytherwllet-q7a01e.com", - "xn--biance-xt7b.com", - "xn--bnance-wic.com", - "xn--biance-jeb.com", - "xn--bttrx-9za8334c.com", - "wwwkodakcoin.com", - "myetherwallet.uk.com", - "kodakone.cc", - "nyeihitervvallet.com", - "xn--myeterwalet-cm8eoi.com", - "nucleus.foundation", - "beetoken-ico.com", - "data-token.com", - "tron-labs.com", - "ocoin.tech", - "aionfoundation.com", - "ico-telegram.org", - "nyeihitervvallat.com", - "telegramcoin.us", - "daddi.cloud", - "daditoken.com", - "blockarray.org", - "dadi-cloud.net", - "wanchainfunding.org", - "ico-telegram.io", - "iconfoundation.site", - "iost.co", - "beetoken-ico.eu", - "cindicator.network", - "wanchainetwork.org", - "wamchain.org", - "wanchainltd.org", - "wanchainalliance.org", - "nucleus-vision.net", - "ledgerwallet.by", - "nucleuss.vision", - "myenhterswailct.com", - "cobin-hood.com", - "wanchainfoundation.org", - "xn--polniex-ex4c.com", - "xn--polniex-s1a.com", - "xn--polonex-ieb.com", - "xn--polonex-sza.com", - "xn--polonex-zw4c.com", - "xn--polonix-ws4c.com", - "xn--polonix-y8a.com", - "xn--pooniex-ojb.com", - "gramico.info", - "dimnsions.network", - "www-gemini.com", - "login-kucoin.net", - "venchain.foundation", - "grampreico.com", - "tgram.cc", - "ton-gramico.com", - "wwwpaywithink.com", - "coniomi.com", - "paywithnk.com", - "paywithlnk.com", - "iluminatto.com.br", - "pundix.eu", - "xn--bttrx-esay.com", - "xn--bttrex-w8a.com", - "xn--bnance-bwa.com", - "xn--shpeshift-11a.com", - "xn--shapeshif-ts6d.com", - "xn--shapshift-yf7d.com", - "wwwbluzelle.com", - "bluzelie.com", - "nucleus-vision.org", - "omisegonetwork.site", - "etlherzero.com", - "etlherdelta.com", - "xn--condesk-0ya.com", - "xn--condesk-sfb.com", - "xn--coindsk-vs4c.com", - "iexecplatform.com", - "tongramico.com", - "nucleus-vision.eu", - "intchain.network", - "wanchain.cloud", - "bluzelle-ico.com", - "ethzero-wallet.com", - "xn--metherwalle-jb9et7d.com", - "xn--coinesk-jo3c.com", - "venchainfoundation.com", - "myenhtersvvailot.com", - "ether-zero.net", - "ins.foundation", - "nastoken.org", - "telcointoken.com", - "ether0.org", - "eterzero.org", - "bluzelle-ico.eu", - "bleuzelle.com", - "appcoinstoken.org", - "xn--quanstamp-8s6d.com", - "myehntersvvailct.com", - "myeherwalllet.com", - "ico-bluzelle.com", - "bluzelle.im", - "bluzelle.one", - "bluzele.sale", - "bluzele.co", - "sether.ws", - "xn--myetherwalet-6gf.com", - "xn--rnyethewaliet-om1g.com", - "rnyethervailet.com", - "mvetherwaliet.com", - "rnyetherwailet.com", - "myethervaliet.com", - "rnyethervaliet.com", - "mvetherwalilet.com", - "xn--myethewalie-3ic0947g.com", - "xn--mthrwallet-z6ac3y.com", - "xn--myeherwalie-vici.com", - "xn--myethervvalie-8vc.com", - "xn--mythrwallt-06acf.com", - "xn--mtherwallet-y9a6y.com", - "myetherwallet.applytoken.tk", - "ethereum-zero.com", - "quanstamptoken.tk", - "bluzelle.network", - "ether-wallet.org", - "tron-wallet.info", - "appcoinsproject.com", - "vechain.foundation", - "tronlab.site", - "tronlabs.network", - "bluzelle.cc", - "ethblender.com", - "ethpaperwallet.net", - "waltontoken.org", - "icoselfkey.org", - "etherzeroclaim.com", - "etherzero.promo", - "bluzelle.pro", - "token-selfkey.org", - "xn--etherdlta-0f7d.com", - "sether.in", - "xn--ttrex-ysa9423c.com", - "bluzelle.eu", - "bluzelle.site", - "gifto.tech", - "xn--os-g7s.com", - "selfkey.co", - "xn--myeherwalet-ns8exy.com", - "xn--coinelegraph-wk5f.com", - "dai-stablecoin.com", - "eos-token.org", - "venchain.org", - "gatcoins.io", - "deepbrainchain.co", - "myetherwalililet.info", - "myehvterwallet.com", - "myehterumswallet.com", - "nucleusico.com", - "tronlab.tech", - "0x-project.com", - "gift-token-events.mywebcommunity.org", - "funfairtoken.org", - "breadtokenapp.com", - "cloudpetstore.com", - "myethwalilet.com", - "selfkeys.org", - "wallet-ethereum.com", - "xn--methrwallt-26ar0z.com", - "xn--mytherwllet-r8a0c.com", - "bluzelle.promo", - "tokensale.bluzelle.promo", - "cedarlake.org", - "marketingleads4u.com", - "cashaa.co", - "xn--inance-hrb.com", - "wanchain.tech", - "zenprolocol.com", - "ethscan.io", - "etherscan.in", - "props-project.com", - "zilliaq.com", - "reqestnetwork.com", - "etherdelta.pw", - "ethereum-giveaway.org", - "mysimpletoken.org", - "binancc.com", - "blnance.org", - "elherdelta.io", - "xn--hapeshit-ez9c2y.com", - "tenxwallet.co", - "singularitynet.info", - "mytlherwaliet.info", - "iconmainnet.ml", - "tokenselfkey.org", - "xn--myetewallet-cm8e5y.com", - "envione.org", - "myetherwalletet.com", - "claimbcd.com", - "ripiocreditnetwork.in", - "xn--yeterwallet-ml8euo.com", - "ethclassicwallet.info", - "myltherwallet.ru.com", - "etherdella.com", - "xn--yeterwallet-bm8ewn.com", - "singularty.net", - "cloudkitties.co", - "iconfoundation.io", - "kittystat.com", - "gatscoin.io", - "singularitynet.in", - "sale.canay.io", - "canay.io", - "wabicoin.co", - "envion.top", - "sirinslabs.com", - "tronlab.co", - "paxful.com.ng", - "changellyli.com", - "ethereum-code.com", - "xn--plonex-6va6c.com", - "envion.co", - "envion.cc", - "envion.site", - "ethereumchain.info", - "xn--envon-1sa.org", - "xn--btstamp-rfb.net", - "envlon.org", - "envion-ico.org", - "spectivvr.org", - "sirinlbs.com", - "ethereumdoubler.life", - "xn--myetherwllet-fnb.com", - "sirin-labs.com", - "sirin-labs.org", - "envion.one", - "envion.live", - "propsproject.org", - "propsprojects.com", - "decentralland.org", - "xn--metherwalet-ns8ep4b.com", - "redpulsetoken.co", - "propsproject.tech", - "xn--myeterwalet-nl8emj.com", - "powrerledger.com", - "cryptokitties.com", - "sirinlabs.pro", - "sirinlabs.co", - "sirnlabs.com", - "superbitcoin-blockchain.info", - "hellobloom.me", - "mobus.network", - "powrrledger.com", - "xn--myeherwalet-ms8eyy.com", - "qlink-ico.com", - "gatcoin.in", - "tokensale.gamefllp.com", - "gamefllp.com", - "xn--myeherwalle-vici.com", - "xn--myetherwalet-39b.com", - "xn--polonex-ffb.com", - "xn--birex-leba.com", - "raiden-network.org", - "sirintabs.com", - "xn--metherwallt-79a30a.com", - "xn--myethrwllet-2kb3p.com", - "myethlerwallet.eu", - "xn--btrex-b4a.com", - "powerrledger.com", - "xn--cointeegraph-wz4f.com", - "myerherwalet.com", - "qauntstanp.com", - "myetherermwallet.com", - "xn--myethewalet-ns8eqq.com", - "xn--nvion-hza.org", - "nnyetherwallelt.ru.com", - "ico-wacoin.com", - "xn--myeterwalet-nl8enj.com", - "bitcoinsilver.io", - "t0zero.com", - "tokensale.gizer.in", - "gizer.in", - "wabitoken.com", - "gladius.ws", - "xn--metherwallt-8bb4w.com", - "quanttstamp.com", - "gladius.im", - "ethereumstorage.net", - "powerledgerr.com", - "xn--myeherwallet-4j5f.com", - "quamtstamp.com", - "quntstamp.com", - "xn--changely-j59c.com", - "shapeshlft.com", - "coinbasenews.co.uk", - "xn--metherwallet-hmb.com", - "envoin.org", - "powerledger.com", - "bitstannp.net", - "xn--myetherallet-4k5fwn.com", - "xn--coinbas-pya.com", - "requestt.network", - "oracls.network", - "sirinlabs.website", - "powrledger.io", - "slackconfirm.com", - "shape-shift.io", - "oracles-network.org", - "xn--myeherwalle-zb9eia.com", - "blockstack.one", - "urtust.io", - "bittrex.one", - "t0-ico.com", - "xn--cinbase-90a.com", - "xn--metherwalet-ns8ez1g.com", - "tzero-ico.com", - "tzero.su", - "tzero.website", - "blockstack.network", - "ico-tzero.com", - "spectre.site", - "tzero.pw", - "spectre-ai.net", - "xn--waxtokn-y8a.com", - "dmarket.pro", - "bittrex.com11648724328774.cf", - "bittrex.com1987465798.ga", - "autcus.org", - "t-zero.org", - "xn--zero-zxb.com", - "myetherwalletfork.com", - "blokclbain.info", - "datum.sale", - "spectre-ai.org", - "powerledgr.com", - "simpletoken.live", - "sale.simpletoken.live", - "qauntstamp.com", - "raiden-network.com", - "metalpayme.com", - "quantstamp-ico.com", - "myetherwailetclient.com", - "biockchain.biz", - "wallets-blockchain.com", - "golemairdrop.com", - "omisegoairdrop.net", - "blodkchainwallet.info", - "walton-chain.org", - "elite888-ico.com", - "bitflyerjp.com", - "chainlinksmartcontract.com", - "stormtoken.eu", - "omise-go.tech", - "saltending.com", - "stormltoken.com", - "xn--quanttamp-42b.com", - "stormtoken.co", - "storntoken.com", - "stromtoken.com", - "storm-token.com", - "stormtokens.io", - "ether-delta.com", - "ethconnect.live", - "ethconnect.trade", - "xn--bttrex-3va.net", - "quantstamp.com.co", - "wancha.in", - "augur-network.com", - "quantstamp.com.ua", - "myetherwalletmew.com", - "myetherumwalletts.com", - "xn--quanstamp-tmd.com", - "quantsstamps.com", - "changellyl.net", - "xn--myetherwalet-1fb.com", - "myethereumwallets.com", - "xn--myetherwalet-e9b.com", - "quantslamp.com", - "metelpay.com", - "xn--eterdelta-m75d.com", - "linksmartcontract.com", - "myetherwalletaccess.com", - "myetherwalletcheck.com", - "myetherwalletcheck.info", - "myetherwalletconf.com", - "myetherwalleteal.com", - "myetherwalletec.com", - "myetherwalletgeth.com", - "myetherwalletmetamask.com", - "myetherwalletmm.com", - "myetherwalletmy.com", - "myetherwalletnh.com", - "myetherwalletnod.com", - "myetherwalletrr.com", - "myetherwalletrty.com", - "myetherwalletsec.com", - "myetherwalletsecure.com", - "myetherwalletutc.com", - "myetherwalletver.info", - "myetherwalletview.com", - "myetherwalletview.info", - "myetherwalletvrf.com", - "myetherwalletmist.com", - "myetherwalletext.com", - "myetherwalletjson.com", - "mettalpay.com", - "bricklblock.io", - "bittrexy.com", - "utrust.so", - "myethierwallet.org", - "metallpay.com", - "kraken-wallet.com", - "dmarkt.io", - "etherdeltla.com", - "unlversa.io", - "universa.sale", - "mercuryprotocol.live", - "ripiocredlt.network", - "myetlherwa11et.com", - "dentacoin.in", - "rdrtg.com", - "myetherwallet.com.rdrgh.com", - "rdrgh.com", - "ripiocreditnetwork.co", - "riaden.network", - "hydrominer.biz", - "rdrblock.com", - "reqest.network", - "senstoken.com", - "myetherwallat.services", - "ripiocredit.net", - "xn--metherwallet-c06f.com", - "ico.ripiocredits.com", - "ripiocredits.com", - "raidens.network", - "artoken.co", - "myetherwalletlgn.com", - "etherblog.click", - "stormtoken.site", - "httpmyetherwallet.com", - "myetherwalletverify.com", - "byzantiumfork.com", - "myetherwallet.com.byzantiumfork.com", - "www-myethervvallet.com", - "ether24.info", - "block-v.io", - "bittrex.cash", - "shapishift.io", - "ripiocerdit.network", - "rnyetherwa11et.com", - "claimether.com", - "enigmatokensale.com", - "ethereum-org.com", - "mvetnerwallet.com", - "myctherwallet.com", - "myetherwaltet.com", - "myetherwatlet.com", - "privatix.me", - "myetherwalletcnf.com", - "myetherwalletver.com", - "privatix.top", - "privatix.pro", - "privatex.io", - "stormtoken.cc", - "raiden.online", - "stormstoken.com", - "myetereumwallet.com", - "stormtokens.net", - "myetherwalletconf.info", - "storrntoken.com", - "worldofbattles.io", - "ico.worldofbattles.io", - "privatix.live", - "riden.network", - "raidan.network", - "ralden.network", - "mymyetherwallet.com", - "myetherwallets.net", - "myetherwalletverify.info", - "stormxtoken.com", - "myethereum-wallet.com", - "myetherwallet-forkprep.pagedemo.co", - "myetnerwailet.com", - "www-mvetherwallet.com", - "etheirdelta.com", - "myetherwalletiu.com", - "myetherwaiiett.com", - "xn--mytherwalet-cbb87i.com", - "xn--myethrwallet-ivb.co", - "xn--myeterwallet-f1b.com", - "myehterwaliet.com", - "omegaone.co", - "myetherwaiietw.com", - "slack.com.ru", - "polkodot.network", - "request-network.net", - "requestnetwork.live", - "binancie.com", - "first-eth.info", - "myewerthwalliet.com", - "enjincoin.pw", - "xn--bitrex-k17b.com", - "alrswap.io", - "www-request.network", - "myetnenwallet.com", - "www-enigma.co", - "cryptoinsidenews.com", - "air-swap.tech", - "launch.airswap.cc", - "airswap.cc", - "airswaptoken.com", - "launch.airswap.in", - "airswap.in", - "security-steemit.com.mx", - "blockchalnwallet.com", - "blodkchainwallet.com", - "blodkchaln.com", - "myethereumwaiiet.com", - "myethereumwaliet.com", - "myethereumwalilet.com", - "myetherswailet.com", - "myetherswaliet.com", - "myetherswalilet.com", - "myetherwalilett.com", - "myetherwalletl.com", - "myetherwalletww.com", - "myethereunwallet.com", - "myethereumwallct.com", - "myetherwaiieti.com", - "myetherwaiiete.com", - "upfirng.com", - "paypie.net", - "paypie.tech", - "soam.co", - "myetherwaiict.com", - "numerai-token.com", - "www-bankera.com", - "vvanchain.org", - "omisegoairdrop.com", - "xn--enjncoin-41a.io", - "suncontract.su", - "myetherwaiietr.com", - "shapeshiff.io", - "warchain.org", - "myethwallett.com", - "myethervvaliet.com", - "wanchains.org", - "etherparty.in", - "enjincoin.me", - "etiam.io", - "invest.smartlands.tech", - "smartlands.tech", - "enijncoin.io", - "wanchain.network", - "nimiq.su", - "enjincoin.sale", - "tenxwallet.io", - "golem-network.net", - "myyethwallet.ml", - "mywetherwailiet.com", - "omg-omise.com", - "district0x.tech", - "centra-token.com", - "etherdetla.com", - "etnerparty.io", - "etherdelta.su", - "myetherwallett.neocities.org", - "myetherwallet-secure.com", - "myethereumwalletntw.info", - "real-markets.io", - "wallet-ethereum.org", - "request-network.com", - "shapeshifth.io", - "shiapeshift.in", - "coin.red-puise.com", - "ibittreix.com", - "coinkbase.com", - "cindicator.pro", - "myetherwallet.com.ailogin.me", - "eventchain.co", - "kinkik.in", - "myetherumwalletview.com", - "protostokenhub.com", - "coinrbase.com", - "myetherwalletlogin.com", - "omisegotoken.com", - "myethereumwalletntw.com", - "reall.markets", - "cobinhood.org", - "cobinhood.io", - "happy-coin.org", - "bitfinex.com.co", - "bitfienex.com", - "iconn.foundation", - "centra.vip", - "smartcontract.live", - "icon.community", - "air-token.com", - "centra.credit", - "myetherwallet-singin.com", - "smartcontractlink.com", - "shapesshift.io", - "0xtoken.io", - "augurproject.co", - "ethereumus.one", - "myetherumwalet.com", - "myetherwalletsignin.com", - "change-bank.org", - "charge-bank.com", - "myetherwalletsingin.com", - "myetherwalletcontract.com", - "change-bank.io", - "chainlink.tech", - "myetherwallet-confirm.com", - "tokensale.kybernet.network", - "kybernet.network", - "kyberr.network", - "kybernetwork.io", - "myetherwalletconfirm.com", - "kvnuke.github.io", - "kin.kikpro.co", - "myethereumwallet.co.uk", - "tokensale-kyber.network", - "kyber-network.co", - "tokensale.kyber-network.co", - "pyro0.github.io", - "tokensale.kyber.digital", - "kyber.digital", - "omise-go.me", - "my.etherwallet.com.de", - "bepartof.change-bank.co", - "change-bank.co", - "enigma-tokens.co", - "coinbase.com.eslogin.co", - "xn--bittrx-mva.com", - "ethrdelta.github.io", - "etherdellta.com", - "ico-nexus.social", - "red-pulse.tech", - "bitj0b.io", - "xn--bttrex-bwa.com", - "kin-klk.com", - "kin-crowdsale.com", - "ethedelta.com", - "coindash.su", - "myethwallet.co.uk", - "swarm.credit", - "myethereumwallet.uk", - "iconexu.social", - "wanchain.co", - "enigrna.co", - "linknetwork.co", - "qtum-token.com", - "omisego.com.co", - "rivetzintl.org", - "etherdelta.one", - "the-ether.pro", - "etherdelta.gitnub.io", - "kirkik.com", - "monetha.ltd", - "vlberate.io", - "ethereumwallet-kr.info", - "omise-go.org", - "iconexus.social", - "bittirrex.com", - "aventus.pro", - "atlant.solutions", - "aventus.group", - "metamak.io", - "omise.com.co", - "herotokens.io", - "starbase.pro", - "etherdelta.githulb.io", - "herotoken.co", - "kinico.net", - "dmarket.ltd", - "etherdelta.gilthub.io", - "golem-network.com", - "etnerscan.io", - "bllttriex.com", - "monetha.me", - "monetha.co", - "monetha-crowdsale.com", - "starbase.tech", - "aventus-crowdsale.com", - "shapeshift.pro", - "bllttrex.com", - "kickico.co", - "statustoken.im", - "bilttrex.com", - "tenxpay.io", - "bittrex.ltd", - "metalpay.im", - "aragon.im", - "coindash.tech", - "decentraland.tech", - "decentraland.pro", - "status-token.com", - "bittrex.cam", - "enigmatoken.com", - "unocoin.company", - "unocoin.fund", - "0xproject.io", - "0xtoken.com", - "numerai.tech", - "decentraiand.org", - "blockcrein.info", - "blockchealn.info", - "bllookchain.info", - "blockcbhain.info", - "myetherwallet.com.ethpromonodes.com", - "mettamask.io", - "tokenswap.org", - "netherum.com", - "etherexx.org", - "etherume.io", - "ethereum.plus", - "ehtereum.org", - "etereurm.org", - "etheream.com", - "ethererum.org", - "ethereum.io", - "etherdelta-glthub.com", - "cryptoalliance.herokuapp.com", - "bitspark2.com", - "indorsetoken.com", - "iconexus.tk", - "iconexus.ml", - "iconexus.ga", - "iconexus.cf", - "etherwallet.online", - "wallet-ethereum.net", - "bitsdigit.com", - "etherswap.org", - "eos.ac", - "uasfwallet.com", - "ziber.io", - "multiply-ethereum.info", - "bittrex.comze.com", - "karbon.vacau.com", - "etherdelta.gitlhub.io", - "etherdelta.glthub.io", - "digitaldevelopersfund.vacau.com", - "district-0x.io", - "coin-dash.com", - "coindash.ru", - "district0x.net", - "aragonproject.io", - "coin-wallet.info", - "coinswallet.info", - "contribute-status.im", - "ether-api.com", - "ether-wall.com", - "mycoinwallet.net", - "ethereumchamber.com", - "ethereumchamber.net", - "ethereumchest.com", - "ethewallet.com", - "myetherwallet.com.vc", - "myetherwallet.com.pe", - "myetherwallet.us.com", - "myetherwallet.com.u0387831.cp.regruhosting.ru", - "myethereumwallet.su", - "myetherweb.com.de", - "myetherieumwallet.com", - "myetehrwallet.com", - "myeterwalet.com", - "myetherwaiiet.com", - "myetherwallet.info", - "myetherwallet.ch", - "myetherwallet.om", - "myethervallet.com", - "myetherwallet.com.cm", - "myetherwallet.com.co", - "myetherwallet.com.de", - "myetherwallet.com.gl", - "myetherwallet.com.im", - "myetherwallet.com.ua", - "secure-myetherwallet.com", - "update-myetherwallet.com", - "wwwmyetherwallet.com", - "myeatherwallet.com", - "myetharwallet.com", - "myelherwallel.com", - "myetherwaillet.com", - "myetherwaliet.com", - "myetherwallel.com", - "myetherwallet.cam", - "myetherwallet.cc", - "myetherwallet.co", - "myetherwallet.cm", - "myetherwallet.cz", - "myetherwallet.org", - "myetherwallet.tech", - "myetherwallet.top", - "myetherwallet.net", - "myetherwallet.ru.com", - "myetherwallet.com.ru", - "metherwallet.com", - "myetrerwallet.com", - "myetlerwallet.com", - "myethterwallet.com", - "myethwallet.io", - "myethterwallet.co", - "myehterwallet.co", - "myaetherwallet.com", - "myetthterwallet.com", - "myetherwallet.one", - "myelterwallet.com", - "myetherwallet.gdn", - "myetherwallt.com", - "myeterwallet.com", - "myeteherwallet.com", - "myethearwailet.com", - "myetherwallelt.com", - "myetherwallett.com", - "etherwallet.org", - "myetherewallet.com", - "myeherwallet.com", - "myethcrwallet.com", - "myetherwallet.link", - "myetherwallets.com", - "myethearwaillet.com", - "myethearwallet.com", - "myetherawllet.com", - "myethereallet.com", - "myetherswallet.com", - "myetherwalet.com", - "myetherwaller.com", - "myetherwalliet.com", - "myetherwllet.com", - "etherwallet.io", - "myetherwallet.ca", - "myetherwallet.me", - "myetherwallet.ru", - "myetherwallet.xyz", - "myetherwallte.com", - "myethirwallet.com", - "myethrewallet.com", - "etherwallet.net", - "maetherwallet.com", - "meyetherwallet.com", - "my.ether-wallet.pw", - "myehterwallet.com", - "myeitherwallet.com", - "myelherwallet.com", - "myeltherwallet.com", - "myerherwallet.com", - "myethearwalet.com", - "myetherewalle.com", - "myethervvallet.com", - "myetherwallent.com", - "myetherwallet.fm", - "myetherwalllet.com", - "myetherwalltet.com", - "myetherwollet.com", - "myetlherwalet.com", - "myetlherwallet.com", - "rnyetherwallet.com", - "etherclassicwallet.com", - "omg-omise.co", - "omise-go.com", - "omise-go.net", - "omise-omg.com", - "omise-go.io", - "tenx-tech.com", - "bitclaive.com", - "tokensale-tenx.tech", - "ubiqcoin.org", - "metamask.com", - "ethtrade.io", - "myetcwallet.com", - "account-kigo.net", - "bitcoin-wallet.net", - "blocklichan.info", - "bloclkicihan.info", - "coindash.ml", - "eos-bonus.com", - "eos-io.info", - "ether-wallet.net", - "ethereum-wallet.info", - "ethereum-wallet.net", - "ethereumchest.net", - "reservations-kigo.net", - "reservations-lodgix.com", - "secure-liverez.com", - "secure-onerooftop.com", - "settings-liverez.com", - "software-liverez.com", - "software-lodgix.com", - "unhackableetherwallets.com", - "www-myetherwallet.com", - "etherwallet.co.za", - "etherwalletchain.com", - "etherwallets.net", - "etherwallets.nl", - "my-ethwallet.com", - "my.ether-wallet.co", - "myetherwallet.com.am", - "myetherwallet.com.ht", - "myetherwalletcom.com", - "myehterwailet.com", - "xn--myetherwalle-xoc.com", - "xn--myetherwalle-44i.com", - "xn--myetherwalle-xhk.com", - "xn--myetherwallt-cfb.com", - "xn--myetherwallt-6tb.com", - "xn--myetherwallt-xub.com", - "xn--myetherwallt-ovb.com", - "xn--myetherwallt-fwb.com", - "xn--myetherwallt-5wb.com", - "xn--myetherwallt-jzi.com", - "xn--myetherwallt-2ck.com", - "xn--myetherwallt-lok.com", - "xn--myetherwallt-lsl.com", - "xn--myetherwallt-ce6f.com", - "xn--myetherwalet-mcc.com", - "xn--myetherwalet-xhf.com", - "xn--myetherwalet-lcc.com", - "xn--myetherwaet-15ba.com", - "xn--myetherwalet-whf.com", - "xn--myetherwaet-v2ea.com", - "xn--myetherwllet-59a.com", - "xn--myetherwllet-jbb.com", - "xn--myetherwllet-wbb.com", - "xn--myetherwllet-9bb.com", - "xn--myetherwllet-ncb.com", - "xn--myetherwllet-0cb.com", - "xn--myetherwllet-5nb.com", - "xn--myetherwllet-ktd.com", - "xn--myetherwllet-mre.com", - "xn--myetherwllet-76e.com", - "xn--myetherwllet-o0l.com", - "xn--myetherwllet-c45f.com", - "xn--myetherallet-ejn.com", - "xn--myethewallet-4nf.com", - "xn--myethewallet-iof.com", - "xn--myethewallet-mpf.com", - "xn--myethewallet-6bk.com", - "xn--myethewallet-i31f.com", - "xn--myethrwallet-feb.com", - "xn--myethrwallt-fbbf.com", - "xn--myethrwallet-seb.com", - "xn--myethrwallt-rbbf.com", - "xn--myethrwallet-5eb.com", - "xn--myethrwallt-3bbf.com", - "xn--myethrwallet-0tb.com", - "xn--myethrwallt-tpbf.com", - "xn--myethrwallet-rub.com", - "xn--myethrwallt-iqbf.com", - "xn--myethrwallet-ivb.com", - "xn--myethrwallt-6qbf.com", - "xn--myethrwallet-8vb.com", - "xn--myethrwallt-vrbf.com", - "xn--myethrwallet-zwb.com", - "xn--myethrwallt-ksbf.com", - "xn--myethrwallet-dzi.com", - "xn--myethrwallt-wbif.com", - "xn--myethrwallet-wck.com", - "xn--myethrwallt-skjf.com", - "xn--myethrwallet-fok.com", - "xn--myethrwallt-fvjf.com", - "xn--myethrwallet-fsl.com", - "xn--myethrwallt-fwkf.com", - "xn--myethrwallet-5d6f.com", - "xn--myethrwallt-319ef.com", - "xn--myeterwallet-ufk.com", - "xn--myeterwallet-nrl.com", - "xn--myeterwallet-von.com", - "xn--myeterwallet-jl6c.com", - "xn--myeherwallet-ooc.com", - "xn--myeherwalle-6hci.com", - "xn--myeherwallet-v4i.com", - "xn--myeherwalle-zgii.com", - "xn--myeherwallet-ohk.com", - "xn--myeherwalle-6oji.com", - "xn--mytherwallet-ceb.com", - "xn--mythrwallet-cbbc.com", - "xn--mythrwallt-c7acf.com", - "xn--mytherwallet-peb.com", - "xn--mythrwallet-obbc.com", - "xn--mythrwallt-n7acf.com", - "xn--mytherwallet-2eb.com", - "xn--mythrwallet-0bbc.com", - "xn--mythrwallt-y7acf.com", - "xn--mytherwallet-xtb.com", - "xn--mythrwallet-qpbc.com", - "xn--mythrwallt-jlbcf.com", - "xn--mytherwallet-oub.com", - "xn--mythrwallet-fqbc.com", - "xn--mythrwallt-5lbcf.com", - "xn--mythrwallet-3qbc.com", - "xn--mythrwallt-smbcf.com", - "xn--mytherwallet-5vb.com", - "xn--mythrwallet-srbc.com", - "xn--mythrwallt-fnbcf.com", - "xn--mytherwallet-wwb.com", - "xn--mythrwallet-hsbc.com", - "xn--mythrwallt-1nbcf.com", - "xn--mytherwallet-9yi.com", - "xn--mythrwallet-tbic.com", - "xn--mythrwallt-dnhcf.com", - "xn--mytherwallet-tck.com", - "xn--mythrwallet-pkjc.com", - "xn--mythrwallt-lsicf.com", - "xn--mytherwallet-cok.com", - "xn--mythrwallet-cvjc.com", - "xn--mythrwallt-c2icf.com", - "xn--mytherwallet-csl.com", - "xn--mythrwallet-cwkc.com", - "xn--mythrwallt-c0jcf.com", - "xn--mytherwallet-2d6f.com", - "xn--mythrwallet-019ec.com", - "xn--mythrwallt-yq3ecf.com", - "xn--metherwallet-qlb.com", - "xn--metherwallet-1uf.com", - "xn--metherwallet-iyi.com", - "xn--metherwallet-zhk.com", - "xn--metherwallet-3ml.com", - "xn--mytherwallet-fvb.com", - "xn--myetherwallt-7db.com", - "xn--myetherwallt-leb.com", - "xn--myetherwallt-yeb.com", - "xn--yetherwallet-vjf.com", - "xn--yetherwallet-dfk.com", - "xn--yetherwallet-1t1f.com", - "xn--yetherwallet-634f.com", - "xn--myeherwallet-fpc.com", - "xn--myethewallt-crb.com", - "xn--metherwallet-1vc.com", - "xn--myeherwallt-kbb8039g.com", - "xn--myeherwallet-vk5f.com", - "xn--yethewallet-iw8ejl.com", - "xn--bittrx-th8b.com", - "xn--polniex-n0a.com", - "thekey.vin", - "thekey-vip.com", - "digitexftures.com", - "ethzero-wallet.org", - "zeepln.io", - "wepowers.network", - "wepower.vision" - ] -} diff --git a/test/unit/actions/save_account_label_test.js b/test/unit/actions/save_account_label_test.js deleted file mode 100644 index c5ffd6cbf..000000000 --- a/test/unit/actions/save_account_label_test.js +++ /dev/null @@ -1,35 +0,0 @@ -// var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') - -var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) - -describe('SAVE_ACCOUNT_LABEL', function () { - it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () { - var initialState = { - metamask: { - identities: { - foo: { - name: 'bar', - }, - }, - }, - } - freeze(initialState) - - const action = { - type: actions.SAVE_ACCOUNT_LABEL, - value: { - account: 'foo', - label: 'baz', - }, - } - freeze(action) - - var resultingState = reducers(initialState, action) - assert.equal(resultingState.metamask.identities.foo.name, action.value.label) - }) -}) - diff --git a/test/unit/actions/set_account_label_test.js b/test/unit/actions/set_account_label_test.js new file mode 100644 index 000000000..53ea1d130 --- /dev/null +++ b/test/unit/actions/set_account_label_test.js @@ -0,0 +1,34 @@ +const assert = require('assert') +const freeze = require('deep-freeze-strict') +const path = require('path') + +const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'actions.js')) +const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'reducers.js')) + +describe('SET_ACCOUNT_LABEL', function () { + it('updates the state.metamask.identities[:i].name property of the state to the action.value.label', function () { + const initialState = { + metamask: { + identities: { + foo: { + name: 'bar', + }, + }, + }, + } + freeze(initialState) + + const action = { + type: actions.SET_ACCOUNT_LABEL, + value: { + account: 'foo', + label: 'baz', + }, + } + freeze(action) + + const resultingState = reducers(initialState, action) + assert.equal(resultingState.metamask.identities.foo.name, action.value.label) + }) +}) + diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index b6a691860..c110f71fc 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -9,7 +9,7 @@ var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'redu describe('tx confirmation screen', function () { beforeEach(function () { - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { diff --git a/test/unit/ComposableObservableStore.js b/test/unit/app/ComposableObservableStore.js index 3fba200c1..aa8abd463 100644 --- a/test/unit/ComposableObservableStore.js +++ b/test/unit/app/ComposableObservableStore.js @@ -1,5 +1,5 @@ const assert = require('assert') -const ComposableObservableStore = require('../../app/scripts/lib/ComposableObservableStore') +const ComposableObservableStore = require('../../../app/scripts/lib/ComposableObservableStore') const ObservableStore = require('obs-store') describe('ComposableObservableStore', () => { diff --git a/test/unit/app/account-import-strategies.spec.js b/test/unit/app/account-import-strategies.spec.js new file mode 100644 index 000000000..83cfaeb3e --- /dev/null +++ b/test/unit/app/account-import-strategies.spec.js @@ -0,0 +1,31 @@ +const assert = require('assert') +const path = require('path') +const accountImporter = require('../../../app/scripts/account-import-strategies/index') +const ethUtil = require('ethereumjs-util') + +describe('Account Import Strategies', function () { + const privkey = '0x4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' + const json = '{"version":3,"id":"dbb54385-0a99-437f-83c0-647de9f244c3","address":"a7f92ce3fba24196cf6f4bd2e1eb3db282ba998c","Crypto":{"ciphertext":"bde13d9ade5c82df80281ca363320ce254a8a3a06535bbf6ffdeaf0726b1312c","cipherparams":{"iv":"fbf93718a57f26051b292f072f2e5b41"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"7ffe00488319dec48e4c49a120ca49c6afbde9272854c64d9541c83fc6acdffe","n":8192,"r":8,"p":1},"mac":"2adfd9c4bc1cdac4c85bddfb31d9e21a684e0e050247a70c5698facf6b7d4681"}}' + + it('imports a private key and strips 0x prefix', async function () { + const importPrivKey = await accountImporter.importAccount('Private Key', [ privkey ]) + assert.equal(importPrivKey, ethUtil.stripHexPrefix(privkey)) + }) + + it('fails when password is incorrect for keystore', async function () { + const wrongPassword = 'password2' + + try { + await accountImporter.importAccount('JSON File', [ json, wrongPassword]) + } catch (error) { + assert.equal(error.message, 'Key derivation failed - possibly wrong passphrase') + } + }) + + it('imports json string and password to return a private key', async function () { + const fileContentsPassword = 'password1' + const importJson = await accountImporter.importAccount('JSON File', [ json, fileContentsPassword]) + assert.equal(importJson, '0x5733876abe94146069ce8bcbabbde2677f2e35fa33e875e92041ed2ac87e5bc7') + }) + +}) diff --git a/test/unit/app/buy-eth-url.spec.js b/test/unit/app/buy-eth-url.spec.js new file mode 100644 index 000000000..36646fa68 --- /dev/null +++ b/test/unit/app/buy-eth-url.spec.js @@ -0,0 +1,48 @@ +const assert = require('assert') +const getBuyEthUrl = require('../../../app/scripts/lib/buy-eth-url') + +describe('', function () { + const mainnet = { + network: '1', + amount: 5, + address: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + } + const ropsten = { + network: '3', + } + const rinkeby = { + network: '4', + } + const kovan = { + network: '42', + } + + it('returns coinbase url with amount and address for network 1', function () { + const coinbaseUrl = getBuyEthUrl(mainnet) + const coinbase = coinbaseUrl.match(/(https:\/\/buy.coinbase.com)/) + const amount = coinbaseUrl.match(/(amount)\D\d/) + const address = coinbaseUrl.match(/(address)(.*)(?=&)/) + + assert.equal(coinbase[0], 'https://buy.coinbase.com') + assert.equal(amount[0], 'amount=5') + assert.equal(address[0], 'address=0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') + + }) + + it('returns metamask ropsten faucet for network 3', function () { + const ropstenUrl = getBuyEthUrl(ropsten) + assert.equal(ropstenUrl, 'https://faucet.metamask.io/') + }) + + it('returns rinkeby dapp for network 4', function () { + const rinkebyUrl = getBuyEthUrl(rinkeby) + assert.equal(rinkebyUrl, 'https://www.rinkeby.io/') + }) + + it('returns kovan github test faucet for network 42', function () { + const kovanUrl = getBuyEthUrl(kovan) + assert.equal(kovanUrl, 'https://github.com/kovan-testnet/faucet') + }) + +}) + diff --git a/test/unit/address-book-controller.js b/test/unit/app/controllers/address-book-controller.js index 655c9022c..dc4b8e3ff 100644 --- a/test/unit/address-book-controller.js +++ b/test/unit/app/controllers/address-book-controller.js @@ -1,26 +1,26 @@ const assert = require('assert') -const AddressBookController = require('../../app/scripts/controllers/address-book') +const AddressBookController = require('../../../../app/scripts/controllers/address-book') -const mockKeyringController = { - memStore: { - getState: function () { - return { - identities: { - '0x0aaa': { - address: '0x0aaa', - name: 'owned', - }, +const stubPreferencesStore = { + getState: function () { + return { + identities: { + '0x0aaa': { + address: '0x0aaa', + name: 'owned', }, - } - }, + }, + } }, -} +}; describe('address-book-controller', function () { var addressBookController beforeEach(function () { - addressBookController = new AddressBookController({}, mockKeyringController) + addressBookController = new AddressBookController({ + preferencesStore: stubPreferencesStore, + }) }) describe('addres book management', function () { diff --git a/test/unit/blacklist-controller-test.js b/test/unit/app/controllers/blacklist-controller-test.js index cbf73d3e5..085641777 100644 --- a/test/unit/blacklist-controller-test.js +++ b/test/unit/app/controllers/blacklist-controller-test.js @@ -1,5 +1,5 @@ const assert = require('assert') -const BlacklistController = require('../../app/scripts/controllers/blacklist') +const BlacklistController = require('../../../../app/scripts/controllers/blacklist') describe('blacklist controller', function () { let blacklistController diff --git a/test/unit/currency-controller-test.js b/test/unit/app/controllers/currency-controller-test.js index 63ab60f9e..1941d1c43 100644 --- a/test/unit/currency-controller-test.js +++ b/test/unit/app/controllers/currency-controller-test.js @@ -3,7 +3,7 @@ global.fetch = global.fetch || require('isomorphic-fetch') const assert = require('assert') const nock = require('nock') -const CurrencyController = require('../../app/scripts/controllers/currency') +const CurrencyController = require('../../../../app/scripts/controllers/currency') describe('currency-controller', function () { var currencyController @@ -45,7 +45,6 @@ describe('currency-controller', function () { currencyController.updateConversionRate() .then(function () { var result = currencyController.getConversionRate() - console.log('currencyController.getConversionRate:', result) assert.equal(typeof result, 'number') done() }).catch(function (err) { diff --git a/test/unit/infura-controller-test.js b/test/unit/app/controllers/infura-controller-test.js index 605305efa..7bd95dd4b 100644 --- a/test/unit/infura-controller-test.js +++ b/test/unit/app/controllers/infura-controller-test.js @@ -1,6 +1,6 @@ const assert = require('assert') const sinon = require('sinon') -const InfuraController = require('../../app/scripts/controllers/infura') +const InfuraController = require('../../../../app/scripts/controllers/infura') describe('infura-controller', function () { let infuraController, sandbox, networkStatus @@ -8,7 +8,7 @@ describe('infura-controller', function () { before(async function () { infuraController = new InfuraController() - sandbox = sinon.sandbox.create() + sandbox = sinon.createSandbox() sinon.stub(infuraController, 'checkInfuraNetworkStatus').resolves(response) networkStatus = await infuraController.checkInfuraNetworkStatus() }) diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js new file mode 100644 index 000000000..4bc16e65e --- /dev/null +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -0,0 +1,550 @@ +const assert = require('assert') +const sinon = require('sinon') +const clone = require('clone') +const nock = require('nock') +const createThoughStream = require('through2').obj +const MetaMaskController = require('../../../../app/scripts/metamask-controller') +const blacklistJSON = require('eth-phishing-detect/src/config') +const firstTimeState = require('../../../../app/scripts/first-time-state') + +const currentNetworkId = 42 +const DEFAULT_LABEL = 'Account 1' +const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' +const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' +const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' +const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' + +describe('MetaMaskController', function () { + let metamaskController + const sandbox = sinon.createSandbox() + const noop = () => {} + + beforeEach(function () { + + nock('https://api.infura.io') + .persist() + .get('/v2/blacklist') + .reply(200, blacklistJSON) + + nock('https://api.infura.io') + .get('/v1/ticker/ethusd') + .reply(200, '{"base": "ETH", "quote": "USD", "bid": 288.45, "ask": 288.46, "volume": 112888.17569277, "exchange": "bitfinex", "total_volume": 272175.00106721005, "num_exchanges": 8, "timestamp": 1506444677}') + + nock('https://api.infura.io') + .get('/v1/ticker/ethjpy') + .reply(200, '{"base": "ETH", "quote": "JPY", "bid": 32300.0, "ask": 32400.0, "volume": 247.4616071, "exchange": "kraken", "total_volume": 247.4616071, "num_exchanges": 1, "timestamp": 1506444676}') + + nock('https://api.infura.io') + .persist() + .get(/.*/) + .reply(200) + + metamaskController = new MetaMaskController({ + showUnapprovedTx: noop, + showUnconfirmedMessage: noop, + encryptor: { + encrypt: function (password, object) { + this.object = object + return Promise.resolve() + }, + decrypt: function () { + return Promise.resolve(this.object) + }, + }, + initState: clone(firstTimeState), + }) + sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') + sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore') + }) + + afterEach(function () { + nock.cleanAll() + sandbox.restore() + }) + + describe('#getGasPrice', function () { + + it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () { + const realRecentBlocksController = metamaskController.recentBlocksController + metamaskController.recentBlocksController = { + store: { + getState: () => { + return { + recentBlocks: [ + { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, + { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, + { gasPrices: [ '0x174876e800', '0x174876e800' ]}, + { gasPrices: [ '0x174876e800', '0x174876e800' ]}, + ], + } + }, + }, + } + + const gasPrice = metamaskController.getGasPrice() + assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price') + + metamaskController.recentBlocksController = realRecentBlocksController + }) + }) + + describe('#createNewVaultAndKeychain', function () { + it('can only create new vault on keyringController once', async function () { + const selectStub = sandbox.stub(metamaskController, 'selectFirstIdentity') + + const password = 'a-fake-password' + + await metamaskController.createNewVaultAndKeychain(password) + await metamaskController.createNewVaultAndKeychain(password) + + assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce) + + selectStub.reset() + }) + }) + + describe('#createNewVaultAndRestore', function () { + it('should be able to call newVaultAndRestore despite a mistake.', async function () { + const password = 'what-what-what' + await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null) + await metamaskController.createNewVaultAndRestore(password, TEST_SEED) + + assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice) + }) + + it('should clear previous identities after vault restoration', async () => { + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) + assert.deepEqual(metamaskController.getState().identities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, + }) + + await metamaskController.preferencesController.setAccountLabel(TEST_ADDRESS, 'Account Foo') + assert.deepEqual(metamaskController.getState().identities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, + }) + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + assert.deepEqual(metamaskController.getState().identities, { + [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL }, + }) + }) + }) + + describe('#getApi', function () { + let getApi, state + + beforeEach(function () { + getApi = metamaskController.getApi() + }) + + it('getState', function (done) { + getApi.getState((err, res) => { + if (err) { + done(err) + } else { + state = res + } + }) + assert.deepEqual(state, metamaskController.getState()) + done() + }) + + }) + + describe('preferencesController', function () { + + it('defaults useBlockie to false', function () { + assert.equal(metamaskController.preferencesController.store.getState().useBlockie, false) + }) + + it('setUseBlockie to true', function () { + metamaskController.setUseBlockie(true, noop) + assert.equal(metamaskController.preferencesController.store.getState().useBlockie, true) + }) + + }) + + describe('#selectFirstIdentity', function () { + let identities, address + + beforeEach(function () { + address = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' + identities = { + '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { + 'address': address, + 'name': 'Account 1', + }, + '0xc42edfcc21ed14dda456aa0756c153f7985d8813': { + 'address': '0xc42edfcc21ed14dda456aa0756c153f7985d8813', + 'name': 'Account 2', + }, + } + metamaskController.preferencesController.store.updateState({ identities }) + metamaskController.selectFirstIdentity() + }) + + it('changes preferences controller select address', function () { + const preferenceControllerState = metamaskController.preferencesController.store.getState() + assert.equal(preferenceControllerState.selectedAddress, address) + }) + + it('changes metamask controller selected address', function () { + const metamaskState = metamaskController.getState() + assert.equal(metamaskState.selectedAddress, address) + }) + }) + + describe('#setCustomRpc', function () { + const customRPC = 'https://custom.rpc/' + let rpcTarget + + beforeEach(function () { + + nock('https://custom.rpc') + .post('/') + .reply(200) + + rpcTarget = metamaskController.setCustomRpc(customRPC) + }) + + afterEach(function () { + nock.cleanAll() + }) + + it('returns custom RPC that when called', async function () { + assert.equal(await rpcTarget, customRPC) + }) + + it('changes the network controller rpc', function () { + const networkControllerState = metamaskController.networkController.store.getState() + assert.equal(networkControllerState.provider.rpcTarget, customRPC) + }) + }) + + describe('#setCurrentCurrency', function () { + let defaultMetaMaskCurrency + + beforeEach(function () { + defaultMetaMaskCurrency = metamaskController.currencyController.getCurrentCurrency() + }) + + it('defaults to usd', function () { + assert.equal(defaultMetaMaskCurrency, 'usd') + }) + + it('sets currency to JPY', function () { + metamaskController.setCurrentCurrency('JPY', noop) + assert.equal(metamaskController.currencyController.getCurrentCurrency(), 'JPY') + }) + }) + + describe('#createShapeshifttx', function () { + let depositAddress, depositType, shapeShiftTxList + + beforeEach(function () { + nock('https://shapeshift.io') + .get('/txStat/3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc') + .reply(200, '{"status": "no_deposits", "address": "3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc"}') + + depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc' + depositType = 'ETH' + shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList + }) + + it('creates a shapeshift tx', async function () { + metamaskController.createShapeShiftTx(depositAddress, depositType) + assert.equal(shapeShiftTxList[0].depositAddress, depositAddress) + }) + + }) + + describe('#addNewAccount', function () { + let addNewAccount + + beforeEach(function () { + addNewAccount = metamaskController.addNewAccount() + }) + + it('errors when an primary keyring is does not exist', async function () { + try { + await addNewAccount + assert.equal(1 === 0) + } catch (e) { + assert.equal(e.message, 'MetamaskController - No HD Key Tree found') + } + }) + }) + + describe('#verifyseedPhrase', function () { + let seedPhrase, getConfigSeed + + it('errors when no keying is provided', async function () { + try { + await metamaskController.verifySeedPhrase() + } catch (error) { + assert.equal(error.message, 'MetamaskController - No HD Key Tree found') + } + }) + + beforeEach(async function () { + await metamaskController.createNewVaultAndKeychain('password') + seedPhrase = await metamaskController.verifySeedPhrase() + }) + + it('#placeSeedWords should match the initially created vault seed', function () { + + metamaskController.placeSeedWords((err, result) => { + if (err) { + console.log(err) + } else { + getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(result, seedPhrase) + assert.equal(result, getConfigSeed) + } + }) + assert.equal(getConfigSeed, undefined) + }) + + it('#addNewAccount', async function () { + await metamaskController.addNewAccount() + const getAccounts = await metamaskController.keyringController.getAccounts() + assert.equal(getAccounts.length, 2) + }) + }) + + describe('#resetAccount', function () { + + beforeEach(function () { + const selectedAddressStub = sinon.stub(metamaskController.preferencesController, 'getSelectedAddress') + const getNetworkstub = sinon.stub(metamaskController.txController.txStateManager, 'getNetwork') + + selectedAddressStub.returns('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') + getNetworkstub.returns(42) + + metamaskController.txController.txStateManager._saveTxList([ + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }, + { id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }, + ]) + }) + + it('wipes transactions from only the correct network id and with the selected address', async function () { + await metamaskController.resetAccount() + assert.equal(metamaskController.txController.txStateManager.getTx(1), undefined) + }) + }) + + describe('#clearSeedWordCache', function () { + + it('should have set seed words', function () { + metamaskController.configManager.setSeedWords('test words') + const getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(getConfigSeed, 'test words') + }) + + it('should clear config seed phrase', function () { + metamaskController.configManager.setSeedWords('test words') + metamaskController.clearSeedWordCache((err, result) => { + if (err) console.log(err) + }) + const getConfigSeed = metamaskController.configManager.getSeedWords() + assert.equal(getConfigSeed, null) + }) + + }) + + describe('#setCurrentLocale', function () { + + it('checks the default currentLocale', function () { + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + assert.equal(preferenceCurrentLocale, undefined) + }) + + it('sets current locale in preferences controller', function () { + metamaskController.setCurrentLocale('ja', noop) + const preferenceCurrentLocale = metamaskController.preferencesController.store.getState().currentLocale + assert.equal(preferenceCurrentLocale, 'ja') + }) + + }) + + describe('#newUnsignedMessage', function () { + + let msgParams, metamaskMsgs, messages, msgId + + const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' + const data = '0x43727970746f6b697474696573' + + beforeEach(async function () { + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + + msgParams = { + 'from': address, + 'data': data, + } + + metamaskController.newUnsignedMessage(msgParams, noop) + metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() + messages = metamaskController.messageManager.messages + msgId = Object.keys(metamaskMsgs)[0] + messages[0].msgParams.metamaskId = parseInt(msgId) + }) + + it('persists address from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.from, address) + }) + + it('persists data from msg params', function () { + assert.equal(metamaskMsgs[msgId].msgParams.data, data) + }) + + it('sets the status to unapproved', function () { + assert.equal(metamaskMsgs[msgId].status, 'unapproved') + }) + + it('sets the type to eth_sign', function () { + assert.equal(metamaskMsgs[msgId].type, 'eth_sign') + }) + + it('rejects the message', function () { + const msgIdInt = parseInt(msgId) + metamaskController.cancelMessage(msgIdInt, noop) + assert.equal(messages[0].status, 'rejected') + }) + + it('errors when signing a message', async function () { + try { + await metamaskController.signMessage(messages[0].msgParams) + } catch (error) { + assert.equal(error.message, 'message length is invalid') + } + }) + }) + + describe('#newUnsignedPersonalMessage', function () { + + it('errors with no from in msgParams', function () { + const msgParams = { + 'data': data, + } + metamaskController.newUnsignedPersonalMessage(msgParams, function (error) { + assert.equal(error.message, 'MetaMask Message Signature: from field is required.') + }) + }) + + let msgParams, metamaskPersonalMsgs, personalMessages, msgId + + const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' + const data = '0x43727970746f6b697474696573' + + beforeEach(async function () { + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) + + msgParams = { + 'from': address, + 'data': data, + } + + metamaskController.newUnsignedPersonalMessage(msgParams, noop) + metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() + personalMessages = metamaskController.personalMessageManager.messages + msgId = Object.keys(metamaskPersonalMsgs)[0] + personalMessages[0].msgParams.metamaskId = parseInt(msgId) + }) + + it('persists address from msg params', function () { + assert.equal(metamaskPersonalMsgs[msgId].msgParams.from, address) + }) + + it('persists data from msg params', function () { + assert.equal(metamaskPersonalMsgs[msgId].msgParams.data, data) + }) + + it('sets the status to unapproved', function () { + assert.equal(metamaskPersonalMsgs[msgId].status, 'unapproved') + }) + + it('sets the type to personal_sign', function () { + assert.equal(metamaskPersonalMsgs[msgId].type, 'personal_sign') + }) + + it('rejects the message', function () { + const msgIdInt = parseInt(msgId) + metamaskController.cancelPersonalMessage(msgIdInt, noop) + assert.equal(personalMessages[0].status, 'rejected') + }) + + it('errors when signing a message', async function () { + await metamaskController.signPersonalMessage(personalMessages[0].msgParams) + assert.equal(metamaskPersonalMsgs[msgId].status, 'signed') + assert.equal(metamaskPersonalMsgs[msgId].rawSig, '0x6a1b65e2b8ed53cf398a769fad24738f9fbe29841fe6854e226953542c4b6a173473cb152b6b1ae5f06d601d45dd699a129b0a8ca84e78b423031db5baa734741b') + }) + }) + + describe('#setupUntrustedCommunication', function () { + let streamTest + + const phishingUrl = 'decentral.market' + + afterEach(function () { + streamTest.end() + }) + + it('sets up phishing stream for untrusted communication ', async function () { + await metamaskController.blacklistController.updatePhishingList() + + streamTest = createThoughStream((chunk, enc, cb) => { + assert.equal(chunk.name, 'phishing') + assert.equal(chunk.data.hostname, phishingUrl) + cb() + }) + // console.log(streamTest) + metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + }) + }) + + describe('#setupTrustedCommunication', function () { + let streamTest + + afterEach(function () { + streamTest.end() + }) + + it('sets up controller dnode api for trusted communication', function (done) { + streamTest = createThoughStream((chunk, enc, cb) => { + assert.equal(chunk.name, 'controller') + cb() + done() + }) + + metamaskController.setupTrustedCommunication(streamTest, 'mycrypto.com') + }) + }) + + describe('#markAccountsFound', function () { + it('adds lost accounts to config manager data', function () { + metamaskController.markAccountsFound(noop) + const configManagerData = metamaskController.configManager.getData() + assert.deepEqual(configManagerData.lostAccounts, []) + }) + }) + + describe('#markPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to true', function () { + metamaskController.markPasswordForgotten(noop) + const configManagerData = metamaskController.configManager.getData() + assert.equal(configManagerData.forgottenPassword, true) + }) + }) + + describe('#unMarkPasswordForgotten', function () { + it('adds and sets forgottenPassword to config data to false', function () { + metamaskController.unMarkPasswordForgotten(noop) + const configManagerData = metamaskController.configManager.getData() + assert.equal(configManagerData.forgottenPassword, false) + }) + }) + +}) diff --git a/test/unit/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js index 2b905718b..789850ef3 100644 --- a/test/unit/network-contoller-test.js +++ b/test/unit/app/controllers/network-contoller-test.js @@ -1,19 +1,17 @@ const assert = require('assert') const nock = require('nock') -const NetworkController = require('../../app/scripts/controllers/network') +const NetworkController = require('../../../../app/scripts/controllers/network') const { getNetworkDisplayName, - getNetworkEndpoints, -} = require('../../app/scripts/controllers/network/util') +} = require('../../../../app/scripts/controllers/network/util') -const { createTestProviderTools } = require('../stub/provider') +const { createTestProviderTools } = require('../../../stub/provider') const providerResultStub = {} -const provider = createTestProviderTools({ scaffold: providerResultStub }).provider describe('# Network Controller', function () { let networkController const noop = () => {} - const networkControllerProviderInit = { + const networkControllerProviderConfig = { getAccounts: noop, } @@ -24,11 +22,9 @@ describe('# Network Controller', function () { .post('/metamask') .reply(200) - networkController = new NetworkController({ - provider, - }) + networkController = new NetworkController() - networkController.initializeProvider(networkControllerProviderInit, provider) + networkController.initializeProvider(networkControllerProviderConfig) }) afterEach(function () { @@ -38,7 +34,7 @@ describe('# Network Controller', function () { describe('network', function () { describe('#provider', function () { it('provider should be updatable without reassignment', function () { - networkController.initializeProvider(networkControllerProviderInit, provider) + networkController.initializeProvider(networkControllerProviderConfig) const proxy = networkController._proxy proxy.setTarget({ test: true, on: () => {} }) assert.ok(proxy.test) @@ -59,12 +55,6 @@ describe('# Network Controller', function () { }) }) - describe('#getRpcAddressForType', function () { - it('should return the right rpc address', function () { - const rpcTarget = networkController.getRpcAddressForType('mainnet') - assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') - }) - }) describe('#setProviderType', function () { it('should update provider.type', function () { networkController.setProviderType('mainnet') @@ -76,16 +66,11 @@ describe('# Network Controller', function () { const loading = networkController.isNetworkLoading() assert.ok(loading, 'network is loading') }) - it('should set the right rpcTarget', function () { - networkController.setProviderType('mainnet') - const rpcTarget = networkController.getProviderConfig().rpcTarget - assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') - }) }) }) }) -describe('# Network utils', () => { +describe('Network utils', () => { it('getNetworkDisplayName should return the correct network name', () => { const tests = [ { @@ -114,9 +99,4 @@ describe('# Network utils', () => { tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected)) }) - - it('getNetworkEndpoints should return the correct endpoints', () => { - assert.equal(getNetworkEndpoints('networkBeta').ropsten, 'https://ropsten.infura.io/metamask2') - assert.equal(getNetworkEndpoints('network').rinkeby, 'https://rinkeby.infura.io/metamask') - }) }) diff --git a/test/unit/notice-controller-test.js b/test/unit/app/controllers/notice-controller-test.js index 09eeda15c..e78b69623 100644 --- a/test/unit/notice-controller-test.js +++ b/test/unit/app/controllers/notice-controller-test.js @@ -1,6 +1,6 @@ const assert = require('assert') -const configManagerGen = require('../lib/mock-config-manager') -const NoticeController = require('../../app/scripts/notice-controller') +const configManagerGen = require('../../../lib/mock-config-manager') +const NoticeController = require('../../../../app/scripts/notice-controller') describe('notice-controller', function () { var noticeController diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js new file mode 100644 index 000000000..e5e751b57 --- /dev/null +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -0,0 +1,162 @@ +const assert = require('assert') +const PreferencesController = require('../../../../app/scripts/controllers/preferences') + +describe('preferences controller', function () { + let preferencesController + + beforeEach(() => { + preferencesController = new PreferencesController() + }) + + describe('setAddresses', function () { + it('should keep a map of addresses to names and addresses in the store', function () { + preferencesController.setAddresses([ + '0xda22le', + '0x7e57e2', + ]) + + const {identities} = preferencesController.store.getState() + assert.deepEqual(identities, { + '0xda22le': { + name: 'Account 1', + address: '0xda22le', + }, + '0x7e57e2': { + name: 'Account 2', + address: '0x7e57e2', + }, + }) + }) + + it('should replace its list of addresses', function () { + preferencesController.setAddresses([ + '0xda22le', + '0x7e57e2', + ]) + preferencesController.setAddresses([ + '0xda22le77', + '0x7e57e277', + ]) + + const {identities} = preferencesController.store.getState() + assert.deepEqual(identities, { + '0xda22le77': { + name: 'Account 1', + address: '0xda22le77', + }, + '0x7e57e277': { + name: 'Account 2', + address: '0x7e57e277', + }, + }) + }) + }) + + describe('setAccountLabel', function () { + it('should update a label for the given account', function () { + preferencesController.setAddresses([ + '0xda22le', + '0x7e57e2', + ]) + + assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], { + name: 'Account 1', + address: '0xda22le', + }) + + + preferencesController.setAccountLabel('0xda22le', 'Dazzle') + assert.deepEqual(preferencesController.store.getState().identities['0xda22le'], { + name: 'Dazzle', + address: '0xda22le', + }) + }) + }) + + describe('getTokens', function () { + it('should return an empty list initially', async function () { + await preferencesController.setSelectedAddress('0x7e57e2') + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 0, 'empty list of tokens') + }) + }) + + describe('addToken', function () { + it('should add that token to its state', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken(address, symbol, decimals) + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 1, 'one token added') + + const added = tokens[0] + assert.equal(added.address, address, 'set address correctly') + assert.equal(added.symbol, symbol, 'set symbol correctly') + assert.equal(added.decimals, decimals, 'set decimals correctly') + }) + + it('should allow updating a token value', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken(address, symbol, decimals) + + const newDecimals = 6 + await preferencesController.addToken(address, symbol, newDecimals) + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 1, 'one token added') + + const added = tokens[0] + assert.equal(added.address, address, 'set address correctly') + assert.equal(added.symbol, symbol, 'set symbol correctly') + assert.equal(added.decimals, newDecimals, 'updated decimals correctly') + }) + + it('should allow adding tokens to two separate addresses', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken(address, symbol, decimals) + assert.equal(preferencesController.getTokens().length, 1, 'one token added for 1st address') + + await preferencesController.setSelectedAddress('0xda22le') + await preferencesController.addToken(address, symbol, decimals) + assert.equal(preferencesController.getTokens().length, 1, 'one token added for 2nd address') + }) + }) + + describe('removeToken', function () { + it('should remove the only token from its state', async function () { + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken('0xa', 'A', 5) + await preferencesController.removeToken('0xa') + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 0, 'one token removed') + }) + + it('should remove a token from its state', async function () { + await preferencesController.setSelectedAddress('0x7e57e2') + await preferencesController.addToken('0xa', 'A', 4) + await preferencesController.addToken('0xb', 'B', 5) + await preferencesController.removeToken('0xa') + + const tokens = preferencesController.getTokens() + assert.equal(tokens.length, 1, 'one token removed') + + const [token1] = tokens + assert.deepEqual(token1, {address: '0xb', symbol: 'B', decimals: 5}) + }) + }) +}) + diff --git a/test/unit/token-rates-controller.js b/test/unit/app/controllers/token-rates-controller.js index a49547313..28e583d8d 100644 --- a/test/unit/token-rates-controller.js +++ b/test/unit/app/controllers/token-rates-controller.js @@ -1,6 +1,6 @@ const assert = require('assert') const sinon = require('sinon') -const TokenRatesController = require('../../app/scripts/controllers/token-rates') +const TokenRatesController = require('../../../../app/scripts/controllers/token-rates') const ObservableStore = require('obs-store') describe('TokenRatesController', () => { diff --git a/test/unit/nonce-tracker-test.js b/test/unit/app/controllers/transactions/nonce-tracker-test.js index cf26945d3..fc852458c 100644 --- a/test/unit/nonce-tracker-test.js +++ b/test/unit/app/controllers/transactions/nonce-tracker-test.js @@ -1,6 +1,6 @@ const assert = require('assert') -const NonceTracker = require('../../app/scripts/controllers/transactions/nonce-tracker') -const MockTxGen = require('../lib/mock-tx-gen') +const NonceTracker = require('../../../../../app/scripts/controllers/transactions/nonce-tracker') +const MockTxGen = require('../../../../lib/mock-tx-gen') let providerResultStub = {} describe('Nonce Tracker', function () { diff --git a/test/unit/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js index 001b86dd1..e7705c594 100644 --- a/test/unit/pending-tx-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -3,9 +3,9 @@ const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') const clone = require('clone') -const { createTestProviderTools } = require('../stub/provider') -const PendingTransactionTracker = require('../../app/scripts/controllers/transactions/pending-tx-tracker') -const MockTxGen = require('../lib/mock-tx-gen') +const { createTestProviderTools } = require('../../../../stub/provider') +const PendingTransactionTracker = require('../../../../../app/scripts/controllers/transactions/pending-tx-tracker') +const MockTxGen = require('../../../../lib/mock-tx-gen') const sinon = require('sinon') const noop = () => true const currentNetworkId = 42 @@ -294,7 +294,7 @@ describe('PendingTransactionTracker', function () { }) afterEach(() => { - pendingTxTracker.publishTransaction.reset() + pendingTxTracker.publishTransaction.restore() }) it('should publish the transaction', function (done) { diff --git a/test/unit/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 0b5c7226a..1f32a0f37 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -4,9 +4,9 @@ const EthTx = require('ethereumjs-tx') const EthjsQuery = require('ethjs-query') const ObservableStore = require('obs-store') const sinon = require('sinon') -const TransactionController = require('../../app/scripts/controllers/transactions') -const TxGasUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils') -const { createTestProviderTools, getTestAccounts } = require('../stub/provider') +const TransactionController = require('../../../../../app/scripts/controllers/transactions') +const TxGasUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils') +const { createTestProviderTools, getTestAccounts } = require('../../../../stub/provider') const noop = () => true const currentNetworkId = 42 diff --git a/test/unit/tx-gas-util-test.js b/test/unit/app/controllers/transactions/tx-gas-util-test.js index c1d5966da..d1ee86033 100644 --- a/test/unit/tx-gas-util-test.js +++ b/test/unit/app/controllers/transactions/tx-gas-util-test.js @@ -3,8 +3,8 @@ const Transaction = require('ethereumjs-tx') const BN = require('bn.js') -const { hexToBn, bnToHex } = require('../../app/scripts/lib/util') -const TxUtils = require('../../app/scripts/controllers/transactions/tx-gas-utils') +const { hexToBn, bnToHex } = require('../../../../../app/scripts/lib/util') +const TxUtils = require('../../../../../app/scripts/controllers/transactions/tx-gas-utils') describe('txUtils', function () { diff --git a/test/unit/tx-helper-test.js b/test/unit/app/controllers/transactions/tx-helper-test.js index cc6543c30..ce54ef483 100644 --- a/test/unit/tx-helper-test.js +++ b/test/unit/app/controllers/transactions/tx-helper-test.js @@ -1,5 +1,5 @@ const assert = require('assert') -const txHelper = require('../../ui/lib/tx-helper') +const txHelper = require('../../../../../ui/lib/tx-helper') describe('txHelper', function () { it('always shows the oldest tx first', function () { diff --git a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js new file mode 100644 index 000000000..f4c3a6be1 --- /dev/null +++ b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js @@ -0,0 +1,129 @@ +const assert = require('assert') +const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper') +const testVault = require('../../../../data/v17-long-history.json') + +describe ('Transaction state history helper', function () { + + describe('#snapshotFromTxMeta', function () { + it('should clone deep', function () { + const input = { + foo: { + bar: { + bam: 'baz' + } + } + } + const output = txStateHistoryHelper.snapshotFromTxMeta(input) + assert('foo' in output, 'has a foo key') + assert('bar' in output.foo, 'has a bar key') + assert('bam' in output.foo.bar, 'has a bar key') + assert.equal(output.foo.bar.bam, 'baz', 'has a baz value') + }) + + it('should remove the history key', function () { + const input = { foo: 'bar', history: 'remembered' } + const output = txStateHistoryHelper.snapshotFromTxMeta(input) + assert(typeof output.history, 'undefined', 'should remove history') + }) + }) + + describe('#migrateFromSnapshotsToDiffs', function () { + it('migrates history to diffs and can recover original values', function () { + testVault.data.TransactionController.transactions.forEach((tx, index) => { + const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history) + newHistory.forEach((newEntry, index) => { + if (index === 0) { + assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj') + } else { + assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj') + } + const oldEntry = tx.history[index] + const historySubset = newHistory.slice(0, index + 1) + const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset) + assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs') + }) + }) + }) + }) + + describe('#replayHistory', function () { + it('replaying history does not mutate the original obj', function () { + const initialState = { test: true, message: 'hello', value: 1 } + const diff1 = [{ + "op": "replace", + "path": "/message", + "value": "haay", + }] + const diff2 = [{ + "op": "replace", + "path": "/value", + "value": 2, + }] + const history = [initialState, diff1, diff2] + + const beforeStateSnapshot = JSON.stringify(initialState) + const latestState = txStateHistoryHelper.replayHistory(history) + const afterStateSnapshot = JSON.stringify(initialState) + + assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state') + assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run') + }) + }) + + describe('#generateHistoryEntry', function () { + + function generateHistoryEntryTest(note) { + + const prevState = { + someValue: 'value 1', + foo: { + bar: { + bam: 'baz' + } + } + } + + const nextState = { + newPropRoot: 'new property - root', + someValue: 'value 2', + foo: { + newPropFirstLevel: 'new property - first level', + bar: { + bam: 'baz' + } + } + } + + const before = new Date().getTime() + const result = txStateHistoryHelper.generateHistoryEntry(prevState, nextState, note) + const after = new Date().getTime() + + assert.ok(Array.isArray(result)) + assert.equal(result.length, 3) + + const expectedEntry1 = { op: 'add', path: '/foo/newPropFirstLevel', value: 'new property - first level' } + assert.equal(result[0].op, expectedEntry1.op) + assert.equal(result[0].path, expectedEntry1.path) + assert.equal(result[0].value, expectedEntry1.value) + assert.equal(result[0].value, expectedEntry1.value) + if (note) + assert.equal(result[0].note, note) + + assert.ok(result[0].timestamp >= before && result[0].timestamp <= after) + + const expectedEntry2 = { op: 'replace', path: '/someValue', value: 'value 2' } + assert.deepEqual(result[1], expectedEntry2) + + const expectedEntry3 = { op: 'add', path: '/newPropRoot', value: 'new property - root' } + assert.deepEqual(result[2], expectedEntry3) + } + + it('should generate history entries', function () { + generateHistoryEntryTest() + }) + + it('should add note to first entry', function () { + generateHistoryEntryTest('custom note') + }) + }) +})
\ No newline at end of file diff --git a/test/unit/tx-state-manager-test.js b/test/unit/app/controllers/transactions/tx-state-manager-test.js index e5fe68d0b..20bc08b94 100644 --- a/test/unit/tx-state-manager-test.js +++ b/test/unit/app/controllers/transactions/tx-state-manager-test.js @@ -1,8 +1,8 @@ const assert = require('assert') const clone = require('clone') const ObservableStore = require('obs-store') -const TxStateManager = require('../../app/scripts/controllers/transactions/tx-state-manager') -const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') +const TxStateManager = require('../../../../../app/scripts/controllers/transactions/tx-state-manager') +const txStateHistoryHelper = require('../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper') const noop = () => true describe('TransactionStateManager', function () { @@ -176,14 +176,21 @@ describe('TransactionStateManager', function () { assert.deepEqual(updatedTx.history[0], txStateHistoryHelper.snapshotFromTxMeta(updatedTx), 'first history item is initial state') // modify value and updateTx updatedTx.txParams.gasPrice = desiredGasPrice + const before = new Date().getTime() txStateManager.updateTx(updatedTx) + const after = new Date().getTime() // check updated value const result = txStateManager.getTx('1') assert.equal(result.txParams.gasPrice, desiredGasPrice, 'gas price updated') // validate history was updated assert.equal(result.history.length, 2, 'two history items (initial + diff)') + assert.equal(result.history[1].length, 1, 'two history state items (initial + diff)') + const expectedEntry = { op: 'replace', path: '/txParams/gasPrice', value: desiredGasPrice } - assert.deepEqual(result.history[1], [expectedEntry], 'two history items (initial + diff)') + assert.deepEqual(result.history[1][0].op, expectedEntry.op, 'two history items (initial + diff) operation') + assert.deepEqual(result.history[1][0].path, expectedEntry.path, 'two history items (initial + diff) path') + assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value') + assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after) }) }) diff --git a/test/unit/tx-utils-test.js b/test/unit/app/controllers/transactions/tx-utils-test.js index be16225ba..115127f85 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/app/controllers/transactions/tx-utils-test.js @@ -1,5 +1,5 @@ const assert = require('assert') -const txUtils = require('../../app/scripts/controllers/transactions/lib/util') +const txUtils = require('../../../../../app/scripts/controllers/transactions/lib/util') describe('txUtils', function () { diff --git a/test/unit/edge-encryptor-test.js b/test/unit/app/edge-encryptor-test.js index d3f014d74..cc9777389 100644 --- a/test/unit/edge-encryptor-test.js +++ b/test/unit/app/edge-encryptor-test.js @@ -1,6 +1,6 @@ const assert = require('assert') -const EdgeEncryptor = require('../../app/scripts/edge-encryptor') +const EdgeEncryptor = require('../../../app/scripts/edge-encryptor') var password = 'passw0rd1' var data = 'some random data' diff --git a/test/unit/message-manager-test.js b/test/unit/app/message-manager-test.js index 5e7039841..36ef6c29f 100644 --- a/test/unit/message-manager-test.js +++ b/test/unit/app/message-manager-test.js @@ -1,5 +1,5 @@ const assert = require('assert') -const MessageManager = require('../../app/scripts/lib/message-manager') +const MessageManager = require('../../../app/scripts/lib/message-manager') describe('Message Manager', function () { let messageManager diff --git a/test/unit/nodeify-test.js b/test/unit/app/nodeify-test.js index c7b127889..901603c8b 100644 --- a/test/unit/nodeify-test.js +++ b/test/unit/app/nodeify-test.js @@ -1,5 +1,5 @@ const assert = require('assert') -const nodeify = require('../../app/scripts/lib/nodeify') +const nodeify = require('../../../app/scripts/lib/nodeify') describe('nodeify', function () { var obj = { diff --git a/test/unit/pending-balance-test.js b/test/unit/app/pending-balance-test.js index dc4c1c3e4..1418e4a4e 100644 --- a/test/unit/pending-balance-test.js +++ b/test/unit/app/pending-balance-test.js @@ -1,6 +1,6 @@ const assert = require('assert') -const PendingBalanceCalculator = require('../../app/scripts/lib/pending-balance-calculator') -const MockTxGen = require('../lib/mock-tx-gen') +const PendingBalanceCalculator = require('../../../app/scripts/lib/pending-balance-calculator') +const MockTxGen = require('../../lib/mock-tx-gen') const BN = require('ethereumjs-util').BN let providerResultStub = {} diff --git a/test/unit/personal-message-manager-test.js b/test/unit/app/personal-message-manager-test.js index ec2f9a4d1..b07167bff 100644 --- a/test/unit/personal-message-manager-test.js +++ b/test/unit/app/personal-message-manager-test.js @@ -1,6 +1,6 @@ const assert = require('assert') -const PersonalMessageManager = require('../../app/scripts/lib/personal-message-manager') +const PersonalMessageManager = require('../../../app/scripts/lib/personal-message-manager') describe('Personal Message Manager', function () { let messageManager diff --git a/test/unit/seed-phrase-verifier-test.js b/test/unit/app/seed-phrase-verifier-test.js index 4e314806b..b0da534da 100644 --- a/test/unit/seed-phrase-verifier-test.js +++ b/test/unit/app/seed-phrase-verifier-test.js @@ -1,9 +1,9 @@ const assert = require('assert') const clone = require('clone') const KeyringController = require('eth-keyring-controller') -const firstTimeState = require('../../app/scripts/first-time-state') -const seedPhraseVerifier = require('../../app/scripts/lib/seed-phrase-verifier') -const mockEncryptor = require('../lib/mock-encryptor') +const firstTimeState = require('../../../app/scripts/first-time-state') +const seedPhraseVerifier = require('../../../app/scripts/lib/seed-phrase-verifier') +const mockEncryptor = require('../../lib/mock-encryptor') describe('SeedPhraseVerifier', function () { diff --git a/test/unit/util-test.js b/test/unit/app/util-test.js index 6da185b2c..670bc4d22 100644 --- a/test/unit/util-test.js +++ b/test/unit/app/util-test.js @@ -1,5 +1,5 @@ const assert = require('assert') -const { sufficientBalance } = require('../../app/scripts/lib/util') +const { sufficientBalance } = require('../../../app/scripts/lib/util') describe('SufficientBalance', function () { diff --git a/test/unit/metamask-controller-test.js b/test/unit/metamask-controller-test.js deleted file mode 100644 index 18c3f9ab9..000000000 --- a/test/unit/metamask-controller-test.js +++ /dev/null @@ -1,120 +0,0 @@ -const assert = require('assert') -const sinon = require('sinon') -const clone = require('clone') -const nock = require('nock') -const MetaMaskController = require('../../app/scripts/metamask-controller') -const blacklistJSON = require('../stub/blacklist') -const firstTimeState = require('../../app/scripts/first-time-state') - -const DEFAULT_LABEL = 'Account 1' -const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' -const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' -const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' -const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' - -describe('MetaMaskController', function () { - let metamaskController - const sandbox = sinon.sandbox.create() - const noop = () => { } - - beforeEach(function () { - - nock('https://api.infura.io') - .persist() - .get('/v2/blacklist') - .reply(200, blacklistJSON) - - nock('https://api.infura.io') - .persist() - .get(/.*/) - .reply(200) - - metamaskController = new MetaMaskController({ - showUnapprovedTx: noop, - encryptor: { - encrypt: function (password, object) { - this.object = object - return Promise.resolve() - }, - decrypt: function () { - return Promise.resolve(this.object) - }, - }, - initState: clone(firstTimeState), - }) - sandbox.spy(metamaskController.keyringController, 'createNewVaultAndKeychain') - sandbox.spy(metamaskController.keyringController, 'createNewVaultAndRestore') - }) - - afterEach(function () { - nock.cleanAll() - sandbox.restore() - }) - - describe('#getGasPrice', function () { - it('gives the 50th percentile lowest accepted gas price from recentBlocksController', async function () { - const realRecentBlocksController = metamaskController.recentBlocksController - metamaskController.recentBlocksController = { - store: { - getState: () => { - return { - recentBlocks: [ - { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, - { gasPrices: [ '0x3b9aca00', '0x174876e800'] }, - { gasPrices: [ '0x174876e800', '0x174876e800' ]}, - { gasPrices: [ '0x174876e800', '0x174876e800' ]}, - ], - } - }, - }, - } - - const gasPrice = metamaskController.getGasPrice() - assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price') - - metamaskController.recentBlocksController = realRecentBlocksController - }) - }) - - describe('#createNewVaultAndKeychain', function () { - it('can only create new vault on keyringController once', async function () { - const selectStub = sandbox.stub(metamaskController, 'selectFirstIdentity') - - const password = 'a-fake-password' - - await metamaskController.createNewVaultAndKeychain(password) - await metamaskController.createNewVaultAndKeychain(password) - - assert(metamaskController.keyringController.createNewVaultAndKeychain.calledOnce) - - selectStub.reset() - }) - }) - - describe('#createNewVaultAndRestore', function () { - it('should be able to call newVaultAndRestore despite a mistake.', async function () { - const password = 'what-what-what' - await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null) - await metamaskController.createNewVaultAndRestore(password, TEST_SEED) - - assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice) - }) - - it('should clear previous identities after vault restoration', async () => { - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) - assert.deepEqual(metamaskController.getState().identities, { - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, - }) - - await metamaskController.keyringController.saveAccountLabel(TEST_ADDRESS, 'Account Foo') - assert.deepEqual(metamaskController.getState().identities, { - [TEST_ADDRESS]: { address: TEST_ADDRESS, name: 'Account Foo' }, - }) - - await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) - assert.deepEqual(metamaskController.getState().identities, { - [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL }, - }) - }) - }) -}) diff --git a/test/unit/migrations/026-test.js b/test/unit/migrations/026-test.js new file mode 100644 index 000000000..b3f5470cf --- /dev/null +++ b/test/unit/migrations/026-test.js @@ -0,0 +1,41 @@ +const assert = require('assert') +const migration26 = require('../../../app/scripts/migrations/026') +const oldStorage = { + 'meta': {'version': 25}, + 'data': { + 'PreferencesController': {}, + 'KeyringController': { + 'walletNicknames': { + '0x1e77e2': 'Test Account 1', + '0x7e57e2': 'Test Account 2', + }, + }, + }, +} + +describe('migration #26', () => { + it('should move the identities from KeyringController', (done) => { + migration26.migrate(oldStorage) + .then((newStorage) => { + const identities = newStorage.data.PreferencesController.identities + assert.deepEqual(identities, { + '0x1e77e2': {name: 'Test Account 1', address: '0x1e77e2'}, + '0x7e57e2': {name: 'Test Account 2', address: '0x7e57e2'}, + }) + assert.strictEqual(newStorage.data.KeyringController.walletNicknames, undefined) + done() + }) + .catch(done) + }) + + it('should successfully migrate first time state', (done) => { + migration26.migrate({ + meta: {}, + data: require('../../../app/scripts/first-time-state'), + }) + .then((migratedData) => { + assert.equal(migratedData.meta.version, migration26.version) + done() + }).catch(done) + }) +}) diff --git a/test/unit/migrations-test.js b/test/unit/migrations/migrations-test.js index 5bad25a45..50afd9c2e 100644 --- a/test/unit/migrations-test.js +++ b/test/unit/migrations/migrations-test.js @@ -1,22 +1,22 @@ const assert = require('assert') const path = require('path') -const wallet1 = require(path.join('..', 'lib', 'migrations', '001.json')) -const vault4 = require(path.join('..', 'lib', 'migrations', '004.json')) +const wallet1 = require(path.join('..', '..', 'lib', 'migrations', '001.json')) +const vault4 = require(path.join('..', '..', 'lib', 'migrations', '004.json')) let vault5, vault6, vault7, vault8, vault9 // vault10, vault11 -const migration2 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '002')) -const migration3 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '003')) -const migration4 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '004')) -const migration5 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '005')) -const migration6 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '006')) -const migration7 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '007')) -const migration8 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '008')) -const migration9 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '009')) -const migration10 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '010')) -const migration11 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '011')) -const migration12 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '012')) -const migration13 = require(path.join('..', '..', 'app', 'scripts', 'migrations', '013')) +const migration2 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '002')) +const migration3 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '003')) +const migration4 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '004')) +const migration5 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '005')) +const migration6 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '006')) +const migration7 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '007')) +const migration8 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '008')) +const migration9 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '009')) +const migration10 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '010')) +const migration11 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '011')) +const migration12 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '012')) +const migration13 = require(path.join('..', '..', '..', 'app', 'scripts', 'migrations', '013')) const oldTestRpc = 'https://rawtestrpc.metamask.io/' diff --git a/test/unit/migrator-test.js b/test/unit/migrations/migrator-test.js index 4404e1dc4..a9374dff1 100644 --- a/test/unit/migrator-test.js +++ b/test/unit/migrations/migrator-test.js @@ -1,7 +1,7 @@ const assert = require('assert') const clone = require('clone') -const Migrator = require('../../app/scripts/lib/migrator/') -const liveMigrations = require('../../app/scripts/migrations/') +const Migrator = require('../../../app/scripts/lib/migrator/') +const liveMigrations = require('../../../app/scripts/migrations/') const stubMigrations = [ { version: 1, @@ -33,7 +33,7 @@ const versionedData = {meta: {version: 0}, data: {hello: 'world'}} const firstTimeState = { meta: { version: 0 }, - data: require('../../app/scripts/first-time-state'), + data: require('../../../app/scripts/first-time-state'), } describe('Migrator', () => { diff --git a/test/unit/nameForAccount_test.js b/test/unit/nameForAccount_test.js index 32af49e9d..9bb02c6bc 100644 --- a/test/unit/nameForAccount_test.js +++ b/test/unit/nameForAccount_test.js @@ -6,7 +6,7 @@ var contractNamer = require(path.join(__dirname, '..', '..', 'old-ui', 'lib', 'c describe('contractNamer', function () { beforeEach(function () { - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { diff --git a/test/unit/preferences-controller-test.js b/test/unit/preferences-controller-test.js deleted file mode 100644 index 9fb5e4251..000000000 --- a/test/unit/preferences-controller-test.js +++ /dev/null @@ -1,48 +0,0 @@ -const assert = require('assert') -const PreferencesController = require('../../app/scripts/controllers/preferences') - -describe('preferences controller', function () { - let preferencesController - - before(() => { - preferencesController = new PreferencesController() - }) - - describe('addToken', function () { - it('should add that token to its state', async function () { - const address = '0xabcdef1234567' - const symbol = 'ABBR' - const decimals = 5 - - await preferencesController.addToken(address, symbol, decimals) - - const tokens = preferencesController.getTokens() - assert.equal(tokens.length, 1, 'one token added') - - const added = tokens[0] - assert.equal(added.address, address, 'set address correctly') - assert.equal(added.symbol, symbol, 'set symbol correctly') - assert.equal(added.decimals, decimals, 'set decimals correctly') - }) - - it('should allow updating a token value', async function () { - const address = '0xabcdef1234567' - const symbol = 'ABBR' - const decimals = 5 - - await preferencesController.addToken(address, symbol, decimals) - - const newDecimals = 6 - await preferencesController.addToken(address, symbol, newDecimals) - - const tokens = preferencesController.getTokens() - assert.equal(tokens.length, 1, 'one token added') - - const added = tokens[0] - assert.equal(added.address, address, 'set address correctly') - assert.equal(added.symbol, symbol, 'set symbol correctly') - assert.equal(added.decimals, newDecimals, 'updated decimals correctly') - }) - }) -}) - diff --git a/test/unit/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js index 2b7d70b2c..d66e8edbb 100644 --- a/test/unit/reducers/unlock_vault_test.js +++ b/test/unit/reducers/unlock_vault_test.js @@ -10,7 +10,7 @@ var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'redu describe('#unlockMetamask(selectedAccount)', function () { beforeEach(function () { // sinon allows stubbing methods that are easily verified - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { diff --git a/test/unit/tx-state-history-helper-test.js b/test/unit/tx-state-history-helper-test.js deleted file mode 100644 index 35e9ef188..000000000 --- a/test/unit/tx-state-history-helper-test.js +++ /dev/null @@ -1,26 +0,0 @@ -const assert = require('assert') -const clone = require('clone') -const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') - -describe('deepCloneFromTxMeta', function () { - it('should clone deep', function () { - const input = { - foo: { - bar: { - bam: 'baz' - } - } - } - const output = txStateHistoryHelper.snapshotFromTxMeta(input) - assert('foo' in output, 'has a foo key') - assert('bar' in output.foo, 'has a bar key') - assert('bam' in output.foo.bar, 'has a bar key') - assert.equal(output.foo.bar.bam, 'baz', 'has a baz value') - }) - - it('should remove the history key', function () { - const input = { foo: 'bar', history: 'remembered' } - const output = txStateHistoryHelper.snapshotFromTxMeta(input) - assert(typeof output.history, 'undefined', 'should remove history') - }) -}) diff --git a/test/unit/tx-state-history-helper.js b/test/unit/tx-state-history-helper.js deleted file mode 100644 index 35f7dac57..000000000 --- a/test/unit/tx-state-history-helper.js +++ /dev/null @@ -1,46 +0,0 @@ -const assert = require('assert') -const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') -const testVault = require('../data/v17-long-history.json') - - -describe('tx-state-history-helper', function () { - it('migrates history to diffs and can recover original values', function () { - testVault.data.TransactionController.transactions.forEach((tx, index) => { - const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history) - newHistory.forEach((newEntry, index) => { - if (index === 0) { - assert.equal(Array.isArray(newEntry), false, 'initial history item IS NOT a json patch obj') - } else { - assert.equal(Array.isArray(newEntry), true, 'non-initial history entry IS a json patch obj') - } - const oldEntry = tx.history[index] - const historySubset = newHistory.slice(0, index + 1) - const reconstructedValue = txStateHistoryHelper.replayHistory(historySubset) - assert.deepEqual(oldEntry, reconstructedValue, 'was able to reconstruct old entry from diffs') - }) - }) - }) - - it('replaying history does not mutate the original obj', function () { - const initialState = { test: true, message: 'hello', value: 1 } - const diff1 = [{ - "op": "replace", - "path": "/message", - "value": "haay", - }] - const diff2 = [{ - "op": "replace", - "path": "/value", - "value": 2, - }] - const history = [initialState, diff1, diff2] - - const beforeStateSnapshot = JSON.stringify(initialState) - const latestState = txStateHistoryHelper.replayHistory(history) - const afterStateSnapshot = JSON.stringify(initialState) - - assert.notEqual(initialState, latestState, 'initial state is not the same obj as the latest state') - assert.equal(beforeStateSnapshot, afterStateSnapshot, 'initial state is not modified during run') - }) - -}) diff --git a/test/unit/util_test.js b/test/unit/util_test.js index 59048975a..39473854f 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -10,7 +10,7 @@ describe('util', function () { for (var i = 0; i < 18; i++) { ethInWei += '0' } beforeEach(function () { - this.sinon = sinon.sandbox.create() + this.sinon = sinon.createSandbox() }) afterEach(function () { |