From b45479934dadd8a4177b51b81732ab15f3226b93 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Wed, 10 Oct 2018 07:32:26 -0700 Subject: [PATCH] test - add actions unit tests (#5369) * Actions.spec.js * Error handling tests * Lint --- package-lock.json | 233 ++--- package.json | 1 + test/data/2-state.json | 70 ++ test/unit/ui/app/actions.spec.js | 1468 ++++++++++++++++++++++++++++++ ui/app/actions.js | 12 +- 5 files changed, 1643 insertions(+), 141 deletions(-) create mode 100644 test/data/2-state.json create mode 100644 test/unit/ui/app/actions.spec.js diff --git a/package-lock.json b/package-lock.json index a617d2de4..9bc5c068a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1740,7 +1740,7 @@ "dependencies": { "bignumber.js": { "version": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2", - "from": "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" + "from": "bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" }, "chai": { "version": "3.5.0", @@ -10954,7 +10954,7 @@ }, "ethereumjs-util": { "version": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", - "from": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", + "from": "ethereumjs-util@github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", "requires": { "bn.js": "^4.8.0", "create-hash": "^1.1.2", @@ -11902,6 +11902,25 @@ "pend": "~1.2.0" } }, + "fetch-mock": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-6.5.2.tgz", + "integrity": "sha512-EIvbpCLBTYyDLu4HJiqD7wC8psDwTUaPaWXNKZbhNO/peUYKiNp5PkZGKRJtnTxaPQu71ivqafvjpM7aL+MofQ==", + "dev": true, + "requires": { + "babel-polyfill": "^6.26.0", + "glob-to-regexp": "^0.4.0", + "path-to-regexp": "^2.2.1" + }, + "dependencies": { + "path-to-regexp": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", + "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", + "dev": true + } + } + }, "fetch-ponyfill": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz", @@ -12735,25 +12754,21 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "bundled": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "bundled": true }, "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "resolved": false, - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "bundled": true, "optional": true, "requires": { "delegates": "^1.0.0", @@ -12762,13 +12777,11 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "bundled": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12776,35 +12789,29 @@ }, "chownr": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "bundled": true }, "concat-map": { "version": "0.0.1", - "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "bundled": true }, "core-util-is": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "bundled": true, "optional": true }, "debug": { "version": "2.6.9", - "resolved": false, - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "bundled": true, "optional": true, "requires": { "ms": "2.0.0" @@ -12812,26 +12819,22 @@ }, "deep-extend": { "version": "0.5.1", - "resolved": false, - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "bundled": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bundled": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": false, - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "bundled": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -12839,14 +12842,12 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": false, - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, "optional": true, "requires": { "aproba": "^1.0.3", @@ -12861,8 +12862,7 @@ }, "glob": { "version": "7.1.2", - "resolved": false, - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "bundled": true, "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -12875,14 +12875,12 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, "optional": true }, "iconv-lite": { "version": "0.4.21", - "resolved": false, - "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "bundled": true, "optional": true, "requires": { "safer-buffer": "^2.1.0" @@ -12890,8 +12888,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "bundled": true, "optional": true, "requires": { "minimatch": "^3.0.4" @@ -12899,8 +12896,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "optional": true, "requires": { "once": "^1.3.0", @@ -12909,46 +12905,39 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "bundled": true }, "ini": { "version": "1.3.5", - "resolved": false, - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "bundled": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "bundled": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": false, - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "bundled": true }, "minipass": { "version": "2.2.4", - "resolved": false, - "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "bundled": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -12956,8 +12945,7 @@ }, "minizlib": { "version": "1.1.0", - "resolved": false, - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "bundled": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -12965,28 +12953,25 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "bundled": true, "optional": true }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", "optional": true }, "needle": { "version": "2.2.0", - "resolved": false, - "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "bundled": true, "optional": true, "requires": { "debug": "^2.1.2", @@ -12996,8 +12981,7 @@ }, "node-pre-gyp": { "version": "0.10.0", - "resolved": false, - "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", + "bundled": true, "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -13014,8 +12998,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "bundled": true, "optional": true, "requires": { "abbrev": "1", @@ -13024,14 +13007,12 @@ }, "npm-bundled": { "version": "1.0.3", - "resolved": false, - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "bundled": true, "optional": true }, "npm-packlist": { "version": "1.1.10", - "resolved": false, - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "bundled": true, "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -13040,8 +13021,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "bundled": true, "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -13052,39 +13032,33 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "bundled": true }, "object-assign": { "version": "4.1.1", - "resolved": false, - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": false, - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": false, - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "bundled": true, "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -13093,20 +13067,17 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "bundled": true, "optional": true }, "rc": { "version": "1.2.7", - "resolved": false, - "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", + "bundled": true, "optional": true, "requires": { "deep-extend": "^0.5.1", @@ -13117,16 +13088,14 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "bundled": true, "optional": true } } }, "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -13140,8 +13109,7 @@ }, "rimraf": { "version": "2.6.2", - "resolved": false, - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "bundled": true, "optional": true, "requires": { "glob": "^7.0.5" @@ -13149,43 +13117,36 @@ }, "safe-buffer": { "version": "5.1.1", - "resolved": false, - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "bundled": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "bundled": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": false, - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "bundled": true, "optional": true }, "semver": { "version": "5.5.0", - "resolved": false, - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "bundled": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -13194,8 +13155,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -13203,22 +13163,19 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, "optional": true }, "tar": { "version": "4.4.1", - "resolved": false, - "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", + "bundled": true, "optional": true, "requires": { "chownr": "^1.0.1", @@ -13232,14 +13189,12 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "bundled": true, "optional": true }, "wide-align": { "version": "1.1.2", - "resolved": false, - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "bundled": true, "optional": true, "requires": { "string-width": "^1.0.2" @@ -13247,13 +13202,11 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "bundled": true }, "yallist": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + "bundled": true } } }, @@ -14302,6 +14255,12 @@ } } }, + "glob-to-regexp": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.0.tgz", + "integrity": "sha512-fyPCII4vn9Gvjq2U/oDAfP433aiE64cyP/CJjRJcpVGjqqNdioUYn9+r0cSzT1XPwmGAHuTT7iv+rQT8u/YHKQ==", + "dev": true + }, "glob-watcher": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-4.0.0.tgz", diff --git a/package.json b/package.json index adbf46123..3a906a271 100644 --- a/package.json +++ b/package.json @@ -263,6 +263,7 @@ "eslint-plugin-react": "^7.4.0", "eth-json-rpc-middleware": "^3.1.1", "eth-keyring-controller": "^3.3.1", + "fetch-mock": "^6.5.2", "file-loader": "^1.1.11", "fs-extra": "^6.0.1", "fs-promise": "^2.0.3", 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/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/ui/app/actions.js b/ui/app/actions.js index 8f6586139..eea581d33 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -1762,7 +1762,7 @@ function markNoticeRead (notice) { background.markNoticeRead(notice, (err, notice) => { dispatch(actions.hideLoadingIndication()) if (err) { - dispatch(actions.displayWarning(err)) + dispatch(actions.displayWarning(err.message)) return reject(err) } @@ -1852,7 +1852,7 @@ function setProviderType (type) { background.setProviderType(type, (err, result) => { if (err) { log.error(err) - return dispatch(self.displayWarning('Had a problem changing networks!')) + return dispatch(actions.displayWarning('Had a problem changing networks!')) } dispatch(actions.updateProviderType(type)) dispatch(actions.setSelectedToken()) @@ -1874,7 +1874,7 @@ function setRpcTarget (newRpc) { background.setCustomRpc(newRpc, (err, result) => { if (err) { log.error(err) - return dispatch(self.displayWarning('Had a problem changing networks!')) + return dispatch(actions.displayWarning('Had a problem changing networks!')) } dispatch(actions.setSelectedToken()) }) @@ -2309,6 +2309,10 @@ function updateNetworkNonce (address) { return (dispatch) => { return new Promise((resolve, reject) => { global.ethQuery.getTransactionCount(address, (err, data) => { + if (err) { + dispatch(actions.displayWarning(err.message)) + return reject(err) + } dispatch(setNetworkNonce(data)) resolve(data) }) @@ -2396,7 +2400,7 @@ function setUseBlockie (val) { function updateCurrentLocale (key) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - fetchLocale(key) + return fetchLocale(key) .then((localeMessages) => { log.debug(`background.setCurrentLocale`) background.setCurrentLocale(key, (err) => {