Newer
Older
client_dwatch / lib / __tests__ / csv.test.ts
import { describe, expect, it } from "vitest";
import { csvDocument, csvRate } from "@/lib/csv";

/**
 * `csvRate()`(D-164。ユーザー指示 2026-10-06「値がないとこは空欄で」)。
 * null は空欄。値があれば四捨五入の整数+`%`(丸めは表示のみ。CLAUDE.md §4.3)。
 */
describe("csvRate — 率のセル", () => {
  const cases: Array<[string, number | null, string]> = [
    ["null → 空欄('- にしない)", null, ""],
    ["89.5 → 90%(四捨五入)", 89.5, "90%"],
    ["91.4 → 91%", 91.4, "91%"],
    ["0 → 0%(0 は値がある。空欄にしない)", 0, "0%"],
  ];

  it.each(cases)("%s", (_name, input, expected) => {
    expect(csvRate(input)).toBe(expected);
  });
});

/**
 * `csvDocument()`(D-163)。清掃詳細ログとレポートの施設別パフォーマンスが共有する。
 * `csvCell()` 単体の期待値表は `lib/clean-logs/__tests__/csv.test.ts`(再エクスポート経由で同じ関数)。
 */
describe("csvDocument — 文書の形", () => {
  it("先頭が BOM・行区切りは CRLF・最後の行も CRLF で終わる", () => {
    const out = csvDocument(["a", "b"], [["1", "2"], ["3", "4"]]);
    expect(out).toBe("a,b\r\n1,2\r\n3,4\r\n");
  });

  it("0件ならBOM + ヘッダ + CRLF だけ", () => {
    expect(csvDocument(["a", "b"], [])).toBe("a,b\r\n");
  });

  it("★ヘッダはエスケープしない★(清掃詳細ログの元の出力と揃える)", () => {
    expect(csvDocument(["=x"], [])).toBe("=x\r\n");
  });

  it("★データ行は全セルを csvCell() に通す★(数式対策と RFC 4180 の引用)", () => {
    const out = csvDocument(["a", "b", "c"], [["-", '=HYPERLINK("x")', "中央店, 本館"]]);
    expect(out).toBe('a,b,c\r\n\'-,"\'=HYPERLINK(""x"")","中央店, 本館"\r\n');
  });

  it("空文字のセルは空のまま並ぶ", () => {
    expect(csvDocument(["a", "b"], [["", ""]])).toBe("a,b\r\n,\r\n");
  });
});