paris 0.19.0 → 0.20.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/package.json +17 -2
  3. package/src/stories/accordion/Accordion.test.tsx +140 -0
  4. package/src/stories/accordionselect/AccordionSelect.test.tsx +252 -0
  5. package/src/stories/avatar/Avatar.test.tsx +77 -0
  6. package/src/stories/button/Button.test.tsx +266 -0
  7. package/src/stories/callout/Callout.test.tsx +79 -0
  8. package/src/stories/card/Card.test.tsx +81 -0
  9. package/src/stories/cardbutton/CardButton.test.tsx +174 -0
  10. package/src/stories/checkbox/Checkbox.test.tsx +531 -0
  11. package/src/stories/combobox/Combobox.test.tsx +164 -0
  12. package/src/stories/dialog/Dialog.module.scss +2 -2
  13. package/src/stories/dialog/Dialog.test.tsx +244 -0
  14. package/src/stories/drawer/Drawer.module.scss +2 -2
  15. package/src/stories/drawer/Drawer.test.tsx +259 -0
  16. package/src/stories/field/Field.test.tsx +146 -0
  17. package/src/stories/icon/Icon.test.tsx +59 -0
  18. package/src/stories/informationaltooltip/InformationalTooltip.test.tsx +178 -0
  19. package/src/stories/input/Input.test.tsx +174 -0
  20. package/src/stories/markdown/Markdown.test.tsx +228 -0
  21. package/src/stories/markdowneditor/FixedToolbar.tsx +44 -14
  22. package/src/stories/markdowneditor/LinkPopover.module.scss +1 -1
  23. package/src/stories/markdowneditor/MarkdownEditor.stories.tsx +4 -1
  24. package/src/stories/markdowneditor/MarkdownEditor.test.tsx +115 -0
  25. package/src/stories/markdowneditor/MarkdownEditor.tsx +11 -1
  26. package/src/stories/markdowneditor/MarkdownEditorContext.tsx +3 -0
  27. package/src/stories/markdowneditor/index.ts +1 -0
  28. package/src/stories/menu/Menu.module.scss +1 -1
  29. package/src/stories/menu/Menu.test.tsx +211 -0
  30. package/src/stories/pagination/usePagination.test.ts +259 -0
  31. package/src/stories/popover/Popover.test.tsx +152 -0
  32. package/src/stories/select/Select.module.scss +2 -1
  33. package/src/stories/select/Select.test.tsx +233 -0
  34. package/src/stories/styledlink/StyledLink.test.tsx +59 -0
  35. package/src/stories/table/Table.test.tsx +156 -0
  36. package/src/stories/tabs/Tabs.module.scss +1 -1
  37. package/src/stories/tabs/Tabs.test.tsx +167 -0
  38. package/src/stories/tag/Tag.test.tsx +90 -0
  39. package/src/stories/text/Text.test.tsx +81 -0
  40. package/src/stories/textarea/TextArea.test.tsx +147 -0
  41. package/src/stories/theme/themes.ts +16 -0
  42. package/src/stories/tilt/Tilt.test.tsx +203 -0
  43. package/src/stories/toast/Toast.test.tsx +86 -0
  44. package/src/stories/utility/Dropdown.module.scss +1 -1
  45. package/src/stories/utility/Utility.test.tsx +96 -0
  46. package/src/test/render.tsx +20 -0
  47. package/src/test/setup.ts +32 -0
