@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
|
@@ -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("
|
|
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("
|
|
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("
|
|
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
|
|
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("
|
|
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("
|
|
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
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
|
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("
|
|
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("
|
|
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
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
192
|
-
it("
|
|
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("
|
|
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("
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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);
|