Skip to content

test(compression): close F5.1 coverage gaps (replay reducer, live accumulator, StatusDot) - #4192

Merged
diegosouzapw merged 2 commits into
release/v3.8.29from
test/compression-f5-coverage-gaps
Jun 18, 2026
Merged

diegosouzapw merged 2 commits into
release/v3.8.29from
test/compression-f5-coverage-gaps

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

F5.1 coverage gaps — compression track

The F5.1 traceability audit (item→code→test) of the compression track (rodada4 master
plan, ~19 items) found the track ~16/19 done+tested, with three untested units —
code present and correct, but no covering test. This PR closes them (tests only).

Gap Unit What was missing
F3.2 useCompressionReplay reducer + stepMs Only buildReplayFrames was tested; the replay state machine (play / pause / tick / auto-stop-at-last-frame / reset / set-speed) and the speed→interval pacing had zero assertions.
F3.3 useLiveCompression → accumulateRun Exported "for unit tests" but never tested. Now pins most-recent-first ordering, the maxRuns cap (drops oldest), and input-array immutability.
F0.2 StatusDot (U0) No dedicated render test. Pins the error-color override and sizeClass via createRoot+act (jsdom).

Scope

Tests only. The single production change is visibility-only: replayReducer,
INITIAL_STATE, stepMs, and the ReplayState/ReplayAction types are exported from
useCompressionReplay.ts as unit-test seams (no logic change), matching the codebase's
pattern of exposing pure functions for direct testing (cf. compressionFlowModel).

Tests

17 new tests, all green: 14 node:test (compression-replay-reducer,
live-compression-accumulate) + 3 vitest (status-dot). typecheck:core / lint /
check:cycles clean.

Context

Part of the F5 consolidation pass. Remaining compression-track items tracked separately:
F2.4 benchmark A/B (VPS-gated), F3.3 real mid-pipeline compression.step streaming
(scope decision — current impl is a client-side replay of one compression.completed
event), and F4.4 quantization (PRs #4187 + #4190).

…umulator, StatusDot)

The F5.1 traceability audit of the compression track found three untested
units (code present + correct, no covering test):

- F3.2 useCompressionReplay state machine: only buildReplayFrames was
  tested; the replay reducer (play/pause/tick/auto-stop-at-end/reset/
  set-speed) + stepMs pacing were not. Export them as unit-test seams
  (visibility-only, no logic change) and pin every transition.
- F3.3 useLiveCompression: accumulateRun (exported "for unit tests" but
  never tested) — pins most-recent-first ordering, the maxRuns cap, and
  immutability of the input list.
- F0.2 StatusDot (U0): no dedicated render test — pins the error-color
  override and sizeClass via createRoot+act (jsdom).

Tests only (+ 5 export keywords as a test seam). 17 new tests green
(14 node:test + 3 vitest). typecheck:core / lint / check:cycles clean.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request exports internal state machine elements from useCompressionReplay.ts to serve as unit-test seams and introduces new unit tests for the compression replay reducer, the live compression run accumulator, and the StatusDot UI component. The review feedback suggests tracking and properly unmounting React 18 roots inside act during test cleanup to prevent memory leaks and ensure lifecycle effects are flushed.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +12 to +23
const containers: HTMLElement[] = [];

function mount(ui: React.ReactElement): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
containers.push(container);
const root = createRoot(container);
act(() => {
root.render(ui);
});
return container;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In React 18, when using createRoot, failing to call root.unmount() can lead to memory leaks and unexpected side effects (such as cleanup effects running after the DOM container has been removed). We should track the created roots and unmount them during cleanup.

Suggested change
const containers: HTMLElement[] = [];
function mount(ui: React.ReactElement): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
containers.push(container);
const root = createRoot(container);
act(() => {
root.render(ui);
});
return container;
}
const roots: { root: ReturnType<typeof createRoot>; container: HTMLElement }[] = [];
function mount(ui: React.ReactElement): HTMLElement {
const container = document.createElement("div");
document.body.appendChild(container);
const root = createRoot(container);
act(() => {
root.render(ui);
});
roots.push({ root, container });
return container;
}

Comment on lines +38 to +41
afterEach(() => {
while (containers.length > 0) containers.pop()?.remove();
document.body.innerHTML = "";
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Unmount the React roots inside act to ensure all React lifecycle cleanups are properly flushed and to prevent memory leaks.

afterEach(() => {
  act(() => {
    while (roots.length > 0) {
      const item = roots.pop();
      if (item) {
        item.root.unmount();
        item.container.remove();
      }
    }
  });
  document.body.innerHTML = "";
});

F5.2 review found that changing the replay speed while playing was silently
ignored: startTick() reads speedRef.current synchronously, but the ref was
only synced in a post-render effect — too late for the in-flight restart, so
the ticker kept the old cadence. Set speedRef.current synchronously in
handleSetSpeed before restarting the interval.

TDD (compression-replay-speed.test.tsx, fake timers + createRoot/act):
after setSpeed(3) mid-play, advancing only 133ms advances a frame (proven to
fail — frameIndex stuck at 0 — before the fix). typecheck:core / lint clean.
@diegosouzapw
diegosouzapw merged commit cd60eb2 into release/v3.8.29 Jun 18, 2026
4 checks passed
@diegosouzapw diegosouzapw mentioned this pull request Jun 19, 2026
@diegosouzapw
diegosouzapw deleted the test/compression-f5-coverage-gaps branch June 19, 2026 12:07
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
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.

1 participant