import { describe, expect, it } from "vitest"; import type { ReviewHistoryItem, StyleFingerprintResponse, } from "@/lib/api/types"; import { buildLearnRequest, buildRefineRequest, hasUsableSamples, narrowStyleEvent, normalizeFingerprint, normalizeStyleDrift, } from "./style"; const reviewItem = (over: Partial): ReviewHistoryItem => ({ id: "00000000-0000-0000-0000-000000000001", project_id: "00000000-0000-0000-0000-000000000002", chapter_no: 1, ...over, }); describe("normalizeFingerprint", () => { it("maps DimensionEntry[] preserving order, evidence per entry", () => { const resp: StyleFingerprintResponse = { dimensions: [ { name: "句长", value: "偏短", evidence: ["他来了。"] }, { name: "比喻密度", value: "高", evidence: ["如龙似虎", "若即若离"] }, ], version: 2, }; const fp = normalizeFingerprint(resp); expect(fp).toEqual({ version: 2, dimensions: [ { name: "句长", value: "偏短", evidence: ["他来了。"] }, { name: "比喻密度", value: "高", evidence: ["如龙似虎", "若即若离"] }, ], }); }); it("defaults missing evidence to empty array", () => { const fp = normalizeFingerprint({ dimensions: [ { name: "节奏", value: "5", evidence: ["x"] }, { name: "视角", value: "true" }, ], version: 1, }); expect(fp?.dimensions).toEqual([ { name: "节奏", value: "5", evidence: ["x"] }, { name: "视角", value: "true", evidence: [] }, ]); }); it("returns null when fingerprint is undefined", () => { expect(normalizeFingerprint(undefined)).toBeNull(); }); }); describe("normalizeStyleDrift", () => { it("tightens style dict into report, carrying text anchor, filtering bad segments", () => { const out = normalizeStyleDrift( reviewItem({ style: { score: 87, segments: [ { idx: 3, text: "他乘坐迈巴赫扬长而去。", score: 60, label: "口语化" }, { idx: 5, score: 72 }, "junk", ], }, }), ); expect(out).toEqual({ score: 87, segments: [ { idx: 3, text: "他乘坐迈巴赫扬长而去。", score: 60, label: "口语化" }, { idx: 5, text: "", score: 72, label: null }, ], }); }); it("defaults missing segment text to empty string", () => { const out = normalizeStyleDrift( reviewItem({ style: { score: 80, segments: [{ idx: 0, score: 50 }] } }), ); expect(out?.segments[0]).toEqual({ idx: 0, text: "", score: 50, label: null, }); }); it("defaults score to 100 (degrade态) and returns null when missing", () => { expect(normalizeStyleDrift(reviewItem({ style: {} }))).toEqual({ score: 100, segments: [], }); expect(normalizeStyleDrift(reviewItem({ style: null }))).toBeNull(); expect(normalizeStyleDrift(undefined)).toBeNull(); }); }); describe("narrowStyleEvent", () => { it("narrows SSE style event data with text anchor + label fallback", () => { expect( narrowStyleEvent({ score: 90, segments: [{ idx: 1, text: "原文锚", score: 50 }], }), ).toEqual({ score: 90, segments: [{ idx: 1, text: "原文锚", score: 50, label: null }], }); expect( narrowStyleEvent({ score: 90, segments: [{ idx: 1, score: 50 }] }), ).toEqual({ score: 90, segments: [{ idx: 1, text: "", score: 50, label: null }], }); }); it("returns degrade态 for non-object data", () => { expect(narrowStyleEvent(null)).toEqual({ score: 100, segments: [] }); }); }); describe("buildLearnRequest", () => { it("trims and drops empty samples; passes mode through", () => { expect(buildLearnRequest([" 甲 ", "", "乙"], "update")).toEqual({ samples: ["甲", "乙"], mode: "update", }); }); }); describe("hasUsableSamples", () => { it("true only when at least one non-blank sample", () => { expect(hasUsableSamples(["", " "])).toBe(false); expect(hasUsableSamples(["", "正文"])).toBe(true); }); }); describe("buildRefineRequest", () => { it("trims segment and omits empty instruction", () => { expect(buildRefineRequest(" 这一段 ")).toEqual({ segment: "这一段" }); expect(buildRefineRequest("段", " ")).toEqual({ segment: "段" }); expect(buildRefineRequest("段", " 更紧凑 ")).toEqual({ segment: "段", instruction: "更紧凑", }); }); });