A network switch now puts the outgoing network's tracked tokens away in a new persisted field, networkTrackedTokens, and brings back the incoming network's list, or an empty one, the way endpoints are kept per network. trackedTokens stays the active network's list, so its readers are unchanged, and the background's switch goes through the same function. The two add-token screens offer their common-token picks, which are mainnet contracts, on Ethereum Mainnet only. After a switch in Settings, Back finds the screens beneath as a reopened popup does: it renders the one it lands on for the new network, and the stack is cut at the first screen that cannot be rendered from saved state. Model: opus-5-5
190 lines
6.6 KiB
JavaScript
190 lines
6.6 KiB
JavaScript
// Tracked tokens are kept per network, and the two add-token screens offer the
|
|
// bundled token list, which holds Ethereum Mainnet contracts, on Ethereum
|
|
// Mainnet only (https://git.eeqj.de/sneak/AutistMask/issues/523), including on
|
|
// the screen "Back" lands on after a switch made from the Settings gear. Driven
|
|
// through the real popup over a stored profile; the same switch made by the
|
|
// background is in tests/coldWorkerChainSwitch.test.js.
|
|
|
|
const {
|
|
bootPopup,
|
|
cleanupPopup,
|
|
unversionedValidProfile,
|
|
TOKEN_ADDRESS,
|
|
} = require("./support/popupBoot");
|
|
|
|
const SEPOLIA_TOKEN = {
|
|
address: TOKEN_ADDRESS,
|
|
symbol: "TST",
|
|
decimals: 18,
|
|
name: "Test Token",
|
|
};
|
|
const MAINNET_TOKEN = {
|
|
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
symbol: "DAI",
|
|
decimals: 18,
|
|
name: "Dai Stablecoin",
|
|
};
|
|
|
|
// Fake timers hold the timer of the flash message a switch ends with, which
|
|
// would otherwise fire after cleanupPopup() has removed the page and throw.
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanupPopup();
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
// The popup reopened onto SettingsAddToken, on `networkId`.
|
|
function bootOnAddToken(networkId, extra) {
|
|
return bootPopup(
|
|
unversionedValidProfile({
|
|
networkId,
|
|
currentView: "settings-addtoken",
|
|
...extra,
|
|
}),
|
|
);
|
|
}
|
|
|
|
// The popup reopened onto AddressDetail on Ethereum Mainnet, with DAI tracked
|
|
// there and TST tracked on Sepolia.
|
|
function bootOnAddress() {
|
|
return bootPopup(
|
|
unversionedValidProfile({
|
|
currentView: "address",
|
|
selectedWallet: 0,
|
|
selectedAddress: 0,
|
|
trackedTokens: [MAINNET_TOKEN],
|
|
networkTrackedTokens: { sepolia: [SEPOLIA_TOKEN] },
|
|
}),
|
|
);
|
|
}
|
|
|
|
// Picks `networkId` in the Settings network selector, as the user does.
|
|
async function chooseNetwork(boot, networkId) {
|
|
const select = boot.node("settings-network");
|
|
select.value = networkId;
|
|
for (const fn of select.listeners.change) await fn();
|
|
await boot.settle();
|
|
}
|
|
|
|
// The Settings gear, a switch to Sepolia, and Back.
|
|
async function gearSwitchBack(boot) {
|
|
await boot.click("btn-settings");
|
|
expect(boot.visibleViews()).toEqual(["settings"]);
|
|
await chooseNetwork(boot, "sepolia");
|
|
await boot.click("btn-settings-back");
|
|
}
|
|
|
|
describe.each([
|
|
["mainnet", false],
|
|
["sepolia", true],
|
|
])("on %s", (networkId, hidden) => {
|
|
test(`the common tokens are hidden: ${hidden}`, async () => {
|
|
const boot = await bootOnAddToken(networkId);
|
|
expect(boot.pageErrors).toEqual([]);
|
|
expect(boot.visibleViews()).toEqual(["settings-addtoken"]);
|
|
expect(boot.hidden("settings-addtoken-common")).toBe(hidden);
|
|
|
|
require("../src/popup/views/addToken").show();
|
|
expect(boot.visibleViews()).toEqual(["add-token"]);
|
|
expect(boot.hidden("add-token-common")).toBe(hidden);
|
|
});
|
|
});
|
|
|
|
test("a token added by contract address on Sepolia is tracked on Sepolia only", async () => {
|
|
const boot = await bootOnAddToken("sepolia", {
|
|
networkTrackedTokens: { mainnet: [MAINNET_TOKEN] },
|
|
});
|
|
const stored = () => boot.storage.read("autistmask");
|
|
const { lookupTokenInfo } = require("../src/shared/balances");
|
|
lookupTokenInfo.mockResolvedValueOnce({
|
|
symbol: SEPOLIA_TOKEN.symbol,
|
|
decimals: SEPOLIA_TOKEN.decimals,
|
|
name: SEPOLIA_TOKEN.name,
|
|
});
|
|
|
|
boot.node("settings-addtoken-address").value = TOKEN_ADDRESS;
|
|
await boot.click("btn-settings-addtoken-manual");
|
|
|
|
expect(lookupTokenInfo).toHaveBeenCalledWith(
|
|
TOKEN_ADDRESS,
|
|
expect.any(String),
|
|
"sepolia",
|
|
expect.anything(),
|
|
);
|
|
expect(stored().trackedTokens).toEqual([SEPOLIA_TOKEN]);
|
|
|
|
await chooseNetwork(boot, "mainnet");
|
|
expect(stored().networkId).toBe("mainnet");
|
|
expect(stored().trackedTokens).toEqual([MAINNET_TOKEN]);
|
|
const onMainnet = boot.node("settings-tracked-tokens").innerHTML;
|
|
expect(onMainnet).toContain("DAI");
|
|
expect(onMainnet).not.toContain("TST");
|
|
|
|
await chooseNetwork(boot, "sepolia");
|
|
expect(stored().networkId).toBe("sepolia");
|
|
expect(stored().trackedTokens).toEqual([SEPOLIA_TOKEN]);
|
|
const onSepolia = boot.node("settings-tracked-tokens").innerHTML;
|
|
expect(onSepolia).toContain("TST");
|
|
expect(onSepolia).not.toContain("DAI");
|
|
});
|
|
|
|
describe("Back after a switch from the Settings gear", () => {
|
|
test("SettingsAddToken no longer offers the common tokens", async () => {
|
|
const boot = await bootOnAddToken("mainnet");
|
|
expect(boot.hidden("settings-addtoken-common")).toBe(false);
|
|
|
|
await gearSwitchBack(boot);
|
|
expect(boot.visibleViews()).toEqual(["settings-addtoken"]);
|
|
expect(boot.hidden("settings-addtoken-common")).toBe(true);
|
|
});
|
|
|
|
test("AddressDetail lists the new network's tracked tokens", async () => {
|
|
const boot = await bootOnAddress();
|
|
expect(boot.node("address-balances").innerHTML).toContain("DAI");
|
|
|
|
await gearSwitchBack(boot);
|
|
expect(boot.visibleViews()).toEqual(["address"]);
|
|
const balances = boot.node("address-balances").innerHTML;
|
|
expect(balances).toContain("TST");
|
|
expect(balances).not.toContain("DAI");
|
|
});
|
|
|
|
// Neither can be rendered from saved state, so neither is landed on.
|
|
test.each([
|
|
["btn-add-token", "add-token"],
|
|
["btn-send", "send"],
|
|
])("from over %s, it lands on AddressDetail", async (button, view) => {
|
|
const boot = await bootOnAddress();
|
|
await boot.click(button);
|
|
expect(boot.visibleViews()).toEqual([view]);
|
|
|
|
await gearSwitchBack(boot);
|
|
expect(boot.visibleViews()).toEqual(["address"]);
|
|
expect(boot.node("address-balances").innerHTML).not.toContain("DAI");
|
|
});
|
|
|
|
test("without a wallet, it lands on Welcome", async () => {
|
|
const boot = await bootPopup(undefined);
|
|
expect(boot.visibleViews()).toEqual(["welcome"]);
|
|
|
|
await gearSwitchBack(boot);
|
|
expect(boot.visibleViews()).toEqual(["welcome"]);
|
|
});
|
|
|
|
test("a later Back onto Settings keeps what was typed there", async () => {
|
|
const boot = await bootOnAddress();
|
|
await boot.click("btn-settings");
|
|
await chooseNetwork(boot, "sepolia");
|
|
boot.node("settings-rpc").value = "https://typed.example";
|
|
|
|
await boot.click("btn-settings-add-token");
|
|
expect(boot.visibleViews()).toEqual(["settings-addtoken"]);
|
|
await boot.click("btn-settings-addtoken-back");
|
|
expect(boot.visibleViews()).toEqual(["settings"]);
|
|
expect(boot.value("settings-rpc")).toBe("https://typed.example");
|
|
});
|
|
});
|