@izumisy-tailor/tailor-data-viewer 0.2.32 → 0.2.33
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 +59 -51
- package/package.json +1 -1
- package/src/component/data-table/column-selector.test.tsx +3 -4
- package/src/component/data-table/column-selector.tsx +3 -4
- package/src/component/data-table/csv-button.test.tsx +6 -2
- package/src/component/data-table/csv-button.tsx +4 -10
- package/src/component/data-table/data-table.test.tsx +10 -16
- package/src/component/data-table/data-table.tsx +27 -55
- package/src/component/data-table/pagination.test.tsx +2 -2
- package/src/component/data-table/search-filter-form.test.tsx +9 -12
- package/src/component/data-table/search-filter-form.tsx +8 -14
- package/src/component/data-table/use-data-table.test.ts +13 -16
- package/src/component/data-table/use-data-table.ts +1 -1
- package/src/component/field-helpers.test.ts +219 -136
- package/src/component/field-helpers.ts +93 -144
- package/src/component/index.ts +1 -6
- package/src/component/types.ts +47 -103
|
@@ -12,18 +12,17 @@ type TestRow = {
|
|
|
12
12
|
|
|
13
13
|
const testColumns: Column<TestRow>[] = [
|
|
14
14
|
{
|
|
15
|
-
kind: "field",
|
|
16
|
-
dataKey: "name",
|
|
17
15
|
label: "Name",
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
render: (row) => row.name,
|
|
17
|
+
sort: { field: "name", type: "string" },
|
|
18
|
+
filter: { field: "name", type: "string" },
|
|
20
19
|
},
|
|
21
20
|
{
|
|
22
|
-
kind: "field",
|
|
23
|
-
dataKey: "status",
|
|
24
21
|
label: "Status",
|
|
25
|
-
|
|
22
|
+
render: (row) => row.status,
|
|
23
|
+
sort: { field: "status", type: "string" },
|
|
26
24
|
filter: {
|
|
25
|
+
field: "status",
|
|
27
26
|
type: "enum",
|
|
28
27
|
options: [
|
|
29
28
|
{ value: "ACTIVE", label: "Active" },
|
|
@@ -32,13 +31,11 @@ const testColumns: Column<TestRow>[] = [
|
|
|
32
31
|
},
|
|
33
32
|
},
|
|
34
33
|
{
|
|
35
|
-
kind: "field",
|
|
36
|
-
dataKey: "amount",
|
|
37
34
|
label: "Amount",
|
|
38
|
-
|
|
35
|
+
render: (row) => String(row.amount),
|
|
36
|
+
sort: { field: "amount", type: "number" },
|
|
39
37
|
},
|
|
40
38
|
{
|
|
41
|
-
kind: "display",
|
|
42
39
|
id: "actions",
|
|
43
40
|
label: "Actions",
|
|
44
41
|
render: () => null,
|
|
@@ -115,7 +112,7 @@ describe("useDataTable", () => {
|
|
|
115
112
|
useDataTable<TestRow>({ columns: testColumns, data: testData }),
|
|
116
113
|
);
|
|
117
114
|
expect(result.current.visibleColumns).toHaveLength(4);
|
|
118
|
-
expect(result.current.isColumnVisible("
|
|
115
|
+
expect(result.current.isColumnVisible("Name")).toBe(true);
|
|
119
116
|
expect(result.current.isColumnVisible("actions")).toBe(true);
|
|
120
117
|
});
|
|
121
118
|
|
|
@@ -125,16 +122,16 @@ describe("useDataTable", () => {
|
|
|
125
122
|
);
|
|
126
123
|
|
|
127
124
|
act(() => {
|
|
128
|
-
result.current.toggleColumn("
|
|
125
|
+
result.current.toggleColumn("Status");
|
|
129
126
|
});
|
|
130
127
|
expect(result.current.visibleColumns).toHaveLength(3);
|
|
131
|
-
expect(result.current.isColumnVisible("
|
|
128
|
+
expect(result.current.isColumnVisible("Status")).toBe(false);
|
|
132
129
|
|
|
133
130
|
act(() => {
|
|
134
|
-
result.current.toggleColumn("
|
|
131
|
+
result.current.toggleColumn("Status");
|
|
135
132
|
});
|
|
136
133
|
expect(result.current.visibleColumns).toHaveLength(4);
|
|
137
|
-
expect(result.current.isColumnVisible("
|
|
134
|
+
expect(result.current.isColumnVisible("Status")).toBe(true);
|
|
138
135
|
});
|
|
139
136
|
|
|
140
137
|
it("hideAllColumns hides all columns", () => {
|
|
@@ -93,7 +93,7 @@ export function useDataTable<TRow extends Record<string, unknown>>(
|
|
|
93
93
|
const [hiddenColumns, setHiddenColumns] = useState<Set<string>>(new Set());
|
|
94
94
|
|
|
95
95
|
const getColumnKey = useCallback((col: Column<TRow>): string => {
|
|
96
|
-
return col.
|
|
96
|
+
return col.id ?? col.label ?? "";
|
|
97
97
|
}, []);
|
|
98
98
|
|
|
99
99
|
const visibleColumns = useMemo<Column<TRow>[]>(() => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, expectTypeOf } from "vitest";
|
|
2
|
-
import { createColumnHelper } from "./field-helpers";
|
|
2
|
+
import { column, inferColumns, createColumnHelper } from "./field-helpers";
|
|
3
3
|
import type { TableMetadataMap } from "../generator/metadata-generator";
|
|
4
4
|
import { fieldTypeToSortConfig, fieldTypeToFilterConfig } from "./types";
|
|
5
5
|
import type { NodeType, TableFieldName } from "./types";
|
|
@@ -19,62 +19,58 @@ describe("NodeType", () => {
|
|
|
19
19
|
type Row = NodeType<Result>;
|
|
20
20
|
expectTypeOf<Row>().toEqualTypeOf<{ id: string; amount: number }>();
|
|
21
21
|
});
|
|
22
|
-
|
|
23
|
-
it("works with createColumnHelper", () => {
|
|
24
|
-
type Result = { edges: { node: { id: string; title: string } }[] } | null;
|
|
25
|
-
type Row = NodeType<Result>;
|
|
26
|
-
const { field } = createColumnHelper<Row>();
|
|
27
|
-
const col = field("title");
|
|
28
|
-
expect(col.dataKey).toBe("title");
|
|
29
|
-
});
|
|
30
22
|
});
|
|
31
23
|
|
|
32
|
-
describe("
|
|
24
|
+
describe("column()", () => {
|
|
33
25
|
type TestRow = { name: string; age: number };
|
|
34
26
|
|
|
35
|
-
it("
|
|
36
|
-
const
|
|
37
|
-
expect(typeof helpers.field).toBe("function");
|
|
38
|
-
expect(typeof helpers.display).toBe("function");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("field helper creates a field column without explicit type param", () => {
|
|
42
|
-
const { field } = createColumnHelper<TestRow>();
|
|
43
|
-
const col = field("name", {
|
|
27
|
+
it("creates a column with render and sort/filter", () => {
|
|
28
|
+
const col = column<TestRow>({
|
|
44
29
|
label: "Name",
|
|
45
|
-
|
|
46
|
-
|
|
30
|
+
render: (row) => row.name,
|
|
31
|
+
sort: { field: "name", type: "string" },
|
|
32
|
+
filter: { field: "name", type: "string" },
|
|
47
33
|
});
|
|
48
|
-
expect(col.kind).toBe("field");
|
|
49
|
-
expect(col.dataKey).toBe("name");
|
|
50
34
|
expect(col.label).toBe("Name");
|
|
51
|
-
expect(col.sort).toEqual({ type: "string" });
|
|
52
|
-
expect(col.filter).toEqual({ type: "string" });
|
|
35
|
+
expect(col.sort).toEqual({ field: "name", type: "string" });
|
|
36
|
+
expect(col.filter).toEqual({ field: "name", type: "string" });
|
|
37
|
+
expect(typeof col.render).toBe("function");
|
|
53
38
|
});
|
|
54
39
|
|
|
55
|
-
it("
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
expect(col.label).
|
|
40
|
+
it("creates minimal column with label and render", () => {
|
|
41
|
+
const col = column<TestRow>({
|
|
42
|
+
label: "Age",
|
|
43
|
+
render: (row) => String(row.age),
|
|
44
|
+
});
|
|
45
|
+
expect(col.label).toBe("Age");
|
|
46
|
+
expect(col.sort).toBeUndefined();
|
|
47
|
+
expect(col.filter).toBeUndefined();
|
|
61
48
|
});
|
|
62
49
|
|
|
63
|
-
it("
|
|
64
|
-
const
|
|
65
|
-
const col = display("actions", {
|
|
50
|
+
it("supports accessor and width", () => {
|
|
51
|
+
const col = column<TestRow>({
|
|
66
52
|
label: "Actions",
|
|
67
53
|
width: 100,
|
|
68
54
|
render: (row) => `${row.name}: ${row.age}`,
|
|
55
|
+
accessor: (row) => row.name,
|
|
69
56
|
});
|
|
70
|
-
expect(col.kind).toBe("display");
|
|
71
|
-
expect(col.id).toBe("actions");
|
|
72
57
|
expect(col.label).toBe("Actions");
|
|
73
58
|
expect(col.width).toBe(100);
|
|
74
59
|
expect(typeof col.render).toBe("function");
|
|
60
|
+
expect(typeof col.accessor).toBe("function");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("allows undefined label", () => {
|
|
64
|
+
const col = column<TestRow>({
|
|
65
|
+
render: (row) => row.name,
|
|
66
|
+
});
|
|
67
|
+
expect(col.label).toBeUndefined();
|
|
68
|
+
expect(typeof col.render).toBe("function");
|
|
75
69
|
});
|
|
70
|
+
});
|
|
76
71
|
|
|
77
|
-
|
|
72
|
+
describe("inferColumns()", () => {
|
|
73
|
+
it("returns a function that produces column options from metadata", () => {
|
|
78
74
|
type TaskRow = { id: string; title: string; status: string };
|
|
79
75
|
const metadata = {
|
|
80
76
|
name: "task",
|
|
@@ -92,75 +88,103 @@ describe("createColumnHelper()", () => {
|
|
|
92
88
|
],
|
|
93
89
|
} as const;
|
|
94
90
|
|
|
95
|
-
const
|
|
96
|
-
const { column, columns } = inferColumns(metadata);
|
|
91
|
+
const infer = inferColumns<TaskRow>(metadata);
|
|
97
92
|
|
|
98
|
-
//
|
|
99
|
-
const
|
|
100
|
-
expect(
|
|
101
|
-
expect(
|
|
93
|
+
// infer() returns ColumnOptions that can be passed to column()
|
|
94
|
+
const titleOpts = infer("title");
|
|
95
|
+
expect(titleOpts.label).toBe("title");
|
|
96
|
+
expect(titleOpts.sort).toEqual({ field: "title", type: "string" });
|
|
97
|
+
expect(typeof titleOpts.render).toBe("function");
|
|
98
|
+
expect(typeof titleOpts.accessor).toBe("function");
|
|
102
99
|
|
|
103
|
-
//
|
|
104
|
-
const
|
|
105
|
-
expect(
|
|
100
|
+
// Can be used with column()
|
|
101
|
+
const titleCol = column(infer("title"));
|
|
102
|
+
expect(titleCol.label).toBe("title");
|
|
103
|
+
expect(titleCol.sort).toEqual({ field: "title", type: "string" });
|
|
106
104
|
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
|
|
105
|
+
// Can be spread and overridden
|
|
106
|
+
const customCol = column({
|
|
107
|
+
...infer("status"),
|
|
108
|
+
label: "Custom Status",
|
|
110
109
|
});
|
|
111
|
-
expect(
|
|
110
|
+
expect(customCol.label).toBe("Custom Status");
|
|
112
111
|
});
|
|
113
112
|
});
|
|
114
113
|
|
|
115
114
|
describe("fieldTypeToSortConfig", () => {
|
|
116
115
|
it("maps string to string sort", () => {
|
|
117
|
-
expect(fieldTypeToSortConfig("string")).toEqual({
|
|
116
|
+
expect(fieldTypeToSortConfig("name", "string")).toEqual({
|
|
117
|
+
field: "name",
|
|
118
|
+
type: "string",
|
|
119
|
+
});
|
|
118
120
|
});
|
|
119
121
|
|
|
120
122
|
it("maps number to number sort", () => {
|
|
121
|
-
expect(fieldTypeToSortConfig("number")).toEqual({
|
|
123
|
+
expect(fieldTypeToSortConfig("amount", "number")).toEqual({
|
|
124
|
+
field: "amount",
|
|
125
|
+
type: "number",
|
|
126
|
+
});
|
|
122
127
|
});
|
|
123
128
|
|
|
124
129
|
it("maps datetime to date sort", () => {
|
|
125
|
-
expect(fieldTypeToSortConfig("datetime")).toEqual({
|
|
130
|
+
expect(fieldTypeToSortConfig("createdAt", "datetime")).toEqual({
|
|
131
|
+
field: "createdAt",
|
|
132
|
+
type: "date",
|
|
133
|
+
});
|
|
126
134
|
});
|
|
127
135
|
|
|
128
136
|
it("maps date to date sort", () => {
|
|
129
|
-
expect(fieldTypeToSortConfig("date")).toEqual({
|
|
137
|
+
expect(fieldTypeToSortConfig("dueDate", "date")).toEqual({
|
|
138
|
+
field: "dueDate",
|
|
139
|
+
type: "date",
|
|
140
|
+
});
|
|
130
141
|
});
|
|
131
142
|
|
|
132
143
|
it("maps enum to string sort", () => {
|
|
133
|
-
expect(fieldTypeToSortConfig("enum")).toEqual({
|
|
144
|
+
expect(fieldTypeToSortConfig("status", "enum")).toEqual({
|
|
145
|
+
field: "status",
|
|
146
|
+
type: "string",
|
|
147
|
+
});
|
|
134
148
|
});
|
|
135
149
|
|
|
136
150
|
it("returns undefined for uuid", () => {
|
|
137
|
-
expect(fieldTypeToSortConfig("uuid")).toBeUndefined();
|
|
151
|
+
expect(fieldTypeToSortConfig("id", "uuid")).toBeUndefined();
|
|
138
152
|
});
|
|
139
153
|
|
|
140
154
|
it("returns undefined for array", () => {
|
|
141
|
-
expect(fieldTypeToSortConfig("array")).toBeUndefined();
|
|
155
|
+
expect(fieldTypeToSortConfig("tags", "array")).toBeUndefined();
|
|
142
156
|
});
|
|
143
157
|
|
|
144
158
|
it("returns undefined for nested", () => {
|
|
145
|
-
expect(fieldTypeToSortConfig("nested")).toBeUndefined();
|
|
159
|
+
expect(fieldTypeToSortConfig("meta", "nested")).toBeUndefined();
|
|
146
160
|
});
|
|
147
161
|
});
|
|
148
162
|
|
|
149
163
|
describe("fieldTypeToFilterConfig", () => {
|
|
150
164
|
it("maps string to string filter", () => {
|
|
151
|
-
expect(fieldTypeToFilterConfig("string")).toEqual({
|
|
165
|
+
expect(fieldTypeToFilterConfig("name", "string")).toEqual({
|
|
166
|
+
field: "name",
|
|
167
|
+
type: "string",
|
|
168
|
+
});
|
|
152
169
|
});
|
|
153
170
|
|
|
154
171
|
it("maps number to number filter", () => {
|
|
155
|
-
expect(fieldTypeToFilterConfig("number")).toEqual({
|
|
172
|
+
expect(fieldTypeToFilterConfig("amount", "number")).toEqual({
|
|
173
|
+
field: "amount",
|
|
174
|
+
type: "number",
|
|
175
|
+
});
|
|
156
176
|
});
|
|
157
177
|
|
|
158
178
|
it("maps uuid to uuid filter", () => {
|
|
159
|
-
expect(fieldTypeToFilterConfig("uuid")).toEqual({
|
|
179
|
+
expect(fieldTypeToFilterConfig("id", "uuid")).toEqual({
|
|
180
|
+
field: "id",
|
|
181
|
+
type: "uuid",
|
|
182
|
+
});
|
|
160
183
|
});
|
|
161
184
|
|
|
162
185
|
it("maps enum with values to enum filter", () => {
|
|
163
|
-
expect(fieldTypeToFilterConfig("enum", ["a", "b", "c"])).toEqual({
|
|
186
|
+
expect(fieldTypeToFilterConfig("status", "enum", ["a", "b", "c"])).toEqual({
|
|
187
|
+
field: "status",
|
|
164
188
|
type: "enum",
|
|
165
189
|
options: [
|
|
166
190
|
{ value: "a", label: "a" },
|
|
@@ -171,11 +195,11 @@ describe("fieldTypeToFilterConfig", () => {
|
|
|
171
195
|
});
|
|
172
196
|
|
|
173
197
|
it("returns undefined for array", () => {
|
|
174
|
-
expect(fieldTypeToFilterConfig("array")).toBeUndefined();
|
|
198
|
+
expect(fieldTypeToFilterConfig("tags", "array")).toBeUndefined();
|
|
175
199
|
});
|
|
176
200
|
});
|
|
177
201
|
|
|
178
|
-
describe("
|
|
202
|
+
describe("inferColumns() with metadata", () => {
|
|
179
203
|
const testMetadata = {
|
|
180
204
|
task: {
|
|
181
205
|
name: "task",
|
|
@@ -213,22 +237,22 @@ describe("createColumnHelper().inferColumns()", () => {
|
|
|
213
237
|
tags: string[];
|
|
214
238
|
};
|
|
215
239
|
|
|
216
|
-
it("creates
|
|
217
|
-
const
|
|
218
|
-
const { column } = inferColumns(testMetadata.task);
|
|
240
|
+
it("creates column options with auto-detected sort/filter", () => {
|
|
241
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
219
242
|
|
|
220
|
-
const
|
|
221
|
-
expect(
|
|
222
|
-
expect(
|
|
223
|
-
expect(
|
|
224
|
-
expect(
|
|
243
|
+
const titleOpts = infer("title");
|
|
244
|
+
expect(titleOpts.label).toBe("title");
|
|
245
|
+
expect(titleOpts.sort).toEqual({ field: "title", type: "string" });
|
|
246
|
+
expect(titleOpts.filter).toEqual({ field: "title", type: "string" });
|
|
247
|
+
expect(typeof titleOpts.render).toBe("function");
|
|
248
|
+
expect(typeof titleOpts.accessor).toBe("function");
|
|
225
249
|
});
|
|
226
250
|
|
|
227
251
|
it("auto-detects enum options", () => {
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
252
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
253
|
+
const statusOpts = infer("status");
|
|
254
|
+
expect(statusOpts.filter).toEqual({
|
|
255
|
+
field: "status",
|
|
232
256
|
type: "enum",
|
|
233
257
|
options: [
|
|
234
258
|
{ value: "todo", label: "todo" },
|
|
@@ -237,88 +261,68 @@ describe("createColumnHelper().inferColumns()", () => {
|
|
|
237
261
|
],
|
|
238
262
|
});
|
|
239
263
|
// enum maps to string sort
|
|
240
|
-
expect(
|
|
264
|
+
expect(statusOpts.sort).toEqual({ field: "status", type: "string" });
|
|
241
265
|
});
|
|
242
266
|
|
|
243
267
|
it("auto-detects date type", () => {
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
expect(
|
|
248
|
-
expect(dateCol.filter).toEqual({ type: "date" });
|
|
268
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
269
|
+
const dateOpts = infer("dueDate");
|
|
270
|
+
expect(dateOpts.sort).toEqual({ field: "dueDate", type: "date" });
|
|
271
|
+
expect(dateOpts.filter).toEqual({ field: "dueDate", type: "date" });
|
|
249
272
|
});
|
|
250
273
|
|
|
251
274
|
it("disables sort with sort: false", () => {
|
|
252
|
-
const
|
|
253
|
-
const {
|
|
254
|
-
|
|
255
|
-
expect(
|
|
256
|
-
expect(col.filter).toEqual({ type: "string" });
|
|
275
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
276
|
+
const opts = infer("title", { sort: false });
|
|
277
|
+
expect(opts.sort).toBeUndefined();
|
|
278
|
+
expect(opts.filter).toEqual({ field: "title", type: "string" });
|
|
257
279
|
});
|
|
258
280
|
|
|
259
281
|
it("disables filter with filter: false", () => {
|
|
260
|
-
const
|
|
261
|
-
const {
|
|
262
|
-
|
|
263
|
-
expect(
|
|
264
|
-
expect(col.filter).toBeUndefined();
|
|
282
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
283
|
+
const opts = infer("title", { filter: false });
|
|
284
|
+
expect(opts.sort).toEqual({ field: "title", type: "string" });
|
|
285
|
+
expect(opts.filter).toBeUndefined();
|
|
265
286
|
});
|
|
266
287
|
|
|
267
288
|
it("uuid has no sort, has uuid filter", () => {
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
expect(
|
|
272
|
-
expect(col.filter).toEqual({ type: "uuid" });
|
|
289
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
290
|
+
const opts = infer("id");
|
|
291
|
+
expect(opts.sort).toBeUndefined();
|
|
292
|
+
expect(opts.filter).toEqual({ field: "id", type: "uuid" });
|
|
273
293
|
});
|
|
274
294
|
|
|
275
295
|
it("array type has no sort/filter", () => {
|
|
276
|
-
const
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
expect(
|
|
280
|
-
expect(col.filter).toBeUndefined();
|
|
296
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
297
|
+
const opts = infer("tags");
|
|
298
|
+
expect(opts.sort).toBeUndefined();
|
|
299
|
+
expect(opts.filter).toBeUndefined();
|
|
281
300
|
});
|
|
282
301
|
|
|
283
|
-
it("
|
|
284
|
-
const
|
|
285
|
-
const { columns } = inferColumns(testMetadata.task);
|
|
286
|
-
const cols = columns(["title", "status", "dueDate"]);
|
|
287
|
-
expect(cols).toHaveLength(3);
|
|
288
|
-
expect(cols[0].dataKey).toBe("title");
|
|
289
|
-
expect(cols[1].dataKey).toBe("status");
|
|
290
|
-
expect(cols[2].dataKey).toBe("dueDate");
|
|
291
|
-
});
|
|
302
|
+
it("generates default render and accessor functions", () => {
|
|
303
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
292
304
|
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
});
|
|
305
|
+
const opts = infer("title");
|
|
306
|
+
const testRow = {
|
|
307
|
+
id: "1",
|
|
308
|
+
title: "Test Task",
|
|
309
|
+
status: "todo",
|
|
310
|
+
dueDate: "2024-01-01",
|
|
311
|
+
count: 5,
|
|
312
|
+
isActive: true,
|
|
313
|
+
tags: ["a"],
|
|
314
|
+
};
|
|
304
315
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const { columns } = inferColumns(testMetadata.task);
|
|
308
|
-
const cols = columns(["title", "status"], { sort: false });
|
|
309
|
-
expect(cols[0].sort).toBeUndefined();
|
|
310
|
-
expect(cols[1].sort).toBeUndefined();
|
|
311
|
-
// filter should still be auto-detected
|
|
312
|
-
expect(cols[0].filter).toEqual({ type: "string" });
|
|
316
|
+
expect(opts.render(testRow)).toBe("Test Task");
|
|
317
|
+
expect(opts.accessor!(testRow)).toBe("Test Task");
|
|
313
318
|
});
|
|
314
319
|
|
|
315
320
|
it("throws for non-existent field", () => {
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
);
|
|
321
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
322
|
+
expect(() =>
|
|
323
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
324
|
+
infer("nonExistent" as any),
|
|
325
|
+
).toThrow('Field "nonExistent" not found in table "task" metadata');
|
|
322
326
|
});
|
|
323
327
|
|
|
324
328
|
it("infers TableFieldName type correctly", () => {
|
|
@@ -327,4 +331,83 @@ describe("createColumnHelper().inferColumns()", () => {
|
|
|
327
331
|
"id" | "title" | "status" | "dueDate" | "count" | "isActive" | "tags"
|
|
328
332
|
>();
|
|
329
333
|
});
|
|
334
|
+
|
|
335
|
+
it("spread override works with column()", () => {
|
|
336
|
+
const infer = inferColumns<TaskRow>(testMetadata.task);
|
|
337
|
+
|
|
338
|
+
const col = column({
|
|
339
|
+
...infer("title"),
|
|
340
|
+
label: "Custom Title",
|
|
341
|
+
width: 200,
|
|
342
|
+
});
|
|
343
|
+
expect(col.label).toBe("Custom Title");
|
|
344
|
+
expect(col.width).toBe(200);
|
|
345
|
+
expect(col.sort).toEqual({ field: "title", type: "string" });
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
describe("createColumnHelper()", () => {
|
|
350
|
+
type OrderRow = { id: string; name: string; amount: number };
|
|
351
|
+
|
|
352
|
+
it("returns column and inferColumns with TRow bound", () => {
|
|
353
|
+
const helper = createColumnHelper<OrderRow>();
|
|
354
|
+
expect(typeof helper.column).toBe("function");
|
|
355
|
+
expect(typeof helper.inferColumns).toBe("function");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("column() works without type parameter", () => {
|
|
359
|
+
const { column } = createColumnHelper<OrderRow>();
|
|
360
|
+
const col = column({
|
|
361
|
+
label: "Name",
|
|
362
|
+
render: (row) => row.name,
|
|
363
|
+
sort: { field: "name", type: "string" },
|
|
364
|
+
});
|
|
365
|
+
expect(col.label).toBe("Name");
|
|
366
|
+
expect(col.sort).toEqual({ field: "name", type: "string" });
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("inferColumns() works without type parameter", () => {
|
|
370
|
+
const metadata = {
|
|
371
|
+
name: "order",
|
|
372
|
+
pluralForm: "orders",
|
|
373
|
+
readAllowedRoles: [],
|
|
374
|
+
fields: [
|
|
375
|
+
{ name: "id", type: "uuid", required: true },
|
|
376
|
+
{ name: "name", type: "string", required: true },
|
|
377
|
+
{ name: "amount", type: "number", required: false },
|
|
378
|
+
],
|
|
379
|
+
} as const;
|
|
380
|
+
|
|
381
|
+
const { column, inferColumns } = createColumnHelper<OrderRow>();
|
|
382
|
+
const infer = inferColumns(metadata);
|
|
383
|
+
|
|
384
|
+
const col = column(infer("name"));
|
|
385
|
+
expect(col.label).toBe("name");
|
|
386
|
+
expect(col.sort).toEqual({ field: "name", type: "string" });
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("column + inferColumns spread override", () => {
|
|
390
|
+
const metadata = {
|
|
391
|
+
name: "order",
|
|
392
|
+
pluralForm: "orders",
|
|
393
|
+
readAllowedRoles: [],
|
|
394
|
+
fields: [
|
|
395
|
+
{ name: "id", type: "uuid", required: true },
|
|
396
|
+
{ name: "name", type: "string", required: true },
|
|
397
|
+
{ name: "amount", type: "number", required: false },
|
|
398
|
+
],
|
|
399
|
+
} as const;
|
|
400
|
+
|
|
401
|
+
const { column, inferColumns } = createColumnHelper<OrderRow>();
|
|
402
|
+
const infer = inferColumns(metadata);
|
|
403
|
+
|
|
404
|
+
const col = column({
|
|
405
|
+
...infer("name"),
|
|
406
|
+
label: "Custom Name",
|
|
407
|
+
render: (row) => `Name: ${row.name}`,
|
|
408
|
+
});
|
|
409
|
+
expect(col.label).toBe("Custom Name");
|
|
410
|
+
expect(col.sort).toEqual({ field: "name", type: "string" });
|
|
411
|
+
expect(col.render({ id: "1", name: "Test", amount: 0 })).toBe("Name: Test");
|
|
412
|
+
});
|
|
330
413
|
});
|