@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
|
@@ -30,7 +30,7 @@ const testData: TestItem[] = [
|
|
|
30
30
|
];
|
|
31
31
|
|
|
32
32
|
describe("DataSheet", () => {
|
|
33
|
-
it("
|
|
33
|
+
it("basic rendering: column headers and data rows are displayed", () => {
|
|
34
34
|
const { container } = render(() => (
|
|
35
35
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
36
36
|
<TestWrapper>
|
|
@@ -59,7 +59,7 @@ describe("DataSheet", () => {
|
|
|
59
59
|
expect(rows.length).toBe(3);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
it("
|
|
62
|
+
it("multi-level header: colspan and rowspan are applied correctly", () => {
|
|
63
63
|
const { container } = render(() => (
|
|
64
64
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
65
65
|
<TestWrapper>
|
|
@@ -79,22 +79,22 @@ describe("DataSheet", () => {
|
|
|
79
79
|
));
|
|
80
80
|
|
|
81
81
|
const headerRows = container.querySelectorAll("thead tr");
|
|
82
|
-
// 2
|
|
82
|
+
// 2 rows: first row has "기본정보"(colspan=2) + "이메일"(rowspan=2), second row has "이름" + "나이"
|
|
83
83
|
expect(headerRows.length).toBeGreaterThanOrEqual(2);
|
|
84
84
|
|
|
85
85
|
const firstRowThs = headerRows[0].querySelectorAll("th");
|
|
86
|
-
// "기본정보" th
|
|
86
|
+
// "기본정보" th has colspan=2
|
|
87
87
|
const groupTh = Array.from(firstRowThs).find((th) => th.textContent.includes("기본정보"));
|
|
88
88
|
expect(groupTh).toBeTruthy();
|
|
89
89
|
expect(groupTh!.getAttribute("colspan")).toBe("2");
|
|
90
90
|
|
|
91
|
-
// "이메일" th
|
|
91
|
+
// "이메일" th has rowspan=2
|
|
92
92
|
const emailTh = Array.from(firstRowThs).find((th) => th.textContent.includes("이메일"));
|
|
93
93
|
expect(emailTh).toBeTruthy();
|
|
94
94
|
expect(emailTh!.getAttribute("rowspan")).toBe("2");
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
it("
|
|
97
|
+
it("summary row: summary column displays a summary row in thead", () => {
|
|
98
98
|
const { container } = render(() => (
|
|
99
99
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
100
100
|
<TestWrapper>
|
|
@@ -111,14 +111,14 @@ describe("DataSheet", () => {
|
|
|
111
111
|
));
|
|
112
112
|
|
|
113
113
|
const theadRows = container.querySelectorAll("thead tr");
|
|
114
|
-
//
|
|
114
|
+
// 1 header row + 1 summary row = 2 rows
|
|
115
115
|
expect(theadRows.length).toBe(2);
|
|
116
116
|
|
|
117
117
|
const summaryRow = theadRows[theadRows.length - 1];
|
|
118
118
|
expect(summaryRow.textContent).toContain("합계: 83");
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
it("
|
|
121
|
+
it("empty data: tbody is empty", () => {
|
|
122
122
|
const { container } = render(() => (
|
|
123
123
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
124
124
|
<TestWrapper>
|
|
@@ -134,12 +134,12 @@ describe("DataSheet", () => {
|
|
|
134
134
|
const rows = container.querySelectorAll("tbody tr");
|
|
135
135
|
expect(rows.length).toBe(0);
|
|
136
136
|
|
|
137
|
-
//
|
|
137
|
+
// header is still displayed
|
|
138
138
|
const ths = container.querySelectorAll("thead th");
|
|
139
139
|
expect(ths.length).toBe(1);
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
it("hidden
|
|
142
|
+
it("hidden columns are not rendered", () => {
|
|
143
143
|
const { container } = render(() => (
|
|
144
144
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
145
145
|
<TestWrapper>
|
|
@@ -160,7 +160,7 @@ describe("DataSheet", () => {
|
|
|
160
160
|
expect(ths[0].textContent).toContain("이름");
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
it("
|
|
163
|
+
it("sort: clicking header calls onSortsChange", () => {
|
|
164
164
|
let capturedSorts: SortingDef[] = [];
|
|
165
165
|
const { container } = render(() => (
|
|
166
166
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -184,13 +184,13 @@ describe("DataSheet", () => {
|
|
|
184
184
|
</I18nProvider></ConfigProvider>
|
|
185
185
|
));
|
|
186
186
|
|
|
187
|
-
// "이름"
|
|
187
|
+
// click "이름" header
|
|
188
188
|
const ths = container.querySelectorAll("thead th");
|
|
189
189
|
(ths[0] as HTMLElement).click();
|
|
190
190
|
expect(capturedSorts).toEqual([{ key: "name", desc: false }]);
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
it("
|
|
193
|
+
it("sort: sortable={false} column does not sort on click", () => {
|
|
194
194
|
let capturedSorts: SortingDef[] = [];
|
|
195
195
|
const { container } = render(() => (
|
|
196
196
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
@@ -216,7 +216,7 @@ describe("DataSheet", () => {
|
|
|
216
216
|
expect(capturedSorts).toEqual([]);
|
|
217
217
|
});
|
|
218
218
|
|
|
219
|
-
it("
|
|
219
|
+
it("auto sort: data is sorted when autoSort is true", () => {
|
|
220
220
|
const { container } = render(() => (
|
|
221
221
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
222
222
|
<TestWrapper>
|
|
@@ -239,7 +239,7 @@ describe("DataSheet", () => {
|
|
|
239
239
|
expect(names).toEqual(["김철수", "이영희", "홍길동"]);
|
|
240
240
|
});
|
|
241
241
|
|
|
242
|
-
it("
|
|
242
|
+
it("pagination: data is sliced by itemsPerPage", () => {
|
|
243
243
|
const { container } = render(() => (
|
|
244
244
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
245
245
|
<TestWrapper>
|
|
@@ -256,7 +256,7 @@ describe("DataSheet", () => {
|
|
|
256
256
|
expect(rows.length).toBe(2);
|
|
257
257
|
});
|
|
258
258
|
|
|
259
|
-
it("
|
|
259
|
+
it("pagination: Pagination is displayed when there are 2 or more pages", () => {
|
|
260
260
|
const { container } = render(() => (
|
|
261
261
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
262
262
|
<TestWrapper>
|
|
@@ -273,7 +273,7 @@ describe("DataSheet", () => {
|
|
|
273
273
|
expect(pagination).toBeTruthy();
|
|
274
274
|
});
|
|
275
275
|
|
|
276
|
-
it("
|
|
276
|
+
it("pagination: Pagination is not displayed when there is only 1 page", () => {
|
|
277
277
|
const { container } = render(() => (
|
|
278
278
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
279
279
|
<TestWrapper>
|
|
@@ -290,7 +290,7 @@ describe("DataSheet", () => {
|
|
|
290
290
|
expect(pagination).toBeFalsy();
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
-
it("
|
|
293
|
+
it("fixed column: sticky class is applied to td of fixed column", () => {
|
|
294
294
|
const { container } = render(() => (
|
|
295
295
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
296
296
|
<TestWrapper>
|
|
@@ -311,7 +311,7 @@ describe("DataSheet", () => {
|
|
|
311
311
|
expect(tds[1].classList.contains("sticky")).toBe(false);
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
-
it("
|
|
314
|
+
it("fixed column: border class is applied to the last fixed column", () => {
|
|
315
315
|
const { container } = render(() => (
|
|
316
316
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
317
317
|
<TestWrapper>
|
|
@@ -328,11 +328,11 @@ describe("DataSheet", () => {
|
|
|
328
328
|
));
|
|
329
329
|
|
|
330
330
|
const tds = container.querySelectorAll("tbody tr:first-child td");
|
|
331
|
-
//
|
|
331
|
+
// verify class included in fixedLastClass
|
|
332
332
|
expect(tds[0].classList.contains("border-r")).toBe(true);
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
it("
|
|
335
|
+
it("resizer: resizable column has a resizer handle", () => {
|
|
336
336
|
const { container } = render(() => (
|
|
337
337
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
338
338
|
<TestWrapper>
|
|
@@ -349,7 +349,7 @@ describe("DataSheet", () => {
|
|
|
349
349
|
));
|
|
350
350
|
|
|
351
351
|
const resizers = container.querySelectorAll(".cursor-ew-resize");
|
|
352
|
-
//
|
|
352
|
+
// only the first column should have a resizer
|
|
353
353
|
expect(resizers.length).toBe(1);
|
|
354
354
|
});
|
|
355
355
|
});
|
|
@@ -366,22 +366,22 @@ describe("applySorting", () => {
|
|
|
366
366
|
{ name: "나", age: 28 },
|
|
367
367
|
];
|
|
368
368
|
|
|
369
|
-
it("
|
|
369
|
+
it("preserves original order when sorts is empty", () => {
|
|
370
370
|
const result = applySorting(items, []);
|
|
371
371
|
expect(result.map((i) => i.name)).toEqual(["다", "가", "나"]);
|
|
372
372
|
});
|
|
373
373
|
|
|
374
|
-
it("
|
|
374
|
+
it("single ascending sort", () => {
|
|
375
375
|
const result = applySorting(items, [{ key: "name", desc: false }]);
|
|
376
376
|
expect(result.map((i) => i.name)).toEqual(["가", "나", "다"]);
|
|
377
377
|
});
|
|
378
378
|
|
|
379
|
-
it("
|
|
379
|
+
it("single descending sort", () => {
|
|
380
380
|
const result = applySorting(items, [{ key: "age", desc: true }]);
|
|
381
381
|
expect(result.map((i) => i.age)).toEqual([30, 28, 25]);
|
|
382
382
|
});
|
|
383
383
|
|
|
384
|
-
it("
|
|
384
|
+
it("multi-sort: first key takes priority, ties broken by second key", () => {
|
|
385
385
|
const data: Item[] = [
|
|
386
386
|
{ name: "가", age: 30 },
|
|
387
387
|
{ name: "나", age: 25 },
|
|
@@ -398,7 +398,7 @@ describe("applySorting", () => {
|
|
|
398
398
|
]);
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
-
it("
|
|
401
|
+
it("does not mutate the original array", () => {
|
|
402
402
|
const original = [...items];
|
|
403
403
|
applySorting(items, [{ key: "name", desc: false }]);
|
|
404
404
|
expect(items).toEqual(original);
|
|
@@ -421,21 +421,21 @@ describe("flattenTree", () => {
|
|
|
421
421
|
{ id: "b" },
|
|
422
422
|
];
|
|
423
423
|
|
|
424
|
-
it("getChildren
|
|
424
|
+
it("returns flat list when getChildren is not provided", () => {
|
|
425
425
|
const result = flattenTree(tree, []);
|
|
426
426
|
expect(result.map((r) => r.item.id)).toEqual(["a", "b"]);
|
|
427
427
|
expect(result.every((r) => r.depth === 0)).toBe(true);
|
|
428
428
|
expect(result.every((r) => !r.hasChildren)).toBe(true);
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
-
it("
|
|
431
|
+
it("returns only roots when all are collapsed", () => {
|
|
432
432
|
const result = flattenTree(tree, [], getChildren);
|
|
433
433
|
expect(result.map((r) => r.item.id)).toEqual(["a", "b"]);
|
|
434
434
|
expect(result[0].hasChildren).toBe(true);
|
|
435
435
|
expect(result[1].hasChildren).toBe(false);
|
|
436
436
|
});
|
|
437
437
|
|
|
438
|
-
it("
|
|
438
|
+
it("includes 1-level children when root is expanded", () => {
|
|
439
439
|
const result = flattenTree(tree, [tree[0]], getChildren);
|
|
440
440
|
expect(result.map((r) => r.item.id)).toEqual(["a", "a1", "a2", "b"]);
|
|
441
441
|
expect(result[1].depth).toBe(1);
|
|
@@ -443,7 +443,7 @@ describe("flattenTree", () => {
|
|
|
443
443
|
expect(result[2].hasChildren).toBe(true);
|
|
444
444
|
});
|
|
445
445
|
|
|
446
|
-
it("
|
|
446
|
+
it("includes up to 2-level children when nested expanded", () => {
|
|
447
447
|
const a2 = tree[0].children![1];
|
|
448
448
|
const result = flattenTree(tree, [tree[0], a2], getChildren);
|
|
449
449
|
expect(result.map((r) => r.item.id)).toEqual(["a", "a1", "a2", "a2x", "b"]);
|
|
@@ -451,24 +451,24 @@ describe("flattenTree", () => {
|
|
|
451
451
|
expect(result[3].parent).toBe(a2);
|
|
452
452
|
});
|
|
453
453
|
|
|
454
|
-
it("
|
|
455
|
-
// a2
|
|
454
|
+
it("children of collapsed nodes are not included", () => {
|
|
455
|
+
// only a2 is expanded but a is collapsed → a2 itself is not visible, so its children are also hidden
|
|
456
456
|
const a2 = tree[0].children![1];
|
|
457
457
|
const result = flattenTree(tree, [a2], getChildren);
|
|
458
458
|
expect(result.map((r) => r.item.id)).toEqual(["a", "b"]);
|
|
459
459
|
});
|
|
460
460
|
|
|
461
|
-
it("
|
|
461
|
+
it("returns empty result for empty array", () => {
|
|
462
462
|
const result = flattenTree([], [], getChildren);
|
|
463
463
|
expect(result).toEqual([]);
|
|
464
464
|
});
|
|
465
465
|
|
|
466
|
-
it("row
|
|
466
|
+
it("row increments in order", () => {
|
|
467
467
|
const result = flattenTree(tree, [tree[0]], getChildren);
|
|
468
468
|
expect(result.map((r) => r.row)).toEqual([0, 1, 2, 3]);
|
|
469
469
|
});
|
|
470
470
|
|
|
471
|
-
it("index
|
|
471
|
+
it("index returns position within containing array", () => {
|
|
472
472
|
const result = flattenTree(tree, [tree[0]], getChildren);
|
|
473
473
|
// tree[0]="a" → index 0 (root items[0])
|
|
474
474
|
// tree[0].children[0]="a1" → index 0 (children[0])
|
|
@@ -477,7 +477,7 @@ describe("flattenTree", () => {
|
|
|
477
477
|
expect(result.map((r) => r.index)).toEqual([0, 0, 1, 1]);
|
|
478
478
|
});
|
|
479
479
|
|
|
480
|
-
it("
|
|
480
|
+
it("uses original index for root when getOriginalIndex is provided", () => {
|
|
481
481
|
const items = [tree[1], tree[0]]; // reversed
|
|
482
482
|
const originalMap = new Map<TreeNode, number>();
|
|
483
483
|
tree.forEach((item, i) => originalMap.set(item, i));
|
|
@@ -505,7 +505,7 @@ describe("collectAllExpandable", () => {
|
|
|
505
505
|
|
|
506
506
|
const getChildren = (item: TreeNode) => item.children;
|
|
507
507
|
|
|
508
|
-
it("
|
|
508
|
+
it("recursively collects all nodes that have children", () => {
|
|
509
509
|
const tree: TreeNode[] = [
|
|
510
510
|
{
|
|
511
511
|
id: "a",
|
|
@@ -517,14 +517,14 @@ describe("collectAllExpandable", () => {
|
|
|
517
517
|
expect(result.map((r) => r.id)).toEqual(["a", "a2"]);
|
|
518
518
|
});
|
|
519
519
|
|
|
520
|
-
it("
|
|
520
|
+
it("returns empty array when no nodes have children", () => {
|
|
521
521
|
const tree: TreeNode[] = [{ id: "a" }, { id: "b" }];
|
|
522
522
|
const result = collectAllExpandable(tree, getChildren);
|
|
523
523
|
expect(result).toEqual([]);
|
|
524
524
|
});
|
|
525
525
|
});
|
|
526
526
|
|
|
527
|
-
describe("DataSheet
|
|
527
|
+
describe("DataSheet tree expansion", () => {
|
|
528
528
|
interface TreeItem {
|
|
529
529
|
name: string;
|
|
530
530
|
children?: TreeItem[];
|
|
@@ -538,7 +538,7 @@ describe("DataSheet 트리 확장", () => {
|
|
|
538
538
|
{ name: "폴더B" },
|
|
539
539
|
];
|
|
540
540
|
|
|
541
|
-
it("
|
|
541
|
+
it("renders expand column when getChildren is set", () => {
|
|
542
542
|
const { container } = render(() => (
|
|
543
543
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
544
544
|
<TestWrapper>
|
|
@@ -551,16 +551,16 @@ describe("DataSheet 트리 확장", () => {
|
|
|
551
551
|
</I18nProvider></ConfigProvider>
|
|
552
552
|
));
|
|
553
553
|
|
|
554
|
-
//
|
|
554
|
+
// expand column col is added to colgroup
|
|
555
555
|
const cols = container.querySelectorAll("colgroup col");
|
|
556
|
-
expect(cols.length).toBe(2); //
|
|
556
|
+
expect(cols.length).toBe(2); // expand column + name column
|
|
557
557
|
|
|
558
|
-
//
|
|
558
|
+
// expand toggle button exists in header
|
|
559
559
|
const expandBtn = container.querySelector("thead button");
|
|
560
560
|
expect(expandBtn).toBeTruthy();
|
|
561
561
|
});
|
|
562
562
|
|
|
563
|
-
it("
|
|
563
|
+
it("displays only root items in collapsed state", () => {
|
|
564
564
|
const { container } = render(() => (
|
|
565
565
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
566
566
|
<TestWrapper>
|
|
@@ -585,7 +585,7 @@ describe("DataSheet 트리 확장", () => {
|
|
|
585
585
|
expect(Array.from(names).map((n) => n.textContent)).toEqual(["폴더A", "폴더B"]);
|
|
586
586
|
});
|
|
587
587
|
|
|
588
|
-
it("
|
|
588
|
+
it("displays children of expanded items", () => {
|
|
589
589
|
const { container } = render(() => (
|
|
590
590
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
591
591
|
<TestWrapper>
|
|
@@ -604,7 +604,7 @@ describe("DataSheet 트리 확장", () => {
|
|
|
604
604
|
));
|
|
605
605
|
|
|
606
606
|
const rows = container.querySelectorAll("tbody tr");
|
|
607
|
-
expect(rows.length).toBe(4); // 폴더A, 파일A1, 파일A2, 폴더B
|
|
607
|
+
expect(rows.length).toBe(4); // 폴더A, 파일A1, 파일A2, 폴더B (test data kept as-is)
|
|
608
608
|
|
|
609
609
|
const names = container.querySelectorAll("tbody [data-testid='name']");
|
|
610
610
|
expect(Array.from(names).map((n) => n.textContent)).toEqual([
|
|
@@ -615,7 +615,7 @@ describe("DataSheet 트리 확장", () => {
|
|
|
615
615
|
]);
|
|
616
616
|
});
|
|
617
617
|
|
|
618
|
-
it("
|
|
618
|
+
it("hides expand icon for items with no children", () => {
|
|
619
619
|
const { container } = render(() => (
|
|
620
620
|
<ConfigProvider clientName="test"><I18nProvider>
|
|
621
621
|
<TestWrapper>
|
|
@@ -634,11 +634,11 @@ describe("DataSheet 트리 확장", () => {
|
|
|
634
634
|
));
|
|
635
635
|
|
|
636
636
|
const tbodyRows = container.querySelectorAll("tbody tr");
|
|
637
|
-
// 폴더B(
|
|
637
|
+
// 폴더B (index 1) has no children → no expand button
|
|
638
638
|
const secondRowBtns = tbodyRows[1].querySelectorAll("button");
|
|
639
639
|
expect(secondRowBtns.length).toBe(0);
|
|
640
640
|
|
|
641
|
-
// 폴더A(
|
|
641
|
+
// 폴더A (index 0) has children → expand button exists
|
|
642
642
|
const firstRowBtns = tbodyRows[0].querySelectorAll("button");
|
|
643
643
|
expect(firstRowBtns.length).toBe(1);
|
|
644
644
|
});
|
|
@@ -17,33 +17,33 @@ describe("Collapse", () => {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
describe("rendering", () => {
|
|
20
|
-
it("open={false}
|
|
20
|
+
it("sets visibility:hidden when open={false}", () => {
|
|
21
21
|
const { container } = render(() => <Collapse open={false}>Content</Collapse>);
|
|
22
22
|
const contentDiv = container.querySelector("[data-collapse]")
|
|
23
23
|
?.firstElementChild as HTMLElement;
|
|
24
24
|
expect(contentDiv.style.visibility).toBe("hidden");
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
it("open={true}
|
|
27
|
+
it("sets content visible when open={true}", () => {
|
|
28
28
|
const { container } = render(() => <Collapse open={true}>Content</Collapse>);
|
|
29
29
|
const contentDiv = container.querySelector("[data-collapse]")
|
|
30
30
|
?.firstElementChild as HTMLElement;
|
|
31
31
|
expect(contentDiv.style.visibility).not.toBe("hidden");
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it("
|
|
34
|
+
it("treats undefined open as false (visibility:hidden)", () => {
|
|
35
35
|
const { container } = render(() => <Collapse>Content</Collapse>);
|
|
36
36
|
const contentDiv = container.querySelector("[data-collapse]")
|
|
37
37
|
?.firstElementChild as HTMLElement;
|
|
38
38
|
expect(contentDiv.style.visibility).toBe("hidden");
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it("
|
|
41
|
+
it("renders correctly with no children", () => {
|
|
42
42
|
const { container } = render(() => <Collapse open={false} />);
|
|
43
43
|
expect(container.firstChild).toBeTruthy();
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
it("
|
|
46
|
+
it("merges custom classes", () => {
|
|
47
47
|
const { container } = render(() => (
|
|
48
48
|
// eslint-disable-next-line tailwindcss/no-custom-classname
|
|
49
49
|
<Collapse open={true} class="my-test-class">
|
|
@@ -51,14 +51,14 @@ describe("Collapse", () => {
|
|
|
51
51
|
</Collapse>
|
|
52
52
|
));
|
|
53
53
|
expect(container.querySelector(".my-test-class")).toBeTruthy();
|
|
54
|
-
// overflow: hidden
|
|
54
|
+
// overflow: hidden is applied as inline style
|
|
55
55
|
const rootDiv = container.querySelector("[data-collapse]") as HTMLElement;
|
|
56
56
|
expect(rootDiv.style.overflow).toBe("hidden");
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
describe("margin-top
|
|
61
|
-
it("
|
|
60
|
+
describe("margin-top calculation", () => {
|
|
61
|
+
it("sets margin-top to negative content height when open={false}", async () => {
|
|
62
62
|
const { container } = render(() => (
|
|
63
63
|
<Collapse open={false}>
|
|
64
64
|
<div style={{ height: "100px" }}>Content</div>
|
|
@@ -68,16 +68,16 @@ describe("Collapse", () => {
|
|
|
68
68
|
?.firstElementChild as HTMLElement;
|
|
69
69
|
expect(contentDiv).toBeTruthy();
|
|
70
70
|
|
|
71
|
-
// ResizeObserver
|
|
71
|
+
// wait for ResizeObserver to finish measuring
|
|
72
72
|
await waitFor(() => {
|
|
73
73
|
const marginTop = contentDiv.style.marginTop;
|
|
74
|
-
// margin-top
|
|
74
|
+
// verify margin-top is negative (actual height measured)
|
|
75
75
|
expect(marginTop).toMatch(/^-\d+px$/);
|
|
76
76
|
expect(parseInt(marginTop)).toBeLessThan(0);
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
it("open={true}
|
|
80
|
+
it("clears margin-top when open={true}", () => {
|
|
81
81
|
const { container } = render(() => (
|
|
82
82
|
<Collapse open={true}>
|
|
83
83
|
<div style={{ height: "100px" }}>Content</div>
|
|
@@ -89,14 +89,14 @@ describe("Collapse", () => {
|
|
|
89
89
|
});
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
-
describe("
|
|
93
|
-
it("
|
|
92
|
+
describe("initial render and transition", () => {
|
|
93
|
+
it("applies transition class after mount", () => {
|
|
94
94
|
const { container } = render(() => <Collapse open={false}>Content</Collapse>);
|
|
95
95
|
const contentDiv = container.querySelector("[data-collapse]")?.firstElementChild;
|
|
96
96
|
expect(contentDiv?.classList.contains("transition-[margin-top]")).toBeTruthy();
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
it("
|
|
99
|
+
it("retains transition class and updates margin-top on open state change", async () => {
|
|
100
100
|
const [open, setOpen] = createSignal(false);
|
|
101
101
|
const { container } = render(() => (
|
|
102
102
|
<Collapse open={open()}>
|
|
@@ -107,17 +107,17 @@ describe("Collapse", () => {
|
|
|
107
107
|
const contentDiv = container.querySelector("[data-collapse]")
|
|
108
108
|
?.firstElementChild as HTMLElement;
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// initial state: closed, negative margin-top
|
|
111
111
|
await waitFor(() => {
|
|
112
112
|
expect(parseInt(contentDiv.style.marginTop)).toBeLessThan(0);
|
|
113
113
|
});
|
|
114
114
|
expect(contentDiv.classList.contains("transition-[margin-top]")).toBeTruthy();
|
|
115
115
|
expect(contentDiv.classList.contains("duration-200")).toBeTruthy();
|
|
116
116
|
|
|
117
|
-
//
|
|
117
|
+
// change to open state
|
|
118
118
|
setOpen(true);
|
|
119
119
|
|
|
120
|
-
// transition class
|
|
120
|
+
// transition class retained, margin-top updated
|
|
121
121
|
await waitFor(() => {
|
|
122
122
|
const marginTop = contentDiv.style.marginTop;
|
|
123
123
|
expect(!marginTop || marginTop === "").toBeTruthy();
|
|
@@ -126,8 +126,8 @@ describe("Collapse", () => {
|
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
describe("
|
|
130
|
-
it("
|
|
129
|
+
describe("dynamic state changes", () => {
|
|
130
|
+
it("updates visibility when open state changes", () => {
|
|
131
131
|
const [open, setOpen] = createSignal(false);
|
|
132
132
|
const { container } = render(() => <Collapse open={open()}>Content</Collapse>);
|
|
133
133
|
|
|
@@ -139,7 +139,7 @@ describe("Collapse", () => {
|
|
|
139
139
|
expect(contentDiv.style.visibility).not.toBe("hidden");
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
it("
|
|
142
|
+
it("recalculates margin-top when content height changes", async () => {
|
|
143
143
|
const [showExtra, setShowExtra] = createSignal(false);
|
|
144
144
|
const { container } = render(() => (
|
|
145
145
|
<Collapse open={false}>
|
|
@@ -151,7 +151,7 @@ describe("Collapse", () => {
|
|
|
151
151
|
const contentDiv = container.querySelector("[data-collapse]")
|
|
152
152
|
?.firstElementChild as HTMLElement;
|
|
153
153
|
|
|
154
|
-
//
|
|
154
|
+
// wait for initial height measurement
|
|
155
155
|
await waitFor(() => {
|
|
156
156
|
const initialMarginTop = parseInt(contentDiv.style.marginTop);
|
|
157
157
|
expect(initialMarginTop).toBeLessThan(0);
|
|
@@ -159,13 +159,13 @@ describe("Collapse", () => {
|
|
|
159
159
|
|
|
160
160
|
const initialMarginTop = parseInt(contentDiv.style.marginTop);
|
|
161
161
|
|
|
162
|
-
//
|
|
162
|
+
// add content to change height
|
|
163
163
|
setShowExtra(true);
|
|
164
164
|
|
|
165
|
-
// ResizeObserver
|
|
165
|
+
// wait for ResizeObserver to detect new height and recalculate margin-top
|
|
166
166
|
await waitFor(() => {
|
|
167
167
|
const newMarginTop = parseInt(contentDiv.style.marginTop);
|
|
168
|
-
//
|
|
168
|
+
// new margin-top should be more negative than initial (larger content)
|
|
169
169
|
expect(newMarginTop).toBeLessThan(initialMarginTop);
|
|
170
170
|
});
|
|
171
171
|
});
|