116 lines
5.4 KiB
TypeScript
116 lines
5.4 KiB
TypeScript
|
|
// autoSaveService (FR-17/FR-3) — คุมพฤติกรรม flag "มีการแก้ไขที่ยังไม่เป็น version":
|
||
|
|
// ติดเฉพาะ clean→dirty (ผู้ใช้แก้จริง) หรือ notifyDocumentChanged ของ service ที่แก้เอกสาร
|
||
|
|
// ส่วนไฟล์ที่เปิดมา dirty ตั้งแต่แรก (quirk ของ Word หลัง stamp properties หรือแก้ค้าง
|
||
|
|
// จาก session ก่อน) ต้องถูก save เงียบ ๆ ไม่เด้ง banner ให้ผู้ใช้งงว่าไม่ได้ทำอะไร
|
||
|
|
// (คำขอผู้ใช้ 2026-11-03) — mock documentService แทน Word.run ทั้งหมด
|
||
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||
|
|
|
||
|
|
import { autoSaveConfig, autoSaveService } from "@/wordaddin/services/autoSaveService";
|
||
|
|
|
||
|
|
const doc = vi.hoisted(() => ({ dirty: false, saves: 0 }));
|
||
|
|
vi.mock("@/wordaddin/services/documentService", () => ({
|
||
|
|
isDocumentDirty: vi.fn(async () => doc.dirty),
|
||
|
|
saveDocument: vi.fn(async () => {
|
||
|
|
doc.saves += 1;
|
||
|
|
}),
|
||
|
|
}));
|
||
|
|
|
||
|
|
/** หมายเหตุ timing: seedBaseline ของ start() อ่าน dirty ณ ตอนเรียก — จะซิม "เปิดมา dirty"
|
||
|
|
* ต้องตั้ง doc.dirty ก่อน startService() แล้ว flush microtask ให้ baseline จบก่อนเดินเวลา */
|
||
|
|
async function flushMicrotasks(): Promise<void> {
|
||
|
|
await vi.advanceTimersByTimeAsync(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** เดินเวลาไปอีกหนึ่ง periodic tick (30 วิ) แบบ flush microtask ของ tick ด้วย */
|
||
|
|
async function tick(): Promise<void> {
|
||
|
|
await vi.advanceTimersByTimeAsync(autoSaveConfig.periodicIntervalMs);
|
||
|
|
}
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.useFakeTimers();
|
||
|
|
doc.dirty = false;
|
||
|
|
doc.saves = 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
autoSaveService.stop();
|
||
|
|
autoSaveService.clearUncommittedSinceVersion();
|
||
|
|
vi.useRealTimers();
|
||
|
|
vi.restoreAllMocks();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("flag FR-3 (hasUncommitted) กับ periodic save", () => {
|
||
|
|
it("เปิดไฟล์สะอาด → ผู้ใช้แก้จริง (dirty ภายหลัง) → tick ตั้ง flag + save", async () => {
|
||
|
|
autoSaveService.start();
|
||
|
|
await flushMicrotasks(); // baseline: สะอาด
|
||
|
|
doc.dirty = true; // ผู้ใช้พิมพ์
|
||
|
|
await tick();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(true);
|
||
|
|
expect(doc.saves).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("เปิดมา dirty ตั้งแต่แรก (quirk หลัง stamp/แก้ค้าง) → save เงียบ ๆ ไม่ตั้ง flag", async () => {
|
||
|
|
doc.dirty = true; // ก่อน start — ไฟล์เปิดมา dirty
|
||
|
|
autoSaveService.start();
|
||
|
|
await flushMicrotasks();
|
||
|
|
await tick();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(false);
|
||
|
|
expect(doc.saves).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("quirk ถาวร (save แล้วยัง dirty ทุกรอบ) → หลาย tick ก็ไม่ตั้ง flag (แต่ยัง save)", async () => {
|
||
|
|
doc.dirty = true;
|
||
|
|
autoSaveService.start();
|
||
|
|
await flushMicrotasks();
|
||
|
|
await tick();
|
||
|
|
await tick();
|
||
|
|
await tick();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(false);
|
||
|
|
expect(doc.saves).toBe(3);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("dirty แรก (quirk) → save แล้วสะอาด → แก้จริงหลังจากนั้น → flag ติด (เห็น clean→dirty)", async () => {
|
||
|
|
doc.dirty = true;
|
||
|
|
autoSaveService.start();
|
||
|
|
await flushMicrotasks();
|
||
|
|
await tick(); // save + ไม่ flag
|
||
|
|
doc.dirty = false; // save ทำให้สะอาด
|
||
|
|
await tick(); // เห็น clean → จุดอ้างอิงกลับมา
|
||
|
|
doc.dirty = true; // ผู้ใช้แก้จริง
|
||
|
|
await tick();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("notifyDocumentChanged (service ที่แก้เอกสาร เช่น เครื่องมือ template) → flag ทันที + debounce save 3 วิ", async () => {
|
||
|
|
autoSaveService.start();
|
||
|
|
await flushMicrotasks();
|
||
|
|
doc.dirty = true;
|
||
|
|
autoSaveService.notifyDocumentChanged();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(true); // ไม่รอ tick
|
||
|
|
expect(doc.saves).toBe(0); // ยังไม่ save — รอ debounce
|
||
|
|
await vi.advanceTimersByTimeAsync(autoSaveConfig.debounceMs);
|
||
|
|
expect(doc.saves).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("สร้าง version สำเร็จ → clearUncommittedSinceVersion เคลียร์ flag", async () => {
|
||
|
|
autoSaveService.start();
|
||
|
|
await flushMicrotasks();
|
||
|
|
doc.dirty = true;
|
||
|
|
await tick();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(true);
|
||
|
|
autoSaveService.clearUncommittedSinceVersion();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("suspend ระหว่าง flow (versioning) → tick ไม่ save ไม่แตะ flag", async () => {
|
||
|
|
autoSaveService.start();
|
||
|
|
await flushMicrotasks();
|
||
|
|
doc.dirty = true;
|
||
|
|
await autoSaveService.suspend();
|
||
|
|
await tick();
|
||
|
|
expect(autoSaveService.hasUncommittedSinceVersion).toBe(false);
|
||
|
|
expect(doc.saves).toBe(0);
|
||
|
|
autoSaveService.resume();
|
||
|
|
});
|
||
|
|
});
|