@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
@@ -4,63 +4,63 @@ import { createSignal } from "solid-js";
4
4
  import { Time } from "@simplysm/core-common";
5
5
  import { TimePicker } from "../../../../src/components/form-control/field/TimePicker";
6
6
 
7
- describe("TimePicker 컴포넌트", () => {
7
+ describe("TimePicker component", () => {
8
8
  describe("basic rendering", () => {
9
- it("unit=minute일 input type=time 렌더링된다", () => {
9
+ it("renders input type=time when unit=minute", () => {
10
10
  const { container } = render(() => <TimePicker unit="minute" />);
11
11
  const input = container.querySelector("input") as HTMLInputElement;
12
12
  expect(input).toBeTruthy();
13
13
  expect(input.type).toBe("time");
14
14
  });
15
15
 
16
- it("unit=second일 input type=time 렌더링된다", () => {
16
+ it("renders input type=time when unit=second", () => {
17
17
  const { container } = render(() => <TimePicker unit="second" />);
18
18
  const input = container.querySelector("input") as HTMLInputElement;
19
19
  expect(input).toBeTruthy();
20
20
  expect(input.type).toBe("time");
21
21
  });
22
22
 
23
- it("기본 unit minute이다", () => {
23
+ it("defaults unit to minute", () => {
24
24
  const { container } = render(() => <TimePicker />);
25
25
  const input = container.querySelector("input") as HTMLInputElement;
26
26
  expect(input.type).toBe("time");
27
27
  });
28
28
 
29
- it("autocomplete 기본값은 one-time-code이다", () => {
29
+ it("defaults autocomplete to one-time-code", () => {
30
30
  const { container } = render(() => <TimePicker />);
31
31
  const input = container.querySelector("input") as HTMLInputElement;
32
32
  expect(input.autocomplete).toBe("one-time-code");
33
33
  });
34
34
 
35
- it("unit=second일 step=1 설정된다", () => {
35
+ it("sets step=1 when unit=second", () => {
36
36
  const { container } = render(() => <TimePicker unit="second" />);
37
37
  const input = container.querySelector("input") as HTMLInputElement;
38
38
  expect(input.step).toBe("1");
39
39
  });
40
40
 
41
- it("unit=minute일 step 설정되지 않는다", () => {
41
+ it("does not set step when unit=minute", () => {
42
42
  const { container } = render(() => <TimePicker unit="minute" />);
43
43
  const input = container.querySelector("input") as HTMLInputElement;
44
44
  expect(input.step).toBe("");
45
45
  });
46
46
  });
47
47
 
48
- describe(" 변환", () => {
49
- it("Time 값이 minute 단위에서 HH:mm 형식으로 표시된다", () => {
48
+ describe("value conversion", () => {
49
+ it("displays Time in HH:mm format for minute unit", () => {
50
50
  const time = new Time(10, 30, 45);
51
51
  const { container } = render(() => <TimePicker unit="minute" value={time} />);
52
52
  const input = container.querySelector("input") as HTMLInputElement;
53
53
  expect(input.value).toBe("10:30");
54
54
  });
55
55
 
56
- it("Time 값이 second 단위에서 HH:mm:ss 형식으로 표시된다", () => {
56
+ it("displays Time in HH:mm:ss format for second unit", () => {
57
57
  const time = new Time(10, 30, 45);
58
58
  const { container } = render(() => <TimePicker unit="second" value={time} />);
59
59
  const input = container.querySelector("input") as HTMLInputElement;
60
60
  expect(input.value).toBe("10:30:45");
61
61
  });
62
62
 
63
- it("minute 단위 입력 Time으로 변환되어 onValueChange로 전달된다", () => {
63
+ it("passes Time converted from minute input to onValueChange", () => {
64
64
  const handleChange = vi.fn();
65
65
  const { container } = render(() => <TimePicker unit="minute" onValueChange={handleChange} />);
66
66
  const input = container.querySelector("input") as HTMLInputElement;
@@ -74,7 +74,7 @@ describe("TimePicker 컴포넌트", () => {
74
74
  expect(result.second).toBe(0);
75
75
  });
76
76
 
77
- it("second 단위 입력 Time으로 변환되어 onValueChange로 전달된다", () => {
77
+ it("passes Time converted from second input to onValueChange", () => {
78
78
  const handleChange = vi.fn();
79
79
  const { container } = render(() => <TimePicker unit="second" onValueChange={handleChange} />);
80
80
  const input = container.querySelector("input") as HTMLInputElement;
@@ -88,7 +88,7 @@ describe("TimePicker 컴포넌트", () => {
88
88
  expect(result.second).toBe(45);
89
89
  });
90
90
 
91
- it(" 입력 undefined가 onValueChange로 전달된다", () => {
91
+ it("passes undefined to onValueChange on empty input", () => {
92
92
  const handleChange = vi.fn();
93
93
  const time = new Time(10, 30, 45);
94
94
  const { container } = render(() => (
@@ -102,8 +102,8 @@ describe("TimePicker 컴포넌트", () => {
102
102
  });
103
103
  });
104
104
 
105
- describe("controlled 패턴", () => {
106
- it("외부 상태 변경 input 값이 업데이트된다", () => {
105
+ describe("controlled pattern", () => {
106
+ it("updates input value when external state changes", () => {
107
107
  const [value, setValue] = createSignal<Time | undefined>(new Time(10, 0, 0));
108
108
  const { container } = render(() => (
109
109
  <TimePicker unit="minute" value={value()} onValueChange={setValue} />
@@ -117,8 +117,8 @@ describe("TimePicker 컴포넌트", () => {
117
117
  });
118
118
  });
119
119
 
120
- describe("uncontrolled 패턴", () => {
121
- it("onValueChange 없이 내부 상태로 값이 관리된다", () => {
120
+ describe("uncontrolled pattern", () => {
121
+ it("manages value internally without onValueChange", () => {
122
122
  const { container } = render(() => <TimePicker unit="minute" value={new Time(10, 0, 0)} />);
123
123
  const input = container.querySelector("input") as HTMLInputElement;
124
124
 
@@ -130,7 +130,7 @@ describe("TimePicker 컴포넌트", () => {
130
130
  });
131
131
 
132
132
  describe("disabled state", () => {
133
- it("disabled=true일 div 렌더링된다", () => {
133
+ it("renders as div when disabled=true", () => {
134
134
  const { container } = render(() => <TimePicker disabled value={new Time(10, 30, 0)} />);
135
135
  const input = container.querySelector("input:not([aria-hidden])");
136
136
  const div = container.querySelector("div.sd-time-field");
@@ -139,22 +139,22 @@ describe("TimePicker 컴포넌트", () => {
139
139
  expect(div).toBeTruthy();
140
140
  });
141
141
 
142
- it("disabled 상태에서 value 표시된다", () => {
142
+ it("displays value when disabled", () => {
143
143
  const { getByText } = render(() => (
144
144
  <TimePicker unit="minute" disabled value={new Time(10, 30, 0)} />
145
145
  ));
146
146
  expect(getByText("10:30")).toBeTruthy();
147
147
  });
148
148
 
149
- it("disabled 스타일이 적용된다", () => {
149
+ it("applies disabled style", () => {
150
150
  const { container } = render(() => <TimePicker disabled value={new Time(10, 30, 0)} />);
151
151
  const div = container.querySelector("div.sd-time-field") as HTMLElement;
152
152
  expect(div.classList.contains("bg-base-100")).toBe(true);
153
153
  });
154
154
  });
155
155
 
156
- describe("readonly 상태", () => {
157
- it("readonly=true일 div 렌더링된다", () => {
156
+ describe("readonly state", () => {
157
+ it("renders as div when readonly=true", () => {
158
158
  const { container } = render(() => <TimePicker readonly value={new Time(10, 30, 0)} />);
159
159
  const input = container.querySelector("input:not([aria-hidden])");
160
160
  const div = container.querySelector("div.sd-time-field");
@@ -163,7 +163,7 @@ describe("TimePicker 컴포넌트", () => {
163
163
  expect(div).toBeTruthy();
164
164
  });
165
165
 
166
- it("readonly 상태에서 value 표시된다", () => {
166
+ it("displays value when readonly", () => {
167
167
  const { getByText } = render(() => (
168
168
  <TimePicker unit="minute" readonly value={new Time(10, 30, 0)} />
169
169
  ));
@@ -171,22 +171,22 @@ describe("TimePicker 컴포넌트", () => {
171
171
  });
172
172
  });
173
173
 
174
- describe("size 옵션", () => {
175
- it("size=sm일 작은 padding 적용된다", () => {
174
+ describe("size option", () => {
175
+ it("applies small padding when size=sm", () => {
176
176
  const { container } = render(() => <TimePicker size="sm" />);
177
177
  const wrapper = container.firstChild as HTMLElement;
178
178
  expect(wrapper.classList.contains("py-0.5")).toBe(true);
179
179
  });
180
180
 
181
- it("size=lg일 padding 적용된다", () => {
181
+ it("applies large padding when size=lg", () => {
182
182
  const { container } = render(() => <TimePicker size="lg" />);
183
183
  const wrapper = container.firstChild as HTMLElement;
184
184
  expect(wrapper.classList.contains("py-2")).toBe(true);
185
185
  });
186
186
  });
187
187
 
188
- describe("inset 스타일", () => {
189
- it("inset=true일 outer div에는 relative만, content div에 inset 스타일이 적용된다", () => {
188
+ describe("inset style", () => {
189
+ it("applies relative to outer and inset style to content when inset=true", () => {
190
190
  const { container } = render(() => <TimePicker inset />);
191
191
  const outer = container.firstChild as HTMLElement;
192
192
  expect(outer.classList.contains("relative")).toBe(true);
@@ -197,7 +197,7 @@ describe("TimePicker 컴포넌트", () => {
197
197
  expect(contentDiv.classList.contains("bg-primary-50")).toBe(true);
198
198
  });
199
199
 
200
- it("inset + readonly일 때 content div 보이고 input 없다", () => {
200
+ it("shows content div and no input when inset + readonly", () => {
201
201
  const { container } = render(() => <TimePicker inset readonly value={new Time(14, 30, 0)} />);
202
202
  const outer = container.firstChild as HTMLElement;
203
203
  expect(outer.classList.contains("relative")).toBe(true);
@@ -209,7 +209,7 @@ describe("TimePicker 컴포넌트", () => {
209
209
  expect(outer.querySelector("input:not([aria-hidden])")).toBeFalsy();
210
210
  });
211
211
 
212
- it("inset + editable일 때 content div(hidden)와 input 모두 존재한다", () => {
212
+ it("shows hidden content div and input when inset + editable", () => {
213
213
  const { container } = render(() => <TimePicker inset value={new Time(14, 30, 0)} />);
214
214
  const outer = container.firstChild as HTMLElement;
215
215
 
@@ -220,7 +220,7 @@ describe("TimePicker 컴포넌트", () => {
220
220
  expect(outer.querySelector("input")).toBeTruthy();
221
221
  });
222
222
 
223
- it("inset + 값일 때 content div NBSP가 표시된다", () => {
223
+ it("shows NBSP in content div when inset + empty value", () => {
224
224
  const { container } = render(() => <TimePicker inset readonly />);
225
225
  const outer = container.firstChild as HTMLElement;
226
226
  const contentDiv = outer.querySelector("[data-time-field-content]") as HTMLElement;
@@ -228,14 +228,14 @@ describe("TimePicker 컴포넌트", () => {
228
228
  });
229
229
  });
230
230
 
231
- describe("다크 모드 스타일", () => {
232
- it("다크 모드 border 스타일이 적용된다", () => {
231
+ describe("dark mode style", () => {
232
+ it("applies dark mode border style", () => {
233
233
  const { container } = render(() => <TimePicker />);
234
234
  const wrapper = container.firstChild as HTMLElement;
235
235
  expect(wrapper.classList.contains("dark:border-base-700")).toBe(true);
236
236
  });
237
237
 
238
- it("다크 모드 background 스타일이 적용된다", () => {
238
+ it("applies dark mode background style", () => {
239
239
  const { container } = render(() => <TimePicker />);
240
240
  const wrapper = container.firstChild as HTMLElement;
241
241
  expect(wrapper.classList.contains("dark:bg-primary-950/30")).toBe(true);
@@ -243,7 +243,7 @@ describe("TimePicker 컴포넌트", () => {
243
243
  });
244
244
 
245
245
  describe("class merging", () => {
246
- it("사용자 정의 class 기존 스타일과 병합된다", () => {
246
+ it("merges custom class with existing styles", () => {
247
247
  // eslint-disable-next-line tailwindcss/no-custom-classname
248
248
  const { container } = render(() => <TimePicker class="my-custom-class" />);
249
249
  const wrapper = container.firstChild as HTMLElement;
@@ -258,13 +258,13 @@ describe("TimePicker 컴포넌트", () => {
258
258
  expect(hiddenInput.validationMessage).toBe("This field is required");
259
259
  });
260
260
 
261
- it("required 값이 있으면 유효하다", () => {
261
+ it("is valid when required and value exists", () => {
262
262
  const { container } = render(() => <TimePicker required value={new Time(10, 0, 0)} />);
263
263
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
264
264
  expect(hiddenInput.validity.valid).toBe(true);
265
265
  });
266
266
 
267
- it("min 위반 에러 메시지가 설정된다", () => {
267
+ it("sets error message when min is violated", () => {
268
268
  const { container } = render(() => (
269
269
  <TimePicker min={new Time(12, 0, 0)} value={new Time(8, 0, 0)} />
270
270
  ));
@@ -272,7 +272,7 @@ describe("TimePicker 컴포넌트", () => {
272
272
  expect(hiddenInput.validationMessage).not.toBe("");
273
273
  });
274
274
 
275
- it("min 충족 시 유효하다", () => {
275
+ it("is valid when min is satisfied", () => {
276
276
  const { container } = render(() => (
277
277
  <TimePicker min={new Time(8, 0, 0)} value={new Time(12, 0, 0)} />
278
278
  ));
@@ -280,7 +280,7 @@ describe("TimePicker 컴포넌트", () => {
280
280
  expect(hiddenInput.validity.valid).toBe(true);
281
281
  });
282
282
 
283
- it("max 위반 에러 메시지가 설정된다", () => {
283
+ it("sets error message when max is violated", () => {
284
284
  const { container } = render(() => (
285
285
  <TimePicker max={new Time(12, 0, 0)} value={new Time(18, 0, 0)} />
286
286
  ));
@@ -288,7 +288,7 @@ describe("TimePicker 컴포넌트", () => {
288
288
  expect(hiddenInput.validationMessage).not.toBe("");
289
289
  });
290
290
 
291
- it("max 충족 시 유효하다", () => {
291
+ it("is valid when max is satisfied", () => {
292
292
  const { container } = render(() => (
293
293
  <TimePicker max={new Time(23, 59, 59)} value={new Time(12, 0, 0)} />
294
294
  ));
@@ -296,7 +296,7 @@ describe("TimePicker 컴포넌트", () => {
296
296
  expect(hiddenInput.validity.valid).toBe(true);
297
297
  });
298
298
 
299
- it("validate 함수가 에러를 반환하면 에러 메시지가 설정된다", () => {
299
+ it("sets error message returned by validate function", () => {
300
300
  const { container } = render(() => (
301
301
  <TimePicker value={new Time(10, 0, 0)} validate={() => "커스텀 에러"} />
302
302
  ));
@@ -304,7 +304,7 @@ describe("TimePicker 컴포넌트", () => {
304
304
  expect(hiddenInput.validationMessage).toBe("커스텀 에러");
305
305
  });
306
306
 
307
- it("validate 함수가 undefined를 반환하면 유효하다", () => {
307
+ it("is valid when validate function returns undefined", () => {
308
308
  const { container } = render(() => (
309
309
  <TimePicker value={new Time(10, 0, 0)} validate={() => undefined} />
310
310
  ));
@@ -5,13 +5,13 @@ import { Numpad } from "../../../../src/components/form-control/numpad/Numpad";
5
5
 
6
6
  describe("Numpad", () => {
7
7
  describe("basic rendering", () => {
8
- it("data-numpad 속성이 있는 루트 요소를 렌더링한다", () => {
8
+ it("renders root element with data-numpad attribute", () => {
9
9
  const { container } = render(() => <Numpad />);
10
10
  const root = container.querySelector("[data-numpad]");
11
11
  expect(root).toBeTruthy();
12
12
  });
13
13
 
14
- it("숫자 버튼 0-9가 렌더링된다", () => {
14
+ it("renders digit buttons 0-9", () => {
15
15
  render(() => <Numpad />);
16
16
 
17
17
  for (let i = 0; i <= 9; i++) {
@@ -19,18 +19,18 @@ describe("Numpad", () => {
19
19
  }
20
20
  });
21
21
 
22
- it("소수점 버튼이 렌더링된다", () => {
22
+ it("renders decimal point button", () => {
23
23
  render(() => <Numpad />);
24
24
  expect(screen.getByText(".")).toBeInTheDocument();
25
25
  });
26
26
 
27
- it("NumberInput(input)가 렌더링된다", () => {
27
+ it("renders NumberInput (input)", () => {
28
28
  render(() => <Numpad />);
29
29
  const input = screen.getByRole("textbox");
30
30
  expect(input).toBeInTheDocument();
31
31
  });
32
32
 
33
- it("C 버튼이 text-danger-500 클래스로 렌더링된다", () => {
33
+ it("renders C button with text-danger-500 class", () => {
34
34
  render(() => <Numpad />);
35
35
  const cButton = screen
36
36
  .getAllByRole("button")
@@ -38,7 +38,7 @@ describe("Numpad", () => {
38
38
  expect(cButton).toBeTruthy();
39
39
  });
40
40
 
41
- it("BS 버튼이 text-warning-500 클래스로 렌더링된다", () => {
41
+ it("renders BS button with text-warning-500 class", () => {
42
42
  render(() => <Numpad />);
43
43
  const bsButton = screen
44
44
  .getAllByRole("button")
@@ -46,21 +46,21 @@ describe("Numpad", () => {
46
46
  expect(bsButton).toBeTruthy();
47
47
  });
48
48
 
49
- it("ENT 버튼은 기본적으로 렌더링되지 않는다", () => {
49
+ it("does not render ENT button by default", () => {
50
50
  render(() => <Numpad />);
51
51
  expect(screen.queryByText("ENT")).not.toBeInTheDocument();
52
52
  });
53
53
 
54
- it("- 버튼은 기본적으로 렌더링되지 않는다", () => {
54
+ it("does not render minus button by default", () => {
55
55
  render(() => <Numpad />);
56
- // useMinusButton 없으면 "-" 텍스트를 가진 버튼이 없어야 한다
56
+ // without useMinusButton, there should be no button with "-" text
57
57
  const minusButton = screen.getAllByRole("button").find((btn) => btn.textContent === "-");
58
58
  expect(minusButton).toBeFalsy();
59
59
  });
60
60
  });
61
61
 
62
- describe("숫자 입력", () => {
63
- it("숫자 버튼 클릭 값이 업데이트된다", () => {
62
+ describe("digit input", () => {
63
+ it("updates value on digit button click", () => {
64
64
  const handleChange = vi.fn();
65
65
  render(() => <Numpad onValueChange={handleChange} />);
66
66
 
@@ -71,7 +71,7 @@ describe("Numpad", () => {
71
71
  expect(handleChange).toHaveBeenLastCalledWith(123);
72
72
  });
73
73
 
74
- it("소수점 버튼 클릭 소수점이 추가된다", () => {
74
+ it("appends decimal point on decimal button click", () => {
75
75
  const handleChange = vi.fn();
76
76
  render(() => <Numpad onValueChange={handleChange} />);
77
77
 
@@ -82,19 +82,19 @@ describe("Numpad", () => {
82
82
  expect(handleChange).toHaveBeenLastCalledWith(1.5);
83
83
  });
84
84
 
85
- it("소수점 중복 입력은 무시된다", () => {
85
+ it("ignores duplicate decimal point", () => {
86
86
  const handleChange = vi.fn();
87
87
  render(() => <Numpad onValueChange={handleChange} />);
88
88
 
89
89
  fireEvent.click(screen.getByText("1"));
90
90
  fireEvent.click(screen.getByText("."));
91
- fireEvent.click(screen.getByText(".")); // 중복 소수점
91
+ fireEvent.click(screen.getByText(".")); // duplicate decimal
92
92
  fireEvent.click(screen.getByText("5"));
93
93
 
94
94
  expect(handleChange).toHaveBeenLastCalledWith(1.5);
95
95
  });
96
96
 
97
- it("0 버튼을 여러 클릭할 수 있다", () => {
97
+ it("allows clicking 0 multiple times", () => {
98
98
  const handleChange = vi.fn();
99
99
  render(() => <Numpad onValueChange={handleChange} />);
100
100
 
@@ -106,16 +106,16 @@ describe("Numpad", () => {
106
106
  });
107
107
  });
108
108
 
109
- describe("기능 버튼", () => {
110
- it("C 버튼 클릭 값이 초기화된다", () => {
109
+ describe("function buttons", () => {
110
+ it("clears value on C button click", () => {
111
111
  const handleChange = vi.fn();
112
112
  render(() => <Numpad onValueChange={handleChange} />);
113
113
 
114
- // 입력
114
+ // enter value
115
115
  fireEvent.click(screen.getByText("5"));
116
116
  fireEvent.click(screen.getByText("6"));
117
117
 
118
- // C 버튼 클릭
118
+ // click C button
119
119
  const cButton = screen
120
120
  .getAllByRole("button")
121
121
  .find((btn) => btn.className.includes("text-danger-500"))!;
@@ -124,16 +124,16 @@ describe("Numpad", () => {
124
124
  expect(handleChange).toHaveBeenLastCalledWith(undefined);
125
125
  });
126
126
 
127
- it("BS 버튼 클릭 마지막 문자가 제거된다", () => {
127
+ it("removes last character on BS button click", () => {
128
128
  const handleChange = vi.fn();
129
129
  render(() => <Numpad onValueChange={handleChange} />);
130
130
 
131
- // 123 입력
131
+ // enter 123
132
132
  fireEvent.click(screen.getByText("1"));
133
133
  fireEvent.click(screen.getByText("2"));
134
134
  fireEvent.click(screen.getByText("3"));
135
135
 
136
- // BS 버튼 클릭
136
+ // click BS button
137
137
  const bsButton = screen
138
138
  .getAllByRole("button")
139
139
  .find((btn) => btn.className.includes("text-warning-500"))!;
@@ -142,14 +142,14 @@ describe("Numpad", () => {
142
142
  expect(handleChange).toHaveBeenLastCalledWith(12);
143
143
  });
144
144
 
145
- it("BS 버튼으로 모든 문자를 제거하면 undefined가 된다", () => {
145
+ it("returns undefined when all characters are removed with BS", () => {
146
146
  const handleChange = vi.fn();
147
147
  render(() => <Numpad onValueChange={handleChange} />);
148
148
 
149
- // 1 입력
149
+ // enter 1
150
150
  fireEvent.click(screen.getByText("1"));
151
151
 
152
- // BS 버튼 클릭
152
+ // click BS button
153
153
  const bsButton = screen
154
154
  .getAllByRole("button")
155
155
  .find((btn) => btn.className.includes("text-warning-500"))!;
@@ -159,13 +159,13 @@ describe("Numpad", () => {
159
159
  });
160
160
  });
161
161
 
162
- describe("ENT 버튼", () => {
163
- it("useEnterButton=true일 ENT 버튼이 렌더링된다", () => {
162
+ describe("ENT button", () => {
163
+ it("renders ENT button when useEnterButton=true", () => {
164
164
  render(() => <Numpad useEnterButton />);
165
165
  expect(screen.getByText("ENT")).toBeInTheDocument();
166
166
  });
167
167
 
168
- it("ENT 버튼 클릭 onEnterButtonClick이 호출된다", () => {
168
+ it("calls onEnterButtonClick on ENT button click", () => {
169
169
  const handleEnter = vi.fn();
170
170
  render(() => <Numpad useEnterButton onEnterButtonClick={handleEnter} />);
171
171
 
@@ -173,14 +173,14 @@ describe("Numpad", () => {
173
173
  expect(handleEnter).toHaveBeenCalledTimes(1);
174
174
  });
175
175
 
176
- it("required이고 값이 없을 ENT 버튼이 비활성화된다", () => {
176
+ it("disables ENT button when required and no value", () => {
177
177
  render(() => <Numpad useEnterButton required />);
178
178
 
179
179
  const entButton = screen.getByText("ENT").closest("button")!;
180
180
  expect(entButton.disabled).toBe(true);
181
181
  });
182
182
 
183
- it("required이고 값이 있을 ENT 버튼이 활성화된다", () => {
183
+ it("enables ENT button when required and value exists", () => {
184
184
  render(() => <Numpad useEnterButton required value={123} />);
185
185
 
186
186
  const entButton = screen.getByText("ENT").closest("button")!;
@@ -188,33 +188,33 @@ describe("Numpad", () => {
188
188
  });
189
189
  });
190
190
 
191
- describe("Minus 버튼", () => {
192
- it("useMinusButton=true일 - 버튼이 렌더링된다", () => {
191
+ describe("minus button", () => {
192
+ it("renders minus button when useMinusButton=true", () => {
193
193
  render(() => <Numpad useMinusButton />);
194
194
 
195
195
  const minusButton = screen.getAllByRole("button").find((btn) => btn.textContent === "-");
196
196
  expect(minusButton).toBeTruthy();
197
197
  });
198
198
 
199
- it("- 버튼 클릭 부호가 토글된다 (양수 -> 음수)", () => {
199
+ it("toggles sign on minus button click (positive to negative)", () => {
200
200
  const handleChange = vi.fn();
201
201
  render(() => <Numpad useMinusButton onValueChange={handleChange} />);
202
202
 
203
- // 5 입력
203
+ // enter 5
204
204
  fireEvent.click(screen.getByText("5"));
205
205
 
206
- // - 버튼 클릭
206
+ // click minus button
207
207
  const minusButton = screen.getAllByRole("button").find((btn) => btn.textContent === "-")!;
208
208
  fireEvent.click(minusButton);
209
209
 
210
210
  expect(handleChange).toHaveBeenLastCalledWith(-5);
211
211
  });
212
212
 
213
- it("- 버튼 클릭 부호가 토글된다 (음수 -> 양수)", () => {
213
+ it("toggles sign on minus button click (negative to positive)", () => {
214
214
  const handleChange = vi.fn();
215
215
  render(() => <Numpad useMinusButton value={-5} onValueChange={handleChange} />);
216
216
 
217
- // - 버튼 클릭
217
+ // click minus button
218
218
  const minusButton = screen.getAllByRole("button").find((btn) => btn.textContent === "-")!;
219
219
  fireEvent.click(minusButton);
220
220
 
@@ -222,8 +222,8 @@ describe("Numpad", () => {
222
222
  });
223
223
  });
224
224
 
225
- describe("controlled 모드", () => {
226
- it("외부 변경이 반영된다", () => {
225
+ describe("controlled mode", () => {
226
+ it("reflects external value changes", () => {
227
227
  const [value, setValue] = createSignal<number | undefined>(100);
228
228
  render(() => <Numpad value={value()} onValueChange={setValue} />);
229
229
 
@@ -234,7 +234,7 @@ describe("Numpad", () => {
234
234
  expect(input).toHaveValue("200");
235
235
  });
236
236
 
237
- it("외부에서 undefined로 설정하면 입력이 비워진다", () => {
237
+ it("clears input when set to undefined externally", () => {
238
238
  const [value, setValue] = createSignal<number | undefined>(100);
239
239
  render(() => <Numpad value={value()} onValueChange={setValue} />);
240
240
 
@@ -41,7 +41,7 @@ describe("Select component", () => {
41
41
  });
42
42
 
43
43
  describe("dropdown opening/closing", () => {
44
- it("트리거 클릭 드롭다운이 열린다", async () => {
44
+ it("opens dropdown on trigger click", async () => {
45
45
  const { getByRole } = render(() => (
46
46
  <ConfigProvider clientName="test"><I18nProvider>
47
47
  <Select renderValue={(v) => <>{v}</>}>
@@ -82,7 +82,7 @@ describe("Select component", () => {
82
82
 
83
83
  // Value changed
84
84
  expect(value()).toBe("apple");
85
- // aria-expanded false (닫힘 트리거됨)
85
+ // aria-expanded becomes false (close triggered)
86
86
  expect(getByRole("combobox").getAttribute("aria-expanded")).toBe("false");
87
87
  });
88
88
  });
@@ -174,7 +174,7 @@ describe("Select component", () => {
174
174
  const selectItem = document.querySelector("[data-select-item]") as HTMLElement;
175
175
  fireEvent.click(selectItem);
176
176
 
177
- // 드롭다운이 여전히 열려 있음
177
+ // dropdown remains open
178
178
  expect(document.querySelector("[data-dropdown]")).not.toBeNull();
179
179
  });
180
180
  });
@@ -327,7 +327,7 @@ describe("Select component", () => {
327
327
 
328
328
  await waitFor(() => {
329
329
  const items = document.querySelectorAll("[data-select-item]");
330
- // 미지정 + banana = 2
330
+ // unset + banana = 2
331
331
  expect(items.length).toBe(2);
332
332
  expect(items[0].textContent).toContain("Unset");
333
333
  expect(items[1].textContent).toContain("banana");
@@ -358,7 +358,7 @@ describe("Select component", () => {
358
358
 
359
359
  await waitFor(() => {
360
360
  const items = document.querySelectorAll("[data-select-item]");
361
- // 미지정 + red apple = 2
361
+ // unset + red apple = 2
362
362
  expect(items.length).toBe(2);
363
363
  expect(items[0].textContent).toContain("Unset");
364
364
  expect(items[1].textContent).toContain("red apple");
@@ -401,7 +401,7 @@ describe("Select component", () => {
401
401
  });
402
402
 
403
403
  const items = document.querySelectorAll("[data-select-item]");
404
- // 미지정 + apple + banana = 3
404
+ // unset + apple + banana = 3
405
405
  expect(items.length).toBe(3);
406
406
  expect(items[0].textContent).toContain("Unset");
407
407
  });
@@ -422,7 +422,7 @@ describe("Select component", () => {
422
422
  });
423
423
 
424
424
  const items = document.querySelectorAll("[data-select-item]");
425
- // apple + banana = 2 (미지정 없음)
425
+ // apple + banana = 2 (no unset)
426
426
  expect(items.length).toBe(2);
427
427
  });
428
428
 
@@ -442,7 +442,7 @@ describe("Select component", () => {
442
442
  });
443
443
 
444
444
  const items = document.querySelectorAll("[data-select-item]");
445
- // apple + banana = 2 (미지정 없음)
445
+ // apple + banana = 2 (no unset)
446
446
  expect(items.length).toBe(2);
447
447
  });
448
448
  });
@@ -581,7 +581,7 @@ describe("Select component", () => {
581
581
 
582
582
  const items = document.querySelectorAll("[data-select-item]");
583
583
  const texts = Array.from(items).map((el) => el.textContent);
584
- // 미지정 + apple + cherry (banana 숨김)
584
+ // unset + apple + cherry (banana hidden)
585
585
  expect(texts).toContain("Unset");
586
586
  expect(texts.some((t) => t.includes("apple"))).toBe(true);
587
587
  expect(texts.some((t) => t.includes("cherry"))).toBe(true);