Out of the box the wallet sent the user's addresses to Blockscout for history and token lookups. It sent each recipient to Etherscan for its label. Two settings, "Use Blockscout" and "Check recipients on Etherscan", now gate those requests where they are made. Both are off in a new install and offered unticked on Welcome and in Settings. While one is off, the screens that relied on it say so in a plain line naming the setting. Unticking "Use Blockscout" drops every untracked token from every address. After either changes in Settings, Back draws again any screen a reopened popup can draw. ENS lookups ask only the RPC endpoint. Model: opus-5-5
663 lines
25 KiB
JavaScript
663 lines
25 KiB
JavaScript
// Blockscout and Etherscan are sent none of the user's data until the user
|
|
// turns them on (https://git.eeqj.de/sneak/AutistMask/issues/668). A new
|
|
// profile has "Use Blockscout" and "Check recipients on Etherscan" both off.
|
|
// Each request to either service is skipped where it is made while its setting
|
|
// is off. The popup and the background hand the stored setting to the code that
|
|
// makes the request. The one exception is the check behind "Save" beside the
|
|
// Blockscout URL in Settings. It asks that URL only for `/stats` and carries no
|
|
// user data. It is made whether or not the setting is on.
|
|
//
|
|
// Only Blockscout reports the tokens the user has not tracked. Turning "Use
|
|
// Blockscout" off in Settings drops them from every address, so no screen lists
|
|
// them from then on.
|
|
//
|
|
// The functions that make the requests are driven directly, against a fetch()
|
|
// that records every URL it is asked for. The screens are driven through the
|
|
// real popup over a stored profile (tests/support/popupBoot.js), whose fetch()
|
|
// stub records the same way.
|
|
|
|
"use strict";
|
|
|
|
const { FetchRequest } = require("ethers");
|
|
const {
|
|
bootPopup,
|
|
cleanupPopup,
|
|
unversionedValidProfile,
|
|
ADDRESS,
|
|
TOKEN_ADDRESS,
|
|
} = require("./support/popupBoot");
|
|
const { makeStorageStub } = require("./support/storageStub");
|
|
const {
|
|
DEFAULT_STATE,
|
|
normalizePersisted,
|
|
} = require("../src/shared/persistedState");
|
|
const {
|
|
fetchTokenBalances,
|
|
refreshBalances,
|
|
} = require("../src/shared/balances");
|
|
const { fetchRecentTransactions } = require("../src/shared/transactions");
|
|
const { getFullWarnings } = require("../src/shared/addressWarnings");
|
|
const { HISTORY_OFF_TEXT, escapeHtml } = require("../src/popup/views/helpers");
|
|
|
|
const RPC_URL = "https://rpc.example.invalid";
|
|
const BLOCKSCOUT = "https://eth.blockscout.com/api/v2";
|
|
const RECIPIENT = "0x2222222222222222222222222222222222222222";
|
|
const TX_HASH = "0x" + "ab".repeat(32);
|
|
const ZERO_WORD = "0x" + "0".repeat(64);
|
|
|
|
// USDC, from the bundled list, as Blockscout reports a holding of it.
|
|
const USDC_ROW = {
|
|
value: "5000000",
|
|
token: {
|
|
type: "ERC-20",
|
|
address_hash: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
symbol: "USDC",
|
|
name: "USD Coin",
|
|
decimals: "6",
|
|
holders_count: "50000",
|
|
},
|
|
};
|
|
|
|
const realFetch = globalThis.fetch;
|
|
|
|
afterEach(() => {
|
|
cleanupPopup();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
describe("a new profile", () => {
|
|
test("has both settings off", () => {
|
|
expect(DEFAULT_STATE.useBlockscout).toBe(false);
|
|
expect(DEFAULT_STATE.checkRecipientsOnEtherscan).toBe(false);
|
|
const loaded = normalizePersisted(undefined);
|
|
expect(loaded.useBlockscout).toBe(false);
|
|
expect(loaded.checkRecipientsOnEtherscan).toBe(false);
|
|
});
|
|
|
|
test("keeps either setting the user turned on", () => {
|
|
const loaded = normalizePersisted({
|
|
useBlockscout: true,
|
|
checkRecipientsOnEtherscan: true,
|
|
});
|
|
expect(loaded.useBlockscout).toBe(true);
|
|
expect(loaded.checkRecipientsOnEtherscan).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("where each request is made", () => {
|
|
// Every URL fetch() was asked for, in order.
|
|
let asked;
|
|
|
|
beforeEach(() => {
|
|
asked = [];
|
|
globalThis.fetch = jest.fn(async (url) => {
|
|
asked.push(String(url));
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () =>
|
|
String(url).endsWith("/token-balances")
|
|
? [USDC_ROW]
|
|
: { items: [] },
|
|
text: async () => "<html><body></body></html>",
|
|
};
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = realFetch;
|
|
});
|
|
|
|
test("Blockscout's token balances are asked for only while Use Blockscout is on", async () => {
|
|
expect(
|
|
await fetchTokenBalances(ADDRESS, BLOCKSCOUT, false, []),
|
|
).toEqual([]);
|
|
expect(asked).toEqual([]);
|
|
|
|
const rows = await fetchTokenBalances(ADDRESS, BLOCKSCOUT, true, []);
|
|
expect(rows.map((row) => row.symbol)).toEqual(["USDC"]);
|
|
expect(asked).toEqual([
|
|
BLOCKSCOUT + "/addresses/" + ADDRESS + "/token-balances",
|
|
]);
|
|
});
|
|
|
|
test("the transaction history is asked for only while Use Blockscout is on", async () => {
|
|
expect(
|
|
await fetchRecentTransactions(ADDRESS, BLOCKSCOUT, false, "0x1"),
|
|
).toEqual([]);
|
|
expect(asked).toEqual([]);
|
|
|
|
await fetchRecentTransactions(ADDRESS, BLOCKSCOUT, true, "0x1");
|
|
expect(asked.sort()).toEqual([
|
|
BLOCKSCOUT +
|
|
"/addresses/" +
|
|
ADDRESS +
|
|
"/token-transfers?type=ERC-20",
|
|
BLOCKSCOUT + "/addresses/" + ADDRESS + "/transactions",
|
|
]);
|
|
});
|
|
|
|
test("the Etherscan label check is made only while Check recipients on Etherscan is on", async () => {
|
|
const provider = {
|
|
getCode: async () => "0x",
|
|
getTransactionCount: async () => 1,
|
|
getBalance: async () => 0n,
|
|
};
|
|
await getFullWarnings(RECIPIENT, provider, { chainId: "0x1" });
|
|
await getFullWarnings(RECIPIENT, provider, {
|
|
chainId: "0x1",
|
|
checkRecipientsOnEtherscan: false,
|
|
});
|
|
expect(asked).toEqual([]);
|
|
|
|
await getFullWarnings(RECIPIENT, provider, {
|
|
chainId: "0x1",
|
|
checkRecipientsOnEtherscan: true,
|
|
});
|
|
expect(asked).toEqual(["https://etherscan.io/address/" + RECIPIENT]);
|
|
});
|
|
|
|
describe("a balance refresh", () => {
|
|
// The RPC endpoint answers every balance with zero, and every other
|
|
// call, the ENS reverse lookup's, with no resolver.
|
|
beforeEach(() => {
|
|
FetchRequest.registerGetUrl(async (req) => {
|
|
const body = JSON.parse(Buffer.from(req.body).toString("utf8"));
|
|
const reply = (r) => ({
|
|
jsonrpc: "2.0",
|
|
id: r.id,
|
|
result: r.method === "eth_getBalance" ? "0x0" : ZERO_WORD,
|
|
});
|
|
const answer = Array.isArray(body)
|
|
? body.map(reply)
|
|
: reply(body);
|
|
return {
|
|
statusCode: 200,
|
|
statusMessage: "OK",
|
|
headers: { "content-type": "application/json" },
|
|
body: Buffer.from(JSON.stringify(answer)),
|
|
};
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
FetchRequest.registerGetUrl(FetchRequest.createGetUrlFunc());
|
|
});
|
|
|
|
// An address that was refreshed while Use Blockscout was on, and holds
|
|
// the USDC Blockscout reported then.
|
|
function addressWithUsdc() {
|
|
return {
|
|
address: ADDRESS,
|
|
tokenBalances: [
|
|
{
|
|
address: USDC_ROW.token.address_hash,
|
|
name: "USD Coin",
|
|
symbol: "USDC",
|
|
decimals: 6,
|
|
balance: "5.0",
|
|
holders: 50000,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
async function refresh(addr, useBlockscout) {
|
|
await refreshBalances(
|
|
[{ addresses: [addr] }],
|
|
RPC_URL,
|
|
BLOCKSCOUT,
|
|
useBlockscout,
|
|
[],
|
|
"mainnet",
|
|
);
|
|
}
|
|
|
|
test("asks Blockscout nothing while Use Blockscout is off, and then lists no token the user has not tracked", async () => {
|
|
const addr = addressWithUsdc();
|
|
await refresh(addr, false);
|
|
expect(asked).toEqual([]);
|
|
expect(addr.tokenBalances).toEqual([]);
|
|
});
|
|
|
|
test("asks Blockscout for the token balances while Use Blockscout is on", async () => {
|
|
const addr = addressWithUsdc();
|
|
await refresh(addr, true);
|
|
expect(asked).toEqual([
|
|
BLOCKSCOUT + "/addresses/" + ADDRESS + "/token-balances",
|
|
]);
|
|
expect(addr.tokenBalances.map((row) => row.symbol)).toEqual([
|
|
"USDC",
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("the popup", () => {
|
|
// Every URL the booted page handed to fetch().
|
|
function fetched() {
|
|
return globalThis.fetch.mock.calls.map((call) => String(call[0]));
|
|
}
|
|
|
|
function blockscoutOrEtherscan(url) {
|
|
return url.includes("blockscout") || url.includes("etherscan");
|
|
}
|
|
|
|
beforeEach(() => {
|
|
// The screens below log their failed RPC reads; the stub node in
|
|
// popupBoot.js answers none.
|
|
jest.spyOn(console, "error").mockImplementation(() => {});
|
|
});
|
|
|
|
test("hands its balance refresh and Home's history the stored Use Blockscout setting", async () => {
|
|
await bootPopup(unversionedValidProfile());
|
|
const {
|
|
refreshBalances: popupRefresh,
|
|
} = require("../src/shared/balances");
|
|
const {
|
|
fetchRecentTransactions: popupHistory,
|
|
} = require("../src/shared/transactions");
|
|
expect(popupRefresh.mock.calls[0][3]).toBe(false);
|
|
expect(popupHistory.mock.calls[0][2]).toBe(false);
|
|
cleanupPopup();
|
|
|
|
await bootPopup(unversionedValidProfile({ useBlockscout: true }));
|
|
const {
|
|
refreshBalances: onRefresh,
|
|
} = require("../src/shared/balances");
|
|
const {
|
|
fetchRecentTransactions: onHistory,
|
|
} = require("../src/shared/transactions");
|
|
expect(onRefresh.mock.calls[0][3]).toBe(true);
|
|
expect(onHistory.mock.calls[0][2]).toBe(true);
|
|
});
|
|
|
|
test("Home says why there is no history and only tracked tokens while Use Blockscout is off", async () => {
|
|
const page = await bootPopup(unversionedValidProfile());
|
|
expect(page.node("home-tx-list").innerHTML).toContain(
|
|
escapeHtml(HISTORY_OFF_TEXT),
|
|
);
|
|
expect(page.hidden("home-tokens-note")).toBe(false);
|
|
cleanupPopup();
|
|
|
|
const on = await bootPopup(
|
|
unversionedValidProfile({ useBlockscout: true }),
|
|
);
|
|
expect(on.node("home-tx-list").innerHTML).toContain(
|
|
"No transactions found.",
|
|
);
|
|
expect(on.hidden("home-tokens-note")).toBe(true);
|
|
});
|
|
|
|
// The transaction screen, as a reopened popup draws it.
|
|
function onTransactionScreen(extra) {
|
|
return unversionedValidProfile({
|
|
currentView: "transaction",
|
|
viewStack: ["main"],
|
|
selectedWallet: 0,
|
|
selectedAddress: 0,
|
|
viewData: {
|
|
tx: {
|
|
hash: TX_HASH,
|
|
from: ADDRESS,
|
|
to: RECIPIENT,
|
|
value: "1.0000",
|
|
exactValue: "1.0",
|
|
symbol: "ETH",
|
|
timestamp: 1790000000,
|
|
isError: false,
|
|
direction: "sent",
|
|
directionLabel: "Sent",
|
|
contractAddress: null,
|
|
chainId: "0x1",
|
|
},
|
|
},
|
|
...extra,
|
|
});
|
|
}
|
|
|
|
test("TransactionDetail asks Blockscout for the transaction only while Use Blockscout is on", async () => {
|
|
const page = await bootPopup(onTransactionScreen());
|
|
expect(page.visibleViews()).toEqual(["transaction"]);
|
|
expect(fetched().filter(blockscoutOrEtherscan)).toEqual([]);
|
|
expect(page.hidden("tx-detail-blockscout-note")).toBe(false);
|
|
cleanupPopup();
|
|
|
|
const on = await bootPopup(
|
|
onTransactionScreen({ useBlockscout: true }),
|
|
);
|
|
expect(fetched().filter(blockscoutOrEtherscan)).toEqual([
|
|
BLOCKSCOUT + "/transactions/" + TX_HASH,
|
|
]);
|
|
expect(on.hidden("tx-detail-blockscout-note")).toBe(true);
|
|
});
|
|
|
|
// The confirmation screen for a send of 0.1 ETH to RECIPIENT, as a
|
|
// reopened popup draws it.
|
|
function onConfirmScreen(extra) {
|
|
return unversionedValidProfile({
|
|
currentView: "confirm-tx",
|
|
viewStack: ["main"],
|
|
selectedWallet: 0,
|
|
selectedAddress: 0,
|
|
viewData: {
|
|
pendingTx: {
|
|
token: "ETH",
|
|
from: ADDRESS,
|
|
to: RECIPIENT,
|
|
amount: "0.1",
|
|
chainId: "0x1",
|
|
},
|
|
},
|
|
...extra,
|
|
});
|
|
}
|
|
|
|
test("ConfirmTx asks Etherscan about the recipient only while Check recipients on Etherscan is on", async () => {
|
|
const page = await bootPopup(onConfirmScreen());
|
|
expect(page.visibleViews()).toEqual(["confirm-tx"]);
|
|
expect(fetched().filter(blockscoutOrEtherscan)).toEqual([]);
|
|
expect(page.hidden("confirm-etherscan-note")).toBe(false);
|
|
expect(page.hidden("confirm-etherscan-warning")).toBe(true);
|
|
cleanupPopup();
|
|
|
|
const on = await bootPopup(
|
|
onConfirmScreen({ checkRecipientsOnEtherscan: true }),
|
|
);
|
|
expect(fetched().filter(blockscoutOrEtherscan)).toEqual([
|
|
"https://etherscan.io/address/" + RECIPIENT,
|
|
]);
|
|
expect(on.hidden("confirm-etherscan-note")).toBe(true);
|
|
expect(on.hidden("confirm-etherscan-warning")).toBe(false);
|
|
});
|
|
|
|
// Ticks or unticks a checkbox on the page, as the user does.
|
|
async function setBox(page, id, checked) {
|
|
const box = page.node(id);
|
|
box.checked = checked;
|
|
for (const fn of box.listeners.change) await fn();
|
|
await page.settle();
|
|
}
|
|
|
|
test("Welcome offers both settings unticked, and ticking one there stores it", async () => {
|
|
const page = await bootPopup(undefined);
|
|
expect(page.visibleViews()).toEqual(["welcome"]);
|
|
expect(page.node("welcome-use-blockscout").checked).toBe(false);
|
|
expect(page.node("welcome-check-recipients-on-etherscan").checked).toBe(
|
|
false,
|
|
);
|
|
|
|
await setBox(page, "welcome-use-blockscout", true);
|
|
expect(page.storage.read("autistmask").useBlockscout).toBe(true);
|
|
await setBox(page, "welcome-check-recipients-on-etherscan", true);
|
|
expect(page.storage.read("autistmask").checkRecipientsOnEtherscan).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
test("Settings shows both settings unticked, and ticking one there stores it", async () => {
|
|
const page = await bootPopup(unversionedValidProfile());
|
|
await page.click("btn-settings");
|
|
expect(page.node("settings-use-blockscout").checked).toBe(false);
|
|
expect(
|
|
page.node("settings-check-recipients-on-etherscan").checked,
|
|
).toBe(false);
|
|
|
|
await setBox(page, "settings-use-blockscout", true);
|
|
expect(page.storage.read("autistmask").useBlockscout).toBe(true);
|
|
await setBox(page, "settings-check-recipients-on-etherscan", true);
|
|
expect(page.storage.read("autistmask").checkRecipientsOnEtherscan).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
// A token the user tracks, and one Blockscout reported that the user has
|
|
// not tracked. Both are written in lower case, as the balance rows' and
|
|
// the dropdown's token ids are compared below.
|
|
const TRACKED = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
|
|
const UNTRACKED = TOKEN_ADDRESS.toLowerCase();
|
|
const SECOND_ADDRESS = "0x3333333333333333333333333333333333333333";
|
|
|
|
function tokenRow(address, symbol) {
|
|
return {
|
|
address,
|
|
name: symbol,
|
|
symbol,
|
|
decimals: 18,
|
|
balance: "5.0",
|
|
holders: 50000,
|
|
};
|
|
}
|
|
|
|
// AddressDetail of the first of two addresses, as a reopened popup draws
|
|
// it. Both addresses hold the tracked token. While "Use Blockscout" is on,
|
|
// both hold the untracked one too.
|
|
function onAddressDetail(useBlockscout) {
|
|
const profile = unversionedValidProfile({
|
|
useBlockscout,
|
|
currentView: "address",
|
|
viewStack: ["main"],
|
|
selectedWallet: 0,
|
|
selectedAddress: 0,
|
|
trackedTokens: [
|
|
{ address: TRACKED, name: "TRK", symbol: "TRK", decimals: 18 },
|
|
],
|
|
});
|
|
const wallet = profile.wallets[0];
|
|
wallet.nextIndex = 2;
|
|
wallet.addresses.push({ address: SECOND_ADDRESS, balance: "0.0" });
|
|
for (const addr of wallet.addresses) {
|
|
addr.tokenBalances = useBlockscout
|
|
? [tokenRow(TRACKED, "TRK"), tokenRow(UNTRACKED, "TST")]
|
|
: [tokenRow(TRACKED, "TRK")];
|
|
}
|
|
return profile;
|
|
}
|
|
|
|
// The token ids of the options the page has created since it had created
|
|
// `from` elements. The page stub keeps no children, so the options Send's
|
|
// dropdown was filled with are read from there.
|
|
function optionsSince(page, from) {
|
|
return page.document.created
|
|
.slice(from)
|
|
.filter((el) => el.tagName === "OPTION")
|
|
.map((el) => el.value.toLowerCase());
|
|
}
|
|
|
|
test("unticking Use Blockscout from AddressDetail's gear takes the untracked token off AddressDetail, Send's dropdown and Home on Back", async () => {
|
|
const page = await bootPopup(onAddressDetail(true));
|
|
expect(page.visibleViews()).toEqual(["address"]);
|
|
expect(page.node("address-balances").innerHTML).toContain(UNTRACKED);
|
|
|
|
await page.click("btn-settings");
|
|
await setBox(page, "settings-use-blockscout", false);
|
|
const from = page.document.created.length;
|
|
await page.click("btn-settings-back");
|
|
|
|
expect(page.visibleViews()).toEqual(["address"]);
|
|
const balances = page.node("address-balances").innerHTML;
|
|
expect(balances).toContain(TRACKED);
|
|
expect(balances).not.toContain(UNTRACKED);
|
|
expect(page.hidden("address-tokens-note")).toBe(false);
|
|
expect(optionsSince(page, from)).toEqual([TRACKED]);
|
|
|
|
await page.click("btn-send");
|
|
expect(page.visibleViews()).toEqual(["send"]);
|
|
expect(page.hidden("send-token-dropdown")).toBe(false);
|
|
expect(page.hidden("send-tokens-note")).toBe(false);
|
|
|
|
await page.click("btn-send-back");
|
|
await page.click("btn-address-back");
|
|
expect(page.visibleViews()).toEqual(["main"]);
|
|
const home = page.node("wallet-list").innerHTML;
|
|
expect(home).toContain(TRACKED);
|
|
expect(home).not.toContain(UNTRACKED);
|
|
|
|
const stored = page.storage.read("autistmask");
|
|
for (const addr of stored.wallets[0].addresses) {
|
|
expect(
|
|
addr.tokenBalances.map((row) => row.address.toLowerCase()),
|
|
).toEqual([TRACKED]);
|
|
}
|
|
});
|
|
|
|
// Send's dropdown was filled under the old setting, and Back cannot draw
|
|
// Send again. So Back lands on the screen under it.
|
|
test.each([
|
|
["unticking", true],
|
|
["ticking", false],
|
|
])(
|
|
"%s Use Blockscout from Send's gear lands Back on AddressDetail",
|
|
async (_how, before) => {
|
|
const page = await bootPopup(onAddressDetail(before));
|
|
await page.click("btn-send");
|
|
expect(page.visibleViews()).toEqual(["send"]);
|
|
|
|
await page.click("btn-settings");
|
|
await setBox(page, "settings-use-blockscout", !before);
|
|
await page.click("btn-settings-back");
|
|
expect(page.visibleViews()).toEqual(["address"]);
|
|
expect(page.hidden("address-tokens-note")).toBe(!before);
|
|
},
|
|
);
|
|
|
|
test("Send says under its token dropdown that only tracked tokens are listed while Use Blockscout is off", async () => {
|
|
const off = await bootPopup(onAddressDetail(false));
|
|
await off.click("btn-send");
|
|
expect(off.hidden("send-token-dropdown")).toBe(false);
|
|
expect(off.hidden("send-tokens-note")).toBe(false);
|
|
cleanupPopup();
|
|
|
|
const on = await bootPopup(onAddressDetail(true));
|
|
await on.click("btn-send");
|
|
expect(on.hidden("send-token-dropdown")).toBe(false);
|
|
expect(on.hidden("send-tokens-note")).toBe(true);
|
|
});
|
|
|
|
test("Send opened from AddressToken shows neither the dropdown nor its line", async () => {
|
|
const page = await bootPopup({
|
|
...onAddressDetail(false),
|
|
currentView: "address-token",
|
|
viewStack: ["main", "address"],
|
|
selectedToken: "ETH",
|
|
});
|
|
expect(page.visibleViews()).toEqual(["address-token"]);
|
|
await page.click("btn-address-token-send");
|
|
expect(page.visibleViews()).toEqual(["send"]);
|
|
expect(page.hidden("send-token-dropdown")).toBe(true);
|
|
expect(page.hidden("send-token-static")).toBe(false);
|
|
});
|
|
|
|
test("DeleteAddress says only tracked tokens are listed while Use Blockscout is off", async () => {
|
|
for (const useBlockscout of [false, true]) {
|
|
const page = await bootPopup(onAddressDetail(useBlockscout));
|
|
require("../src/popup/views/deleteAddress").show(0, 1);
|
|
expect(page.visibleViews()).toEqual(["delete-address-confirm"]);
|
|
expect(page.hidden("delete-address-tokens-note")).toBe(
|
|
useBlockscout,
|
|
);
|
|
cleanupPopup();
|
|
}
|
|
});
|
|
|
|
// Turning the setting off dropped the untracked token. A refresh that
|
|
// started while it was on must not bring the token back when it returns.
|
|
// The same refresh in the background is in
|
|
// tests/backgroundStateIsolation.test.js.
|
|
test("a refresh that returns after Use Blockscout was turned off saves none of its tokens", async () => {
|
|
const page = await bootPopup(onAddressDetail(true));
|
|
const { refreshBalances: held } = require("../src/shared/balances");
|
|
let answer;
|
|
held.mockImplementationOnce(async (wallets) => {
|
|
await new Promise((resolve) => {
|
|
answer = resolve;
|
|
});
|
|
wallets[0].addresses[0].tokenBalances = [
|
|
tokenRow(TRACKED, "TRK"),
|
|
tokenRow(UNTRACKED, "TST"),
|
|
];
|
|
});
|
|
const ticking = page.tick();
|
|
await page.settle();
|
|
|
|
await page.click("btn-settings");
|
|
await setBox(page, "settings-use-blockscout", false);
|
|
const before = page.storage.read("autistmask").lastBalanceRefresh;
|
|
answer();
|
|
await ticking;
|
|
|
|
const stored = page.storage.read("autistmask");
|
|
expect(
|
|
stored.wallets[0].addresses[0].tokenBalances.map((row) =>
|
|
row.address.toLowerCase(),
|
|
),
|
|
).toEqual([TRACKED]);
|
|
expect(stored.lastBalanceRefresh).toBe(before);
|
|
await page.click("btn-settings-back");
|
|
expect(page.node("address-balances").innerHTML).not.toContain(
|
|
UNTRACKED,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("the background", () => {
|
|
afterEach(() => {
|
|
delete globalThis.chrome;
|
|
jest.dontMock("../src/shared/balances");
|
|
});
|
|
|
|
// Starts the background over `profile`, fires its balance alarm, and
|
|
// answers with the Use Blockscout setting its refresh was handed.
|
|
async function settingTheRefreshIsHanded(profile) {
|
|
jest.resetModules();
|
|
let handed;
|
|
const called = new Promise((resolve) => {
|
|
handed = resolve;
|
|
});
|
|
jest.doMock("../src/shared/balances", () => ({
|
|
getProvider: () => ({}),
|
|
refreshBalances: async (...args) => handed(args[3]),
|
|
}));
|
|
let onAlarm = null;
|
|
globalThis.chrome = {
|
|
storage: makeStorageStub({ autistmask: profile }),
|
|
alarms: {
|
|
create: () => {},
|
|
get: async () => undefined,
|
|
clear: async () => false,
|
|
onAlarm: {
|
|
addListener: (fn) => {
|
|
onAlarm = fn;
|
|
},
|
|
},
|
|
},
|
|
runtime: {
|
|
onMessage: { addListener: () => {} },
|
|
onConnect: { addListener: () => {} },
|
|
onInstalled: { addListener: () => {} },
|
|
onStartup: { addListener: () => {} },
|
|
getURL: (p) => "chrome-extension://test/" + p,
|
|
lastError: null,
|
|
},
|
|
windows: { onRemoved: { addListener: () => {} } },
|
|
tabs: { query: () => {}, sendMessage: () => {} },
|
|
action: { setPopup: () => {} },
|
|
};
|
|
require("../src/background/index");
|
|
const { BALANCE_REFRESH_ALARM } = require("../src/shared/alarms");
|
|
onAlarm({ name: BALANCE_REFRESH_ALARM });
|
|
return called;
|
|
}
|
|
|
|
test("hands its balance refresh the stored Use Blockscout setting", async () => {
|
|
const profile = { ...unversionedValidProfile(), lastBalanceRefresh: 0 };
|
|
expect(await settingTheRefreshIsHanded(profile)).toBe(false);
|
|
expect(
|
|
await settingTheRefreshIsHanded({
|
|
...profile,
|
|
useBlockscout: true,
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
});
|