@@ -0,0 +1,164 @@
1
+ import { useState } from 'react';
2
+ import { render, screen, waitFor } from '../../test/render';
3
+ import type { ComboboxProps, Option } from './Combobox';
4
+ import { Combobox } from './Combobox';
5
+
6
+ const options: Option[] = [
7
+ { id: '1', node: 'Mia Dolan' },
8
+ { id: '2', node: 'Sebastian Wilder' },
9
+ { id: '3', node: 'Amy Brandt' },
10
+ { id: '4', node: 'Laura Wilder' },
11
+ ];
12
+
13
+ function ControlledCombobox(props: Partial<ComboboxProps<Record<string, any>>>) {
14
+ const [selected, setSelected] = useState<Option | null>((props.value as Option | null) ?? null);
15
+ const [inputValue, setInputValue] = useState('');
16
+
17
+ const currentOptions = props.options ?? options;
18
+ const filteredOptions = currentOptions.filter((o) => {
19
+ const text = typeof o.node === 'string' ? o.node : '';
20
+ return text.toLowerCase().includes(inputValue.toLowerCase());
21
+ });
22
+
23
+ return (
24
+ <Combobox
25
+ placeholder="Search..."
26
+ label="Share"
27
+ {...props}
28
+ options={filteredOptions}
29
+ value={selected?.id === null ? { id: null, node: inputValue } : selected}
30
+ onChange={(opt) => {
31
+ setSelected(opt);
32
+ props.onChange?.(opt);
33
+ }}
34
+ onInputChange={(v) => {
35
+ setInputValue(v);
36
+ props.onInputChange?.(v);
37
+ }}
38
+ />
39
+ );
40
+ }
41
+
42
+ describe('Combobox', () => {
43
+ it('renders with placeholder text', () => {
44
+ render(<Combobox options={options} placeholder="Search..." />);
45
+ expect(screen.getByPlaceholderText('Search...')).toBeInTheDocument();
46
+ });
47
+
48
+ it('renders label and description', () => {
49
+ render(<Combobox options={options} label="Share" description="Search for a friend." placeholder="Search..." />);
50
+ expect(screen.getByText('Share')).toBeInTheDocument();
51
+ expect(screen.getByText('Search for a friend.')).toBeInTheDocument();
52
+ });
53
+
54
+ it('shows options when input is focused', async () => {
55
+ const { user } = render(<ControlledCombobox />);
56
+ const input = screen.getByPlaceholderText('Search...');
57
+ await user.click(input);
58
+
59
+ await waitFor(() => {
60
+ expect(screen.getByText('Mia Dolan')).toBeInTheDocument();
61
+ expect(screen.getByText('Sebastian Wilder')).toBeInTheDocument();
62
+ });
63
+ });
64
+
65
+ it('filters options as user types', async () => {
66
+ const { user } = render(<ControlledCombobox />);
67
+ const input = screen.getByPlaceholderText('Search...');
68
+
69
+ await user.click(input);
70
+ await user.type(input, 'wilder');
71
+
72
+ await waitFor(() => {
73
+ expect(screen.getByText('Sebastian Wilder')).toBeInTheDocument();
74
+ expect(screen.getByText('Laura Wilder')).toBeInTheDocument();
75
+ expect(screen.queryByText('Mia Dolan')).not.toBeInTheDocument();
76
+ expect(screen.queryByText('Amy Brandt')).not.toBeInTheDocument();
77
+ });
78
+ });
79
+
80
+ it('selects an option and calls onChange', async () => {
81
+ const handleChange = vi.fn();
82
+ const { user } = render(<ControlledCombobox onChange={handleChange} />);
83
+ const input = screen.getByPlaceholderText('Search...');
84
+
85
+ await user.click(input);
86
+ await waitFor(() => {
87
+ expect(screen.getByText('Amy Brandt')).toBeInTheDocument();
88
+ });
89
+
90
+ await user.click(screen.getByText('Amy Brandt'));
91
+ expect(handleChange).toHaveBeenCalledWith(expect.objectContaining({ id: '3', node: 'Amy Brandt' }));
92
+ });
93
+
94
+ it('calls onInputChange when the user types', async () => {
95
+ const handleInputChange = vi.fn();
96
+ const { user } = render(<ControlledCombobox onInputChange={handleInputChange} />);
97
+ const input = screen.getByPlaceholderText('Search...');
98
+
99
+ await user.click(input);
100
+ await user.type(input, 'test');
101
+
102
+ expect(handleInputChange).toHaveBeenCalled();
103
+ });
104
+
105
+ it('shows clear button when a value is selected', () => {
106
+ const { container } = render(
107
+ <Combobox options={options} value={{ id: '1', node: 'Mia Dolan' }} placeholder="Search..." />,
108
+ );
109
+ // The clear button uses shape="circle" which hides children text,
110
+ // but sets aria-details="Clear"
111
+ const clearButton = container.querySelector('button[aria-details="Clear"]');
112
+ expect(clearButton).toBeInTheDocument();
113
+ });
114
+
115
+ it('clears selection when clear button is clicked', async () => {
116
+ const handleChange = vi.fn();
117
+ const { user, container } = render(
118
+ <ControlledCombobox value={{ id: '1', node: 'Mia Dolan' }} onChange={handleChange} />,
119
+ );
120
+
121
+ const clearButton = container.querySelector('button[aria-details="Clear"]');
122
+ expect(clearButton).toBeInTheDocument();
123
+ await user.click(clearButton!);
124
+
125
+ expect(handleChange).toHaveBeenCalledWith(null);
126
+ });
127
+
128
+ it('hides clear button when hideClearButton is true and node is string', () => {
129
+ render(
130
+ <Combobox
131
+ options={options}
132
+ value={{ id: '1', node: 'Mia Dolan' }}
133
+ hideClearButton
134
+ placeholder="Search..."
135
+ />,
136
+ );
137
+ expect(screen.queryByRole('button', { name: /clear/i })).not.toBeInTheDocument();
138
+ });
139
+
140
+ it('applies disabled status data attribute', () => {
141
+ render(<Combobox options={options} disabled placeholder="Search..." />);
142
+ const input = screen.getByPlaceholderText('Search...');
143
+ expect(input).toHaveAttribute('aria-disabled', 'true');
144
+ expect(input).toHaveAttribute('data-status', 'disabled');
145
+ });
146
+
147
+ it('applies error status data attribute to input', () => {
148
+ render(<Combobox options={options} status="error" placeholder="Search..." />);
149
+ const input = screen.getByPlaceholderText('Search...');
150
+ expect(input).toHaveAttribute('data-status', 'error');
151
+ });
152
+
153
+ it('shows custom value option when allowCustomValue is true', async () => {
154
+ const { user } = render(<ControlledCombobox allowCustomValue customValueString='Add "%v"' />);
155
+ const input = screen.getByPlaceholderText('Search...');
156
+
157
+ await user.click(input);
158
+ await user.type(input, 'New Artist');
159
+
160
+ await waitFor(() => {
161
+ expect(screen.getByText('Add "New Artist"')).toBeInTheDocument();
162
+ });
163
+ });
164
+ });
@@ -5,7 +5,7 @@ $panelAnimationDelay: var(--pte-animations-duration-fast);
5
5
 
