// balanceLine() is the row that issue #307 was reported against: every
// screen that lists a holding renders through it, and the symbol it renders
// is whatever an ERC-20's symbol() returned. This asserts against the
// string it emits, which is what gets assigned to innerHTML.
//
// The browser half of the same claim — that a real Chrome renders that
// string as text and puts no iframe in the popup DOM — is in
// tests/e2e/run.js. This half runs inside the 20-second make test cap.

"use strict";

// helpers.js reaches for both at module scope through the modules it pulls
// in. Neither is exercised by anything asserted here.
global.chrome = {
    storage: {
        local: {
            get: () => Promise.resolve({}),
            set: () => Promise.resolve(),
        },
    },
    runtime: { sendMessage: () => {} },
};
global.document = {
    getElementById: () => null,
    createElement: () => ({ style: {}, classList: { toggle() {} } }),
    body: { prepend: () => {} },
    addEventListener: () => {},
};

const { balanceLine } = require("../src/popup/views/helpers");
const { MAX_SYMBOL_LENGTH } = require("../src/shared/symbolDisplay");

// The payload from the issue's reproduction, verbatim.
const HOSTILE_SYMBOL =
    '<iframe id="pwn" src="https://dapp.e2e.test/" ' +
    'style="position:fixed;left:0;top:0;width:360px;height:600px;z-index:99999"></iframe>';

describe("balanceLine", () => {
    test("emits a hostile symbol as text, not as an element", () => {
        // Deliberately asserted on the escaping alone. The cap truncates
        // this payload before its id attribute, so an assertion about the
        // rest of the payload would pass on the cap and say nothing about
        // the escape.
        const html = balanceLine(HOSTILE_SYMBOL, 1, null, null);
        expect(html).not.toContain("<iframe");
        expect(html).toContain("&lt;iframe");
    });

    test("caps the symbol before rendering it", () => {
        const html = balanceLine("A".repeat(4096), 1, null, null);
        expect(html).toContain("A".repeat(MAX_SYMBOL_LENGTH - 1) + "…");
        expect(html).not.toContain("A".repeat(MAX_SYMBOL_LENGTH + 1));
    });

    // The token id lands inside data-token="...", so a quote in it is a
    // way out of the attribute and into a new one.
    test("keeps a quote-bearing token id inside its attribute", () => {
        const html = balanceLine("TKN", 1, null, '" onclick="alert(1)');
        expect(html).not.toContain('onclick="');
        expect(html).toContain('data-token="&quot; onclick=&quot;alert(1)"');
    });

    test("renders an ordinary holding unchanged", () => {
        const html = balanceLine("USDC", 1.5, null, "0xabc");
        expect(html).toContain("<span>USDC</span>");
        expect(html).toContain("<span>1.5000</span>");
        expect(html).toContain('data-token="0xabc"');
    });
});
