@simplysm/solid 13.0.70 → 13.0.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +1 -1
  2. package/dist/components/disclosure/Dropdown.d.ts +6 -4
  3. package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
  4. package/dist/components/disclosure/Dropdown.js +24 -8
  5. package/dist/components/disclosure/Dropdown.js.map +2 -2
  6. package/dist/components/disclosure/dialogZIndex.d.ts +2 -0
  7. package/dist/components/disclosure/dialogZIndex.d.ts.map +1 -1
  8. package/dist/components/disclosure/dialogZIndex.js +4 -0
  9. package/dist/components/disclosure/dialogZIndex.js.map +1 -1
  10. package/dist/components/features/crud-detail/CrudDetail.d.ts.map +1 -1
  11. package/dist/components/features/crud-detail/CrudDetail.js +16 -7
  12. package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
  13. package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
  14. package/dist/components/features/crud-sheet/CrudSheet.js +14 -5
  15. package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
  16. package/dist/components/features/crudRegistry.d.ts +16 -0
  17. package/dist/components/features/crudRegistry.d.ts.map +1 -0
  18. package/dist/components/features/crudRegistry.js +37 -0
  19. package/dist/components/features/crudRegistry.js.map +6 -0
  20. package/dist/components/features/permission-table/PermissionTable.d.ts.map +1 -1
  21. package/dist/components/features/permission-table/PermissionTable.js +71 -86
  22. package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
  23. package/dist/components/features/shared-data/SharedDataSelect.js +2 -4
  24. package/dist/components/features/shared-data/SharedDataSelect.js.map +2 -2
  25. package/dist/components/features/shared-data/SharedDataSelectList.d.ts +2 -4
  26. package/dist/components/features/shared-data/SharedDataSelectList.d.ts.map +1 -1
  27. package/dist/components/features/shared-data/SharedDataSelectList.js +11 -46
  28. package/dist/components/features/shared-data/SharedDataSelectList.js.map +2 -2
  29. package/dist/components/form-control/select/Select.d.ts.map +1 -1
  30. package/dist/components/form-control/select/Select.js +1 -1
  31. package/dist/components/form-control/select/Select.js.map +1 -1
  32. package/dist/helpers/createAppStructure.d.ts.map +1 -1
  33. package/dist/helpers/createAppStructure.js +3 -2
  34. package/dist/helpers/createAppStructure.js.map +1 -1
  35. package/dist/helpers/createHmrSafeContext.d.ts +3 -0
  36. package/dist/helpers/createHmrSafeContext.d.ts.map +1 -0
  37. package/dist/helpers/createHmrSafeContext.js +10 -0
  38. package/dist/helpers/createHmrSafeContext.js.map +6 -0
  39. package/dist/hooks/createSelectionGroup.d.ts.map +1 -1
  40. package/dist/hooks/createSelectionGroup.js +3 -2
  41. package/dist/hooks/createSelectionGroup.js.map +2 -2
  42. package/package.json +6 -5
  43. package/src/components/disclosure/Dropdown.tsx +31 -17
  44. package/src/components/disclosure/dialogZIndex.ts +5 -0
  45. package/src/components/features/crud-detail/CrudDetail.tsx +16 -5
  46. package/src/components/features/crud-sheet/CrudSheet.tsx +13 -3
  47. package/src/components/features/crudRegistry.ts +60 -0
  48. package/src/components/features/permission-table/PermissionTable.tsx +49 -46
  49. package/src/components/features/shared-data/SharedDataSelect.tsx +2 -2
  50. package/src/components/features/shared-data/SharedDataSelectList.tsx +11 -36
  51. package/src/components/form-control/select/Select.tsx +1 -5
  52. package/src/helpers/createAppStructure.ts +3 -2
  53. package/src/helpers/createHmrSafeContext.ts +8 -0
  54. package/src/hooks/createSelectionGroup.tsx +4 -2
  55. package/tests/components/data/List.spec.tsx +52 -52
  56. package/tests/components/data/Pagination.spec.tsx +43 -43
  57. package/tests/components/data/Table.spec.tsx +4 -4
  58. package/tests/components/data/kanban/Kanban.selection.spec.tsx +21 -21
  59. package/tests/components/data/sheet/DataSheet.spec.tsx +50 -50
  60. package/tests/components/disclosure/Collapse.spec.tsx +24 -24
  61. package/tests/components/disclosure/Dialog.spec.tsx +33 -33
  62. package/tests/components/disclosure/DialogProvider.spec.tsx +9 -9
  63. package/tests/components/disclosure/Dropdown.spec.tsx +134 -14
  64. package/tests/components/disclosure/Tabs.spec.tsx +21 -21
  65. package/tests/components/disclosure/dialogZIndex.spec.ts +45 -0
  66. package/tests/components/display/Alert.spec.tsx +4 -4
  67. package/tests/components/display/Barcode.spec.tsx +7 -7
  68. package/tests/components/display/Card.spec.tsx +3 -3
  69. package/tests/components/display/Link.spec.tsx +5 -5
  70. package/tests/components/display/Tag.spec.tsx +4 -4
  71. package/tests/components/features/address/AddressSearch.spec.tsx +3 -3
  72. package/tests/components/features/crudRegistry.spec.ts +119 -0
  73. package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +8 -8
  74. package/tests/components/features/permission-table/PermissionTable.spec.tsx +43 -43
  75. package/tests/components/features/shared-data/SharedDataSelectList.spec.tsx +2 -17
  76. package/tests/components/feedback/busy/BusyContainer.spec.tsx +7 -7
  77. package/tests/components/feedback/notification/NotificationBell.spec.tsx +9 -9
  78. package/tests/components/feedback/print/Print.spec.tsx +4 -4
  79. package/tests/components/form-control/Button.spec.tsx +18 -18
  80. package/tests/components/form-control/checkbox/Checkbox.spec.tsx +20 -20
  81. package/tests/components/form-control/checkbox/CheckboxGroup.spec.tsx +12 -12
  82. package/tests/components/form-control/checkbox/Radio.spec.tsx +21 -21
  83. package/tests/components/form-control/checkbox/RadioGroup.spec.tsx +12 -12
  84. package/tests/components/form-control/color-picker/ColorPicker.spec.tsx +10 -10
  85. package/tests/components/form-control/combobox/Combobox.spec.tsx +16 -16
  86. package/tests/components/form-control/combobox/ComboboxItem.spec.tsx +7 -7
  87. package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +24 -24
  88. package/tests/components/form-control/field/DatePicker.spec.tsx +50 -50
  89. package/tests/components/form-control/field/DateTimePicker.spec.tsx +47 -47
  90. package/tests/components/form-control/field/NumberInput.spec.tsx +54 -54
  91. package/tests/components/form-control/field/TextInput.spec.tsx +49 -49
  92. package/tests/components/form-control/field/Textarea.spec.tsx +33 -33
  93. package/tests/components/form-control/field/TimePicker.spec.tsx +42 -42
  94. package/tests/components/form-control/numpad/Numpad.spec.tsx +40 -40
  95. package/tests/components/form-control/select/Select.spec.tsx +9 -9
  96. package/tests/components/form-control/select/SelectItem.spec.tsx +10 -10
  97. package/tests/helpers/createAppStructure.spec.tsx +57 -57
  98. package/tests/helpers/mergeStyles.spec.ts +31 -31