6
6
  .root {
7
7
  position: relative;
8
- z-index: 10;
8
+ z-index: var(--pte-new-layers-overlay);
9
9
  user-select: var(--pte-utils-defaultUserSelect);
10
10
  }
11
11
 
@@ -120,7 +120,7 @@ $panelAnimationDelay: var(--pte-animations-duration-fast);
120
120
 
121
121
  .panel {
122
122
  position: relative;
123
- z-index: 10;
123
+ z-index: var(--pte-new-layers-overlay);
124
124
  width: 100%;
125
125
  margin: auto;
126
126
  padding: 16px;
@@ -0,0 +1,244 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { getCloseButton, render, screen, waitFor } from '../../test/render';
3
+ import { Dialog } from './Dialog';
4
+
5
+ describe('Dialog', () => {
6
+ it('renders when isOpen is true', async () => {
7
+ render(
8
+ <Dialog isOpen={true} title="Test Dialog" onClose={vi.fn()}>
9
+ Dialog content
10
+ </Dialog>,
11
+ );
12
+
13
+ await waitFor(() => {
14
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
15
+ });
16
+ });
17
+
18
+ it('does not render when isOpen is false', () => {
19
+ render(
20
+ <Dialog isOpen={false} title="Test Dialog" onClose={vi.fn()}>
21
+ Dialog content
22
+ </Dialog>,
23
+ );
24
+
25
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
26
+ });
27
+
28
+ it('renders children content', async () => {
29
+ render(
30
+ <Dialog isOpen={true} title="Test Dialog" onClose={vi.fn()}>
31
+ <p>Hello from Dialog</p>
32
+ </Dialog>,
33
+ );
34
+
35
+ await waitFor(() => {
36
+ expect(screen.getByText('Hello from Dialog')).toBeInTheDocument();
37
+ });
38
+ });
39
+
40
+ it('renders the title', async () => {
41
+ render(
42
+ <Dialog isOpen={true} title="My Dialog Title" onClose={vi.fn()}>
43
+ Content
44
+ </Dialog>,
45
+ );
46
+
47
+ await waitFor(() => {
48
+ expect(screen.getByText('My Dialog Title')).toBeInTheDocument();
49
+ });
50
+ });
51
+
52
+ it('visually hides title when hideTitle is true but keeps it accessible', async () => {
53
+ render(
54
+ <Dialog isOpen={true} title="Hidden Title" hideTitle={true} onClose={vi.fn()}>
55
+ Content
56
+ </Dialog>,
57
+ );
58
+
59
+ await waitFor(() => {
60
+ expect(screen.getByText('Hidden Title')).toBeInTheDocument();
61
+ });
62
+ });
63
+
64
+ it('renders the close button by default', async () => {
65
+ render(
66
+ <Dialog isOpen={true} title="Test Dialog" onClose={vi.fn()}>
67
+ Content
68
+ </Dialog>,
69
+ );
70
+
71
+ await waitFor(() => {
72
+ expect(getCloseButton()).toBeInTheDocument();
73
+ });
74
+ });
75
+
76
+ it('hides the close button when hideCloseButton is true', async () => {
77
+ render(
78
+ <Dialog isOpen={true} title="Test Dialog" hideCloseButton={true} onClose={vi.fn()}>
79
+ Content
80
+ </Dialog>,
81
+ );
82
+
83
+ await waitFor(() => {
84
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
85
+ });
86
+
87
+ expect(getCloseButton()).not.toBeInTheDocument();
88
+ });
89
+
90
+ it('calls onClose when the close button is clicked', async () => {
91
+ const onClose = vi.fn();
92
+ const { user } = render(
93
+ <Dialog isOpen={true} title="Test Dialog" onClose={onClose}>
94
+ Content
95
+ </Dialog>,
96
+ );
97
+
98
+ await waitFor(() => {
99
+ expect(getCloseButton()).toBeInTheDocument();
100
+ });
101
+
102
+ const closeButton = getCloseButton()!;
103
+ await user.click(closeButton);
104
+
105
+ expect(onClose).toHaveBeenCalledWith(false);
106
+ });
107
+
108
+ it('applies simple appearance by default', async () => {
109
+ render(
110
+ <Dialog isOpen={true} title="Test Dialog" onClose={vi.fn()}>
111
+ Content
112
+ </Dialog>,
113
+ );
114
+
115
+ await waitFor(() => {
116
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
117
+ });
118
+ });
119
+
120
+ it('accepts glass appearance', async () => {
121
+ render(
122
+ <Dialog isOpen={true} title="Test Dialog" appearance="glass" onClose={vi.fn()}>
123
+ Content
124
+ </Dialog>,
125
+ );
126
+
127
+ await waitFor(() => {
128
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
129
+ });
130
+ });
131
+
132
+ it('accepts width presets', async () => {
133
+ const { rerender } = render(
134
+ <Dialog isOpen={true} title="Test Dialog" width="compact" onClose={vi.fn()}>
135
+ Content
136
+ </Dialog>,
137
+ );
138
+
139
+ await waitFor(() => {
140
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
141
+ });
142
+
143
+ rerender(
144
+ <Dialog isOpen={true} title="Test Dialog" width="large" onClose={vi.fn()}>
145
+ Content
146
+ </Dialog>,
147
+ );
148
+
149
+ await waitFor(() => {
150
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
151
+ });
152
+ });
153
+
154
+ it('accepts custom width as CSSLength', async () => {
155
+ render(
156
+ <Dialog isOpen={true} title="Test Dialog" width="500px" onClose={vi.fn()}>
157
+ Content
158
+ </Dialog>,
159
+ );
160
+
161
+ await waitFor(() => {
162
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
163
+ });
164
+ });
165
+
166
+ it('accepts full height', async () => {
167
+ render(
168
+ <Dialog isOpen={true} title="Test Dialog" height="full" onClose={vi.fn()}>
169
+ Content
170
+ </Dialog>,
171
+ );
172
+
173
+ await waitFor(() => {
174
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
175
+ });
176
+ });
177
+
178
+ it('accepts grey overlay style', async () => {
179
+ render(
180
+ <Dialog isOpen={true} title="Test Dialog" overlayStyle="grey" onClose={vi.fn()}>
181
+ Content
182
+ </Dialog>,
183
+ );
184
+
185
+ await waitFor(() => {
186
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
187
+ });
188
+ });
189
+
190
+ it('renders with overrides applied', async () => {
191
+ render(
192
+ <Dialog
193
+ isOpen={true}
194
+ title="Test Dialog"
195
+ onClose={vi.fn()}
196
+ overrides={{
197
+ root: { 'data-testid': 'dialog-root' } as any,
198
+ panel: { 'data-testid': 'dialog-panel' } as any,
199
+ }}
200
+ >
201
+ Content
202
+ </Dialog>,
203
+ );
204
+
205
+ await waitFor(() => {
206
+ expect(screen.getByTestId('dialog-root')).toBeInTheDocument();
207
+ expect(screen.getByTestId('dialog-panel')).toBeInTheDocument();
208
+ });
209
+ });
210
+
211
+ it('renders a ReactNode title', async () => {
212
+ render(
213
+ <Dialog isOpen={true} title={<span data-testid="custom-title">Custom Title</span>} onClose={vi.fn()}>
214
+ Content
215
+ </Dialog>,
216
+ );
217
+
218
+ await waitFor(() => {
219
+ expect(screen.getByTestId('custom-title')).toBeInTheDocument();
220
+ });
221
+ });
222
+
223
+ it('does not render when isOpen transitions from true to false', async () => {
224
+ const { rerender } = render(
225
+ <Dialog isOpen={true} title="Test Dialog" onClose={vi.fn()}>
226
+ Content
227
+ </Dialog>,
228
+ );
229
+
230
+ await waitFor(() => {
231
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
232
+ });
233
+
234
+ rerender(
235
+ <Dialog isOpen={false} title="Test Dialog" onClose={vi.fn()}>
236
+ Content
237
+ </Dialog>,
238
+ );
239
+
240
+ await waitFor(() => {
241
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
242
+ });
243
+ });
244
+ });
@@ -18,7 +18,7 @@ $panelAnimationDelay: var(--pte-animations-duration-fast);
18
18
  position: fixed;
19
19
  inset: 0;
20
20
  overflow: hidden;
21
- z-index: 10;
21
+ z-index: var(--pte-new-layers-overlay);
22
22
  user-select: var(--pte-utils-defaultUserSelect);
23
23
  }
