Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { renderHook } from '@testing-library/react-hooks';
import * as React from 'react';

import { TagGroupContextProvider } from '../../contexts/tagGroupContext';
import { useInteractionTag_unstable, useInteractionTagBase_unstable } from './useInteractionTag';

const wrap = (
contextOverrides: Parameters<typeof TagGroupContextProvider>[0]['value'] = {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

there is TagGroupContextValue

handleTagDismiss: () => ({}),
size: 'medium',
},
): React.FC<{ children?: React.ReactNode }> => {
const Wrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
<TagGroupContextProvider value={contextOverrides}>{children}</TagGroupContextProvider>
);
return Wrapper;
};

describe('useInteractionTag_unstable', () => {
it('should add design-only fields (appearance, shape, size) on top of the base state', () => {
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(
() => useInteractionTag_unstable({ appearance: 'outline', shape: 'circular', size: 'small' }, ref),
{
wrapper: wrap(),
},
);

expect(result.current.appearance).toBe('outline');
expect(result.current.shape).toBe('circular');
expect(result.current.size).toBe('small');
});

it('should default appearance to filled and shape to rounded', () => {
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(() => useInteractionTag_unstable({}, ref), { wrapper: wrap() });

expect(result.current.appearance).toBe('filled');
expect(result.current.shape).toBe('rounded');
});

it('should inherit appearance and size from TagGroupContext when not set on props', () => {
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(() => useInteractionTag_unstable({}, ref), {
wrapper: wrap({ handleTagDismiss: () => ({}), size: 'extra-small', appearance: 'brand' }),
});

expect(result.current.appearance).toBe('brand');
expect(result.current.size).toBe('extra-small');
});
});

describe('useInteractionTagBase_unstable', () => {
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');
});
Comment on lines +54 to +61
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this test helpful?


it('should force disabled when TagGroupContext.disabled is true regardless of props', () => {
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(() => useInteractionTagBase_unstable({ disabled: false }, ref), {
wrapper: wrap({ handleTagDismiss: () => ({}), size: 'medium', disabled: true }),
});
expect(result.current.disabled).toBe(true);
});

it('should derive selected from props OR context.selectedValues containing the tag value', () => {
const ref = React.createRef<HTMLDivElement>();

const propSelected = renderHook(() => useInteractionTagBase_unstable({ selected: true, value: 'a' }, ref), {
wrapper: wrap(),
});
expect(propSelected.result.current.selected).toBe(true);

const contextSelected = renderHook(() => useInteractionTagBase_unstable({ value: 'a' }, ref), {
wrapper: wrap({ handleTagDismiss: () => ({}), size: 'medium', selectedValues: ['a'] }),
});
expect(contextSelected.result.current.selected).toBe(true);

const notSelected = renderHook(() => useInteractionTagBase_unstable({ value: 'b' }, ref), {
wrapper: wrap({ handleTagDismiss: () => ({}), size: 'medium', selectedValues: ['a'] }),
});
expect(notSelected.result.current.selected).toBe(false);
});

it('should generate interactionTagPrimaryId for use by aria-labelledby', () => {
const ref = React.createRef<HTMLDivElement>();
const { result } = renderHook(() => useInteractionTagBase_unstable({}, ref), { wrapper: wrap() });

expect(result.current.interactionTagPrimaryId).toEqual(expect.stringMatching(/^fui-InteractionTagPrimary-/));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { renderHook } from '@testing-library/react-hooks';
import * as React from 'react';

import { InteractionTagContextProvider } from '../../contexts/interactionTagContext';
import type { InteractionTagContextValue } from '../../contexts/interactionTagContext';
import { useInteractionTagPrimary_unstable, useInteractionTagPrimaryBase_unstable } from './useInteractionTagPrimary';

const baseContext: InteractionTagContextValue = {
appearance: 'filled',
disabled: false,
handleTagDismiss: () => ({}),
interactionTagPrimaryId: 'fui-InteractionTagPrimary-_test_',
selected: false,
selectedValues: [],
shape: 'rounded',
size: 'medium',
value: 'test',
};

const wrap = (
overrides: Partial<Parameters<typeof InteractionTagContextProvider>[0]['value']> = {},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

InteractionTagContextValue?

): React.FC<{ children?: React.ReactNode }> => {
const Wrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
<InteractionTagContextProvider value={{ ...baseContext, ...overrides }}>{children}</InteractionTagContextProvider>
);
return Wrapper;
};

describe('useInteractionTagPrimary_unstable', () => {
it('should add design-only fields (appearance, shape, size, avatar*) on top of the base state', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagPrimary_unstable({}, ref), {
wrapper: wrap({ appearance: 'brand', shape: 'circular', size: 'small' }),
});

expect(result.current.appearance).toBe('brand');
expect(result.current.shape).toBe('circular');
expect(result.current.size).toBe('small');
expect(result.current.avatarShape).toBe('circular');
expect(result.current.avatarSize).toBe(20);
});
});

