diff --git a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts index 62125ed3..39ed6bd1 100644 --- a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts +++ b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts @@ -268,6 +268,15 @@ describe('appendRow / flush', () => { expect(sheet.sentCount).toBe(3); }); + test('pendingCount returns to 0 after [Symbol.asyncDispose] flushes the buffer', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + await sheet.appendRow(...Array.from({ length: 3 }, () => eagerRow())); + await sheet[Symbol.asyncDispose](); + expect(sheet.pendingCount).toBe(0); + }); + test('suspends auto-flush as soon as a lazy row enters the buffer', async () => { const { parent, updateCellsRows } = createRecordingParent(); const sheet = new Sheet(mockSheet as never, parent as never); @@ -328,6 +337,47 @@ describe('appendRow / flush', () => { expect(sheet.sentCount).toBe(0); }); + test('pendingCount reflects buffered rows below the auto-flush threshold', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + expect(sheet.pendingCount).toBe(0); + + await sheet.appendRow(...Array.from({ length: 2499 }, () => eagerRow())); + expect(sheet.pendingCount).toBe(2499); + }); + + test('pendingCount drops to the chunk remainder once auto-flush fires', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + // 6000 rows -> two auto-flushed chunks of 2500, 1000 left buffered. + await sheet.appendRow(...Array.from({ length: 6000 }, () => eagerRow())); + expect(sheet.pendingCount).toBe(1000); + }); + + test('pendingCount returns to 0 after flush() drains the buffer', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + await sheet.appendRow(...Array.from({ length: 6000 }, () => eagerRow())); + await sheet.flush(); + expect(sheet.pendingCount).toBe(0); + }); + + test('pendingCount includes rows held past SEND_CHUNK_SIZE while a lazy row suspends auto-flush', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + // 2499 eager (below threshold, no flush yet) + 1 lazy (latches + // auto-flush) + 5000 more eager = 7500 held in the buffer instead of + // auto-flushing at the 2500 boundary. + await sheet.appendRow(...Array.from({ length: 2499 }, () => eagerRow())); + await sheet.appendRow(lazyRow()); + await sheet.appendRow(...Array.from({ length: 5000 }, () => eagerRow())); + expect(sheet.pendingCount).toBe(7500); + }); + test('detects a lazy cell at any column position, not just the first cell of the row', async () => { // containsLazyCell() iterates every cell of the row. A defensive // "optimization" that only checks row[0] would slip past unit tests diff --git a/packages/@d-zero/google-sheets/src/sheets/sheet.ts b/packages/@d-zero/google-sheets/src/sheets/sheet.ts index 3bf40800..067fa448 100644 --- a/packages/@d-zero/google-sheets/src/sheets/sheet.ts +++ b/packages/@d-zero/google-sheets/src/sheets/sheet.ts @@ -100,6 +100,28 @@ export class Sheet { return props; } + /** + * バッファに溜まっているが未送信の行数。`SEND_CHUNK_SIZE` 未満で + * 自動 flush の閾値に達していない行、または遅延セルによって自動 + * flush が保留されている行を指す。 + * + * `flush()` 呼び出し前に「これから何行分のネットワーク往復が + * 発生するか」を呼び出し元が表示する用途(例: 進捗表示の + * "flushing N rows..." メッセージ)。`onProgress` は chunk の + * `batchUpdate` 完了後にしか呼ばれないため、送信開始前の見積もりは + * この getter でのみ得られる。 + * @example + * ```ts + * if (sheet.pendingCount > 0) { + * showProgress(`flushing ${sheet.pendingCount} rows...`); + * } + * await sheet.flush(); + * ``` + */ + get pendingCount() { + return this.#pendingRows.length; + } + /** * `appendRow()` / `flush()` を通じてこれまでに送信した累計行数。 * 進捗表示用途。`setHeaders()` の送信分は含まない。