@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
@@ -30,7 +30,7 @@ const testData: TestItem[] = [
30
30
  ];
31
31
 
32
32
  describe("DataSheet", () => {
33
- it("기본 렌더링: 컬럼 헤더와 데이터 행이 표시된다", () => {
33
+ it("basic rendering: column headers and data rows are displayed", () => {
34
34
  const { container } = render(() => (
35
35
  <ConfigProvider clientName="test"><I18nProvider>
36
36
  <TestWrapper>
@@ -59,7 +59,7 @@ describe("DataSheet", () => {
59
59
  expect(rows.length).toBe(3);
60
60
  });
61
61
 
62
- it("다단계 헤더: colspan rowspan 올바르게 적용된다", () => {
62
+ it("multi-level header: colspan and rowspan are applied correctly", () => {
63
63
  const { container } = render(() => (
64
64
  <ConfigProvider clientName="test"><I18nProvider>
65
65
  <TestWrapper>
@@ -79,22 +79,22 @@ describe("DataSheet", () => {
79
79
  ));
80
80
 
81
81
  const headerRows = container.querySelectorAll("thead tr");
82
- // 2행: 행에 "기본정보"(colspan=2) + "이메일"(rowspan=2), 둘째 행에 "이름" + "나이"
82
+ // 2 rows: first row has "기본정보"(colspan=2) + "이메일"(rowspan=2), second row has "이름" + "나이"
83
83
  expect(headerRows.length).toBeGreaterThanOrEqual(2);
84
84
 
85
85
  const firstRowThs = headerRows[0].querySelectorAll("th");
86
- // "기본정보" th colspan=2
86
+ // "기본정보" th has colspan=2
87
87
  const groupTh = Array.from(firstRowThs).find((th) => th.textContent.includes("기본정보"));
88
88
  expect(groupTh).toBeTruthy();
89
89
  expect(groupTh!.getAttribute("colspan")).toBe("2");
90
90
 
91
- // "이메일" th rowspan=2
91
+ // "이메일" th has rowspan=2
92
92
  const emailTh = Array.from(firstRowThs).find((th) => th.textContent.includes("이메일"));
93
93
  expect(emailTh).toBeTruthy();
94
94
  expect(emailTh!.getAttribute("rowspan")).toBe("2");
95
95
  });
96
96
 
97
- it("합계 행: summary 있으면 thead에 합계 행이 표시된다", () => {
97
+ it("summary row: summary column displays a summary row in thead", () => {
98
98
  const { container } = render(() => (
99
99
  <ConfigProvider clientName="test"><I18nProvider>
100
100
  <TestWrapper>
@@ -111,14 +111,14 @@ describe("DataSheet", () => {
111
111
  ));
112
112
 
113
113
  const theadRows = container.querySelectorAll("thead tr");
114
- // 헤더 1 + 합계 1 = 2
114
+ // 1 header row + 1 summary row = 2 rows
115
115
  expect(theadRows.length).toBe(2);
116
116
 
117
117
  const summaryRow = theadRows[theadRows.length - 1];
118
118
  expect(summaryRow.textContent).toContain("합계: 83");
119
119
  });
120
120
 
121
- it(" 데이터: tbody 비어있다", () => {
121
+ it("empty data: tbody is empty", () => {
122
122
  const { container } = render(() => (
123
123
  <ConfigProvider clientName="test"><I18nProvider>
124
124
  <TestWrapper>
@@ -134,12 +134,12 @@ describe("DataSheet", () => {
134
134
  const rows = container.querySelectorAll("tbody tr");
135
135
  expect(rows.length).toBe(0);
136
136
 
137
- // 헤더는 여전히 표시
137
+ // header is still displayed
138
138
  const ths = container.querySelectorAll("thead th");
139
139
  expect(ths.length).toBe(1);
140
140
  });
141
141
 
142
- it("hidden 컬럼은 렌더링되지 않는다", () => {
142
+ it("hidden columns are not rendered", () => {
143
143
  const { container } = render(() => (
144
144
  <ConfigProvider clientName="test"><I18nProvider>
145
145
  <TestWrapper>
@@ -160,7 +160,7 @@ describe("DataSheet", () => {
160
160
  expect(ths[0].textContent).toContain("이름");
161
161
  });
162
162
 
163
- it("정렬: 헤더 클릭 onSortsChange가 호출된다", () => {
163
+ it("sort: clicking header calls onSortsChange", () => {
164
164
  let capturedSorts: SortingDef[] = [];
165
165
  const { container } = render(() => (
166
166
  <ConfigProvider clientName="test"><I18nProvider>
@@ -184,13 +184,13 @@ describe("DataSheet", () => {
184
184
  </I18nProvider></ConfigProvider>
185
185
  ));
186
186
 
187
- // "이름" 헤더 클릭
187
+ // click "이름" header
188
188
  const ths = container.querySelectorAll("thead th");
189
189
  (ths[0] as HTMLElement).click();
190
190
  expect(capturedSorts).toEqual([{ key: "name", desc: false }]);
191
191
  });
192
192
 
193
- it("정렬: sortable={false} 컬럼은 클릭해도 정렬되지 않는다", () => {
193
+ it("sort: sortable={false} column does not sort on click", () => {
194
194
  let capturedSorts: SortingDef[] = [];
195
195
  const { container } = render(() => (
196
196
  <ConfigProvider clientName="test"><I18nProvider>
@@ -216,7 +216,7 @@ describe("DataSheet", () => {
216
216
  expect(capturedSorts).toEqual([]);
217
217
  });
218
218
 
219
- it("자동정렬: autoSort true면 데이터가 정렬된다", () => {
219
+ it("auto sort: data is sorted when autoSort is true", () => {
220
220
  const { container } = render(() => (
221
221
  <ConfigProvider clientName="test"><I18nProvider>
222
222
  <TestWrapper>
@@ -239,7 +239,7 @@ describe("DataSheet", () => {
239
239
  expect(names).toEqual(["김철수", "이영희", "홍길동"]);
240
240
  });
241
241
 
242
- it("페이지네이션: itemsPerPage로 데이터가 잘린다", () => {
242
+ it("pagination: data is sliced by itemsPerPage", () => {
243
243
  const { container } = render(() => (
244
244
  <ConfigProvider clientName="test"><I18nProvider>
245
245
  <TestWrapper>
@@ -256,7 +256,7 @@ describe("DataSheet", () => {
256
256
  expect(rows.length).toBe(2);
257
257
  });
258
258
 
259
- it("페이지네이션: 2페이지 이상일 Pagination이 표시된다", () => {
259
+ it("pagination: Pagination is displayed when there are 2 or more pages", () => {
260
260
  const { container } = render(() => (
261
261
  <ConfigProvider clientName="test"><I18nProvider>
262
262
  <TestWrapper>
@@ -273,7 +273,7 @@ describe("DataSheet", () => {
273
273
  expect(pagination).toBeTruthy();
274
274
  });
275
275
 
276
- it("페이지네이션: 1페이지면 Pagination 표시되지 않는다", () => {
276
+ it("pagination: Pagination is not displayed when there is only 1 page", () => {
277
277
  const { container } = render(() => (
278
278
  <ConfigProvider clientName="test"><I18nProvider>
279
279
  <TestWrapper>
@@ -290,7 +290,7 @@ describe("DataSheet", () => {
290
290
  expect(pagination).toBeFalsy();
291
291
  });
292
292
 
293
- it("고정 컬럼: fixed 컬럼의 td sticky 클래스가 적용된다", () => {
293
+ it("fixed column: sticky class is applied to td of fixed column", () => {
294
294
  const { container } = render(() => (
295
295
  <ConfigProvider clientName="test"><I18nProvider>
296
296
  <TestWrapper>
@@ -311,7 +311,7 @@ describe("DataSheet", () => {
311
311
  expect(tds[1].classList.contains("sticky")).toBe(false);
312
312
  });
313
313
 
314
- it("고정 컬럼: 마지막 고정 컬럼에 경계 테두리 클래스가 적용된다", () => {
314
+ it("fixed column: border class is applied to the last fixed column", () => {
315
315
  const { container } = render(() => (
316
316
  <ConfigProvider clientName="test"><I18nProvider>
317
317
  <TestWrapper>
@@ -328,11 +328,11 @@ describe("DataSheet", () => {
328
328
  ));
329
329
 
330
330
  const tds = container.querySelectorAll("tbody tr:first-child td");
331
- // fixedLastClass에 포함된 클래스 확인
331
+ // verify class included in fixedLastClass
332
332
  expect(tds[0].classList.contains("border-r")).toBe(true);
333
333
  });
334
334
 
335
- it("리사이저: resizable 컬럼에 리사이저 핸들이 있다", () => {
335
+ it("resizer: resizable column has a resizer handle", () => {
336
336
  const { container } = render(() => (
337
337
  <ConfigProvider clientName="test"><I18nProvider>
338
338
  <TestWrapper>
@@ -349,7 +349,7 @@ describe("DataSheet", () => {
349
349
  ));
350
350
 
351
351
  const resizers = container.querySelectorAll(".cursor-ew-resize");
352
- // 번째 컬럼에만 리사이저가 있어야
352
+ // only the first column should have a resizer
353
353
  expect(resizers.length).toBe(1);
354
354
  });
355
355
  });
@@ -366,22 +366,22 @@ describe("applySorting", () => {
366
366
  { name: "나", age: 28 },
367
367
  ];
368
368
 
369
- it(" sorts 원본 순서 유지", () => {
369
+ it("preserves original order when sorts is empty", () => {
370
370
  const result = applySorting(items, []);
371
371
  expect(result.map((i) => i.name)).toEqual(["다", "가", "나"]);
372
372
  });
373
373
 
374
- it("단일 오름차순 정렬", () => {
374
+ it("single ascending sort", () => {
375
375
  const result = applySorting(items, [{ key: "name", desc: false }]);
376
376
  expect(result.map((i) => i.name)).toEqual(["가", "나", "다"]);
377
377
  });
378
378
 
379
- it("단일 내림차순 정렬", () => {
379
+ it("single descending sort", () => {
380
380
  const result = applySorting(items, [{ key: "age", desc: true }]);
381
381
  expect(result.map((i) => i.age)).toEqual([30, 28, 25]);
382
382
  });
383
383
 
384
- it("다중 정렬: 번째 우선, 동일 값은 번째 키로", () => {
384
+ it("multi-sort: first key takes priority, ties broken by second key", () => {
385
385
  const data: Item[] = [
386
386
  { name: "가", age: 30 },
387
387
  { name: "나", age: 25 },
@@ -398,7 +398,7 @@ describe("applySorting", () => {
398
398
  ]);
399
399
  });
400
400
 
401
- it("원본 배열을 변경하지 않는다", () => {
401
+ it("does not mutate the original array", () => {
402
402
  const original = [...items];
403
403
  applySorting(items, [{ key: "name", desc: false }]);
404
404
  expect(items).toEqual(original);
@@ -421,21 +421,21 @@ describe("flattenTree", () => {
421
421
  { id: "b" },
422
422
  ];
423
423
 
424
- it("getChildren 없으면 flat 리스트를 반환한다", () => {
424
+ it("returns flat list when getChildren is not provided", () => {
425
425
  const result = flattenTree(tree, []);
426
426
  expect(result.map((r) => r.item.id)).toEqual(["a", "b"]);
427
427
  expect(result.every((r) => r.depth === 0)).toBe(true);
428
428
  expect(result.every((r) => !r.hasChildren)).toBe(true);
429
429
  });
430
430
 
431
- it("모두 접힌 상태면 루트만 반환한다", () => {
431
+ it("returns only roots when all are collapsed", () => {
432
432
  const result = flattenTree(tree, [], getChildren);
433
433
  expect(result.map((r) => r.item.id)).toEqual(["a", "b"]);
434
434
  expect(result[0].hasChildren).toBe(true);
435
435
  expect(result[1].hasChildren).toBe(false);
436
436
  });
437
437
 
438
- it("루트 확장 1단계 자식이 포함된다", () => {
438
+ it("includes 1-level children when root is expanded", () => {
439
439
  const result = flattenTree(tree, [tree[0]], getChildren);
440
440
  expect(result.map((r) => r.item.id)).toEqual(["a", "a1", "a2", "b"]);
441
441
  expect(result[1].depth).toBe(1);
@@ -443,7 +443,7 @@ describe("flattenTree", () => {
443
443
  expect(result[2].hasChildren).toBe(true);
444
444
  });
445
445
 
446
- it("중첩 확장 2단계 자식까지 포함된다", () => {
446
+ it("includes up to 2-level children when nested expanded", () => {
447
447
  const a2 = tree[0].children![1];
448
448
  const result = flattenTree(tree, [tree[0], a2], getChildren);
449
449
  expect(result.map((r) => r.item.id)).toEqual(["a", "a1", "a2", "a2x", "b"]);
@@ -451,24 +451,24 @@ describe("flattenTree", () => {
451
451
  expect(result[3].parent).toBe(a2);
452
452
  });
453
453
 
454
- it("접힌 노드의 자식은 포함되지 않는다", () => {
455
- // a2 확장하고 a 접힘 → a2 자체가 보이지 않으므로 a2의 자식도 보임
454
+ it("children of collapsed nodes are not included", () => {
455
+ // only a2 is expanded but a is collapsed → a2 itself is not visible, so its children are also hidden
456
456
  const a2 = tree[0].children![1];
457
457
  const result = flattenTree(tree, [a2], getChildren);
458
458
  expect(result.map((r) => r.item.id)).toEqual(["a", "b"]);
459
459
  });
460
460
 
461
- it(" 배열이면 결과를 반환한다", () => {
461
+ it("returns empty result for empty array", () => {
462
462
  const result = flattenTree([], [], getChildren);
463
463
  expect(result).toEqual([]);
464
464
  });
465
465
 
466
- it("row 순서대로 증가한다", () => {
466
+ it("row increments in order", () => {
467
467
  const result = flattenTree(tree, [tree[0]], getChildren);
468
468
  expect(result.map((r) => r.row)).toEqual([0, 1, 2, 3]);
469
469
  });
470
470
 
471
- it("index 포함 배열 위치를 반환한다", () => {
471
+ it("index returns position within containing array", () => {
472
472
  const result = flattenTree(tree, [tree[0]], getChildren);
473
473
  // tree[0]="a" → index 0 (root items[0])
474
474
  // tree[0].children[0]="a1" → index 0 (children[0])
@@ -477,7 +477,7 @@ describe("flattenTree", () => {
477
477
  expect(result.map((r) => r.index)).toEqual([0, 0, 1, 1]);
478
478
  });
479
479
 
480
- it("getOriginalIndex가 주어지면 root index에 원본 인덱스를 사용한다", () => {
480
+ it("uses original index for root when getOriginalIndex is provided", () => {
481
481
  const items = [tree[1], tree[0]]; // reversed
482
482
  const originalMap = new Map<TreeNode, number>();
483
483
  tree.forEach((item, i) => originalMap.set(item, i));
@@ -505,7 +505,7 @@ describe("collectAllExpandable", () => {
505
505
 
506
506
  const getChildren = (item: TreeNode) => item.children;
507
507
 
508
- it("자식이 있는 모든 노드를 재귀적으로 수집한다", () => {
508
+ it("recursively collects all nodes that have children", () => {
509
509
  const tree: TreeNode[] = [
510
510
  {
511
511
  id: "a",
@@ -517,14 +517,14 @@ describe("collectAllExpandable", () => {
517
517
  expect(result.map((r) => r.id)).toEqual(["a", "a2"]);
518
518
  });
519
519
 
520
- it("자식이 없는 트리면 배열을 반환한다", () => {
520
+ it("returns empty array when no nodes have children", () => {
521
521
  const tree: TreeNode[] = [{ id: "a" }, { id: "b" }];
522
522
  const result = collectAllExpandable(tree, getChildren);
523
523
  expect(result).toEqual([]);
524
524
  });
525
525
  });
526
526
 
527
- describe("DataSheet 트리 확장", () => {
527
+ describe("DataSheet tree expansion", () => {
528
528
  interface TreeItem {
529
529
  name: string;
530
530
  children?: TreeItem[];
@@ -538,7 +538,7 @@ describe("DataSheet 트리 확장", () => {
538
538
  { name: "폴더B" },
539
539
  ];
540
540
 
541
- it("getChildren 설정 확장 기능 컬럼이 렌더링된다", () => {
541
+ it("renders expand column when getChildren is set", () => {
542
542
  const { container } = render(() => (
543
543
  <ConfigProvider clientName="test"><I18nProvider>
544
544
  <TestWrapper>
@@ -551,16 +551,16 @@ describe("DataSheet 트리 확장", () => {
551
551
  </I18nProvider></ConfigProvider>
552
552
  ));
553
553
 
554
- // colgroup에 확장 컬럼 col 추가됨
554
+ // expand column col is added to colgroup
555
555
  const cols = container.querySelectorAll("colgroup col");
556
- expect(cols.length).toBe(2); // 확장 컬럼 + name 컬럼
556
+ expect(cols.length).toBe(2); // expand column + name column
557
557
 
558
- // 헤더에 확장 토글 버튼이 존재
558
+ // expand toggle button exists in header
559
559
  const expandBtn = container.querySelector("thead button");
560
560
  expect(expandBtn).toBeTruthy();
561
561
  });
562
562
 
563
- it("접힌 상태에서는 루트 항목만 표시된다", () => {
563
+ it("displays only root items in collapsed state", () => {
564
564
  const { container } = render(() => (
565
565
  <ConfigProvider clientName="test"><I18nProvider>
566
566
  <TestWrapper>
@@ -585,7 +585,7 @@ describe("DataSheet 트리 확장", () => {
585
585
  expect(Array.from(names).map((n) => n.textContent)).toEqual(["폴더A", "폴더B"]);
586
586
  });
587
587
 
588
- it("확장된 항목의 자식이 표시된다", () => {
588
+ it("displays children of expanded items", () => {
589
589
  const { container } = render(() => (
590
590
  <ConfigProvider clientName="test"><I18nProvider>
591
591
  <TestWrapper>
@@ -604,7 +604,7 @@ describe("DataSheet 트리 확장", () => {
604
604
  ));
605
605
 
606
606
  const rows = container.querySelectorAll("tbody tr");
607
- expect(rows.length).toBe(4); // 폴더A, 파일A1, 파일A2, 폴더B
607
+ expect(rows.length).toBe(4); // 폴더A, 파일A1, 파일A2, 폴더B (test data kept as-is)
608
608
 
609
609
  const names = container.querySelectorAll("tbody [data-testid='name']");
610
610
  expect(Array.from(names).map((n) => n.textContent)).toEqual([
@@ -615,7 +615,7 @@ describe("DataSheet 트리 확장", () => {
615
615
  ]);
616
616
  });
617
617
 
618
- it("자식이 없는 항목은 확장 아이콘이 숨겨진다", () => {
618
+ it("hides expand icon for items with no children", () => {
619
619
  const { container } = render(() => (
620
620
  <ConfigProvider clientName="test"><I18nProvider>
621
621
  <TestWrapper>
@@ -634,11 +634,11 @@ describe("DataSheet 트리 확장", () => {
634
634
  ));
635
635
 
636
636
  const tbodyRows = container.querySelectorAll("tbody tr");
637
- // 폴더B(인덱스 1)에는 자식이 없으므로 확장 버튼이 없어야
637
+ // 폴더B (index 1) has no children no expand button
638
638
  const secondRowBtns = tbodyRows[1].querySelectorAll("button");
639
639
  expect(secondRowBtns.length).toBe(0);
640
640
 
641
- // 폴더A(인덱스 0)에는 자식이 있으므로 확장 버튼이 있어야
641
+ // 폴더A (index 0) has children expand button exists
642
642
  const firstRowBtns = tbodyRows[0].querySelectorAll("button");
643
643
  expect(firstRowBtns.length).toBe(1);
644
644
  });
@@ -17,33 +17,33 @@ describe("Collapse", () => {
17
17
  });
18
18
 
19
19
  describe("rendering", () => {
20
- it("open={false}일 때 콘텐츠가 visibility:hidden", () => {
20
+ it("sets visibility:hidden when open={false}", () => {
21
21
  const { container } = render(() => <Collapse open={false}>Content</Collapse>);
22
22
  const contentDiv = container.querySelector("[data-collapse]")
23
23
  ?.firstElementChild as HTMLElement;
24
24
  expect(contentDiv.style.visibility).toBe("hidden");
25
25
  });
26
26
 
27
- it("open={true}일 때 콘텐츠가 visible", () => {
27
+ it("sets content visible when open={true}", () => {
28
28
  const { container } = render(() => <Collapse open={true}>Content</Collapse>);
29
29
  const contentDiv = container.querySelector("[data-collapse]")
30
30
  ?.firstElementChild as HTMLElement;
31
31
  expect(contentDiv.style.visibility).not.toBe("hidden");
32
32
  });
33
33
 
34
- it("open이 undefined false 처리 (visibility:hidden)", () => {
34
+ it("treats undefined open as false (visibility:hidden)", () => {
35
35
  const { container } = render(() => <Collapse>Content</Collapse>);
36
36
  const contentDiv = container.querySelector("[data-collapse]")
37
37
  ?.firstElementChild as HTMLElement;
38
38
  expect(contentDiv.style.visibility).toBe("hidden");
39
39
  });
40
40
 
41
- it("콘텐츠가 비어있어도 정상 렌더링", () => {
41
+ it("renders correctly with no children", () => {
42
42
  const { container } = render(() => <Collapse open={false} />);
43
43
  expect(container.firstChild).toBeTruthy();
44
44
  });
45
45
 
46
- it("추가 class가 병합됨", () => {
46
+ it("merges custom classes", () => {
47
47
  const { container } = render(() => (
48
48
  // eslint-disable-next-line tailwindcss/no-custom-classname
49
49
  <Collapse open={true} class="my-test-class">
@@ -51,14 +51,14 @@ describe("Collapse", () => {
51
51
  </Collapse>
52
52
  ));
53
53
  expect(container.querySelector(".my-test-class")).toBeTruthy();
54
- // overflow: hidden inline style로 적용됨
54
+ // overflow: hidden is applied as inline style
55
55
  const rootDiv = container.querySelector("[data-collapse]") as HTMLElement;
56
56
  expect(rootDiv.style.overflow).toBe("hidden");
57
57
  });
58
58
  });
59
59
 
60
- describe("margin-top 계산", () => {
61
- it("open={false}일 margin-top 콘텐츠 높이의 음수값", async () => {
60
+ describe("margin-top calculation", () => {
61
+ it("sets margin-top to negative content height when open={false}", async () => {
62
62
  const { container } = render(() => (
63
63
  <Collapse open={false}>
64
64
  <div style={{ height: "100px" }}>Content</div>
@@ -68,16 +68,16 @@ describe("Collapse", () => {
68
68
  ?.firstElementChild as HTMLElement;
69
69
  expect(contentDiv).toBeTruthy();
70
70
 
71
- // ResizeObserver 측정을 완료할 때까지 대기
71
+ // wait for ResizeObserver to finish measuring
72
72
  await waitFor(() => {
73
73
  const marginTop = contentDiv.style.marginTop;
74
- // margin-top 음수값인지 확인 (실제 높이 측정됨)
74
+ // verify margin-top is negative (actual height measured)
75
75
  expect(marginTop).toMatch(/^-\d+px$/);
76
76
  expect(parseInt(marginTop)).toBeLessThan(0);
77
77
  });
78
78
  });
79
79
 
80
- it("open={true}일 때 margin-top이 없음", () => {
80
+ it("clears margin-top when open={true}", () => {
81
81
  const { container } = render(() => (
82
82
  <Collapse open={true}>
83
83
  <div style={{ height: "100px" }}>Content</div>
@@ -89,14 +89,14 @@ describe("Collapse", () => {
89
89
  });
90
90
  });
91
91
 
92
- describe("초기 렌더링 transition", () => {
93
- it("마운트 transition 클래스가 적용됨", () => {
92
+ describe("initial render and transition", () => {
93
+ it("applies transition class after mount", () => {
94
94
  const { container } = render(() => <Collapse open={false}>Content</Collapse>);
95
95
  const contentDiv = container.querySelector("[data-collapse]")?.firstElementChild;
96
96
  expect(contentDiv?.classList.contains("transition-[margin-top]")).toBeTruthy();
97
97
  });
98
98
 
99
- it("open 상태 변경 시 transition class 유지되며 margin-top 변경", async () => {
99
+ it("retains transition class and updates margin-top on open state change", async () => {
100
100
  const [open, setOpen] = createSignal(false);
101
101
  const { container } = render(() => (
102
102
  <Collapse open={open()}>
@@ -107,17 +107,17 @@ describe("Collapse", () => {
107
107
  const contentDiv = container.querySelector("[data-collapse]")
108
108
  ?.firstElementChild as HTMLElement;
109
109
 
110
- // 초기 상태: 닫힘, margin-top 음수
110
+ // initial state: closed, negative margin-top
111
111
  await waitFor(() => {
112
112
  expect(parseInt(contentDiv.style.marginTop)).toBeLessThan(0);
113
113
  });
114
114
  expect(contentDiv.classList.contains("transition-[margin-top]")).toBeTruthy();
115
115
  expect(contentDiv.classList.contains("duration-200")).toBeTruthy();
116
116
 
117
- // 열림 상태로 변경
117
+ // change to open state
118
118
  setOpen(true);
119
119
 
120
- // transition class 유지, margin-top 변경
120
+ // transition class retained, margin-top updated
121
121
  await waitFor(() => {
122
122
  const marginTop = contentDiv.style.marginTop;
123
123
  expect(!marginTop || marginTop === "").toBeTruthy();
@@ -126,8 +126,8 @@ describe("Collapse", () => {
126
126
  });
127
127
  });
128
128
 
129
- describe("동적 상태 변경", () => {
130
- it("open 상태 변경 visibility 업데이트", () => {
129
+ describe("dynamic state changes", () => {
130
+ it("updates visibility when open state changes", () => {
131
131
  const [open, setOpen] = createSignal(false);
132
132
  const { container } = render(() => <Collapse open={open()}>Content</Collapse>);
133
133
 
@@ -139,7 +139,7 @@ describe("Collapse", () => {
139
139
  expect(contentDiv.style.visibility).not.toBe("hidden");
140
140
  });
141
141
 
142
- it("콘텐츠 높이 변경 시 margin-top 재계산", async () => {
142
+ it("recalculates margin-top when content height changes", async () => {
143
143
  const [showExtra, setShowExtra] = createSignal(false);
144
144
  const { container } = render(() => (
145
145
  <Collapse open={false}>
@@ -151,7 +151,7 @@ describe("Collapse", () => {
151
151
  const contentDiv = container.querySelector("[data-collapse]")
152
152
  ?.firstElementChild as HTMLElement;
153
153
 
154
- // 초기 높이 측정 대기
154
+ // wait for initial height measurement
155
155
  await waitFor(() => {
156
156
  const initialMarginTop = parseInt(contentDiv.style.marginTop);
157
157
  expect(initialMarginTop).toBeLessThan(0);
@@ -159,13 +159,13 @@ describe("Collapse", () => {
159
159
 
160
160
  const initialMarginTop = parseInt(contentDiv.style.marginTop);
161
161
 
162
- // 콘텐츠 추가로 높이 변경
162
+ // add content to change height
163
163
  setShowExtra(true);
164
164
 
165
- // ResizeObserver 높이를 감지하고 margin-top 재계산 대기
165
+ // wait for ResizeObserver to detect new height and recalculate margin-top
166
166
  await waitFor(() => {
167
167
  const newMarginTop = parseInt(contentDiv.style.marginTop);
168
- // margin-top 초기값보다 작아야 ( 큰 음수)
168
+ // new margin-top should be more negative than initial (larger content)
169
169
  expect(newMarginTop).toBeLessThan(initialMarginTop);
170
170
  });
171
171
  });