@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.
Files changed (98) hide show
  1. package/README.md +1 -1
  2. package/dist/components/disclosure/Dropdown.d.ts +6 -4
  3. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  4. package/dist/components/disclosure/Dropdown.js +24 -8
  5. package/dist/components/disclosure/Dropdown.js.map +2 -2
  6. package/dist/components/disclosure/dialogZIndex.d.ts +2 -0
  7. package/dist/components/disclosure/dialogZIndex.d.ts.map +1 -1
  8. package/dist/components/disclosure/dialogZIndex.js +4 -0
  9. package/dist/components/disclosure/dialogZIndex.js.map +1 -1
  10. package/dist/components/features/crud-detail/CrudDetail.d.ts.map +1 -1
  11. package/dist/components/features/crud-detail/CrudDetail.js +16 -7
  12. package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
  13. package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
  14. package/dist/components/features/crud-sheet/CrudSheet.js +14 -5
  15. package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
  16. package/dist/components/features/crudRegistry.d.ts +16 -0
  17. package/dist/components/features/crudRegistry.d.ts.map +1 -0
  18. package/dist/components/features/crudRegistry.js +37 -0
  19. package/dist/components/features/crudRegistry.js.map +6 -0
  20. package/dist/components/features/permission-table/PermissionTable.d.ts.map +1 -1
  21. package/dist/components/features/permission-table/PermissionTable.js +71 -86
  22. package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
  23. package/dist/components/features/shared-data/SharedDataSelect.js +2 -4
  24. package/dist/components/features/shared-data/SharedDataSelect.js.map +2 -2
  25. package/dist/components/features/shared-data/SharedDataSelectList.d.ts +2 -4
  26. package/dist/components/features/shared-data/SharedDataSelectList.d.ts.map +1 -1
  27. package/dist/components/features/shared-data/SharedDataSelectList.js +11 -46
  28. package/dist/components/features/shared-data/SharedDataSelectList.js.map +2 -2
  29. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  30. package/dist/components/form-control/select/Select.js +1 -1
  31. package/dist/components/form-control/select/Select.js.map +1 -1
  32. package/dist/helpers/createAppStructure.d.ts.map +1 -1
  33. package/dist/helpers/createAppStructure.js +3 -2
  34. package/dist/helpers/createAppStructure.js.map +1 -1
  35. package/dist/helpers/createHmrSafeContext.d.ts +3 -0
  36. package/dist/helpers/createHmrSafeContext.d.ts.map +1 -0
  37. package/dist/helpers/createHmrSafeContext.js +10 -0
  38. package/dist/helpers/createHmrSafeContext.js.map +6 -0
  39. package/dist/hooks/createSelectionGroup.d.ts.map +1 -1
  40. package/dist/hooks/createSelectionGroup.js +3 -2
  41. package/dist/hooks/createSelectionGroup.js.map +2 -2
  42. package/package.json +6 -5
  43. package/src/components/disclosure/Dropdown.tsx +31 -17
  44. package/src/components/disclosure/dialogZIndex.ts +5 -0
  45. package/src/components/features/crud-detail/CrudDetail.tsx +16 -5
  46. package/src/components/features/crud-sheet/CrudSheet.tsx +13 -3
  47. package/src/components/features/crudRegistry.ts +60 -0
  48. package/src/components/features/permission-table/PermissionTable.tsx +49 -46
  49. package/src/components/features/shared-data/SharedDataSelect.tsx +2 -2
  50. package/src/components/features/shared-data/SharedDataSelectList.tsx +11 -36
  51. package/src/components/form-control/select/Select.tsx +1 -5
  52. package/src/helpers/createAppStructure.ts +3 -2
  53. package/src/helpers/createHmrSafeContext.ts +8 -0
  54. package/src/hooks/createSelectionGroup.tsx +4 -2
  55. package/tests/components/data/List.spec.tsx +52 -52
  56. package/tests/components/data/Pagination.spec.tsx +43 -43
  57. package/tests/components/data/Table.spec.tsx +4 -4
  58. package/tests/components/data/kanban/Kanban.selection.spec.tsx +21 -21
  59. package/tests/components/data/sheet/DataSheet.spec.tsx +50 -50
  60. package/tests/components/disclosure/Collapse.spec.tsx +24 -24
  61. package/tests/components/disclosure/Dialog.spec.tsx +33 -33
  62. package/tests/components/disclosure/DialogProvider.spec.tsx +9 -9
  63. package/tests/components/disclosure/Dropdown.spec.tsx +134 -14
  64. package/tests/components/disclosure/Tabs.spec.tsx +21 -21
  65. package/tests/components/disclosure/dialogZIndex.spec.ts +45 -0
  66. package/tests/components/display/Alert.spec.tsx +4 -4
  67. package/tests/components/display/Barcode.spec.tsx +7 -7
  68. package/tests/components/display/Card.spec.tsx +3 -3
  69. package/tests/components/display/Link.spec.tsx +5 -5
  70. package/tests/components/display/Tag.spec.tsx +4 -4
  71. package/tests/components/features/address/AddressSearch.spec.tsx +3 -3
  72. package/tests/components/features/crudRegistry.spec.ts +119 -0
  73. package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +8 -8
  74. package/tests/components/features/permission-table/PermissionTable.spec.tsx +43 -43
  75. package/tests/components/features/shared-data/SharedDataSelectList.spec.tsx +2 -17
  76. package/tests/components/feedback/busy/BusyContainer.spec.tsx +7 -7
  77. package/tests/components/feedback/notification/NotificationBell.spec.tsx +9 -9
  78. package/tests/components/feedback/print/Print.spec.tsx +4 -4
  79. package/tests/components/form-control/Button.spec.tsx +18 -18
  80. package/tests/components/form-control/checkbox/Checkbox.spec.tsx +20 -20
  81. package/tests/components/form-control/checkbox/CheckboxGroup.spec.tsx +12 -12
  82. package/tests/components/form-control/checkbox/Radio.spec.tsx +21 -21
  83. package/tests/components/form-control/checkbox/RadioGroup.spec.tsx +12 -12
  84. package/tests/components/form-control/color-picker/ColorPicker.spec.tsx +10 -10
  85. package/tests/components/form-control/combobox/Combobox.spec.tsx +16 -16
  86. package/tests/components/form-control/combobox/ComboboxItem.spec.tsx +7 -7
  87. package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +24 -24
  88. package/tests/components/form-control/field/DatePicker.spec.tsx +50 -50
  89. package/tests/components/form-control/field/DateTimePicker.spec.tsx +47 -47
  90. package/tests/components/form-control/field/NumberInput.spec.tsx +54 -54
  91. package/tests/components/form-control/field/TextInput.spec.tsx +49 -49
  92. package/tests/components/form-control/field/Textarea.spec.tsx +33 -33
  93. package/tests/components/form-control/field/TimePicker.spec.tsx +42 -42
  94. package/tests/components/form-control/numpad/Numpad.spec.tsx +40 -40
  95. package/tests/components/form-control/select/Select.spec.tsx +9 -9
  96. package/tests/components/form-control/select/SelectItem.spec.tsx +10 -10
  97. package/tests/helpers/createAppStructure.spec.tsx +57 -57
  98. 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 tab role로 렌더링된다", () => {
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("클릭하면 aria-selected true 된다", () => {
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("size prop에 따라 스타일이 달라진다", () => {
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("disabled 탭에 aria-disabled 설정된다", () => {
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("disabled 탭의 tabIndex -1이다", () => {
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("Tabs에 사용자 정의 class 병합된다", () => {
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("Tabs.Tab에 사용자 정의 class 병합된다", () => {
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이 Alert 내부에 표시된다", () => {
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("theme prop에 따라 스타일이 달라진다", () => {
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 있으면 SVG가 렌더링된다", () => {
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("value 변경 SVG가 업데이트된다", () => {
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 문자열로 변경되면 SVG가 제거된다", () => {
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이 Card 내부에 표시된다", () => {
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이 Link 내부에 표시된다", () => {
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 a 요소에 전달된다", () => {
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이 Tag 내부에 표시된다", () => {
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("theme prop에 따라 스타일이 달라진다", () => {
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("마운트 Daum Postcode 위젯이 content 영역에 렌더된다", async () => {
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 스크립트 로드 + 위젯 embed 대기
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 위젯이 embed되면 content 내부에 자식 요소가 생긴다
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
- // DialogProvider 래핑 헬퍼
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 컴포넌트가 hidden input에 setCustomValidity 설정
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");