Skip to content

Commit c4c50f2

Browse files
docs(rules): add portal testing guidelines to Storybook test rules (Requested by Jake Ruesink)
1 parent 8886fe6 commit c4c50f2

File tree

1 file changed

+32
-1
lines changed

1 file changed

+32
-1
lines changed

.cursor/rules/storybook-testing.mdc

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,4 +985,35 @@ When creating or modifying Storybook interaction tests, ensure:
985985
- Fast feedback loop optimized for developer productivity
986986
- Individual story decorators provide flexibility for different testing scenarios
987987

988-
Remember: Every story should test real user workflows and serve as living documentation. Focus on behavior, not implementation details. The testing infrastructure should be reliable, fast, and easy to maintain for local development and Codegen workflows. **Always place decorators on individual stories for maximum flexibility and clarity.**
988+
Remember: Every story should test real user workflows and serve as living documentation. Focus on behavior, not implementation details. The testing infrastructure should be reliable, fast, and easy to maintain for local development and Codegen workflows. **Always place decorators on individual stories for maximum flexibility and clarity.**
989+
990+
### Testing Portaled UI (Select, Popover, Combobox)
991+
992+
- Open the trigger first, then query the portaled content (many libs render to document.body).
993+
- Query portal content from document.body using findByRole/waitFor; avoid raw setTimeout.
994+
- Give the trigger a stable accessible name via aria-label; do not rely on placeholder text (it changes after selection).
995+
- Prefer role-based queries:
996+
- role="listbox" for the popup container
997+
- role="option" for items
998+
- It’s OK to assert component-specific data attributes for positioning checks (e.g., data-slot="popover-content", data-align="end").
999+
- After selection or Escape, assert teardown with waitFor(() => expect(document.body.querySelector('[data-slot="popover-content"]').toBeNull())).
1000+
- In play functions, use within(document.body) to scope queries to the portal when needed.
1001+
- For controlled components, use the correct handler (e.g., onValueChange) so state updates reflect in assertions.
1002+
1003+
Example snippet:
1004+
```
1005+
await step('Open', async () => {
1006+
const trigger = await canvas.findByRole('combobox', { name: 'Favorite state' });
1007+
await userEvent.click(trigger);
1008+
const listbox = await within(document.body).findByRole('listbox');
1009+
expect(listbox).toBeInTheDocument();
1010+
});
1011+
1012+
await step('Select and close', async () => {
1013+
await userEvent.keyboard('{ArrowDown}{Enter}');
1014+
await waitFor(() => {
1015+
expect(document.body.querySelector('[data-slot="popover-content"]').toBeNull());
1016+
});
1017+
});
1018+
```
1019+

0 commit comments

Comments
 (0)