paris 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/package.json +17 -2
- package/src/stories/accordion/Accordion.test.tsx +140 -0
- package/src/stories/accordionselect/AccordionSelect.test.tsx +252 -0
- package/src/stories/avatar/Avatar.test.tsx +77 -0
- package/src/stories/button/Button.test.tsx +266 -0
- package/src/stories/callout/Callout.test.tsx +79 -0
- package/src/stories/card/Card.test.tsx +81 -0
- package/src/stories/cardbutton/CardButton.test.tsx +174 -0
- package/src/stories/checkbox/Checkbox.test.tsx +531 -0
- package/src/stories/combobox/Combobox.test.tsx +164 -0
- package/src/stories/combobox/Combobox.tsx +44 -54
- package/src/stories/dialog/Dialog.module.scss +2 -2
- package/src/stories/dialog/Dialog.test.tsx +244 -0
- package/src/stories/drawer/Drawer.module.scss +2 -2
- package/src/stories/drawer/Drawer.test.tsx +259 -0
- package/src/stories/field/Field.test.tsx +146 -0
- package/src/stories/icon/Icon.test.tsx +59 -0
- package/src/stories/informationaltooltip/InformationalTooltip.test.tsx +178 -0
- package/src/stories/input/Input.test.tsx +174 -0
- package/src/stories/markdown/Markdown.test.tsx +228 -0
- package/src/stories/markdowneditor/FixedToolbar.tsx +44 -14
- package/src/stories/markdowneditor/LinkPopover.module.scss +1 -1
- package/src/stories/markdowneditor/MarkdownEditor.stories.tsx +4 -1
- package/src/stories/markdowneditor/MarkdownEditor.test.tsx +115 -0
- package/src/stories/markdowneditor/MarkdownEditor.tsx +11 -1
- package/src/stories/markdowneditor/MarkdownEditorContext.tsx +3 -0
- package/src/stories/markdowneditor/index.ts +1 -0
- package/src/stories/menu/Menu.module.scss +2 -15
- package/src/stories/menu/Menu.test.tsx +211 -0
- package/src/stories/menu/Menu.tsx +2 -6
- package/src/stories/pagination/usePagination.test.ts +259 -0
- package/src/stories/popover/Popover.test.tsx +152 -0
- package/src/stories/select/Select.module.scss +8 -5
- package/src/stories/select/Select.test.tsx +233 -0
- package/src/stories/select/Select.tsx +32 -50
- package/src/stories/styledlink/StyledLink.test.tsx +59 -0
- package/src/stories/table/Table.test.tsx +156 -0
- package/src/stories/tabs/Tabs.module.scss +1 -1
- package/src/stories/tabs/Tabs.test.tsx +167 -0
- package/src/stories/tag/Tag.test.tsx +90 -0
- package/src/stories/text/Text.test.tsx +81 -0
- package/src/stories/textarea/TextArea.test.tsx +147 -0
- package/src/stories/theme/themes.ts +16 -0
- package/src/stories/tilt/Tilt.test.tsx +203 -0
- package/src/stories/toast/Toast.test.tsx +86 -0
- package/src/stories/utility/Dropdown.module.scss +1 -1
- package/src/stories/utility/Utility.test.tsx +96 -0
- package/src/test/render.tsx +20 -0
- package/src/test/setup.ts +32 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { fireEvent } from '@testing-library/react';
|
|
2
|
+
import { render, screen } from '../../test/render';
|
|
3
|
+
import { Button } from './Button';
|
|
4
|
+
|
|
5
|
+
describe('Button', () => {
|
|
6
|
+
it('renders with default props', () => {
|
|
7
|
+
render(<Button>Click me</Button>);
|
|
8
|
+
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('renders default text when no children provided', () => {
|
|
12
|
+
render(<Button />);
|
|
13
|
+
expect(screen.getByRole('button')).toHaveTextContent('Button');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('kind variants', () => {
|
|
17
|
+
it.each(['primary', 'secondary', 'tertiary'] as const)('renders %s kind', (kind) => {
|
|
18
|
+
render(<Button kind={kind}>Label</Button>);
|
|
19
|
+
const button = screen.getByRole('button');
|
|
20
|
+
expect(button).toHaveClass(kind);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('defaults to primary kind', () => {
|
|
24
|
+
render(<Button>Label</Button>);
|
|
25
|
+
expect(screen.getByRole('button')).toHaveClass('primary');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('size variants', () => {
|
|
30
|
+
it.each(['large', 'medium', 'small', 'xs'] as const)('renders %s size', (size) => {
|
|
31
|
+
render(<Button size={size}>Label</Button>);
|
|
32
|
+
expect(screen.getByRole('button')).toHaveClass(size);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('defaults to large size', () => {
|
|
36
|
+
render(<Button>Label</Button>);
|
|
37
|
+
expect(screen.getByRole('button')).toHaveClass('large');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('shape variants', () => {
|
|
42
|
+
it.each(['pill', 'circle', 'rectangle', 'square'] as const)('renders %s shape', (shape) => {
|
|
43
|
+
render(<Button shape={shape}>Label</Button>);
|
|
44
|
+
expect(screen.getByRole('button')).toHaveClass(shape);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('defaults to pill shape', () => {
|
|
48
|
+
render(<Button>Label</Button>);
|
|
49
|
+
expect(screen.getByRole('button')).toHaveClass('pill');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('hides text content for circle shape but keeps it for accessibility', () => {
|
|
53
|
+
render(<Button shape="circle">Action</Button>);
|
|
54
|
+
const button = screen.getByRole('button');
|
|
55
|
+
expect(button).toBeInTheDocument();
|
|
56
|
+
expect(button).toHaveAttribute('aria-details', 'Action');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('hides text content for square shape but keeps it for accessibility', () => {
|
|
60
|
+
render(<Button shape="square">Action</Button>);
|
|
61
|
+
const button = screen.getByRole('button');
|
|
62
|
+
expect(button).toBeInTheDocument();
|
|
63
|
+
expect(button).toHaveAttribute('aria-details', 'Action');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('disabled state', () => {
|
|
68
|
+
it('sets aria-disabled when disabled', () => {
|
|
69
|
+
render(<Button disabled>Label</Button>);
|
|
70
|
+
expect(screen.getByRole('button')).toHaveAttribute('aria-disabled', 'true');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('does not fire onClick when disabled', () => {
|
|
74
|
+
const onClick = vi.fn();
|
|
75
|
+
render(
|
|
76
|
+
<Button disabled onClick={onClick}>
|
|
77
|
+
Label
|
|
78
|
+
</Button>,
|
|
79
|
+
);
|
|
80
|
+
fireEvent.click(screen.getByRole('button'));
|
|
81
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('sets aria-disabled to false when not disabled', () => {
|
|
85
|
+
render(<Button>Label</Button>);
|
|
86
|
+
expect(screen.getByRole('button')).toHaveAttribute('aria-disabled', 'false');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('loading state', () => {
|
|
91
|
+
it('does not fire onClick when loading', async () => {
|
|
92
|
+
const onClick = vi.fn();
|
|
93
|
+
const { user } = render(
|
|
94
|
+
<Button loading onClick={onClick}>
|
|
95
|
+
Label
|
|
96
|
+
</Button>,
|
|
97
|
+
);
|
|
98
|
+
await user.click(screen.getByRole('button'));
|
|
99
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('hides children text when loading', () => {
|
|
103
|
+
render(<Button loading>Label</Button>);
|
|
104
|
+
expect(screen.getByRole('button')).not.toHaveTextContent('Label');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('hides enhancers when loading', () => {
|
|
108
|
+
render(
|
|
109
|
+
<Button loading startEnhancer={<span data-testid="start">S</span>}>
|
|
110
|
+
Label
|
|
111
|
+
</Button>,
|
|
112
|
+
);
|
|
113
|
+
expect(screen.queryByTestId('start')).not.toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('onClick', () => {
|
|
118
|
+
it('fires onClick when clicked', async () => {
|
|
119
|
+
const onClick = vi.fn();
|
|
120
|
+
const { user } = render(<Button onClick={onClick}>Label</Button>);
|
|
121
|
+
await user.click(screen.getByRole('button'));
|
|
122
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('href rendering', () => {
|
|
127
|
+
it('renders as an anchor when href is provided', () => {
|
|
128
|
+
render(<Button href="https://example.com">Link</Button>);
|
|
129
|
+
const anchor = screen.getByRole('link', { name: /link/i });
|
|
130
|
+
expect(anchor).toBeInTheDocument();
|
|
131
|
+
expect(anchor).toHaveAttribute('href', 'https://example.com');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('defaults target to _self', () => {
|
|
135
|
+
render(<Button href="https://example.com">Link</Button>);
|
|
136
|
+
expect(screen.getByRole('link')).toHaveAttribute('target', '_self');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('applies hreftarget when provided', () => {
|
|
140
|
+
render(
|
|
141
|
+
<Button href="https://example.com" hreftarget="_blank">
|
|
142
|
+
Link
|
|
143
|
+
</Button>,
|
|
144
|
+
);
|
|
145
|
+
const anchor = screen.getByRole('link');
|
|
146
|
+
expect(anchor).toHaveAttribute('target', '_blank');
|
|
147
|
+
expect(anchor).toHaveAttribute('rel', 'noreferrer');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('does not fire onClick when href is set', async () => {
|
|
151
|
+
const onClick = vi.fn();
|
|
152
|
+
const { user } = render(
|
|
153
|
+
<Button href="https://example.com" onClick={onClick}>
|
|
154
|
+
Link
|
|
155
|
+
</Button>,
|
|
156
|
+
);
|
|
157
|
+
await user.click(screen.getByRole('link'));
|
|
158
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('enhancers', () => {
|
|
163
|
+
it('renders startEnhancer as ReactNode', () => {
|
|
164
|
+
render(<Button startEnhancer={<span data-testid="start-icon">icon</span>}>Label</Button>);
|
|
165
|
+
expect(screen.getByTestId('start-icon')).toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('renders endEnhancer as ReactNode', () => {
|
|
169
|
+
render(<Button endEnhancer={<span data-testid="end-icon">icon</span>}>Label</Button>);
|
|
170
|
+
expect(screen.getByTestId('end-icon')).toBeInTheDocument();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('renders startEnhancer as function', () => {
|
|
174
|
+
render(<Button startEnhancer={({ size }) => <span data-testid="fn-icon">{size}</span>}>Label</Button>);
|
|
175
|
+
expect(screen.getByTestId('fn-icon')).toBeInTheDocument();
|
|
176
|
+
expect(screen.getByTestId('fn-icon')).toHaveTextContent('13');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('passes correct size to enhancer based on button size', () => {
|
|
180
|
+
render(
|
|
181
|
+
<Button size="xs" startEnhancer={({ size }) => <span data-testid="fn-icon">{size}</span>}>
|
|
182
|
+
Label
|
|
183
|
+
</Button>,
|
|
184
|
+
);
|
|
185
|
+
expect(screen.getByTestId('fn-icon')).toHaveTextContent('9');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('notification dot', () => {
|
|
190
|
+
it('renders notification dot when displayNotificationDot is true', () => {
|
|
191
|
+
const { container } = render(<Button displayNotificationDot>Label</Button>);
|
|
192
|
+
const dotWrapper = container.querySelector('.absolute');
|
|
193
|
+
expect(dotWrapper).toBeInTheDocument();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('does not render notification dot by default', () => {
|
|
197
|
+
const { container } = render(<Button>Label</Button>);
|
|
198
|
+
const dotWrapper = container.querySelector('.absolute');
|
|
199
|
+
expect(dotWrapper).not.toBeInTheDocument();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('custom colors and theme', () => {
|
|
204
|
+
it('applies custom colors as CSS variables', () => {
|
|
205
|
+
render(<Button colors={{ primary: '#ff0000', secondary: '#00ff00' }}>Label</Button>);
|
|
206
|
+
const button = screen.getByRole('button');
|
|
207
|
+
expect(button.style.getPropertyValue('--pte-new-colors-buttonFill')).toBe('#ff0000');
|
|
208
|
+
expect(button.style.getPropertyValue('--pte-new-colors-buttonFillHover')).toBe('#00ff00');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('applies theme preset', () => {
|
|
212
|
+
render(<Button theme="negative">Label</Button>);
|
|
213
|
+
const button = screen.getByRole('button');
|
|
214
|
+
expect(button).toHaveAttribute('style');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('className forwarding', () => {
|
|
219
|
+
it('forwards custom className', () => {
|
|
220
|
+
render(<Button className="custom-class">Label</Button>);
|
|
221
|
+
expect(screen.getByRole('button')).toHaveClass('custom-class');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('preserves internal classes when custom className is added', () => {
|
|
225
|
+
render(<Button className="custom-class">Label</Button>);
|
|
226
|
+
const button = screen.getByRole('button');
|
|
227
|
+
expect(button).toHaveClass('button');
|
|
228
|
+
expect(button).toHaveClass('primary');
|
|
229
|
+
expect(button).toHaveClass('pill');
|
|
230
|
+
expect(button).toHaveClass('large');
|
|
231
|
+
expect(button).toHaveClass('custom-class');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('corners', () => {
|
|
236
|
+
it('applies corner preset class', () => {
|
|
237
|
+
render(
|
|
238
|
+
<Button shape="rectangle" corners="sharp">
|
|
239
|
+
Label
|
|
240
|
+
</Button>,
|
|
241
|
+
);
|
|
242
|
+
expect(screen.getByRole('button')).toHaveClass('sharp');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('applies custom corner radius as inline style', () => {
|
|
246
|
+
render(
|
|
247
|
+
<Button shape="rectangle" corners="8px">
|
|
248
|
+
Label
|
|
249
|
+
</Button>,
|
|
250
|
+
);
|
|
251
|
+
expect(screen.getByRole('button').style.borderRadius).toBe('8px');
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('type attribute', () => {
|
|
256
|
+
it('defaults to button type', () => {
|
|
257
|
+
render(<Button>Label</Button>);
|
|
258
|
+
expect(screen.getByRole('button')).toHaveAttribute('type', 'button');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('allows submit type', () => {
|
|
262
|
+
render(<Button type="submit">Label</Button>);
|
|
263
|
+
expect(screen.getByRole('button')).toHaveAttribute('type', 'submit');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { render, screen } from '../../test/render';
|
|
2
|
+
import { Callout } from './Callout';
|
|
3
|
+
|
|
4
|
+
describe('Callout', () => {
|
|
5
|
+
it('renders children text', () => {
|
|
6
|
+
render(<Callout>Important message</Callout>);
|
|
7
|
+
expect(screen.getByText('Important message')).toBeInTheDocument();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('renders non-string children', () => {
|
|
11
|
+
render(
|
|
12
|
+
<Callout>
|
|
13
|
+
<strong>Bold message</strong>
|
|
14
|
+
</Callout>,
|
|
15
|
+
);
|
|
16
|
+
expect(screen.getByText('Bold message')).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('applies default variant class', () => {
|
|
20
|
+
const { container } = render(<Callout>Message</Callout>);
|
|
21
|
+
expect(container.firstElementChild).toHaveClass('content', 'default');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('applies warning variant', () => {
|
|
25
|
+
const { container } = render(<Callout variant="warning">Warning!</Callout>);
|
|
26
|
+
expect(container.firstElementChild).toHaveClass('content', 'warning');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('applies positive variant', () => {
|
|
30
|
+
const { container } = render(<Callout variant="positive">Success!</Callout>);
|
|
31
|
+
expect(container.firstElementChild).toHaveClass('content', 'positive');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('applies negative variant', () => {
|
|
35
|
+
const { container } = render(<Callout variant="negative">Error!</Callout>);
|
|
36
|
+
expect(container.firstElementChild).toHaveClass('content', 'negative');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders default icon when no icon prop is provided', () => {
|
|
40
|
+
const { container } = render(<Callout>With icon</Callout>);
|
|
41
|
+
const iconWrapper = container.querySelector('.icon');
|
|
42
|
+
expect(iconWrapper).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders a custom icon', () => {
|
|
46
|
+
render(<Callout icon={<svg data-testid="custom-icon" />}>With custom icon</Callout>);
|
|
47
|
+
expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('hides the icon when icon is null', () => {
|
|
51
|
+
const { container } = render(<Callout icon={null}>No icon</Callout>);
|
|
52
|
+
const iconWrapper = container.querySelector('.icon');
|
|
53
|
+
expect(iconWrapper).not.toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('forwards className', () => {
|
|
57
|
+
const { container } = render(<Callout className="extra">Message</Callout>);
|
|
58
|
+
expect(container.firstElementChild).toHaveClass('extra');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('forwards HTML div attributes', () => {
|
|
62
|
+
render(
|
|
63
|
+
<Callout data-testid="my-callout" role="alert">
|
|
64
|
+
Alert!
|
|
65
|
+
</Callout>,
|
|
66
|
+
);
|
|
67
|
+
const callout = screen.getByTestId('my-callout');
|
|
68
|
+
expect(callout).toHaveAttribute('role', 'alert');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('combines variant and custom className', () => {
|
|
72
|
+
const { container } = render(
|
|
73
|
+
<Callout variant="negative" className="custom">
|
|
74
|
+
Error
|
|
75
|
+
</Callout>,
|
|
76
|
+
);
|
|
77
|
+
expect(container.firstElementChild).toHaveClass('content', 'negative', 'custom');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { render, screen } from '../../test/render';
|
|
2
|
+
import { Card } from './Card';
|
|
3
|
+
|
|
4
|
+
describe('Card', () => {
|
|
5
|
+
it('renders children', () => {
|
|
6
|
+
render(<Card>Hello world</Card>);
|
|
7
|
+
expect(screen.getByText('Hello world')).toBeInTheDocument();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('renders non-string children without Text wrapper', () => {
|
|
11
|
+
render(
|
|
12
|
+
<Card>
|
|
13
|
+
<button type="button">Click me</button>
|
|
14
|
+
</Card>,
|
|
15
|
+
);
|
|
16
|
+
expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('forwards className', () => {
|
|
20
|
+
const { container } = render(<Card className="custom-class">Content</Card>);
|
|
21
|
+
expect(container.firstElementChild).toHaveClass('custom-class');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('applies raised kind by default', () => {
|
|
25
|
+
const { container } = render(<Card>Content</Card>);
|
|
26
|
+
expect(container.firstElementChild).toHaveClass('container', 'raised');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('applies surface kind', () => {
|
|
30
|
+
const { container } = render(<Card kind="surface">Content</Card>);
|
|
31
|
+
expect(container.firstElementChild).toHaveClass('container', 'surface');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('applies flat kind', () => {
|
|
35
|
+
const { container } = render(<Card kind="flat">Content</Card>);
|
|
36
|
+
expect(container.firstElementChild).toHaveClass('container', 'flat');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('applies default status by default', () => {
|
|
40
|
+
const { container } = render(<Card>Content</Card>);
|
|
41
|
+
expect(container.firstElementChild).toHaveClass('default');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('applies pending status', () => {
|
|
45
|
+
const { container } = render(<Card status="pending">Content</Card>);
|
|
46
|
+
expect(container.firstElementChild).toHaveClass('pending');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('applies text class when children is a string', () => {
|
|
50
|
+
const { container } = render(<Card>Plain text</Card>);
|
|
51
|
+
expect(container.firstElementChild).toHaveClass('text');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does not apply text class when children is not a string', () => {
|
|
55
|
+
const { container } = render(
|
|
56
|
+
<Card>
|
|
57
|
+
<span>Not a string</span>
|
|
58
|
+
</Card>,
|
|
59
|
+
);
|
|
60
|
+
expect(container.firstElementChild).not.toHaveClass('text');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('forwards HTML div attributes', () => {
|
|
64
|
+
render(
|
|
65
|
+
<Card data-testid="my-card" id="card-1">
|
|
66
|
+
Content
|
|
67
|
+
</Card>,
|
|
68
|
+
);
|
|
69
|
+
const card = screen.getByTestId('my-card');
|
|
70
|
+
expect(card).toHaveAttribute('id', 'card-1');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('combines kind and status classes', () => {
|
|
74
|
+
const { container } = render(
|
|
75
|
+
<Card kind="flat" status="pending">
|
|
76
|
+
Content
|
|
77
|
+
</Card>,
|
|
78
|
+
);
|
|
79
|
+
expect(container.firstElementChild).toHaveClass('container', 'flat', 'pending');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { fireEvent } from '@testing-library/react';
|
|
2
|
+
import { render, screen } from '../../test/render';
|
|
3
|
+
import { CardButton } from './CardButton';
|
|
4
|
+
|
|
5
|
+
describe('CardButton', () => {
|
|
6
|
+
it('renders children', () => {
|
|
7
|
+
render(<CardButton>Card Content</CardButton>);
|
|
8
|
+
expect(screen.getByRole('button')).toHaveTextContent('Card Content');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('renders non-string children', () => {
|
|
12
|
+
render(
|
|
13
|
+
<CardButton>
|
|
14
|
+
<div data-testid="child">Complex child</div>
|
|
15
|
+
</CardButton>,
|
|
16
|
+
);
|
|
17
|
+
expect(screen.getByTestId('child')).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('kind variants', () => {
|
|
21
|
+
it.each(['raised', 'surface', 'flat'] as const)('renders %s kind', (kind) => {
|
|
22
|
+
render(<CardButton kind={kind}>Label</CardButton>);
|
|
23
|
+
expect(screen.getByRole('button')).toHaveClass(kind);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('defaults to raised kind', () => {
|
|
27
|
+
render(<CardButton>Label</CardButton>);
|
|
28
|
+
expect(screen.getByRole('button')).toHaveClass('raised');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('status variants', () => {
|
|
33
|
+
it.each(['default', 'pending'] as const)('renders %s status', (status) => {
|
|
34
|
+
render(<CardButton status={status}>Label</CardButton>);
|
|
35
|
+
expect(screen.getByRole('button')).toHaveClass(status);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('defaults to default status', () => {
|
|
39
|
+
render(<CardButton>Label</CardButton>);
|
|
40
|
+
expect(screen.getByRole('button')).toHaveClass('default');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('onClick', () => {
|
|
45
|
+
it('fires onClick when clicked', async () => {
|
|
46
|
+
const onClick = vi.fn();
|
|
47
|
+
const { user } = render(<CardButton onClick={onClick}>Label</CardButton>);
|
|
48
|
+
await user.click(screen.getByRole('button'));
|
|
49
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('disabled state', () => {
|
|
54
|
+
it('sets aria-disabled when disabled', () => {
|
|
55
|
+
render(<CardButton disabled>Label</CardButton>);
|
|
56
|
+
expect(screen.getByRole('button')).toHaveAttribute('aria-disabled', 'true');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('does not fire onClick when disabled', () => {
|
|
60
|
+
const onClick = vi.fn();
|
|
61
|
+
render(
|
|
62
|
+
<CardButton disabled onClick={onClick}>
|
|
63
|
+
Label
|
|
64
|
+
</CardButton>,
|
|
65
|
+
);
|
|
66
|
+
fireEvent.click(screen.getByRole('button'));
|
|
67
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('sets aria-disabled to false when not disabled', () => {
|
|
71
|
+
render(<CardButton>Label</CardButton>);
|
|
72
|
+
expect(screen.getByRole('button')).toHaveAttribute('aria-disabled', 'false');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('className forwarding', () => {
|
|
77
|
+
it('forwards custom className', () => {
|
|
78
|
+
render(<CardButton className="custom-class">Label</CardButton>);
|
|
79
|
+
expect(screen.getByRole('button')).toHaveClass('custom-class');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('preserves internal classes when custom className is added', () => {
|
|
83
|
+
render(<CardButton className="custom-class">Label</CardButton>);
|
|
84
|
+
const button = screen.getByRole('button');
|
|
85
|
+
expect(button).toHaveClass('card');
|
|
86
|
+
expect(button).toHaveClass('raised');
|
|
87
|
+
expect(button).toHaveClass('custom-class');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('href rendering', () => {
|
|
92
|
+
it('renders as an anchor when href is provided', () => {
|
|
93
|
+
render(<CardButton href="https://example.com">Link</CardButton>);
|
|
94
|
+
const anchor = screen.getByRole('link', { name: /link/i });
|
|
95
|
+
expect(anchor).toBeInTheDocument();
|
|
96
|
+
expect(anchor).toHaveAttribute('href', 'https://example.com');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('defaults target to _self', () => {
|
|
100
|
+
render(<CardButton href="https://example.com">Link</CardButton>);
|
|
101
|
+
expect(screen.getByRole('link')).toHaveAttribute('target', '_self');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('applies hreftarget when provided', () => {
|
|
105
|
+
render(
|
|
106
|
+
<CardButton href="https://example.com" hreftarget="_blank">
|
|
107
|
+
Link
|
|
108
|
+
</CardButton>,
|
|
109
|
+
);
|
|
110
|
+
const anchor = screen.getByRole('link');
|
|
111
|
+
expect(anchor).toHaveAttribute('target', '_blank');
|
|
112
|
+
expect(anchor).toHaveAttribute('rel', 'noreferrer');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('does not fire onClick when href is set', async () => {
|
|
116
|
+
const onClick = vi.fn();
|
|
117
|
+
const { user } = render(
|
|
118
|
+
<CardButton href="https://example.com" onClick={onClick}>
|
|
119
|
+
Link
|
|
120
|
+
</CardButton>,
|
|
121
|
+
);
|
|
122
|
+
await user.click(screen.getByRole('link'));
|
|
123
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('text class for string children', () => {
|
|
128
|
+
it('applies text class when children is a string', () => {
|
|
129
|
+
render(<CardButton>Text content</CardButton>);
|
|
130
|
+
expect(screen.getByRole('button')).toHaveClass('text');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('does not apply text class when children is not a string', () => {
|
|
134
|
+
render(
|
|
135
|
+
<CardButton>
|
|
136
|
+
<span>JSX content</span>
|
|
137
|
+
</CardButton>,
|
|
138
|
+
);
|
|
139
|
+
expect(screen.getByRole('button')).not.toHaveClass('text');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('aria-details', () => {
|
|
144
|
+
it('sets aria-details when children is a string', () => {
|
|
145
|
+
render(<CardButton>Description</CardButton>);
|
|
146
|
+
expect(screen.getByRole('button')).toHaveAttribute('aria-details', 'Description');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('does not set aria-details when children is not a string', () => {
|
|
150
|
+
render(
|
|
151
|
+
<CardButton>
|
|
152
|
+
<span>JSX</span>
|
|
153
|
+
</CardButton>,
|
|
154
|
+
);
|
|
155
|
+
expect(screen.getByRole('button')).not.toHaveAttribute('aria-details');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('container', () => {
|
|
160
|
+
it('wraps button in a container div', () => {
|
|
161
|
+
const { container } = render(<CardButton>Label</CardButton>);
|
|
162
|
+
const wrapper = container.firstElementChild;
|
|
163
|
+
expect(wrapper).toHaveClass('container');
|
|
164
|
+
expect(wrapper?.querySelector('button')).toBeInTheDocument();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('type attribute', () => {
|
|
169
|
+
it('defaults to button type', () => {
|
|
170
|
+
render(<CardButton>Label</CardButton>);
|
|
171
|
+
expect(screen.getByRole('button')).toHaveAttribute('type', 'button');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|