@simplysm/solid 13.0.70 → 13.0.72

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 (106) hide show
  1. package/README.md +1 -1
  2. package/dist/components/data/sheet/DataSheet.d.ts.map +1 -1
  3. package/dist/components/data/sheet/DataSheet.js +3 -6
  4. package/dist/components/data/sheet/DataSheet.js.map +1 -1
  5. package/dist/components/data/sheet/DataSheet.styles.d.ts.map +1 -1
  6. package/dist/components/data/sheet/DataSheet.styles.js +1 -1
  7. package/dist/components/data/sheet/DataSheet.styles.js.map +1 -1
  8. package/dist/components/disclosure/Dropdown.d.ts +6 -4
  9. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  10. package/dist/components/disclosure/Dropdown.js +24 -8
  11. package/dist/components/disclosure/Dropdown.js.map +2 -2
  12. package/dist/components/disclosure/dialogZIndex.d.ts +2 -0
  13. package/dist/components/disclosure/dialogZIndex.d.ts.map +1 -1
  14. package/dist/components/disclosure/dialogZIndex.js +4 -0
  15. package/dist/components/disclosure/dialogZIndex.js.map +1 -1
  16. package/dist/components/features/crud-detail/CrudDetail.d.ts.map +1 -1
  17. package/dist/components/features/crud-detail/CrudDetail.js +16 -7
  18. package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
  19. package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
  20. package/dist/components/features/crud-sheet/CrudSheet.js +14 -5
  21. package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
  22. package/dist/components/features/crudRegistry.d.ts +16 -0
  23. package/dist/components/features/crudRegistry.d.ts.map +1 -0
  24. package/dist/components/features/crudRegistry.js +37 -0
  25. package/dist/components/features/crudRegistry.js.map +6 -0
  26. package/dist/components/features/permission-table/PermissionTable.d.ts.map +1 -1
  27. package/dist/components/features/permission-table/PermissionTable.js +71 -86
  28. package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
  29. package/dist/components/features/shared-data/SharedDataSelect.js +2 -4
  30. package/dist/components/features/shared-data/SharedDataSelect.js.map +2 -2
  31. package/dist/components/features/shared-data/SharedDataSelectList.d.ts +2 -4
  32. package/dist/components/features/shared-data/SharedDataSelectList.d.ts.map +1 -1
  33. package/dist/components/features/shared-data/SharedDataSelectList.js +11 -46
  34. package/dist/components/features/shared-data/SharedDataSelectList.js.map +2 -2
  35. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  36. package/dist/components/form-control/select/Select.js +1 -1
  37. package/dist/components/form-control/select/Select.js.map +1 -1
  38. package/dist/helpers/createAppStructure.d.ts.map +1 -1
  39. package/dist/helpers/createAppStructure.js +3 -2
  40. package/dist/helpers/createAppStructure.js.map +1 -1
  41. package/dist/helpers/createHmrSafeContext.d.ts +3 -0
  42. package/dist/helpers/createHmrSafeContext.d.ts.map +1 -0
  43. package/dist/helpers/createHmrSafeContext.js +10 -0
  44. package/dist/helpers/createHmrSafeContext.js.map +6 -0
  45. package/dist/hooks/createSelectionGroup.d.ts.map +1 -1
  46. package/dist/hooks/createSelectionGroup.js +3 -2
  47. package/dist/hooks/createSelectionGroup.js.map +2 -2
  48. package/package.json +6 -5
  49. package/src/components/data/sheet/DataSheet.styles.ts +1 -1
  50. package/src/components/data/sheet/DataSheet.tsx +3 -4
  51. package/src/components/disclosure/Dropdown.tsx +31 -17
  52. package/src/components/disclosure/dialogZIndex.ts +5 -0
  53. package/src/components/features/crud-detail/CrudDetail.tsx +16 -5
  54. package/src/components/features/crud-sheet/CrudSheet.tsx +13 -3
  55. package/src/components/features/crudRegistry.ts +60 -0
  56. package/src/components/features/permission-table/PermissionTable.tsx +49 -46
  57. package/src/components/features/shared-data/SharedDataSelect.tsx +2 -2
  58. package/src/components/features/shared-data/SharedDataSelectList.tsx +11 -36
  59. package/src/components/form-control/select/Select.tsx +1 -5
  60. package/src/helpers/createAppStructure.ts +3 -2
  61. package/src/helpers/createHmrSafeContext.ts +8 -0
  62. package/src/hooks/createSelectionGroup.tsx +4 -2
  63. package/tests/components/data/List.spec.tsx +52 -52
  64. package/tests/components/data/Pagination.spec.tsx +43 -43
  65. package/tests/components/data/Table.spec.tsx +4 -4
  66. package/tests/components/data/kanban/Kanban.selection.spec.tsx +21 -21
  67. package/tests/components/data/sheet/DataSheet.spec.tsx +50 -50
  68. package/tests/components/disclosure/Collapse.spec.tsx +24 -24
  69. package/tests/components/disclosure/Dialog.spec.tsx +33 -33
  70. package/tests/components/disclosure/DialogProvider.spec.tsx +9 -9
  71. package/tests/components/disclosure/Dropdown.spec.tsx +134 -14
  72. package/tests/components/disclosure/Tabs.spec.tsx +21 -21
  73. package/tests/components/disclosure/dialogZIndex.spec.ts +45 -0
  74. package/tests/components/display/Alert.spec.tsx +4 -4
  75. package/tests/components/display/Barcode.spec.tsx +7 -7
  76. package/tests/components/display/Card.spec.tsx +3 -3
  77. package/tests/components/display/Link.spec.tsx +5 -5
  78. package/tests/components/display/Tag.spec.tsx +4 -4
  79. package/tests/components/features/address/AddressSearch.spec.tsx +3 -3
  80. package/tests/components/features/crudRegistry.spec.ts +119 -0
  81. package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +8 -8
  82. package/tests/components/features/permission-table/PermissionTable.spec.tsx +43 -43
  83. package/tests/components/features/shared-data/SharedDataSelectList.spec.tsx +2 -17
  84. package/tests/components/feedback/busy/BusyContainer.spec.tsx +7 -7
  85. package/tests/components/feedback/notification/NotificationBell.spec.tsx +9 -9
  86. package/tests/components/feedback/print/Print.spec.tsx +4 -4
  87. package/tests/components/form-control/Button.spec.tsx +18 -18
  88. package/tests/components/form-control/checkbox/Checkbox.spec.tsx +20 -20
  89. package/tests/components/form-control/checkbox/CheckboxGroup.spec.tsx +12 -12
  90. package/tests/components/form-control/checkbox/Radio.spec.tsx +21 -21
  91. package/tests/components/form-control/checkbox/RadioGroup.spec.tsx +12 -12
  92. package/tests/components/form-control/color-picker/ColorPicker.spec.tsx +10 -10
  93. package/tests/components/form-control/combobox/Combobox.spec.tsx +16 -16
  94. package/tests/components/form-control/combobox/ComboboxItem.spec.tsx +7 -7
  95. package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +24 -24
  96. package/tests/components/form-control/field/DatePicker.spec.tsx +50 -50
  97. package/tests/components/form-control/field/DateTimePicker.spec.tsx +47 -47
  98. package/tests/components/form-control/field/NumberInput.spec.tsx +54 -54
  99. package/tests/components/form-control/field/TextInput.spec.tsx +49 -49
  100. package/tests/components/form-control/field/Textarea.spec.tsx +33 -33
  101. package/tests/components/form-control/field/TimePicker.spec.tsx +42 -42
  102. package/tests/components/form-control/numpad/Numpad.spec.tsx +40 -40
  103. package/tests/components/form-control/select/Select.spec.tsx +9 -9
  104. package/tests/components/form-control/select/SelectItem.spec.tsx +10 -10
  105. package/tests/helpers/createAppStructure.spec.tsx +57 -57
  106. package/tests/helpers/mergeStyles.spec.ts +31 -31