24
24
 
@@ -85,7 +85,7 @@ $panelAnimationDelay: var(--pte-animations-duration-fast);
85
85
 
86
86
  justify-self: flex-end;
87
87
 
88
- z-index: 10;
88
+ z-index: var(--pte-new-layers-overlay);
89
89
 
90
90
  .enter {
91
91
  transition: $duration var(--pte-animations-timing-easeOutQuad);
@@ -0,0 +1,259 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { getCloseButton, render, screen, waitFor } from '../../test/render';
3
+ import { Drawer } from './Drawer';
4
+
5
+ describe('Drawer', () => {
6
+ it('renders when isOpen is true', async () => {
7
+ render(
8
+ <Drawer isOpen={true} title="Test Drawer" onClose={vi.fn()}>
9
+ Drawer content
10
+ </Drawer>,
11
+ );
12
+
13
+ await waitFor(() => {
14
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
15
+ });
16
+ });
17
+
18
+ it('does not render when isOpen is false', () => {
19
+ render(
20
+ <Drawer isOpen={false} title="Test Drawer" onClose={vi.fn()}>
21
+ Drawer content
22
+ </Drawer>,
23
+ );
24
+
25
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
26
+ });
27
+
28
+ it('renders children content', async () => {
29
+ render(
30
+ <Drawer isOpen={true} title="Test Drawer" onClose={vi.fn()}>
31
+ <p>Hello from Drawer</p>
32
+ </Drawer>,
33
+ );
34
+
35
+ await waitFor(() => {
36
+ expect(screen.getByText('Hello from Drawer')).toBeInTheDocument();
37
+ });
38
+ });
39
+
40
+ it('renders the title', async () => {
41
+ render(
42
+ <Drawer isOpen={true} title="My Drawer Title" onClose={vi.fn()}>
43
+ Content
44
+ </Drawer>,
45
+ );
46
+
47
+ await waitFor(() => {
48
+ expect(screen.getByText('My Drawer Title')).toBeInTheDocument();
49
+ });
50
+ });
51
+
52
+ it('visually hides title when hideTitle is true but keeps it accessible', async () => {
53
+ render(
54
+ <Drawer isOpen={true} title="Hidden Title" hideTitle={true} onClose={vi.fn()}>
55
+ Content
56
+ </Drawer>,
57
+ );
58
+
59
+ await waitFor(() => {
60
+ expect(screen.getByText('Hidden Title')).toBeInTheDocument();
61
+ });
62
+ });
63
+
64
+ it('renders the close button by default', async () => {
65
+ render(
66
+ <Drawer isOpen={true} title="Test Drawer" onClose={vi.fn()}>
67
+ Content
68
+ </Drawer>,
69
+ );
70
+
71
+ await waitFor(() => {
72
+ expect(getCloseButton()).toBeInTheDocument();
73
+ });
74
+ });
75
+
76
+ it('hides the close button when hideCloseButton is true', async () => {
77
+ render(
78
+ <Drawer isOpen={true} title="Test Drawer" hideCloseButton={true} onClose={vi.fn()}>
79
+ Content
80
+ </Drawer>,
81
+ );
82
+
83
+ await waitFor(() => {
84
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
85
+ });
86
+
87
+ expect(getCloseButton()).not.toBeInTheDocument();
88
+ });
89
+
90
+ it('calls onClose when the close button is clicked', async () => {
91
+ const onClose = vi.fn();
92
+ const { user } = render(
93
+ <Drawer isOpen={true} title="Test Drawer" onClose={onClose}>
94
+ Content
95
+ </Drawer>,
96
+ );
97
+
98
+ await waitFor(() => {
99
+ expect(getCloseButton()).toBeInTheDocument();
100
+ });
101
+
102
+ const closeButton = getCloseButton()!;
103
+ await user.click(closeButton);
104
+
105
+ expect(onClose).toHaveBeenCalledWith(false);
106
+ });
107
+
108
+ it('renders with from="left"', async () => {
109
+ render(
110
+ <Drawer isOpen={true} title="Test Drawer" from="left" onClose={vi.fn()}>
111
+ Content
112
+ </Drawer>,
113
+ );
114
+
115
+ await waitFor(() => {
116
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
117
+ });
118
+ });
119
+
120
+ it('renders with from="right" (default)', async () => {
121
+ render(
122
+ <Drawer isOpen={true} title="Test Drawer" from="right" onClose={vi.fn()}>
123
+ Content
124
+ </Drawer>,
125
+ );
126
+
127
+ await waitFor(() => {
128
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
129
+ });
130
+ });
131
+
132
+ it('renders with from="top"', async () => {
133
+ render(
134
+ <Drawer isOpen={true} title="Test Drawer" from="top" onClose={vi.fn()}>
135
+ Content
136
+ </Drawer>,
137
+ );
138
+
139
+ await waitFor(() => {
140
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
141
+ });
142
+ });
143
+
144
+ it('renders with from="bottom"', async () => {
145
+ render(
146
+ <Drawer isOpen={true} title="Test Drawer" from="bottom" onClose={vi.fn()}>
147
+ Content
148
+ </Drawer>,
149
+ );
150
+
151
+ await waitFor(() => {
152
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
153
+ });
154
+ });
155
+
156
+ it('renders a bottom panel', async () => {
157
+ render(
158
+ <Drawer
159
+ isOpen={true}
160
+ title="Test Drawer"
161
+ onClose={vi.fn()}
162
+ bottomPanel={<button type="button">Save</button>}
163
+ >
164
+ Content
165
+ </Drawer>,
166
+ );
167
+
168
+ await waitFor(() => {
169
+ expect(screen.getAllByText('Save').length).toBeGreaterThan(0);
170
+ });
171
+ });
172
+
173
+ it('renders additional actions', async () => {
174
+ render(
175
+ <Drawer
176
+ isOpen={true}
177
+ title="Test Drawer"
178
+ onClose={vi.fn()}
179
+ additionalActions={<button type="button">Action</button>}
180
+ >
181
+ Content
182
+ </Drawer>,
183
+ );
184
+
185
+ await waitFor(() => {
186
+ expect(screen.getByText('Action')).toBeInTheDocument();
187
+ });
188
+ });
189
+
190
+ it('renders with blur overlay style', async () => {
191
+ render(
192
+ <Drawer isOpen={true} title="Test Drawer" overlayStyle="blur" onClose={vi.fn()}>
193
+ Content
194
+ </Drawer>,
195
+ );
196
+
197
+ await waitFor(() => {
198
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
199
+ });
200
+ });
201
+
202
+ it('does not render when isOpen transitions from true to false', async () => {
203
+ const { rerender } = render(
204
+ <Drawer isOpen={true} title="Test Drawer" onClose={vi.fn()}>
205
+ Content
206
+ </Drawer>,
207
+ );
208
+
209
+ await waitFor(() => {
210
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
211
+ });
212
+
213
+ rerender(
214
+ <Drawer isOpen={false} title="Test Drawer" onClose={vi.fn()}>
215
+ Content
216
+ </Drawer>,
217
+ );
218
+
219
+ await waitFor(() => {
220
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
221
+ });
222
+ });
223
+
224
+ it('accepts size presets', async () => {
225
+ render(
226
+ <Drawer isOpen={true} title="Test Drawer" size="full" onClose={vi.fn()}>
227
+ Content
228
+ </Drawer>,
229
+ );
230
+
231
+ await waitFor(() => {
232
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
233
+ });
234
+ });
235
+
236
+ it('accepts custom size as CSSLength', async () => {
237
+ render(
238
+ <Drawer isOpen={true} title="Test Drawer" size="500px" onClose={vi.fn()}>
239
+ Content
240
+ </Drawer>,
241
+ );
242
+
243
+ await waitFor(() => {
244
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
245
+ });
246
+ });
247
+
248
+ it('renders a ReactNode title', async () => {
249
+ render(
250
+ <Drawer isOpen={true} title={<span data-testid="custom-title">Custom Title</span>} onClose={vi.fn()}>
251
+ Content
252
+ </Drawer>,
253
+ );
254
+
255
+ await waitFor(() => {
256
+ expect(screen.getByTestId('custom-title')).toBeInTheDocument();
257
+ });
258
+ });
259
+ });