diff options
Diffstat (limited to 'test')
44 files changed, 4251 insertions, 520 deletions
diff --git a/test/data/2-state.json b/test/data/2-state.json new file mode 100644 index 000000000..d41a403ff --- /dev/null +++ b/test/data/2-state.json @@ -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 +}
\ No newline at end of file diff --git a/test/data/mock-state.json b/test/data/mock-state.json new file mode 100644 index 000000000..7e083c60e --- /dev/null +++ b/test/data/mock-state.json @@ -0,0 +1,1251 @@ +{ + "metamask": { + "network": "4", + "identities": { + "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { + "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", + "name": "Test Account" + }, + "0xc42edfcc21ed14dda456aa0756c153f7985d8813": { + "address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", + "name": "Test Account 2" + } + }, + "unapprovedTxs": { + "8393540981007587": { + "id": 8393540981007587, + "time": 1536268017676, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", + "to": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", + "value": "0x0", + "gas": "0x5208", + "gasPrice": "0x3b9aca00" + }, + "history": [ + { + "id": 8393540981007587, + "time": 1536268017676, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": true, + "txParams": { + "from": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", + "to": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", + "value": "0x0", + "gas": "0x5208", + "gasPrice": "0x3b9aca00" + } + }, + [ + { + "op": "replace", + "path": "/loadingDefaults", + "value": false, + "timestamp": 1536268017685 + }, + { + "op": "add", + "path": "/gasPriceSpecified", + "value": true + }, + { + "op": "add", + "path": "/gasLimitSpecified", + "value": true + }, + { + "op": "add", + "path": "/estimatedGas", + "value": "0x5208" + } + ], + [ + { + "op": "add", + "path": "/origin", + "value": "MetaMask", + "note": "#newUnapprovedTransaction - adding the origin", + "timestamp": 1536268017686 + } + ] + ], + "gasPriceSpecified": true, + "gasLimitSpecified": true, + "estimatedGas": "0x5208", + "origin": "MetaMask" + } + }, + "selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc", + "accounts": { + "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { + "balance": "0x0", + "address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" + }, + "0xc42edfcc21ed14dda456aa0756c153f7985d8813": { + "address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", + "balance": "0x0" + } + }, + "tokens": [ + { + "address": "0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d", + "symbol": "TEST", + "decimals": "0" + }, + { + "address": "0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5", + "decimals": "8", + "symbol": "TEST2" + }, + { + "address": "0x617b3f8050a0bd94b6b1da02b4384ee5b4df13f4", + "symbol": "META", + "decimals": "18" + } + ], + "contractExchangeRates": { + "0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d": 0.00039345803819379796, + "0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5": 0.00008189274407698049 + }, + "currentCurrency": "usd", + "conversionRate": 556.12, + "addressBook": [ + { + "address": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", + "name": "" + } + ], + "selectedTokenAddress": "0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d", + "unapprovedMsgs": {}, + "unapprovedMsgCount": 0, + "unapprovedPersonalMsgs": {}, + "unapprovedPersonalMsgCount": 0, + "unapprovedTypedMessages": {}, + "unapprovedTypedMessagesCount": 0, + "send": { + "gasLimit": "0x5208", + "gasPrice": "0xee6b2800", + "gasTotal": "0x4c65c6294000", + "tokenBalance": null, + "from": "0xc42edfcc21ed14dda456aa0756c153f7985d8813", + "to": "", + "amount": "1bc16d674ec80000", + "memo": "", + "errors": {}, + "maxModeOn": false, + "editingTransactionId": null, + "forceGasMin": null, + "toNickname": "" + }, + "selectedAddressTxList": [ + { + "id": 3387511061307736, + "time": 1528133130531, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x92e659448c48fc926ec942d0da1459260d36bb33", + "value": "0x1bc16d674ec80000", + "gas": "0xcf08", + "gasPrice": "0x3b9aca00", + "nonce": "0xb5" + }, + "history": [ + { + "id": 3387511061307736, + "time": 1528133130531, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": true, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x92e659448c48fc926ec942d0da1459260d36bb33", + "value": "0x1bc16d674ec80000", + "gas": "0xcf08", + "gasPrice": "0x3b9aca00" + } + }, + [ + { + "op": "replace", + "path": "/loadingDefaults", + "value": false, + "timestamp": 1528133130666 + }, + { + "op": "add", + "path": "/gasPriceSpecified", + "value": true + }, + { + "op": "add", + "path": "/gasLimitSpecified", + "value": true + }, + { + "op": "add", + "path": "/estimatedGas", + "value": "0xcf08" + } + ], + [ + { + "op": "add", + "path": "/origin", + "value": "MetaMask", + "note": "#newUnapprovedTransaction - adding the origin", + "timestamp": 1528133130667 + } + ], + [], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved", + "timestamp": 1528133131716 + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0xb5", + "note": "transactions#approveTransaction", + "timestamp": 1528133131806 + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 181, + "nextNetworkNonce": 181 + }, + "local": { + "name": "local", + "nonce": 181, + "details": { + "startPoint": 181, + "highest": 181 + } + }, + "network": { + "name": "network", + "nonce": 181, + "details": { + "baseCount": 181 + } + } + } + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "signed", + "note": "transactions#publishTransaction", + "timestamp": 1528133131825 + }, + { + "op": "add", + "path": "/rawTx", + "value": "0xf86c81b5843b9aca0082cf089492e659448c48fc926ec942d0da1459260d36bb33881bc16d674ec80000802ba03f879cd33a31180da38545d0f809822e00ddf35954d8b0ece83bacf22347ce54a06ad050487978e425ca6a014ed55ea8e9a190069863ed96a0eefa88d729ea1eda" + } + ], + [], + [ + { + "op": "add", + "path": "/hash", + "value": "0x516b77569173a04c76fdb6545cf279ebd0c75f5d25d6e4ce019925205f0e3709", + "note": "transactions#setTxHash", + "timestamp": 1528133131951 + } + ], + [ + { + "op": "add", + "path": "/submittedTime", + "value": 1528133131951, + "note": "txStateManager - add submitted time stamp", + "timestamp": 1528133131952 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted", + "timestamp": 1528133131955 + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x24af6b", + "note": "transactions/pending-tx-tracker#event: tx:block-update", + "timestamp": 1528133134414 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "confirmed", + "note": "txStateManager: setting status to confirmed", + "timestamp": 1528133158516 + } + ] + ], + "gasPriceSpecified": true, + "gasLimitSpecified": true, + "estimatedGas": "0xcf08", + "origin": "MetaMask", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 181, + "nextNetworkNonce": 181 + }, + "local": { + "name": "local", + "nonce": 181, + "details": { + "startPoint": 181, + "highest": 181 + } + }, + "network": { + "name": "network", + "nonce": 181, + "details": { + "baseCount": 181 + } + } + }, + "rawTx": "0xf86c81b5843b9aca0082cf089492e659448c48fc926ec942d0da1459260d36bb33881bc16d674ec80000802ba03f879cd33a31180da38545d0f809822e00ddf35954d8b0ece83bacf22347ce54a06ad050487978e425ca6a014ed55ea8e9a190069863ed96a0eefa88d729ea1eda", + "hash": "0x516b77569173a04c76fdb6545cf279ebd0c75f5d25d6e4ce019925205f0e3709", + "submittedTime": 1528133131951, + "firstRetryBlockNumber": "0x24af6b" + }, + { + "id": 3387511061307737, + "time": 1528133149983, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x92e659448c48fc926ec942d0da1459260d36bb33", + "value": "0x1bc16d674ec80000", + "gas": "0xcf08", + "gasPrice": "0x3b9aca00", + "nonce": "0xb6" + }, + "history": [ + { + "id": 3387511061307737, + "time": 1528133149983, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": true, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x92e659448c48fc926ec942d0da1459260d36bb33", + "value": "0x1bc16d674ec80000", + "gas": "0xcf08", + "gasPrice": "0x3b9aca00" + } + }, + [ + { + "op": "replace", + "path": "/loadingDefaults", + "value": false, + "timestamp": 1528133150011 + }, + { + "op": "add", + "path": "/gasPriceSpecified", + "value": true + }, + { + "op": "add", + "path": "/gasLimitSpecified", + "value": true + }, + { + "op": "add", + "path": "/estimatedGas", + "value": "0xcf08" + } + ], + [ + { + "op": "add", + "path": "/origin", + "value": "MetaMask", + "note": "#newUnapprovedTransaction - adding the origin", + "timestamp": 1528133150013 + } + ], + [], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved", + "timestamp": 1528133151102 + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0xb6", + "note": "transactions#approveTransaction", + "timestamp": 1528133151189 + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 181, + "nextNetworkNonce": 181 + }, + "local": { + "name": "local", + "nonce": 182, + "details": { + "startPoint": 181, + "highest": 182 + } + }, + "network": { + "name": "network", + "nonce": 181, + "details": { + "baseCount": 181 + } + } + } + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "signed", + "note": "transactions#publishTransaction", + "timestamp": 1528133151203 + }, + { + "op": "add", + "path": "/rawTx", + "value": "0xf86c81b6843b9aca0082cf089492e659448c48fc926ec942d0da1459260d36bb33881bc16d674ec80000802ba0692deaabf0d79544d41e7c475ad43760679a4f25d0fee908b1da308db1a291a7a0384db85fc6c843ea25986a0760f3c50ab6504fc559fc71fc7f23f60950eb316d" + } + ], + [], + [ + { + "op": "add", + "path": "/hash", + "value": "0x9271b266d05022cfa841362fae43763ebafcee540d84278b0157ef4a68d4e26f", + "note": "transactions#setTxHash", + "timestamp": 1528133151342 + } + ], + [ + { + "op": "add", + "path": "/submittedTime", + "value": 1528133151347, + "note": "txStateManager - add submitted time stamp", + "timestamp": 1528133151347 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted", + "timestamp": 1528133151368 + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x24af6d", + "note": "transactions/pending-tx-tracker#event: tx:block-update", + "timestamp": 1528133158532 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "confirmed", + "note": "txStateManager: setting status to confirmed", + "timestamp": 1528133190636 + } + ] + ], + "gasPriceSpecified": true, + "gasLimitSpecified": true, + "estimatedGas": "0xcf08", + "origin": "MetaMask", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 0, + "highestSuggested": 181, + "nextNetworkNonce": 181 + }, + "local": { + "name": "local", + "nonce": 182, + "details": { + "startPoint": 181, + "highest": 182 + } + }, + "network": { + "name": "network", + "nonce": 181, + "details": { + "baseCount": 181 + } + } + }, + "rawTx": "0xf86c81b6843b9aca0082cf089492e659448c48fc926ec942d0da1459260d36bb33881bc16d674ec80000802ba0692deaabf0d79544d41e7c475ad43760679a4f25d0fee908b1da308db1a291a7a0384db85fc6c843ea25986a0760f3c50ab6504fc559fc71fc7f23f60950eb316d", + "hash": "0x9271b266d05022cfa841362fae43763ebafcee540d84278b0157ef4a68d4e26f", + "submittedTime": 1528133151347, + "firstRetryBlockNumber": "0x24af6d" + }, + { + "id": 3387511061307738, + "time": 1528133180635, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x92e659448c48fc926ec942d0da1459260d36bb33", + "value": "0x1bc16d674ec80000", + "gas": "0xcf08", + "gasPrice": "0x12a05f200", + "nonce": "0xb7" + }, + "history": [ + { + "id": 3387511061307738, + "time": 1528133180635, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": true, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x92e659448c48fc926ec942d0da1459260d36bb33", + "value": "0x1bc16d674ec80000", + "gas": "0xcf08", + "gasPrice": "0x12a05f200" + } + }, + [ + { + "op": "replace", + "path": "/loadingDefaults", + "value": false, + "timestamp": 1528133180720 + }, + { + "op": "add", + "path": "/gasPriceSpecified", + "value": true + }, + { + "op": "add", + "path": "/gasLimitSpecified", + "value": true + }, + { + "op": "add", + "path": "/estimatedGas", + "value": "0xcf08" + } + ], + [ + { + "op": "add", + "path": "/origin", + "value": "MetaMask", + "note": "#newUnapprovedTransaction - adding the origin", + "timestamp": 1528133180722 + } + ], + [], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved", + "timestamp": 1528133181623 + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0xb7", + "note": "transactions#approveTransaction", + "timestamp": 1528133181726 + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocallyConfirmed": 182, + "highestSuggested": 182, + "nextNetworkNonce": 182 + }, + "local": { + "name": "local", + "nonce": 183, + "details": { + "startPoint": 182, + "highest": 183 + } + }, + "network": { + "name": "network", + "nonce": 182, + "details": { + "baseCount": 182 + } + } + } + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "signed", + "note": "transactions#publishTransaction", + "timestamp": 1528133181749 + }, + { + "op": "add", + "path": "/rawTx", + "value": "0xf86d81b785012a05f20082cf089492e659448c48fc926ec942d0da1459260d36bb33881bc16d674ec80000802ba086f9846798be6988c39a5cf85f0dbe267e59ca0b96a6a7077e92cba33e10a258a064ffa52ac90c238ce21e6f085283216191b185a1eccd7daae6e2ab66ba26ada0" + } + ], + [], + [ + { + "op": "add", + "path": "/hash", + "value": "0x4e061e977c099735bc9e5203e717f7d9dccb3fcb2f82031a12a3ed326f95d43b", + "note": "transactions#setTxHash", + "timestamp": 1528133181885 + } + ], + [ + { + "op": "add", + "path": "/submittedTime", + "value": 1528133181885, + "note": "txStateManager - add submitted time stamp", + "timestamp": 1528133181885 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted", + "timestamp": 1528133181888 + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x24af6f", + "note": "transactions/pending-tx-tracker#event: tx:block-update", + "timestamp": 1528133190653 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "confirmed", + "note": "txStateManager: setting status to confirmed", + "timestamp": 1528133222745 + } + ] + ], + "gasPriceSpecified": true, + "gasLimitSpecified": true, + "estimatedGas": "0xcf08", + "origin": "MetaMask", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 182, + "highestSuggested": 182, + "nextNetworkNonce": 182 + }, + "local": { + "name": "local", + "nonce": 183, + "details": { + "startPoint": 182, + "highest": 183 + } + }, + "network": { + "name": "network", + "nonce": 182, + "details": { + "baseCount": 182 + } + } + }, + "rawTx": "0xf86d81b785012a05f20082cf089492e659448c48fc926ec942d0da1459260d36bb33881bc16d674ec80000802ba086f9846798be6988c39a5cf85f0dbe267e59ca0b96a6a7077e92cba33e10a258a064ffa52ac90c238ce21e6f085283216191b185a1eccd7daae6e2ab66ba26ada0", + "hash": "0x4e061e977c099735bc9e5203e717f7d9dccb3fcb2f82031a12a3ed326f95d43b", + "submittedTime": 1528133181885, + "firstRetryBlockNumber": "0x24af6f" + }, + { + "id": 3387511061307739, + "time": 1528133223918, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0xfe2149773b3513703e79ad23d05a778a185016ee", + "value": "0xaa87bee538000", + "data": "0xea94496b000000000000000000000000000000000000000000000000000000000001e1eb000000000000000000000000000000000000000000000000000000000001de33", + "gasPrice": "0x3b9aca00", + "gas": "0x6169e", + "nonce": "0xb8" + }, + "history": [ + { + "id": 3387511061307739, + "time": 1528133223918, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": true, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0xfe2149773b3513703e79ad23d05a778a185016ee", + "value": "0xaa87bee538000", + "data": "0xea94496b000000000000000000000000000000000000000000000000000000000001e1eb000000000000000000000000000000000000000000000000000000000001de33", + "gasPrice": "0x3b9aca00" + } + }, + [ + { + "op": "add", + "path": "/txParams/gas", + "value": "0x6169e", + "timestamp": 1528133225488 + }, + { + "op": "replace", + "path": "/loadingDefaults", + "value": false + }, + { + "op": "add", + "path": "/gasPriceSpecified", + "value": true + }, + { + "op": "add", + "path": "/gasLimitSpecified", + "value": false + }, + { + "op": "add", + "path": "/estimatedGas", + "value": "40f14" + } + ], + [ + { + "op": "replace", + "path": "/estimatedGas", + "value": "40f14", + "note": "#newUnapprovedTransaction - adding the origin", + "timestamp": 1528133225492 + }, + { + "op": "add", + "path": "/origin", + "value": "crypko.ai" + } + ], + [], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved", + "timestamp": 1528133227279 + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0xb8", + "note": "transactions#approveTransaction", + "timestamp": 1528133227374 + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocallyConfirmed": 184, + "highestSuggested": 184, + "nextNetworkNonce": 184 + }, + "local": { + "name": "local", + "nonce": 184, + "details": { + "startPoint": 184, + "highest": 184 + } + }, + "network": { + "name": "network", + "nonce": 184, + "details": { + "baseCount": 184 + } + } + } + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "signed", + "note": "transactions#publishTransaction", + "timestamp": 1528133227405 + }, + { + "op": "add", + "path": "/rawTx", + "value": "0xf8b181b8843b9aca008306169e94fe2149773b3513703e79ad23d05a778a185016ee870aa87bee538000b844ea94496b000000000000000000000000000000000000000000000000000000000001e1eb000000000000000000000000000000000000000000000000000000000001de332ca07bb2efbb8529d67606f9f89e7934c594a31d50c7d24a3286c20a2944a3b8c2a9a07b55ebd8aa28728ce0e38dd3b3503b78fccedae80053626d8649c68346c7c49c" + } + ], + [], + [ + { + "op": "add", + "path": "/hash", + "value": "0x466ae7d4b7c270121f0a8d68fbc6c9091ffc4aa976a553a5bfa56a79cf9f63dd", + "note": "transactions#setTxHash", + "timestamp": 1528133227534 + } + ], + [ + { + "op": "add", + "path": "/submittedTime", + "value": 1528133227538, + "note": "txStateManager - add submitted time stamp", + "timestamp": 1528133227538 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted", + "timestamp": 1528133227543 + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x24af72", + "note": "transactions/pending-tx-tracker#event: tx:block-update", + "timestamp": 1528133238980 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "confirmed", + "note": "txStateManager: setting status to confirmed", + "timestamp": 1528133255035 + } + ] + ], + "gasPriceSpecified": true, + "gasLimitSpecified": false, + "estimatedGas": "40f14", + "origin": "crypko.ai", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 184, + "highestSuggested": 184, + "nextNetworkNonce": 184 + }, + "local": { + "name": "local", + "nonce": 184, + "details": { + "startPoint": 184, + "highest": 184 + } + }, + "network": { + "name": "network", + "nonce": 184, + "details": { + "baseCount": 184 + } + } + }, + "rawTx": "0xf8b181b8843b9aca008306169e94fe2149773b3513703e79ad23d05a778a185016ee870aa87bee538000b844ea94496b000000000000000000000000000000000000000000000000000000000001e1eb000000000000000000000000000000000000000000000000000000000001de332ca07bb2efbb8529d67606f9f89e7934c594a31d50c7d24a3286c20a2944a3b8c2a9a07b55ebd8aa28728ce0e38dd3b3503b78fccedae80053626d8649c68346c7c49c", + "hash": "0x466ae7d4b7c270121f0a8d68fbc6c9091ffc4aa976a553a5bfa56a79cf9f63dd", + "submittedTime": 1528133227538, + "firstRetryBlockNumber": "0x24af72" + }, + { + "id": 3387511061307740, + "time": 1528133291381, + "status": "confirmed", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d", + "value": "0x0", + "data": "0xa9059cbb00000000000000000000000092e659448c48fc926ec942d0da1459260d36bb330000000000000000000000000000000000000000000000000000000000000002", + "gas": "0xd508", + "gasPrice": "0x3b9aca00", + "nonce": "0xb9" + }, + "history": [ + { + "id": 3387511061307740, + "time": 1528133291381, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": true, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d", + "value": "0x0", + "data": "0xa9059cbb00000000000000000000000092e659448c48fc926ec942d0da1459260d36bb330000000000000000000000000000000000000000000000000000000000000002", + "gas": "0xd508", + "gasPrice": "0x3b9aca00" + } + }, + [ + { + "op": "replace", + "path": "/loadingDefaults", + "value": false, + "timestamp": 1528133291486 + }, + { + "op": "add", + "path": "/gasPriceSpecified", + "value": true + }, + { + "op": "add", + "path": "/gasLimitSpecified", + "value": true + }, + { + "op": "add", + "path": "/estimatedGas", + "value": "0xd508" + } + ], + [ + { + "op": "add", + "path": "/origin", + "value": "MetaMask", + "note": "#newUnapprovedTransaction - adding the origin", + "timestamp": 1528133291486 + } + ], + [], + [ + { + "op": "replace", + "path": "/status", + "value": "approved", + "note": "txStateManager: setting status to approved", + "timestamp": 1528133293588 + } + ], + [ + { + "op": "add", + "path": "/txParams/nonce", + "value": "0xb9", + "note": "transactions#approveTransaction", + "timestamp": 1528133293706 + }, + { + "op": "add", + "path": "/nonceDetails", + "value": { + "params": { + "highestLocallyConfirmed": 185, + "highestSuggested": 185, + "nextNetworkNonce": 185 + }, + "local": { + "name": "local", + "nonce": 185, + "details": { + "startPoint": 185, + "highest": 185 + } + }, + "network": { + "name": "network", + "nonce": 185, + "details": { + "baseCount": 185 + } + } + } + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "signed", + "note": "transactions#publishTransaction", + "timestamp": 1528133293724 + }, + { + "op": "add", + "path": "/rawTx", + "value": "0xf8a981b9843b9aca0082d50894108cf70c7d384c552f42c07c41c0e1e46d77ea0d80b844a9059cbb00000000000000000000000092e659448c48fc926ec942d0da1459260d36bb3300000000000000000000000000000000000000000000000000000000000000022ca04f05310490d3e3a9a159ae25f52cec9afb0a69527d30be832aaae12e64ff056ea075f81a5220bed481e764bab8830c57169c59fe528ca9cf3442f47f7618a9b4a9" + } + ], + [], + [ + { + "op": "add", + "path": "/hash", + "value": "0x3680dc9815cd05b620b6dd0017d949604ca7d92f051d5542fc8a5ecaa876af09", + "note": "transactions#setTxHash", + "timestamp": 1528133293853 + } + ], + [ + { + "op": "add", + "path": "/submittedTime", + "value": 1528133293859, + "note": "txStateManager - add submitted time stamp", + "timestamp": 1528133293862 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "submitted", + "note": "txStateManager: setting status to submitted", + "timestamp": 1528133293867 + } + ], + [ + { + "op": "add", + "path": "/firstRetryBlockNumber", + "value": "0x24af76", + "note": "transactions/pending-tx-tracker#event: tx:block-update", + "timestamp": 1528133295200 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "confirmed", + "note": "txStateManager: setting status to confirmed", + "timestamp": 1528133327522 + } + ] + ], + "gasPriceSpecified": true, + "gasLimitSpecified": true, + "estimatedGas": "0xd508", + "origin": "MetaMask", + "nonceDetails": { + "params": { + "highestLocallyConfirmed": 185, + "highestSuggested": 185, + "nextNetworkNonce": 185 + }, + "local": { + "name": "local", + "nonce": 185, + "details": { + "startPoint": 185, + "highest": 185 + } + }, + "network": { + "name": "network", + "nonce": 185, + "details": { + "baseCount": 185 + } + } + }, + "rawTx": "0xf8a981b9843b9aca0082d50894108cf70c7d384c552f42c07c41c0e1e46d77ea0d80b844a9059cbb00000000000000000000000092e659448c48fc926ec942d0da1459260d36bb3300000000000000000000000000000000000000000000000000000000000000022ca04f05310490d3e3a9a159ae25f52cec9afb0a69527d30be832aaae12e64ff056ea075f81a5220bed481e764bab8830c57169c59fe528ca9cf3442f47f7618a9b4a9", + "hash": "0x3680dc9815cd05b620b6dd0017d949604ca7d92f051d5542fc8a5ecaa876af09", + "submittedTime": 1528133293859, + "firstRetryBlockNumber": "0x24af76" + }, + { + "id": 3387511061307741, + "time": 1528133318440, + "status": "rejected", + "metamaskNetworkId": "4", + "loadingDefaults": false, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "value": "0x0", + "gasPrice": "0x3b9aca00", + "gas": "0x5208" + }, + "history": [ + { + "id": 3387511061307741, + "time": 1528133318440, + "status": "unapproved", + "metamaskNetworkId": "4", + "loadingDefaults": true, + "txParams": { + "from": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62", + "to": "0x3b222de3aaba8ec9771ca9e9af5d8ed757fb7f62" + } + }, + [ + { + "op": "add", + "path": "/txParams/value", + "value": "0x0", + "timestamp": 1528133319641 + }, + { + "op": "add", + "path": "/txParams/gasPrice", + "value": "0x3b9aca00" + }, + { + "op": "add", + "path": "/txParams/gas", + "value": "0x5208" + }, + { + "op": "replace", + "path": "/loadingDefaults", + "value": false + }, + { + "op": "add", + "path": "/gasPriceSpecified", + "value": false + }, + { + "op": "add", + "path": "/gasLimitSpecified", + "value": false + }, + { + "op": "add", + "path": "/simpleSend", + "value": true + }, + { + "op": "add", + "path": "/estimatedGas", + "value": "0x5208" + } + ], + [ + { + "op": "add", + "path": "/origin", + "value": "tmashuang.github.io", + "note": "#newUnapprovedTransaction - adding the origin", + "timestamp": 1528133319642 + } + ], + [ + { + "op": "replace", + "path": "/status", + "value": "rejected", + "note": "txStateManager: setting status to rejected", + "timestamp": 1528133320924 + } + ] + ], + "gasPriceSpecified": false, + "gasLimitSpecified": false, + "simpleSend": true, + "estimatedGas": "0x5208", + "origin": "tmashuang.github.io" + } + ] + }, + "appState": { + "gasIsLoading": false, + "currentView": { + "name": "accountDetail", + "detailView": null, + "context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc" + } + } +}
\ No newline at end of file diff --git a/test/e2e/beta/drizzle.spec.js b/test/e2e/beta/drizzle.spec.js new file mode 100644 index 000000000..ff4b4b74d --- /dev/null +++ b/test/e2e/beta/drizzle.spec.js @@ -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}$/)) + }) + }) +}) diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js index 1261b6f95..32aaa29a6 100644 --- a/test/e2e/beta/from-import-beta-ui.spec.js +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -314,12 +314,12 @@ describe('Using MetaMask with an existing account', function () { }) it('finds the transaction in the transactions list', async function () { - const transactions = await findElements(driver, By.css('.tx-list-item')) + const transactions = await findElements(driver, By.css('.transaction-list-item')) assert.equal(transactions.length, 1) - const txValues = await findElements(driver, By.css('.tx-list-value')) + const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) assert.equal(txValues.length, 1) - assert.equal(await txValues[0].getText(), '1 ETH') + assert.equal(await txValues[0].getText(), '-1 ETH') }) }) diff --git a/test/e2e/beta/helpers.js b/test/e2e/beta/helpers.js index d90cd5d66..4055d8155 100644 --- a/test/e2e/beta/helpers.js +++ b/test/e2e/beta/helpers.js @@ -2,8 +2,8 @@ const fs = require('fs') const mkdirp = require('mkdirp') const pify = require('pify') const assert = require('assert') -const {until} = require('selenium-webdriver') const { delay } = require('../func') +const { until } = require('selenium-webdriver') module.exports = { assertElementNotPresent, @@ -126,10 +126,7 @@ async function assertElementNotPresent (webdriver, driver, by) { try { dataTab = await findElement(driver, by, 4000) } catch (err) { - console.log(err) assert(err instanceof webdriver.error.NoSuchElementError || err instanceof webdriver.error.TimeoutError) } - if (dataTab) { - assert(false, 'Data tab should not be present') - } + assert.ok(!dataTab, 'Found element that should not be present') } diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index 43300bda6..8d1ecac0d 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -225,19 +225,9 @@ describe('MetaMask', function () { await delay(regularDelayMs) } - await clickWordAndWait(words[0]) - await clickWordAndWait(words[1]) - await clickWordAndWait(words[2]) - await clickWordAndWait(words[3]) - await clickWordAndWait(words[4]) - await clickWordAndWait(words[5]) - await clickWordAndWait(words[6]) - await clickWordAndWait(words[7]) - await clickWordAndWait(words[8]) - await clickWordAndWait(words[9]) - await clickWordAndWait(words[10]) - await clickWordAndWait(words[11]) - + for (let i = 0; i < 12; i++) { + await clickWordAndWait(words[i]) + } } catch (e) { if (count > 2) { throw e @@ -281,6 +271,17 @@ describe('MetaMask', function () { await driver.wait(until.stalenessOf(accountModal)) await delay(regularDelayMs) }) + it('show account details dropdown menu', async () => { + + const {width, height} = await driver.manage().window().getSize() + driver.manage().window().setSize(320, 480) + await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() + const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) + assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option + await delay(regularDelayMs) + driver.manage().window().setSize(width, height) + + }) }) describe('Log out an log back in', () => { @@ -414,12 +415,12 @@ describe('MetaMask', function () { }) it('finds the transaction in the transactions list', async function () { - const transactions = await findElements(driver, By.css('.tx-list-item')) + const transactions = await findElements(driver, By.css('.transaction-list-item')) assert.equal(transactions.length, 1) if (process.env.SELENIUM_BROWSER !== 'firefox') { - const txValues = await findElement(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txValues, /1\sETH/), 10000) + const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues, /-1\sETH/), 10000) } }) }) @@ -457,16 +458,11 @@ describe('MetaMask', function () { }) it('finds the transaction in the transactions list', async function () { - const transactions = await findElements(driver, By.css('.tx-list-item')) + const transactions = await findElements(driver, By.css('.transaction-list-item')) assert.equal(transactions.length, 2) - await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`)) - - const txStatuses = await findElements(driver, By.css('.tx-list-status')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/)) - - const txValues = await findElement(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txValues, /3\sETH/), 10000) + const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues, /-3\sETH/), 10000) }) }) @@ -489,9 +485,9 @@ describe('MetaMask', function () { await driver.switchTo().window(extension) await delay(regularDelayMs) - const txListItem = await findElement(driver, By.xpath(`//span[contains(text(), 'Contract Deployment')]`)) + const txListItem = await findElement(driver, By.xpath(`//div[contains(text(), 'Contract Deployment')]`)) await txListItem.click() - await delay(regularDelayMs) + await delay(largeDelayMs) }) it('displays the contract creation data', async () => { @@ -513,15 +509,15 @@ describe('MetaMask', function () { it('confirms a deploy contract transaction', async () => { const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) await confirmButton.click() - await delay(regularDelayMs) - - await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`)) + await delay(largeDelayMs) - const txStatuses = await findElements(driver, By.css('.tx-list-status')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/)) + driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 3 + }, 10000) - const txAccounts = await findElements(driver, By.css('.tx-list-account')) - assert.equal(await txAccounts[0].getText(), 'Contract Deployment') + const txAction = await findElements(driver, By.css('.transaction-list-item__action')) + await driver.wait(until.elementTextMatches(txAction[0], /Contract\sDeployment/), 10000) await delay(regularDelayMs) }) @@ -542,9 +538,9 @@ describe('MetaMask', function () { await driver.switchTo().window(extension) await delay(largeDelayMs) - await findElements(driver, By.css('.tx-list-pending-item-container')) - const [txListValue] = await findElements(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txListValue, /4\sETH/), 10000) + await findElements(driver, By.css('.transaction-list-item')) + const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txListValue, /-4\sETH/), 10000) await txListValue.click() await delay(regularDelayMs) @@ -572,15 +568,17 @@ describe('MetaMask', function () { await confirmButton.click() await delay(regularDelayMs) - const txStatuses = await findElements(driver, By.css('.tx-list-status')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/)) + driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 4 + }, 10000) - const txValues = await findElement(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txValues, /4\sETH/), 10000) + const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues[0], /-4\sETH/), 10000) - const txAccounts = await findElements(driver, By.css('.tx-list-account')) - const firstTxAddress = await txAccounts[0].getText() - assert(firstTxAddress.match(/^0x\w{8}\.{3}\w{4}$/)) + // const txAccounts = await findElements(driver, By.css('.tx-list-account')) + // const firstTxAddress = await txAccounts[0].getText() + // assert(firstTxAddress.match(/^0x\w{8}\.{3}\w{4}$/)) }) it('calls and confirms a contract method where ETH is received', async () => { @@ -594,7 +592,7 @@ describe('MetaMask', function () { await driver.switchTo().window(extension) await delay(regularDelayMs) - const txListItem = await findElement(driver, By.css('.tx-list-item')) + const txListItem = await findElement(driver, By.css('.transaction-list-item')) await txListItem.click() await delay(regularDelayMs) @@ -602,18 +600,20 @@ describe('MetaMask', function () { await confirmButton.click() await delay(regularDelayMs) - const txStatuses = await findElements(driver, By.css('.tx-list-status')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/)) + driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 5 + }, 10000) - const txValues = await findElement(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txValues, /0\sETH/), 10000) + const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues, /-0\sETH/), 10000) await closeAllWindowHandlesExcept(driver, [extension, dapp]) await driver.switchTo().window(extension) }) it('renders the correct ETH balance', async () => { - const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount')) + const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) await delay(regularDelayMs) if (process.env.SELENIUM_BROWSER !== 'firefox') { await driver.wait(until.elementTextMatches(balance, /^92.*ETH.*$/), 10000) @@ -626,20 +626,21 @@ describe('MetaMask', function () { describe('Add a custom token from a dapp', () => { it('creates a new token', async () => { - const windowHandles = await driver.getAllWindowHandles() + let windowHandles = await driver.getAllWindowHandles() const extension = windowHandles[0] const dapp = windowHandles[1] await delay(regularDelayMs * 2) await driver.switchTo().window(dapp) - await delay(regularDelayMs) + await delay(regularDelayMs * 2) const createToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Create Token')]`)) await createToken.click() - await delay(regularDelayMs) + await delay(largeDelayMs) - await driver.switchTo().window(extension) - await loadExtension(driver, extensionId) + windowHandles = await driver.getAllWindowHandles() + const popup = windowHandles[2] + await driver.switchTo().window(popup) await delay(regularDelayMs) const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) @@ -657,18 +658,17 @@ describe('MetaMask', function () { await closeAllWindowHandlesExcept(driver, [extension, dapp]) await delay(regularDelayMs) await driver.switchTo().window(extension) - await delay(regularDelayMs) - + await delay(largeDelayMs) }) it('clicks on the Add Token button', async () => { - const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`)) + const addToken = await driver.findElement(By.css('.wallet-view__add-token-button')) await addToken.click() await delay(regularDelayMs) }) it('picks the newly created Test token', async () => { - const addCustomToken = await findElement(driver, By.xpath("//div[contains(text(), 'Custom Token')]")) + const addCustomToken = await findElement(driver, By.xpath("//li[contains(text(), 'Custom Token')]")) await addCustomToken.click() await delay(regularDelayMs) @@ -686,7 +686,7 @@ describe('MetaMask', function () { }) it('renders the balance for the new token', async () => { - const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount')) + const balance = await findElement(driver, By.css('.transaction-view-balance .transaction-view-balance__token-balance')) await driver.wait(until.elementTextMatches(balance, /^100\s*TST\s*$/)) const tokenAmount = await balance.getText() assert.ok(/^100\s*TST\s*$/.test(tokenAmount)) @@ -755,21 +755,25 @@ describe('MetaMask', function () { }) it('finds the transaction in the transactions list', async function () { - const transactions = await findElements(driver, By.css('.tx-list-item')) + const transactions = await findElements(driver, By.css('.transaction-list-item')) assert.equal(transactions.length, 1) - const txValues = await findElements(driver, By.css('.tx-list-value')) + const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) assert.equal(txValues.length, 1) // test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved, // or possibly until we use latest version of firefox in the tests if (process.env.SELENIUM_BROWSER !== 'firefox') { - await driver.wait(until.elementTextMatches(txValues[0], /50\sTST/), 10000) + await driver.wait(until.elementTextMatches(txValues[0], /-50\sTST/), 10000) } - const txStatuses = await findElements(driver, By.css('.tx-list-status')) - const tx = await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed|Failed/), 10000) - assert.equal(await tx.getText(), 'Confirmed') + driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 1 + }, 10000) + const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) + const tx = await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken|Failed/), 10000) + assert.equal(await tx.getText(), 'Sent Tokens') }) }) @@ -792,9 +796,9 @@ describe('MetaMask', function () { await driver.switchTo().window(extension) await delay(largeDelayMs) - await findElements(driver, By.css('.tx-list-pending-item-container')) - const [txListValue] = await findElements(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txListValue, /7\sTST/), 10000) + await findElements(driver, By.css('.transaction-list__pending-transactions')) + const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/), 10000) await txListValue.click() await delay(regularDelayMs) @@ -841,25 +845,28 @@ describe('MetaMask', function () { }) it('finds the transaction in the transactions list', async function () { - const transactions = await findElements(driver, By.css('.tx-list-item')) - assert.equal(transactions.length, 2) + driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 2 + }, 10000) - const txValues = await findElements(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txValues[0], /7\sTST/)) - const txStatuses = await findElements(driver, By.css('.tx-list-status')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/)) + const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/)) + const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) + await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/)) const walletBalance = await findElement(driver, By.css('.wallet-balance')) await walletBalance.click() const tokenListItems = await findElements(driver, By.css('.token-list-item')) await tokenListItems[0].click() + await delay(regularDelayMs) // test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved, // or possibly until we use latest version of firefox in the tests if (process.env.SELENIUM_BROWSER !== 'firefox') { - const tokenBalanceAmount = await findElement(driver, By.css('.token-balance__amount')) - assert.equal(await tokenBalanceAmount.getText(), '43') + const tokenBalanceAmount = await findElement(driver, By.css('.transaction-view-balance__token-balance')) + assert.equal(await tokenBalanceAmount.getText(), '43 TST') } }) }) @@ -883,9 +890,14 @@ describe('MetaMask', function () { await driver.switchTo().window(extension) await delay(regularDelayMs) - const [txListItem] = await findElements(driver, By.css('.tx-list-item')) - const [txListValue] = await findElements(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txListValue, /0\sETH/)) + driver.wait(async () => { + const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item')) + return pendingTxes.length === 1 + }, 10000) + + const [txListItem] = await findElements(driver, By.css('.transaction-list-item')) + const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/)) await txListItem.click() await delay(regularDelayMs) }) @@ -956,10 +968,15 @@ describe('MetaMask', function () { }) it('finds the transaction in the transactions list', async function () { - const txValues = await findElements(driver, By.css('.tx-list-value')) - await driver.wait(until.elementTextMatches(txValues[0], /0\sETH/)) - const txStatuses = await findElements(driver, By.css('.tx-list-status')) - await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/)) + driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 3 + }, 10000) + + const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/)) + const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) + await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/)) }) }) @@ -1009,9 +1026,59 @@ describe('MetaMask', function () { }) it('renders the balance for the chosen token', async () => { - const balance = await findElement(driver, By.css('.tx-view .balance-display .token-amount')) + const balance = await findElement(driver, By.css('.transaction-view-balance__token-balance')) await driver.wait(until.elementTextMatches(balance, /0\sBAT/)) await delay(regularDelayMs) }) }) + + describe('Stores custom RPC history', () => { + const customRpcUrls = [ + 'https://mainnet.infura.io/1', + 'https://mainnet.infura.io/2', + 'https://mainnet.infura.io/3', + 'https://mainnet.infura.io/4', + ] + + customRpcUrls.forEach(customRpcUrl => { + it(`creates custom RPC: ${customRpcUrl}`, async () => { + const networkDropdown = await findElement(driver, By.css('.network-name')) + await networkDropdown.click() + await delay(regularDelayMs) + + const customRpcButton = await findElement(driver, By.xpath(`//span[contains(text(), 'Custom RPC')]`)) + await customRpcButton.click() + await delay(regularDelayMs) + + const customRpcInput = await findElement(driver, By.css('input[placeholder="New RPC URL"]')) + await customRpcInput.clear() + await customRpcInput.sendKeys(customRpcUrl) + + const customRpcSave = await findElement(driver, By.css('.settings-tab__rpc-save-button')) + await customRpcSave.click() + await delay(largeDelayMs * 2) + }) + }) + + it('selects another provider', async () => { + const networkDropdown = await findElement(driver, By.css('.network-name')) + await networkDropdown.click() + await delay(regularDelayMs) + + const customRpcButton = await findElement(driver, By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`)) + await customRpcButton.click() + await delay(largeDelayMs * 2) + }) + + it('finds all recent RPCs in history', async () => { + const networkDropdown = await findElement(driver, By.css('.network-name')) + await networkDropdown.click() + await delay(regularDelayMs) + + // only recent 3 are found and in correct order (most recent at the top) + const customRpcs = await findElements(driver, By.xpath(`//span[contains(text(), 'https://mainnet.infura.io/')]`)) + + assert.equal(customRpcs.length, customRpcUrls.length) + }) + }) }) diff --git a/test/e2e/beta/run-all.sh b/test/e2e/beta/run-all.sh index 7da61e504..c51f19fdf 100755 --- a/test/e2e/beta/run-all.sh +++ b/test/e2e/beta/run-all.sh @@ -6,5 +6,5 @@ set -o pipefail export PATH="$PATH:./node_modules/.bin" -shell-parallel -s 'npm run ganache:start' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec' -shell-parallel -s 'npm run ganache:start -- -d' -x 'sleep 5 && static-server test/e2e/beta/contract-test/ --port 8080' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec' +shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test --port 8080' -x 'sleep 5 && mocha test/e2e/beta/metamask-beta-ui.spec' +shell-parallel -s 'npm run ganache:start -- -d -b 2' -x 'sleep 5 && mocha test/e2e/beta/from-import-beta-ui.spec' diff --git a/test/e2e/beta/run-drizzle.sh b/test/e2e/beta/run-drizzle.sh new file mode 100755 index 000000000..7bfffd7e6 --- /dev/null +++ b/test/e2e/beta/run-drizzle.sh @@ -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 diff --git a/test/e2e/func.js b/test/e2e/func.js index 7b1730959..13dfb82f9 100644 --- a/test/e2e/func.js +++ b/test/e2e/func.js @@ -1,14 +1,19 @@ require('chromedriver') require('geckodriver') -const fs = require('fs') +const fs = require('fs-extra') const os = require('os') const path = require('path') +const pify = require('pify') +const prependFile = pify(require('prepend-file')) const webdriver = require('selenium-webdriver') const Command = require('selenium-webdriver/lib/command').Command const By = webdriver.By module.exports = { delay, + createModifiedTestBuild, + setupBrowserAndExtension, + verboseReportOnFailure, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, @@ -20,6 +25,37 @@ function delay (time) { return new Promise(resolve => setTimeout(resolve, time)) } +async function createModifiedTestBuild ({ browser, srcPath }) { + // copy build to test-builds directory + const extPath = path.resolve(`test-builds/${browser}`) + await fs.ensureDir(extPath) + await fs.copy(srcPath, extPath) + // inject METAMASK_TEST_CONFIG setting default test network + const config = { NetworkController: { provider: { type: 'localhost' } } } + await prependFile(`${extPath}/background.js`, `window.METAMASK_TEST_CONFIG=${JSON.stringify(config)};\n`) + return { extPath } +} + +async function setupBrowserAndExtension ({ browser, extPath }) { + let driver, extensionId, extensionUri + + if (browser === 'chrome') { + driver = buildChromeWebDriver(extPath) + extensionId = await getExtensionIdChrome(driver) + extensionUri = `chrome-extension://${extensionId}/home.html` + } else if (browser === 'firefox') { + driver = buildFirefoxWebdriver() + await installWebExt(driver, extPath) + await delay(700) + extensionId = await getExtensionIdFirefox(driver) + extensionUri = `moz-extension://${extensionId}/home.html` + } else { + throw new Error(`Unknown Browser "${browser}"`) + } + + return { driver, extensionId, extensionUri } +} + function buildChromeWebDriver (extPath) { const tmpProfile = fs.mkdtempSync(path.join(os.tmpdir(), 'mm-chrome-profile')) return new webdriver.Builder() @@ -61,3 +97,13 @@ async function installWebExt (driver, extension) { return await driver.schedule(cmd, 'installWebExt(' + extension + ')') } + +async function verboseReportOnFailure ({ browser, driver, title }) { + const artifactDir = `./test-artifacts/${browser}/${title}` + const filepathBase = `${artifactDir}/test-failure` + await fs.ensureDir(artifactDir) + const screenshot = await driver.takeScreenshot() + await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) + const htmlSource = await driver.getPageSource() + await fs.writeFile(`${filepathBase}-dom.html`, htmlSource) +} diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index ac7600f09..13af6cb22 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -1,49 +1,41 @@ -const fs = require('fs') -const mkdirp = require('mkdirp') const path = require('path') const assert = require('assert') -const pify = require('pify') -const webdriver = require('selenium-webdriver') -const { By, Key, until } = webdriver -const { delay, buildChromeWebDriver, buildFirefoxWebdriver, installWebExt, getExtensionIdChrome, getExtensionIdFirefox } = require('./func') +const { By, Key, until } = require('selenium-webdriver') +const { delay, createModifiedTestBuild, setupBrowserAndExtension, verboseReportOnFailure } = require('./func') describe('Metamask popup page', function () { - let driver, accountAddress, tokenAddress, extensionId + const browser = process.env.SELENIUM_BROWSER + let driver, accountAddress, tokenAddress, extensionUri this.timeout(0) before(async function () { - 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`) - } + const srcPath = path.resolve(`dist/${browser}`) + const { extPath } = await createModifiedTestBuild({ browser, srcPath }) + const installResult = await setupBrowserAndExtension({ browser, extPath }) + driver = installResult.driver + extensionUri = installResult.extensionUri + + await driver.get(extensionUri) + await delay(300) }) afterEach(async function () { // logs command not supported in firefox // https://github.com/SeleniumHQ/selenium/issues/2910 - if (process.env.SELENIUM_BROWSER === 'chrome') { + if (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)) + console.error(new Error(errorMessage)) + } } // gather extra data if test failed if (this.currentTest.state === 'failed') { - await verboseReportOnFailure(this.currentTest) + await verboseReportOnFailure({ browser, driver, title: this.currentTest.title }) } }) @@ -54,7 +46,6 @@ describe('Metamask popup page', function () { describe('Setup', function () { it('switches to Chrome extensions list', async function () { - await delay(300) const windowHandles = await driver.getAllWindowHandles() await driver.switchTo().window(windowHandles[0]) }) @@ -98,6 +89,7 @@ describe('Metamask popup page', function () { 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('shows privacy notice', async () => { @@ -108,7 +100,6 @@ describe('Metamask popup page', function () { }) it('shows phishing notice', async () => { - await delay(300) const noticeHeader = await driver.findElement(By.css('.terms-header')).getText() assert.equal(noticeHeader, 'PHISHING WARNING', 'shows phishing warning') const element = await driver.findElement(By.css('.markdown')) @@ -210,17 +201,17 @@ describe('Metamask popup page', function () { }) it('balance renders', async function () { - await delay(200) + await delay(500) 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) + 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 () { @@ -295,11 +286,7 @@ describe('Metamask popup page', function () { }) 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 driver.get(extensionUri) await delay(700) }) }) @@ -362,21 +349,4 @@ describe('Metamask popup page', function () { 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 - const screenshot = await driver.takeScreenshot() - await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' }) - // capture dom source - const htmlSource = await driver.getPageSource() - await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource) - } - }) diff --git a/test/helper.js b/test/helper.js index a3abbebf2..51f28de17 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,10 +1,21 @@ +const Ganache = require('ganache-core') +const nock = require('nock') import Enzyme from 'enzyme' import Adapter from 'enzyme-adapter-react-15' +nock.disableNetConnect() +nock.enableNetConnect('localhost') + Enzyme.configure({ adapter: new Adapter() }) // disallow promises from swallowing errors enableFailureOnUnhandledPromiseRejection() +// ganache server +const server = Ganache.server() +server.listen(8545, () => { + console.log('Ganache Testrpc is running on "http://localhost:8545"') +}) + // logging util var log = require('loglevel') log.setDefaultLevel(5) @@ -14,6 +25,9 @@ global.log = log // polyfills // +// fetch +global.fetch = require('isomorphic-fetch') + // dom require('jsdom-global')() diff --git a/test/integration/lib/add-token.js b/test/integration/lib/add-token.js index 6de7574c4..bb9d0d10f 100644 --- a/test/integration/lib/add-token.js +++ b/test/integration/lib/add-token.js @@ -86,7 +86,7 @@ async function runAddTokenFlowTest (assert, done) { $('button.btn-primary.btn--large')[0].click() // Verify added token image - let heroBalance = await queryAsync($, '.hero-balance') + let heroBalance = await queryAsync($, '.transaction-view-balance__balance-container') assert.ok(heroBalance, 'rendered hero balance') assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added') @@ -134,7 +134,7 @@ async function runAddTokenFlowTest (assert, done) { // $('button.btn-primary--lg')[0].click() // Verify added token image - heroBalance = await queryAsync($, '.hero-balance') + heroBalance = await queryAsync($, '.transaction-view-balance__balance-container') assert.ok(heroBalance, 'rendered hero balance') assert.ok(heroBalance.find('.identicon')[0], 'token added') } diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js index dcc25c493..9c2ad7cf4 100644 --- a/test/integration/lib/confirm-sig-requests.js +++ b/test/integration/lib/confirm-sig-requests.js @@ -19,7 +19,7 @@ async function runConfirmSigRequestsTest (assert, done) { selectState.val('confirm sig requests') reactTriggerChange(selectState[0]) - const pendingRequestItem = $.find('.tx-list-item.tx-list-pending-item-container.tx-list-clickable') + const pendingRequestItem = $.find('.transaction-list-item .transaction-list-item__grid') if (pendingRequestItem[0]) { pendingRequestItem[0].click() diff --git a/test/integration/lib/currency-localization.js b/test/integration/lib/currency-localization.js index d42b7495d..8d5acf5d0 100644 --- a/test/integration/lib/currency-localization.js +++ b/test/integration/lib/currency-localization.js @@ -22,8 +22,8 @@ async function runCurrencyLocalizationTest (assert, done) { await timeout(1000) reactTriggerChange(selectState[0]) await timeout(1000) - const txView = await queryAsync($, '.tx-view') - const heroBalance = await findAsync($(txView), '.hero-balance') - const fiatAmount = await findAsync($(heroBalance), '.fiat-amount') - assert.equal(fiatAmount[0].textContent, '₱102,707.97') + const txView = await queryAsync($, '.transaction-view') + const heroBalance = await findAsync($(txView), '.transaction-view-balance__balance') + const fiatAmount = await findAsync($(heroBalance), '.transaction-view-balance__secondary-balance') + assert.equal(fiatAmount[0].textContent, '₱102,707.97 PHP') } diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js index 406863ca6..ac1cc2e14 100644 --- a/test/integration/lib/send-new-ui.js +++ b/test/integration/lib/send-new-ui.js @@ -58,7 +58,7 @@ async function runSendFlowTest (assert, done) { selectState.val('send new ui') reactTriggerChange(selectState[0]) - const sendScreenButton = await queryAsync($, 'button.btn-primary.hero-balance-button') + const sendScreenButton = await queryAsync($, 'button.btn-primary.transaction-view-balance__button') assert.ok(sendScreenButton[1], 'send screen button present') sendScreenButton[1].click() @@ -94,7 +94,7 @@ async function runSendFlowTest (assert, done) { sendToDropdownList.children()[2].click() const sendToAccountAddress = sendToFieldInput.val() - assert.equal(sendToAccountAddress, '0x2f8d4a878cfa04a6e60d46362f5644deab66572d', 'send to dropdown selects the correct address') + assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address') const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)') sendAmountField.find('.currency-display')[0].click() @@ -124,10 +124,10 @@ async function runSendFlowTest (assert, done) { selectState.val('send edit') reactTriggerChange(selectState[0]) - const confirmFromName = (await queryAsync($, '.sender-to-recipient__sender-name')).first() + const confirmFromName = (await queryAsync($, '.sender-to-recipient__name')).first() assert.equal(confirmFromName[0].textContent, 'Send Account 4', 'confirm screen should show correct from name') - const confirmToName = (await queryAsync($, '.sender-to-recipient__recipient-name')).last() + const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last() assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name') const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__fiat') diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js index 9075efe03..ed4f82074 100644 --- a/test/integration/lib/tx-list-items.js +++ b/test/integration/lib/tx-list-items.js @@ -29,26 +29,25 @@ async function runTxListItemsTest (assert, done) { assert.ok(metamaskLogo[0], 'metamask logo present') metamaskLogo[0].click() - const txListItems = await queryAsync($, '.tx-list-item') + const txListItems = await queryAsync($, '.transaction-list-item') assert.equal(txListItems.length, 8, 'all tx list items are rendered') - const unapprovedTx = txListItems[0] - assert.equal($(unapprovedTx).hasClass('tx-list-pending-item-container'), true, 'unapprovedTx has the correct class') - - const retryTx = txListItems[1] - const retryTxLink = await findAsync($(retryTx), '.tx-list-item-retry-container span') - assert.equal(retryTxLink[0].textContent, 'Taking too long? Increase the gas price on your transaction', 'retryTx has expected link') + const retryTxGrid = await findAsync($(txListItems[2]), '.transaction-list-item__grid') + retryTxGrid[0].click() + const retryTxDetails = await findAsync($, '.transaction-list-item-details') + const headerButtons = await findAsync($(retryTxDetails[0]), '.transaction-list-item-details__header-button') + assert.equal(headerButtons[0].textContent, 'speed up') const approvedTx = txListItems[2] - const approvedTxRenderedStatus = await findAsync($(approvedTx), '.tx-list-status') - assert.equal(approvedTxRenderedStatus[0].textContent, 'Approved', 'approvedTx has correct label') + const approvedTxRenderedStatus = await findAsync($(approvedTx), '.transaction-list-item__status') + assert.equal(approvedTxRenderedStatus[0].textContent, 'pending', 'approvedTx has correct label') - const unapprovedMsg = txListItems[3] - const unapprovedMsgDescription = await findAsync($(unapprovedMsg), '.tx-list-account') + const unapprovedMsg = txListItems[0] + const unapprovedMsgDescription = await findAsync($(unapprovedMsg), '.transaction-list-item__action') assert.equal(unapprovedMsgDescription[0].textContent, 'Signature Request', 'unapprovedMsg has correct description') const failedTx = txListItems[4] - const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status') + const failedTxRenderedStatus = await findAsync($(failedTx), '.transaction-list-item__status') assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label') const shapeShiftTx = txListItems[5] @@ -56,10 +55,10 @@ async function runTxListItemsTest (assert, done) { assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status') const confirmedTokenTx = txListItems[6] - const confirmedTokenTxAddress = await findAsync($(confirmedTokenTx), '.tx-list-account') - assert.equal(confirmedTokenTxAddress[0].textContent, '0xE7884118...81a9', 'confirmedTokenTx has correct address') + const confirmedTokenTxAddress = await findAsync($(confirmedTokenTx), '.transaction-list-item__status') + assert.equal(confirmedTokenTxAddress[0].textContent, 'Confirmed', 'confirmedTokenTx has correct address') const rejectedTx = txListItems[7] - const rejectedTxRenderedStatus = await findAsync($(rejectedTx), '.tx-list-status') + const rejectedTxRenderedStatus = await findAsync($(rejectedTx), '.transaction-list-item__status') assert.equal(rejectedTxRenderedStatus[0].textContent, 'Rejected', 'rejectedTx has correct label') } diff --git a/test/lib/createTxMeta.js b/test/lib/createTxMeta.js new file mode 100644 index 000000000..0e88e3cfb --- /dev/null +++ b/test/lib/createTxMeta.js @@ -0,0 +1,16 @@ +const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') + +module.exports = createTxMeta + +function createTxMeta (partialMeta) { + const txMeta = Object.assign({ + status: 'unapproved', + txParams: {}, + }, partialMeta) + // initialize history + txMeta.history = [] + // capture initial snapshot of txMeta for history + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history.push(snapshot) + return txMeta +} diff --git a/test/lib/mock-config-manager.js b/test/lib/mock-config-manager.js deleted file mode 100644 index 0cc6953bb..000000000 --- a/test/lib/mock-config-manager.js +++ /dev/null @@ -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 }) -} diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js index 48aa9e52c..852c536c2 100644 --- a/test/lib/mock-encryptor.js +++ b/test/lib/mock-encryptor.js @@ -1,5 +1,5 @@ var mockHex = '0xabcdef0123456789' -var mockKey = new Buffer(32) +var mockKey = Buffer.alloc(32) let cacheVal module.exports = { diff --git a/test/lib/render-helpers.js b/test/lib/render-helpers.js new file mode 100644 index 000000000..81f0e27aa --- /dev/null +++ b/test/lib/render-helpers.js @@ -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()) +} diff --git a/test/lib/shallow-with-store.js b/test/lib/shallow-with-store.js deleted file mode 100644 index 9df10a3c5..000000000 --- a/test/lib/shallow-with-store.js +++ /dev/null @@ -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}) -} diff --git a/test/unit/app/cleanErrorStack.spec.js b/test/unit/app/cleanErrorStack.spec.js new file mode 100644 index 000000000..7a1ab1ed8 --- /dev/null +++ b/test/unit/app/cleanErrorStack.spec.js @@ -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') + }) + +}) diff --git a/test/unit/app/controllers/blacklist-controller-test.js b/test/unit/app/controllers/blacklist-controller-test.js index 085641777..7a14c02cc 100644 --- a/test/unit/app/controllers/blacklist-controller-test.js +++ b/test/unit/app/controllers/blacklist-controller-test.js @@ -8,6 +8,16 @@ describe('blacklist controller', function () { blacklistController = new BlacklistController() }) + describe('whitelistDomain', function () { + it('should add hostname to the runtime whitelist', function () { + blacklistController.whitelistDomain('foo.com') + assert.deepEqual(blacklistController.store.getState().whitelist, ['foo.com']) + + blacklistController.whitelistDomain('bar.com') + assert.deepEqual(blacklistController.store.getState().whitelist, ['bar.com', 'foo.com']) + }) + }) + describe('checkForPhishing', function () { it('should not flag whitelisted values', function () { const result = blacklistController.checkForPhishing('www.metamask.io') @@ -37,5 +47,10 @@ describe('blacklist controller', function () { const result = blacklistController.checkForPhishing('zero-faucet.metamask.io') assert.equal(result, false) }) + it('should not flag whitelisted domain', function () { + blacklistController.whitelistDomain('metamask.com') + const result = blacklistController.checkForPhishing('metamask.com') + assert.equal(result, false) + }) }) }) diff --git a/test/unit/app/controllers/currency-controller-test.js b/test/unit/app/controllers/currency-controller-test.js index 1941d1c43..7c4644d9f 100644 --- a/test/unit/app/controllers/currency-controller-test.js +++ b/test/unit/app/controllers/currency-controller-test.js @@ -1,6 +1,3 @@ -// polyfill fetch -global.fetch = global.fetch || require('isomorphic-fetch') - const assert = require('assert') const nock = require('nock') const CurrencyController = require('../../../../app/scripts/controllers/currency') diff --git a/test/unit/app/controllers/detect-tokens-test.js b/test/unit/app/controllers/detect-tokens-test.js index d6c3fad8a..2acc53e92 100644 --- a/test/unit/app/controllers/detect-tokens-test.js +++ b/test/unit/app/controllers/detect-tokens-test.js @@ -1,4 +1,5 @@ const assert = require('assert') +const nock = require('nock') const sinon = require('sinon') const ObservableStore = require('obs-store') const DetectTokensController = require('../../../../app/scripts/controllers/detect-tokens') @@ -6,15 +7,34 @@ const NetworkController = require('../../../../app/scripts/controllers/network/n const PreferencesController = require('../../../../app/scripts/controllers/preferences') describe('DetectTokensController', () => { - const sandbox = sinon.createSandbox() - let clock, keyringMemStore, network, preferences - beforeEach(async () => { - keyringMemStore = new ObservableStore({ isUnlocked: false}) - network = new NetworkController({ provider: { type: 'mainnet' }}) - preferences = new PreferencesController({ network }) - }) - after(() => { - sandbox.restore() + const sandbox = sinon.createSandbox() + let clock, keyringMemStore, network, preferences, controller + + const noop = () => {} + + const networkControllerProviderConfig = { + getAccounts: noop, + } + + beforeEach(async () => { + + + nock('https://api.infura.io') + .get(/.*/) + .reply(200) + + keyringMemStore = new ObservableStore({ isUnlocked: false}) + network = new NetworkController() + preferences = new PreferencesController({ network }) + controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) + + network.initializeProvider(networkControllerProviderConfig) + + }) + + after(() => { + sandbox.restore() + nock.cleanAll() }) it('should poll on correct interval', async () => { @@ -26,7 +46,10 @@ describe('DetectTokensController', () => { it('should be called on every polling period', async () => { clock = sandbox.useFakeTimers() + const network = new NetworkController() + network.initializeProvider(networkControllerProviderConfig) network.setProviderType('mainnet') + const preferences = new PreferencesController({ network }) const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true @@ -44,8 +67,6 @@ describe('DetectTokensController', () => { }) it('should not check tokens while in test network', async () => { - network.setProviderType('rinkeby') - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true @@ -58,7 +79,6 @@ describe('DetectTokensController', () => { }) it('should only check and add tokens while in main network', async () => { - network.setProviderType('mainnet') const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true @@ -75,7 +95,6 @@ describe('DetectTokensController', () => { }) it('should not detect same token while in main network', async () => { - network.setProviderType('mainnet') preferences.addToken('0x0d262e5dc4a06a0f1c90ce79c7a60c09dfc884e4', 'J8T', 8) const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true @@ -93,8 +112,6 @@ describe('DetectTokensController', () => { }) it('should trigger detect new tokens when change address', async () => { - network.setProviderType('mainnet') - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = true var stub = sandbox.stub(controller, 'detectNewTokens') @@ -103,8 +120,6 @@ describe('DetectTokensController', () => { }) it('should trigger detect new tokens when submit password', async () => { - network.setProviderType('mainnet') - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.selectedAddress = '0x0' var stub = sandbox.stub(controller, 'detectNewTokens') @@ -113,8 +128,6 @@ describe('DetectTokensController', () => { }) it('should not trigger detect new tokens when not open or not unlocked', async () => { - network.setProviderType('mainnet') - const controller = new DetectTokensController({ preferences: preferences, network: network, keyringMemStore: keyringMemStore }) controller.isOpen = true controller.isUnlocked = false var stub = sandbox.stub(controller, 'detectTokenBalance') diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 9f25cf376..17be2c028 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -3,16 +3,22 @@ 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 MetaMaskController = require('../../../../app/scripts/metamask-controller') +const firstTimeState = require('../../../unit/localhostState') +const createTxMeta = require('../../../lib/createTxMeta') +const EthQuery = require('eth-query') const currentNetworkId = 42 const DEFAULT_LABEL = 'Account 1' +const DEFAULT_LABEL_2 = 'Account 2' const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' +const TEST_ADDRESS_2 = '0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b' +const TEST_ADDRESS_3 = '0xeb9e64b93097bc15f01f13eae97015c57ab64823' const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' +const CUSTOM_RPC_URL = 'http://localhost:8545' describe('MetaMaskController', function () { let metamaskController @@ -110,7 +116,7 @@ describe('MetaMaskController', function () { } const gasPrice = metamaskController.getGasPrice() - assert.equal(gasPrice, '0x3b9aca00', 'accurately estimates 50th percentile accepted gas price') + assert.equal(gasPrice, '0x174876e800', 'accurately estimates 65th percentile accepted gas price') metamaskController.recentBlocksController = realRecentBlocksController }) @@ -134,6 +140,9 @@ describe('MetaMaskController', function () { describe('#createNewVaultAndRestore', function () { it('should be able to call newVaultAndRestore despite a mistake.', async function () { const password = 'what-what-what' + sandbox.stub(metamaskController, 'getBalance') + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) + await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null) await metamaskController.createNewVaultAndRestore(password, TEST_SEED) @@ -141,6 +150,9 @@ describe('MetaMaskController', function () { }) it('should clear previous identities after vault restoration', async () => { + sandbox.stub(metamaskController, 'getBalance') + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) assert.deepEqual(metamaskController.getState().identities, { [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, @@ -156,6 +168,54 @@ describe('MetaMaskController', function () { [TEST_ADDRESS_ALT]: { address: TEST_ADDRESS_ALT, name: DEFAULT_LABEL }, }) }) + + it('should restore any consecutive accounts with balances', async () => { + sandbox.stub(metamaskController, 'getBalance') + metamaskController.getBalance.withArgs(TEST_ADDRESS).callsFake(() => { + return Promise.resolve('0x14ced5122ce0a000') + }) + metamaskController.getBalance.withArgs(TEST_ADDRESS_2).callsFake(() => { + return Promise.resolve('0x0') + }) + metamaskController.getBalance.withArgs(TEST_ADDRESS_3).callsFake(() => { + return Promise.resolve('0x14ced5122ce0a000') + }) + + await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) + assert.deepEqual(metamaskController.getState().identities, { + [TEST_ADDRESS]: { address: TEST_ADDRESS, name: DEFAULT_LABEL }, + [TEST_ADDRESS_2]: { address: TEST_ADDRESS_2, name: DEFAULT_LABEL_2 }, + }) + }) + }) + + describe('#getBalance', () => { + it('should return the balance known by accountTracker', async () => { + const accounts = {} + const balance = '0x14ced5122ce0a000' + accounts[TEST_ADDRESS] = { balance: balance } + + metamaskController.accountTracker.store.putState({ accounts: accounts }) + + const gotten = await metamaskController.getBalance(TEST_ADDRESS) + + assert.equal(balance, gotten) + }) + + it('should ask the network for a balance when not known by accountTracker', async () => { + const accounts = {} + const balance = '0x14ced5122ce0a000' + const ethQuery = new EthQuery() + sinon.stub(ethQuery, 'getBalance').callsFake((account, callback) => { + callback(undefined, balance) + }) + + metamaskController.accountTracker.store.putState({ accounts: accounts }) + + const gotten = await metamaskController.getBalance(TEST_ADDRESS, ethQuery) + + assert.equal(balance, gotten) + }) }) describe('#getApi', function () { @@ -360,29 +420,19 @@ describe('MetaMaskController', function () { }) 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() + rpcTarget = metamaskController.setCustomRpc(CUSTOM_RPC_URL) }) it('returns custom RPC that when called', async function () { - assert.equal(await rpcTarget, customRPC) + assert.equal(await rpcTarget, CUSTOM_RPC_URL) }) it('changes the network controller rpc', function () { const networkControllerState = metamaskController.networkController.store.getState() - assert.equal(networkControllerState.provider.rpcTarget, customRPC) + assert.equal(networkControllerState.provider.rpcTarget, CUSTOM_RPC_URL) }) }) @@ -487,9 +537,10 @@ describe('MetaMaskController', function () { 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'} }, + createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }), + createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }), + createTxMeta({ id: 2, status: 'rejected', metamaskNetworkId: 32 }), + createTxMeta({ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }), ]) }) @@ -522,7 +573,7 @@ describe('MetaMaskController', function () { assert(metamaskController.preferencesController.removeAddress.calledWith(addressToRemove)) }) it('should call accountTracker.removeAccount', async function () { - assert(metamaskController.accountTracker.removeAccount.calledWith(addressToRemove)) + assert(metamaskController.accountTracker.removeAccount.calledWith([addressToRemove])) }) it('should call keyringController.removeAccount', async function () { assert(metamaskController.keyringController.removeAccount.calledWith(addressToRemove)) @@ -533,22 +584,18 @@ describe('MetaMaskController', function () { }) describe('#clearSeedWordCache', function () { + it('should set seed words to null', function (done) { + sandbox.stub(metamaskController.preferencesController, 'setSeedWords') + metamaskController.clearSeedWordCache((err) => { + if (err) { + done(err) + } - 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) + assert.ok(metamaskController.preferencesController.setSeedWords.calledOnce) + assert.deepEqual(metamaskController.preferencesController.setSeedWords.args, [[null]]) + done() }) - const getConfigSeed = metamaskController.configManager.getSeedWords() - assert.equal(getConfigSeed, null) }) - }) describe('#setCurrentLocale', function () { @@ -566,14 +613,16 @@ describe('MetaMaskController', function () { }) - describe('#newUnsignedMessage', function () { + describe('#newUnsignedMessage', () => { let msgParams, metamaskMsgs, messages, msgId const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' const data = '0x43727970746f6b697474696573' - beforeEach(async function () { + beforeEach(async () => { + sandbox.stub(metamaskController, 'getBalance') + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -582,7 +631,10 @@ describe('MetaMaskController', function () { 'data': data, } - metamaskController.newUnsignedMessage(msgParams, noop) + const promise = metamaskController.newUnsignedMessage(msgParams) + // handle the promise so it doesn't throw an unhandledRejection + promise.then(noop).catch(noop) + metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() messages = metamaskController.messageManager.messages msgId = Object.keys(metamaskMsgs)[0] @@ -622,13 +674,16 @@ describe('MetaMaskController', function () { describe('#newUnsignedPersonalMessage', function () { - it('errors with no from in msgParams', function () { + it('errors with no from in msgParams', async () => { const msgParams = { 'data': data, } - metamaskController.newUnsignedPersonalMessage(msgParams, function (error) { + try { + await metamaskController.newUnsignedPersonalMessage(msgParams) + assert.fail('should have thrown') + } catch (error) { assert.equal(error.message, 'MetaMask Message Signature: from field is required.') - }) + } }) let msgParams, metamaskPersonalMsgs, personalMessages, msgId @@ -637,6 +692,8 @@ describe('MetaMaskController', function () { const data = '0x43727970746f6b697474696573' beforeEach(async function () { + sandbox.stub(metamaskController, 'getBalance') + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -645,7 +702,10 @@ describe('MetaMaskController', function () { 'data': data, } - metamaskController.newUnsignedPersonalMessage(msgParams, noop) + const promise = metamaskController.newUnsignedPersonalMessage(msgParams) + // handle the promise so it doesn't throw an unhandledRejection + promise.then(noop).catch(noop) + metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() personalMessages = metamaskController.personalMessageManager.messages msgId = Object.keys(metamaskPersonalMsgs)[0] @@ -684,22 +744,27 @@ describe('MetaMaskController', function () { describe('#setupUntrustedCommunication', function () { let streamTest - const phishingUrl = 'decentral.market' + const phishingUrl = 'myethereumwalletntw.com' afterEach(function () { streamTest.end() }) - it('sets up phishing stream for untrusted communication ', async function () { + it('sets up phishing stream for untrusted communication ', async () => { await metamaskController.blacklistController.updatePhishingList() + console.log(blacklistJSON.blacklist.includes(phishingUrl)) + + const { promise, resolve } = deferredPromise() streamTest = createThoughStream((chunk, enc, cb) => { - assert.equal(chunk.name, 'phishing') + if (chunk.name !== 'phishing') return cb() assert.equal(chunk.data.hostname, phishingUrl) - cb() - }) - // console.log(streamTest) - metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + resolve() + cb() + }) + metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + + await promise }) }) @@ -724,25 +789,102 @@ describe('MetaMaskController', function () { describe('#markAccountsFound', function () { it('adds lost accounts to config manager data', function () { metamaskController.markAccountsFound(noop) - const configManagerData = metamaskController.configManager.getData() - assert.deepEqual(configManagerData.lostAccounts, []) + const state = metamaskController.getState() + assert.deepEqual(state.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) + const state = metamaskController.getState() + assert.equal(state.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) + const state = metamaskController.getState() + assert.equal(state.forgottenPassword, false) + }) + }) + + describe('#_onKeyringControllerUpdate', function () { + it('should do nothing if there are no keyrings in state', async function () { + const addAddresses = sinon.fake() + const syncWithAddresses = sinon.fake() + sandbox.replace(metamaskController, 'preferencesController', { + addAddresses, + }) + sandbox.replace(metamaskController, 'accountTracker', { + syncWithAddresses, + }) + + const oldState = metamaskController.getState() + await metamaskController._onKeyringControllerUpdate({keyrings: []}) + + assert.ok(addAddresses.notCalled) + assert.ok(syncWithAddresses.notCalled) + assert.deepEqual(metamaskController.getState(), oldState) + }) + + it('should update selected address if keyrings was locked', async function () { + const addAddresses = sinon.fake() + const getSelectedAddress = sinon.fake.returns('0x42') + const setSelectedAddress = sinon.fake() + const syncWithAddresses = sinon.fake() + sandbox.replace(metamaskController, 'preferencesController', { + addAddresses, + getSelectedAddress, + setSelectedAddress, + }) + sandbox.replace(metamaskController, 'accountTracker', { + syncWithAddresses, + }) + + const oldState = metamaskController.getState() + await metamaskController._onKeyringControllerUpdate({ + isUnlocked: false, + keyrings: [{ + accounts: ['0x1', '0x2'], + }], + }) + + assert.deepEqual(addAddresses.args, [[['0x1', '0x2']]]) + assert.deepEqual(syncWithAddresses.args, [[['0x1', '0x2']]]) + assert.deepEqual(setSelectedAddress.args, [['0x1']]) + assert.deepEqual(metamaskController.getState(), oldState) + }) + + it('should NOT update selected address if already unlocked', async function () { + const addAddresses = sinon.fake() + const syncWithAddresses = sinon.fake() + sandbox.replace(metamaskController, 'preferencesController', { + addAddresses, + }) + sandbox.replace(metamaskController, 'accountTracker', { + syncWithAddresses, + }) + + const oldState = metamaskController.getState() + await metamaskController._onKeyringControllerUpdate({ + isUnlocked: true, + keyrings: [{ + accounts: ['0x1', '0x2'], + }], + }) + + assert.deepEqual(addAddresses.args, [[['0x1', '0x2']]]) + assert.deepEqual(syncWithAddresses.args, [[['0x1', '0x2']]]) + assert.deepEqual(metamaskController.getState(), oldState) }) }) }) + +function deferredPromise () { + let resolve + const promise = new Promise(_resolve => { resolve = _resolve }) + return { promise, resolve } +} diff --git a/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js index e16fb104e..822311931 100644 --- a/test/unit/app/controllers/network-contoller-test.js +++ b/test/unit/app/controllers/network-contoller-test.js @@ -32,9 +32,10 @@ describe('# Network Controller', function () { describe('#provider', function () { it('provider should be updatable without reassignment', function () { networkController.initializeProvider(networkControllerProviderConfig) - const proxy = networkController._proxy - proxy.setTarget({ test: true, on: () => {} }) - assert.ok(proxy.test) + const providerProxy = networkController.getProviderAndBlockTracker().provider + assert.equal(providerProxy.test, undefined) + providerProxy.setTarget({ test: true }) + assert.equal(providerProxy.test, true) }) }) describe('#getNetworkState', function () { diff --git a/test/unit/app/controllers/notice-controller-test.js b/test/unit/app/controllers/notice-controller-test.js index b3ae75080..834c88f7b 100644 --- a/test/unit/app/controllers/notice-controller-test.js +++ b/test/unit/app/controllers/notice-controller-test.js @@ -1,16 +1,11 @@ const assert = require('assert') -const configManagerGen = require('../../../lib/mock-config-manager') const NoticeController = require('../../../../app/scripts/notice-controller') describe('notice-controller', function () { var noticeController beforeEach(function () { - // simple localStorage polyfill - const configManager = configManagerGen() - noticeController = new NoticeController({ - configManager: configManager, - }) + noticeController = new NoticeController() }) describe('notices', function () { diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js index 9b2c846bd..b5ccf3fb5 100644 --- a/test/unit/app/controllers/preferences-controller-test.js +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -1,6 +1,7 @@ const assert = require('assert') const ObservableStore = require('obs-store') const PreferencesController = require('../../../../app/scripts/controllers/preferences') +const sinon = require('sinon') describe('preferences controller', function () { let preferencesController @@ -339,5 +340,144 @@ describe('preferences controller', function () { assert.deepEqual(tokensSecond, initialTokensSecond, 'tokens equal for same network') }) }) + + describe('on watchAsset', function () { + var stubNext, stubEnd, stubHandleWatchAssetERC20, asy, req, res + const sandbox = sinon.createSandbox() + + beforeEach(() => { + req = {params: {}} + res = {} + asy = {next: () => {}, end: () => {}} + stubNext = sandbox.stub(asy, 'next') + stubEnd = sandbox.stub(asy, 'end').returns(0) + stubHandleWatchAssetERC20 = sandbox.stub(preferencesController, '_handleWatchAssetERC20') + }) + after(() => { + sandbox.restore() + }) + + it('shouldn not do anything if method not corresponds', async function () { + const asy = {next: () => {}, end: () => {}} + var stubNext = sandbox.stub(asy, 'next') + var stubEnd = sandbox.stub(asy, 'end').returns(0) + req.method = 'metamask' + await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) + sandbox.assert.notCalled(stubEnd) + sandbox.assert.called(stubNext) + }) + it('should do something if method is supported', async function () { + const asy = {next: () => {}, end: () => {}} + var stubNext = sandbox.stub(asy, 'next') + var stubEnd = sandbox.stub(asy, 'end').returns(0) + req.method = 'metamask_watchAsset' + req.params.type = 'someasset' + await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) + sandbox.assert.called(stubEnd) + sandbox.assert.notCalled(stubNext) + }) + it('should through error if method is supported but asset type is not', async function () { + req.method = 'metamask_watchAsset' + req.params.type = 'someasset' + await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) + sandbox.assert.called(stubEnd) + sandbox.assert.notCalled(stubHandleWatchAssetERC20) + sandbox.assert.notCalled(stubNext) + assert.deepEqual(res, {}) + }) + it('should trigger handle add asset if type supported', async function () { + const asy = {next: () => {}, end: () => {}} + req.method = 'metamask_watchAsset' + req.params.type = 'ERC20' + await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) + sandbox.assert.called(stubHandleWatchAssetERC20) + }) + }) + + describe('on watchAsset of type ERC20', function () { + var req + + const sandbox = sinon.createSandbox() + beforeEach(() => { + req = {params: {type: 'ERC20'}} + }) + after(() => { + sandbox.restore() + }) + + it('should add suggested token', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + const image = 'someimage' + req.params.options = { address, symbol, decimals, image } + + sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true) + preferencesController.showWatchAssetUi = async () => {} + + await preferencesController._handleWatchAssetERC20(req.params.options) + const suggested = preferencesController.getSuggestedTokens() + assert.equal(Object.keys(suggested).length, 1, `one token added ${Object.keys(suggested)}`) + + assert.equal(suggested[address].address, address, 'set address correctly') + assert.equal(suggested[address].symbol, symbol, 'set symbol correctly') + assert.equal(suggested[address].decimals, decimals, 'set decimals correctly') + assert.equal(suggested[address].image, image, 'set image correctly') + }) + + it('should add token correctly if user confirms', async function () { + const address = '0xabcdef1234567' + const symbol = 'ABBR' + const decimals = 5 + const image = 'someimage' + req.params.options = { address, symbol, decimals, image } + + sandbox.stub(preferencesController, '_validateERC20AssetParams').returns(true) + preferencesController.showWatchAssetUi = async () => { + await preferencesController.addToken(address, symbol, decimals, image) + } + + await preferencesController._handleWatchAssetERC20(req.params.options) + 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') + + const assetImages = preferencesController.getAssetImages() + assert.ok(assetImages[address], `set image correctly`) + }) + }) + + describe('setPasswordForgotten', function () { + it('should default to false', function () { + const state = preferencesController.store.getState() + assert.equal(state.forgottenPassword, false) + }) + + it('should set the forgottenPassword property in state', function () { + assert.equal(preferencesController.store.getState().forgottenPassword, false) + + preferencesController.setPasswordForgotten(true) + + assert.equal(preferencesController.store.getState().forgottenPassword, true) + }) + }) + + describe('setSeedWords', function () { + it('should default to null', function () { + const state = preferencesController.store.getState() + assert.equal(state.seedWords, null) + }) + + it('should set the seedWords property in state', function () { + assert.equal(preferencesController.store.getState().seedWords, null) + + preferencesController.setSeedWords('foo bar baz') + + assert.equal(preferencesController.store.getState().seedWords, 'foo bar baz') + }) + }) }) diff --git a/test/unit/app/controllers/transactions/nonce-tracker-test.js b/test/unit/app/controllers/transactions/nonce-tracker-test.js index 6c0ac759f..51ac390e9 100644 --- a/test/unit/app/controllers/transactions/nonce-tracker-test.js +++ b/test/unit/app/controllers/transactions/nonce-tracker-test.js @@ -224,14 +224,15 @@ function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { providerResultStub.result = providerStub const provider = { sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, - _blockTracker: { - getCurrentBlock: () => '0x11b568', - }, + } + const blockTracker = { + getCurrentBlock: () => '0x11b568', + getLatestBlock: async () => '0x11b568', } return new NonceTracker({ provider, + blockTracker, getPendingTransactions, getConfirmedTransactions, }) } - diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js index 8bf2da6f8..ba15f1953 100644 --- a/test/unit/app/controllers/transactions/pending-tx-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -9,6 +9,7 @@ describe('PendingTransactionTracker', function () { let pendingTxTracker, txMeta, txMetaNoHash, providerResultStub, provider, txMeta3, txList, knownErrors this.timeout(10000) + beforeEach(function () { txMeta = { id: 1, @@ -40,7 +41,10 @@ describe('PendingTransactionTracker', function () { getPendingTransactions: () => { return [] }, getCompletedTransactions: () => { return [] }, publishTransaction: () => {}, + confirmTransaction: () => {}, }) + + pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} } }) describe('_checkPendingTx state management', function () { @@ -92,58 +96,6 @@ describe('PendingTransactionTracker', function () { }) }) - describe('#checkForTxInBlock', function () { - it('should return if no pending transactions', function () { - // throw a type error if it trys to do anything on the block - // thus failing the test - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] - pendingTxTracker.once('tx:failed', (txId, err) => { - assert(txId, txMetaNoHash.id, 'should pass txId') - done() - }) - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'txConfirmed\' if the tx is in the block', function (done) { - const block = { transactions: [txMeta]} - pendingTxTracker.getPendingTransactions = () => [txMeta] - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker.checkForTxInBlock(block) - }) - }) - describe('#queryPendingTxs', function () { - it('should call #_checkPendingTxs if their is no oldBlock', function (done) { - let oldBlock - const newBlock = { number: '0x01' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { - const oldBlock = { number: '0x01' } - const newBlock = { number: '0x03' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { - const oldBlock = { number: '0x1' } - const newBlock = { number: '0x2' } - pendingTxTracker._checkPendingTxs = () => { - const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') - done(err) - } - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - done() - }) - }) - describe('#_checkPendingTx', function () { it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { pendingTxTracker.once('tx:failed', (txId, err) => { @@ -157,16 +109,6 @@ describe('PendingTransactionTracker', function () { providerResultStub.eth_getTransactionByHash = null pendingTxTracker._checkPendingTx(txMeta) }) - - it('should emit \'txConfirmed\'', function (done) { - providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'} - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker._checkPendingTx(txMeta) - }) }) describe('#_checkPendingTxs', function () { @@ -180,19 +122,19 @@ describe('PendingTransactionTracker', function () { }) }) - it('should warp all txMeta\'s in #_checkPendingTx', function (done) { + it('should warp all txMeta\'s in #updatePendingTxs', function (done) { pendingTxTracker.getPendingTransactions = () => txList pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxTracker._checkPendingTxs() + pendingTxTracker.updatePendingTxs() }) }) describe('#resubmitPendingTxs', function () { - const blockStub = { number: '0x0' } + const blockNumberStub = '0x0' beforeEach(function () { const txMeta2 = txMeta3 = txMeta txList = [txMeta, txMeta2, txMeta3].map((tx) => { @@ -210,7 +152,7 @@ describe('PendingTransactionTracker', function () { Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNumberStub) }) it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) { knownErrors = [ @@ -237,7 +179,7 @@ describe('PendingTransactionTracker', function () { .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNumberStub) }) it('should emit \'tx:warning\' if it encountered a real error', function (done) { pendingTxTracker.once('tx:warning', (txMeta, err) => { @@ -255,7 +197,7 @@ describe('PendingTransactionTracker', function () { .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNumberStub) }) }) describe('#_resubmitTx', function () { diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 26dc7b656..ea58aa560 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -1,4 +1,5 @@ const assert = require('assert') +const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const ObservableStore = require('obs-store') @@ -22,12 +23,14 @@ describe('Transaction Controller', function () { } provider = createTestProviderTools({ scaffold: providerResultStub }).provider fromAccount = getTestAccounts()[0] - + const blockTrackerStub = new EventEmitter() + blockTrackerStub.getCurrentBlock = noop + blockTrackerStub.getLatestBlock = noop txController = new TransactionController({ provider, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, - blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, + blockTracker: blockTrackerStub, signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(fromAccount.key) resolve() @@ -49,9 +52,9 @@ describe('Transaction Controller', function () { describe('#getUnapprovedTxCount', function () { it('should return the number of unapproved txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, ]) const unapprovedTxCount = txController.getUnapprovedTxCount() assert.equal(unapprovedTxCount, 3, 'should be 3') @@ -61,9 +64,9 @@ describe('Transaction Controller', function () { describe('#getPendingTxCount', function () { it('should return the number of pending txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, ]) const pendingTxCount = txController.getPendingTxCount() assert.equal(pendingTxCount, 3, 'should be 3') @@ -79,15 +82,15 @@ describe('Transaction Controller', function () { 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', } txController.txStateManager._saveTxList([ - {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams}, - {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams}, - {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams}, + {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, ]) }) @@ -155,9 +158,19 @@ describe('Transaction Controller', function () { }) describe('#addUnapprovedTransaction', function () { + const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d' + + let getSelectedAddress + beforeEach(function () { + getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress) + }) + + afterEach(function () { + getSelectedAddress.restore() + }) it('should add an unapproved transaction and return a valid txMeta', function (done) { - txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) + txController.addUnapprovedTransaction({ from: selectedAddress }) .then((txMeta) => { assert(('id' in txMeta), 'should have a id') assert(('time' in txMeta), 'should have a time stamp') @@ -177,48 +190,58 @@ describe('Transaction Controller', function () { assert(txMetaFromEmit, 'txMeta is falsey') done() }) - txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d' }) + txController.addUnapprovedTransaction({ from: selectedAddress }) .catch(done) }) it('should fail if recipient is public', function (done) { txController.networkStore = new ObservableStore(1) - txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) + txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch((err) => { if (err.message === 'Recipient is a public account') done() else done(err) }) }) + it('should fail if the from address isn\'t the selected address', function (done) { + txController.addUnapprovedTransaction({from: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2'}) + .then(function () { + assert.fail('transaction should not have been added') + done() + }) + .catch(function () { + assert.ok('pass') + done() + }) + }) + it('should not fail if recipient is public but not on mainnet', function (done) { txController.once('newUnapprovedTx', (txMetaFromEmit) => { assert(txMetaFromEmit, 'txMeta is falsey') done() }) - txController.addUnapprovedTransaction({ from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) + txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch(done) }) }) describe('#addTxGasDefaults', function () { - it('should add the tx defaults if their are none', function (done) { + it('should add the tx defaults if their are none', async () => { const txMeta = { - 'txParams': { - 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + txParams: { + from: '0xc684832530fcbddae4b4230a47e991ddcec2831d', + to: '0xc684832530fcbddae4b4230a47e991ddcec2831d', }, + history: [], } - providerResultStub.eth_gasPrice = '4a817c800' - providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } - providerResultStub.eth_estimateGas = '5209' - txController.addTxGasDefaults(txMeta) - .then((txMetaWithDefaults) => { - assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') - assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') - assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') - done() - }) - .catch(done) + providerResultStub.eth_gasPrice = '4a817c800' + providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } + providerResultStub.eth_estimateGas = '5209' + + const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta) + assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') + assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') + assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') }) }) @@ -381,8 +404,9 @@ describe('Transaction Controller', function () { }) it('should publish a tx, updates the rawTx when provided a one', async function () { + const rawTx = '0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c' txController.txStateManager.addTx(txMeta) - await txController.publishTransaction(txMeta.id) + await txController.publishTransaction(txMeta.id, rawTx) const publishedTx = txController.txStateManager.getTx(1) assert.equal(publishedTx.hash, hash) assert.equal(publishedTx.status, 'submitted') @@ -398,7 +422,7 @@ describe('Transaction Controller', function () { data: '0x0', } txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams }, + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] }, ]) txController.retryTransaction(1) .then((txMeta) => { diff --git a/test/unit/components/balance-component-test.js b/test/unit/components/balance-component-test.js index 81e6fdf9e..aa9763b72 100644 --- a/test/unit/components/balance-component-test.js +++ b/test/unit/components/balance-component-test.js @@ -1,7 +1,7 @@ const assert = require('assert') const h = require('react-hyperscript') const { createMockStore } = require('redux-test-utils') -const { shallowWithStore } = require('../../lib/shallow-with-store') +const { shallowWithStore } = require('../../lib/render-helpers') const BalanceComponent = require('../../../ui/app/components/balance-component') const mockState = { metamask: { @@ -42,4 +42,3 @@ describe('BalanceComponent', function () { }) }) - diff --git a/test/unit/components/binary-renderer-test.js b/test/unit/components/binary-renderer-test.js index 7bf9250cc..e428c26ad 100644 --- a/test/unit/components/binary-renderer-test.js +++ b/test/unit/components/binary-renderer-test.js @@ -4,7 +4,7 @@ var BinaryRenderer = require('../../../old-ui/app/components/binary-renderer') describe('BinaryRenderer', function () { let binaryRenderer const message = 'Hello, world!' - const buffer = new Buffer(message, 'utf8') + const buffer = Buffer.from(message, 'utf8') const hex = buffer.toString('hex') beforeEach(function () { diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js deleted file mode 100644 index b710e2dfb..000000000 --- a/test/unit/config-manager-test.js +++ /dev/null @@ -1,115 +0,0 @@ -// polyfill fetch -global.fetch = global.fetch || require('isomorphic-fetch') - -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') - }) - }) - }) -}) diff --git a/test/unit/development/sample-changelog.md b/test/unit/development/sample-changelog.md index 8fc9d2145..fd980e375 100644 --- a/test/unit/development/sample-changelog.md +++ b/test/unit/development/sample-changelog.md @@ -67,7 +67,7 @@ - Estimating gas limit for simple ether sends now faster & cheaper, by avoiding VM usage on recipients with no code. - Add an extra px to address for Firefox clipping. - Fix Firefox scrollbar. -- Open metamask popup for transaction confirmation before gas estimation finishes and add a loading screen over transaction confirmation. +- Open MetaMask popup for transaction confirmation before gas estimation finishes and add a loading screen over transaction confirmation. - Fix bug that prevented eth_signTypedData from signing bytes. - Further improve gas price estimation. @@ -330,7 +330,7 @@ rollback to 3.10.0 due to bug ## 3.7.7 2017-6-8 -- Fix bug where metamask would show old data after computer being asleep or disconnected from the internet. +- Fix bug where MetaMask would show old data after computer being asleep or disconnected from the internet. ## 3.7.6 2017-6-5 diff --git a/test/unit/localhostState.js b/test/unit/localhostState.js new file mode 100644 index 000000000..f9fa157d7 --- /dev/null +++ b/test/unit/localhostState.js @@ -0,0 +1,21 @@ + +/** + * @typedef {Object} FirstTimeState + * @property {Object} config Initial configuration parameters + * @property {Object} NetworkController Network controller state + */ + +/** + * @type {FirstTimeState} + */ +const initialState = { + config: {}, + NetworkController: { + provider: { + type: 'rpc', + rpcTarget: 'http://localhost:8545', + }, + }, +} + +module.exports = initialState diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js index 493b01918..f3f236d90 100644 --- a/test/unit/responsive/components/dropdown-test.js +++ b/test/unit/responsive/components/dropdown-test.js @@ -6,7 +6,7 @@ const path = require('path') const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'dropdowns', 'index.js')).Dropdown const { createMockStore } = require('redux-test-utils') -const { mountWithStore } = require('../../../lib/shallow-with-store') +const { mountWithStore } = require('../../../lib/render-helpers') const mockState = { metamask: { diff --git a/test/unit/ui/add-token.spec.js b/test/unit/ui/add-token.spec.js index 69b7fb620..f6b6155a0 100644 --- a/test/unit/ui/add-token.spec.js +++ b/test/unit/ui/add-token.spec.js @@ -1,7 +1,7 @@ const assert = require('assert') const { createMockStore } = require('redux-test-utils') const h = require('react-hyperscript') -const { shallowWithStore } = require('../../lib/shallow-with-store') +const { shallowWithStore } = require('../../lib/render-helpers') const AddTokenScreen = require('../../../old-ui/app/add-token') describe('Add Token Screen', function () { diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js new file mode 100644 index 000000000..748a58b32 --- /dev/null +++ b/test/unit/ui/app/actions.spec.js @@ -0,0 +1,1468 @@ +// Used to inspect long objects +// util.inspect({JSON}, false, null)) +// const util = require('util') +const assert = require('assert') +const sinon = require('sinon') +const clone = require('clone') +const nock = require('nock') +const fetchMock = require('fetch-mock') +const configureStore = require('redux-mock-store').default +const thunk = require('redux-thunk').default +const EthQuery = require('eth-query') +const Eth = require('ethjs') +const KeyringController = require('eth-keyring-controller') + +const { createTestProviderTools } = require('../../../stub/provider') +const provider = createTestProviderTools({ scaffold: {}}).provider + +const enLocale = require('../../../../app/_locales/en/messages.json') +const actions = require('../../../../ui/app/actions') +const MetaMaskController = require('../../../../app/scripts/metamask-controller') + +const firstTimeState = require('../../../unit/localhostState') +const devState = require('../../../data/2-state.json') + +const middleware = [thunk] +const mockStore = configureStore(middleware) + +describe('Actions', () => { + + const noop = () => {} + + let background, metamaskController + + const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm reduce speak stadium' + const password = 'a-fake-password' + const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553' + + beforeEach(async () => { + + + metamaskController = new MetaMaskController({ + provider, + keyringController: new KeyringController({}), + showUnapprovedTx: noop, + showUnconfirmedMessage: noop, + encryptor: { + encrypt: function (password, object) { + this.object = object + return Promise.resolve('mock-encrypted') + }, + decrypt: function () { + return Promise.resolve(this.object) + }, + }, + initState: clone(firstTimeState), + }) + + await metamaskController.createNewVaultAndRestore(password, TEST_SEED) + + await metamaskController.importAccountWithStrategy('Private Key', [ importPrivkey ]) + + background = metamaskController.getApi() + + actions._setBackgroundConnection(background) + + global.ethQuery = new EthQuery(provider) + }) + + describe('#tryUnlockMetamask', () => { + + let submitPasswordSpy, verifySeedPhraseSpy + + afterEach(() => { + submitPasswordSpy.restore() + verifySeedPhraseSpy.restore() + }) + + it('', async () => { + + const store = mockStore({}) + + submitPasswordSpy = sinon.spy(background, 'submitPassword') + verifySeedPhraseSpy = sinon.spy(background, 'verifySeedPhrase') + + return store.dispatch(actions.tryUnlockMetamask()) + .then(() => { + assert(submitPasswordSpy.calledOnce) + assert(verifySeedPhraseSpy.calledOnce) + }) + }) + + it('errors on submitPassword will fail', () => { + + const store = mockStore({}) + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'UNLOCK_IN_PROGRESS' }, + { type: 'UNLOCK_FAILED', value: 'error in submitPassword' }, + { type: 'HIDE_LOADING_INDICATION' }, + ] + + + submitPasswordSpy = sinon.stub(background, 'submitPassword') + + submitPasswordSpy.callsFake((password, callback) => { + callback(new Error('error in submitPassword')) + }) + + return store.dispatch(actions.tryUnlockMetamask('test')) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('displays warning error and unlock failed when verifySeed fails', () => { + const store = mockStore({}) + const displayWarningError = [ { type: 'DISPLAY_WARNING', value: 'error' } ] + const unlockFailedError = [ { type: 'UNLOCK_FAILED', value: 'error' } ] + + verifySeedPhraseSpy = sinon.stub(background, 'verifySeedPhrase') + verifySeedPhraseSpy.callsFake(callback => { + callback(new Error('error')) + }) + + return store.dispatch(actions.tryUnlockMetamask('test')) + .catch(() => { + const actions = store.getActions() + const warning = actions.filter(action => action.type === 'DISPLAY_WARNING') + const unlockFailed = actions.filter(action => action.type === 'UNLOCK_FAILED') + assert.deepEqual(warning, displayWarningError) + assert.deepEqual(unlockFailed, unlockFailedError) + }) + }) + }) + + describe('#confirmSeedWords', () => { + + let clearSeedWordCacheSpy + + afterEach(() => { + clearSeedWordCacheSpy.restore() + }) + + it('shows account page after clearing seed word cache', () => { + + const store = mockStore({}) + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'SHOW_ACCOUNTS_PAGE' }, + ] + + clearSeedWordCacheSpy = sinon.spy(background, 'clearSeedWordCache') + + return store.dispatch(actions.confirmSeedWords()) + .then(() => { + assert.equal(clearSeedWordCacheSpy.callCount, 1) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('errors in callback will display warning', () => { + const store = mockStore({}) + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + clearSeedWordCacheSpy = sinon.stub(background, 'clearSeedWordCache') + + clearSeedWordCacheSpy.callsFake((callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.confirmSeedWords()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#createNewVaultAndRestore', () => { + + let createNewVaultAndRestoreSpy, clearSeedWordCacheSpy + + afterEach(() => { + createNewVaultAndRestoreSpy.restore() + }) + + it('clears seed words and restores new vault', () => { + + const store = mockStore({}) + + createNewVaultAndRestoreSpy = sinon.spy(background, 'createNewVaultAndRestore') + clearSeedWordCacheSpy = sinon.spy(background, 'clearSeedWordCache') + return store.dispatch(actions.createNewVaultAndRestore()) + .then(() => { + assert(clearSeedWordCacheSpy.calledOnce) + assert(createNewVaultAndRestoreSpy.calledOnce) + }) + }) + + it('errors when callback in clearSeedWordCache throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'HIDE_LOADING_INDICATION' }, + ] + + clearSeedWordCacheSpy = sinon.stub(background, 'clearSeedWordCache') + clearSeedWordCacheSpy.callsFake((callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.createNewVaultAndRestore()) + .then(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('errors when callback in createNewVaultAndRestore throws', () => { + + const store = mockStore({}) + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'HIDE_LOADING_INDICATION' }, + ] + + createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore') + + createNewVaultAndRestoreSpy.callsFake((password, seed, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.createNewVaultAndRestore()) + .then(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#createNewVaultAndKeychain', () => { + + let createNewVaultAndKeychainSpy, placeSeedWordsSpy + + afterEach(() => { + createNewVaultAndKeychainSpy.restore() + placeSeedWordsSpy.restore() + }) + + it('calls createNewVaultAndKeychain and placeSeedWords in background', () => { + + const store = mockStore() + + createNewVaultAndKeychainSpy = sinon.spy(background, 'createNewVaultAndKeychain') + placeSeedWordsSpy = sinon.spy(background, 'placeSeedWords') + + return store.dispatch(actions.createNewVaultAndKeychain()) + .then(() => { + assert(createNewVaultAndKeychainSpy.calledOnce) + assert(placeSeedWordsSpy.calledOnce) + }) + }) + + it('displays error and value when callback errors', () => { + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'HIDE_LOADING_INDICATION' }, + ] + + createNewVaultAndKeychainSpy = sinon.stub(background, 'createNewVaultAndKeychain') + createNewVaultAndKeychainSpy.callsFake((password, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.createNewVaultAndKeychain()) + .then(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + + }) + + it('errors when placeSeedWords throws', () => { + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'HIDE_LOADING_INDICATION' }, + ] + + placeSeedWordsSpy = sinon.stub(background, 'placeSeedWords') + placeSeedWordsSpy.callsFake((callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.createNewVaultAndKeychain()) + .then(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#requestRevealSeed', () => { + + let submitPasswordSpy, placeSeedWordsSpy + + afterEach(() => { + submitPasswordSpy.restore() + }) + + it('calls submitPassword and placeSeedWords from background', () => { + + const store = mockStore() + + submitPasswordSpy = sinon.spy(background, 'submitPassword') + placeSeedWordsSpy = sinon.spy(background, 'placeSeedWords') + + return store.dispatch(actions.requestRevealSeed()) + .then(() => { + assert(submitPasswordSpy.calledOnce) + assert(placeSeedWordsSpy.calledOnce) + }) + }) + + it('displays warning error with value when callback errors', () => { + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + submitPasswordSpy = sinon.stub(background, 'submitPassword') + submitPasswordSpy.callsFake((password, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.requestRevealSeed()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#requestRevealSeedWords', () => { + let submitPasswordSpy + + it('calls submitPassword in background', () => { + const store = mockStore() + + submitPasswordSpy = sinon.spy(background, 'verifySeedPhrase') + + return store.dispatch(actions.requestRevealSeedWords()) + .then(() => { + assert(submitPasswordSpy.calledOnce) + }) + }) + + it('displays warning error message then callback in background errors', () => { + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + submitPasswordSpy = sinon.stub(background, 'verifySeedPhrase') + submitPasswordSpy.callsFake((callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.requestRevealSeedWords()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + + }) + }) + + describe('#requestRevealSeed', () => { + + let submitPasswordSpy, placeSeedWordsSpy + + afterEach(() => { + submitPasswordSpy.restore() + placeSeedWordsSpy.restore() + }) + + it('calls submitPassword and placeSeedWords in background', () => { + + const store = mockStore() + + submitPasswordSpy = sinon.spy(background, 'submitPassword') + placeSeedWordsSpy = sinon.spy(background, 'placeSeedWords') + + return store.dispatch(actions.requestRevealSeed()) + .then(() => { + assert(submitPasswordSpy.calledOnce) + assert(placeSeedWordsSpy.calledOnce) + }) + }) + + it('displays warning error message when submitPassword in background errors', () => { + submitPasswordSpy = sinon.stub(background, 'submitPassword') + submitPasswordSpy.callsFake((password, callback) => { + callback(new Error('error')) + }) + + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + return store.dispatch(actions.requestRevealSeed()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('errors when placeSeedWords throw', () => { + placeSeedWordsSpy = sinon.stub(background, 'placeSeedWords') + placeSeedWordsSpy.callsFake((callback) => { + callback(new Error('error')) + }) + + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + return store.dispatch(actions.requestRevealSeed()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#removeAccount', () => { + let removeAccountSpy + + afterEach(() => { + removeAccountSpy.restore() + }) + + it('calls removeAccount in background and expect actions to show account', () => { + const store = mockStore(devState) + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'SHOW_ACCOUNTS_PAGE' }, + ] + + removeAccountSpy = sinon.spy(background, 'removeAccount') + + return store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')) + .then(() => { + assert(removeAccountSpy.calledOnce) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('displays warning error message when removeAccount callback errors', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + removeAccountSpy = sinon.stub(background, 'removeAccount') + removeAccountSpy.callsFake((address, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.removeAccount('0xe18035bf8712672935fdb4e5e431b1a0183d2dfc')) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + + }) + }) + + describe('#addNewKeyring', () => { + let addNewKeyringSpy + + beforeEach(() => { + addNewKeyringSpy = sinon.stub(background, 'addNewKeyring') + }) + + afterEach(() => { + addNewKeyringSpy.restore() + }) + + it('', () => { + const privateKey = 'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3' + + const store = mockStore() + store.dispatch(actions.addNewKeyring('Simple Key Pair', [ privateKey ])) + assert(addNewKeyringSpy.calledOnce) + }) + + it('errors then addNewKeyring in background throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + addNewKeyringSpy.callsFake((type, opts, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.addNewKeyring()) + assert.deepEqual(store.getActions(), expectedActions) + }) + + }) + + describe('#resetAccount', () => { + + let resetAccountSpy + + afterEach(() => { + resetAccountSpy.restore() + }) + + it('', () => { + + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'SHOW_ACCOUNTS_PAGE' }, + ] + + resetAccountSpy = sinon.spy(background, 'resetAccount') + + return store.dispatch(actions.resetAccount()) + .then(() => { + assert(resetAccountSpy.calledOnce) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('', () => { + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + resetAccountSpy = sinon.stub(background, 'resetAccount') + resetAccountSpy.callsFake((callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.resetAccount()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#importNewAccount', () => { + + let importAccountWithStrategySpy + + afterEach(() => { + importAccountWithStrategySpy.restore() + }) + + it('calls importAccountWithStrategies in background', () => { + const store = mockStore() + + importAccountWithStrategySpy = sinon.spy(background, 'importAccountWithStrategy') + + const importPrivkey = 'c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3' + + return store.dispatch(actions.importNewAccount('Private Key', [ importPrivkey ])) + .then(() => { + assert(importAccountWithStrategySpy.calledOnce) + }) + }) + + it('displays warning error message when importAccount in background callback errors', () => { + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: 'This may take a while, please be patient.' }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + importAccountWithStrategySpy = sinon.stub(background, 'importAccountWithStrategy') + importAccountWithStrategySpy.callsFake((strategy, args, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.importNewAccount()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#addNewAccount', () => { + + let addNewAccountSpy + + afterEach(() => { + addNewAccountSpy.restore() + }) + + it('', () => { + const store = mockStore({ metamask: devState }) + + addNewAccountSpy = sinon.spy(background, 'addNewAccount') + + return store.dispatch(actions.addNewAccount()) + .then(() => { + assert(addNewAccountSpy.calledOnce) + }) + }) + }) + + describe('#setCurrentCurrency', () => { + + let setCurrentCurrencySpy + + beforeEach(() => { + setCurrentCurrencySpy = sinon.stub(background, 'setCurrentCurrency') + }) + + afterEach(() => { + setCurrentCurrencySpy.restore() + }) + + it('', () => { + const store = mockStore() + + store.dispatch(actions.setCurrentCurrency('jpy')) + assert(setCurrentCurrencySpy.calledOnce) + }) + + it('', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + setCurrentCurrencySpy.callsFake((currencyCode, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.setCurrentCurrency()) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + describe('#signMsg', () => { + + let signMessageSpy, metamaskMsgs, msgId, messages + + const msgParams = { + from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', + } + + beforeEach(() => { + metamaskController.newUnsignedMessage(msgParams, noop) + metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() + messages = metamaskController.messageManager.messages + msgId = Object.keys(metamaskMsgs)[0] + messages[0].msgParams.metamaskId = parseInt(msgId) + }) + + afterEach(() => { + signMessageSpy.restore() + }) + + it('calls signMsg in background', () => { + const store = mockStore() + + signMessageSpy = sinon.spy(background, 'signMessage') + + return store.dispatch(actions.signMsg(msgParams)) + .then(() => { + assert(signMessageSpy.calledOnce) + }) + + }) + + it('errors when signMessage in background throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'UPDATE_METAMASK_STATE', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + signMessageSpy = sinon.stub(background, 'signMessage') + signMessageSpy.callsFake((msgData, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.signMsg()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + }) + + describe('#signPersonalMsg', () => { + + let signPersonalMessageSpy, metamaskMsgs, msgId, personalMessages + + const msgParams = { + from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', + data: '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0', + } + + beforeEach(() => { + metamaskController.newUnsignedPersonalMessage(msgParams, noop) + metamaskMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() + personalMessages = metamaskController.personalMessageManager.messages + msgId = Object.keys(metamaskMsgs)[0] + personalMessages[0].msgParams.metamaskId = parseInt(msgId) + }) + + afterEach(() => { + signPersonalMessageSpy.restore() + }) + + it('', () => { + const store = mockStore() + + signPersonalMessageSpy = sinon.spy(background, 'signPersonalMessage') + + return store.dispatch(actions.signPersonalMsg(msgParams)) + .then(() => { + assert(signPersonalMessageSpy.calledOnce) + }) + + }) + + it('', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'UPDATE_METAMASK_STATE', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + signPersonalMessageSpy = sinon.stub(background, 'signPersonalMessage') + signPersonalMessageSpy.callsFake((msgData, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.signPersonalMsg(msgParams)) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + }) + + describe('#signTx', () => { + + let sendTransactionSpy + + beforeEach(() => { + global.ethQuery = new EthQuery(provider) + sendTransactionSpy = sinon.stub(global.ethQuery, 'sendTransaction') + }) + + afterEach(() => { + sendTransactionSpy.restore() + }) + + it('calls sendTransaction in global ethQuery', () => { + const store = mockStore() + store.dispatch(actions.signTx()) + assert(sendTransactionSpy.calledOnce) + }) + + it('errors in when sendTransaction throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'SHOW_CONF_TX_PAGE', transForward: true, id: undefined }, + ] + sendTransactionSpy.callsFake((txData, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.signTx()) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + describe('#signTokenTx', () => { + + let tokenSpy + + beforeEach(() => { + global.eth = new Eth(provider) + tokenSpy = sinon.spy(global.eth, 'contract') + }) + + afterEach(() => { + tokenSpy.restore() + }) + + it('', () => { + const store = mockStore() + store.dispatch(actions.signTokenTx()) + assert(tokenSpy.calledOnce) + }) + }) + + describe('#lockMetamask', () => { + let backgroundSetLockedSpy + + afterEach(() => { + backgroundSetLockedSpy.restore() + }) + + it('', () => { + const store = mockStore() + + backgroundSetLockedSpy = sinon.spy(background, 'setLocked') + + return store.dispatch(actions.lockMetamask()) + .then(() => { + assert(backgroundSetLockedSpy.calledOnce) + }) + }) + + it('returns display warning error with value when setLocked in background callback errors', () => { + const store = mockStore() + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'LOCK_METAMASK' }, + ] + backgroundSetLockedSpy = sinon.stub(background, 'setLocked') + backgroundSetLockedSpy.callsFake(callback => { + callback(new Error('error')) + }) + + return store.dispatch(actions.lockMetamask()) + .then(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#setSelectedAddress', () => { + let setSelectedAddressSpy + + beforeEach(() => { + setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') + }) + + afterEach(() => { + setSelectedAddressSpy.restore() + }) + + it('calls setSelectedAddress in background', () => { + const store = mockStore({ metamask: devState }) + + store.dispatch(actions.setSelectedAddress('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + assert(setSelectedAddressSpy.calledOnce) + }) + + it('errors when setSelectedAddress throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + setSelectedAddressSpy.callsFake((address, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.setSelectedAddress()) + assert.deepEqual(store.getActions(), expectedActions) + + }) + }) + + describe('#showAccountDetail', () => { + let setSelectedAddressSpy + + beforeEach(() => { + setSelectedAddressSpy = sinon.stub(background, 'setSelectedAddress') + }) + + afterEach(() => { + setSelectedAddressSpy.restore() + }) + + it('#showAccountDetail', () => { + const store = mockStore() + + store.dispatch(actions.showAccountDetail()) + assert(setSelectedAddressSpy.calledOnce) + }) + + it('', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + setSelectedAddressSpy.callsFake((address, callback) => { + callback(new Error('error')) + }) + + + store.dispatch(actions.showAccountDetail()) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + describe('#addToken', () => { + let addTokenSpy + + beforeEach(() => { + addTokenSpy = sinon.stub(background, 'addToken') + }) + + afterEach(() => { + addTokenSpy.restore() + }) + + it('calls addToken in background', () => { + const store = mockStore() + + store.dispatch(actions.addToken()) + .then(() => { + assert(addTokenSpy.calledOnce) + }) + }) + + it('errors when addToken in background throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'UPDATE_TOKENS', newTokens: undefined }, + ] + + addTokenSpy.callsFake((address, symbol, decimals, image, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.addToken()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#removeToken', () => { + + let removeTokenSpy + + beforeEach(() => { + removeTokenSpy = sinon.stub(background, 'removeToken') + }) + + afterEach(() => { + removeTokenSpy.restore() + }) + + it('calls removeToken in background', () => { + const store = mockStore() + store.dispatch(actions.removeToken()) + .then(() => { + assert(removeTokenSpy.calledOnce) + }) + }) + + it('errors when removeToken in background fails', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'UPDATE_TOKENS', newTokens: undefined }, + ] + + removeTokenSpy.callsFake((address, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.removeToken()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#markNoticeRead', () => { + let markNoticeReadSpy + const notice = { + id: 0, + read: false, + date: 'test date', + title: 'test title', + body: 'test body', + } + + beforeEach(() => { + markNoticeReadSpy = sinon.stub(background, 'markNoticeRead') + }) + + afterEach(() => { + markNoticeReadSpy.restore() + }) + + it('calls markNoticeRead in background', () => { + const store = mockStore() + + store.dispatch(actions.markNoticeRead(notice)) + .then(() => { + assert(markNoticeReadSpy.calledOnce) + }) + + }) + + it('errors when markNoticeRead in background throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + markNoticeReadSpy.callsFake((notice, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.markNoticeRead()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#setProviderType', () => { + let setProviderTypeSpy + + beforeEach(() => { + setProviderTypeSpy = sinon.stub(background, 'setProviderType') + }) + + afterEach(() => { + setProviderTypeSpy.restore() + }) + + it('', () => { + const store = mockStore() + store.dispatch(actions.setProviderType()) + assert(setProviderTypeSpy.calledOnce) + }) + + it('', () => { + const store = mockStore() + const expectedActions = [ + { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, + ] + + setProviderTypeSpy.callsFake((type, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.setProviderType()) + assert(setProviderTypeSpy.calledOnce) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + describe('#setRpcTarget', () => { + let setRpcTargetSpy + + beforeEach(() => { + setRpcTargetSpy = sinon.stub(background, 'setCustomRpc') + }) + + afterEach(() => { + setRpcTargetSpy.restore() + }) + + it('', () => { + const store = mockStore() + store.dispatch(actions.setRpcTarget('http://localhost:8545')) + assert(setRpcTargetSpy.calledOnce) + }) + + it('', () => { + const store = mockStore() + const expectedActions = [ + { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, + ] + + setRpcTargetSpy.callsFake((newRpc, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.setRpcTarget()) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + describe('#addToAddressBook', () => { + let addToAddressBookSpy + + beforeEach(() => { + addToAddressBookSpy = sinon.stub(background, 'setAddressBook') + }) + + afterEach(() => { + addToAddressBookSpy.restore() + }) + + it('', () => { + const store = mockStore() + store.dispatch(actions.addToAddressBook('test')) + assert(addToAddressBookSpy.calledOnce) + }) + }) + + describe('#exportAccount', () => { + let submitPasswordSpy, exportAccountSpy + + afterEach(() => { + submitPasswordSpy.restore() + exportAccountSpy.restore() + }) + + it('returns expected actions for successful action', () => { + const store = mockStore(devState) + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'SHOW_PRIVATE_KEY', value: '7ec73b91bb20f209a7ff2d32f542c3420b4fccf14abcc7840d2eff0ebcb18505' }, + ] + + submitPasswordSpy = sinon.spy(background, 'submitPassword') + exportAccountSpy = sinon.spy(background, 'exportAccount') + + return store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + .then((result) => { + assert(submitPasswordSpy.calledOnce) + assert(exportAccountSpy.calledOnce) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('returns action errors when first func callback errors', () => { + const store = mockStore(devState) + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'Incorrect Password.' }, + ] + + submitPasswordSpy = sinon.stub(background, 'submitPassword') + submitPasswordSpy.callsFake((password, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('returns action errors when second func callback errors', () => { + const store = mockStore(devState) + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'Had a problem exporting the account.' }, + ] + + exportAccountSpy = sinon.stub(background, 'exportAccount') + exportAccountSpy.callsFake((address, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc')) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#setAccountLabel', () => { + let setAccountLabelSpy + + beforeEach(() => { + setAccountLabelSpy = sinon.stub(background, 'setAccountLabel') + }) + + it('', () => { + const store = mockStore() + store.dispatch(actions.setAccountLabel('0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc', 'test')) + assert(setAccountLabelSpy.calledOnce) + }) + }) + + describe('#pairUpdate', () => { + beforeEach(() => { + + nock('https://shapeshift.io') + .defaultReplyHeaders({ 'access-control-allow-origin': '*' }) + .get('/marketinfo/btc_eth') + .reply(200, {pair: 'BTC_ETH', rate: 25.68289016, minerFee: 0.00176, limit: 0.67748474, minimum: 0.00013569, maxLimit: 0.67758573}) + + nock('https://shapeshift.io') + .defaultReplyHeaders({ 'access-control-allow-origin': '*' }) + .get('/coins') + .reply(200) + }) + + afterEach(() => { + nock.restore() + }) + + it('', () => { + const store = mockStore() + // issue with dispatch action in callback not showing + const expectedActions = [ + { type: 'SHOW_SUB_LOADING_INDICATION' }, + { type: 'HIDE_WARNING' }, + ] + + store.dispatch(actions.pairUpdate('btc')) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + describe('#setFeatureFlag', () => { + let setFeatureFlagSpy + + beforeEach(() => { + setFeatureFlagSpy = sinon.stub(background, 'setFeatureFlag') + }) + + afterEach(() => { + setFeatureFlagSpy.restore() + }) + + it('calls setFeatureFlag in the background', () => { + const store = mockStore() + + store.dispatch(actions.setFeatureFlag()) + assert(setFeatureFlagSpy.calledOnce) + }) + + it('errors when setFeatureFlag in background throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + setFeatureFlagSpy.callsFake((feature, activated, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.setFeatureFlag()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#updateNetworkNonce', () => { + let getTransactionCountSpy + + afterEach(() => { + getTransactionCountSpy.restore() + }) + + it('', () => { + const store = mockStore() + getTransactionCountSpy = sinon.spy(global.ethQuery, 'getTransactionCount') + + store.dispatch(actions.updateNetworkNonce()) + .then(() => { + assert(getTransactionCountSpy.calledOnce) + }) + }) + + it('', () => { + const store = mockStore() + const expectedActions = [ + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + + getTransactionCountSpy = sinon.stub(global.ethQuery, 'getTransactionCount') + getTransactionCountSpy.callsFake((address, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.updateNetworkNonce()) + .catch(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#setUseBlockie', () => { + let setUseBlockieSpy + + beforeEach(() => { + setUseBlockieSpy = sinon.stub(background, 'setUseBlockie') + }) + + afterEach(() => { + setUseBlockieSpy.restore() + }) + + it('calls setUseBlockie in background', () => { + const store = mockStore() + + store.dispatch(actions.setUseBlockie()) + assert(setUseBlockieSpy.calledOnce) + }) + + it('errors when setUseBlockie in background throws', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + { type: 'SET_USE_BLOCKIE', value: undefined }, + ] + + setUseBlockieSpy.callsFake((val, callback) => { + callback(new Error('error')) + }) + + store.dispatch(actions.setUseBlockie()) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + describe('#updateCurrentLocale', () => { + let setCurrentLocaleSpy + + beforeEach(() => { + fetchMock.get('*', enLocale) + }) + + afterEach(() => { + setCurrentLocaleSpy.restore() + fetchMock.restore() + }) + + it('', () => { + const store = mockStore() + setCurrentLocaleSpy = sinon.spy(background, 'setCurrentLocale') + + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'SET_CURRENT_LOCALE', value: 'en' }, + { type: 'SET_LOCALE_MESSAGES', value: enLocale }, + ] + + return store.dispatch(actions.updateCurrentLocale('en')) + .then(() => { + assert(setCurrentLocaleSpy.calledOnce) + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + + it('', () => { + const store = mockStore() + const expectedActions = [ + { type: 'SHOW_LOADING_INDICATION', value: undefined }, + { type: 'HIDE_LOADING_INDICATION' }, + { type: 'DISPLAY_WARNING', value: 'error' }, + ] + setCurrentLocaleSpy = sinon.stub(background, 'setCurrentLocale') + setCurrentLocaleSpy.callsFake((key, callback) => { + callback(new Error('error')) + }) + + return store.dispatch(actions.updateCurrentLocale('en')) + .then(() => { + assert.deepEqual(store.getActions(), expectedActions) + }) + }) + }) + + describe('#markPasswordForgotten', () => { + let markPasswordForgottenSpy + + beforeEach(() => { + markPasswordForgottenSpy = sinon.stub(background, 'markPasswordForgotten') + }) + + afterEach(() => { + markPasswordForgottenSpy.restore() + }) + + it('', () => { + const store = mockStore() + store.dispatch(actions.markPasswordForgotten()) + assert(markPasswordForgottenSpy.calledOnce) + }) + }) + + describe('#unMarkPasswordForgotten', () => { + let unMarkPasswordForgottenSpy + + beforeEach(() => { + unMarkPasswordForgottenSpy = sinon.stub(background, 'unMarkPasswordForgotten') + }) + + afterEach(() => { + unMarkPasswordForgottenSpy.restore() + }) + + it('', () => { + const store = mockStore() + store.dispatch(actions.unMarkPasswordForgotten()) + assert(unMarkPasswordForgottenSpy.calledOnce) + }) + }) + + +}) diff --git a/test/unit/ui/app/components/identicon.spec.js b/test/unit/ui/app/components/identicon.spec.js new file mode 100644 index 000000000..a2f8d8246 --- /dev/null +++ b/test/unit/ui/app/components/identicon.spec.js @@ -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') + }) +}) diff --git a/test/unit/ui/app/components/token-cell.spec.js b/test/unit/ui/app/components/token-cell.spec.js new file mode 100644 index 000000000..6145c6924 --- /dev/null +++ b/test/unit/ui/app/components/token-cell.spec.js @@ -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') + }) + +}) diff --git a/test/unit/ui/app/selectors.spec.js b/test/unit/ui/app/selectors.spec.js new file mode 100644 index 000000000..78c4267ee --- /dev/null +++ b/test/unit/ui/app/selectors.spec.js @@ -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) + }) +}) diff --git a/test/unit/ui/etherscan-prefix-for-network.spec.js b/test/unit/ui/etherscan-prefix-for-network.spec.js new file mode 100644 index 000000000..f0aeb8306 --- /dev/null +++ b/test/unit/ui/etherscan-prefix-for-network.spec.js @@ -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.') + }) + +}) |