import { JSDOM } from "jsdom"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { createDetailContentController } from "../src/content/detail/index"; import { createRouteState } from "../src/content/route-state"; import { CANDIDATE_ANALYSIS_MESSAGE_TYPE, CANDIDATE_REQUEST_MESSAGE_TYPE, EXTENSION_MESSAGE_SOURCE, HOOK_READY_MESSAGE_TYPE, RESULT_MESSAGE_TYPE } from "../src/shared/message-types"; import type { AfterSearchRateResult } from "../src/shared/result-types"; describe("content bridge", () => { let dom: JSDOM; beforeEach(() => { dom = new JSDOM("", { url: "https://xingtu.cn/ad/creator/author-homepage/douyin-video/6629661559960371207" }); }); afterEach(() => { dom.window.close(); }); test("initializes route state from the current url", () => { const state = createRouteState(dom.window.location.href); expect(state.getSnapshot()).toMatchObject({ navigationSeq: 1, pageStarId: "6629661559960371207", pathname: "/ad/creator/author-homepage/douyin-video/6629661559960371207", routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::1" }); }); test("increments the navigation sequence when the route changes", () => { const state = createRouteState(dom.window.location.href); const nextSnapshot = state.advance( "https://xingtu.cn/ad/creator/author-homepage/douyin-video/7777777777777777777" ); expect(nextSnapshot).toMatchObject({ navigationSeq: 2, pageStarId: "7777777777777777777", routeKey: "7777777777777777777::/ad/creator/author-homepage/douyin-video/7777777777777777777::2" }); }); test("ignores stale route messages", () => { const logger = createLogger(); const controller = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); const stalePayload: AfterSearchRateResult = { capturedAt: Date.now(), pageStarId: "old-id", pageUrl: dom.window.location.href, rawPathHints: [], routeKey: "old-id::/old-path::1", stage: "captured", success: true, rates: { personalVideoAfterSearchRate: "0.5% - 1%", singleVideoAfterSearchRate: "1% - 2%" } }; dom.window.dispatchEvent( new dom.window.MessageEvent("message", { data: { payload: stalePayload, source: EXTENSION_MESSAGE_SOURCE, type: RESULT_MESSAGE_TYPE }, source: dom.window }) ); expect(resultLogs(logger)).toHaveLength(0); controller.dispose(); }); test("injects the built page-hook asset path", () => { const logger = createLogger(); const firstController = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); const secondController = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); const injectedScripts = dom.window.document.querySelectorAll( "#star-chart-search-enhancer-page-hook" ); expect(injectedScripts).toHaveLength(1); expect((injectedScripts[0] as HTMLScriptElement).src).toBe( "chrome-extension://test/page/hook.global.js" ); firstController.dispose(); secondController.dispose(); }); test("logs a hook-ready diagnostic when the page hook announces itself", () => { const logger = createLogger(); const controller = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); dom.window.dispatchEvent( new dom.window.MessageEvent("message", { data: { payload: { pageStarId: "6629661559960371207", routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::1" }, source: EXTENSION_MESSAGE_SOURCE, type: HOOK_READY_MESSAGE_TYPE }, source: dom.window }) ); expect(logger.info).toHaveBeenCalledWith( "[star-chart-search-enhancer]", "hook-ready", expect.objectContaining({ pageStarId: "6629661559960371207" }) ); controller.dispose(); }); test("logs candidate-request diagnostics for the current route", () => { const logger = createLogger(); const controller = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); dom.window.dispatchEvent( new dom.window.MessageEvent("message", { data: { payload: { requestMethod: "GET", requestUrl: "https://api.xingtu.cn/creator/value", routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::1", status: 200 }, source: EXTENSION_MESSAGE_SOURCE, type: CANDIDATE_REQUEST_MESSAGE_TYPE }, source: dom.window }) ); expect(logger.info).toHaveBeenCalledWith( "[star-chart-search-enhancer]", "candidate-request", expect.objectContaining({ requestUrl: "https://api.xingtu.cn/creator/value" }) ); controller.dispose(); }); test("logs candidate-analysis diagnostics for the current route", () => { const logger = createLogger(); const controller = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); dom.window.dispatchEvent( new dom.window.MessageEvent("message", { data: { payload: { extractorLevel: "none", matched: false, reason: "No after-search-rate signals found", requestUrl: "https://api.xingtu.cn/creator/value", routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::2", signalEntries: [ "$.data.avg_search_after_view_rate=0.0015", "$.data.avg_search_after_view_rate_rank_percent=0.9" ], success: false, topLevelKeys: ["data", "status_code"] }, source: EXTENSION_MESSAGE_SOURCE, type: CANDIDATE_ANALYSIS_MESSAGE_TYPE }, source: dom.window }) ); expect(logger.info).toHaveBeenCalledWith( "[star-chart-search-enhancer]", "candidate-analysis", expect.objectContaining({ requestUrl: "https://api.xingtu.cn/creator/value", signalEntries: [ "$.data.avg_search_after_view_rate=0.0015", "$.data.avg_search_after_view_rate_rank_percent=0.9" ], topLevelKeys: ["data", "status_code"] }) ); controller.dispose(); }); test("accepts result messages when only the navigation sequence differs", () => { const logger = createLogger(); const controller = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); dom.window.dispatchEvent( new dom.window.MessageEvent("message", { data: { payload: { capturedAt: Date.now(), pageStarId: "6629661559960371207", pageUrl: dom.window.location.href, rawPathHints: [], reason: "Timed out waiting for after-search-rate capture", routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::3", stage: "timeout", success: false }, source: EXTENSION_MESSAGE_SOURCE, type: RESULT_MESSAGE_TYPE }, source: dom.window }) ); expect(logger.info).toHaveBeenCalledWith( "[star-chart-search-enhancer]", "result", expect.objectContaining({ routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::3", stage: "timeout" }) ); controller.dispose(); }); test("does not log duplicate final results twice", () => { const logger = createLogger(); const controller = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); const payload = currentRoutePayload(dom.window.location.href); dom.window.dispatchEvent(messageEvent(dom.window, payload)); dom.window.dispatchEvent(messageEvent(dom.window, payload)); expect(resultLogs(logger)).toHaveLength(1); controller.dispose(); }); test("allows a later success result to replace an earlier failure", () => { const logger = createLogger(); const controller = createDetailContentController({ chromeRuntime: { getURL: (value: string) => `chrome-extension://test/${value}` }, document: dom.window.document, logger, window: dom.window }); const timeoutPayload: AfterSearchRateResult = { capturedAt: Date.now(), pageStarId: "6629661559960371207", pageUrl: dom.window.location.href, rawPathHints: [], reason: "Timed out waiting for relevant responses", routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::1", stage: "timeout", success: false }; const successPayload = currentRoutePayload(dom.window.location.href); dom.window.dispatchEvent(messageEvent(dom.window, timeoutPayload)); dom.window.dispatchEvent(messageEvent(dom.window, successPayload)); const logs = resultLogs(logger); expect(logs).toHaveLength(2); expect(logs.at(-1)?.[2]).toMatchObject({ stage: "captured", success: true }); controller.dispose(); }); }); function createLogger() { return { debug: vi.fn(), info: vi.fn(), warn: vi.fn() }; } function currentRoutePayload(pageUrl: string): AfterSearchRateResult { return { capturedAt: Date.now(), pageStarId: "6629661559960371207", pageUrl, rawPathHints: ["$.cards[0].metrics[0]"], routeKey: "6629661559960371207::/ad/creator/author-homepage/douyin-video/6629661559960371207::1", stage: "captured", success: true, rates: { personalVideoAfterSearchRate: "0.5% - 1%", singleVideoAfterSearchRate: "1% - 2%" } }; } function messageEvent(window: Window, payload: AfterSearchRateResult): MessageEvent { return new window.MessageEvent("message", { data: { payload, source: EXTENSION_MESSAGE_SOURCE, type: RESULT_MESSAGE_TYPE }, source: window }); } function resultLogs(logger: ReturnType) { return logger.info.mock.calls.filter((call) => call[1] === "result"); }