import { describe, expect, test } from "vitest"; import { buildMarketCsv } from "../src/content/market/csv-exporter"; import type { MarketRecord } from "../src/content/market/types"; describe("csv-exporter", () => { test("uses fallback header order when no page export fields are available", () => { const csv = buildMarketCsv([]); const [headerLine] = csv.split("\n"); expect(headerLine).toBe( [ "达人ID", "达人名称", "地区", "21-60s报价", "单视频看后搜率", "个人视频看后搜率", "看后搜率", "看后搜数", "新增A3数", "新增A3率", "CPA3", "cp_search" ].join(",") ); }); test("uses page export field order and appends the two plugin columns", () => { const csv = buildMarketCsv([ { authorId: "123", authorName: "Alice", exportFields: { 达人信息: "Alice", 代表视频: "示例视频", 粉丝数: "100w", "21-60s报价": "¥450,000" }, status: "success", backendMetrics: { a3IncreaseCount: "78,366.22", afterViewSearchCount: "9,689.96", afterViewSearchRate: "0.36%", cpSearch: "14.46", cpa3: "1.79", newA3Rate: "3.44%" }, rates: { singleVideoAfterSearchRate: "0.5%-1%", personalVideoAfterSearchRate: "1% - 3%" } } satisfies MarketRecord ]); const [headerLine, rowLine] = csv.split("\n"); expect(headerLine).toBe( [ "达人信息", "粉丝数", "21-60s报价", "单视频看后搜率", "个人视频看后搜率", "看后搜率", "看后搜数", "新增A3数", "新增A3率", "CPA3", "cp_search" ].join(",") ); expect(rowLine).toBe( 'Alice,100w,"¥450,000",0.5% - 1%,1% - 3%,0.36%,"9,689.96","78,366.22",3.44%,1.79,14.46' ); }); test("omits the representative video column from exported page fields", () => { const csv = buildMarketCsv([ { authorId: "123", authorName: "Alice", exportFields: { 达人信息: "Alice", 代表视频: "示例视频", 粉丝数: "100w" }, status: "success" } satisfies MarketRecord ]); const [headerLine, rowLine] = csv.split("\n"); expect(headerLine).toBe( [ "达人信息", "粉丝数", "单视频看后搜率", "个人视频看后搜率", "看后搜率", "看后搜数", "新增A3数", "新增A3率", "CPA3", "cp_search" ].join(",") ); expect(rowLine).toBe("Alice,100w,,,,,,,,"); }); test("escapes commas and quotes", () => { const csv = buildMarketCsv([ { authorId: "123", authorName: "Alice, \"A\"", location: "Hangzhou", price21To60s: "450000", status: "success", rates: { singleVideoAfterSearchRate: "0.5%-1%", personalVideoAfterSearchRate: "1% - 3%" } } ]); const [, rowLine] = csv.split("\n"); expect(rowLine).toContain("\"Alice, \"\"A\"\"\""); }); test("emits empty rate fields plus failed status for failed rows", () => { const csv = buildMarketCsv([ { authorId: "123", authorName: "Alice", status: "failed", failureReason: "request-failed" } ]); const [, rowLine] = csv.split("\n"); expect(rowLine).toBe("123,Alice,,,,,,,,,,"); }); test("uses normalized display values in export rows", () => { const csv = buildMarketCsv([ { authorId: "123", authorName: "Alice", status: "success", rates: { singleVideoAfterSearchRate: "0.5%-1%", personalVideoAfterSearchRate: "0.02 - 0.1%" } } satisfies MarketRecord ]); const [, rowLine] = csv.split("\n"); expect(rowLine).toContain("0.5% - 1%"); expect(rowLine).toContain("0.02% - 0.1%"); }); test("emits empty backend metric cells when backend metrics are absent", () => { const csv = buildMarketCsv([ { authorId: "123", authorName: "Alice", status: "success", rates: { singleVideoAfterSearchRate: "0.5%-1%", personalVideoAfterSearchRate: "0.02 - 0.1%" } } satisfies MarketRecord ]); const [, rowLine] = csv.split("\n"); expect(rowLine).toBe("123,Alice,,,0.5% - 1%,0.02% - 0.1%,,,,,,"); }); });