124 lines
4.3 KiB
JavaScript
124 lines
4.3 KiB
JavaScript
// Tests for the token filtering in the Send view's token selector
|
|
// (src/popup/views/send.js).
|
|
//
|
|
// The selector decides which of the user's tokens can be spent at all, so
|
|
// over-filtering here is worse than in the history list: the asset is not
|
|
// merely hidden, it becomes unspendable through the UI. Issue #230: an
|
|
// explorer that omits holders_count was read as "zero holders" and the token
|
|
// disappeared from this list.
|
|
//
|
|
// renderSendTokenSelect only ever touches getElementById, createElement,
|
|
// innerHTML, value, textContent and appendChild, so a small stub document is
|
|
// enough to drive it; the real DOM behaviour of the view is covered by
|
|
// tests/e2e/run.js.
|
|
|
|
globalThis.chrome = {
|
|
storage: { local: { get: async () => ({}), set: async () => {} } },
|
|
};
|
|
|
|
const { state } = require("../src/shared/state");
|
|
const { renderSendTokenSelect } = require("../src/popup/views/send");
|
|
|
|
const USDC_CONTRACT = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
|
|
const NOVEL_TOKEN = "0x1111111111111111111111111111111111111111";
|
|
|
|
let select;
|
|
|
|
function installStubDocument() {
|
|
select = { innerHTML: "", children: [] };
|
|
select.appendChild = (child) => select.children.push(child);
|
|
globalThis.document = {
|
|
getElementById: (id) => (id === "send-token" ? select : null),
|
|
createElement: () => ({ value: "", textContent: "" }),
|
|
};
|
|
}
|
|
|
|
// The symbols offered for sending, excluding the hardcoded ETH option that
|
|
// renderSendTokenSelect writes straight into innerHTML.
|
|
function offeredTokens() {
|
|
return select.children.map((opt) => opt.value.toLowerCase());
|
|
}
|
|
|
|
function tokenBalance(overrides) {
|
|
return {
|
|
address: NOVEL_TOKEN,
|
|
symbol: "SPAMTKN",
|
|
decimals: 18,
|
|
balance: "12.5",
|
|
holders: 50000,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function render(tokenBalances) {
|
|
installStubDocument();
|
|
renderSendTokenSelect({ address: "0x" + "a".repeat(40), tokenBalances });
|
|
}
|
|
|
|
beforeEach(() => {
|
|
state.fraudContracts = [];
|
|
state.hideLowHolderTokens = true;
|
|
});
|
|
|
|
describe("the low-holder rule in the send token selector", () => {
|
|
test("ETH is always offered", () => {
|
|
render([]);
|
|
expect(select.innerHTML).toBe('<option value="ETH">ETH</option>');
|
|
expect(offeredTokens()).toEqual([]);
|
|
});
|
|
|
|
test("a token with plenty of holders is offered", () => {
|
|
render([tokenBalance()]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
test("a token reporting zero holders is withheld", () => {
|
|
render([tokenBalance({ holders: 0 })]);
|
|
expect(offeredTokens()).toEqual([]);
|
|
});
|
|
|
|
test("boundary: 999 holders is withheld, 1000 is offered", () => {
|
|
render([tokenBalance({ holders: 999 })]);
|
|
expect(offeredTokens()).toEqual([]);
|
|
render([tokenBalance({ holders: 1000 })]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
// Issue #230: an unknown holder count must not read as zero. A token the
|
|
// user demonstrably holds — it has a balance — cannot be made unspendable
|
|
// by a field the block explorer failed to report.
|
|
test("a token whose holder count is unknown is still offered", () => {
|
|
render([tokenBalance({ holders: null })]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
test("a token balance carrying no holders field at all is offered", () => {
|
|
const t = tokenBalance();
|
|
delete t.holders;
|
|
render([t]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
|
|
test("the rule is bypassed entirely when the setting is off", () => {
|
|
state.hideLowHolderTokens = false;
|
|
render([tokenBalance({ holders: 0 })]);
|
|
expect(offeredTokens()).toEqual([NOVEL_TOKEN]);
|
|
});
|
|
});
|
|
|
|
describe("the other send-selector rules are unaffected", () => {
|
|
test("a token spoofing a known symbol from a wrong address is withheld", () => {
|
|
render([
|
|
tokenBalance({ symbol: "USDC", holders: null }),
|
|
tokenBalance({ address: USDC_CONTRACT, symbol: "USDC" }),
|
|
]);
|
|
expect(offeredTokens()).toEqual([USDC_CONTRACT.toLowerCase()]);
|
|
});
|
|
|
|
test("a blocklisted fraud contract is withheld even with an unknown count", () => {
|
|
state.fraudContracts = [NOVEL_TOKEN.toUpperCase()];
|
|
render([tokenBalance({ holders: null })]);
|
|
expect(offeredTokens()).toEqual([]);
|
|
});
|
|
});
|