Skip to content

test: undo/redo state integrity + ensureDataIds idempotency tests - #143

Merged
cheerc merged 1 commit into
mainfrom
test/wave3a-undo-redo-ensure-ids
Jun 23, 2026
Merged

test: undo/redo state integrity + ensureDataIds idempotency tests#143
cheerc merged 1 commit into
mainfrom
test/wave3a-undo-redo-ensure-ids

Conversation

@cheerc

@cheerc cheerc commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What

Add comprehensive tests for undo/redo state management (#135) and ensureDataIds idempotency (#137).

Closes #135
Closes #137

How

  • DI extraction (Strategy 1): Extracted createHistoryModule undo/redo stack logic from History.js.html L2-62 into tests/lib/undoRedoHelpers.js, preserving all behavioral contracts (dedup, redo truncation, 50-entry cap, boundary guards).
  • DI extraction (Strategy 1): Extracted ensureDataIds + generateUniqueId from JavaScript.html L881-900 into tests/lib/dataIdHelpers.js with injectable ID generator for deterministic testing.
  • 27 new undo/redo tests in tests/unit/undoRedoState.test.js covering:
    • saveState: dedup identical consecutive, truncate redo branch, 50-entry cap
    • undo: restore previous state, consecutive undos, boundary guard (index 0)
    • redo: restore next state, consecutive redos, boundary guard (end of stack)
    • interleaved undo/redo cycles, rapid back-and-forth stability
    • resetHistory: clear stack, disable undo/redo
    • canUndo/canRedo: boundary conditions
    • State isolation: structuredClone independence
    • Complex nested scheduleData through undo/redo round-trips
  • 23 new ensureDataIds tests in tests/unit/ensureDataIds.test.js covering:
    • Adding IDs to items without them (single, multiple, cross-classroom)
    • Idempotency: existing IDs preserved, multiple calls yield same result
    • Mixed partial IDs across classrooms and days
    • Edge cases: null, undefined, empty, non-array day values, null items
    • ID format: non-empty string, uniqueness, base-36 characters
    • Mutation behavior: returns same reference, mutates in place
  • Wiring contract update: module count 13 → 15

Scope

  • Test-only changes — zero production code modifications
  • Files changed: tests/lib/undoRedoHelpers.js (new), tests/lib/dataIdHelpers.js (new), tests/unit/undoRedoState.test.js (new), tests/unit/ensureDataIds.test.js (new), tests/unit/wiringContracts.test.js (count update)

Verification

  • ./workflow.sh t6 — 8/8 PASS (t1-t5, t7-t9)
  • npx vitest --run tests/unit/undoRedoState.test.js tests/unit/ensureDataIds.test.js — 50 tests passed
  • Scope gate: git diff --name-only origin/main...HEAD shows only test files

follows spec

@cheerc

cheerc commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

✅ VERIFIED

Reviewer: cb-team-reviewer
Reviewed HEAD: 7e2f6eb753da279f475b6d64dd28bb5da319a4b2
Audit mode: fast_path (D1, correctness)
Skills: review-task-loop, ecc-code-reviewer

Files Reviewed

  • tests/lib/undoRedoHelpers.js (107 lines, NEW) — DI extraction of undo/redo stack from History.js.html L2-62
  • tests/lib/dataIdHelpers.js (53 lines, NEW) — DI extraction of ensureDataIds from JavaScript.html L881-900
  • tests/unit/undoRedoState.test.js (413 lines, 27 tests) — saveState/undo/redo/resetHistory/canUndo/canRedo/state isolation/complex scheduleData
  • tests/unit/ensureDataIds.test.js (255 lines, 23 tests) — ID assignment/idempotency/mixed partial/edge cases/ID format/mutation
  • tests/unit/wiringContracts.test.js (6 lines ±) — module count 13 → 15

Summary

Pure test-only PR (zero production code changes). 50 new tests across two DI-extracted helper modules.

undoRedoHelpers.js: Extracted createHistoryModule stack logic as createTestableHistoryModule with injected callbacks. Preserves all behavioral contracts: dedup via JSON.stringify comparison, redo truncation on new action, 50-entry cap with shift, boundary guards (index > 0 for undo, index < length-1 for redo).

dataIdHelpers.js: Extracted ensureDataIds + generateUniqueId from JavaScript.html L881-900. Verified line-by-line against source. Only DI change: this.generateUniqueId() → injected idGenerator parameter.

undoRedoState.test.js (27 tests): saveState (dedup, truncate redo, 50-cap, shift oldest, callbacks), undo (restore, consecutive, boundary guard), redo (restore, consecutive, boundary), interleaved cycles (full undo/redo traversal, rapid back-and-forth), resetHistory (clear + disable undo/redo + callbacks), canUndo/canRedo boundary conditions, state isolation (structuredClone independence), complex nested scheduleData round-trips.

ensureDataIds.test.js (23 tests): ID assignment (single, multiple, cross-classroom), idempotency (existing preserved, multiple calls identical, three consecutive), mixed partial IDs, edge cases (null/undefined/empty/0 input, empty data, no days, empty day array, null classroom, non-array day, null items), ID format (non-empty, uniqueness ×100, base-36 chars), mutation behavior (same reference, in-place).

Stage 1 — Correctness

Zero findings. No conflict markers. Imports resolve. Issue linkage (Closes #135, #137). No risk-flags triggered. Both DI extractions verified against source.

Stage 2 — Adversarial

Zero findings. Dedup uses JSON.stringify comparison (matches source — same limitation: key order dependent, but consistent within same app). State isolation test verifies structuredClone independence correctly. createTestContext helper cleanly simulates app state mutation via closured appState. 50-entry cap test correctly verifies shift behavior (Room-2 at index 0 after 52 saves). Deterministic idGenerator injection enables reproducible assertion without time-dependency.

Evidence

  • ran: gh pr view 143 --json headRefOid7e2f6eb753da279f475b6d64dd28bb5da319a4b2 (SHA aligned)
  • ran: gh api .../JavaScript.html L881-900 → ensureDataIds source verified: this.generateUniqueId() → injected idGenerator
  • ran: gh api .../dataIdHelpers.js | grep conflict-markers → 0
  • cited: dataIdHelpers.js:L39-50 — ensureDataIds traversal matches source L881-896 exactly (null guards, Array.isArray check, classItem && !classItem.id)
  • cited: undoRedoHelpers.js:L47-48 — dedup via JSON.stringify(currentState) === JSON.stringify(history[historyIndex]) matches History.js.html original
  • cited: undoRedoState.test.js:L79-83 — 50-entry cap: history.shift(); historyIndex-- after 55 saves → stack[0] = Room-2

Closes #135, Closes #137

- Extract undo/redo stack logic from History.js.html as DI pure module
  in tests/lib/undoRedoHelpers.js (saveState/undo/redo/resetHistory)
- Extract ensureDataIds + generateUniqueId from JavaScript.html L881-900
  as DI pure functions in tests/lib/dataIdHelpers.js
- Add 27 tests in tests/unit/undoRedoState.test.js covering:
  - saveState dedup, redo truncation, 50-entry cap
  - undo/redo boundary guards, interleaved cycles
  - resetHistory, canUndo/canRedo, state isolation (structuredClone)
  - Complex nested scheduleData through undo/redo
- Add 23 tests in tests/unit/ensureDataIds.test.js covering:
  - Adding IDs to items without them, idempotency across multiple calls
  - Mixed partial IDs, edge cases (null/undefined/empty/non-array)
  - ID format validation, mutation behavior
- Update wiringContracts module count 13 → 15

Closes t-20260623135418587642-97909-0

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

Agend-Agent: cb-team-impl
Agend-Task: t-20260623135418587642-97909-0
Agend-Branch: test/wave3a-undo-redo-ensure-ids
@cheerc
cheerc force-pushed the test/wave3a-undo-redo-ensure-ids branch from 7e2f6eb to c327d31 Compare June 23, 2026 14:12
@cheerc
cheerc merged commit 9178602 into main Jun 23, 2026
1 check passed
@cheerc
cheerc deleted the test/wave3a-undo-redo-ensure-ids branch June 23, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase 0] ensureDataIds idempotency 行為測試 [Phase 0] Undo/Redo state integrity 行為測試

1 participant