// @vitest-environment jsdom import { beforeEach, describe, expect, test } from "vitest"; import { applyRowOrder, applyRowVisibility, renderMarketRowState, syncMarketTable } from "../src/content/market/dom-sync"; import type { MarketRecord } from "../src/content/market/types"; describe("market-dom-sync", () => { beforeEach(() => { document.body.innerHTML = `
达人
21-60s报价
Alpha 450000
Beta 70000
`; }); test("injects the two header cells and per-row cells", () => { const table = syncMarketTable(document); expect(table).not.toBeNull(); expect( document.querySelector('[data-market-header-cell="singleVideoAfterSearchRate"]') ).not.toBeNull(); expect( document.querySelector( '[data-market-header-cell="personalVideoAfterSearchRate"]' ) ).not.toBeNull(); expect(document.querySelectorAll("[data-market-row-cell]").length).toBe(4); }); test("renders loading, success, and failed states", () => { const table = syncMarketTable(document); if (!table) { throw new Error("Expected market table"); } const alphaRow = table.rows[0]; const betaRow = table.rows[1]; renderMarketRowState(alphaRow, { authorId: "a", authorName: "Alpha", status: "loading" }); renderMarketRowState(betaRow, { authorId: "b", authorName: "Beta", status: "success", rates: { singleVideoAfterSearchRate: "0.5%-1%", personalVideoAfterSearchRate: "0.02 - 0.1%" } }); expect(alphaRow.singleCell.textContent).toBe("加载中..."); expect(betaRow.singleCell.textContent).toBe("0.5% - 1%"); expect(betaRow.personalCell.textContent).toBe("0.02% - 0.1%"); renderMarketRowState(betaRow, { authorId: "b", authorName: "Beta", status: "failed" }); expect(betaRow.singleCell.textContent).toBe("加载失败"); expect(betaRow.personalCell.textContent).toBe("加载失败"); }); test("hides rows outside the visible author ids", () => { const table = syncMarketTable(document); if (!table) { throw new Error("Expected market table"); } applyRowVisibility(table, new Set(["b"])); expect(table.rows[0].row.hidden).toBe(true); expect(table.rows[1].row.hidden).toBe(false); }); test("reorders rows based on ordered author ids", () => { const table = syncMarketTable(document); if (!table) { throw new Error("Expected market table"); } applyRowOrder(table, ["b", "a"]); expect( Array.from( document.querySelectorAll("[data-market-row]") ).map((row) => row.getAttribute("data-author-id")) ).toEqual(["b", "a"]); }); test("supports the real div-grid market layout and keeps rows aligned", () => { document.body.innerHTML = buildRealMarketGridFixture(); const table = syncMarketTable(document); if (!table) { throw new Error("Expected market table"); } expect(readRightHeaderTexts()).toEqual([ "21-60s报价", "单视频看后搜率", "个人视频看后搜率", "操作" ]); expect(table.rows.map((row) => row.authorId)).toEqual(["111", "222"]); renderMarketRowState(table.rows[0], { authorId: "111", authorName: "达人 A", status: "success", rates: { singleVideoAfterSearchRate: "0.5%-1%", personalVideoAfterSearchRate: "0.02 - 0.1%" } }); expect(readRightRowTexts(0)).toEqual([ "¥450,000", "0.5% - 1%", "0.02% - 0.1%", "下单" ]); applyRowVisibility(table, new Set(["222"])); expect(readAuthorRowHiddenStates()).toEqual([true, false]); expect(readRightActionHiddenStates()).toEqual([true, false]); applyRowVisibility(table, new Set(["111", "222"])); applyRowOrder(table, ["222", "111"]); expect(readAuthorNames()).toEqual(["达人 B", "达人 A"]); expect(readRightRowTexts(0)).toEqual(["¥20,000", "", "", "下单"]); }); test("falls back to the market vue state when the DOM has no author id", () => { document.body.innerHTML = buildRealMarketGridFixtureWithoutAuthorIds(); attachMarketVueState([ { attribute_datas: { nickname: "达人 A" }, star_id: "111" }, { attribute_datas: { nickname: "达人 B" }, star_id: "222" } ]); const table = syncMarketTable(document); if (!table) { throw new Error("Expected market table"); } expect(table.rows.map((row) => row.authorId)).toEqual(["111", "222"]); }); test("falls back to serialized market rows when vue state is unavailable", () => { document.body.innerHTML = buildRealMarketGridFixtureWithoutAuthorIds(); document.documentElement.setAttribute( "data-sces-market-rows", JSON.stringify([ { authorId: "111", authorName: "达人 A", singleVideoAfterSearchRate: "0.02%" }, { authorId: "222", authorName: "达人 B" } ]) ); const table = syncMarketTable(document); if (!table) { throw new Error("Expected market table"); } expect(table.rows.map((row) => row.authorId)).toEqual(["111", "222"]); expect(table.rows[0].rates).toEqual({ singleVideoAfterSearchRate: "0.02%" }); }); }); function buildRealMarketGridFixture() { return `
代表视频A
代表视频B
¥450,000
¥20,000
下单
下单
`; } function buildRealMarketGridFixtureWithoutAuthorIds() { return `
达人 A
达人 B
¥450,000
¥20,000
下单
下单
`; } function attachMarketVueState( marketList: Array<{ attribute_datas?: { nickname?: string }; star_id?: string }> ) { const marketRoot = document.querySelector(".base-author-list"); if (!(marketRoot instanceof HTMLElement)) { throw new Error("Expected market root"); } Object.defineProperty(marketRoot, "__vue__", { configurable: true, value: { _setupState: { __$temp_1: { marketList } } } }); } function readRightHeaderTexts() { return Array.from( document.querySelectorAll('[data-testid="right-header"] > *'), (cell) => cell.textContent?.trim() ?? "" ); } function readRightRowTexts(rowIndex: number) { return Array.from( document.querySelectorAll('[data-testid="right-section"] > .content-column'), (column) => column.querySelectorAll(".content-cell")[rowIndex]?.textContent?.trim() ?? "" ); } function readAuthorNames() { return Array.from( document.querySelectorAll('[data-testid="author-section"] .content-cell a'), (link) => link.textContent?.trim() ?? "" ); } function readAuthorRowHiddenStates() { return Array.from( document.querySelectorAll('[data-testid^="author-cell-"]'), (cell) => (cell as HTMLElement).hidden ); } function readRightActionHiddenStates() { return Array.from( document.querySelectorAll('[data-testid^="action-cell-"]'), (cell) => (cell as HTMLElement).hidden ); }