@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
@@ -7,12 +7,12 @@ import {
7
7
  type SelectContextValue,
8
8
  } from "../../../../src/components/form-control/select/SelectContext";
9
9
 
10
- // 테스트용 Provider
10
+ // Test provider
11
11
  function TestProvider(props: { children: JSX.Element; value: SelectContextValue }) {
12
12
  return <SelectContext.Provider value={props.value}>{props.children}</SelectContext.Provider>;
13
13
  }
14
14
 
15
- describe("SelectItem 컴포넌트", () => {
15
+ describe("SelectItem component", () => {
16
16
  describe("basic rendering", () => {
17
17
  it("renders children", () => {
18
18
  const mockContext: SelectContextValue = {
@@ -34,7 +34,7 @@ describe("SelectItem 컴포넌트", () => {
34
34
  expect(getByText("사과")).not.toBeNull();
35
35
  });
36
36
 
37
- it("data-select-item 속성이 설정된다", () => {
37
+ it("sets data-select-item attribute", () => {
38
38
  const mockContext: SelectContextValue = {
39
39
  multiple: () => false,
40
40
  isSelected: () => false,
@@ -55,8 +55,8 @@ describe("SelectItem 컴포넌트", () => {
55
55
  });
56
56
  });
57
57
 
58
- describe("선택 동작", () => {
59
- it("클릭 toggleValue 호출된다", () => {
58
+ describe("selection behavior", () => {
59
+ it("calls toggleValue on click", () => {
60
60
  const toggleValue = vi.fn();
61
61
  const mockContext: SelectContextValue = {
62
62
  multiple: () => false,
@@ -78,7 +78,7 @@ describe("SelectItem 컴포넌트", () => {
78
78
  expect(toggleValue).toHaveBeenCalledWith("apple");
79
79
  });
80
80
 
81
- it("단일 선택 모드에서 클릭 closeDropdown이 호출된다", () => {
81
+ it("calls closeDropdown on click in single selection mode", () => {
82
82
  const closeDropdown = vi.fn();
83
83
  const mockContext: SelectContextValue = {
84
84
  multiple: () => false,
@@ -100,7 +100,7 @@ describe("SelectItem 컴포넌트", () => {
100
100
  expect(closeDropdown).toHaveBeenCalled();
101
101
  });
102
102
 
103
- it("다중 선택 모드에서 클릭 closeDropdown이 호출되지 않는다", () => {
103
+ it("does not call closeDropdown on click in multiple selection mode", () => {
104
104
  const closeDropdown = vi.fn();
105
105
  const mockContext: SelectContextValue = {
106
106
  multiple: () => true,
@@ -123,8 +123,8 @@ describe("SelectItem 컴포넌트", () => {
123
123
  });
124
124
  });
125
125
 
126
- describe("선택 상태", () => {
127
- it("선택된 아이템에 aria-selected=true 설정된다", () => {
126
+ describe("selected state", () => {
127
+ it("sets aria-selected=true on selected item", () => {
128
128
  const mockContext: SelectContextValue = {
129
129
  multiple: () => false,
130
130
  isSelected: (v) => v === "apple",
@@ -147,7 +147,7 @@ describe("SelectItem 컴포넌트", () => {
147
147
  });
148
148
 
149
149
  describe("disabled state", () => {
150
- it("disabled일 클릭해도 toggleValue 호출되지 않는다", () => {
150
+ it("does not call toggleValue on click when disabled", () => {
151
151
  const toggleValue = vi.fn();
152
152
  const mockContext: SelectContextValue = {
153
153
  multiple: () => false,
@@ -3,13 +3,13 @@ import { type Accessor, createRoot, createSignal } from "solid-js";
3
3
  import type { Component } from "solid-js";
4
4
  import { createAppStructure, type AppStructureItem } from "../../src/helpers/createAppStructure";
5
5
 
6
- // 테스트용 더미 컴포넌트
6
+ // dummy components for tests
7
7
  const DummyA: Component = () => null;
8
8
  const DummyB: Component = () => null;
9
9
  const DummyC: Component = () => null;
10
10
  const DummyD: Component = () => null;
11
11
 
12
- // 공통 테스트 구조
12
+ // common test structure
13
13
  function createTestItems(): AppStructureItem<string>[] {
14
14
  return [
15
15
  {
@@ -77,7 +77,7 @@ function buildTestStructure<const TItems extends AppStructureItem<string>[]>(opt
77
77
  }
78
78
 
79
79
  describe("createAppStructure", () => {
80
- it("AppStructureProvider useAppStructure를 반환한다", () => {
80
+ it("returns AppStructureProvider and useAppStructure", () => {
81
81
  createRoot((dispose) => {
82
82
  const { AppStructureProvider, useAppStructure } = createAppStructure(() => ({
83
83
  items: [
@@ -104,7 +104,7 @@ describe("createAppStructure", () => {
104
104
  });
105
105
 
106
106
  describe("usableRoutes", () => {
107
- it("모든 리프 아이템의 경로와 컴포넌트를 포함한다", () => {
107
+ it("includes paths and components for all leaf items", () => {
108
108
  createRoot((dispose) => {
109
109
  const result = buildTestStructure({ items: createTestItems() });
110
110
 
@@ -119,7 +119,7 @@ describe("createAppStructure", () => {
119
119
  });
120
120
  });
121
121
 
122
- it("component가 없는 아이템은 usableRoutes에 포함되지 않는다", () => {
122
+ it("excludes items without a component from usableRoutes", () => {
123
123
  createRoot((dispose) => {
124
124
  const items: AppStructureItem<string>[] = [
125
125
  {
@@ -136,7 +136,7 @@ describe("createAppStructure", () => {
136
136
  });
137
137
  });
138
138
 
139
- it("usableModules에 매칭되지 않는 모듈은 필터링된다", () => {
139
+ it("filters out modules not matching usableModules", () => {
140
140
  createRoot((dispose) => {
141
141
  const [modules] = createSignal<string[]>(["erp"]);
142
142
 
@@ -145,8 +145,8 @@ describe("createAppStructure", () => {
145
145
  usableModules: modules,
146
146
  });
147
147
 
148
- // sales 그룹은 modules: ["sales"] — "erp" 있으면 필터링됨
149
- // admin modules 없음 항상 포함
148
+ // sales group has modules: ["sales"] — filtered out when only "erp" is present
149
+ // admin has no modules — always included
150
150
  const routes = result.usableRoutes();
151
151
  expect(routes.map((r) => r.path)).toEqual(["/admin/users", "/admin/hidden"]);
152
152
 
@@ -154,7 +154,7 @@ describe("createAppStructure", () => {
154
154
  });
155
155
  });
156
156
 
157
- it("requiredModules 모두 없으면 필터링된다", () => {
157
+ it("filters out items when all requiredModules are missing", () => {
158
158
  createRoot((dispose) => {
159
159
  const [modules] = createSignal<string[]>(["sales"]);
160
160
 
@@ -163,8 +163,8 @@ describe("createAppStructure", () => {
163
163
  usableModules: modules,
164
164
  });
165
165
 
166
- // order requiredModules: ["sales", "erp"] — "erp" 없으므로 필터링됨
167
- // invoice requiredModules 없음 포함
166
+ // order has requiredModules: ["sales", "erp"] — filtered because "erp" is missing
167
+ // invoice has no requiredModules — included
168
168
  const routes = result.usableRoutes();
169
169
  expect(routes.map((r) => r.path)).toEqual([
170
170
  "/sales/invoice",
@@ -176,7 +176,7 @@ describe("createAppStructure", () => {
176
176
  });
177
177
  });
178
178
 
179
- it("permRecord에서 use 권한이 없으면 필터링된다", () => {
179
+ it("filters out items without use permission in permRecord", () => {
180
180
  createRoot((dispose) => {
181
181
  const [perms] = createSignal<Record<string, boolean>>({
182
182
  "/home/sales/invoice/use": false,
@@ -199,7 +199,7 @@ describe("createAppStructure", () => {
199
199
  });
200
200
  });
201
201
 
202
- it("module perm 모두 충족해야 포함된다", () => {
202
+ it("includes only items satisfying both module and perm requirements", () => {
203
203
  createRoot((dispose) => {
204
204
  const [modules] = createSignal<string[]>(["sales", "erp"]);
205
205
  const [perms] = createSignal<Record<string, boolean>>({
@@ -224,7 +224,7 @@ describe("createAppStructure", () => {
224
224
  });
225
225
  });
226
226
 
227
- it("isNotMenu 아이템도 route에 포함된다", () => {
227
+ it("includes isNotMenu items in routes", () => {
228
228
  createRoot((dispose) => {
229
229
  const result = buildTestStructure({ items: createTestItems() });
230
230
 
@@ -237,18 +237,18 @@ describe("createAppStructure", () => {
237
237
  });
238
238
 
239
239
  describe("allFlatPerms", () => {
240
- it("requiredModulesChain 계층별로 수집한다", () => {
240
+ it("collects requiredModulesChain per hierarchy level", () => {
241
241
  createRoot((dispose) => {
242
242
  const result = buildTestStructure({ items: createTestItems() });
243
243
 
244
- // order requiredModules: ["sales", "erp"]
245
- // 부모 sales modules: ["sales"] (requiredModules 아님)
244
+ // order has requiredModules: ["sales", "erp"]
245
+ // parent sales has modules: ["sales"] (not requiredModules)
246
246
  const orderUsePerm = result.allFlatPerms.find((p) => p.code === "/home/sales/order/use");
247
247
  expect(orderUsePerm).toBeDefined();
248
248
  expect(orderUsePerm!.modulesChain).toEqual([["sales"]]);
249
249
  expect(orderUsePerm!.requiredModulesChain).toEqual([["sales", "erp"]]);
250
250
 
251
- // invoice requiredModules 없음
251
+ // invoice has no requiredModules
252
252
  const invoiceUsePerm = result.allFlatPerms.find(
253
253
  (p) => p.code === "/home/sales/invoice/use",
254
254
  );
@@ -262,16 +262,16 @@ describe("createAppStructure", () => {
262
262
  });
263
263
 
264
264
  describe("usableMenus", () => {
265
- it("permRecord가 없으면 perms 있는 리프는 숨겨지고 perms 없는 리프만 표시된다", () => {
266
- // permRecord 기본값이 {}이므로, perms에 "use" 있는 아이템은
267
- // permRecord[href + "/use"] undefined(falsy)가 되어 필터링됨
265
+ it("hides leaves with perms and shows only perms-less leaves when permRecord is absent", () => {
266
+ // permRecord defaults to {}, so items with "use" in perms are
267
+ // filtered because permRecord[href + "/use"] is undefined (falsy)
268
268
  createRoot((dispose) => {
269
269
  const result = buildTestStructure({ items: createTestItems() });
270
270
  const menus = result.usableMenus();
271
271
 
272
- // 영업 하위 아이템(송장, 주문) 모두 perms에 "use" 있어 숨겨짐
273
- // → 영업 그룹 자체도 자식이 없으므로 숨겨짐
274
- // 관리 하위 아이템: 사용자(perms 없음) 표시, 숨김(isNotMenu) 제외
272
+ // sales sub-items (invoice, order) both have "use" in perms → hidden
273
+ // → sales group itself is also hidden because no children remain
274
+ // admin sub-items: only 사용자 (no perms) shown, 숨김 (isNotMenu) excluded
275
275
  expect(menus).toHaveLength(1);
276
276
  expect(menus[0].title).toBe("관리");
277
277
  expect(menus[0].children).toHaveLength(1);
@@ -281,7 +281,7 @@ describe("createAppStructure", () => {
281
281
  });
282
282
  });
283
283
 
284
- it("모든 권한이 부여되면 isNotMenu 제외한 모든 메뉴가 표시된다", () => {
284
+ it("displays all menus except isNotMenu when all permissions are granted", () => {
285
285
  createRoot((dispose) => {
286
286
  const [perms] = createSignal<Record<string, boolean>>({
287
287
  "/home/sales/invoice/use": true,
@@ -309,7 +309,7 @@ describe("createAppStructure", () => {
309
309
  });
310
310
  });
311
311
 
312
- it("href 올바른 전체 경로로 생성된다", () => {
312
+ it("generates href as the correct full path", () => {
313
313
  createRoot((dispose) => {
314
314
  const [perms] = createSignal<Record<string, boolean>>({
315
315
  "/home/sales/invoice/use": true,
@@ -333,7 +333,7 @@ describe("createAppStructure", () => {
333
333
  });
334
334
  });
335
335
 
336
- it("modules OR 필터링: 모듈이 없으면 해당 그룹이 숨겨진다", () => {
336
+ it("modules OR filter: group is hidden when its module is not in usableModules", () => {
337
337
  createRoot((dispose) => {
338
338
  const [modules] = createSignal<string[]>(["erp"]);
339
339
 
@@ -343,7 +343,7 @@ describe("createAppStructure", () => {
343
343
  });
344
344
 
345
345
  const menus = result.usableMenus();
346
- // sales 그룹은 modules: ["sales"]인데 usableModules에 "sales" 없으므로 숨겨짐
346
+ // sales group has modules: ["sales"] but "sales" is not in usableModules → hidden
347
347
  expect(menus).toHaveLength(1);
348
348
  expect(menus[0].title).toBe("관리");
349
349
 
@@ -351,7 +351,7 @@ describe("createAppStructure", () => {
351
351
  });
352
352
  });
353
353
 
354
- it("requiredModules AND 필터링: 모든 모듈이 있어야 표시된다", () => {
354
+ it("requiredModules AND filter: displayed only when all required modules are present", () => {
355
355
  createRoot((dispose) => {
356
356
  const [modules] = createSignal<string[]>(["sales"]);
357
357
  const [perms] = createSignal<Record<string, boolean>>({
@@ -366,9 +366,9 @@ describe("createAppStructure", () => {
366
366
  });
367
367
 
368
368
  const menus = result.usableMenus();
369
- // sales 그룹은 modules: ["sales"] 매칭되어 표시됨
370
- // order requiredModules: ["sales", "erp"]인데 "erp" 없으므로 숨겨짐
371
- // invoice requiredModules 없으므로 표시됨
369
+ // sales group matches modules: ["sales"] shown
370
+ // order has requiredModules: ["sales", "erp"] but "erp" is missing → hidden
371
+ // invoice has no requiredModules shown
372
372
  expect(menus[0].children).toHaveLength(1);
373
373
  expect(menus[0].children![0].title).toBe("송장");
374
374
 
@@ -376,7 +376,7 @@ describe("createAppStructure", () => {
376
376
  });
377
377
  });
378
378
 
379
- it("permRecord 필터링: use 권한이 없으면 숨겨진다", () => {
379
+ it("permRecord filter: hidden when use permission is absent", () => {
380
380
  createRoot((dispose) => {
381
381
  const [perms] = createSignal<Record<string, boolean>>({
382
382
  "/home/sales/invoice/use": false,
@@ -399,7 +399,7 @@ describe("createAppStructure", () => {
399
399
  });
400
400
  });
401
401
 
402
- it("perms 없는 리프는 권한 체크 없이 항상 표시된다", () => {
402
+ it("leaves without perms are always shown without permission check", () => {
403
403
  createRoot((dispose) => {
404
404
  const [perms] = createSignal<Record<string, boolean>>({});
405
405
 
@@ -409,9 +409,9 @@ describe("createAppStructure", () => {
409
409
  });
410
410
 
411
411
  const menus = result.usableMenus();
412
- // permRecord 객체이므로 perms: ["use"] 있는 아이템은 모두 숨겨짐
413
- // perms가 없는 사용자(DummyC) 표시됨
414
- // 관리 그룹만 남음 (인덱스 0)
412
+ // permRecord is empty → all items with perms: ["use"] are hidden
413
+ // only 사용자 (DummyC, no perms) is shown
414
+ // only 관리 group remains (index 0)
415
415
  expect(menus).toHaveLength(1);
416
416
  expect(menus[0].title).toBe("관리");
417
417
  expect(menus[0].children![0].title).toBe("사용자");
@@ -420,7 +420,7 @@ describe("createAppStructure", () => {
420
420
  });
421
421
  });
422
422
 
423
- it("자식이 모두 필터링되면 그룹도 숨겨진다", () => {
423
+ it("hides group when all children are filtered out", () => {
424
424
  createRoot((dispose) => {
425
425
  const [perms] = createSignal<Record<string, boolean>>({
426
426
  "/home/sales/invoice/use": false,
@@ -436,7 +436,7 @@ describe("createAppStructure", () => {
436
436
  });
437
437
 
438
438
  const menus = result.usableMenus();
439
- // 영업 하위 아이템이 모두 권한 없음으로 필터링영업 그룹도 숨겨짐
439
+ // all sales sub-items filtered out by missing permissions sales group is also hidden
440
440
  expect(menus).toHaveLength(1);
441
441
  expect(menus[0].title).toBe("관리");
442
442
 
@@ -444,7 +444,7 @@ describe("createAppStructure", () => {
444
444
  });
445
445
  });
446
446
 
447
- it("usableModules가 변경되면 메뉴가 재계산된다", () => {
447
+ it("menus are recalculated when usableModules changes", () => {
448
448
  createRoot((dispose) => {
449
449
  const [modules, setModules] = createSignal<string[] | undefined>(undefined);
450
450
  const [perms] = createSignal<Record<string, boolean>>({
@@ -458,10 +458,10 @@ describe("createAppStructure", () => {
458
458
  permRecord: perms,
459
459
  });
460
460
 
461
- // usableModules undefined이면 모듈 필터링 없음전부 표시
461
+ // usableModules is undefined no module filtering all shown
462
462
  expect(result.usableMenus()).toHaveLength(2);
463
463
 
464
- // usableModules ["sales"] 설정하면 order requiredModules 불충족
464
+ // setting usableModules to ["sales"] fails order's requiredModules
465
465
  setModules(["sales"]);
466
466
  expect(result.usableMenus()[0].title).toBe("영업");
467
467
  expect(result.usableMenus()[0].children).toHaveLength(1);
@@ -473,7 +473,7 @@ describe("createAppStructure", () => {
473
473
  });
474
474
 
475
475
  describe("usableFlatMenus", () => {
476
- it("트리를 평탄화하여 titleChain href를 반환한다", () => {
476
+ it("flattens the tree and returns titleChain and href", () => {
477
477
  createRoot((dispose) => {
478
478
  const [perms] = createSignal<Record<string, boolean>>({
479
479
  "/home/sales/invoice/use": true,
@@ -505,7 +505,7 @@ describe("createAppStructure", () => {
505
505
  });
506
506
 
507
507
  describe("getTitleChainByHref", () => {
508
- it("href에서 title 체인을 반환한다", () => {
508
+ it("returns title chain from href", () => {
509
509
  createRoot((dispose) => {
510
510
  const result = buildTestStructure({ items: createTestItems() });
511
511
 
@@ -516,7 +516,7 @@ describe("createAppStructure", () => {
516
516
  });
517
517
  });
518
518
 
519
- it("isNotMenu 아이템도 찾는다", () => {
519
+ it("also finds isNotMenu items", () => {
520
520
  createRoot((dispose) => {
521
521
  const result = buildTestStructure({ items: createTestItems() });
522
522
 
@@ -526,7 +526,7 @@ describe("createAppStructure", () => {
526
526
  });
527
527
  });
528
528
 
529
- it("존재하지 않는 href는 배열을 반환한다", () => {
529
+ it("returns empty array for nonexistent href", () => {
530
530
  createRoot((dispose) => {
531
531
  const result = buildTestStructure({ items: createTestItems() });
532
532
 
@@ -539,7 +539,7 @@ describe("createAppStructure", () => {
539
539
  });
540
540
 
541
541
  describe("usablePerms", () => {
542
- it("perms 없는 leaf는 usablePerms에서 제외된다", () => {
542
+ it("excludes leaves without perms from usablePerms", () => {
543
543
  createRoot((dispose) => {
544
544
  const result = buildTestStructure({
545
545
  items: [
@@ -559,7 +559,7 @@ describe("createAppStructure", () => {
559
559
  });
560
560
 
561
561
  const perms = result.usablePerms();
562
- // home 그룹의 children "메인"이 없어야 함
562
+ // home group's children should not include "메인"
563
563
  const homeGroup = perms[0];
564
564
  expect(homeGroup.children).toHaveLength(1);
565
565
  expect(homeGroup.children![0].title).toBe("사용자");
@@ -568,7 +568,7 @@ describe("createAppStructure", () => {
568
568
  });
569
569
  });
570
570
 
571
- it("perms가 배열인 leaf도 제외된다", () => {
571
+ it("excludes leaves with empty perms array", () => {
572
572
  createRoot((dispose) => {
573
573
  const result = buildTestStructure({
574
574
  items: [
@@ -600,7 +600,7 @@ describe("createAppStructure", () => {
600
600
  });
601
601
  });
602
602
 
603
- it("children 모두 필터링되면 그룹도 제외된다", () => {
603
+ it("excludes group when all children are filtered out", () => {
604
604
  createRoot((dispose) => {
605
605
  const result = buildTestStructure({
606
606
  items: [
@@ -634,7 +634,7 @@ describe("createAppStructure", () => {
634
634
  });
635
635
 
636
636
  describe("perms", () => {
637
- it("permRecord에서 true perm은 true 반환한다", () => {
637
+ it("returns true for perms that are true in permRecord", () => {
638
638
  createRoot((dispose) => {
639
639
  const [perms] = createSignal<Record<string, boolean>>({
640
640
  "/home/sales/invoice/use": true,
@@ -671,7 +671,7 @@ describe("createAppStructure", () => {
671
671
  });
672
672
  });
673
673
 
674
- it("permRecord에 없는 perm은 false를 반환한다", () => {
674
+ it("returns false for perms absent from permRecord", () => {
675
675
  createRoot((dispose) => {
676
676
  const [perms] = createSignal<Record<string, boolean>>({});
677
677
 
@@ -698,7 +698,7 @@ describe("createAppStructure", () => {
698
698
  });
699
699
  });
700
700
 
701
- it("permRecord가 없으면 모든 perm이 false다", () => {
701
+ it("all perms are false when permRecord is absent", () => {
702
702
  createRoot((dispose) => {
703
703
  const result = buildTestStructure({
704
704
  items: [
@@ -722,7 +722,7 @@ describe("createAppStructure", () => {
722
722
  });
723
723
  });
724
724
 
725
- it("subPerms에도 동일하게 접근할 있다", () => {
725
+ it("subPerms are accessible in the same way", () => {
726
726
  createRoot((dispose) => {
727
727
  const [perms] = createSignal<Record<string, boolean>>({
728
728
  "/home/user/auth/use": true,
@@ -752,7 +752,7 @@ describe("createAppStructure", () => {
752
752
  });
753
753
  });
754
754
 
755
- it("permRecord 변경 perm 값이 반응적으로 업데이트된다", () => {
755
+ it("perm values update reactively when permRecord changes", () => {
756
756
  createRoot((dispose) => {
757
757
  const [perms, setPerms] = createSignal<Record<string, boolean>>({
758
758
  "/home/user/use": false,
@@ -784,7 +784,7 @@ describe("createAppStructure", () => {
784
784
  });
785
785
  });
786
786
 
787
- it("perms 없는 leaf는 perms 트리에서 제외된다", () => {
787
+ it("excludes leaves without perms from the perms tree", () => {
788
788
  createRoot((dispose) => {
789
789
  const result = buildTestStructure({
790
790
  items: [
@@ -810,7 +810,7 @@ describe("createAppStructure", () => {
810
810
  });
811
811
  });
812
812
 
813
- it("하위에 perm 없는 group은 perms 트리에서 제외된다", () => {
813
+ it("excludes groups without any perm from the perms tree", () => {
814
814
  createRoot((dispose) => {
815
815
  const result = buildTestStructure({
816
816
  items: [