After Width: | Height: | Size: 795 B |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,12 @@ |
||||
const TRANSACTION_TYPE_CANCEL = 'cancel' |
||||
const TRANSACTION_TYPE_RETRY = 'retry' |
||||
const TRANSACTION_TYPE_STANDARD = 'standard' |
||||
|
||||
const TRANSACTION_STATUS_APPROVED = 'approved' |
||||
|
||||
module.exports = { |
||||
TRANSACTION_TYPE_CANCEL, |
||||
TRANSACTION_TYPE_RETRY, |
||||
TRANSACTION_TYPE_STANDARD, |
||||
TRANSACTION_STATUS_APPROVED, |
||||
} |
@ -1,254 +0,0 @@ |
||||
const ethUtil = require('ethereumjs-util') |
||||
const normalize = require('eth-sig-util').normalize |
||||
const { |
||||
MAINNET_RPC_URL, |
||||
ROPSTEN_RPC_URL, |
||||
KOVAN_RPC_URL, |
||||
RINKEBY_RPC_URL, |
||||
} = require('../controllers/network/enums') |
||||
|
||||
/* The config-manager is a convenience object |
||||
* wrapping a pojo-migrator. |
||||
* |
||||
* It exists mostly to allow the creation of |
||||
* convenience methods to access and persist |
||||
* particular portions of the state. |
||||
*/ |
||||
module.exports = ConfigManager |
||||
function ConfigManager (opts) { |
||||
// ConfigManager is observable and will emit updates
|
||||
this._subs = [] |
||||
this.store = opts.store |
||||
} |
||||
|
||||
ConfigManager.prototype.setConfig = function (config) { |
||||
var data = this.getData() |
||||
data.config = config |
||||
this.setData(data) |
||||
this._emitUpdates(config) |
||||
} |
||||
|
||||
ConfigManager.prototype.getConfig = function () { |
||||
var data = this.getData() |
||||
return data.config |
||||
} |
||||
|
||||
ConfigManager.prototype.setData = function (data) { |
||||
this.store.putState(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.getData = function () { |
||||
return this.store.getState() |
||||
} |
||||
|
||||
ConfigManager.prototype.setPasswordForgotten = function (passwordForgottenState) { |
||||
const data = this.getData() |
||||
data.forgottenPassword = passwordForgottenState |
||||
this.setData(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.getPasswordForgotten = function (passwordForgottenState) { |
||||
const data = this.getData() |
||||
return data.forgottenPassword |
||||
} |
||||
|
||||
ConfigManager.prototype.setWallet = function (wallet) { |
||||
var data = this.getData() |
||||
data.wallet = wallet |
||||
this.setData(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.setVault = function (encryptedString) { |
||||
var data = this.getData() |
||||
data.vault = encryptedString |
||||
this.setData(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.getVault = function () { |
||||
var data = this.getData() |
||||
return data.vault |
||||
} |
||||
|
||||
ConfigManager.prototype.getKeychains = function () { |
||||
return this.getData().keychains || [] |
||||
} |
||||
|
||||
ConfigManager.prototype.setKeychains = function (keychains) { |
||||
var data = this.getData() |
||||
data.keychains = keychains |
||||
this.setData(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.getSelectedAccount = function () { |
||||
var config = this.getConfig() |
||||
return config.selectedAccount |
||||
} |
||||
|
||||
ConfigManager.prototype.setSelectedAccount = function (address) { |
||||
var config = this.getConfig() |
||||
config.selectedAccount = ethUtil.addHexPrefix(address) |
||||
this.setConfig(config) |
||||
} |
||||
|
||||
ConfigManager.prototype.getWallet = function () { |
||||
return this.getData().wallet |
||||
} |
||||
|
||||
// Takes a boolean
|
||||
ConfigManager.prototype.setShowSeedWords = function (should) { |
||||
var data = this.getData() |
||||
data.showSeedWords = should |
||||
this.setData(data) |
||||
} |
||||
|
||||
|
||||
ConfigManager.prototype.getShouldShowSeedWords = function () { |
||||
var data = this.getData() |
||||
return data.showSeedWords |
||||
} |
||||
|
||||
ConfigManager.prototype.setSeedWords = function (words) { |
||||
var data = this.getData() |
||||
data.seedWords = words |
||||
this.setData(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.getSeedWords = function () { |
||||
var data = this.getData() |
||||
return data.seedWords |
||||
} |
||||
ConfigManager.prototype.setRpcTarget = function (rpcUrl) { |
||||
var config = this.getConfig() |
||||
config.provider = { |
||||
type: 'rpc', |
||||
rpcTarget: rpcUrl, |
||||
} |
||||
this.setConfig(config) |
||||
} |
||||
|
||||
ConfigManager.prototype.setProviderType = function (type) { |
||||
var config = this.getConfig() |
||||
config.provider = { |
||||
type: type, |
||||
} |
||||
this.setConfig(config) |
||||
} |
||||
|
||||
ConfigManager.prototype.useEtherscanProvider = function () { |
||||
var config = this.getConfig() |
||||
config.provider = { |
||||
type: 'etherscan', |
||||
} |
||||
this.setConfig(config) |
||||
} |
||||
|
||||
ConfigManager.prototype.getProvider = function () { |
||||
var config = this.getConfig() |
||||
return config.provider |
||||
} |
||||
|
||||
ConfigManager.prototype.getCurrentRpcAddress = function () { |
||||
var provider = this.getProvider() |
||||
if (!provider) return null |
||||
switch (provider.type) { |
||||
|
||||
case 'mainnet': |
||||
return MAINNET_RPC_URL |
||||
|
||||
case 'ropsten': |
||||
return ROPSTEN_RPC_URL |
||||
|
||||
case 'kovan': |
||||
return KOVAN_RPC_URL |
||||
|
||||
case 'rinkeby': |
||||
return RINKEBY_RPC_URL |
||||
|
||||
default: |
||||
return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC_URL |
||||
} |
||||
} |
||||
|
||||
//
|
||||
// Tx
|
||||
//
|
||||
|
||||
ConfigManager.prototype.getTxList = function () { |
||||
var data = this.getData() |
||||
if (data.transactions !== undefined) { |
||||
return data.transactions |
||||
} else { |
||||
return [] |
||||
} |
||||
} |
||||
|
||||
ConfigManager.prototype.setTxList = function (txList) { |
||||
var data = this.getData() |
||||
data.transactions = txList |
||||
this.setData(data) |
||||
} |
||||
|
||||
|
||||
// wallet nickname methods
|
||||
|
||||
ConfigManager.prototype.getWalletNicknames = function () { |
||||
var data = this.getData() |
||||
const nicknames = ('walletNicknames' in data) ? data.walletNicknames : {} |
||||
return nicknames |
||||
} |
||||
|
||||
ConfigManager.prototype.nicknameForWallet = function (account) { |
||||
const address = normalize(account) |
||||
const nicknames = this.getWalletNicknames() |
||||
return nicknames[address] |
||||
} |
||||
|
||||
ConfigManager.prototype.setNicknameForWallet = function (account, nickname) { |
||||
const address = normalize(account) |
||||
const nicknames = this.getWalletNicknames() |
||||
nicknames[address] = nickname |
||||
var data = this.getData() |
||||
data.walletNicknames = nicknames |
||||
this.setData(data) |
||||
} |
||||
|
||||
// observable
|
||||
|
||||
ConfigManager.prototype.getSalt = function () { |
||||
var data = this.getData() |
||||
return data.salt |
||||
} |
||||
|
||||
ConfigManager.prototype.setSalt = function (salt) { |
||||
var data = this.getData() |
||||
data.salt = salt |
||||
this.setData(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.subscribe = function (fn) { |
||||
this._subs.push(fn) |
||||
var unsubscribe = this.unsubscribe.bind(this, fn) |
||||
return unsubscribe |
||||
} |
||||
|
||||
ConfigManager.prototype.unsubscribe = function (fn) { |
||||
var index = this._subs.indexOf(fn) |
||||
if (index !== -1) this._subs.splice(index, 1) |
||||
} |
||||
|
||||
ConfigManager.prototype._emitUpdates = function (state) { |
||||
this._subs.forEach(function (handler) { |
||||
handler(state) |
||||
}) |
||||
} |
||||
|
||||
ConfigManager.prototype.setLostAccounts = function (lostAccounts) { |
||||
var data = this.getData() |
||||
data.lostAccounts = lostAccounts |
||||
this.setData(data) |
||||
} |
||||
|
||||
ConfigManager.prototype.getLostAccounts = function () { |
||||
var data = this.getData() |
||||
return data.lostAccounts || [] |
||||
} |
@ -1,50 +0,0 @@ |
||||
const version = 5 |
||||
|
||||
/* |
||||
|
||||
This is an incomplete migration bc it requires post-decrypted data |
||||
which we dont have access to at the time of this writing. |
||||
|
||||
*/ |
||||
|
||||
const ObservableStore = require('obs-store') |
||||
const ConfigManager = require('../../app/scripts/lib/config-manager') |
||||
const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator') |
||||
const KeyringController = require('eth-keyring-controller') |
||||
|
||||
const password = 'obviously not correct' |
||||
|
||||
module.exports = { |
||||
version, |
||||
|
||||
migrate: function (versionedData) { |
||||
versionedData.meta.version = version |
||||
|
||||
const store = new ObservableStore(versionedData.data) |
||||
const configManager = new ConfigManager({ store }) |
||||
const idStoreMigrator = new IdentityStoreMigrator({ configManager }) |
||||
const keyringController = new KeyringController({ |
||||
configManager: configManager, |
||||
}) |
||||
|
||||
// attempt to migrate to multiVault
|
||||
return idStoreMigrator.migratedVaultForPassword(password) |
||||
.then((result) => { |
||||
// skip if nothing to migrate
|
||||
if (!result) return Promise.resolve(versionedData) |
||||
delete versionedData.data.wallet |
||||
// create new keyrings
|
||||
const privKeys = result.lostAccounts.map(acct => acct.privateKey) |
||||
return Promise.all([ |
||||
keyringController.restoreKeyring(result.serialized), |
||||
keyringController.restoreKeyring({ type: 'Simple Key Pair', data: privKeys }), |
||||
]).then(() => { |
||||
return keyringController.persistAllKeyrings(password) |
||||
}).then(() => { |
||||
// copy result on to state object
|
||||
versionedData.data = store.get() |
||||
return Promise.resolve(versionedData) |
||||
}) |
||||
}) |
||||
}, |
||||
} |
@ -0,0 +1,59 @@ |
||||
window.onload = function () { |
||||
if (window.location.pathname === '/phishing.html') { |
||||
const {hostname} = parseHash() |
||||
document.getElementById('esdbLink').innerHTML = '<b>To read more about this scam, navigate to: <a href="https://etherscamdb.info/domain/' + hostname + '"> https://etherscamdb.info/domain/' + hostname + '</a></b>' |
||||
} |
||||
} |
||||
|
||||
const querystring = require('querystring') |
||||
const dnode = require('dnode') |
||||
const { EventEmitter } = require('events') |
||||
const PortStream = require('extension-port-stream') |
||||
const extension = require('extensionizer') |
||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex |
||||
const { getEnvironmentType } = require('./lib/util') |
||||
const ExtensionPlatform = require('./platforms/extension') |
||||
|
||||
document.addEventListener('DOMContentLoaded', start) |
||||
|
||||
function start () { |
||||
const windowType = getEnvironmentType(window.location.href) |
||||
|
||||
global.platform = new ExtensionPlatform() |
||||
global.METAMASK_UI_TYPE = windowType |
||||
|
||||
const extensionPort = extension.runtime.connect({ name: windowType }) |
||||
const connectionStream = new PortStream(extensionPort) |
||||
const mx = setupMultiplex(connectionStream) |
||||
setupControllerConnection(mx.createStream('controller'), (err, metaMaskController) => { |
||||
if (err) { |
||||
return |
||||
} |
||||
|
||||
const suspect = parseHash() |
||||
const unsafeContinue = () => { |
||||
window.location.href = suspect.href |
||||
} |
||||
const continueLink = document.getElementById('unsafe-continue') |
||||
continueLink.addEventListener('click', () => { |
||||
metaMaskController.whitelistPhishingDomain(suspect.hostname) |
||||
unsafeContinue() |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
function setupControllerConnection (connectionStream, cb) { |
||||
const eventEmitter = new EventEmitter() |
||||
const accountManagerDnode = dnode({ |
||||
sendUpdate (state) { |
||||
eventEmitter.emit('update', state) |
||||
}, |
||||
}) |
||||
connectionStream.pipe(accountManagerDnode).pipe(connectionStream) |
||||
accountManagerDnode.once('remote', (accountManager) => cb(null, accountManager)) |
||||
} |
||||
|
||||
function parseHash () { |
||||
const hash = window.location.hash.substring(1) |
||||
return querystring.parse(hash) |
||||
} |
@ -0,0 +1,70 @@ |
||||
{ "isInitialized": true, |
||||
"provider": { "type": "rpc", "rpcTarget": "http://localhost:8545" }, |
||||
"network": "loading", |
||||
"accounts": { |
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { |
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", |
||||
"balance": "0x0" |
||||
}, |
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": { |
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b", |
||||
"balance": "0x0" |
||||
} |
||||
}, |
||||
"currentBlockGasLimit": "", |
||||
"unapprovedTxs": {}, |
||||
"selectedAddressTxList": [], |
||||
"computedBalances": {}, |
||||
"unapprovedMsgs": {}, |
||||
"unapprovedMsgCount": 0, |
||||
"unapprovedPersonalMsgs": {}, |
||||
"unapprovedPersonalMsgCount": 0, |
||||
"unapprovedTypedMessages": {}, |
||||
"unapprovedTypedMessagesCount": 0, |
||||
"isUnlocked": true, |
||||
"keyringTypes": [ "Simple Key Pair", "HD Key Tree" ], |
||||
"keyrings":[ |
||||
{ "type": "HD Key Tree", |
||||
"accounts": [ |
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" |
||||
] |
||||
}, |
||||
{ |
||||
"type": "Simple Key Pair", |
||||
"accounts": [ |
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b" |
||||
] |
||||
} |
||||
], |
||||
"frequentRpcList": [], |
||||
"currentAccountTab": "history", |
||||
"tokens": [], |
||||
"useBlockie": false, |
||||
"featureFlags": {}, |
||||
"currentLocale": null, |
||||
"identities": { |
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { |
||||
"name": "Account 1", |
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" |
||||
}, |
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": { |
||||
"name": "Account 2", |
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b" |
||||
} |
||||
}, |
||||
|
||||
"lostIdentities": {}, |
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", |
||||
"recentBlocks": [], |
||||
"addressBook": [], |
||||
"currentCurrency": "usd", |
||||
"conversionRate": 288.45, |
||||
"conversionDate": 1506444677, |
||||
"nextUnreadNotice": null, |
||||
"noActiveNotices": true, |
||||
"shapeShiftTxList": [], |
||||
"infuraNetworkStatus": {}, |
||||
"lostAccounts": [], |
||||
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium", |
||||
"forgottenPassword": null |
||||
} |
@ -0,0 +1,286 @@ |
||||
const path = require('path') |
||||
const assert = require('assert') |
||||
const webdriver = require('selenium-webdriver') |
||||
const { By, until } = webdriver |
||||
const { |
||||
delay, |
||||
buildChromeWebDriver, |
||||
buildFirefoxWebdriver, |
||||
installWebExt, |
||||
getExtensionIdChrome, |
||||
getExtensionIdFirefox, |
||||
} = require('../func') |
||||
const { |
||||
checkBrowserForConsoleErrors, |
||||
closeAllWindowHandlesExcept, |
||||
findElement, |
||||
findElements, |
||||
loadExtension, |
||||
openNewPage, |
||||
verboseReportOnFailure, |
||||
waitUntilXWindowHandles, |
||||
} = require('./helpers') |
||||
|
||||
describe('MetaMask', function () { |
||||
let extensionId |
||||
let driver |
||||
|
||||
const tinyDelayMs = 200 |
||||
const regularDelayMs = tinyDelayMs * 2 |
||||
const largeDelayMs = regularDelayMs * 2 |
||||
|
||||
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(driver, this.currentTest) |
||||
} |
||||
}) |
||||
|
||||
after(async function () { |
||||
await driver.quit() |
||||
}) |
||||
|
||||
|
||||
describe('New UI setup', async function () { |
||||
it('switches to first tab', async function () { |
||||
await delay(tinyDelayMs) |
||||
const [firstTab] = await driver.getAllWindowHandles() |
||||
await driver.switchTo().window(firstTab) |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('selects the new UI option', async () => { |
||||
try { |
||||
const overlay = await findElement(driver, By.css('.full-flex-height')) |
||||
await driver.wait(until.stalenessOf(overlay)) |
||||
} catch (e) {} |
||||
|
||||
let button |
||||
try { |
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]")) |
||||
} catch (e) { |
||||
await loadExtension(driver, extensionId) |
||||
await delay(largeDelayMs) |
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]")) |
||||
} |
||||
await button.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
// Close all other tabs
|
||||
const [tab0, tab1, tab2] = await driver.getAllWindowHandles() |
||||
await driver.switchTo().window(tab0) |
||||
await delay(tinyDelayMs) |
||||
|
||||
let selectedUrl = await driver.getCurrentUrl() |
||||
await delay(tinyDelayMs) |
||||
if (tab0 && selectedUrl.match(/popup.html/)) { |
||||
await closeAllWindowHandlesExcept(driver, tab0) |
||||
} else if (tab1) { |
||||
await driver.switchTo().window(tab1) |
||||
selectedUrl = await driver.getCurrentUrl() |
||||
await delay(tinyDelayMs) |
||||
if (selectedUrl.match(/popup.html/)) { |
||||
await closeAllWindowHandlesExcept(driver, tab1) |
||||
} else if (tab2) { |
||||
await driver.switchTo().window(tab2) |
||||
selectedUrl = await driver.getCurrentUrl() |
||||
selectedUrl.match(/popup.html/) && await closeAllWindowHandlesExcept(driver, tab2) |
||||
} |
||||
} else { |
||||
throw new Error('popup.html not found') |
||||
} |
||||
await delay(regularDelayMs) |
||||
const [appTab] = await driver.getAllWindowHandles() |
||||
await driver.switchTo().window(appTab) |
||||
await delay(tinyDelayMs) |
||||
|
||||
await loadExtension(driver, extensionId) |
||||
await delay(regularDelayMs) |
||||
|
||||
const continueBtn = await findElement(driver, 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 findElement(driver, By.css('.create-password #create-password')) |
||||
const passwordBoxConfirm = await findElement(driver, By.css('.create-password #confirm-password')) |
||||
const button = await findElement(driver, 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 findElement(driver, By.css('.unique-image button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the ToS', async () => { |
||||
// terms of use
|
||||
const canClickThrough = await driver.findElement(By.css('.tou button')).isEnabled() |
||||
assert.equal(canClickThrough, false, 'disabled continue button') |
||||
const bottomOfTos = await findElement(driver, By.linkText('Attributions')) |
||||
await driver.executeScript('arguments[0].scrollIntoView(true)', bottomOfTos) |
||||
await delay(regularDelayMs) |
||||
const acceptTos = await findElement(driver, By.css('.tou button')) |
||||
driver.wait(until.elementIsEnabled(acceptTos)) |
||||
await acceptTos.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the privacy notice', async () => { |
||||
// privacy notice
|
||||
const nextScreen = await findElement(driver, By.css('.tou button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the phishing notice', async () => { |
||||
// phishing notice
|
||||
const noticeElement = await driver.findElement(By.css('.markdown')) |
||||
await driver.executeScript('arguments[0].scrollTop = arguments[0].scrollHeight', noticeElement) |
||||
await delay(regularDelayMs) |
||||
const nextScreen = await findElement(driver, By.css('.tou button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
let seedPhrase |
||||
|
||||
it('reveals the seed phrase', async () => { |
||||
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button') |
||||
await driver.wait(until.elementLocated(byRevealButton, 10000)) |
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) |
||||
await revealSeedPhraseButton.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 findElement(driver, By.css('.backup-phrase button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
async function clickWordAndWait (word) { |
||||
const xpathClass = 'backup-phrase__confirm-seed-option backup-phrase__confirm-seed-option--unselected' |
||||
const xpath = `//button[@class='${xpathClass}' and contains(text(), '${word}')]` |
||||
const word0 = await findElement(driver, By.xpath(xpath), 10000) |
||||
|
||||
await word0.click() |
||||
await delay(tinyDelayMs) |
||||
} |
||||
|
||||
async function retypeSeedPhrase (words, wasReloaded, count = 0) { |
||||
try { |
||||
if (wasReloaded) { |
||||
const byRevealButton = By.css('.backup-phrase__secret-blocker .backup-phrase__reveal-button') |
||||
await driver.wait(until.elementLocated(byRevealButton, 10000)) |
||||
const revealSeedPhraseButton = await findElement(driver, byRevealButton, 10000) |
||||
await revealSeedPhraseButton.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const nextScreen = await findElement(driver, By.css('.backup-phrase button')) |
||||
await nextScreen.click() |
||||
await delay(regularDelayMs) |
||||
} |
||||
|
||||
for (let i = 0; i < 12; i++) { |
||||
await clickWordAndWait(words[i]) |
||||
} |
||||
} catch (e) { |
||||
if (count > 2) { |
||||
throw e |
||||
} else { |
||||
await loadExtension(driver, extensionId) |
||||
await retypeSeedPhrase(words, true, count + 1) |
||||
} |
||||
} |
||||
} |
||||
|
||||
it('can retype the seed phrase', async () => { |
||||
const words = seedPhrase.split(' ') |
||||
|
||||
await retypeSeedPhrase(words) |
||||
|
||||
const confirm = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) |
||||
await confirm.click() |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('clicks through the deposit modal', async () => { |
||||
const byBuyModal = By.css('span .modal') |
||||
const buyModal = await driver.wait(until.elementLocated(byBuyModal)) |
||||
const closeModal = await findElement(driver, By.css('.page-container__header-close')) |
||||
await closeModal.click() |
||||
await driver.wait(until.stalenessOf(buyModal)) |
||||
await delay(regularDelayMs) |
||||
}) |
||||
|
||||
it('switches to localhost', async () => { |
||||
const networkDropdown = await findElement(driver, By.css('.network-name')) |
||||
await networkDropdown.click() |
||||
await delay(regularDelayMs) |
||||
|
||||
const [localhost] = await findElements(driver, By.xpath(`//span[contains(text(), 'Localhost')]`)) |
||||
await localhost.click() |
||||
await delay(largeDelayMs * 2) |
||||
}) |
||||
}) |
||||
|
||||
describe('Drizzle', () => { |
||||
it('should be able to detect our eth address', async () => { |
||||
await openNewPage(driver, 'http://127.0.0.1:3000/') |
||||
await delay(regularDelayMs) |
||||
|
||||
await waitUntilXWindowHandles(driver, 2) |
||||
const windowHandles = await driver.getAllWindowHandles() |
||||
const dapp = windowHandles[1] |
||||
|
||||
await driver.switchTo().window(dapp) |
||||
await delay(regularDelayMs) |
||||
|
||||
|
||||
const addressElement = await findElement(driver, By.css(`.pure-u-1-1 h4`)) |
||||
const addressText = await addressElement.getText() |
||||
assert(addressText.match(/^0x[a-fA-F0-9]{40}$/)) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,20 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
set -e |
||||
set -u |
||||
set -o pipefail |
||||
|
||||
export PATH="$PATH:./node_modules/.bin" |
||||
|
||||
npm run ganache:start -- -b 2 >> /dev/null 2>&1 & |
||||
sleep 5 |
||||
cd test/e2e/beta/ |
||||
rm -rf drizzle-test |
||||
mkdir drizzle-test && cd drizzle-test |
||||
npm install truffle |
||||
truffle unbox drizzle |
||||
echo "Deploying contracts for Drizzle test..." |
||||
truffle compile && truffle migrate |
||||
BROWSER=none npm start >> /dev/null 2>&1 & |
||||
cd ../../../../ |
||||
mocha test/e2e/beta/drizzle.spec |
@ -1,9 +0,0 @@ |
||||
const ObservableStore = require('obs-store') |
||||
const clone = require('clone') |
||||
const ConfigManager = require('../../app/scripts/lib/config-manager') |
||||
const firstTimeState = require('../../app/scripts/first-time-state') |
||||
|
||||
module.exports = function () { |
||||
const store = new ObservableStore(clone(firstTimeState)) |
||||
return new ConfigManager({ store }) |
||||
} |
@ -0,0 +1,42 @@ |
||||
const { shallow, mount } = require('enzyme') |
||||
import { BrowserRouter } from 'react-router-dom' |
||||
import { shape } from 'prop-types' |
||||
|
||||
module.exports = { |
||||
shallowWithStore, |
||||
mountWithStore, |
||||
mountWithRouter, |
||||
} |
||||
|
||||
function shallowWithStore (component, store) { |
||||
const context = { |
||||
store, |
||||
} |
||||
return shallow(component, {context}) |
||||
} |
||||
|
||||
function mountWithStore (component, store) { |
||||
const context = { |
||||
store, |
||||
} |
||||
return mount(component, {context}) |
||||
} |
||||
|
||||
function mountWithRouter (node) { |
||||
|
||||
// Instantiate router context
|
||||
const router = { |
||||
history: new BrowserRouter().history, |
||||
route: { |
||||
location: {}, |
||||
match: {}, |
||||
}, |
||||
} |
||||
|
||||
const createContext = () => ({ |
||||
context: { router, t: () => {} }, |
||||
childContextTypes: { router: shape({}), t: () => {} }, |
||||
}) |
||||
|
||||
return mount(node, createContext()) |
||||
} |
@ -1,20 +0,0 @@ |
||||
const { shallow, mount } = require('enzyme') |
||||
|
||||
module.exports = { |
||||
shallowWithStore, |
||||
mountWithStore, |
||||
} |
||||
|
||||
function shallowWithStore (component, store) { |
||||
const context = { |
||||
store, |
||||
} |
||||
return shallow(component, {context}) |
||||
} |
||||
|
||||
function mountWithStore (component, store) { |
||||
const context = { |
||||
store, |
||||
} |
||||
return mount(component, {context}) |
||||
} |
@ -0,0 +1,33 @@ |
||||
const assert = require('assert') |
||||
const cleanErrorStack = require('../../../app/scripts/lib/cleanErrorStack') |
||||
|
||||
describe('Clean Error Stack', () => { |
||||
|
||||
const testMessage = 'Test Message' |
||||
const testError = new Error(testMessage) |
||||
const undefinedErrorName = new Error(testMessage) |
||||
const blankErrorName = new Error(testMessage) |
||||
const blankMsgError = new Error() |
||||
|
||||
beforeEach(() => { |
||||
undefinedErrorName.name = undefined |
||||
blankErrorName.name = '' |
||||
}) |
||||
|
||||
it('tests error with message', () => { |
||||
assert.equal(cleanErrorStack(testError), 'Error: Test Message') |
||||
}) |
||||
|
||||
it('tests error with undefined name', () => { |
||||
assert.equal(cleanErrorStack(undefinedErrorName).toString(), 'Error: Test Message') |
||||
}) |
||||
|
||||
it('tests error with blank name', () => { |
||||
assert.equal(cleanErrorStack(blankErrorName).toString(), 'Test Message') |
||||
}) |
||||
|
||||
it('tests error with blank message', () => { |
||||
assert.equal(cleanErrorStack(blankMsgError), 'Error') |
||||
}) |
||||
|
||||
}) |
@ -1,112 +0,0 @@ |
||||
const assert = require('assert') |
||||
const configManagerGen = require('../lib/mock-config-manager') |
||||
|
||||
describe('config-manager', function () { |
||||
var configManager |
||||
|
||||
beforeEach(function () { |
||||
configManager = configManagerGen() |
||||
}) |
||||
|
||||
describe('#setConfig', function () { |
||||
it('should set the config key', function () { |
||||
var testConfig = { |
||||
provider: { |
||||
type: 'rpc', |
||||
rpcTarget: 'foobar', |
||||
}, |
||||
} |
||||
configManager.setConfig(testConfig) |
||||
var result = configManager.getData() |
||||
|
||||
assert.equal(result.config.provider.type, testConfig.provider.type) |
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget) |
||||
}) |
||||
|
||||
it('setting wallet should not overwrite config', function () { |
||||
var testConfig = { |
||||
provider: { |
||||
type: 'rpc', |
||||
rpcTarget: 'foobar', |
||||
}, |
||||
} |
||||
configManager.setConfig(testConfig) |
||||
|
||||
var testWallet = { |
||||
name: 'this is my fake wallet', |
||||
} |
||||
configManager.setWallet(testWallet) |
||||
|
||||
var result = configManager.getData() |
||||
assert.equal(result.wallet.name, testWallet.name, 'wallet name is set') |
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget) |
||||
|
||||
testConfig.provider.type = 'something else!' |
||||
configManager.setConfig(testConfig) |
||||
|
||||
result = configManager.getData() |
||||
assert.equal(result.wallet.name, testWallet.name, 'wallet name is set') |
||||
assert.equal(result.config.provider.rpcTarget, testConfig.provider.rpcTarget) |
||||
assert.equal(result.config.provider.type, testConfig.provider.type) |
||||
}) |
||||
}) |
||||
|
||||
describe('wallet nicknames', function () { |
||||
it('should return null when no nicknames are saved', function () { |
||||
var nick = configManager.nicknameForWallet('0x0') |
||||
assert.equal(nick, null, 'no nickname returned') |
||||
}) |
||||
|
||||
it('should persist nicknames', function () { |
||||
var account = '0x0' |
||||
var nick1 = 'foo' |
||||
var nick2 = 'bar' |
||||
configManager.setNicknameForWallet(account, nick1) |
||||
|
||||
var result1 = configManager.nicknameForWallet(account) |
||||
assert.equal(result1, nick1) |
||||
|
||||
configManager.setNicknameForWallet(account, nick2) |
||||
var result2 = configManager.nicknameForWallet(account) |
||||
assert.equal(result2, nick2) |
||||
}) |
||||
}) |
||||
|
||||
describe('rpc manipulations', function () { |
||||
it('changing rpc should return a different rpc', function () { |
||||
var firstRpc = 'first' |
||||
var secondRpc = 'second' |
||||
|
||||
configManager.setRpcTarget(firstRpc) |
||||
var firstResult = configManager.getCurrentRpcAddress() |
||||
assert.equal(firstResult, firstRpc) |
||||
|
||||
configManager.setRpcTarget(secondRpc) |
||||
var secondResult = configManager.getCurrentRpcAddress() |
||||
assert.equal(secondResult, secondRpc) |
||||
}) |
||||
}) |
||||
|
||||
describe('transactions', function () { |
||||
beforeEach(function () { |
||||
configManager.setTxList([]) |
||||
}) |
||||
|
||||
describe('#getTxList', function () { |
||||
it('when new should return empty array', function () { |
||||
var result = configManager.getTxList() |
||||
assert.ok(Array.isArray(result)) |
||||
assert.equal(result.length, 0) |
||||
}) |
||||
}) |
||||
|
||||
describe('#setTxList', function () { |
||||
it('saves the submitted data to the tx list', function () { |
||||
var target = [{ foo: 'bar' }] |
||||
configManager.setTxList(target) |
||||
var result = configManager.getTxList() |
||||
assert.equal(result[0].foo, 'bar') |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,36 @@ |
||||
import React from 'react' |
||||
import assert from 'assert' |
||||
import thunk from 'redux-thunk' |
||||
import configureMockStore from 'redux-mock-store' |
||||
import { mount } from 'enzyme' |
||||
|
||||
import IdenticonComponent from '../../../../../ui/app/components/identicon' |
||||
|
||||
describe('Identicon Component', () => { |
||||
|
||||
const state = { |
||||
metamask: { |
||||
useBlockie: false, |
||||
}, |
||||
} |
||||
|
||||
const middlewares = [thunk] |
||||
const mockStore = configureMockStore(middlewares) |
||||
const store = mockStore(state) |
||||
|
||||
it('renders default eth_logo identicon with no props', () => { |
||||
const wrapper = mount(<IdenticonComponent store={store}/>) |
||||
assert.equal(wrapper.find('img.balance-icon').prop('src'), './images/eth_logo.svg') |
||||
}) |
||||
|
||||
it('renders custom image and add className props', () => { |
||||
const wrapper = mount(<IdenticonComponent store={store} className={'test-image'} image={'test-image'} />) |
||||
assert.equal(wrapper.find('img.test-image').prop('className'), 'test-image identicon') |
||||
assert.equal(wrapper.find('img.test-image').prop('src'), 'test-image') |
||||
}) |
||||
|
||||
it('renders div with address prop', () => { |
||||
const wrapper = mount(<IdenticonComponent store={store} className={'test-address'} address={'0xTest'} />) |
||||
assert.equal(wrapper.find('div.test-address').prop('className'), 'test-address identicon') |
||||
}) |
||||
}) |
@ -0,0 +1,69 @@ |
||||
import React from 'react' |
||||
import assert from 'assert' |
||||
import thunk from 'redux-thunk' |
||||
import { Provider } from 'react-redux' |
||||
import configureMockStore from 'redux-mock-store' |
||||
import { mount } from 'enzyme' |
||||
|
||||
import TokenCell from '../../../../../ui/app/components/token-cell' |
||||
import Identicon from '../../../../../ui/app/components/identicon' |
||||
|
||||
describe('Token Cell', () => { |
||||
let wrapper |
||||
|
||||
const state = { |
||||
metamask: { |
||||
network: 'test', |
||||
currentCurrency: 'usd', |
||||
selectedTokenAddress: '0xToken', |
||||
selectedAddress: '0xAddress', |
||||
contractExchangeRates: { |
||||
'0xAnotherToken': 0.015, |
||||
}, |
||||
conversionRate: 7.00, |
||||
}, |
||||
appState: { |
||||
sidebar: { |
||||
isOpen: true, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
const middlewares = [thunk] |
||||
const mockStore = configureMockStore(middlewares) |
||||
const store = mockStore(state) |
||||
|
||||
beforeEach(() => { |
||||
wrapper = mount( |
||||
<Provider store={store}> |
||||
<TokenCell |
||||
address={'0xAnotherToken'} |
||||
symbol={'TEST'} |
||||
string={'5.000'} |
||||
network={22} |
||||
currentCurrency={'usd'} |
||||
image={'./test-image'} |
||||
/> |
||||
</Provider> |
||||
) |
||||
}) |
||||
|
||||
it('renders Identicon with props from token cell', () => { |
||||
assert.equal(wrapper.find(Identicon).prop('address'), '0xAnotherToken') |
||||
assert.equal(wrapper.find(Identicon).prop('network'), 'test') |
||||
assert.equal(wrapper.find(Identicon).prop('image'), './test-image') |
||||
}) |
||||
|
||||
it('renders token balance', () => { |
||||
assert.equal(wrapper.find('.token-list-item__token-balance').text(), '5.000') |
||||
}) |
||||
|
||||
it('renders token symbol', () => { |
||||
assert.equal(wrapper.find('.token-list-item__token-symbol').text(), 'TEST') |
||||
}) |
||||
|
||||
it('renders converted fiat amount', () => { |
||||
assert.equal(wrapper.find('.token-list-item__fiat-amount').text(), '0.52 USD') |
||||
}) |
||||
|
||||
}) |
@ -0,0 +1,175 @@ |
||||
const assert = require('assert') |
||||
const selectors = require('../../../../ui/app/selectors') |
||||
const mockState = require('../../../data/mock-state.json') |
||||
const Eth = require('ethjs') |
||||
|
||||
const { createTestProviderTools } = require('../../../stub/provider') |
||||
const provider = createTestProviderTools({ scaffold: {}}).provider |
||||
|
||||
describe('Selectors', function () { |
||||
|
||||
describe('#getSelectedAddress', function () { |
||||
let state |
||||
beforeEach(function () { |
||||
state = { |
||||
metamask: { |
||||
accounts: { |
||||
'0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc': { |
||||
'balance': '0x0', |
||||
'address': '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
}) |
||||
|
||||
it('returns first account if selectedAddress is undefined', function () { |
||||
assert.equal(selectors.getSelectedAddress(state), '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') |
||||
}) |
||||
|
||||
it('returns selectedAddress', function () { |
||||
assert.equal(selectors.getSelectedAddress(mockState), '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') |
||||
}) |
||||
|
||||
}) |
||||
|
||||
it('returns selected identity', function () { |
||||
const identity = selectors.getSelectedIdentity(mockState) |
||||
assert.equal(identity.address, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') |
||||
assert.equal(identity.name, 'Test Account') |
||||
}) |
||||
|
||||
it('returns selected account', function () { |
||||
const account = selectors.getSelectedAccount(mockState) |
||||
assert.equal(account.balance, '0x0') |
||||
assert.equal(account.address, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') |
||||
}) |
||||
|
||||
it('returns selected token from first token list', function () { |
||||
const token = selectors.getSelectedToken(mockState) |
||||
assert.equal(token.address, '0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d') |
||||
assert.equal(token.symbol, 'TEST') |
||||
assert.equal(token.decimals, '0') |
||||
}) |
||||
|
||||
describe('#getSelectedTokenExchangeRate', function () { |
||||
it('returns token exchange rate for first token', function () { |
||||
const tokenRate = selectors.getSelectedTokenExchangeRate(mockState) |
||||
assert.equal(tokenRate, '0.00039345803819379796') |
||||
}) |
||||
}) |
||||
|
||||
|
||||
describe('#getTokenExchangeRate', function () { |
||||
let missingTokenRate |
||||
|
||||
beforeEach(function () { |
||||
missingTokenRate = { |
||||
metamask: { |
||||
'contractExchangeRates': {}, |
||||
}, |
||||
} |
||||
}) |
||||
|
||||
it('returns 0 token exchange rate for a token not in state', function () { |
||||
const tokenRate = selectors.getTokenExchangeRate(missingTokenRate, '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5') |
||||
assert.equal(tokenRate, 0) |
||||
}) |
||||
|
||||
it('returns token exchange rate for specified token in state', function () { |
||||
const tokenRate = selectors.getTokenExchangeRate(mockState, '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5') |
||||
assert.equal(tokenRate, 0.00008189274407698049) |
||||
}) |
||||
|
||||
}) |
||||
|
||||
it('returns conversionRate from state', function () { |
||||
assert.equal(selectors.conversionRateSelector(mockState), 556.12) |
||||
}) |
||||
|
||||
it('returns address book from state', function () { |
||||
const addressBook = selectors.getAddressBook(mockState) |
||||
assert.equal(addressBook[0].address, '0xc42edfcc21ed14dda456aa0756c153f7985d8813') |
||||
assert.equal(addressBook[0].name, '') |
||||
}) |
||||
|
||||
it('returns accounts with balance, address, and name from identity and accounts in state', function () { |
||||
const accountsWithSendEther = selectors.accountsWithSendEtherInfoSelector(mockState) |
||||
assert.equal(accountsWithSendEther.length, 2) |
||||
assert.equal(accountsWithSendEther[0].balance, '0x0') |
||||
assert.equal(accountsWithSendEther[0].address, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') |
||||
assert.equal(accountsWithSendEther[0].name, 'Test Account') |
||||
}) |
||||
|
||||
it('returns selected account with balance, address, and name from accountsWithSendEtherInfoSelector', function () { |
||||
const currentAccountwithSendEther = selectors.getCurrentAccountWithSendEtherInfo(mockState) |
||||
assert.equal(currentAccountwithSendEther.balance, '0x0') |
||||
assert.equal(currentAccountwithSendEther.address, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') |
||||
assert.equal(currentAccountwithSendEther.name, 'Test Account') |
||||
}) |
||||
|
||||
describe('#transactionSelector', function () { |
||||
it('returns transactions from state', function () { |
||||
selectors.transactionsSelector(mockState) |
||||
}) |
||||
}) |
||||
|
||||
it('#getGasIsLoading', () => { |
||||
const gasIsLoading = selectors.getGasIsLoading(mockState) |
||||
assert.equal(gasIsLoading, false) |
||||
}) |
||||
|
||||
describe('Send From', () => { |
||||
it('#getSendFrom', () => { |
||||
const sendFrom = selectors.getSendFrom(mockState) |
||||
assert.equal(sendFrom, '0xc42edfcc21ed14dda456aa0756c153f7985d8813') |
||||
}) |
||||
|
||||
it('#getForceGasMin', () => { |
||||
const forceGasMin = selectors.getForceGasMin(mockState) |
||||
assert.equal(forceGasMin, null) |
||||
}) |
||||
|
||||
it('#getSendAmount', () => { |
||||
const sendAmount = selectors.getSendAmount(mockState) |
||||
assert.equal(sendAmount, '1bc16d674ec80000') |
||||
}) |
||||
|
||||
it('#getSendMaxModeState', () => { |
||||
const sendMaxModeState = selectors.getSendMaxModeState(mockState) |
||||
assert.equal(sendMaxModeState, false) |
||||
}) |
||||
}) |
||||
|
||||
it('#getCurrentCurrency', () => { |
||||
const currentCurrency = selectors.getCurrentCurrency(mockState) |
||||
assert.equal(currentCurrency, 'usd') |
||||
}) |
||||
|
||||
it('#getSelectedTokenToFiatRate', () => { |
||||
const selectedTokenToFiatRate = selectors.getSelectedTokenToFiatRate(mockState) |
||||
assert.equal(selectedTokenToFiatRate, '0.21880988420033493') |
||||
}) |
||||
|
||||
describe('#getSelectedTokenContract', () => { |
||||
|
||||
beforeEach(() => { |
||||
global.eth = new Eth(provider) |
||||
}) |
||||
|
||||
it('', () => { |
||||
const selectedTokenContract = selectors.getSelectedTokenContract(mockState) |
||||
assert(selectedTokenContract.abi) |
||||
}) |
||||
}) |
||||
|
||||
it('#getCurrentViewContext', () => { |
||||
const currentViewContext = selectors.getCurrentViewContext(mockState) |
||||
assert.equal(currentViewContext, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc') |
||||
}) |
||||
|
||||
it('#getTotalUnapprovedCount', () => { |
||||
const totalUnapprovedCount = selectors.getTotalUnapprovedCount(mockState) |
||||
assert.equal(totalUnapprovedCount, 1) |
||||
}) |
||||
}) |
@ -0,0 +1,26 @@ |
||||
const assert = require('assert') |
||||
const etherscanNetworkPrefix = require('../../../ui/lib/etherscan-prefix-for-network') |
||||
|
||||
describe('Etherscan Network Prefix', () => { |
||||
|
||||
it('returns empy string as default value', () => { |
||||
assert.equal(etherscanNetworkPrefix(), '') |
||||
}) |
||||
|
||||
it('returns empty string as a prefix for networkId of 1', () => { |
||||
assert.equal(etherscanNetworkPrefix(1), '') |
||||
}) |
||||
|
||||
it('returns ropsten as prefix for networkId of 3', () => { |
||||
assert.equal(etherscanNetworkPrefix(3), 'ropsten.') |
||||
}) |
||||
|
||||
it('returns rinkeby as prefix for networkId of 4', () => { |
||||
assert.equal(etherscanNetworkPrefix(4), 'rinkeby.') |
||||
}) |
||||
|
||||
it('returs kovan as prefix for networkId of 42', () => { |
||||
assert.equal(etherscanNetworkPrefix(42), 'kovan.') |
||||
}) |
||||
|
||||
}) |
@ -0,0 +1,25 @@ |
||||
import React, { PureComponent } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import classnames from 'classnames' |
||||
|
||||
export default class Card extends PureComponent { |
||||
static propTypes = { |
||||
className: PropTypes.string, |
||||
overrideClassName: PropTypes.bool, |
||||
title: PropTypes.string, |
||||
children: PropTypes.node, |
||||
} |
||||
|
||||
render () { |
||||
const { className, overrideClassName, title } = this.props |
||||
|
||||
return ( |
||||
<div className={classnames({ 'card': !overrideClassName }, className)}> |
||||
<div className="card__title"> |
||||
{ title } |
||||
</div> |
||||
{ this.props.children } |
||||
</div> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
export { default } from './card.component' |
@ -0,0 +1,11 @@ |
||||
.card { |
||||
border-radius: 4px; |
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08); |
||||
padding: 8px; |
||||
|
||||
&__title { |
||||
border-bottom: 1px solid #d8d8d8; |
||||
padding-bottom: 4px; |
||||
text-transform: capitalize; |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
import React from 'react' |
||||
import assert from 'assert' |
||||
import { shallow } from 'enzyme' |
||||
import Card from '../card.component' |
||||
|
||||
describe('Card Component', () => { |
||||
it('should render a card with a title and child element', () => { |
||||
const wrapper = shallow( |
||||
<Card |
||||
title="Test" |
||||
className="card-test-class" |
||||
> |
||||
<div className="child-test-class">Child</div> |
||||
</Card> |
||||
) |
||||
|
||||
assert.ok(wrapper.hasClass('card-test-class')) |
||||
const title = wrapper.find('.card__title') |
||||
assert.ok(title) |
||||
assert.equal(title.text(), 'Test') |
||||
const child = wrapper.find('.child-test-class') |
||||
assert.ok(child) |
||||
assert.equal(child.text(), 'Child') |
||||
}) |
||||
}) |