import { describe, expect, it, vi } from "vitest";
import {
noteDraftValue,
saveNoteDrafts,
setNoteDraft,
type NoteRow,
} from "@/lib/ds-devices/note-drafts";
/**
* D-Sec ログの備考の「編集モード」の下書き(ユーザー決定 2026-10-19 U3。指示書BC 段階4。D-183)。
* 規則は `lib/prechecks/edit-drafts.ts` と同じ(行の値に戻したら消す・null と "" は同じ・入力の Map を書き換えない・
* 順に送る・1行の失敗で止めない)。
*/
const A: NoteRow = { serial_number: "S1", note: "点検済み" };
const B: NoteRow = { serial_number: "S2", note: null };
const C: NoteRow = { serial_number: "S3", note: "x" };
function entries(map: ReadonlyMap<string, unknown>): Array<[string, unknown]> {
return Array.from(map.entries());
}
describe("setNoteDraft — 下書きを入れる", () => {
it("N1 変えたら入る", () => {
expect(entries(setNoteDraft(new Map(), A, "工事中"))).toEqual([["S1", "工事中"]]);
});
it("N2 行の値に戻したら消える(件数が「保存(N件)」になる)", () => {
expect(setNoteDraft(new Map([["S1", "工事中"]]), A, "点検済み").size).toBe(0);
});
it("N3 行の null と \"\" は同じ", () => {
expect(setNoteDraft(new Map(), B, "").size).toBe(0);
});
it("N4 null の行にも書ける", () => {
expect(entries(setNoteDraft(new Map(), B, "メモ"))).toEqual([["S2", "メモ"]]);
});
it("N5 入力の Map を書き換えない・戻り値は別の Map", () => {
const input = new Map([["S1", "工事中"]]);
const output = setNoteDraft(input, C, "y");
expect(output).not.toBe(input);
expect(entries(input)).toEqual([["S1", "工事中"]]);
expect(entries(output)).toEqual([
["S1", "工事中"],
["S3", "y"],
]);
});
});
describe("noteDraftValue — 画面に出す備考", () => {
it("N6a 下書きがあれば下書き", () => {
expect(noteDraftValue(A, new Map([["S1", "工事中"]]))).toBe("工事中");
});
it("N6b 行の null は \"\"", () => {
expect(noteDraftValue(B, new Map())).toBe("");
});
it("N6c 下書きが無ければ行の値", () => {
expect(noteDraftValue(A, new Map())).toBe("点検済み");
});
});
describe("saveNoteDrafts — まとめて保存", () => {
it("S1 rows の並び順に1行ずつ送る(下書きの順ではない)", async () => {
const sent: string[] = [];
const send = vi.fn(async (row: NoteRow, note: string) => {
sent.push(`${row.serial_number}:${note}`);
return note;
});
await saveNoteDrafts(
[A, B, C],
new Map([
["S3", "y"],
["S1", "工事中"],
]),
send
);
expect(sent).toEqual(["S1:工事中", "S3:y"]);
});
it("S2 1行の失敗で止めない・失敗の下書きは残す", async () => {
const boom = new Error("500");
const send = vi.fn(async (row: NoteRow, note: string) => {
if (row.serial_number === "S1") throw boom;
return `${note}!`;
});
const result = await saveNoteDrafts(
[A, C],
new Map([
["S1", "工事中"],
["S3", "y"],
]),
send
);
expect(send).toHaveBeenCalledTimes(2);
expect(entries(result.updated)).toEqual([["S3", "y!"]]);
expect(entries(result.failed)).toEqual([["S1", boom]]);
expect(entries(result.remaining)).toEqual([["S1", "工事中"]]);
});
it("S3 下書きが空なら送らない", async () => {
const send = vi.fn(async (_row: NoteRow, note: string) => note);
const result = await saveNoteDrafts([A, B, C], new Map(), send);
expect(send).not.toHaveBeenCalled();
expect(result.updated.size).toBe(0);
expect(result.failed.size).toBe(0);
expect(result.remaining.size).toBe(0);
});
it("S4 空にすると削除(\"\" を送り、サーバーが返した null を持つ)", async () => {
const send = vi.fn(async (): Promise<string | null> => null);
const result = await saveNoteDrafts([A], new Map([["S1", ""]]), send);
expect(send).toHaveBeenCalledWith(A, "");
expect(entries(result.updated)).toEqual([["S1", null]]);
});
});