@@ -3,65 +3,65 @@ import { describe, it, expect, vi } from "vitest";
3
3
  import { createSignal } from "solid-js";
4
4
  import { TextInput } from "../../../../src/components/form-control/field/TextInput";
5
5
 
6
- describe("TextInput 컴포넌트", () => {
6
+ describe("TextInput component", () => {
7
7
  describe("basic rendering", () => {
8
- it("input 요소가 렌더링된다", () => {
8
+ it("renders input element", () => {
9
9
  const { container } = render(() => <TextInput />);
10
10
  const input = container.querySelector("input");
11
11
  expect(input).toBeTruthy();
12
12
  });
13
13
 
14
- it("기본 type text이다", () => {
14
+ it("defaults type to text", () => {
15
15
  const { container } = render(() => <TextInput />);
16
16
  const input = container.querySelector("input") as HTMLInputElement;
17
17
  expect(input.type).toBe("text");
18
18
  });
19
19
 
20
- it("type=password일 password input 렌더링된다", () => {
20
+ it("renders password input when type=password", () => {
21
21
  const { container } = render(() => <TextInput type="password" />);
22
22
  const input = container.querySelector("input") as HTMLInputElement;
23
23
  expect(input.type).toBe("password");
24
24
  });
25
25
 
26
- it("type=email일 email input 렌더링된다", () => {
26
+ it("renders email input when type=email", () => {
27
27
  const { container } = render(() => <TextInput type="email" />);
28
28
  const input = container.querySelector("input") as HTMLInputElement;
29
29
  expect(input.type).toBe("email");
30
30
  });
31
31
 
32
- it("placeholder input에 적용된다", () => {
32
+ it("applies placeholder to input", () => {
33
33
  const { container } = render(() => <TextInput placeholder="Enter text" />);
34
34
  const input = container.querySelector("input") as HTMLInputElement;
35
35
  expect(input.placeholder).toBe("Enter text");
36
36
  });
37
37
 
38
- it("title input에 적용된다", () => {
38
+ it("applies title to input", () => {
39
39
  const { container } = render(() => <TextInput title="Input title" />);
40
40
  const input = container.querySelector("input") as HTMLInputElement;
41
41
  expect(input.title).toBe("Input title");
42
42
  });
43
43
 
44
- it("autocomplete 기본값은 one-time-code이다", () => {
44
+ it("defaults autocomplete to one-time-code", () => {
45
45
  const { container } = render(() => <TextInput />);
46
46
  const input = container.querySelector("input") as HTMLInputElement;
47
47
  expect(input.autocomplete).toBe("one-time-code");
48
48
  });
49
49
 
50
- it("autocomplete를 명시적으로 지정하면 해당 값이 적용된다", () => {
50
+ it("applies explicitly specified autocomplete value", () => {
51
51
  const { container } = render(() => <TextInput autocomplete="email" />);
52
52
  const input = container.querySelector("input") as HTMLInputElement;
53
53
  expect(input.autocomplete).toBe("email");
54
54
  });
55
55
  });
56
56
 
57
- describe("controlled 패턴", () => {
58
- it("value prop input의 값으로 표시된다", () => {
57
+ describe("controlled pattern", () => {
58
+ it("displays value prop in input", () => {
59
59
  const { container } = render(() => <TextInput value="Hello" />);
60
60
  const input = container.querySelector("input") as HTMLInputElement;
61
61
  expect(input.value).toBe("Hello");
62
62
  });
63
63
 
64
- it("onValueChange 입력 시 호출된다", () => {
64
+ it("calls onValueChange on input", () => {
65
65
  const handleChange = vi.fn();
66
66
  const { container } = render(() => <TextInput value="" onValueChange={handleChange} />);
67
67
  const input = container.querySelector("input") as HTMLInputElement;
@@ -71,7 +71,7 @@ describe("TextInput 컴포넌트", () => {
71
71
  expect(handleChange).toHaveBeenCalledWith("Test");
72
72
  });
73
73
 
74
- it("외부 상태 변경 input 값이 업데이트된다", () => {
74
+ it("updates input value when external state changes", () => {
75
75
  const [value, setValue] = createSignal("Initial");
76
76
  const { container } = render(() => <TextInput value={value()} onValueChange={setValue} />);
77
77
  const input = container.querySelector("input") as HTMLInputElement;
@@ -83,8 +83,8 @@ describe("TextInput 컴포넌트", () => {
83
83
  });
84
84
  });
85
85
 
86
- describe("uncontrolled 패턴", () => {
87
- it("onValueChange 없이 내부 상태로 값이 관리된다", () => {
86
+ describe("uncontrolled pattern", () => {
87
+ it("manages value internally without onValueChange", () => {
88
88
  const { container } = render(() => <TextInput value="Initial" />);
89
89
  const input = container.querySelector("input") as HTMLInputElement;
90
90
 
@@ -96,7 +96,7 @@ describe("TextInput 컴포넌트", () => {
96
96
  });
97
97
 
98
98
  describe("disabled state", () => {
99
- it("disabled=true일 div 렌더링된다", () => {
99
+ it("renders as div when disabled=true", () => {
100
100
  const { container } = render(() => <TextInput disabled value="Disabled text" />);
101
101
  const input = container.querySelector("input:not([aria-hidden])");
102
102
  const div = container.querySelector("div.sd-text-field");
@@ -105,20 +105,20 @@ describe("TextInput 컴포넌트", () => {
105
105
  expect(div).toBeTruthy();
106
106
  });
107
107
 
108
- it("disabled 상태에서 value 표시된다", () => {
108
+ it("displays value when disabled", () => {
109
109
  const { getByText } = render(() => <TextInput disabled value="Disabled text" />);
110
110
  expect(getByText("Disabled text")).toBeTruthy();
111
111
  });
112
112
 
113
- it("disabled 스타일이 적용된다", () => {
113
+ it("applies disabled style", () => {
114
114
  const { container } = render(() => <TextInput disabled value="Text" />);
115
115
  const div = container.querySelector("div.sd-text-field") as HTMLElement;
116
116
  expect(div.classList.contains("bg-base-100")).toBe(true);
117
117
  });
118
118
  });
119
119
 
120
- describe("readonly 상태", () => {
121
- it("readonly=true일 div 렌더링된다", () => {
120
+ describe("readonly state", () => {
121
+ it("renders as div when readonly=true", () => {
122
122
  const { container } = render(() => <TextInput readonly value="Readonly text" />);
123
123
  const input = container.querySelector("input:not([aria-hidden])");
124
124
  const div = container.querySelector("div.sd-text-field");
@@ -127,20 +127,20 @@ describe("TextInput 컴포넌트", () => {
127
127
  expect(div).toBeTruthy();
128
128
  });
129
129
 
130
- it("readonly 상태에서 value 표시된다", () => {
130
+ it("displays value when readonly", () => {
131
131
  const { getByText } = render(() => <TextInput readonly value="Readonly text" />);
132
132
  expect(getByText("Readonly text")).toBeTruthy();
133
133
  });
134
134
  });
135
135
 
136
- describe("format 옵션", () => {
137
- it("format 적용되어 표시된다", () => {
136
+ describe("format option", () => {
137
+ it("displays value with format applied", () => {
138
138
  const { container } = render(() => <TextInput format="XXX-XXXX-XXXX" value="01012345678" />);
139
139
  const input = container.querySelector("input") as HTMLInputElement;
140
140
  expect(input.value).toBe("010-1234-5678");
141
141
  });
142
142
 
143
- it("format이 적용된 상태에서 입력 원본 값이 onValueChange로 전달된다", () => {
143
+ it("passes raw value to onValueChange when format is applied", () => {
144
144
  const handleChange = vi.fn();
145
145
  const { container } = render(() => (
146
146
  <TextInput format="XXX-XXXX-XXXX" value="" onValueChange={handleChange} />
@@ -149,27 +149,27 @@ describe("TextInput 컴포넌트", () => {
149
149
 
150
150
  fireEvent.input(input, { target: { value: "010-1234-5678" } });
151
151
 
152
- // 포맷 문자('-') 제거된 원본 값이 전달됨
152
+ // raw value with format characters ('-') stripped is passed
153
153
  expect(handleChange).toHaveBeenCalledWith("01012345678");
154
154
  });
155
155
  });
156
156
 
157
- describe("size 옵션", () => {
158
- it("size=sm일 작은 padding 적용된다", () => {
157
+ describe("size option", () => {
158
+ it("applies small padding when size=sm", () => {
159
159
  const { container } = render(() => <TextInput size="sm" />);
160
160
  const wrapper = container.firstChild as HTMLElement;
161
161
  expect(wrapper.classList.contains("py-0.5")).toBe(true);
162
162
  });
163
163
 
164
- it("size=lg일 padding 적용된다", () => {
164
+ it("applies large padding when size=lg", () => {
165
165
  const { container } = render(() => <TextInput size="lg" />);
166
166
  const wrapper = container.firstChild as HTMLElement;
167
167
  expect(wrapper.classList.contains("py-2")).toBe(true);
168
168
  });
169
169
  });
170
170
 
171
- describe("inset 스타일", () => {
172
- it("inset=true일 테두리가 없고 inset 배경색이 적용된다", () => {
171
+ describe("inset style", () => {
172
+ it("removes border and applies inset background when inset=true", () => {
173
173
  const { container } = render(() => <TextInput inset />);
174
174
  const outer = container.firstChild as HTMLElement;
175
175
  const contentDiv = outer.querySelector("[data-text-field-content]") as HTMLElement;
@@ -177,7 +177,7 @@ describe("TextInput 컴포넌트", () => {
177
177
  expect(contentDiv.classList.contains("bg-primary-50")).toBe(true);
178
178
  });
179
179
 
180
- it("inset + readonly일 때 content div 보이고 input 없다", () => {
180
+ it("shows content div and no input when inset + readonly", () => {
181
181
  const { container } = render(() => <TextInput inset readonly value="Hello" />);
182
182
  const outer = container.firstChild as HTMLElement;
183
183
  expect(outer.classList.contains("relative")).toBe(true);
@@ -190,7 +190,7 @@ describe("TextInput 컴포넌트", () => {
190
190
  expect(input).toBeFalsy();
191
191
  });
192
192
 
193
- it("inset + editable일 때 content div(hidden)와 input 모두 존재한다", () => {
193
+ it("shows hidden content div and input when inset + editable", () => {
194
194
  const { container } = render(() => <TextInput inset value="Hello" />);
195
195
  const outer = container.firstChild as HTMLElement;
196
196
 
@@ -203,14 +203,14 @@ describe("TextInput 컴포넌트", () => {
203
203
  expect(input.value).toBe("Hello");
204
204
  });
205
205
 
206
- it("inset + 값일 때 content div NBSP가 표시된다", () => {
206
+ it("shows NBSP in content div when inset + empty value", () => {
207
207
  const { container } = render(() => <TextInput inset readonly />);
208
208
  const outer = container.firstChild as HTMLElement;
209
209
  const contentDiv = outer.querySelector("[data-text-field-content]") as HTMLElement;
210
210
  expect(contentDiv.textContent).toBe("\u00A0");
211
211
  });
212
212
 
213
- it("inset + readonly editable 전환 content div가 항상 DOM에 존재한다", () => {
213
+ it("content div is always in DOM when toggling inset + readonly/editable", () => {
214
214
  const [readonly, setReadonly] = createSignal(true);
215
215
  const { container } = render(() => <TextInput inset readonly={readonly()} value="Test" />);
216
216
  const outer = container.firstChild as HTMLElement;
@@ -227,7 +227,7 @@ describe("TextInput 컴포넌트", () => {
227
227
  });
228
228
 
229
229
  describe("class merging", () => {
230
- it("사용자 정의 class 기존 스타일과 병합된다", () => {
230
+ it("merges custom class with existing styles", () => {
231
231
  // eslint-disable-next-line tailwindcss/no-custom-classname
232
232
  const { container } = render(() => <TextInput class="my-custom-class" />);
233
233
  const wrapper = container.firstChild as HTMLElement;
@@ -235,21 +235,21 @@ describe("TextInput 컴포넌트", () => {
235
235
  });
236
236
  });
237
237
 
238
- describe("기본 스타일", () => {
239
- it("기본 border 스타일이 적용된다", () => {
238
+ describe("default style", () => {
239
+ it("applies default border style", () => {
240
240
  const { container } = render(() => <TextInput />);
241
241
  const wrapper = container.firstChild as HTMLElement;
242
242
  expect(wrapper.classList.contains("border")).toBe(true);
243
243
  expect(wrapper.classList.contains("border-base-200")).toBe(true);
244
244
  });
245
245
 
246
- it("focus border 색상이 변경된다", () => {
246
+ it("changes border color on focus", () => {
247
247
  const { container } = render(() => <TextInput />);
248
248
  const wrapper = container.firstChild as HTMLElement;
249
249
  expect(wrapper.classList.contains("focus-within:border-primary-500")).toBe(true);
250
250
  });
251
251
 
252
- it("다크 모드 스타일이 적용된다", () => {
252
+ it("applies dark mode style", () => {
253
253
  const { container } = render(() => <TextInput />);
254
254
  const wrapper = container.firstChild as HTMLElement;
255
255
  expect(wrapper.classList.contains("dark:border-base-700")).toBe(true);
@@ -258,37 +258,37 @@ describe("TextInput 컴포넌트", () => {
258
258
  });
259
259
 
260
260
  describe("validation", () => {
261
- it("required일 값이면 hidden input 에러 메시지가 설정된다", () => {
261
+ it("sets error message in hidden input when required and empty", () => {
262
262
  const { container } = render(() => <TextInput required value="" />);
263
263
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
264
264
  expect(hiddenInput.validationMessage).toBe("This is a required field");
265
265
  });
266
266
 
267
- it("required 값이 있으면 유효하다", () => {
267
+ it("is valid when required and value exists", () => {
268
268
  const { container } = render(() => <TextInput required value="hello" />);
269
269
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
270
270
  expect(hiddenInput.validity.valid).toBe(true);
271
271
  });
272
272
 
273
- it("minLength 위반 에러 메시지가 설정된다", () => {
273
+ it("sets error message when minLength is violated", () => {
274
274
  const { container } = render(() => <TextInput minLength={3} value="ab" />);
275
275
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
276
276
  expect(hiddenInput.validationMessage).toBe("Enter at least 3 characters");
277
277
  });
278
278
 
279
- it("maxLength 위반 에러 메시지가 설정된다", () => {
279
+ it("sets error message when maxLength is violated", () => {
280
280
  const { container } = render(() => <TextInput maxLength={5} value="abcdef" />);
281
281
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
282
282
  expect(hiddenInput.validationMessage).toBe("Enter up to 5 characters");
283
283
  });
284
284
 
285
- it("pattern 위반 에러 메시지가 설정된다", () => {
285
+ it("sets error message when pattern is violated", () => {
286
286
  const { container } = render(() => <TextInput pattern="^[0-9]+$" value="abc" />);
287
287
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
288
288
  expect(hiddenInput.validationMessage).toBe("The input format is invalid");
289
289
  });
290
290
 
291
- it("validate 함수가 에러를 반환하면 해당 메시지가 설정된다", () => {
291
+ it("sets error message returned by validate function", () => {
292
292
  const { container } = render(() => (
293
293
  <TextInput
294
294
  validate={(v) => (v.includes("@") ? undefined : "이메일 형식이 아닙니다")}
@@ -299,7 +299,7 @@ describe("TextInput 컴포넌트", () => {
299
299
  expect(hiddenInput.validationMessage).toBe("이메일 형식이 아닙니다");
300
300
  });
301
301
 
302
- it("기본 validator 통과 validate 함수가 실행된다", () => {
302
+ it("runs validate function after base validators pass", () => {
303
303
  const { container } = render(() => (
304
304
  <TextInput
305
305
  required
@@ -312,8 +312,8 @@ describe("TextInput 컴포넌트", () => {
312
312
  });
313
313
  });
314
314
 
315
- describe("Prefix 슬롯", () => {
316
- it("TextInput.Prefix 슬롯이 렌더링된다", () => {
315
+ describe("Prefix slot", () => {
316
+ it("renders TextInput.Prefix slot", () => {
317
317
  render(() => (
318
318
  <TextInput>
319
319
  <TextInput.Prefix>
@@ -325,7 +325,7 @@ describe("TextInput 컴포넌트", () => {
325
325
  expect(document.querySelector('[data-testid="prefix"]')).not.toBeNull();
326
326
  });
327
327
 
328
- it("Prefix 슬롯 사용 gap 클래스가 적용된다", () => {
328
+ it("applies gap class when Prefix slot is used", () => {
329
329
  const { container } = render(() => (
330
330
  <TextInput>
331
331
  <TextInput.Prefix>
@@ -3,35 +3,35 @@ import { describe, it, expect, vi } from "vitest";
3
3
  import { createSignal } from "solid-js";
4
4
  import { Textarea } from "../../../../src/components/form-control/field/Textarea";
5
5
 
6
- describe("Textarea 컴포넌트", () => {
6
+ describe("Textarea component", () => {
7
7
  describe("basic rendering", () => {
8
- it("textarea 요소가 렌더링된다", () => {
8
+ it("renders textarea element", () => {
9
9
  const { container } = render(() => <Textarea />);
10
10
  const textarea = container.querySelector("textarea");
11
11
  expect(textarea).toBeTruthy();
12
12
  });
13
13
 
14
- it("placeholder textarea에 적용된다", () => {
14
+ it("applies placeholder to textarea", () => {
15
15
  const { container } = render(() => <Textarea placeholder="내용을 입력하세요" />);
16
16
  const textarea = container.querySelector("textarea") as HTMLTextAreaElement;
17
17
  expect(textarea.placeholder).toBe("내용을 입력하세요");
18
18
  });
19
19
 
20
- it("title textarea에 적용된다", () => {
20
+ it("applies title to textarea", () => {
21
21
  const { container } = render(() => <Textarea title="Textarea title" />);
22
22
  const textarea = container.querySelector("textarea") as HTMLTextAreaElement;
23
23
  expect(textarea.title).toBe("Textarea title");
24
24
  });
25
25
  });
26
26
 
27
- describe("controlled 패턴", () => {
28
- it("value prop textarea의 값으로 표시된다", () => {
27
+ describe("controlled pattern", () => {
28
+ it("displays value prop in textarea", () => {
29
29
  const { container } = render(() => <Textarea value="Hello" />);
30
30
  const textarea = container.querySelector("textarea") as HTMLTextAreaElement;
31
31
  expect(textarea.value).toBe("Hello");
32
32
  });
33
33
 
34
- it("onValueChange 입력 시 호출된다", () => {
34
+ it("calls onValueChange on input", () => {
35
35
  const handleChange = vi.fn();
36
36
  const { container } = render(() => <Textarea value="" onValueChange={handleChange} />);
37
37
  const textarea = container.querySelector("textarea") as HTMLTextAreaElement;
@@ -41,7 +41,7 @@ describe("Textarea 컴포넌트", () => {
41
41
  expect(handleChange).toHaveBeenCalledWith("Test");
42
42
  });
43
43
 
44
- it("외부 상태 변경 textarea 값이 업데이트된다", () => {
44
+ it("updates textarea value when external state changes", () => {
45
45
  const [value, setValue] = createSignal("Initial");
46
46
  const { container } = render(() => <Textarea value={value()} onValueChange={setValue} />);
47
47
  const textarea = container.querySelector("textarea") as HTMLTextAreaElement;
@@ -53,8 +53,8 @@ describe("Textarea 컴포넌트", () => {
53
53
  });
54
54
  });
55
55
 
56
- describe("uncontrolled 패턴", () => {
57
- it("onValueChange 없이 내부 상태로 값이 관리된다", () => {
56
+ describe("uncontrolled pattern", () => {
57
+ it("manages value internally without onValueChange", () => {
58
58
  const { container } = render(() => <Textarea value="Initial" />);
59
59
  const textarea = container.querySelector("textarea") as HTMLTextAreaElement;
60
60
 
@@ -66,53 +66,53 @@ describe("Textarea 컴포넌트", () => {
66
66
  });
67
67
 
68
68
  describe("disabled state", () => {
69
- it("disabled=true일 textarea 렌더링되지 않는다", () => {
69
+ it("does not render textarea when disabled=true", () => {
70
70
  const { container } = render(() => <Textarea disabled value="Disabled text" />);
71
71
  const textarea = container.querySelector("textarea");
72
72
  expect(textarea).toBeFalsy();
73
73
  });
74
74
 
75
- it("disabled 상태에서 value 표시된다", () => {
75
+ it("displays value when disabled", () => {
76
76
  const { getByText } = render(() => <Textarea disabled value="Disabled text" />);
77
77
  expect(getByText("Disabled text")).toBeTruthy();
78
78
  });
79
79
 
80
- it("disabled 스타일이 적용된다", () => {
80
+ it("applies disabled style", () => {
81
81
  const { container } = render(() => <Textarea disabled value="Text" />);
82
82
  const wrapper = container.firstChild as HTMLElement;
83
83
  expect(wrapper.classList.contains("bg-base-100")).toBe(true);
84
84
  });
85
85
  });
86
86
 
87
- describe("readonly 상태", () => {
88
- it("readonly=true일 textarea 렌더링되지 않는다", () => {
87
+ describe("readonly state", () => {
88
+ it("does not render textarea when readonly=true", () => {
89
89
  const { container } = render(() => <Textarea readonly value="Readonly text" />);
90
90
  const textarea = container.querySelector("textarea");
91
91
  expect(textarea).toBeFalsy();
92
92
  });
93
93
 
94
- it("readonly 상태에서 value 표시된다", () => {
94
+ it("displays value when readonly", () => {
95
95
  const { getByText } = render(() => <Textarea readonly value="Readonly text" />);
96
96
  expect(getByText("Readonly text")).toBeTruthy();
97
97
  });
98
98
  });
99
99
 
100
- describe("size 옵션", () => {
101
- it("size=sm일 작은 padding 적용된다", () => {
100
+ describe("size option", () => {
101
+ it("applies small padding when size=sm", () => {
102
102
  const { container } = render(() => <Textarea size="sm" />);
103
103
  const wrapper = container.firstChild as HTMLElement;
104
104
  expect(wrapper.classList.contains("py-0.5")).toBe(true);
105
105
  });
106
106
 
107
- it("size=lg일 padding 적용된다", () => {
107
+ it("applies large padding when size=lg", () => {
108
108
  const { container } = render(() => <Textarea size="lg" />);
109
109
  const wrapper = container.firstChild as HTMLElement;
110
110
  expect(wrapper.classList.contains("py-2")).toBe(true);
111
111
  });
112
112
  });
113
113
 
114
- describe("inset 스타일", () => {
115
- it("inset=true일 테두리가 없고 inset 배경색이 적용된다", () => {
114
+ describe("inset style", () => {
115
+ it("removes border and applies inset background when inset=true", () => {
116
116
  const { container } = render(() => <Textarea inset />);
117
117
  const wrapper = container.firstChild as HTMLElement;
118
118
  expect(wrapper.classList.contains("relative")).toBe(true);
@@ -121,7 +121,7 @@ describe("Textarea 컴포넌트", () => {
121
121
  expect(content.classList.contains("bg-primary-50")).toBe(true);
122
122
  });
123
123
 
124
- it("inset + readonly일 때 content div 보이고 textarea 없다", () => {
124
+ it("shows content div and no textarea when inset + readonly", () => {
125
125
  const { container } = render(() => <Textarea inset readonly value="Hello" />);
126
126
  const outer = container.firstChild as HTMLElement;
127
127
  expect(outer.classList.contains("relative")).toBe(true);
@@ -133,7 +133,7 @@ describe("Textarea 컴포넌트", () => {
133
133
  expect(outer.querySelector("textarea")).toBeFalsy();
134
134
  });
135
135
 
136
- it("inset + editable일 때 content div(hidden)와 textarea 모두 존재한다", () => {
136
+ it("shows hidden content div and textarea when inset + editable", () => {
137
137
  const { container } = render(() => <Textarea inset value="Hello" />);
138
138
  const outer = container.firstChild as HTMLElement;
139
139
 
@@ -146,7 +146,7 @@ describe("Textarea 컴포넌트", () => {
146
146
  expect(textarea.value).toBe("Hello");
147
147
  });
148
148
 
149
- it("inset + 값일 때 content div NBSP가 표시된다", () => {
149
+ it("shows NBSP in content div when inset + empty value", () => {
150
150
  const { container } = render(() => <Textarea inset readonly />);
151
151
  const outer = container.firstChild as HTMLElement;
152
152
  const contentDiv = outer.querySelector("[data-textarea-field-content]") as HTMLElement;
@@ -155,7 +155,7 @@ describe("Textarea 컴포넌트", () => {
155
155
  });
156
156
 
157
157
  describe("class merging", () => {
158
- it("사용자 정의 class 기존 스타일과 병합된다", () => {
158
+ it("merges custom class with existing styles", () => {
159
159
  // eslint-disable-next-line tailwindcss/no-custom-classname
160
160
  const { container } = render(() => <Textarea class="my-custom-class" />);
161
161
  const wrapper = container.firstChild as HTMLElement;
@@ -163,8 +163,8 @@ describe("Textarea 컴포넌트", () => {
163
163
  });
164
164
  });
165
165
 
166
- describe("자동 높이 조절", () => {
167
- it("hidden div 존재한다 (높이 측정용)", () => {
166
+ describe("auto height adjustment", () => {
167
+ it("renders hidden div for height measurement", () => {
168
168
  const { container } = render(() => <Textarea value="Test" />);
169
169
  const wrapper = container.firstChild as HTMLElement;
170
170
  const hiddenDiv = wrapper.querySelector("[data-hidden-content]") as HTMLElement;
@@ -174,31 +174,31 @@ describe("Textarea 컴포넌트", () => {
174
174
  });
175
175
 
176
176
  describe("validation", () => {
177
- it("required일 값이면 hidden input 에러 메시지가 설정된다", () => {
177
+ it("sets error message in hidden input when required and empty", () => {
178
178
  const { container } = render(() => <Textarea required value="" />);
179
179
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
180
180
  expect(hiddenInput.validationMessage).toBe("This is a required field");
181
181
  });
182
182
 
183
- it("required 값이 있으면 유효하다", () => {
183
+ it("is valid when required and value exists", () => {
184
184
  const { container } = render(() => <Textarea required value="hello" />);
185
185
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
186
186
  expect(hiddenInput.validity.valid).toBe(true);
187
187
  });
188
188
 
189
- it("minLength 위반 에러 메시지가 설정된다", () => {
189
+ it("sets error message when minLength is violated", () => {
190
190
  const { container } = render(() => <Textarea minLength={3} value="ab" />);
191
191
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
192
192
  expect(hiddenInput.validationMessage).toBe("Enter at least 3 characters");
193
193
  });
194
194
 
195
- it("maxLength 위반 에러 메시지가 설정된다", () => {
195
+ it("sets error message when maxLength is violated", () => {
196
196
  const { container } = render(() => <Textarea maxLength={5} value="abcdef" />);
197
197
  const hiddenInput = container.querySelector("input[aria-hidden='true']") as HTMLInputElement;
198
198
  expect(hiddenInput.validationMessage).toBe("Enter up to 5 characters");
199
199
  });
200
200
 
201
- it("validate 함수가 에러를 반환하면 해당 메시지가 설정된다", () => {
201
+ it("sets error message returned by validate function", () => {
202
202
  const { container } = render(() => (
203
203
  <Textarea
204
204
  validate={(v) => (v.includes("@") ? undefined : "이메일 형식이 아닙니다")}
@@ -209,7 +209,7 @@ describe("Textarea 컴포넌트", () => {
209
209
  expect(hiddenInput.validationMessage).toBe("이메일 형식이 아닙니다");
210
210
  });
211
211
 
212
- it("기본 validator 통과 validate 함수가 실행된다", () => {
212
+ it("runs validate function after base validators pass", () => {
213
213
  const { container } = render(() => (
214
214
  <Textarea
215
215
  required