uikit-react-public 0.27.2 → 0.29.1
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/dist/components/BreadcrumbsNew/BaseBreadcrumbsItem.d.ts +12 -0
- package/dist/components/BreadcrumbsNew/Breadcrumbs.d.ts +18 -0
- package/dist/components/BreadcrumbsNew/BreadcrumbsCurrent.d.ts +8 -0
- package/dist/components/BreadcrumbsNew/BreadcrumbsLink.d.ts +7 -0
- package/dist/components/BreadcrumbsNew/__tests__/Breadcrumbs.test.d.ts +1 -0
- package/dist/components/BreadcrumbsNew/index.d.ts +2 -0
- package/dist/components/Chip/Chip.d.ts +11 -0
- package/dist/components/Chip/Chip.stories.d.ts +25 -0
- package/dist/components/Chip/__tests__/Chip.test.d.ts +1 -0
- package/dist/components/Chip/index.d.ts +2 -0
- package/dist/components/Link/BaseLink.d.ts +3 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/index.js +5965 -5634
- package/lib/components/BaseCheckbox/BaseCheckbox.tsx +1 -5
- package/lib/components/BreadcrumbsNew/BaseBreadcrumbsItem.tsx +69 -0
- package/lib/components/BreadcrumbsNew/Breadcrumbs.tsx +118 -0
- package/lib/components/BreadcrumbsNew/BreadcrumbsCurrent.tsx +54 -0
- package/lib/components/BreadcrumbsNew/BreadcrumbsLink.tsx +67 -0
- package/lib/components/BreadcrumbsNew/__tests__/Breadcrumbs.test.tsx +53 -0
- package/lib/components/BreadcrumbsNew/index.ts +8 -0
- package/lib/components/Checkbox/__tests__/__snapshots__/Checkbox.test.tsx.snap +4 -4
- package/lib/components/Chip/Chip.stories.tsx +37 -0
- package/lib/components/Chip/Chip.tsx +191 -0
- package/lib/components/Chip/__tests__/Chip.test.tsx +287 -0
- package/lib/components/Chip/__tests__/__snapshots__/Chip.test.tsx.snap +255 -0
- package/lib/components/Chip/index.ts +2 -0
- package/lib/components/Link/BaseLink.tsx +40 -2
- package/lib/components/Link/__tests__/link.test.tsx +58 -1
- package/lib/components/Table/subcomponents/Cell/__tests__/__snapshots__/Cell.test.tsx.snap +1 -1
- package/lib/components/Table/subcomponents/HeadCell/__tests__/__snapshots__/HeadCell.test.tsx.snap +1 -1
- package/lib/components/index.ts +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { createRef } from 'react';
|
|
2
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
6
|
+
import Chip from '../Chip';
|
|
7
|
+
|
|
8
|
+
describe('Chip', () => {
|
|
9
|
+
test('snapshot: no props', () => {
|
|
10
|
+
const renderResult = render(
|
|
11
|
+
<ThemeContextProvider>
|
|
12
|
+
<Chip label='Student' />
|
|
13
|
+
</ThemeContextProvider>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('snapshot: checked prop', () => {
|
|
20
|
+
const renderResult = render(
|
|
21
|
+
<ThemeContextProvider>
|
|
22
|
+
<Chip
|
|
23
|
+
label='Student'
|
|
24
|
+
checked
|
|
25
|
+
onChange={() => {}}
|
|
26
|
+
/>
|
|
27
|
+
</ThemeContextProvider>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('snapshot: disabled prop', () => {
|
|
34
|
+
const renderResult = render(
|
|
35
|
+
<ThemeContextProvider>
|
|
36
|
+
<Chip
|
|
37
|
+
label='Student'
|
|
38
|
+
disabled
|
|
39
|
+
/>
|
|
40
|
+
</ThemeContextProvider>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('snapshot: disabled and checked props', () => {
|
|
47
|
+
const renderResult = render(
|
|
48
|
+
<ThemeContextProvider>
|
|
49
|
+
<Chip
|
|
50
|
+
label='Student'
|
|
51
|
+
checked
|
|
52
|
+
disabled
|
|
53
|
+
/>
|
|
54
|
+
</ThemeContextProvider>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('snapshot: testId prop', () => {
|
|
61
|
+
const renderResult = render(
|
|
62
|
+
<ThemeContextProvider>
|
|
63
|
+
<Chip
|
|
64
|
+
label='Student'
|
|
65
|
+
testId='test123'
|
|
66
|
+
/>
|
|
67
|
+
</ThemeContextProvider>
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(renderResult.container.firstChild).toMatchSnapshot();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('checked prop: controlled state does not toggle without rerender', async () => {
|
|
74
|
+
const user = userEvent.setup();
|
|
75
|
+
const handleChange = vi.fn();
|
|
76
|
+
|
|
77
|
+
render(
|
|
78
|
+
<ThemeContextProvider>
|
|
79
|
+
<Chip
|
|
80
|
+
label='Student'
|
|
81
|
+
checked
|
|
82
|
+
onChange={handleChange}
|
|
83
|
+
/>
|
|
84
|
+
</ThemeContextProvider>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
|
88
|
+
expect(checkbox.checked).toBe(true);
|
|
89
|
+
|
|
90
|
+
await user.click(checkbox);
|
|
91
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
92
|
+
expect(checkbox.checked).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('defaultChecked prop: initializes as checked in uncontrolled mode', () => {
|
|
96
|
+
render(
|
|
97
|
+
<ThemeContextProvider>
|
|
98
|
+
<Chip
|
|
99
|
+
label='Student'
|
|
100
|
+
defaultChecked
|
|
101
|
+
/>
|
|
102
|
+
</ThemeContextProvider>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
|
106
|
+
expect(checkbox.checked).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('test ID: default', () => {
|
|
110
|
+
render(
|
|
111
|
+
<ThemeContextProvider>
|
|
112
|
+
<Chip label='Student' />
|
|
113
|
+
</ThemeContextProvider>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const chip = screen.getByTestId('ucl-uikit-chip');
|
|
117
|
+
expect(chip).toBeDefined();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('test ID: custom', () => {
|
|
121
|
+
render(
|
|
122
|
+
<ThemeContextProvider>
|
|
123
|
+
<Chip
|
|
124
|
+
label='Student'
|
|
125
|
+
testId='custom-test-id'
|
|
126
|
+
/>
|
|
127
|
+
</ThemeContextProvider>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const chip = screen.getByTestId('custom-test-id');
|
|
131
|
+
expect(chip).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('click: uncontrolled via checkbox', async () => {
|
|
135
|
+
const user = userEvent.setup();
|
|
136
|
+
const handleChange = vi.fn();
|
|
137
|
+
|
|
138
|
+
render(
|
|
139
|
+
<ThemeContextProvider>
|
|
140
|
+
<Chip
|
|
141
|
+
label='Student'
|
|
142
|
+
onChange={handleChange}
|
|
143
|
+
/>
|
|
144
|
+
</ThemeContextProvider>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
|
148
|
+
expect(checkbox.checked).toBe(false);
|
|
149
|
+
|
|
150
|
+
await user.click(checkbox);
|
|
151
|
+
expect(checkbox.checked).toBe(true);
|
|
152
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('click: label toggles checkbox', async () => {
|
|
156
|
+
const user = userEvent.setup();
|
|
157
|
+
const handleChange = vi.fn();
|
|
158
|
+
|
|
159
|
+
render(
|
|
160
|
+
<ThemeContextProvider>
|
|
161
|
+
<Chip
|
|
162
|
+
label='Student'
|
|
163
|
+
onChange={handleChange}
|
|
164
|
+
/>
|
|
165
|
+
</ThemeContextProvider>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
|
169
|
+
const label = screen.getByText('Student').closest('label');
|
|
170
|
+
expect(checkbox.checked).toBe(false);
|
|
171
|
+
expect(label).not.toBeNull();
|
|
172
|
+
|
|
173
|
+
await user.click(label!);
|
|
174
|
+
expect(checkbox.checked).toBe(true);
|
|
175
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('click: disabled', async () => {
|
|
179
|
+
const user = userEvent.setup();
|
|
180
|
+
const handleChange = vi.fn();
|
|
181
|
+
|
|
182
|
+
render(
|
|
183
|
+
<ThemeContextProvider>
|
|
184
|
+
<Chip
|
|
185
|
+
label='Student'
|
|
186
|
+
disabled
|
|
187
|
+
onChange={handleChange}
|
|
188
|
+
/>
|
|
189
|
+
</ThemeContextProvider>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
|
193
|
+
const label = screen.getByText('Student').closest('label');
|
|
194
|
+
expect(checkbox.checked).toBe(false);
|
|
195
|
+
expect(label).not.toBeNull();
|
|
196
|
+
|
|
197
|
+
await user.click(label!);
|
|
198
|
+
expect(checkbox.checked).toBe(false);
|
|
199
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('click: disabled and checked remains checked', async () => {
|
|
203
|
+
const user = userEvent.setup();
|
|
204
|
+
const handleChange = vi.fn();
|
|
205
|
+
|
|
206
|
+
render(
|
|
207
|
+
<ThemeContextProvider>
|
|
208
|
+
<Chip
|
|
209
|
+
label='Student'
|
|
210
|
+
checked
|
|
211
|
+
disabled
|
|
212
|
+
onChange={handleChange}
|
|
213
|
+
/>
|
|
214
|
+
</ThemeContextProvider>
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
|
|
218
|
+
const label = screen.getByText('Student').closest('label');
|
|
219
|
+
expect(checkbox.checked).toBe(true);
|
|
220
|
+
expect(label).not.toBeNull();
|
|
221
|
+
|
|
222
|
+
await user.click(label!);
|
|
223
|
+
expect(checkbox.checked).toBe(true);
|
|
224
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('uses BaseCheckbox', () => {
|
|
228
|
+
render(
|
|
229
|
+
<ThemeContextProvider>
|
|
230
|
+
<Chip label='Student' />
|
|
231
|
+
</ThemeContextProvider>
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const root = screen.getByTestId('ucl-uikit-chip__root');
|
|
235
|
+
|
|
236
|
+
expect(root).toHaveClass('ucl-uikit-base-checkbox');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('ariaLabel prop is applied to the input as aria-label', () => {
|
|
240
|
+
render(
|
|
241
|
+
<ThemeContextProvider>
|
|
242
|
+
<Chip
|
|
243
|
+
label='Student'
|
|
244
|
+
ariaLabel='Select student'
|
|
245
|
+
/>
|
|
246
|
+
</ThemeContextProvider>
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
|
250
|
+
'aria-label',
|
|
251
|
+
'Select student'
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('aria-label passed via spread props overrides ariaLabel', () => {
|
|
256
|
+
render(
|
|
257
|
+
<ThemeContextProvider>
|
|
258
|
+
<Chip
|
|
259
|
+
label='Student'
|
|
260
|
+
ariaLabel='Default label'
|
|
261
|
+
aria-label='Override label'
|
|
262
|
+
/>
|
|
263
|
+
</ThemeContextProvider>
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
|
267
|
+
'aria-label',
|
|
268
|
+
'Override label'
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('ref is forwarded to the underlying input element', () => {
|
|
273
|
+
const ref = createRef<HTMLInputElement>();
|
|
274
|
+
|
|
275
|
+
render(
|
|
276
|
+
<ThemeContextProvider>
|
|
277
|
+
<Chip
|
|
278
|
+
label='Student'
|
|
279
|
+
ref={ref}
|
|
280
|
+
/>
|
|
281
|
+
</ThemeContextProvider>
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(ref.current).toBeInstanceOf(HTMLInputElement);
|
|
285
|
+
expect(ref.current).toBe(screen.getByRole('checkbox'));
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Chip > snapshot: checked prop 1`] = `
|
|
4
|
+
<label
|
|
5
|
+
class="ucl-uikit-chip css-1j50qe4"
|
|
6
|
+
>
|
|
7
|
+
<span
|
|
8
|
+
class="ucl-uikit-base-checkbox css-1mmc74g"
|
|
9
|
+
data-testid="ucl-uikit-chip__root"
|
|
10
|
+
>
|
|
11
|
+
<input
|
|
12
|
+
checked=""
|
|
13
|
+
class="css-1bjd9qx"
|
|
14
|
+
data-testid="ucl-uikit-chip"
|
|
15
|
+
type="checkbox"
|
|
16
|
+
/>
|
|
17
|
+
<span
|
|
18
|
+
aria-hidden="true"
|
|
19
|
+
class="css-g3adpt"
|
|
20
|
+
>
|
|
21
|
+
<span
|
|
22
|
+
class="css-vv99xy"
|
|
23
|
+
>
|
|
24
|
+
<svg
|
|
25
|
+
class="ucl-uikit-icon css-1fx6elc"
|
|
26
|
+
data-testid="ucl-uikit-icon"
|
|
27
|
+
fill="none"
|
|
28
|
+
focusable="false"
|
|
29
|
+
height="16"
|
|
30
|
+
stroke="currentColor"
|
|
31
|
+
stroke-linecap="round"
|
|
32
|
+
stroke-linejoin="round"
|
|
33
|
+
stroke-width="2"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
width="16"
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
>
|
|
38
|
+
<path
|
|
39
|
+
d="M20 6 9 17l-5-5"
|
|
40
|
+
/>
|
|
41
|
+
</svg>
|
|
42
|
+
<span
|
|
43
|
+
class="ucl-uikit-text css-3kj57w"
|
|
44
|
+
data-testid="ucl-uikit-text"
|
|
45
|
+
>
|
|
46
|
+
Student
|
|
47
|
+
</span>
|
|
48
|
+
</span>
|
|
49
|
+
</span>
|
|
50
|
+
</span>
|
|
51
|
+
</label>
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
exports[`Chip > snapshot: disabled and checked props 1`] = `
|
|
55
|
+
<label
|
|
56
|
+
class="ucl-uikit-chip css-1j50qe4"
|
|
57
|
+
>
|
|
58
|
+
<span
|
|
59
|
+
class="ucl-uikit-base-checkbox css-lp1eko"
|
|
60
|
+
data-testid="ucl-uikit-chip__root"
|
|
61
|
+
>
|
|
62
|
+
<input
|
|
63
|
+
checked=""
|
|
64
|
+
class="css-1bjd9qx"
|
|
65
|
+
data-testid="ucl-uikit-chip"
|
|
66
|
+
disabled=""
|
|
67
|
+
type="checkbox"
|
|
68
|
+
/>
|
|
69
|
+
<span
|
|
70
|
+
aria-hidden="true"
|
|
71
|
+
class="css-g3adpt"
|
|
72
|
+
>
|
|
73
|
+
<span
|
|
74
|
+
class="css-vv99xy"
|
|
75
|
+
>
|
|
76
|
+
<svg
|
|
77
|
+
class="ucl-uikit-icon css-1fx6elc"
|
|
78
|
+
data-testid="ucl-uikit-icon"
|
|
79
|
+
fill="none"
|
|
80
|
+
focusable="false"
|
|
81
|
+
height="16"
|
|
82
|
+
stroke="currentColor"
|
|
83
|
+
stroke-linecap="round"
|
|
84
|
+
stroke-linejoin="round"
|
|
85
|
+
stroke-width="2"
|
|
86
|
+
viewBox="0 0 24 24"
|
|
87
|
+
width="16"
|
|
88
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
89
|
+
>
|
|
90
|
+
<path
|
|
91
|
+
d="M20 6 9 17l-5-5"
|
|
92
|
+
/>
|
|
93
|
+
</svg>
|
|
94
|
+
<span
|
|
95
|
+
class="ucl-uikit-text css-3kj57w"
|
|
96
|
+
data-testid="ucl-uikit-text"
|
|
97
|
+
>
|
|
98
|
+
Student
|
|
99
|
+
</span>
|
|
100
|
+
</span>
|
|
101
|
+
</span>
|
|
102
|
+
</span>
|
|
103
|
+
</label>
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
exports[`Chip > snapshot: disabled prop 1`] = `
|
|
107
|
+
<label
|
|
108
|
+
class="ucl-uikit-chip css-1j50qe4"
|
|
109
|
+
>
|
|
110
|
+
<span
|
|
111
|
+
class="ucl-uikit-base-checkbox css-lp1eko"
|
|
112
|
+
data-testid="ucl-uikit-chip__root"
|
|
113
|
+
>
|
|
114
|
+
<input
|
|
115
|
+
class="css-1bjd9qx"
|
|
116
|
+
data-testid="ucl-uikit-chip"
|
|
117
|
+
disabled=""
|
|
118
|
+
type="checkbox"
|
|
119
|
+
/>
|
|
120
|
+
<span
|
|
121
|
+
aria-hidden="true"
|
|
122
|
+
class="css-g3adpt"
|
|
123
|
+
>
|
|
124
|
+
<span
|
|
125
|
+
class="css-vv99xy"
|
|
126
|
+
>
|
|
127
|
+
<svg
|
|
128
|
+
class="ucl-uikit-icon css-10yks9o"
|
|
129
|
+
data-testid="ucl-uikit-icon"
|
|
130
|
+
fill="none"
|
|
131
|
+
focusable="false"
|
|
132
|
+
height="16"
|
|
133
|
+
stroke="currentColor"
|
|
134
|
+
stroke-linecap="round"
|
|
135
|
+
stroke-linejoin="round"
|
|
136
|
+
stroke-width="2"
|
|
137
|
+
viewBox="0 0 24 24"
|
|
138
|
+
width="16"
|
|
139
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
140
|
+
>
|
|
141
|
+
<path
|
|
142
|
+
d="M20 6 9 17l-5-5"
|
|
143
|
+
/>
|
|
144
|
+
</svg>
|
|
145
|
+
<span
|
|
146
|
+
class="ucl-uikit-text css-10p3qy4"
|
|
147
|
+
data-testid="ucl-uikit-text"
|
|
148
|
+
>
|
|
149
|
+
Student
|
|
150
|
+
</span>
|
|
151
|
+
</span>
|
|
152
|
+
</span>
|
|
153
|
+
</span>
|
|
154
|
+
</label>
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
exports[`Chip > snapshot: no props 1`] = `
|
|
158
|
+
<label
|
|
159
|
+
class="ucl-uikit-chip css-1j50qe4"
|
|
160
|
+
>
|
|
161
|
+
<span
|
|
162
|
+
class="ucl-uikit-base-checkbox css-1mmc74g"
|
|
163
|
+
data-testid="ucl-uikit-chip__root"
|
|
164
|
+
>
|
|
165
|
+
<input
|
|
166
|
+
class="css-1bjd9qx"
|
|
167
|
+
data-testid="ucl-uikit-chip"
|
|
168
|
+
type="checkbox"
|
|
169
|
+
/>
|
|
170
|
+
<span
|
|
171
|
+
aria-hidden="true"
|
|
172
|
+
class="css-g3adpt"
|
|
173
|
+
>
|
|
174
|
+
<span
|
|
175
|
+
class="css-vv99xy"
|
|
176
|
+
>
|
|
177
|
+
<svg
|
|
178
|
+
class="ucl-uikit-icon css-10yks9o"
|
|
179
|
+
data-testid="ucl-uikit-icon"
|
|
180
|
+
fill="none"
|
|
181
|
+
focusable="false"
|
|
182
|
+
height="16"
|
|
183
|
+
stroke="currentColor"
|
|
184
|
+
stroke-linecap="round"
|
|
185
|
+
stroke-linejoin="round"
|
|
186
|
+
stroke-width="2"
|
|
187
|
+
viewBox="0 0 24 24"
|
|
188
|
+
width="16"
|
|
189
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
190
|
+
>
|
|
191
|
+
<path
|
|
192
|
+
d="M20 6 9 17l-5-5"
|
|
193
|
+
/>
|
|
194
|
+
</svg>
|
|
195
|
+
<span
|
|
196
|
+
class="ucl-uikit-text css-10p3qy4"
|
|
197
|
+
data-testid="ucl-uikit-text"
|
|
198
|
+
>
|
|
199
|
+
Student
|
|
200
|
+
</span>
|
|
201
|
+
</span>
|
|
202
|
+
</span>
|
|
203
|
+
</span>
|
|
204
|
+
</label>
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
exports[`Chip > snapshot: testId prop 1`] = `
|
|
208
|
+
<label
|
|
209
|
+
class="ucl-uikit-chip css-1j50qe4"
|
|
210
|
+
>
|
|
211
|
+
<span
|
|
212
|
+
class="ucl-uikit-base-checkbox css-1mmc74g"
|
|
213
|
+
data-testid="test123__root"
|
|
214
|
+
>
|
|
215
|
+
<input
|
|
216
|
+
class="css-1bjd9qx"
|
|
217
|
+
data-testid="test123"
|
|
218
|
+
type="checkbox"
|
|
219
|
+
/>
|
|
220
|
+
<span
|
|
221
|
+
aria-hidden="true"
|
|
222
|
+
class="css-g3adpt"
|
|
223
|
+
>
|
|
224
|
+
<span
|
|
225
|
+
class="css-vv99xy"
|
|
226
|
+
>
|
|
227
|
+
<svg
|
|
228
|
+
class="ucl-uikit-icon css-10yks9o"
|
|
229
|
+
data-testid="ucl-uikit-icon"
|
|
230
|
+
fill="none"
|
|
231
|
+
focusable="false"
|
|
232
|
+
height="16"
|
|
233
|
+
stroke="currentColor"
|
|
234
|
+
stroke-linecap="round"
|
|
235
|
+
stroke-linejoin="round"
|
|
236
|
+
stroke-width="2"
|
|
237
|
+
viewBox="0 0 24 24"
|
|
238
|
+
width="16"
|
|
239
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
240
|
+
>
|
|
241
|
+
<path
|
|
242
|
+
d="M20 6 9 17l-5-5"
|
|
243
|
+
/>
|
|
244
|
+
</svg>
|
|
245
|
+
<span
|
|
246
|
+
class="ucl-uikit-text css-10p3qy4"
|
|
247
|
+
data-testid="ucl-uikit-text"
|
|
248
|
+
>
|
|
249
|
+
Student
|
|
250
|
+
</span>
|
|
251
|
+
</span>
|
|
252
|
+
</span>
|
|
253
|
+
</span>
|
|
254
|
+
</label>
|
|
255
|
+
`;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import React, {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
2
4
|
ComponentPropsWithoutRef,
|
|
3
5
|
ElementType,
|
|
4
6
|
forwardRef,
|
|
7
|
+
isValidElement,
|
|
8
|
+
ReactElement,
|
|
5
9
|
useCallback,
|
|
6
10
|
} from 'react';
|
|
7
11
|
import { css, cx } from '@emotion/css';
|
|
@@ -30,13 +34,16 @@ type BaseLinkOwnProps = {
|
|
|
30
34
|
testId?: string;
|
|
31
35
|
className?: string;
|
|
32
36
|
children?: React.ReactNode;
|
|
37
|
+
/** @deprecated Prefer `asChild` with a routing link child. */
|
|
33
38
|
to?: string;
|
|
39
|
+
asChild?: boolean;
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
type PolymorphicRef<C extends ElementType> =
|
|
37
43
|
React.ComponentPropsWithRef<C>['ref'];
|
|
38
44
|
|
|
39
45
|
export type BaseLinkProps<C extends ElementType = 'a'> = BaseLinkOwnProps & {
|
|
46
|
+
/** @deprecated Prefer `asChild` with a custom link child. */
|
|
40
47
|
component?: C;
|
|
41
48
|
} & Omit<
|
|
42
49
|
ComponentPropsWithoutRef<C>,
|
|
@@ -58,6 +65,7 @@ const BaseLink = forwardRef(function BaseLinkInner(
|
|
|
58
65
|
disabled = false,
|
|
59
66
|
component,
|
|
60
67
|
to,
|
|
68
|
+
asChild = false,
|
|
61
69
|
...props
|
|
62
70
|
}: BaseLinkProps<ElementType>,
|
|
63
71
|
ref: React.Ref<Element>
|
|
@@ -110,6 +118,31 @@ const BaseLink = forwardRef(function BaseLinkInner(
|
|
|
110
118
|
className
|
|
111
119
|
);
|
|
112
120
|
|
|
121
|
+
if (asChild) {
|
|
122
|
+
const child = Children.only(children) as ReactElement<
|
|
123
|
+
Record<string, unknown> & {
|
|
124
|
+
className?: string;
|
|
125
|
+
onClick?: React.MouseEventHandler;
|
|
126
|
+
tabIndex?: number;
|
|
127
|
+
}
|
|
128
|
+
>;
|
|
129
|
+
|
|
130
|
+
if (isValidElement(child)) {
|
|
131
|
+
return cloneElement(child, {
|
|
132
|
+
...props,
|
|
133
|
+
ref,
|
|
134
|
+
className: cx(style, child.props.className),
|
|
135
|
+
'data-testid': testId,
|
|
136
|
+
'aria-disabled': disabled || undefined,
|
|
137
|
+
tabIndex: disabled ? -1 : (props.tabIndex ?? child.props.tabIndex),
|
|
138
|
+
...(to ? { to } : {}),
|
|
139
|
+
onClick: disabled
|
|
140
|
+
? disabledHandleClick
|
|
141
|
+
: (props.onClick ?? child.props.onClick),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
113
146
|
return (
|
|
114
147
|
<Component
|
|
115
148
|
ref={ref}
|
|
@@ -117,9 +150,14 @@ const BaseLink = forwardRef(function BaseLinkInner(
|
|
|
117
150
|
data-testid={testId}
|
|
118
151
|
aria-disabled={disabled || undefined}
|
|
119
152
|
tabIndex={disabled ? -1 : props.tabIndex}
|
|
120
|
-
href={isAnchor ? (disabled ? undefined : (href ?? to)) : undefined}
|
|
121
153
|
onClick={disabled ? disabledHandleClick : onClick}
|
|
122
|
-
{...(isAnchor
|
|
154
|
+
{...(isAnchor
|
|
155
|
+
? { href: disabled ? undefined : (href ?? to) }
|
|
156
|
+
: to
|
|
157
|
+
? { to }
|
|
158
|
+
: href
|
|
159
|
+
? { href }
|
|
160
|
+
: {})}
|
|
123
161
|
{...rest}
|
|
124
162
|
>
|
|
125
163
|
{children}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest';
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import Link from '../Link';
|
|
4
4
|
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
@@ -57,4 +57,61 @@ describe('Link', () => {
|
|
|
57
57
|
expect(link.textContent).toBe('linktext');
|
|
58
58
|
expect(link.href).toBe('http://localhost:3000/testlink');
|
|
59
59
|
});
|
|
60
|
+
|
|
61
|
+
test('renders a custom link child with asChild', () => {
|
|
62
|
+
const handleClick = vi.fn((event: React.MouseEvent) =>
|
|
63
|
+
event.preventDefault()
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
render(
|
|
67
|
+
<ThemeContextProvider>
|
|
68
|
+
<Link asChild>
|
|
69
|
+
<a
|
|
70
|
+
href='/custom'
|
|
71
|
+
onClick={handleClick}
|
|
72
|
+
>
|
|
73
|
+
Custom link
|
|
74
|
+
</a>
|
|
75
|
+
</Link>
|
|
76
|
+
</ThemeContextProvider>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const link = screen.getByRole('link', { name: 'Custom link' });
|
|
80
|
+
link.click();
|
|
81
|
+
|
|
82
|
+
expect(link).toHaveAttribute('href', '/custom');
|
|
83
|
+
expect(link).toHaveAttribute('data-testid', 'ucl-uikit-link');
|
|
84
|
+
expect(handleClick).toHaveBeenCalledOnce();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('continues to support the deprecated component and to props', () => {
|
|
88
|
+
const CustomLink = ({
|
|
89
|
+
to,
|
|
90
|
+
children,
|
|
91
|
+
...props
|
|
92
|
+
}: React.AnchorHTMLAttributes<HTMLAnchorElement> & { to?: string }) => (
|
|
93
|
+
<a
|
|
94
|
+
href={to}
|
|
95
|
+
{...props}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</a>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
render(
|
|
102
|
+
<ThemeContextProvider>
|
|
103
|
+
<Link
|
|
104
|
+
component={CustomLink}
|
|
105
|
+
to='/legacy'
|
|
106
|
+
>
|
|
107
|
+
Legacy link
|
|
108
|
+
</Link>
|
|
109
|
+
</ThemeContextProvider>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(screen.getByRole('link', { name: 'Legacy link' })).toHaveAttribute(
|
|
113
|
+
'href',
|
|
114
|
+
'/legacy'
|
|
115
|
+
);
|
|
116
|
+
});
|
|
60
117
|
});
|