@simplysm/solid 13.0.70 → 13.0.71
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/README.md +1 -1
- package/dist/components/disclosure/Dropdown.d.ts +6 -4
- package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
- package/dist/components/disclosure/Dropdown.js +24 -8
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/disclosure/dialogZIndex.d.ts +2 -0
- package/dist/components/disclosure/dialogZIndex.d.ts.map +1 -1
- package/dist/components/disclosure/dialogZIndex.js +4 -0
- package/dist/components/disclosure/dialogZIndex.js.map +1 -1
- package/dist/components/features/crud-detail/CrudDetail.d.ts.map +1 -1
- package/dist/components/features/crud-detail/CrudDetail.js +16 -7
- package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
- package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheet.js +14 -5
- package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/features/crudRegistry.d.ts +16 -0
- package/dist/components/features/crudRegistry.d.ts.map +1 -0
- package/dist/components/features/crudRegistry.js +37 -0
- package/dist/components/features/crudRegistry.js.map +6 -0
- package/dist/components/features/permission-table/PermissionTable.d.ts.map +1 -1
- package/dist/components/features/permission-table/PermissionTable.js +71 -86
- package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
- package/dist/components/features/shared-data/SharedDataSelect.js +2 -4
- package/dist/components/features/shared-data/SharedDataSelect.js.map +2 -2
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts +2 -4
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts.map +1 -1
- package/dist/components/features/shared-data/SharedDataSelectList.js +11 -46
- package/dist/components/features/shared-data/SharedDataSelectList.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +1 -1
- package/dist/components/form-control/select/Select.js.map +1 -1
- package/dist/helpers/createAppStructure.d.ts.map +1 -1
- package/dist/helpers/createAppStructure.js +3 -2
- package/dist/helpers/createAppStructure.js.map +1 -1
- package/dist/helpers/createHmrSafeContext.d.ts +3 -0
- package/dist/helpers/createHmrSafeContext.d.ts.map +1 -0
- package/dist/helpers/createHmrSafeContext.js +10 -0
- package/dist/helpers/createHmrSafeContext.js.map +6 -0
- package/dist/hooks/createSelectionGroup.d.ts.map +1 -1
- package/dist/hooks/createSelectionGroup.js +3 -2
- package/dist/hooks/createSelectionGroup.js.map +2 -2
- package/package.json +6 -5
- package/src/components/disclosure/Dropdown.tsx +31 -17
- package/src/components/disclosure/dialogZIndex.ts +5 -0
- package/src/components/features/crud-detail/CrudDetail.tsx +16 -5
- package/src/components/features/crud-sheet/CrudSheet.tsx +13 -3
- package/src/components/features/crudRegistry.ts +60 -0
- package/src/components/features/permission-table/PermissionTable.tsx +49 -46
- package/src/components/features/shared-data/SharedDataSelect.tsx +2 -2
- package/src/components/features/shared-data/SharedDataSelectList.tsx +11 -36
- package/src/components/form-control/select/Select.tsx +1 -5
- package/src/helpers/createAppStructure.ts +3 -2
- package/src/helpers/createHmrSafeContext.ts +8 -0
- package/src/hooks/createSelectionGroup.tsx +4 -2
- package/tests/components/data/List.spec.tsx +52 -52
- package/tests/components/data/Pagination.spec.tsx +43 -43
- package/tests/components/data/Table.spec.tsx +4 -4
- package/tests/components/data/kanban/Kanban.selection.spec.tsx +21 -21
- package/tests/components/data/sheet/DataSheet.spec.tsx +50 -50
- package/tests/components/disclosure/Collapse.spec.tsx +24 -24
- package/tests/components/disclosure/Dialog.spec.tsx +33 -33
- package/tests/components/disclosure/DialogProvider.spec.tsx +9 -9
- package/tests/components/disclosure/Dropdown.spec.tsx +134 -14
- package/tests/components/disclosure/Tabs.spec.tsx +21 -21
- package/tests/components/disclosure/dialogZIndex.spec.ts +45 -0
- package/tests/components/display/Alert.spec.tsx +4 -4
- package/tests/components/display/Barcode.spec.tsx +7 -7
- package/tests/components/display/Card.spec.tsx +3 -3
- package/tests/components/display/Link.spec.tsx +5 -5
- package/tests/components/display/Tag.spec.tsx +4 -4
- package/tests/components/features/address/AddressSearch.spec.tsx +3 -3
- package/tests/components/features/crudRegistry.spec.ts +119 -0
- package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +8 -8
- package/tests/components/features/permission-table/PermissionTable.spec.tsx +43 -43
- package/tests/components/features/shared-data/SharedDataSelectList.spec.tsx +2 -17
- package/tests/components/feedback/busy/BusyContainer.spec.tsx +7 -7
- package/tests/components/feedback/notification/NotificationBell.spec.tsx +9 -9
- package/tests/components/feedback/print/Print.spec.tsx +4 -4
- package/tests/components/form-control/Button.spec.tsx +18 -18
- package/tests/components/form-control/checkbox/Checkbox.spec.tsx +20 -20
- package/tests/components/form-control/checkbox/CheckboxGroup.spec.tsx +12 -12
- package/tests/components/form-control/checkbox/Radio.spec.tsx +21 -21
- package/tests/components/form-control/checkbox/RadioGroup.spec.tsx +12 -12
- package/tests/components/form-control/color-picker/ColorPicker.spec.tsx +10 -10
- package/tests/components/form-control/combobox/Combobox.spec.tsx +16 -16
- package/tests/components/form-control/combobox/ComboboxItem.spec.tsx +7 -7
- package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +24 -24
- package/tests/components/form-control/field/DatePicker.spec.tsx +50 -50
- package/tests/components/form-control/field/DateTimePicker.spec.tsx +47 -47
- package/tests/components/form-control/field/NumberInput.spec.tsx +54 -54
- package/tests/components/form-control/field/TextInput.spec.tsx +49 -49
- package/tests/components/form-control/field/Textarea.spec.tsx +33 -33
- package/tests/components/form-control/field/TimePicker.spec.tsx +42 -42
- package/tests/components/form-control/numpad/Numpad.spec.tsx +40 -40
- package/tests/components/form-control/select/Select.spec.tsx +9 -9
- package/tests/components/form-control/select/SelectItem.spec.tsx +10 -10
- package/tests/helpers/createAppStructure.spec.tsx +57 -57
- package/tests/helpers/mergeStyles.spec.ts +31 -31
|
@@ -4,9 +4,9 @@ import { Dialog } from "../../../src/components/disclosure/Dialog";
|
|
|
4
4
|
import { I18nProvider } from "../../../src/providers/i18n/I18nContext";
|
|
5
5
|
import { ConfigProvider } from "../../../src/providers/ConfigContext";
|
|
6
6
|
|
|
7
|
-
describe("Dialog
|
|
7
|
+
describe("Dialog", () => {
|
|
8
8
|
describe("basic rendering", () => {
|
|
9
|
-
it("open=true
|
|
9
|
+
it("renders when open=true", async () => {
|
|
10
10
|
render(() => (
|
|
11
11
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
12
12
|
<Dialog open={true}>
|
|
@@ -21,7 +21,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
it("
|
|
24
|
+
it("is absent from DOM when open=false", () => {
|
|
25
25
|
render(() => (
|
|
26
26
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
27
27
|
<Dialog open={false}>
|
|
@@ -34,7 +34,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
34
34
|
expect(content).toBeNull();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it("data-modal
|
|
37
|
+
it("sets data-modal attribute", async () => {
|
|
38
38
|
render(() => (
|
|
39
39
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
40
40
|
<Dialog open={true}>
|
|
@@ -49,7 +49,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it("Dialog.Header
|
|
52
|
+
it("renders Dialog.Header slot in header", async () => {
|
|
53
53
|
render(() => (
|
|
54
54
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
55
55
|
<Dialog open={true}>
|
|
@@ -66,8 +66,8 @@ describe("Dialog 컴포넌트", () => {
|
|
|
66
66
|
});
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
describe("
|
|
70
|
-
it("Dialog.Header
|
|
69
|
+
describe("header options", () => {
|
|
70
|
+
it("does not render header when Dialog.Header is absent", async () => {
|
|
71
71
|
render(() => (
|
|
72
72
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
73
73
|
<Dialog open={true}>
|
|
@@ -84,7 +84,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
84
84
|
expect(header).toBeNull();
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
it("Dialog.Action
|
|
87
|
+
it("renders Dialog.Action slot beside the close button", async () => {
|
|
88
88
|
render(() => (
|
|
89
89
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
90
90
|
<Dialog open={true}>
|
|
@@ -102,7 +102,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
it("closable={false}
|
|
105
|
+
it("hides close button when closable={false}", async () => {
|
|
106
106
|
render(() => (
|
|
107
107
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
108
108
|
<Dialog open={true} closable={false}>
|
|
@@ -119,8 +119,8 @@ describe("Dialog 컴포넌트", () => {
|
|
|
119
119
|
});
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
describe("
|
|
123
|
-
it("
|
|
122
|
+
describe("close behavior", () => {
|
|
123
|
+
it("calls onOpenChange(false) when close button is clicked", async () => {
|
|
124
124
|
const handleOpenChange = vi.fn();
|
|
125
125
|
render(() => (
|
|
126
126
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -137,7 +137,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
137
137
|
expect(handleOpenChange).toHaveBeenCalledWith(false);
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
-
it("
|
|
140
|
+
it("closes on backdrop click when closeOnBackdrop=true", async () => {
|
|
141
141
|
const handleOpenChange = vi.fn();
|
|
142
142
|
render(() => (
|
|
143
143
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -154,7 +154,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
154
154
|
expect(handleOpenChange).toHaveBeenCalledWith(false);
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
it("
|
|
157
|
+
it("does not close on backdrop click when closeOnBackdrop is not set", async () => {
|
|
158
158
|
const handleOpenChange = vi.fn();
|
|
159
159
|
render(() => (
|
|
160
160
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -171,7 +171,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
171
171
|
expect(handleOpenChange).not.toHaveBeenCalled();
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
it("
|
|
174
|
+
it("closes on Escape key by default", async () => {
|
|
175
175
|
const handleOpenChange = vi.fn();
|
|
176
176
|
render(() => (
|
|
177
177
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -188,7 +188,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
188
188
|
expect(handleOpenChange).toHaveBeenCalledWith(false);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
-
it("
|
|
191
|
+
it("closes on Escape when closeOnEscape=true", async () => {
|
|
192
192
|
const handleOpenChange = vi.fn();
|
|
193
193
|
render(() => (
|
|
194
194
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -205,7 +205,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
205
205
|
expect(handleOpenChange).toHaveBeenCalledWith(false);
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
it("
|
|
208
|
+
it("does not close on Escape when closeOnEscape=false", async () => {
|
|
209
209
|
const handleOpenChange = vi.fn();
|
|
210
210
|
render(() => (
|
|
211
211
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -222,7 +222,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
222
222
|
expect(handleOpenChange).not.toHaveBeenCalled();
|
|
223
223
|
});
|
|
224
224
|
|
|
225
|
-
it("
|
|
225
|
+
it("does not close when canDeactivate returns false", async () => {
|
|
226
226
|
const handleOpenChange = vi.fn();
|
|
227
227
|
render(() => (
|
|
228
228
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -241,7 +241,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
describe("accessibility", () => {
|
|
244
|
-
it("role=dialog
|
|
244
|
+
it("sets role=dialog and aria-modal attributes", async () => {
|
|
245
245
|
render(() => (
|
|
246
246
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
247
247
|
<Dialog open={true}>
|
|
@@ -258,7 +258,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
258
258
|
});
|
|
259
259
|
});
|
|
260
260
|
|
|
261
|
-
it("aria-labelledby
|
|
261
|
+
it("aria-labelledby references the Dialog.Header element", async () => {
|
|
262
262
|
render(() => (
|
|
263
263
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
264
264
|
<Dialog open={true}>
|
|
@@ -277,7 +277,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
277
277
|
});
|
|
278
278
|
});
|
|
279
279
|
|
|
280
|
-
it("Dialog.Header
|
|
280
|
+
it("omits aria-labelledby when Dialog.Header is absent", async () => {
|
|
281
281
|
render(() => (
|
|
282
282
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
283
283
|
<Dialog open={true}>
|
|
@@ -293,7 +293,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
293
293
|
});
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
-
it("
|
|
296
|
+
it("does not set aria-modal in float mode", async () => {
|
|
297
297
|
render(() => (
|
|
298
298
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
299
299
|
<Dialog open={true} float>
|
|
@@ -311,8 +311,8 @@ describe("Dialog 컴포넌트", () => {
|
|
|
311
311
|
});
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
-
describe("float
|
|
315
|
-
it("float=true
|
|
314
|
+
describe("float mode", () => {
|
|
315
|
+
it("has no backdrop when float=true", async () => {
|
|
316
316
|
render(() => (
|
|
317
317
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
318
318
|
<Dialog open={true} float>
|
|
@@ -329,8 +329,8 @@ describe("Dialog 컴포넌트", () => {
|
|
|
329
329
|
});
|
|
330
330
|
});
|
|
331
331
|
|
|
332
|
-
describe("fill
|
|
333
|
-
it("fill
|
|
332
|
+
describe("fill mode", () => {
|
|
333
|
+
it("applies fill style to dialog when fill=true", async () => {
|
|
334
334
|
render(() => (
|
|
335
335
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
336
336
|
<Dialog open={true} fill>
|
|
@@ -348,8 +348,8 @@ describe("Dialog 컴포넌트", () => {
|
|
|
348
348
|
});
|
|
349
349
|
});
|
|
350
350
|
|
|
351
|
-
describe("
|
|
352
|
-
it("width
|
|
351
|
+
describe("size control", () => {
|
|
352
|
+
it("applies width and height", async () => {
|
|
353
353
|
render(() => (
|
|
354
354
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
355
355
|
<Dialog open={true} width={400} height={300}>
|
|
@@ -366,7 +366,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
366
366
|
});
|
|
367
367
|
});
|
|
368
368
|
|
|
369
|
-
it("minWidth
|
|
369
|
+
it("applies minWidth and minHeight", async () => {
|
|
370
370
|
render(() => (
|
|
371
371
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
372
372
|
<Dialog open={true} minWidth={300} minHeight={200}>
|
|
@@ -384,8 +384,8 @@ describe("Dialog 컴포넌트", () => {
|
|
|
384
384
|
});
|
|
385
385
|
});
|
|
386
386
|
|
|
387
|
-
describe("
|
|
388
|
-
it("
|
|
387
|
+
describe("resize", () => {
|
|
388
|
+
it("renders resize bars when resizable=true", async () => {
|
|
389
389
|
render(() => (
|
|
390
390
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
391
391
|
<Dialog open={true} resizable>
|
|
@@ -400,7 +400,7 @@ describe("Dialog 컴포넌트", () => {
|
|
|
400
400
|
});
|
|
401
401
|
});
|
|
402
402
|
|
|
403
|
-
it("resizable=false(
|
|
403
|
+
it("has no resize bars when resizable=false (default)", async () => {
|
|
404
404
|
render(() => (
|
|
405
405
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
406
406
|
<Dialog open={true}>
|
|
@@ -417,8 +417,8 @@ describe("Dialog 컴포넌트", () => {
|
|
|
417
417
|
});
|
|
418
418
|
});
|
|
419
419
|
|
|
420
|
-
describe("
|
|
421
|
-
it("
|
|
420
|
+
describe("animation", () => {
|
|
421
|
+
it("applies transition classes on open", async () => {
|
|
422
422
|
render(() => (
|
|
423
423
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
424
424
|
<Dialog open={true}>
|
|
@@ -4,7 +4,7 @@ import { DialogProvider } from "../../../src/components/disclosure/DialogProvide
|
|
|
4
4
|
import { useDialog } from "../../../src/components/disclosure/DialogContext";
|
|
5
5
|
import { useDialogInstance } from "../../../src/components/disclosure/DialogInstanceContext";
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// dialog content component for testing
|
|
8
8
|
function TestContent() {
|
|
9
9
|
const dialog = useDialogInstance<string>();
|
|
10
10
|
return (
|
|
@@ -20,7 +20,7 @@ function TestContent() {
|
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// test component that calls useDialog
|
|
24
24
|
function TestApp() {
|
|
25
25
|
const dialog = useDialog();
|
|
26
26
|
|
|
@@ -36,7 +36,7 @@ function TestApp() {
|
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// test component that opens without a header
|
|
40
40
|
function TestAppNoHeader() {
|
|
41
41
|
const dialog = useDialog();
|
|
42
42
|
|
|
@@ -53,7 +53,7 @@ function TestAppNoHeader() {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
describe("DialogProvider", () => {
|
|
56
|
-
it("show()
|
|
56
|
+
it("displays dialog via show()", async () => {
|
|
57
57
|
render(() => (
|
|
58
58
|
<DialogProvider>
|
|
59
59
|
<TestApp />
|
|
@@ -67,7 +67,7 @@ describe("DialogProvider", () => {
|
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
it("
|
|
70
|
+
it("closes dialog when close is called via useDialogInstance", async () => {
|
|
71
71
|
render(() => (
|
|
72
72
|
<DialogProvider>
|
|
73
73
|
<TestApp />
|
|
@@ -82,13 +82,13 @@ describe("DialogProvider", () => {
|
|
|
82
82
|
|
|
83
83
|
fireEvent.click(document.querySelector('[data-testid="close-btn"]')!);
|
|
84
84
|
|
|
85
|
-
//
|
|
85
|
+
// dialog content is removed after close animation fallback timer (200ms)
|
|
86
86
|
await waitFor(() => {
|
|
87
87
|
expect(document.querySelector('[data-testid="modal-content"]')).toBeNull();
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
it("close()
|
|
91
|
+
it("closes dialog when close() is called", async () => {
|
|
92
92
|
render(() => (
|
|
93
93
|
<DialogProvider>
|
|
94
94
|
<TestApp />
|
|
@@ -108,7 +108,7 @@ describe("DialogProvider", () => {
|
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
it("
|
|
111
|
+
it("displays dialog header", async () => {
|
|
112
112
|
render(() => (
|
|
113
113
|
<DialogProvider>
|
|
114
114
|
<TestApp />
|
|
@@ -124,7 +124,7 @@ describe("DialogProvider", () => {
|
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
it("header
|
|
127
|
+
it("does not render header when header is not provided", async () => {
|
|
128
128
|
render(() => (
|
|
129
129
|
<DialogProvider>
|
|
130
130
|
<TestAppNoHeader />
|
|
@@ -3,7 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
3
3
|
import { createSignal } from "solid-js";
|
|
4
4
|
import { Dropdown } from "../../../src/components/disclosure/Dropdown";
|
|
5
5
|
|
|
6
|
-
describe("Dropdown
|
|
6
|
+
describe("Dropdown", () => {
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
vi.stubGlobal("innerWidth", 1024);
|
|
9
9
|
vi.stubGlobal("innerHeight", 768);
|
|
@@ -15,8 +15,8 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
15
15
|
vi.unstubAllGlobals();
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
describe("Trigger/Content
|
|
19
|
-
it("
|
|
18
|
+
describe("Trigger/Content structure", () => {
|
|
19
|
+
it("renders Content on Trigger click", async () => {
|
|
20
20
|
render(() => (
|
|
21
21
|
<Dropdown>
|
|
22
22
|
<Dropdown.Trigger>
|
|
@@ -38,7 +38,7 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it("
|
|
41
|
+
it("closes on second Trigger click", () => {
|
|
42
42
|
const [open, setOpen] = createSignal(false);
|
|
43
43
|
const handleOpenChange = vi.fn((v: boolean) => setOpen(v));
|
|
44
44
|
|
|
@@ -61,7 +61,7 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
61
61
|
expect(handleOpenChange).toHaveBeenCalledWith(false);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
it("
|
|
64
|
+
it("ignores Trigger click when disabled", () => {
|
|
65
65
|
const [open, setOpen] = createSignal(false);
|
|
66
66
|
const handleOpenChange = vi.fn((v: boolean) => setOpen(v));
|
|
67
67
|
|
|
@@ -81,8 +81,8 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
81
81
|
});
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
describe("
|
|
85
|
-
it("open prop
|
|
84
|
+
describe("controlled mode", () => {
|
|
85
|
+
it("is controlled by open prop", async () => {
|
|
86
86
|
const [open, setOpen] = createSignal(false);
|
|
87
87
|
|
|
88
88
|
render(() => (
|
|
@@ -106,8 +106,8 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
describe("Context menu (Trigger
|
|
110
|
-
it("position
|
|
109
|
+
describe("Context menu (no Trigger)", () => {
|
|
110
|
+
it("sets position via position prop", async () => {
|
|
111
111
|
render(() => (
|
|
112
112
|
<Dropdown position={{ x: 300, y: 200 }} open={true}>
|
|
113
113
|
<Dropdown.Content>
|
|
@@ -124,8 +124,8 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
124
124
|
});
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
describe("
|
|
128
|
-
it("
|
|
127
|
+
describe("close detection", () => {
|
|
128
|
+
it("closes on outside pointerdown", async () => {
|
|
129
129
|
const handleOpenChange = vi.fn();
|
|
130
130
|
|
|
131
131
|
render(() => (
|
|
@@ -150,7 +150,7 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
150
150
|
expect(handleOpenChange).toHaveBeenCalledWith(false);
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
it("Escape
|
|
153
|
+
it("closes on Escape key", async () => {
|
|
154
154
|
const handleOpenChange = vi.fn();
|
|
155
155
|
|
|
156
156
|
render(() => (
|
|
@@ -174,7 +174,7 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
174
174
|
});
|
|
175
175
|
|
|
176
176
|
describe("maxHeight", () => {
|
|
177
|
-
it("
|
|
177
|
+
it("defaults to 300px", async () => {
|
|
178
178
|
render(() => (
|
|
179
179
|
<Dropdown open={true}>
|
|
180
180
|
<Dropdown.Trigger>
|
|
@@ -192,7 +192,7 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
192
192
|
});
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
it("
|
|
195
|
+
it("applies custom value", async () => {
|
|
196
196
|
render(() => (
|
|
197
197
|
<Dropdown open={true} maxHeight={500}>
|
|
198
198
|
<Dropdown.Trigger>
|
|
@@ -210,4 +210,124 @@ describe("Dropdown 컴포넌트", () => {
|
|
|
210
210
|
});
|
|
211
211
|
});
|
|
212
212
|
});
|
|
213
|
+
|
|
214
|
+
describe("keyboardNav tabbable navigation", () => {
|
|
215
|
+
it("ArrowDown from trigger focuses first tabbable in popup (input, not list item)", async () => {
|
|
216
|
+
const [open, setOpen] = createSignal(true);
|
|
217
|
+
|
|
218
|
+
render(() => (
|
|
219
|
+
<Dropdown open={open()} onOpenChange={setOpen} keyboardNav>
|
|
220
|
+
<Dropdown.Trigger>
|
|
221
|
+
<button data-testid="trigger">trigger</button>
|
|
222
|
+
</Dropdown.Trigger>
|
|
223
|
+
<Dropdown.Content>
|
|
224
|
+
<input data-testid="search" />
|
|
225
|
+
<button data-testid="action-btn" type="button">action</button>
|
|
226
|
+
<button data-list-item data-testid="item1" type="button">item1</button>
|
|
227
|
+
</Dropdown.Content>
|
|
228
|
+
</Dropdown>
|
|
229
|
+
));
|
|
230
|
+
|
|
231
|
+
await waitFor(() => {
|
|
232
|
+
expect(document.querySelector("[data-dropdown]")).not.toBeNull();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const triggerWrapper = document.querySelector("[data-dropdown-trigger]") as HTMLElement;
|
|
236
|
+
triggerWrapper.focus();
|
|
237
|
+
|
|
238
|
+
fireEvent.keyDown(triggerWrapper, { key: "ArrowDown" });
|
|
239
|
+
|
|
240
|
+
const search = document.querySelector('[data-testid="search"]') as HTMLElement;
|
|
241
|
+
expect(document.activeElement).toBe(search);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("ArrowDown from input focuses next tabbable (button)", async () => {
|
|
245
|
+
const [open, setOpen] = createSignal(true);
|
|
246
|
+
|
|
247
|
+
render(() => (
|
|
248
|
+
<Dropdown open={open()} onOpenChange={setOpen} keyboardNav>
|
|
249
|
+
<Dropdown.Trigger>
|
|
250
|
+
<button data-testid="trigger">trigger</button>
|
|
251
|
+
</Dropdown.Trigger>
|
|
252
|
+
<Dropdown.Content>
|
|
253
|
+
<input data-testid="search" />
|
|
254
|
+
<button data-testid="action-btn" type="button">action</button>
|
|
255
|
+
<button data-list-item data-testid="item1" type="button">item1</button>
|
|
256
|
+
</Dropdown.Content>
|
|
257
|
+
</Dropdown>
|
|
258
|
+
));
|
|
259
|
+
|
|
260
|
+
await waitFor(() => {
|
|
261
|
+
expect(document.querySelector("[data-dropdown]")).not.toBeNull();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const search = document.querySelector('[data-testid="search"]') as HTMLElement;
|
|
265
|
+
search.focus();
|
|
266
|
+
|
|
267
|
+
const popup = document.querySelector("[data-dropdown]") as HTMLElement;
|
|
268
|
+
fireEvent.keyDown(popup, { key: "ArrowDown" });
|
|
269
|
+
|
|
270
|
+
const actionBtn = document.querySelector('[data-testid="action-btn"]') as HTMLElement;
|
|
271
|
+
expect(document.activeElement).toBe(actionBtn);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("ArrowUp from input focuses trigger (no prev tabbable, dir=down)", async () => {
|
|
275
|
+
const [open, setOpen] = createSignal(true);
|
|
276
|
+
|
|
277
|
+
render(() => (
|
|
278
|
+
<Dropdown open={open()} onOpenChange={setOpen} keyboardNav>
|
|
279
|
+
<Dropdown.Trigger>
|
|
280
|
+
<button data-testid="trigger">trigger</button>
|
|
281
|
+
</Dropdown.Trigger>
|
|
282
|
+
<Dropdown.Content>
|
|
283
|
+
<input data-testid="search" />
|
|
284
|
+
<button data-list-item data-testid="item1" type="button">item1</button>
|
|
285
|
+
</Dropdown.Content>
|
|
286
|
+
</Dropdown>
|
|
287
|
+
));
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
expect(document.querySelector("[data-dropdown]")).not.toBeNull();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const search = document.querySelector('[data-testid="search"]') as HTMLElement;
|
|
294
|
+
search.focus();
|
|
295
|
+
|
|
296
|
+
const popup = document.querySelector("[data-dropdown]") as HTMLElement;
|
|
297
|
+
fireEvent.keyDown(popup, { key: "ArrowUp" });
|
|
298
|
+
|
|
299
|
+
const triggerWrapper = document.querySelector("[data-dropdown-trigger]") as HTMLElement;
|
|
300
|
+
expect(document.activeElement).toBe(triggerWrapper);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("ArrowUp from action button focuses prev tabbable (input)", async () => {
|
|
304
|
+
const [open, setOpen] = createSignal(true);
|
|
305
|
+
|
|
306
|
+
render(() => (
|
|
307
|
+
<Dropdown open={open()} onOpenChange={setOpen} keyboardNav>
|
|
308
|
+
<Dropdown.Trigger>
|
|
309
|
+
<button data-testid="trigger">trigger</button>
|
|
310
|
+
</Dropdown.Trigger>
|
|
311
|
+
<Dropdown.Content>
|
|
312
|
+
<input data-testid="search" />
|
|
313
|
+
<button data-testid="action-btn" type="button">action</button>
|
|
314
|
+
<button data-list-item data-testid="item1" type="button">item1</button>
|
|
315
|
+
</Dropdown.Content>
|
|
316
|
+
</Dropdown>
|
|
317
|
+
));
|
|
318
|
+
|
|
319
|
+
await waitFor(() => {
|
|
320
|
+
expect(document.querySelector("[data-dropdown]")).not.toBeNull();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const actionBtn = document.querySelector('[data-testid="action-btn"]') as HTMLElement;
|
|
324
|
+
actionBtn.focus();
|
|
325
|
+
|
|
326
|
+
const popup = document.querySelector("[data-dropdown]") as HTMLElement;
|
|
327
|
+
fireEvent.keyDown(popup, { key: "ArrowUp" });
|
|
328
|
+
|
|
329
|
+
const search = document.querySelector('[data-testid="search"]') as HTMLElement;
|
|
330
|
+
expect(document.activeElement).toBe(search);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
213
333
|
});
|