Skip to content

test(components): add tests for Button, Badge, Divider, Link, Typography - #216

Merged
Chibuzor-Nwemambu merged 5 commits into
mainfrom
214-batch-1-tests
Jul 8, 2026
Merged

test(components): add tests for Button, Badge, Divider, Link, Typography#216
Chibuzor-Nwemambu merged 5 commits into
mainfrom
214-batch-1-tests

Conversation

@Chibuzor-Nwemambu

@Chibuzor-Nwemambu Chibuzor-Nwemambu commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Batch 1 of test: add component test infrastructure #214's component test rollout: adds behavior tests for Button (+ Button.Icon), Badge, Divider, Link, and Typography (+ Typography.Header) — 38 tests total (rebalanced from an initial 42 → briefly 70 → 36 addressing PR review on test quality, then +2 for the accessibilityState merge regression tests below).
  • Adds shared test infra reused by all future batches: test-utils.tsx (render wrapped in EDSProvider), an @expo/vector-icons mock in jest.setup.ts (fixes act() warnings from async font-loading state, while preserving real static properties like .font so useEDS.ts isn't silently broken), and @types/react-test-renderer (fixes RNTL query results resolving to TS's error type).
  • Narrows jest.config.cjs's untested-component checklist: removes Button/Badge/Divider/Link/Typography (now tested), and also drops Dialog/ErrorBoundary/Icon/Label/Portal/Scrim/_internal (internal pieces covered indirectly via consumers) and Paper/PressableHighlight (slated for removal/replacement, not worth testing now).
  • Adds regression tests for fix(components): make component's own disabled state win accessibilityState merges #221 (merged) covering Button/Button.Icon's accessibilityState merge — the Button.Icon case specifically targets the JSX-ordering bug that PR fix(components): make component's own disabled state win accessibilityState merges #221's review caught, verified to fail against that pre-fix code.

Contributes to #214 — does not close it (batches 2, SelectionControls, and the new-component-workflow update are still outstanding).

Test plan

  • pnpm test — 38/38 passing across 5 suites
  • pnpm lint — clean
  • pnpm check-types — clean
  • pnpm build — clean, confirmed test-utils.tsx doesn't leak into dist/

Batch 1 of issue #214's component test rollout. Adds a shared test-utils
render helper (wraps EDSProvider), an @expo/vector-icons mock to avoid
act() warnings from its async font-loading state, and @types/react-test-renderer
to fix RNTL's ReactTestInstance types resolving to `error` under tsc/eslint.

Also narrows jest.config.cjs's untested-component checklist: Dialog,
ErrorBoundary, Icon, Label, Portal, Scrim, and _internal are internal
pieces covered indirectly via their consumers, and Paper/PressableHighlight
are excluded since both are slated for removal/replacement.
@Chibuzor-Nwemambu Chibuzor-Nwemambu linked an issue Jul 6, 2026 that may be closed by this pull request
5 tasks
@Chibuzor-Nwemambu

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Jul 6, 2026

Copy link
Copy Markdown

Claude finished @Chibuzor-Nwemambu's task in 1m 54s —— View job


Code review — PR #216

  • Read changed files & test infra
  • Review test coverage & correctness
  • Review config/tsconfig changes
  • Post review feedback

Nicely done — the tests exercise real behavior (roles, press handling, disabled state, icon rendering) rather than snapshots, and the shared infra is well thought through and thoroughly commented. pnpm test/lint/check-types/build are all green per the description, and I verified the assertions line up with the actual component sources (Divider a11y defaults, Link accessibilityRole="link" + open-in-new icon, Button accessibilityRole="button" + disabled). This is a solid foundation for the remaining batches.

A few observations, all non-blocking:

Strengths worth calling out

  • The jest.config.cjs guard (lines 53–82) that fails loudly when a test file exists but the dir is still ignored is exactly the right safeguard for a checklist-driven rollout — it turns a silent "green checkmark, no coverage" into a hard error.
  • The @expo/vector-icons mock preserving static props (.font, glyph maps) via Object.assign instead of dropping them is a careful touch — a naive mock would have silently broken useEDS.ts's ...MaterialCommunityIcons.font spread.
  • test-utils.tsx relying on ES modules giving the explicit export { renderWithProviders as render } precedence over the export * star export is correct and clean.

