import { describe, expect, it, vi } from "vitest"; vi.mock("electron/main", () => ({ ipcMain: { on: vi.fn(), }, })); import { __deepseekTestUtils } from "./deepseek"; import { getMonitorAdapter } from "./index"; const { extractQuestionText, buildSourceItem, buildDeepSeekPayload, detectDeepSeekBusySignals, dedupeSourceItems, classifyDeepseekSources, mergeDeepSeekRawSourceLinks, isObservationComplete, isDeepSeekToggleSelected, isDeepSeekWebpagesTriggerText, } = __deepseekTestUtils; describe("deepseek adapter helpers", () => { it("extracts question text from monitoring payload", () => { expect(extractQuestionText({ question_text: "DeepSeek 怎么看 GEO?" })).toBe("DeepSeek 怎么看 GEO?"); }); it("dedupes sources by normalized url without hash fragments", () => { const items = dedupeSourceItems([ { url: "https://example.com/a#top", title: "A", normalized_url: "https://example.com/a#top", }, { url: "https://example.com/a#bottom", title: "A later", normalized_url: "https://example.com/a#bottom", }, ]); expect(items).toHaveLength(1); expect(items[0]?.normalized_url).toBe("https://example.com/a"); expect(items[0]?.title).toBe("A"); }); it("treats revealed search cards as citation sources and inline links as content citation candidates", () => { const classified = classifyDeepseekSources({ inlineLinks: [ { url: "https://example.com/citation", title: "Citation", normalized_url: "https://example.com/citation", }, ], sourcePanelLinks: [], searchPanelLinks: [ { url: "https://example.com/search", title: "Search", normalized_url: "https://example.com/search", }, ], }); expect(classified.citations).toHaveLength(1); expect(classified.inlineCitationCandidates).toHaveLength(1); expect(classified.searchResults).toHaveLength(1); expect(classified.citations[0]?.url).toBe("https://example.com/search"); expect(classified.inlineCitationCandidates[0]?.url).toBe("https://example.com/citation"); expect(classified.searchResults[0]?.url).toBe("https://example.com/search"); }); it("enriches numeric inline citations with source metadata from revealed search cards", () => { const classified = classifyDeepseekSources({ inlineLinks: [ { url: "https://example.com/citation", title: null, text: "-1", siteName: null, kind: "inline", }, ], sourcePanelLinks: [], searchPanelLinks: [ { url: "https://example.com/citation", title: "DeepSeek source article", text: "Snippet", siteName: "Example", kind: "search", }, ], }); expect(classified.inlineCitationCandidates).toHaveLength(1); expect(classified.inlineCitationCandidates[0]?.title).toBe("DeepSeek source article"); expect(classified.inlineCitationCandidates[0]?.site_name).toBe("Example"); expect(classified.citations).toHaveLength(1); expect(classified.searchResults).toHaveLength(1); }); it("keeps reasoning search pages as citation sources separate from answer content citations", () => { const classified = classifyDeepseekSources({ inlineLinks: [ { url: "https://example.com/body-citation", title: "Answer body citation", text: "[15]", siteName: "Body Site", kind: "inline", }, ], sourcePanelLinks: [], searchPanelLinks: [ { url: "https://example.com/reasoning-search", title: "Reasoning searched page", text: "浏览页面", siteName: "Search Site", kind: "search", }, ], }); expect(classified.inlineCitationCandidates.map((item) => item.url)).toEqual([ "https://example.com/body-citation", ]); expect(classified.citations.map((item) => item.url)).toEqual([ "https://example.com/reasoning-search", ]); expect(classified.searchResults.map((item) => item.url)).toEqual([ "https://example.com/reasoning-search", ]); }); it("retains all revealed DeepSeek search results instead of truncating after 32 items", () => { const searchPanelLinks = Array.from({ length: 78 }, (_, index) => ({ url: `https://example.com/search-${index + 1}`, title: `Search ${index + 1}`, text: `Snippet ${index + 1}`, siteName: "Example", kind: "search" as const, })); const classified = classifyDeepseekSources({ inlineLinks: [], sourcePanelLinks: [], searchPanelLinks, }); expect(classified.citations).toHaveLength(78); expect(classified.searchResults).toHaveLength(78); }); it("merges revealed sidebar search links with the snapshot search links by normalized url", () => { const merged = mergeDeepSeekRawSourceLinks( [ { url: "https://example.com/report#top", title: null, text: "Visible title", siteName: null, kind: "search", }, ], [ { url: "https://example.com/report#bottom", title: "Merged title", text: "Merged snippet", siteName: "Example", kind: "search", }, { url: "https://example.com/extra", title: "Extra result", text: "Extra snippet", siteName: "Example", kind: "search", }, ], ); expect(merged).toHaveLength(2); expect(merged[0]).toEqual(expect.objectContaining({ url: "https://example.com/report", title: "Merged title", text: "Visible title", siteName: "Example", })); expect(merged[1]?.url).toBe("https://example.com/extra"); }); it("normalizes deepseek raw source links into monitoring source items", () => { const item = buildSourceItem({ url: "https://example.com/report#cite-1", title: null, text: "DeepSeek source title", siteName: "Example", kind: "source", }); expect(item).toEqual(expect.objectContaining({ url: "https://example.com/report", normalized_url: "https://example.com/report", title: "DeepSeek source title", site_name: "Example", host: "example.com", })); }); it("unwraps DeepSeek redirect URLs before storing source items", () => { const item = buildSourceItem({ url: "https://chat.deepseek.com/api/redirect?url=https%3A%2F%2Fexample.com%2Fsource%23ref", title: "Redirected source", text: null, siteName: null, kind: "search", }); expect(item).toEqual(expect.objectContaining({ url: "https://example.com/source", normalized_url: "https://example.com/source", host: "example.com", })); }); it("does not use numeric citation markers as source titles", () => { const item = buildSourceItem({ url: "https://example.com/report#cite-1", title: null, text: "-1", siteName: null, kind: "inline", }); expect(item?.title).toBeNull(); expect(item?.site_name).toBe("example.com"); }); it("treats a stable answer as complete even without citations", () => { expect(isObservationComplete({ answer: "这是最终答案", busy: false, loginRequired: false, challengeRequired: false, signature: "sig-1", })).toBe(true); }); it("ignores source-logo loading placeholders when computing busy signals", () => { expect(detectDeepSeekBusySignals({ bodyText: "已阅读 10 个网页", descriptors: [ "site_logo_loading site_logo_fallback", "site_logo_loading site_logo_fallback", ], })).toEqual([]); }); it("recognizes deepseek toggle selected state from class names", () => { expect(isDeepSeekToggleSelected("ds-toggle-button ds-toggle-button--selected")).toBe(true); expect(isDeepSeekToggleSelected("ds-toggle-button")).toBe(false); }); it("only treats the bottom 网页 pill as the DeepSeek source trigger", () => { expect(isDeepSeekWebpagesTriggerText("57 个网页")).toBe(true); expect(isDeepSeekWebpagesTriggerText("已阅读 57 个网页")).toBe(true); expect(isDeepSeekWebpagesTriggerText("搜索到 39 个网页")).toBe(false); }); it("normalizes DeepSeek model labels without mistaking search toggles for models", () => { const snapshot = { url: "https://chat.deepseek.com/", title: "DeepSeek", loginRequired: false, loginReason: null, challengeRequired: false, challengeReason: null, busy: false, busySignals: [], composerValue: null, sendDisabled: false, answer: "Final answer", answerText: "Final answer", reasoning: null, signature: "sig-1", currentModelLabel: "深度思考 (R1)", providerRequestID: null, requestID: null, inlineLinks: [], sourcePanelLinks: [], searchPanelLinks: [], }; const sources = { citations: [], inlineCitationCandidates: [], searchResults: [], }; const r1Payload = buildDeepSeekPayload(snapshot, sources, "Final answer"); const searchPayload = buildDeepSeekPayload({ ...snapshot, currentModelLabel: "联网搜索", }, sources, "Final answer"); expect(r1Payload.provider_model).toBe("DeepSeek-R1"); expect(searchPayload.provider_model).toBeNull(); }); it("builds json-safe payloads with null placeholders instead of undefined", () => { const payload = buildDeepSeekPayload({ url: "https://chat.deepseek.com/", title: "DeepSeek", loginRequired: false, loginReason: null, challengeRequired: false, challengeReason: null, busy: false, busySignals: [], composerValue: null, sendDisabled: false, answer: "Final answer", answerText: "Final answer", reasoning: null, signature: "sig-1", currentModelLabel: null, providerRequestID: null, requestID: null, inlineLinks: [], sourcePanelLinks: [], searchPanelLinks: [], }, { citations: [], inlineCitationCandidates: [], searchResults: [], }, "Final answer"); expect(payload.provider_model).toBeNull(); expect(payload.provider_request_id).toBeNull(); expect(payload.request_id).toBeNull(); expect((payload.raw_response_json as Record).provider_model).toBeNull(); expect((payload.raw_response_json as Record).provider_request_id).toBeNull(); expect((payload.raw_response_json as Record).request_id).toBeNull(); expect((payload.raw_response_json as Record).signature).toBe("sig-1"); expect((payload.raw_response_json as Record).inline_citation_candidates).toEqual([]); }); }); describe("monitor adapter registry", () => { it("registers deepseek as a monitor adapter", () => { expect(getMonitorAdapter("deepseek")?.provider).toBe("deepseek"); }); });