// Tests for the recovery phrase display (issue #161). // // These cover the parts that do not need a DOM: which wallet types may be // offered the action at all, the exclusion of the screen from the set of // views the popup may reopen onto, and the absence of any path from this // module to the logger. The DOM behaviour it guards — nothing rendered // before the password is accepted, a wrong password revealing nothing, and // the wipe on leaving — is driven against the real popup in a real browser // by tests/e2e/run.js, which is where every other view behaviour is tested. const fs = require("fs"); const path = require("path"); const { walletHasRecoveryPhrase } = require("../src/shared/wallet"); const { RESTORABLE_VIEWS } = require("../src/popup/restorableViews"); const SHOW_PHRASE_VIEW = "show-phrase"; // helpers.js pulls in state.js, which reads chrome.storage.local at load. function loadHelpers() { globalThis.chrome = { storage: { local: { get: async () => ({}), set: async () => {} } }, }; return require("../src/popup/views/helpers"); } describe("which wallets have a recovery phrase", () => { test("an HD wallet does", () => { expect(walletHasRecoveryPhrase({ type: "hd" })).toBe(true); }); // A key wallet holds a bare private key and an xprv wallet an extended // private key. Neither can be turned back into words, so neither may be // offered the action. test("a key wallet does not", () => { expect(walletHasRecoveryPhrase({ type: "key" })).toBe(false); }); test("an xprv wallet does not", () => { expect(walletHasRecoveryPhrase({ type: "xprv" })).toBe(false); }); test("an unknown or missing wallet type does not", () => { expect(walletHasRecoveryPhrase({ type: "something-new" })).toBe(false); expect(walletHasRecoveryPhrase({})).toBe(false); expect(walletHasRecoveryPhrase(undefined)).toBe(false); }); }); describe("views the popup may reopen onto", () => { // Restoring onto a secret screen would put the phrase on screen with no // password prompt in front of it, on a popup the user may have reopened // by accident. test("the recovery phrase screen is not restorable", () => { expect(RESTORABLE_VIEWS.has(SHOW_PHRASE_VIEW)).toBe(false); }); test("the private key export screen is not restorable either", () => { expect(RESTORABLE_VIEWS.has("export-privkey")).toBe(false); }); test("the recovery phrase screen is still a registered view", () => { const { VIEWS } = loadHelpers(); expect(VIEWS).toContain(SHOW_PHRASE_VIEW); }); // Guards the other direction: a restorable name that is not a real view // would leave restoreView() showing nothing at all. test("every restorable view is a registered view", () => { const { VIEWS } = loadHelpers(); for (const view of RESTORABLE_VIEWS) { expect(VIEWS).toContain(view); } }); }); describe("the phrase cannot reach the logger", () => { const source = fs.readFileSync( path.join(__dirname, "..", "src", "popup", "views", "showPhrase.js"), "utf8", ); // The decrypted phrase only ever lives in a local and in the DOM node // that displays it. The module has no logger to hand it to, and this // pins that: src/shared/log.js writes to the console, and a console // record of a recovery phrase outlives the popup. test("the view does not import src/shared/log.js", () => { expect(source).not.toMatch(/require\(["'][^"']*shared\/log["']\)/); }); test("the view calls no logger method", () => { expect(source).not.toMatch(/\blog\.(debugf|infof|warnf|errorf)\b/); }); });