Skip to content

Questionnaire v2: outline overlay and submit/draft regression coverage - #16630

Merged
bodhish merged 2 commits into
bodhi/qv2-outline-overlayfrom
bodhi/qv2-outline-tests
Aug 3, 2026
Merged

Questionnaire v2: outline overlay and submit/draft regression coverage#16630
bodhish merged 2 commits into
bodhi/qv2-outline-overlayfrom
bodhi/qv2-outline-tests

Conversation

@bodhish

@bodhish bodhish commented Aug 3, 2026

Copy link
Copy Markdown
Member

Stack 5/5 — chain: #16618#16627#16628#16629this.

  • New fillOutlineNav.spec.ts (15 tests) pins the overlay's contract: collapsed by default with the canvas at full width, tick-per-question rail, open via click/hover/focus, Escape restoring focus to the rail, scroll-spy and click-pin active states, completion adornments, enable_when reveal, group children, and per-form sections in multi-questionnaire sessions.
  • Regressions for the stack's fixes: a repeats answer submits its later rows after row 1 is cleared in place; Discard preserves answers typed while the restore prompt was pending (and persists them as a fresh draft); Resume merges a drafted form that was already re-added by hand; a form_submission that is no longer status draft refuses to resume (created straight through the API, so the spec runs without the save-as-draft build flag).
  • fillPatientSubject.spec.ts de-flaked: its Date.now() title truncated to a constant inside the 25-char slug window and collided with a record baked into the DB snapshot on every run.

Suite status on the stack tip: fill + outline + structured + resource-mount specs green (46 in scope); the two server-draft specs skip without the REACT_ENABLE_QUESTIONNAIRE_DRAFT build flag, as designed. diagnosis.spec.ts has a pre-existing intermittent failure unrelated to this stack (a different test fails each run; tracked separately).

🤖 Generated with Claude Code

…regressions

New fillOutlineNav.spec.ts covers the overlay's contract: collapsed by
default with the canvas at full width, tick-per-question rail, open via
click/hover/focus, Escape restoring focus to the rail, scroll-spy and
click-pin active states, completion adornments, enable_when reveal,
group children, and per-form sections in multi-questionnaire sessions.

Regressions for the submit/draft fixes: a repeats answer submits its
later rows after row 1 is cleared in place, Discard preserves answers
typed while the restore prompt was pending (and persists them as a
fresh draft), Resume merges a drafted form that was already re-added by
hand, and a form_submission that is no longer status "draft" refuses to
resume (created straight through the API, so the spec runs without the
save-as-draft build flag).

fillPatientSubject: the questionnaire title now keeps its unique part
inside the 25-char slug window — the Date.now() suffix truncated to a
constant that collided with a record in the DB snapshot on every run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entire-Checkpoint: 98ae52fe65b0
@bodhish
bodhish requested a review from a team as a code owner August 3, 2026 11:48
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6d97be1e-6533-4ff9-83c1-20724a092c4f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ Merge Checklist Incomplete

Thank you for your contribution! To help us review your PR efficiently, please complete the merge checklist in your PR description.

Your PR will be reviewed once you have marked the appropriate checklist items.

To update the checklist:

  • Change - [ ] to - [x] for completed items
  • Only check items that are relevant to your PR
  • Leave items unchecked if they don't apply