describe('useInteractionTagPrimaryBase_unstable', () => {
it('should render root with the interactionTagPrimaryId from context', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagPrimaryBase_unstable({}, ref), { wrapper: wrap() });
expect(result.current.root.id).toBe('fui-InteractionTagPrimary-_test_');
});

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');
});
Comment on lines +51 to +60
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this helpful?


it('should set aria-pressed when context has handleTagSelect (selectable group)', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagPrimaryBase_unstable({}, ref), {
wrapper: wrap({ selected: true, handleTagSelect: () => ({}) }),
});
expect(result.current.root['aria-pressed']).toBe(true);
});

it('should NOT set aria-pressed when context has no handleTagSelect', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagPrimaryBase_unstable({}, ref), {
wrapper: wrap({ selected: true, handleTagSelect: undefined }),
});
expect(result.current.root).not.toHaveProperty('aria-pressed');
});

it('should default hasSecondaryAction to false', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagPrimaryBase_unstable({}, ref), { wrapper: wrap() });
expect(result.current.hasSecondaryAction).toBe(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { renderHook } from '@testing-library/react-hooks';
import * as React from 'react';

import { InteractionTagContextProvider } from '../../contexts/interactionTagContext';
import type { InteractionTagContextValue } from '../../contexts/interactionTagContext';
import {
useInteractionTagSecondary_unstable,
useInteractionTagSecondaryBase_unstable,
} from './useInteractionTagSecondary';

const baseContext: InteractionTagContextValue = {
appearance: 'filled',
disabled: false,
handleTagDismiss: () => ({}),
interactionTagPrimaryId: 'fui-InteractionTagPrimary-_test_',
selected: false,
selectedValues: [],
shape: 'rounded',
size: 'medium',
value: 'test',
};

const wrap = (
overrides: Partial<Parameters<typeof InteractionTagContextProvider>[0]['value']> = {},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

InteractionTagContextValue?

): React.FC<{ children?: React.ReactNode }> => {
const Wrapper: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
<InteractionTagContextProvider value={{ ...baseContext, ...overrides }}>{children}</InteractionTagContextProvider>
);
return Wrapper;
};

describe('useInteractionTagSecondary_unstable', () => {
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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's add at least React.isValidElement

});

it('should preserve user-provided children instead of the default DismissRegular', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagSecondary_unstable({ children: 'X' }, ref), {
wrapper: wrap(),
});
expect(result.current.root.children).toBe('X');
});

it('should inherit appearance/shape/size from context', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagSecondary_unstable({}, ref), {
wrapper: wrap({ appearance: 'outline', shape: 'circular', size: 'small' }),
});
expect(result.current.appearance).toBe('outline');
expect(result.current.shape).toBe('circular');
expect(result.current.size).toBe('small');
});
});

describe('useInteractionTagSecondaryBase_unstable', () => {
it('should render root with type="button"', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagSecondaryBase_unstable({}, ref), { wrapper: wrap() });
expect(result.current.root.type).toBe('button');
});

it('should NOT inject DismissRegular children by default (icon injection lives in the styled hook)', () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
it('should NOT inject DismissRegular children by default (icon injection lives in the styled hook)', () => {
it('should not inject children by default', () => {

const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagSecondaryBase_unstable({}, ref), { wrapper: wrap() });
expect(result.current.root).not.toHaveProperty('children');
});

it('should attach onClick and onKeyDown handlers', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagSecondaryBase_unstable({}, ref), { wrapper: wrap() });
expect(result.current.root.onClick).toEqual(expect.any(Function));
expect(result.current.root.onKeyDown).toEqual(expect.any(Function));
});

it('should build aria-labelledby from interactionTagPrimaryId and own id', () => {
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagSecondaryBase_unstable({}, ref), { wrapper: wrap() });
expect(result.current.root['aria-labelledby']).toEqual(
expect.stringMatching(/^fui-InteractionTagPrimary-_test_ fui-InteractionTagSecondary-/),
);
});

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');
});
Comment on lines +86 to +92
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this useful?


it('should call handleTagDismiss on Delete/Backspace keyDown via context', () => {
const handleTagDismiss = jest.fn();
const ref = React.createRef<HTMLButtonElement>();
const { result } = renderHook(() => useInteractionTagSecondaryBase_unstable({}, ref), {
wrapper: wrap({ handleTagDismiss, value: 'val' }),
});

const event = { key: 'Delete', defaultPrevented: false } as unknown as React.KeyboardEvent<HTMLButtonElement>;
result.current.root.onKeyDown?.(event);

expect(handleTagDismiss).toHaveBeenCalledWith(event, { value: 'val' });
});
});
Loading
Loading