Skip to content

Commit 101f96a

Browse files
committed
chore(components): add tests
1 parent d7e82b8 commit 101f96a

3 files changed

Lines changed: 200 additions & 7 deletions

File tree

packages/components/src/Popover/__tests__/Popover.test.tsx

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { render } from '@testing-library/react';
1+
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
22
import { createRef } from 'react';
33
import { testA11y } from '@interlay/test-utils';
4+
import userEvent from '@testing-library/user-event';
45

56
import { Popover, PopoverBody, PopoverContent, PopoverFooter, PopoverHeader, PopoverTrigger } from '..';
7+
import { CTA } from '../../CTA';
68

79
describe('Popover', () => {
810
it('should render correctly', () => {
@@ -55,4 +57,118 @@ describe('Popover', () => {
5557
</Popover>
5658
);
5759
});
60+
61+
it('should able to open by using trigger', async () => {
62+
render(
63+
<Popover>
64+
<PopoverTrigger>
65+
<CTA>trigger</CTA>
66+
</PopoverTrigger>
67+
<PopoverContent>
68+
<PopoverHeader>header</PopoverHeader>
69+
<PopoverBody>body</PopoverBody>
70+
<PopoverFooter>footer</PopoverFooter>
71+
</PopoverContent>
72+
</Popover>
73+
);
74+
75+
userEvent.click(screen.getByRole('button', { name: /trigger/i }));
76+
77+
await waitFor(() => {
78+
expect(screen.getByRole('dialog', { name: /header/i }));
79+
});
80+
});
81+
82+
it('should able to close popover by using ESC', async () => {
83+
render(
84+
<Popover>
85+
<PopoverTrigger>
86+
<CTA>trigger</CTA>
87+
</PopoverTrigger>
88+
<PopoverContent>
89+
<PopoverHeader>header</PopoverHeader>
90+
<PopoverBody>body</PopoverBody>
91+
<PopoverFooter>footer</PopoverFooter>
92+
</PopoverContent>
93+
</Popover>
94+
);
95+
96+
userEvent.click(screen.getByRole('button', { name: /trigger/i }));
97+
98+
await waitFor(() => {
99+
expect(screen.getByRole('dialog', { name: /header/i }));
100+
});
101+
102+
userEvent.keyboard('{Escape}');
103+
104+
await waitForElementToBeRemoved(screen.getByRole('dialog', { name: /header/i }));
105+
});
106+
107+
it('should able to close popover by clicking outside of the component', async () => {
108+
render(
109+
<Popover>
110+
<PopoverTrigger>
111+
<CTA>trigger</CTA>
112+
</PopoverTrigger>
113+
<PopoverContent>
114+
<PopoverHeader>header</PopoverHeader>
115+
<PopoverBody>body</PopoverBody>
116+
<PopoverFooter>footer</PopoverFooter>
117+
</PopoverContent>
118+
</Popover>
119+
);
120+
121+
userEvent.click(screen.getByRole('button', { name: /trigger/i }));
122+
123+
await waitFor(() => {
124+
expect(screen.getByRole('dialog', { name: /header/i }));
125+
});
126+
127+
userEvent.click(document.body);
128+
129+
await waitForElementToBeRemoved(screen.getByRole('dialog', { name: /header/i }));
130+
});
131+
132+
it('should emit onOpenChange', async () => {
133+
const handleOpenChange = jest.fn();
134+
135+
render(
136+
<Popover onOpenChange={handleOpenChange}>
137+
<PopoverTrigger>
138+
<CTA>trigger</CTA>
139+
</PopoverTrigger>
140+
<PopoverContent>
141+
<PopoverHeader>header</PopoverHeader>
142+
<PopoverBody>body</PopoverBody>
143+
<PopoverFooter>footer</PopoverFooter>
144+
</PopoverContent>
145+
</Popover>
146+
);
147+
148+
userEvent.click(screen.getByRole('button', { name: /trigger/i }));
149+
150+
await waitFor(() => {
151+
expect(screen.getByRole('dialog', { name: /header/i }));
152+
});
153+
154+
expect(handleOpenChange).toHaveBeenCalledTimes(1);
155+
expect(handleOpenChange).toHaveBeenCalledWith(true);
156+
});
157+
158+
it('should be default open', async () => {
159+
render(
160+
<Popover defaultOpen>
161+
<PopoverTrigger>
162+
<CTA>trigger</CTA>
163+
</PopoverTrigger>
164+
<PopoverContent>
165+
<PopoverHeader>header</PopoverHeader>
166+
<PopoverBody>body</PopoverBody>
167+
<PopoverFooter>footer</PopoverFooter>
168+
</PopoverContent>
169+
</Popover>
170+
);
171+
172+
expect(screen.getByRole('dialog', { name: /header/i }));
173+
});
58174
});

packages/components/src/Switch/Switch.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { AriaSwitchProps, useSwitch } from '@react-aria/switch';
44
import { mergeProps } from '@react-aria/utils';
55
import { useToggleState } from '@react-stately/toggle';
66
import { PressEvent } from '@react-types/shared';
7-
import { ChangeEvent, forwardRef, HTMLAttributes, useRef } from 'react';
7+
import { ChangeEvent, ChangeEventHandler, forwardRef, HTMLAttributes, useRef } from 'react';
88
import { Placement } from '@interlay/theme';
99
import { useDOMRef } from '@interlay/hooks';
1010

@@ -14,6 +14,7 @@ import { StyledInput, StyledLabel, StyledSwitch, StyledWrapper } from './Switch.
1414

1515
type Props = {
1616
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
17+
onValueChange?: (isSelected: boolean) => void;
1718
onPress?: (e: PressEvent) => void;
1819
labelProps?: TextProps;
1920
labelPlacement?: Extract<Placement, 'left' | 'right'>;
@@ -26,11 +27,26 @@ type InheritAttrs = Omit<AriaSwitchProps, keyof Props>;
2627
type SwitchProps = Props & NativeAttrs & InheritAttrs;
2728

2829
const Switch = forwardRef<HTMLLabelElement, SwitchProps>(
29-
({ children, onChange, className, style, hidden, labelProps, labelPlacement, ...props }, ref): JSX.Element => {
30+
(
31+
{
32+
children,
33+
onChange,
34+
onValueChange,
35+
className,
36+
style,
37+
hidden,
38+
labelProps,
39+
labelPlacement,
40+
isSelected,
41+
isReadOnly,
42+
...props
43+
},
44+
ref
45+
): JSX.Element => {
3046
const labelRef = useDOMRef(ref);
3147
const inputRef = useRef<HTMLInputElement>(null);
3248

33-
const ariaProps: AriaSwitchProps = { children, ...props };
49+
const ariaProps: AriaSwitchProps = { children, isSelected, isReadOnly, ...props };
3450

3551
const state = useToggleState(ariaProps);
3652
const { inputProps } = useSwitch(ariaProps, state, inputRef);
@@ -41,6 +57,13 @@ const Switch = forwardRef<HTMLLabelElement, SwitchProps>(
4157

4258
const { pressProps } = usePress(props);
4359

60+
const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
61+
const isSelected = e.target.checked;
62+
63+
onChange?.(e);
64+
onValueChange?.(isSelected);
65+
};
66+
4467
return (
4568
<StyledWrapper
4669
ref={labelRef}
@@ -49,7 +72,7 @@ const Switch = forwardRef<HTMLLabelElement, SwitchProps>(
4972
hidden={hidden}
5073
style={style}
5174
>
52-
<StyledInput {...mergeProps(inputProps, focusProps, pressProps, { onChange })} ref={inputRef} />
75+
<StyledInput {...mergeProps(inputProps, focusProps, pressProps, { onChange: handleChange })} ref={inputRef} />
5376
<StyledSwitch $isChecked={inputProps.checked} $isFocusVisible={isFocusVisible} />
5477
{children && <StyledLabel {...labelProps}>{children}</StyledLabel>}
5578
</StyledWrapper>

packages/components/src/Switch/__tests__/Switch.test.tsx

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { render } from '@testing-library/react';
2-
import { createRef } from 'react';
1+
import { render, screen, waitFor } from '@testing-library/react';
2+
import { createRef, useState } from 'react';
33
import { testA11y } from '@interlay/test-utils';
4+
import userEvent from '@testing-library/user-event';
45

56
import { Switch } from '..';
67

@@ -22,4 +23,57 @@ describe('Switch', () => {
2223
it('should pass a11y', async () => {
2324
await testA11y(<Switch>switch</Switch>);
2425
});
26+
27+
it('should emit onChange and onValueChange', async () => {
28+
const handleChange = jest.fn();
29+
const handleValueChange = jest.fn();
30+
31+
render(
32+
<Switch onChange={handleChange} onValueChange={handleValueChange}>
33+
switch
34+
</Switch>
35+
);
36+
37+
userEvent.click(screen.getByRole('switch', { name: /switch/i }));
38+
39+
await waitFor(() => {
40+
expect(handleChange).toHaveBeenCalledTimes(1);
41+
expect(handleValueChange).toHaveBeenCalledTimes(1);
42+
expect(handleValueChange).toHaveBeenCalledWith(true);
43+
});
44+
});
45+
46+
it('should control value', async () => {
47+
const Component = () => {
48+
const [state, setState] = useState(false);
49+
50+
return (
51+
<Switch isSelected={state} onValueChange={(isSelected) => setState(isSelected)}>
52+
switch
53+
</Switch>
54+
);
55+
};
56+
57+
render(<Component />);
58+
59+
expect(screen.getByRole('switch', { name: /switch/i })).not.toBeChecked();
60+
61+
userEvent.click(screen.getByRole('switch', { name: /switch/i }));
62+
63+
await waitFor(() => {
64+
expect(screen.getByRole('switch', { name: /switch/i })).toBeChecked();
65+
});
66+
});
67+
68+
it('should be disabled', async () => {
69+
render(<Switch isDisabled>switch</Switch>);
70+
71+
expect(screen.getByRole('switch', { name: /switch/i })).toBeDisabled();
72+
});
73+
74+
it('should be read only', async () => {
75+
render(<Switch isReadOnly>switch</Switch>);
76+
77+
expect(screen.getByRole('switch', { name: /switch/i })).toHaveAttribute('aria-readonly', 'true');
78+
});
2579
});

0 commit comments

Comments
 (0)