Add unit tests for autoSaveService to handle document dirty states and periodic saves
- Implement tests for various scenarios including user edits, initial dirty state, and document changes. - Ensure proper flagging of uncommitted changes and silent saves for documents opened in a dirty state. - Test debounce behavior for document change notifications and version clearing functionality. - Validate suspension and resumption of auto-saving during versioning processes.
This commit is contained in:
parent
0b6ee8cc84
commit
454c90d17e
17 changed files with 1167 additions and 33 deletions
|
|
@ -5,8 +5,13 @@
|
|||
* (version เกิดจากปุ่ม FR-2 เท่านั้น — กัน spam history และรักษา concept ของ FR-13)
|
||||
* พอร์ตดิบจาก D:\word-addin-poc\addin\src\services\autoSaveService.ts
|
||||
*
|
||||
* - debounce 3 วิ ผ่าน `notifyDocumentChanged()` (ยังไม่มี caller จริงใน Word — ต่อพ่วงไว้ให้ service อื่น)
|
||||
* - periodic ทุก 30 วิ: อ่าน `document.saved` ก่อน — dirty → ตั้ง flag FR-3 + save, สะอาด → ข้าม
|
||||
* - debounce 3 วิ ผ่าน `notifyDocumentChanged()` — caller จริงคือ service ที่แก้เอกสาร
|
||||
* (เครื่องมือ template) หลัง operation สำเร็จ: ตั้ง flag FR-3 ทันที + debounce save
|
||||
* - periodic ทุก 30 วิ: อ่าน `document.saved` ก่อน — dirty → save, สะอาด → ข้าม
|
||||
* ตั้ง flag FR-3 เฉพาะ **clean→dirty** (เคยเห็นสะอาดก่อน): ไฟล์ที่เปิดมา dirty ตั้งแต่แรก —
|
||||
* quirk ของ Word ที่รายงาน saved=false ค้างหลัง stamp properties/custom XML part
|
||||
* หรือแก้ค้างจาก session ก่อน — ไม่ถูกนับเป็น "ผู้ใช้แก้" กัน banner FR-3 เด้งทั้งที่ผู้ใช้
|
||||
* ไม่ได้ทำอะไรเลย (คำขอผู้ใช้ 2026-11-03; ทุกกรณียัง save ให้เหมือนเดิม — ปลอดภัยไว้ก่อน)
|
||||
* - ช่วง save เดียวเท่านั้น (saving lock): มีเรียกซ้อน mark pending แล้วเก็บอีกรอบเมื่อรอบปัจจุบันจบ
|
||||
* - flow ที่ยุ่งกับไฟล์เอง (FR-2/FR-18) ใช้คู่ `suspend()`/`resume()` — ซ้อนกันได้ (นับ depth)
|
||||
* - เริ่มที่ Office.onReady (App mount) ไม่รอ sign-in · ล้ม → `onError` (banner ไม่บล็อก) ไม่ throw ทิ้ง
|
||||
|
|
@ -39,6 +44,8 @@ class WordAutoSaveService {
|
|||
private idleWaiters: Array<() => void> = [];
|
||||
private started = false;
|
||||
private uncommitted = false;
|
||||
/** เคยเห็น document สะอาด (saved=true) อย่างน้อยหนึ่งจุดตั้งแต่ start — clean→dirty ถัดไปคือ "ผู้ใช้แก้จริง" */
|
||||
private seenClean = false;
|
||||
|
||||
/** auto-save ล้ม (timer-driven) — App ต่อเป็น banner เตือนแบบไม่บล็อก */
|
||||
onError: ((error: unknown) => void) | undefined;
|
||||
|
|
@ -63,6 +70,7 @@ class WordAutoSaveService {
|
|||
}
|
||||
this.started = true;
|
||||
this.startTimers();
|
||||
void this.seedBaseline();
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
|
|
@ -87,11 +95,14 @@ class WordAutoSaveService {
|
|||
}
|
||||
}
|
||||
|
||||
/** FR-17 debounce — service ที่แก้เอกสารเรียกหลัง operation สำเร็จ */
|
||||
/** FR-17 debounce — service ที่แก้เอกสาร (เครื่องมือ template) เรียกหลัง operation สำเร็จ:
|
||||
* การแก้ไขเกิดจริงเสมอจึงตั้ง flag FR-3 ทันที ไม่รอ clean→dirty ของ periodic tick */
|
||||
notifyDocumentChanged(): void {
|
||||
if (!autoSaveConfig.enabled) {
|
||||
return;
|
||||
}
|
||||
this.uncommitted = true;
|
||||
this.notifyState();
|
||||
this.clearDebounce();
|
||||
this.debounceTimer = setTimeout(() => {
|
||||
this.debounceTimer = undefined;
|
||||
|
|
@ -168,19 +179,34 @@ class WordAutoSaveService {
|
|||
return; // มี save หรือ flow อื่นถือไม้ตายอยู่ — รอรอบหน้า
|
||||
}
|
||||
try {
|
||||
// FR-3 — periodic ทำหน้าที่ reminder ด้วย: เห็น dirty ก่อน save ตั้ง flag
|
||||
// (ระบบ reminder เดิมที่ poll แยกถูกแทนด้วย tick นี้)
|
||||
if (!(await isDocumentDirty())) {
|
||||
const dirty = await isDocumentDirty();
|
||||
if (!dirty) {
|
||||
// จุดที่เห็นสะอาด — clean→dirty ของ tick ถัดไปคือการแก้ไขจริงของผู้ใช้
|
||||
this.seenClean = true;
|
||||
return;
|
||||
}
|
||||
this.uncommitted = true;
|
||||
this.notifyState();
|
||||
// FR-3 — ตั้ง flag เฉพาะ clean→dirty: dirty ตั้งแต่เปิดไฟล์/ติดจาก quirk หลัง stamp
|
||||
// ไม่ใช่ฝีมือผู้ใช้ จึงไม่เด้ง banner ให้งง (ยัง save ตามปกติ)
|
||||
if (this.seenClean) {
|
||||
this.uncommitted = true;
|
||||
this.notifyState();
|
||||
}
|
||||
await this.save();
|
||||
} catch (error) {
|
||||
this.reportError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/** จุดตั้งต้นตอน start — ไฟล์ที่เปิดมา dirty ตั้งแต่แรกไม่นับเป็น "ผู้ใช้แก้" (ดูหัวไฟล์)
|
||||
* อ่านไม่ได้ = เงียบไว้ (ค่า false เป็นค่าอนุรักษ์นิยม: ไม่ติด flag จนกว่าจะเห็น clean จริง) */
|
||||
private async seedBaseline(): Promise<void> {
|
||||
try {
|
||||
this.seenClean = !(await isDocumentDirty());
|
||||
} catch {
|
||||
this.seenClean = false;
|
||||
}
|
||||
}
|
||||
|
||||
private waitIdle(): Promise<void> {
|
||||
return this.saving
|
||||
? new Promise<void>((resolve) => this.idleWaiters.push(resolve))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue