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("aligns dims with evidence by name, preserving key order", () => { const resp: StyleFingerprintResponse = { dimensions: { 句长: "偏短", 比喻密度: "高" }, evidence: { 句长: ["他来了。"], 比喻密度: ["如龙似虎", "若即若离"] }, version: 2, }; const fp = normalizeFingerprint(resp); expect(fp).toEqual({ version: 2, dimensions: [ { name: "句长", value: "偏短", evidence: ["他来了。"] }, { name: "比喻密度", value: "高", evidence: ["如龙似虎", "若即若离"] }, ], }); }); it("coerces non-string dim values and missing evidence", () => { const fp = normalizeFingerprint({ dimensions: { 节奏: 5, 视角: true }, evidence: { 节奏: ["x", 1] }, 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, filtering bad segments", () => { const out = normalizeStyleDrift( reviewItem({ style: { score: 87, segments: [ { idx: 3, score: 60, label: "口语化" }, { idx: 5, score: 72 }, "junk", ], }, }), ); expect(out).toEqual({ score: 87, segments: [ { idx: 3, score: 60, label: "口语化" }, { idx: 5, score: 72, 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 label fallback", () => { expect( narrowStyleEvent({ score: 90, segments: [{ idx: 1, score: 50 }] }), ).toEqual({ score: 90, segments: [{ idx: 1, 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: "更紧凑", }); }); });