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.
- package/CHANGELOG.md +10 -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/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 +1 -1
- package/src/stories/menu/Menu.test.tsx +211 -0
- 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 +2 -1
- package/src/stories/select/Select.test.tsx +233 -0
- 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,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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
+
});
|