@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
|
@@ -3,9 +3,9 @@ import { describe, it, expect, vi } from "vitest";
|
|
|
3
3
|
import { createSignal } from "solid-js";
|
|
4
4
|
import { Tabs } from "../../../src/components/disclosure/Tabs";
|
|
5
5
|
|
|
6
|
-
describe("Tabs
|
|
6
|
+
describe("Tabs", () => {
|
|
7
7
|
describe("basic rendering", () => {
|
|
8
|
-
it("tablist role
|
|
8
|
+
it("renders with tablist role", () => {
|
|
9
9
|
const { getByRole } = render(() => (
|
|
10
10
|
<Tabs>
|
|
11
11
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -14,7 +14,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
14
14
|
expect(getByRole("tablist")).toBeTruthy();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it("Tabs.Tab
|
|
17
|
+
it("renders Tabs.Tab with tab role", () => {
|
|
18
18
|
const { getByRole } = render(() => (
|
|
19
19
|
<Tabs>
|
|
20
20
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -23,7 +23,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
23
23
|
expect(getByRole("tab")).toBeTruthy();
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it("children
|
|
26
|
+
it("renders children", () => {
|
|
27
27
|
const { getByText } = render(() => (
|
|
28
28
|
<Tabs>
|
|
29
29
|
<Tabs.Tab value="a">탭 A</Tabs.Tab>
|
|
@@ -35,8 +35,8 @@ describe("Tabs 컴포넌트", () => {
|
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
describe("
|
|
39
|
-
it("
|
|
38
|
+
describe("selection behavior", () => {
|
|
39
|
+
it("sets aria-selected to true on click", () => {
|
|
40
40
|
const { getAllByRole } = render(() => (
|
|
41
41
|
<Tabs>
|
|
42
42
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -50,7 +50,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
50
50
|
expect(tabs[1].getAttribute("aria-selected")).toBe("false");
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
it("
|
|
53
|
+
it("changes selection on different tab click", () => {
|
|
54
54
|
const { getAllByRole } = render(() => (
|
|
55
55
|
<Tabs value="a">
|
|
56
56
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -64,7 +64,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
64
64
|
expect(tabs[0].getAttribute("aria-selected")).toBe("false");
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
it("disabled
|
|
67
|
+
it("does not select disabled tab on click", () => {
|
|
68
68
|
const handleChange = vi.fn();
|
|
69
69
|
const { getAllByRole } = render(() => (
|
|
70
70
|
<Tabs onValueChange={handleChange}>
|
|
@@ -80,8 +80,8 @@ describe("Tabs 컴포넌트", () => {
|
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
describe("
|
|
84
|
-
it("Space
|
|
83
|
+
describe("keyboard behavior", () => {
|
|
84
|
+
it("selects tab with Space key", () => {
|
|
85
85
|
const { getAllByRole } = render(() => (
|
|
86
86
|
<Tabs>
|
|
87
87
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -93,7 +93,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
93
93
|
expect(getAllByRole("tab")[0].getAttribute("aria-selected")).toBe("true");
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
it("Enter
|
|
96
|
+
it("selects tab with Enter key", () => {
|
|
97
97
|
const { getAllByRole } = render(() => (
|
|
98
98
|
<Tabs>
|
|
99
99
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -106,8 +106,8 @@ describe("Tabs 컴포넌트", () => {
|
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
describe("controlled
|
|
110
|
-
it("value prop
|
|
109
|
+
describe("controlled pattern", () => {
|
|
110
|
+
it("reflects value prop as selected state", () => {
|
|
111
111
|
const { getAllByRole } = render(() => (
|
|
112
112
|
<Tabs value="b">
|
|
113
113
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -119,7 +119,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
119
119
|
expect(getAllByRole("tab")[1].getAttribute("aria-selected")).toBe("true");
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
it("onValueChange
|
|
122
|
+
it("calls onValueChange on click", () => {
|
|
123
123
|
const handleChange = vi.fn();
|
|
124
124
|
const { getAllByRole } = render(() => (
|
|
125
125
|
<Tabs value="a" onValueChange={handleChange}>
|
|
@@ -132,7 +132,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
132
132
|
expect(handleChange).toHaveBeenCalledWith("b");
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
it("
|
|
135
|
+
it("updates when external state changes", () => {
|
|
136
136
|
const [value, setValue] = createSignal("a");
|
|
137
137
|
const { getAllByRole } = render(() => (
|
|
138
138
|
<Tabs value={value()} onValueChange={setValue}>
|
|
@@ -149,8 +149,8 @@ describe("Tabs 컴포넌트", () => {
|
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
-
describe("
|
|
153
|
-
it("
|
|
152
|
+
describe("size", () => {
|
|
153
|
+
it("applies different styles per size prop", () => {
|
|
154
154
|
const { getAllByRole: getDefault } = render(() => (
|
|
155
155
|
<Tabs>
|
|
156
156
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -167,7 +167,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
describe("accessibility", () => {
|
|
170
|
-
it("
|
|
170
|
+
it("sets aria-disabled on disabled tab", () => {
|
|
171
171
|
const { getAllByRole } = render(() => (
|
|
172
172
|
<Tabs>
|
|
173
173
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -180,7 +180,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
180
180
|
expect(getAllByRole("tab")[1].getAttribute("aria-disabled")).toBe("true");
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
-
it("
|
|
183
|
+
it("sets tabIndex to -1 on disabled tab", () => {
|
|
184
184
|
const { getAllByRole } = render(() => (
|
|
185
185
|
<Tabs>
|
|
186
186
|
<Tabs.Tab value="a">A</Tabs.Tab>
|
|
@@ -195,7 +195,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
describe("class merging", () => {
|
|
198
|
-
it("
|
|
198
|
+
it("merges custom class on Tabs", () => {
|
|
199
199
|
const { getByRole } = render(() => (
|
|
200
200
|
// eslint-disable-next-line tailwindcss/no-custom-classname
|
|
201
201
|
<Tabs class="my-tab-class">
|
|
@@ -205,7 +205,7 @@ describe("Tabs 컴포넌트", () => {
|
|
|
205
205
|
expect(getByRole("tablist").classList.contains("my-tab-class")).toBe(true);
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
it("
|
|
208
|
+
it("merges custom class on Tabs.Tab", () => {
|
|
209
209
|
const { getByRole } = render(() => (
|
|
210
210
|
<Tabs>
|
|
211
211
|
{/* eslint-disable-next-line tailwindcss/no-custom-classname */}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
registerDialog,
|
|
4
|
+
unregisterDialog,
|
|
5
|
+
getTopmostDialog,
|
|
6
|
+
} from "../../../src/components/disclosure/dialogZIndex";
|
|
7
|
+
|
|
8
|
+
describe("getTopmostDialog", () => {
|
|
9
|
+
let el1: HTMLElement;
|
|
10
|
+
let el2: HTMLElement;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
el1 = document.createElement("div");
|
|
14
|
+
el2 = document.createElement("div");
|
|
15
|
+
// Clean up any leftover registrations
|
|
16
|
+
unregisterDialog(el1);
|
|
17
|
+
unregisterDialog(el2);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns null when no dialogs are registered", () => {
|
|
21
|
+
expect(getTopmostDialog()).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns the only registered dialog", () => {
|
|
25
|
+
registerDialog(el1);
|
|
26
|
+
expect(getTopmostDialog()).toBe(el1);
|
|
27
|
+
unregisterDialog(el1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("returns the last registered dialog when multiple are open", () => {
|
|
31
|
+
registerDialog(el1);
|
|
32
|
+
registerDialog(el2);
|
|
33
|
+
expect(getTopmostDialog()).toBe(el2);
|
|
34
|
+
unregisterDialog(el2);
|
|
35
|
+
unregisterDialog(el1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns previous dialog after topmost is unregistered", () => {
|
|
39
|
+
registerDialog(el1);
|
|
40
|
+
registerDialog(el2);
|
|
41
|
+
unregisterDialog(el2);
|
|
42
|
+
expect(getTopmostDialog()).toBe(el1);
|
|
43
|
+
unregisterDialog(el1);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -2,9 +2,9 @@ import { render } from "@solidjs/testing-library";
|
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
import { Alert } from "../../../src/components/display/Alert";
|
|
4
4
|
|
|
5
|
-
describe("Alert
|
|
5
|
+
describe("Alert", () => {
|
|
6
6
|
describe("basic rendering", () => {
|
|
7
|
-
it("children
|
|
7
|
+
it("renders children", () => {
|
|
8
8
|
const { getByText } = render(() => <Alert>This is a note</Alert>);
|
|
9
9
|
expect(getByText("This is a note")).toBeTruthy();
|
|
10
10
|
});
|
|
@@ -16,8 +16,8 @@ describe("Alert 컴포넌트", () => {
|
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
describe("theme
|
|
20
|
-
it("
|
|
19
|
+
describe("theme prop", () => {
|
|
20
|
+
it("applies different styles per theme", () => {
|
|
21
21
|
const { container: defaultContainer } = render(() => <Alert>Content</Alert>);
|
|
22
22
|
const { container: themedContainer } = render(() => <Alert theme="danger">Content</Alert>);
|
|
23
23
|
|
|
@@ -3,28 +3,28 @@ import { describe, it, expect } from "vitest";
|
|
|
3
3
|
import { createSignal } from "solid-js";
|
|
4
4
|
import { Barcode } from "../../../src/components/display/Barcode";
|
|
5
5
|
|
|
6
|
-
describe("Barcode
|
|
6
|
+
describe("Barcode", () => {
|
|
7
7
|
describe("basic rendering", () => {
|
|
8
|
-
it("data-barcode
|
|
8
|
+
it("renders with data-barcode attribute", () => {
|
|
9
9
|
const { container } = render(() => <Barcode type="qrcode" value="test" />);
|
|
10
10
|
expect(container.querySelector("[data-barcode]")).toBeTruthy();
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
it("value
|
|
13
|
+
it("renders SVG when value is provided", () => {
|
|
14
14
|
const { container } = render(() => <Barcode type="qrcode" value="hello" />);
|
|
15
15
|
const el = container.querySelector("[data-barcode]")!;
|
|
16
16
|
expect(el.querySelector("svg")).toBeTruthy();
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
it("value
|
|
19
|
+
it("renders empty when value is absent", () => {
|
|
20
20
|
const { container } = render(() => <Barcode type="qrcode" />);
|
|
21
21
|
const el = container.querySelector("[data-barcode]")!;
|
|
22
22
|
expect(el.innerHTML).toBe("");
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
describe("
|
|
27
|
-
it("
|
|
26
|
+
describe("reactivity", () => {
|
|
27
|
+
it("updates SVG when value changes", () => {
|
|
28
28
|
const [value, setValue] = createSignal("first");
|
|
29
29
|
const { container } = render(() => <Barcode type="qrcode" value={value()} />);
|
|
30
30
|
const el = container.querySelector("[data-barcode]")!;
|
|
@@ -38,7 +38,7 @@ describe("Barcode 컴포넌트", () => {
|
|
|
38
38
|
expect(firstSvg).not.toBe(secondSvg);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it("value
|
|
41
|
+
it("removes SVG when value changes to empty string", () => {
|
|
42
42
|
const [value, setValue] = createSignal("hello");
|
|
43
43
|
const { container } = render(() => <Barcode type="qrcode" value={value()} />);
|
|
44
44
|
const el = container.querySelector("[data-barcode]")!;
|
|
@@ -2,9 +2,9 @@ import { render } from "@solidjs/testing-library";
|
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
import { Card } from "../../../src/components/display/Card";
|
|
4
4
|
|
|
5
|
-
describe("Card
|
|
5
|
+
describe("Card", () => {
|
|
6
6
|
describe("basic rendering", () => {
|
|
7
|
-
it("children
|
|
7
|
+
it("renders children", () => {
|
|
8
8
|
const { getByText } = render(() => <Card>Card Content</Card>);
|
|
9
9
|
expect(getByText("Card Content")).toBeTruthy();
|
|
10
10
|
});
|
|
@@ -32,7 +32,7 @@ describe("Card 컴포넌트", () => {
|
|
|
32
32
|
expect(card.getAttribute("data-testid")).toBe("test-card");
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it("id
|
|
35
|
+
it("passes id attribute", () => {
|
|
36
36
|
const { container } = render(() => <Card id="my-card">Content</Card>);
|
|
37
37
|
const card = container.firstChild as HTMLElement;
|
|
38
38
|
expect(card.id).toBe("my-card");
|
|
@@ -2,9 +2,9 @@ import { render } from "@solidjs/testing-library";
|
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
import { Link } from "../../../src/components/display/Link";
|
|
4
4
|
|
|
5
|
-
describe("Link
|
|
5
|
+
describe("Link", () => {
|
|
6
6
|
describe("basic rendering", () => {
|
|
7
|
-
it("children
|
|
7
|
+
it("renders children", () => {
|
|
8
8
|
const { getByText } = render(() => <Link href="https://example.com">링크</Link>);
|
|
9
9
|
expect(getByText("링크")).toBeTruthy();
|
|
10
10
|
});
|
|
@@ -16,14 +16,14 @@ describe("Link 컴포넌트", () => {
|
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
describe("href
|
|
20
|
-
it("href
|
|
19
|
+
describe("href prop", () => {
|
|
20
|
+
it("passes href to anchor element", () => {
|
|
21
21
|
const { container } = render(() => <Link href="https://example.com">링크</Link>);
|
|
22
22
|
const link = container.firstChild as HTMLAnchorElement;
|
|
23
23
|
expect(link.getAttribute("href")).toBe("https://example.com");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it("target
|
|
26
|
+
it("passes target attribute", () => {
|
|
27
27
|
const { container } = render(() => (
|
|
28
28
|
<Link href="https://example.com" target="_blank">
|
|
29
29
|
링크
|
|
@@ -2,9 +2,9 @@ import { render } from "@solidjs/testing-library";
|
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
3
|
import { Tag } from "../../../src/components/display/Tag";
|
|
4
4
|
|
|
5
|
-
describe("Tag
|
|
5
|
+
describe("Tag", () => {
|
|
6
6
|
describe("basic rendering", () => {
|
|
7
|
-
it("children
|
|
7
|
+
it("renders children", () => {
|
|
8
8
|
const { getByText } = render(() => <Tag>New</Tag>);
|
|
9
9
|
expect(getByText("New")).toBeTruthy();
|
|
10
10
|
});
|
|
@@ -16,8 +16,8 @@ describe("Tag 컴포넌트", () => {
|
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
describe("theme
|
|
20
|
-
it("
|
|
19
|
+
describe("theme prop", () => {
|
|
20
|
+
it("applies different styles per theme", () => {
|
|
21
21
|
const { container: defaultContainer } = render(() => <Tag>Tag</Tag>);
|
|
22
22
|
const { container: themedContainer } = render(() => <Tag theme="danger">Tag</Tag>);
|
|
23
23
|
|
|
@@ -22,7 +22,7 @@ function TestApp() {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
describe("AddressSearchContent", () => {
|
|
25
|
-
it("
|
|
25
|
+
it("mounts and renders Daum Postcode widget inside content area", async () => {
|
|
26
26
|
const { getByTestId } = render(() => (
|
|
27
27
|
<DialogProvider>
|
|
28
28
|
<TestApp />
|
|
@@ -31,12 +31,12 @@ describe("AddressSearchContent", () => {
|
|
|
31
31
|
|
|
32
32
|
getByTestId("open-btn").click();
|
|
33
33
|
|
|
34
|
-
// Daum Postcode
|
|
34
|
+
// wait for Daum Postcode script load + widget embed
|
|
35
35
|
await waitFor(
|
|
36
36
|
() => {
|
|
37
37
|
const content = document.querySelector("[data-address-content]");
|
|
38
38
|
expect(content).not.toBeNull();
|
|
39
|
-
// Daum Postcode
|
|
39
|
+
// once the Daum Postcode widget is embedded, content has child elements
|
|
40
40
|
expect(content!.children.length).toBeGreaterThan(0);
|
|
41
41
|
},
|
|
42
42
|
{ timeout: 10000 },
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
registerCrud,
|
|
4
|
+
unregisterCrud,
|
|
5
|
+
activateCrud,
|
|
6
|
+
isActiveCrud,
|
|
7
|
+
} from "../../../src/components/features/crudRegistry";
|
|
8
|
+
|
|
9
|
+
// Mock dialogZIndex — we need to control getTopmostDialog
|
|
10
|
+
vi.mock("../../../src/components/disclosure/dialogZIndex", () => ({
|
|
11
|
+
getTopmostDialog: vi.fn(() => null),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
import { getTopmostDialog } from "../../../src/components/disclosure/dialogZIndex";
|
|
15
|
+
const mockGetTopmostDialog = vi.mocked(getTopmostDialog);
|
|
16
|
+
|
|
17
|
+
describe("crudRegistry", () => {
|
|
18
|
+
let form1: HTMLFormElement;
|
|
19
|
+
let form2: HTMLFormElement;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
form1 = document.createElement("form");
|
|
23
|
+
form2 = document.createElement("form");
|
|
24
|
+
// Clean state
|
|
25
|
+
unregisterCrud("a");
|
|
26
|
+
unregisterCrud("b");
|
|
27
|
+
mockGetTopmostDialog.mockReturnValue(null);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("single registered crud is active", () => {
|
|
31
|
+
registerCrud("a", form1);
|
|
32
|
+
expect(isActiveCrud("a")).toBe(true);
|
|
33
|
+
unregisterCrud("a");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("last registered crud is active (auto-activate on register)", () => {
|
|
37
|
+
registerCrud("a", form1);
|
|
38
|
+
registerCrud("b", form2);
|
|
39
|
+
expect(isActiveCrud("a")).toBe(false);
|
|
40
|
+
expect(isActiveCrud("b")).toBe(true);
|
|
41
|
+
unregisterCrud("a");
|
|
42
|
+
unregisterCrud("b");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("activateCrud changes which crud is active", () => {
|
|
46
|
+
registerCrud("a", form1);
|
|
47
|
+
registerCrud("b", form2);
|
|
48
|
+
activateCrud("a");
|
|
49
|
+
expect(isActiveCrud("a")).toBe(true);
|
|
50
|
+
expect(isActiveCrud("b")).toBe(false);
|
|
51
|
+
unregisterCrud("a");
|
|
52
|
+
unregisterCrud("b");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("unregistered crud is not active", () => {
|
|
56
|
+
registerCrud("a", form1);
|
|
57
|
+
unregisterCrud("a");
|
|
58
|
+
expect(isActiveCrud("a")).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns false for unknown id", () => {
|
|
62
|
+
expect(isActiveCrud("unknown")).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("Dialog boundary: only crud inside topmost Dialog is active", () => {
|
|
66
|
+
const dialogEl = document.createElement("div");
|
|
67
|
+
dialogEl.appendChild(form2);
|
|
68
|
+
mockGetTopmostDialog.mockReturnValue(dialogEl);
|
|
69
|
+
|
|
70
|
+
registerCrud("a", form1); // outside dialog
|
|
71
|
+
registerCrud("b", form2); // inside dialog
|
|
72
|
+
|
|
73
|
+
// b is inside the topmost dialog, so b should be active
|
|
74
|
+
expect(isActiveCrud("b")).toBe(true);
|
|
75
|
+
// a is outside, so a should not be active even if it was registered
|
|
76
|
+
expect(isActiveCrud("a")).toBe(false);
|
|
77
|
+
|
|
78
|
+
unregisterCrud("a");
|
|
79
|
+
unregisterCrud("b");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("Dialog boundary: no crud inside topmost Dialog means none active", () => {
|
|
83
|
+
const dialogEl = document.createElement("div");
|
|
84
|
+
// form1 and form2 are NOT inside dialogEl
|
|
85
|
+
mockGetTopmostDialog.mockReturnValue(dialogEl);
|
|
86
|
+
|
|
87
|
+
registerCrud("a", form1);
|
|
88
|
+
registerCrud("b", form2);
|
|
89
|
+
|
|
90
|
+
expect(isActiveCrud("a")).toBe(false);
|
|
91
|
+
expect(isActiveCrud("b")).toBe(false);
|
|
92
|
+
|
|
93
|
+
unregisterCrud("a");
|
|
94
|
+
unregisterCrud("b");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("Dialog boundary: most recently activated crud inside dialog wins", () => {
|
|
98
|
+
const dialogEl = document.createElement("div");
|
|
99
|
+
const form3 = document.createElement("form");
|
|
100
|
+
dialogEl.appendChild(form2);
|
|
101
|
+
dialogEl.appendChild(form3);
|
|
102
|
+
mockGetTopmostDialog.mockReturnValue(dialogEl);
|
|
103
|
+
|
|
104
|
+
registerCrud("a", form1); // outside
|
|
105
|
+
registerCrud("b", form2); // inside
|
|
106
|
+
registerCrud("c", form3); // inside, last registered
|
|
107
|
+
|
|
108
|
+
expect(isActiveCrud("c")).toBe(true);
|
|
109
|
+
expect(isActiveCrud("b")).toBe(false);
|
|
110
|
+
|
|
111
|
+
activateCrud("b");
|
|
112
|
+
expect(isActiveCrud("b")).toBe(true);
|
|
113
|
+
expect(isActiveCrud("c")).toBe(false);
|
|
114
|
+
|
|
115
|
+
unregisterCrud("a");
|
|
116
|
+
unregisterCrud("b");
|
|
117
|
+
unregisterCrud("c");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -8,7 +8,7 @@ import { Dialog } from "../../../../src/components/disclosure/Dialog";
|
|
|
8
8
|
import { I18nProvider } from "../../../../src/providers/i18n/I18nContext";
|
|
9
9
|
import { ConfigProvider } from "../../../../src/providers/ConfigContext";
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// item type for tests
|
|
12
12
|
interface TestItem {
|
|
13
13
|
id: number;
|
|
14
14
|
name: string;
|
|
@@ -20,7 +20,7 @@ const testItems: TestItem[] = [
|
|
|
20
20
|
{ id: 3, name: "Cherry" },
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
-
// load
|
|
23
|
+
// load function (fetch items by key)
|
|
24
24
|
function createTestLoad() {
|
|
25
25
|
const loadFn = vi.fn((keys: number[]) => {
|
|
26
26
|
return testItems.filter((item) => keys.includes(item.id));
|
|
@@ -28,7 +28,7 @@ function createTestLoad() {
|
|
|
28
28
|
return loadFn;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// modal component for tests (returns specific keys)
|
|
32
32
|
function TestModal(selectedKeys: number[]): () => JSX.Element {
|
|
33
33
|
return () => {
|
|
34
34
|
const instance = useDialogInstance<DataSelectModalResult<number>>();
|
|
@@ -48,7 +48,7 @@ function TestModal(selectedKeys: number[]): () => JSX.Element {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// helper to wrap with DialogProvider
|
|
52
52
|
function renderWithDialog(ui: () => JSX.Element) {
|
|
53
53
|
return render(() => (
|
|
54
54
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -74,7 +74,7 @@ describe("DataSelectButton", () => {
|
|
|
74
74
|
|
|
75
75
|
const trigger = container.querySelector("[data-data-select-button]");
|
|
76
76
|
expect(trigger).not.toBeNull();
|
|
77
|
-
// load
|
|
77
|
+
// load should not be called with empty keys
|
|
78
78
|
expect(load).not.toHaveBeenCalled();
|
|
79
79
|
});
|
|
80
80
|
|
|
@@ -261,7 +261,7 @@ describe("DataSelectButton", () => {
|
|
|
261
261
|
const searchBtn = container.querySelector("[data-search-button]") as HTMLButtonElement;
|
|
262
262
|
searchBtn.click();
|
|
263
263
|
|
|
264
|
-
//
|
|
264
|
+
// once modal opens, find and click confirm button
|
|
265
265
|
await vi.waitFor(() => {
|
|
266
266
|
const confirmBtn = document.querySelector(
|
|
267
267
|
"[data-testid='modal-confirm']",
|
|
@@ -328,7 +328,7 @@ describe("DataSelectButton", () => {
|
|
|
328
328
|
cancelBtn.click();
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
-
//
|
|
331
|
+
// value should not change
|
|
332
332
|
await new Promise((r) => setTimeout(r, 100));
|
|
333
333
|
expect(onValueChange).not.toHaveBeenCalled();
|
|
334
334
|
});
|
|
@@ -344,7 +344,7 @@ describe("DataSelectButton", () => {
|
|
|
344
344
|
/>
|
|
345
345
|
));
|
|
346
346
|
|
|
347
|
-
// Invalid
|
|
347
|
+
// Invalid component sets setCustomValidity on hidden input
|
|
348
348
|
const hiddenInput = container.querySelector("input[type='text']") as HTMLInputElement;
|
|
349
349
|
expect(hiddenInput).not.toBeNull();
|
|
350
350
|
expect(hiddenInput.validationMessage).toBe("Required field");
|