@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.
- package/README.md +1 -1
- package/dist/components/disclosure/Dropdown.d.ts +6 -4
- package/dist/components/disclosure/Dropdown.d.ts.map +1 -1
- package/dist/components/disclosure/Dropdown.js +24 -8
- package/dist/components/disclosure/Dropdown.js.map +2 -2
- package/dist/components/disclosure/dialogZIndex.d.ts +2 -0
- package/dist/components/disclosure/dialogZIndex.d.ts.map +1 -1
- package/dist/components/disclosure/dialogZIndex.js +4 -0
- package/dist/components/disclosure/dialogZIndex.js.map +1 -1
- package/dist/components/features/crud-detail/CrudDetail.d.ts.map +1 -1
- package/dist/components/features/crud-detail/CrudDetail.js +16 -7
- package/dist/components/features/crud-detail/CrudDetail.js.map +2 -2
- package/dist/components/features/crud-sheet/CrudSheet.d.ts.map +1 -1
- package/dist/components/features/crud-sheet/CrudSheet.js +14 -5
- package/dist/components/features/crud-sheet/CrudSheet.js.map +2 -2
- package/dist/components/features/crudRegistry.d.ts +16 -0
- package/dist/components/features/crudRegistry.d.ts.map +1 -0
- package/dist/components/features/crudRegistry.js +37 -0
- package/dist/components/features/crudRegistry.js.map +6 -0
- package/dist/components/features/permission-table/PermissionTable.d.ts.map +1 -1
- package/dist/components/features/permission-table/PermissionTable.js +71 -86
- package/dist/components/features/permission-table/PermissionTable.js.map +2 -2
- package/dist/components/features/shared-data/SharedDataSelect.js +2 -4
- package/dist/components/features/shared-data/SharedDataSelect.js.map +2 -2
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts +2 -4
- package/dist/components/features/shared-data/SharedDataSelectList.d.ts.map +1 -1
- package/dist/components/features/shared-data/SharedDataSelectList.js +11 -46
- package/dist/components/features/shared-data/SharedDataSelectList.js.map +2 -2
- package/dist/components/form-control/select/Select.d.ts.map +1 -1
- package/dist/components/form-control/select/Select.js +1 -1
- package/dist/components/form-control/select/Select.js.map +1 -1
- package/dist/helpers/createAppStructure.d.ts.map +1 -1
- package/dist/helpers/createAppStructure.js +3 -2
- package/dist/helpers/createAppStructure.js.map +1 -1
- package/dist/helpers/createHmrSafeContext.d.ts +3 -0
- package/dist/helpers/createHmrSafeContext.d.ts.map +1 -0
- package/dist/helpers/createHmrSafeContext.js +10 -0
- package/dist/helpers/createHmrSafeContext.js.map +6 -0
- package/dist/hooks/createSelectionGroup.d.ts.map +1 -1
- package/dist/hooks/createSelectionGroup.js +3 -2
- package/dist/hooks/createSelectionGroup.js.map +2 -2
- package/package.json +6 -5
- package/src/components/disclosure/Dropdown.tsx +31 -17
- package/src/components/disclosure/dialogZIndex.ts +5 -0
- package/src/components/features/crud-detail/CrudDetail.tsx +16 -5
- package/src/components/features/crud-sheet/CrudSheet.tsx +13 -3
- package/src/components/features/crudRegistry.ts +60 -0
- package/src/components/features/permission-table/PermissionTable.tsx +49 -46
- package/src/components/features/shared-data/SharedDataSelect.tsx +2 -2
- package/src/components/features/shared-data/SharedDataSelectList.tsx +11 -36
- package/src/components/form-control/select/Select.tsx +1 -5
- package/src/helpers/createAppStructure.ts +3 -2
- package/src/helpers/createHmrSafeContext.ts +8 -0
- package/src/hooks/createSelectionGroup.tsx +4 -2
- package/tests/components/data/List.spec.tsx +52 -52
- package/tests/components/data/Pagination.spec.tsx +43 -43
- package/tests/components/data/Table.spec.tsx +4 -4
- package/tests/components/data/kanban/Kanban.selection.spec.tsx +21 -21
- package/tests/components/data/sheet/DataSheet.spec.tsx +50 -50
- package/tests/components/disclosure/Collapse.spec.tsx +24 -24
- package/tests/components/disclosure/Dialog.spec.tsx +33 -33
- package/tests/components/disclosure/DialogProvider.spec.tsx +9 -9
- package/tests/components/disclosure/Dropdown.spec.tsx +134 -14
- package/tests/components/disclosure/Tabs.spec.tsx +21 -21
- package/tests/components/disclosure/dialogZIndex.spec.ts +45 -0
- package/tests/components/display/Alert.spec.tsx +4 -4
- package/tests/components/display/Barcode.spec.tsx +7 -7
- package/tests/components/display/Card.spec.tsx +3 -3
- package/tests/components/display/Link.spec.tsx +5 -5
- package/tests/components/display/Tag.spec.tsx +4 -4
- package/tests/components/features/address/AddressSearch.spec.tsx +3 -3
- package/tests/components/features/crudRegistry.spec.ts +119 -0
- package/tests/components/features/data-select-button/DataSelectButton.spec.tsx +8 -8
- package/tests/components/features/permission-table/PermissionTable.spec.tsx +43 -43
- package/tests/components/features/shared-data/SharedDataSelectList.spec.tsx +2 -17
- package/tests/components/feedback/busy/BusyContainer.spec.tsx +7 -7
- package/tests/components/feedback/notification/NotificationBell.spec.tsx +9 -9
- package/tests/components/feedback/print/Print.spec.tsx +4 -4
- package/tests/components/form-control/Button.spec.tsx +18 -18
- package/tests/components/form-control/checkbox/Checkbox.spec.tsx +20 -20
- package/tests/components/form-control/checkbox/CheckboxGroup.spec.tsx +12 -12
- package/tests/components/form-control/checkbox/Radio.spec.tsx +21 -21
- package/tests/components/form-control/checkbox/RadioGroup.spec.tsx +12 -12
- package/tests/components/form-control/color-picker/ColorPicker.spec.tsx +10 -10
- package/tests/components/form-control/combobox/Combobox.spec.tsx +16 -16
- package/tests/components/form-control/combobox/ComboboxItem.spec.tsx +7 -7
- package/tests/components/form-control/date-range-picker/DateRangePicker.spec.tsx +24 -24
- package/tests/components/form-control/field/DatePicker.spec.tsx +50 -50
- package/tests/components/form-control/field/DateTimePicker.spec.tsx +47 -47
- package/tests/components/form-control/field/NumberInput.spec.tsx +54 -54
- package/tests/components/form-control/field/TextInput.spec.tsx +49 -49
- package/tests/components/form-control/field/Textarea.spec.tsx +33 -33
- package/tests/components/form-control/field/TimePicker.spec.tsx +42 -42
- package/tests/components/form-control/numpad/Numpad.spec.tsx +40 -40
- package/tests/components/form-control/select/Select.spec.tsx +9 -9
- package/tests/components/form-control/select/SelectItem.spec.tsx +10 -10
- package/tests/helpers/createAppStructure.spec.tsx +57 -57
- 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("
|
|
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("
|
|
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("
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
|
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
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
212
|
+
it("runs validate function after base validators pass", () => {
|
|
213
213
|
const { container } = render(() => (
|
|
214
214
|
<Textarea
|
|
215
215
|
required
|