Suggestions (optional)

  1. Smoke tests don't assert the variant actually applied. The it.each(...)("renders without error for tone/weight/tracking %s") cases (e.g. Badge.test.tsx:31, Typography.test.tsx:25) only assert getByText(...) is truthy — they'd still pass if tone/weight were silently ignored by the component. Consider asserting one representative style/prop per axis (at least for the default vs. one non-default) so these guard against real regressions rather than just crash-free rendering. Fine to leave as smoke tests if that's the intent, but worth a deliberate call.

  2. UNSAFE_getByProps({ name }) couples the icon assertions to the mock. In Button.test.tsx:35 and Link.test.tsx:26/33, the query works because the mock forwards name onto a Text host node. It's functional, but UNSAFE_* matches any element with that prop and is brittle to internal changes. A testID on the icon or an accessibility-based query would be more robust and drop the UNSAFE_ escape hatch. Minor.

  3. Divider a11y-override gap (pre-existing, out of scope). Divider.tsx:11-16 spreads {...rest} after accessible={false} / importantForAccessibility="no", so a consumer passing accessible would override the defaults. The new test (Divider.test.tsx:11) locks in the defaults but not the override path. Not something to fix here — just flagging in case it's worth a follow-up, since the ordering makes the "hidden from AT" guarantee overridable.

  4. Icon mock isn't forwardRef. If any component ever forwards a ref down to an icon set, it'd emit a ref warning under test. Nothing in the current tests exercises this, so no action needed now — noting it so it's not a surprise later.

None of these block merging. The core question for a test PR — do the tests assert meaningful behavior and is the infra sound? — is a clear yes.
· 214-batch-1-tests

…ering

Addresses Claude's automated review on PR #216: the it.each(...) variant
loops for Badge/Typography only proved each value rendered without
throwing, not that the prop actually did anything.

Auditing the same pattern surfaced a bigger gap in Button and Link: their
tone/size/variant and size axes had zero test coverage at all, not even
crash-only. Brought all four components to the same standard: an it.each
crash check per axis value, plus one real style-effect assertion per axis
(comparing two representative values via a flattened style, not hardcoded
exact token values, so these don't churn if design tokens change).

Also found and fixed a test bug along the way: UNSAFE_getByProps matches
the first node with a given prop, which for Button's leading/trailing
icons is ButtonIcon itself (the wrapper, which also carries `name` but no
style) rather than the actual rendered icon several layers deeper —
UNSAFE_getAllByProps + the last match gets the real one.
@Chibuzor-Nwemambu

Copy link
Copy Markdown
Collaborator Author

Addressed the review feedback:

  1. Smoke tests not asserting the variant applied — fixed, and broader than just Badge/Typography. Auditing the same pattern turned up zero coverage at all (not even crash-only) for Button's tone/size/variant and Link's size axes. Brought all four components to the same standard in 924ed6d: an it.each crash check per axis value, plus one real style-effect assertion per axis (comparing two representative values via a flattened style rather than hardcoded exact token values, so they don't churn if design tokens change later).

  2. UNSAFE_getByProps({ name }) coupling icon assertions to the mock — left as-is per your own "Minor" call; the alternative (adding testIDs to icons) means touching source components purely for testability, which felt like scope creep for a test-only PR. Worth revisiting if a future batch wants it.

  3. Divider's a11y-override gap — verified accurate ({...rest} does spread after accessible={false}/importantForAccessibility="no"). Filed as its own follow-up rather than fixed here, since it's a genuine judgment call (should a consumer be able to override that at all?) rather than a clear-cut bug — will track separately.

  4. Icon mock not forwardRef — noted, no action needed until something actually forwards a ref to an icon.

Also worth flagging: while writing batch-2's tests, found and fixed an unrelated but related-in-kind bug — Input.tsx was clobbering a caller-supplied accessibilityState instead of merging it (unlike Button/TextArea, which already merge correctly). That's issue #217, fixed in PR #218.

…r axis

Previous commit added a separate it.each crash loop (testing every enum
value) alongside each effect assertion, ballooning the suite from 42 to 70
tests. Comparing two representative values already implicitly proves
neither crashes, and the enum-to-token-key mapping is backed by
TypeScript's own type system (Record<T, ...> satisfies constraints), so
exhaustively sweeping every value added little beyond that. Merged each
pair into a single comparative test: 70 -> 36 tests.
@Chibuzor-Nwemambu

Copy link
Copy Markdown
Collaborator Author

Quick correction to my last comment: the fix for point 1 initially added a separate `it.each` crash-loop (every enum value) alongside each effect assertion, which ballooned the suite to 70 tests. On reflection that was overkill — comparing two representative values already implicitly proves neither crashes, and the enum→token-key mapping is backed by TypeScript's own `satisfies Record<...>` constraints, so exhaustively sweeping every value added little real coverage. Merged each pair into one comparative test per axis in ce53a81: 70 → 36 tests.

Chibuzor-Nwemambu added a commit that referenced this pull request Jul 7, 2026
…yState merges (#221)

## Summary
- Flips the `accessibilityState` merge order in `Button`, `IconButton`,
`Input`, and `TextArea` so the component's own computed `disabled`
always wins over a caller-supplied `accessibilityState.disabled`,
instead of the other way around.
- `disabled` drives real behavior (`editable`, styling) — letting a
caller's `accessibilityState` disagree with it can mislead assistive
technology users specifically (e.g. a screen reader announcing
"disabled" on a field that's actually fully interactive, or vice versa).
No realistic legitimate use case was found for allowing that
disagreement.
- Other `accessibilityState` keys (`selected`, `expanded`, `checked`,
etc.) are untouched and remain fully caller-overridable — this only
changes `disabled`.
- `TextField`/`Search` both wrap `Input` and forward
`accessibilityState` through `{...rest}`, so they inherit the fix
automatically with no changes of their own.
- `Input.tsx` here is written directly as the final, complete form
(merges *and* the component's `disabled` wins) — `main` never had #217's
partial fix, only `214-batch-2-tests` did, and that partial fix has now
been reverted there in favor of this being the single, canonical fix.
This PR fully supersedes and closes #217.
- Source-only change, no test files touched — regression tests are being
added as a follow-up directly into PR #216 (`Button.test.tsx`, currently
zero coverage of this) and PR #218
(`Input.test.tsx`/`TextArea.test.tsx`, currently zero coverage of this
after the #217 partial-fix revert) once this PR merges.

Fixes #220. Fixes #217.

## Test plan
- [x] `pnpm lint` — clean
- [x] `pnpm check-types` — clean
- [x] `pnpm build` — clean
- [ ] Regression tests to be added to PR #216 and PR #218 as a follow-up
once this merges (this PR intentionally has no test changes, per the
agreed atomic-commit split)
…utton/Button.Icon

Covers #221's fix: a caller-supplied accessibilityState merges with the
component's own computed disabled value, with disabled winning on
conflict. The Button.Icon case specifically targets the bug #221's review
caught — verified this exact test fails against that pre-fix IconButton
code (disabled: true, caller's conflicting value winning) and passes
against the fix.
@Chibuzor-Nwemambu

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Claude finished @Chibuzor-Nwemambu's task in 1m 48s —— View job


Code review — PR #216

  • Read changed files & test infra
  • Review test coverage & correctness
  • Review config/tsconfig changes
  • Post review feedback

The re-review looks good. All four prior suggestions were handled sensibly — the smoke tests now assert real style effects (comparing two representative values via flattened style rather than hardcoded token values, so they survive token churn), and points 2–4 were reasonable judgment calls to defer/track. I re-verified the new assertions against the sources:

  • accessibilityState regression tests — correct. Button.tsx:63-65 spreads ...pressableProps.accessibilityState then overrides disabled: disabled ?? false, so a caller passing { selected: true, disabled: true } yields { selected: true, disabled: false } exactly as asserted. The Button.Icon variant targets the JSX-ordering bug fix(components): make component's own disabled state win accessibilityState merges #221 fixed — good regression anchor.
  • Effect assertions (Button.test.tsx:64-114, Badge, Link, Typography) — these now guard against a variant being silently ignored, which is the meaningful upgrade over crash-only smoke tests. The comparative "A ≠ B" shape is a nice choice: it proves the axis has some effect without coupling to specific token values.
  • The jest.config.cjs checklist narrowing reads cleanly, and the comment explaining why Dialog/Icon/Paper/etc. are off-list (covered indirectly vs. slated for removal) is exactly the context a future contributor needs.

One observation (non-blocking)

The two-render()-calls-in-one-test pattern is correct but implicit. In the effect tests (e.g. Button.test.tsx:64-76, Typography.test.tsx:24-36), each test renders twice without cleanup() between. This works because RNTL rebinds screen to the most recent render, and each test captures the first value before the second render() runs — so screen.getByRole("button") never sees two trees at once. It's a subtle ordering contract, though: if someone later reorders the capture to after the second render, or adds a third comparison, the getByRole could start matching the wrong (or multiple) trees. Two ways to make the invariant explicit if you want to harden it for the batches to come:

// Option A — bind queries to each render explicitly, no reliance on screen rebinding
const { getByRole } = render(<Button label="Save" tone="accent" />);
const accentBorder = flattenStyle(getByRole("button"))?.borderColor;

or a small helper that renders, extracts one value, and unmounts — since all these tests follow the identical "render → read one style prop → discard" shape, that'd also cut the duplication. Purely a maintainability call; the current tests are correct and green.

Everything else — infra soundness, the @expo/vector-icons mock preserving statics, the test-utils alias mirrored across jest.config.cjs/tsconfig.json, build not leaking test-utils.tsx into dist/ — remains solid. No blockers; nice incremental foundation for batch 2.
· 214-batch-1-tests

@pomfrida pomfrida left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM 👍

Comment thread packages/components/jest.config.cjs
@Chibuzor-Nwemambu
Chibuzor-Nwemambu merged commit 3ca0586 into main Jul 8, 2026
13 checks passed
@Chibuzor-Nwemambu
Chibuzor-Nwemambu deleted the 214-batch-1-tests branch July 8, 2026 07:38
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.

test: add component test infrastructure

2 participants