test(react-tags): add hook regression tests for Tag family#36229
test(react-tags): add hook regression tests for Tag family#36229mainframev wants to merge 3 commits into
Conversation
9a05d90 to
36292c8
Compare
|
Pull request demo site: URL |
99f8a77 to
24a9f85
Compare
36292c8 to
f368d08
Compare
77859e1 to
495dfb9
Compare
495dfb9 to
1d660cc
Compare
24a9f85 to
4af8734
Compare
1d660cc to
14b9a10
Compare
4af8734 to
08cb5a5
Compare
14b9a10 to
28edd89
Compare
layershifter
left a comment
There was a problem hiding this comment.
Not sure why tests are second in the stack. Also they could target master, no?
| it('should NOT expose design-only fields (appearance/shape/size) on base state', () => { | ||
| const ref = React.createRef<HTMLDivElement>(); | ||
| const { result } = renderHook(() => useInteractionTagBase_unstable({}, ref), { wrapper: wrap() }); | ||
|
|
||
| expect(result.current).not.toHaveProperty('appearance'); | ||
| expect(result.current).not.toHaveProperty('shape'); | ||
| expect(result.current).not.toHaveProperty('size'); | ||
| }); |
There was a problem hiding this comment.
Good point, removed across all four hook test files. The base hook return types already exclude appearance/shape/size/avatar*, so TS prevents it, it was intended as an extra guard against accidental ...spread leaks
| import { useInteractionTag_unstable, useInteractionTagBase_unstable } from './useInteractionTag'; | ||
|
|
||
| const wrap = ( | ||
| contextOverrides: Parameters<typeof TagGroupContextProvider>[0]['value'] = { |
There was a problem hiding this comment.
there is TagGroupContextValue
| }; | ||
|
|
||
| const wrap = ( | ||
| overrides: Partial<Parameters<typeof InteractionTagContextProvider>[0]['value']> = {}, |
There was a problem hiding this comment.
InteractionTagContextValue?
| it('should NOT expose design-only fields (appearance/shape/size/avatar*) on base state', () => { | ||
| const ref = React.createRef<HTMLButtonElement>(); | ||
| const { result } = renderHook(() => useInteractionTagPrimaryBase_unstable({}, ref), { wrapper: wrap() }); | ||
|
|
||
| expect(result.current).not.toHaveProperty('appearance'); | ||
| expect(result.current).not.toHaveProperty('shape'); | ||
| expect(result.current).not.toHaveProperty('size'); | ||
| expect(result.current).not.toHaveProperty('avatarShape'); | ||
| expect(result.current).not.toHaveProperty('avatarSize'); | ||
| }); |
| }; | ||
|
|
||
| const wrap = ( | ||
| overrides: Partial<Parameters<typeof InteractionTagContextProvider>[0]['value']> = {}, |
There was a problem hiding this comment.
InteractionTagContextValue?
| it('should inject DismissRegular as default root children', () => { | ||
| const ref = React.createRef<HTMLButtonElement>(); | ||
| const { result } = renderHook(() => useInteractionTagSecondary_unstable({}, ref), { wrapper: wrap() }); | ||
| expect(result.current.root.children).toBeDefined(); |
There was a problem hiding this comment.
Let's add at least React.isValidElement
| expect(result.current.root.type).toBe('button'); | ||
| }); | ||
|
|
||
| it('should NOT inject DismissRegular children by default (icon injection lives in the styled hook)', () => { |
There was a problem hiding this comment.
| it('should NOT inject DismissRegular children by default (icon injection lives in the styled hook)', () => { | |
| it('should not inject children by default', () => { |
| it('should NOT expose design-only fields (appearance/shape/size)', () => { | ||
| const ref = React.createRef<HTMLButtonElement>(); | ||
| const { result } = renderHook(() => useInteractionTagSecondaryBase_unstable({}, ref), { wrapper: wrap() }); | ||
| expect(result.current).not.toHaveProperty('appearance'); | ||
| expect(result.current).not.toHaveProperty('shape'); | ||
| expect(result.current).not.toHaveProperty('size'); | ||
| }); |
| const { result } = renderHook(() => useTag_unstable({ dismissible: true }, ref), { wrapper: wrap() }); | ||
|
|
||
| expect(result.current.dismissIcon).toBeDefined(); | ||
| expect(result.current.dismissIcon?.children).toBeDefined(); |
There was a problem hiding this comment.
Let's add React.isValidElement
| expect(root.type).toBe('button'); | ||
| }); | ||
|
|
||
| it('should NOT inject a default dismissIcon children (icon injection lives in the styled hook)', () => { |
| const ref = React.createRef<HTMLDivElement>(); | ||
| const { result } = renderHook(() => useTagGroup_unstable({}, ref)); | ||
|
|
||
| expect((result.current.root as RootRecord)['data-tabster']).toEqual(expect.any(String)); |
There was a problem hiding this comment.
toHaveProperty should avoid type cast?
| const arrowNavigationProps: TabsterDOMAttribute = { 'data-tabster': '{"mock":"value"}' }; | ||
| const { result } = renderHook(() => useTagGroupBase_unstable({}, ref, { arrowNavigationProps })); | ||
|
|
||
| expect((result.current.root as RootRecord)['data-tabster']).toBe('{"mock":"value"}'); |
| it('should NOT throw when onDismiss/onAfterTagDismiss are omitted (true headless mode)', () => { | ||
| const ref = React.createRef<HTMLDivElement>(); | ||
| const { result } = renderHook(() => useTagGroupBase_unstable({}, ref)); | ||
|
|
||
| expect(() => result.current.handleTagDismiss({} as React.MouseEvent, { value: 'v1' })).not.toThrow(); | ||
| }); |
| it('should NOT expose design-only fields (size, appearance) on base state', () => { | ||
| const ref = React.createRef<HTMLDivElement>(); | ||
| const { result } = renderHook(() => useTagGroupBase_unstable({}, ref)); | ||
| expect(result.current).not.toHaveProperty('size'); | ||
| expect(result.current).not.toHaveProperty('appearance'); | ||
| }); |
Stack
This PR is part of a 3-PR stack. Review and merge bottom-up:
master) — merge firstSummary
Adds
renderHook-based regression tests for the Tag, InteractionTag, InteractionTagPrimary, InteractionTagSecondary, and TagGroup base + styled hooks. Locks in the public output shape so subsequent base-hook refactors (such as the Tabster decoupling in #36228) cannot silently regress the styled hooks.Coverage highlights:
root.type(button vs span), event-handler presence/absenceDismissRegularinjection happens only in the styled hooks (useTag_unstable,useInteractionTagSecondary_unstable), never in the base hooksappearance/sizefromTagGroupContextdisabled(group overrides tag prop)UseTagGroupBaseOptionscontract:arrowNavigationPropsis spread onto root,onAfterTagDismissis invoked after a dismiss; both default to no-oparia-selected(listbox),aria-pressed(default),aria-labelledby(InteractionTagSecondary)Stack created with GitHub Stacks CLI • Give Feedback 💬