Skip to content

Cover the personality settings write paths - #979

Merged
selfcontained merged 1 commit into
mainfrom
agt_8477dda00ee7/job-test-enforcer-88458cff
Aug 19, 2026
Merged

Cover the personality settings write paths#979
selfcontained merged 1 commit into
mainfrom
agt_8477dda00ee7/job-test-enforcer-88458cff

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

Why

PersonalitySettings (399 lines) had zero coverage — no test sibling, no e2e reference, and its only caller settings-pane.tsx is untested — while owning four write paths against /api/v1/personalities. It was the top-ranked gap carried in the test-enforcer brain state.

What

One new file, apps/web/src/components/app/personality-settings.test.tsx (18 tests). Only the HTTP seam (@/lib/api) and framer-motion are mocked; every row, form, radio toggle and error banner is the real component. The api mock dispatches on ${METHOD} ${path} against a mutable list payload, so refreshes are modelled rather than stubbed away.

Pinned behavior:

  • create — trimmed name, untrimmed prompt, POST then a read-after-write refresh
  • edit — the form replaces that row in place, PATCH by id, no create request
  • active toggle — optimistic flip before the request settles, { id: null } when the active row is toggled off, revert-to-previous on failure
  • deletewindow.confirm gate in both directions, DELETE then refresh
  • validation — blank name and whitespace-only prompt both blocked before any write, each step flipping the banner text so a silent no-op cannot pass
  • submit lock — buttons and fields disabled while a save is open, so a second click cannot double-post and mid-request edits cannot be silently discarded
  • error lifecycle — surfaced on every failure path, edits preserved for retry, cleared before the post-save refresh lands, and cleared by any later successful refresh

Active state is asserted through aria-pressed/aria-label rather than Tailwind classes, so a restyle cannot break the test and a broken affordance cannot pass it.

Notes

The framer-motion mock caches its component per tag. An uncached proxy hands React a fresh component type on every render, which remounts the whole list and silently detaches any element a test is holding — that cost two confusing failures during authoring and is called out in a comment.

50 mutants were run against personality-settings.tsx. The only survivor is the personalities.length === 0 half of the loading guard: setLoading(true) happens solely in the useState initializer and refresh() only ever sets it false, so React never commits a frame with loading === true and a non-empty list. No test can kill it; it is recorded in the brain rather than worked around.

A review agent on the diff found four gaps — a non-discriminating banner assertion, an untested "edits survive a failed save" claim, and two undocumented survivors — all fixed here, with all seven demonstrated mutants re-verified as killed.

Local: check ✓ · format ✓ · lint 0 errors · unit 163/2797 server, 68/1000 web, 60 ext, 4 scripts · e2e 181 passed/12 skipped · live tmux lane 11 passed, no leaked sessions.

🤖 Generated with Claude Code

PersonalitySettings had no coverage at all — no test sibling, no e2e
reference, and its only caller (settings-pane.tsx) is untested — while
owning four write paths against /api/v1/personalities.
The new suite mocks only the HTTP seam; every row, form, radio toggle
and error banner is the real component. It pins create (trimmed name,
read-after-write refresh), in-place edit and PATCH-by-id, the optimistic
active toggle with its revert-on-failure, delete behind window.confirm,
the two pre-write validation guards, the submit lock that stops a
double-post, and the error banner's lifecycle across all of them.
Active state is asserted through aria-pressed/aria-label rather than
Tailwind classes, so a restyle cannot break it and a broken affordance
cannot pass.
The framer-motion mock caches its component per tag: an uncached proxy
hands React a fresh type on every render, remounting the list and
silently detaching held elements.
50 mutants run against personality-settings.tsx; the only survivor is
the `personalities.length === 0` half of the loading guard, which is
unobservable because loading is never set back to true after the first
refresh.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 9820bfe into mainAug 19, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_8477dda00ee7/job-test-enforcer-88458cff branch August 19, 2026 02:34
Sign up for freeto 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

@selfcontained