The checklist helps ensure code quality, testing coverage, and documentation are properly addressed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c6a9d07ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +55 to +56
if ((await toggle.getAttribute("aria-expanded")) !== "true") {
await toggle.click();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exercise the actual click-toggle branch

When Playwright performs toggle.click() with a mouse, it first moves the pointer onto the rail, whose pointerenter handler opens the panel; FillOutlineOverlay then deliberately ignores the ensuing mouse click when already open. Consequently, this helper and the test named “click opens the panel” still pass if the click/touch toggle branch is broken, duplicating the separate hover coverage instead of testing the promised click interaction. Use a touch pointer or another activation path that does not trigger mouse hover first.

Useful? React with 👍 / 👎.

name: /Medications taken \(repeats\)/,
}),
).toHaveAttribute("aria-current", "true");
await expect(activeTick(page)).toHaveCount(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Assert which rail tick becomes active

After navigating to “Medications taken,” this only verifies that some active tick exists. There was already exactly one active tick before the click, so the assertion passes if the rail remains stuck on the first question or activates the wrong tick, despite the step claiming to cover movement of the rail indicator. Assert data-active on the tick whose data-question-tick matches the selected question.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

Test-only PR. The coverage rationale is sound — regression tests for the discard/resume/draft bugs, the outline overlay interaction model, and the repeats submission bug are all valuable. The fillPatientSubject.spec.ts de-flake is the right fix.

Three issues worth addressing:

  1. Hardcoded mouse coordinates (page.mouse.move(900, 400)) in the hover test — fragile, use a locator instead.
  2. Magic + 2 in the enable_when tick count assertion — explain what the 2 represents.
  3. No assertion that submit was found before inspecting submit?.body.results in the repeats test — a URL-pattern change would make submittedValues silently empty and the toContain check would give a false negative (well, not.toContain would false-positive). Add expect(submit).toBeDefined().

Otherwise: structure is clean, test descriptions are specific and actionable, the openOutline helper correctly guards against double-opens, and replacing Date.now() with faker.string.alphanumeric was the right call. Fine, I guess.

Generated by Grumpy PR Reviewer for issue #16630 · 24.5 AIC · ⌖ 6.15 AIC · ⊞ 6.3K

// Leaving for the canvas closes it (200ms grace + transition).
await page.mouse.move(900, 400);
await expect(outlineToggle(page)).toHaveAttribute("aria-expanded", "false");
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hardcoded pixel coordinates. Classic.

await page.mouse.move(900, 400);

This is a hardcoded viewport coordinate assuming the canvas is at x=900, y=400. Change the viewport size, run on a machine with a different DPR, or add any sidebar — and this test starts misfiring silently. Move the mouse to an actual locator instead:

await page.getByRole("region", { name: "Form canvas" }).hover();

That way you're actually testing "user moves mouse to the canvas" rather than "user moves mouse to pixel (900,400) which we hope is the canvas today."

await test.step("the dependent question gains a row and a tick", async () => {
await openOutline(page);
await expect(
nav.getByRole("button", { name: /Stability notes/ }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Magic number arithmetic on a constant. DEFAULT_VISIBLE_TOP_LEVEL + 2 — why 2? What are those 2 questions? This is the kind of thing that silently breaks when the fixture changes and nobody knows why the number is 2 instead of 1 or 3. At minimum, add a comment explaining what the 2 represents (e.g., // the stability-notes question itself + one child it reveals). Better: name the constant.

};
const submit = body.requests.find((request) =>
request.url.includes(`/questionnaire/${questionnaireId}/submit/`),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Silent unsafe cast with no runtime validation. If postData() returns something that doesn't match this shape, you get undefined.find(...) and a cryptic error 10 frames deep. For test fixtures this is acceptable, but at minimum add a !submit guard before accessing submit?.body.results — you have ?. all over this chain already, so at least add an assertion:

expect(submit, "batch request must include a submit call").toBeDefined();

Right now a regression in the API URL pattern would silently pass submittedValues as [], making the toContain assertion vacuously false and the test... still pass. Not great.

…rage for all types (#16631)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: rithviknishad <mail@rithviknishad.dev>
@bodhish
bodhish merged commit aaee1fd into bodhi/qv2-outline-overlay Aug 3, 2026
5 of 6 checks passed
@bodhish
bodhish deleted the bodhi/qv2-outline-tests branch August 3, 2026 14:23

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Overall: Mostly fine — the QuestionLabel cleanup is consistent and the new regression tests are well-commented and clearly motivated. Two gripes worth addressing:

  1. SLUG_MAX_LENGTH 25→50 is the wrong fix for a test collision. This is a production validation constant. Doubling it to avoid a test name clash is backwards — fix the test to generate properly unique slugs. If the backend actually accepts 50-char slugs, then fine, document that alignment explicitly.

  2. Label removal without a clear contract — pulling QuestionLabel out of 10+ components in one shot is a big structural change. The assumption that the parent always renders the label needs to be either enforced (e.g. a wrapper component that is required) or at least documented, otherwise this will silently regress when someone uses these components in a new context.

The test prose and scenario coverage for the draft/resume/discard edge cases are actually quite good. Begrudgingly acknowledged.

Generated by Grumpy PR Reviewer for issue #16630 · 37.2 AIC · ⌖ 8.59 AIC · ⊞ 6.3K

@@ -12,7 +12,7 @@ import {
* clone dialog's `-copy` suffix clamp, so a bound change lands everywhere.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hold on a second. SLUG_MAX_LENGTH jumped from 25 to 50 to fix a test collision? That's treating the symptom, not the disease. If the backend enforces a 25-char slug limit (which it probably does, because backends tend to have opinions about database column widths), you've just let the frontend validate slugs the backend will reject. The fix for a test collision is to generate a better unique slug in the test — not to double the validation limit in production code. What's the actual backend constraint here?

@@ -676,7 +674,6 @@ export function AllergyQuestion({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Removing QuestionLabel from all these structured question components simultaneously — with no fallback rendering — means the question text is now rendered nowhere inside these components. I assume the outline overlay or the parent wrapper now handles the label. But if someone uses one of these question components outside that context (e.g. standalone, embedded), the label silently disappears and the user stares at a form field with no idea what it's asking. A passing test suite won't catch that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant