fix: stabilize market metrics hydration and sorting

This commit is contained in:
2026-04-23 14:35:14 +08:00
parent a51c6f7bf2
commit 233de28713
7 changed files with 362 additions and 45 deletions
+106 -7
View File
@@ -511,6 +511,86 @@ describe("market-content-entry", () => {
]);
});
test("rehydrates real rows after serialized market rows arrive later", async () => {
document.body.innerHTML = buildRealMarketFixtureWithoutAuthorIds([
{
authorName: "达人 A",
price21To60s: "¥450,000"
},
{
authorName: "达人 B",
price21To60s: "¥20,000"
}
]);
const loadAuthorMetrics = vi.fn(async (authorId: string) => ({
success: true as const,
rates:
authorId === "111"
? {
singleVideoAfterSearchRate: "0.02%",
personalVideoAfterSearchRate: "0.03% - 0.2%"
}
: {
singleVideoAfterSearchRate: "0.5%-1%",
personalVideoAfterSearchRate: "0.01% - 0.1%"
}
}));
const { createMarketController } = await import("../src/content/market/index");
const controller = trackController(createMarketController({
document,
loadAuthorMetrics,
window
}));
await controller.ready;
expect(loadAuthorMetrics).not.toHaveBeenCalled();
expect(readDivPluginRowTexts(0)).toEqual(["", "", "", "", "", "", "", ""]);
expect(readDivPluginRowTexts(1)).toEqual(["", "", "", "", "", "", "", ""]);
document.documentElement.setAttribute(
"data-sces-market-rows",
JSON.stringify([
{
authorId: "111",
authorName: "达人 A",
singleVideoAfterSearchRate: "0.02%"
},
{
authorId: "222",
authorName: "达人 B"
}
])
);
await flushWithTimers();
await flushWithTimers();
expect(loadAuthorMetrics).toHaveBeenCalledTimes(2);
expect(readDivPluginRowTexts(0)).toEqual([
"0.02%",
"0.03% - 0.2%",
"",
"",
"",
"",
"",
""
]);
expect(readDivPluginRowTexts(1)).toEqual([
"0.5% - 1%",
"0.01% - 0.1%",
"",
"",
"",
"",
"",
""
]);
});
test("applying plugin filters hides non-matching current-page rows without a full scan", async () => {
document.body.innerHTML = buildMarketFixture();
const resultStore = createMarketResultStore();
@@ -3113,28 +3193,47 @@ function readRowOrder() {
}
function readDivAuthorOrder() {
return Array.from(
document.querySelectorAll('[data-testid^="author-cell-"] a'),
(link) => link.textContent?.trim() ?? ""
const authorColumn = document.querySelector(
'[data-testid="author-section"] .content-column'
);
return readVisualCells(authorColumn).map(
(cell) => cell.querySelector("a")?.textContent?.trim() ?? ""
);
}
function readDivRightRowTexts(rowIndex: number) {
return Array.from(
document.querySelectorAll('[data-testid="right-section"] > .content-column'),
(column) =>
column.querySelectorAll(".content-cell")[rowIndex]?.textContent?.trim() ?? ""
(column) => readVisualCells(column as Element)[rowIndex]?.textContent?.trim() ?? ""
);
}
function readDivPluginRowTexts(rowIndex: number) {
return Array.from(
document.querySelectorAll('[data-testid="plugin-section"] > .content-column'),
(column) =>
column.querySelectorAll(".content-cell")[rowIndex]?.textContent?.trim() ?? ""
(column) => readVisualCells(column as Element)[rowIndex]?.textContent?.trim() ?? ""
);
}
function readVisualCells(root: Element | null): HTMLElement[] {
if (!root) {
return [];
}
return Array.from(root.querySelectorAll(":scope > .content-cell"))
.filter((cell): cell is HTMLElement => cell instanceof HTMLElement)
.sort((left, right) => {
const leftOrder = Number(left.style.order || "0");
const rightOrder = Number(right.style.order || "0");
if (leftOrder !== rightOrder) {
return leftOrder - rightOrder;
}
const cells = Array.from(root.querySelectorAll(":scope > .content-cell"));
return cells.indexOf(left) - cells.indexOf(right);
});
}
function trackController<T extends { dispose?: () => void }>(controller: T): T {
if (controller.dispose) {
disposers.push(() => controller.dispose?.());