Skip to content

test(dashboard): cover SSE message processors and add first component test - #248

Draft
walidozich wants to merge 6 commits into
oblien:mainfrom
walidozich:test/dashboard-sse-processors
Draft

test(dashboard): cover SSE message processors and add first component test#248
walidozich wants to merge 6 commits into
oblien:mainfrom
walidozich:test/dashboard-sse-processors

Conversation

@walidozich

@walidozich walidozich commented Jul 26, 2026

Copy link
Copy Markdown

Important

Draft: stacked on #243. This branch is based on #243, which is not merged yet, so GitHub shows its commits here too. The only new work in this PR is the final commit, test(dashboard): cover SSE build, log, and generic message processors (one new file, +946).

I will rebase onto main and mark this ready once #243 lands. Opening it now so the coverage is visible and reviewable rather than sitting on my machine.

New commits in this PR: test(dashboard): cover SSE build, log, and generic message processors and test(dashboard): add first component test covering Button.

Summary

Two things:

  1. 83 tests covering apps/dashboard/src/lib/sseMessageProcessors.ts, the 419-line module that turns raw SSE stream payloads into the callbacks driving every live build and log view in the dashboard. It previously had no test coverage.
  2. The first component test in the dashboard (10 tests on components/ui/Button), which is the first committed test to exercise the jsdom project added in test(dashboard): add Vitest infrastructure and first test suites #243.

Motivation

This module sits directly under the real-time deploy UI. If parseMessage misclassifies a payload or handleMessage fails to fire a callback, a build silently stops updating, or a failure never surfaces to the user. None of that was protected by a test.

It is also a good testing target: the three factories are callback-driven and pure, so they need no DOM, no network, and no timers.

Related issue

Refs #216. Tests do not require prior issue agreement per CONTRIBUTING.md.

Changes

apps/dashboard/src/lib/sseMessageProcessors.test.ts (new, 83 tests)

  • createBuildMessageProcessor (~57 tests): every recognized type branch in parseMessage, plus null/undefined/empty/malformed input; all 11 optional callbacks firing with correct arguments; that omitting callbacks never throws; the onPrompt and onServiceStatus default fallbacks (title || "Action Required", status ?? "pending"); and the handleMessage boolean return contract across every recognized type.
  • createLogMessageProcessor (~19 tests): the log, connected, end, and error branches, the base64 versus plain-text log fallback, and malformed payloads.
  • createGenericMessageProcessor (7 tests): complete coverage of its small surface.

apps/dashboard/src/components/ui/button.test.tsx (new, 10 tests)

Covers click dispatch, the disabled state gating interaction, asChild slot polymorphism, ref forwarding, arbitrary prop pass-through, and className merging.

It deliberately does not assert exact Tailwind class strings, since those break on any cosmetic change while protecting nothing. Variants are compared against each other (destructive must differ from ghost) rather than against literals, and queries go through accessible roles. It is written to be the copy-paste reference for future component tests, and the file says so.

Verification

$ bun run --cwd apps/dashboard test

 ✓  unit  src/lib/sseMessageProcessors.test.ts (83 tests)
 ✓  dom   src/components/ui/button.test.tsx (10 tests)

 Test Files  10 passed (10)
      Tests  209 passed (209)

Note the dom tag on the component test and unit on the rest: that is the environment split from #243 doing its job, with only the .tsx file paying for jsdom.

Proving the tests can fail. Neutering the onFailure dispatch at line 254:

$ sed -i '254s/callbacks\.onFailure?.(errorMsg, message\.errorCode, message\.errorDetails);/void errorMsg;/' \
    apps/dashboard/src/lib/sseMessageProcessors.ts
$ bun run --cwd apps/dashboard test

 × prefers error over message, and is unconditional (no success check)
 × falls back to message, then a default, in that order

 Test Files  1 failed | 8 passed (9)
      Tests  2 failed | 197 passed (199)

Exactly the two guarding tests fail, with no collateral cascade.

The component test was mutation-checked twice as well. Forcing asChild to always render a button fails exactly the 2 slot tests; dropping ref={ref} fails exactly the 1 ref test. Sources restored with git checkout -- and confirmed clean via git diff. Typecheck and Prettier both pass.

Notes for reviewers: two pre-existing findings

Not fixed here, since behavior changes need an agreed issue first. The tests characterize current behavior. Happy to open an issue for either.

1. Spread-after-literal makes the fallback labels unreachable. All three parseMessage functions are written as:

return { type: "unknown", ...jsonData };

The spread comes after the literal, so any type already on the payload overrides the intended label:

{ type: "unknown", ...{ type: "totally-custom" } }  // => { type: "totally-custom" }
{ ...{ type: "totally-custom" }, type: "unknown" }  // => { type: "unknown" }

Since SSE payloads almost always carry a type, the "unknown" label in the build and log processors, and the "message" default in the generic one, are effectively unreachable. It fails silently as misclassification rather than a crash, which is presumably why it went unnoticed. The fix is a one-line reorder, but it is a behavior change so I have left it alone.

2. handleMessage never returns false. The SSEMessageProcessor contract in hooks/useSSEStream.ts documents false as "stop processing", but every branch in the build and log processors falls through to an unconditional return true. That may well be deliberate today; flagging it in case a future case (a hard container failure, say) is expected to halt stream consumption.

Checklist

  • One change per PR, one bug, or one agreed feature, with nothing unrelated bundled in
  • The diff is scoped, no reformatting or lint fixes on lines I wasn't otherwise changing
  • A test fails without this change and passes with it (or I explained above why there isn't one)
  • bun run test, bun run --cwd <workspace> lint, and bun format all pass locally
  • I understand every line of this diff and can explain it in review

@walidozich walidozich changed the title test(dashboard): cover SSE build, log, and generic message processors test(dashboard): cover SSE message processors and add first component test Jul 26, 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