@@ -2,9 +2,9 @@ import { render } from "@solidjs/testing-library";
2
2
  import { describe, it, expect } from "vitest";
3
3
  import { Table } from "../../../src/components/data/Table";
4
4
 
5
- describe("Table 컴포넌트", () => {
5
+ describe("Table component", () => {
6
6
  describe("basic rendering", () => {
7
- it("children Table 내부에 표시된다", () => {
7
+ it("renders children inside Table", () => {
8
8
  const { container } = render(() => (
9
9
  <Table>
10
10
  <tbody>
@@ -24,8 +24,8 @@ describe("Table 컴포넌트", () => {
24
24
  });
25
25
  });
26
26
 
27
- describe("inset 속성", () => {
28
- it("inset prop에 따라 스타일이 달라진다", () => {
27
+ describe("inset prop", () => {
28
+ it("applies different styles based on inset prop", () => {
29
29
  const { container: defaultContainer } = render(() => <Table>Content</Table>);
30
30
  const { container: insetContainer } = render(() => <Table inset>Content</Table>);
31
31
 
@@ -2,7 +2,7 @@ import { render, fireEvent } from "@solidjs/testing-library";
2
2
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
3
  import { Kanban } from "../../../../src/components/data/kanban/Kanban";
4
4
 
5
- describe("Kanban 선택 시스템", () => {
5
+ describe("Kanban selection system", () => {
6
6
  function renderKanban(options?: {
7
7
  selectedValues?: unknown[];
8
8
  onSelectedValuesChange?: (v: unknown[]) => void;
@@ -36,8 +36,8 @@ describe("Kanban 선택 시스템", () => {
36
36
  ));
37
37
  }
38
38
 
39
- describe("Shift+Click 선택", () => {
40
- it("Shift+Click으로 카드가 선택된다", () => {
39
+ describe("Shift+Click selection", () => {
40
+ it("selects a card with Shift+Click", () => {
41
41
  const handleChange = vi.fn();
42
42
  const { getByText } = renderKanban({
43
43
  selectedValues: [],
@@ -48,7 +48,7 @@ describe("Kanban 선택 시스템", () => {
48
48
  expect(handleChange).toHaveBeenCalledWith([1]);
49
49
  });
50
50
 
51
- it("이미 선택된 카드를 Shift+Click하면 선택 해제된다", () => {
51
+ it("deselects an already selected card with Shift+Click", () => {
52
52
  const handleChange = vi.fn();
53
53
  const { getByText } = renderKanban({
54
54
  selectedValues: [1],
@@ -59,7 +59,7 @@ describe("Kanban 선택 시스템", () => {
59
59
  expect(handleChange).toHaveBeenCalledWith([]);
60
60
  });
61
61
 
62
- it("Shift 없는 클릭은 선택을 변경하지 않는다", () => {
62
+ it("click without Shift does not change selection", () => {
63
63
  const handleChange = vi.fn();
64
64
  const { getByText } = renderKanban({
65
65
  selectedValues: [],
@@ -70,7 +70,7 @@ describe("Kanban 선택 시스템", () => {
70
70
  expect(handleChange).not.toHaveBeenCalled();
71
71
  });
72
72
 
73
- it("selectable=false 카드는 Shift+Click해도 선택되지 않는다", () => {
73
+ it("does not select card with selectable=false on Shift+Click", () => {
74
74
  const handleChange = vi.fn();
75
75
  const { getByText } = renderKanban({
76
76
  selectedValues: [],
@@ -84,8 +84,8 @@ describe("Kanban 선택 시스템", () => {
84
84
  });
85
85
  });
86
86
 
87
- describe("선택 시각 피드백", () => {
88
- it("선택된 카드에 ring + shadow 클래스가 적용된다", () => {
87
+ describe("selection visual feedback", () => {
88
+ it("applies ring + shadow classes to selected card", () => {
89
89
  const { getByText } = renderKanban({ selectedValues: [1] });
90
90
  const card1Content = getByText("Card 1").closest("[data-card]") as HTMLElement;
91
91
  expect(card1Content.classList.contains("ring-2")).toBe(true);
@@ -93,21 +93,21 @@ describe("Kanban 선택 시스템", () => {
93
93
  expect(card1Content.classList.contains("shadow-md")).toBe(true);
94
94
  });
95
95
 
96
- it("선택되지 않은 카드에는 ring 클래스가 없다", () => {
96
+ it("unselected card has no ring class", () => {
97
97
  const { getByText } = renderKanban({ selectedValues: [1] });
98
98
  const card2Content = getByText("Card 2").closest("[data-card]") as HTMLElement;
99
99
  expect(card2Content.classList.contains("ring-2")).toBe(false);
100
100
  });
101
101
  });
102
102
 
103
- describe("레인별 전체 선택 체크박스", () => {
104
- it("selectable 카드가 있는 레인에 전체 선택 체크박스가 표시된다", () => {
103
+ describe("per-lane select-all checkbox", () => {
104
+ it("displays select-all checkbox in lanes that have selectable cards", () => {
105
105
  const { container } = renderKanban({ selectedValues: [] });
106
106
  const checkboxes = container.querySelectorAll("[role='checkbox']");
107
107
  expect(checkboxes.length).toBe(2);
108
108
  });
109
109
 
110
- it("전체 선택 체크박스 클릭 레인 모든 selectable 카드가 선택된다", () => {
110
+ it("selects all selectable cards in lane when select-all checkbox is clicked", () => {
111
111
  const handleChange = vi.fn();
112
112
  const { container } = renderKanban({
113
113
  selectedValues: [],
@@ -119,7 +119,7 @@ describe("Kanban 선택 시스템", () => {
119
119
  expect(handleChange).toHaveBeenCalledWith([1, 2]);
120
120
  });
121
121
 
122
- it("전체 선택 상태에서 체크박스 클릭 레인 카드만 선택 해제된다", () => {
122
+ it("deselects only cards in the lane when clicking checkbox in fully-selected state", () => {
123
123
  const handleChange = vi.fn();
124
124
  const { container } = renderKanban({
125
125
  selectedValues: [1, 2, 4],
@@ -131,14 +131,14 @@ describe("Kanban 선택 시스템", () => {
131
131
  expect(handleChange).toHaveBeenCalledWith([4]);
132
132
  });
133
133
 
134
- it("모든 selectable 카드가 선택되면 체크박스가 체크 상태이다", () => {
134
+ it("checkbox is checked when all selectable cards are selected", () => {
135
135
  const { container } = renderKanban({ selectedValues: [1, 2] });
136
136
  const firstLane = container.querySelector("[data-kanban-lane]") as HTMLElement;
137
137
  const checkbox = firstLane.querySelector("[role='checkbox']") as HTMLElement;
138
138
  expect(checkbox.getAttribute("aria-checked")).toBe("true");
139
139
  });
140
140
 
141
- it("일부만 선택되면 체크박스가 미체크 상태이다", () => {
141
+ it("checkbox is unchecked when only some cards are selected", () => {
142
142
  const { container } = renderKanban({ selectedValues: [1] });
143
143
  const firstLane = container.querySelector("[data-kanban-lane]") as HTMLElement;
144
144
  const checkbox = firstLane.querySelector("[role='checkbox']") as HTMLElement;
@@ -146,7 +146,7 @@ describe("Kanban 선택 시스템", () => {
146
146
  });
147
147
  });
148
148
 
149
- describe("Long press 단독 선택", () => {
149
+ describe("long press exclusive selection", () => {
150
150
  beforeEach(() => {
151
151
  vi.useFakeTimers();
152
152
  });
@@ -155,7 +155,7 @@ describe("Kanban 선택 시스템", () => {
155
155
  vi.useRealTimers();
156
156
  });
157
157
 
158
- it("500ms 이상 누르면 해당 카드만 단독 선택된다", () => {
158
+ it("exclusively selects that card when pressed for 500ms or more", () => {
159
159
  const handleChange = vi.fn();
160
160
  const { getByText } = renderKanban({
161
161
  selectedValues: [2, 4],
@@ -169,7 +169,7 @@ describe("Kanban 선택 시스템", () => {
169
169
  expect(handleChange).toHaveBeenCalledWith([1]);
170
170
  });
171
171
 
172
- it("500ms 미만으로 누르면 선택이 변경되지 않는다", () => {
172
+ it("does not change selection when pressed for less than 500ms", () => {
173
173
  const handleChange = vi.fn();
174
174
  const { getByText } = renderKanban({
175
175
  selectedValues: [],
@@ -184,7 +184,7 @@ describe("Kanban 선택 시스템", () => {
184
184
  expect(handleChange).not.toHaveBeenCalled();
185
185
  });
186
186
 
187
- it("selectable=false 카드는 long press해도 선택되지 않는다", () => {
187
+ it("does not select card with selectable=false on long press", () => {
188
188
  const handleChange = vi.fn();
189
189
  const { getByText } = renderKanban({
190
190
  selectedValues: [],
@@ -201,8 +201,8 @@ describe("Kanban 선택 시스템", () => {
201
201
  });
202
202
  });
203
203
 
204
- describe("Uncontrolled 모드", () => {
205
- it("onSelectedValuesChange 없이도 Shift+Click 선택이 동작한다", () => {
204
+ describe("uncontrolled mode", () => {
205
+ it("Shift+Click selection works without onSelectedValuesChange", () => {
206
206
  const { getByText } = renderKanban();
207
207
  const card1 = getByText("Card 1").closest("[data-kanban-card]") as HTMLElement;
208
208
  fireEvent.click(card1, { shiftKey: true });
@@ -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
  });