@izumisy-tailor/tailor-data-viewer 0.1.20 → 0.1.22
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 +55 -2
- package/dist/generator/index.d.mts +54 -31
- package/dist/generator/index.mjs +20 -13
- package/docs/app-shell-module.md +3 -3
- package/docs/compositional-api.md +366 -0
- package/package.json +1 -1
- package/src/app-shell/create-data-view-module.tsx +3 -3
- package/src/app-shell/types.ts +5 -5
- package/src/component/column-selector.test.tsx +143 -103
- package/src/component/column-selector.tsx +121 -156
- package/src/component/contexts/data-viewer-context.test.tsx +191 -0
- package/src/component/contexts/data-viewer-context.tsx +244 -0
- package/src/component/contexts/index.ts +19 -0
- package/src/component/contexts/table-data-context.tsx +114 -0
- package/src/component/contexts/toolbar-context.tsx +62 -0
- package/src/component/csv-button.tsx +79 -0
- package/src/component/data-table-toolbar.test.tsx +127 -72
- package/src/component/data-table-toolbar.tsx +14 -151
- package/src/component/data-table.tsx +255 -225
- package/src/component/data-view-tab-content.tsx +67 -138
- package/src/component/data-viewer.tsx +11 -11
- package/src/component/hooks/use-column-state.ts +2 -2
- package/src/component/index.ts +33 -1
- package/src/component/refresh-button.tsx +20 -0
- package/src/component/search-filter.tsx +19 -24
- package/src/component/single-record-tab-content.test.tsx +10 -10
- package/src/component/single-record-tab-content.tsx +62 -21
- package/src/component/view-save-load.tsx +13 -17
- package/src/generator/metadata-generator.ts +100 -67
|
@@ -11,64 +11,36 @@ import {
|
|
|
11
11
|
DropdownMenuSeparator,
|
|
12
12
|
DropdownMenuLabel,
|
|
13
13
|
} from "./ui/dropdown-menu";
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
RelationMetadata,
|
|
17
|
-
TableMetadataMap,
|
|
18
|
-
ExpandedRelationFields,
|
|
19
|
-
} from "../generator/metadata-generator";
|
|
20
|
-
|
|
21
|
-
interface ColumnSelectorProps {
|
|
22
|
-
fields: FieldMetadata[];
|
|
23
|
-
selectedFields: string[];
|
|
24
|
-
onToggle: (fieldName: string) => void;
|
|
25
|
-
onSelectAll: () => void;
|
|
26
|
-
onDeselectAll: () => void;
|
|
27
|
-
/** Relations for the current table */
|
|
28
|
-
relations?: RelationMetadata[];
|
|
29
|
-
/** Currently selected relation field names */
|
|
30
|
-
selectedRelations?: string[];
|
|
31
|
-
/** Toggle relation visibility */
|
|
32
|
-
onToggleRelation?: (fieldName: string) => void;
|
|
33
|
-
/** Table metadata map for relation field lookups */
|
|
34
|
-
tableMetadataMap?: TableMetadataMap;
|
|
35
|
-
/** Expanded relation fields (manyToOne fields shown as inline columns) */
|
|
36
|
-
expandedRelationFields?: ExpandedRelationFields;
|
|
37
|
-
/** Toggle a field within an expanded relation */
|
|
38
|
-
onToggleExpandedRelationField?: (
|
|
39
|
-
relationFieldName: string,
|
|
40
|
-
fieldName: string,
|
|
41
|
-
) => void;
|
|
42
|
-
/** Check if a field is selected within an expanded relation */
|
|
43
|
-
isExpandedRelationFieldSelected?: (
|
|
44
|
-
relationFieldName: string,
|
|
45
|
-
fieldName: string,
|
|
46
|
-
) => boolean;
|
|
47
|
-
/** Controlled open state */
|
|
48
|
-
open?: boolean;
|
|
49
|
-
/** Callback when open state changes */
|
|
50
|
-
onOpenChange?: (open: boolean) => void;
|
|
51
|
-
}
|
|
14
|
+
import { useDataViewer } from "./contexts";
|
|
15
|
+
import { useToolbar } from "./contexts";
|
|
52
16
|
|
|
53
17
|
/**
|
|
54
18
|
* Column visibility selector with checkboxes
|
|
19
|
+
* Must be used within DataViewer.Root and DataViewer.Toolbar context
|
|
55
20
|
*/
|
|
56
|
-
export function ColumnSelector({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
21
|
+
export function ColumnSelector() {
|
|
22
|
+
const {
|
|
23
|
+
tableMetadata,
|
|
24
|
+
metadata,
|
|
25
|
+
selectedFields,
|
|
26
|
+
toggleField,
|
|
27
|
+
selectAllFields,
|
|
28
|
+
deselectAllFields,
|
|
29
|
+
selectedRelations,
|
|
30
|
+
toggleRelation,
|
|
31
|
+
expandedRelationFields,
|
|
32
|
+
toggleExpandedRelationField,
|
|
33
|
+
isExpandedRelationFieldSelected,
|
|
34
|
+
} = useDataViewer();
|
|
35
|
+
const { activePanel, setActivePanel } = useToolbar();
|
|
36
|
+
|
|
37
|
+
const fields = tableMetadata?.fields ?? [];
|
|
38
|
+
const relations = tableMetadata?.relations ?? [];
|
|
39
|
+
|
|
40
|
+
const open = activePanel === "column";
|
|
41
|
+
const onOpenChange = (isOpen: boolean) =>
|
|
42
|
+
setActivePanel(isOpen ? "column" : null);
|
|
43
|
+
|
|
72
44
|
// Filter out nested fields as they're not directly selectable
|
|
73
45
|
const selectableFields = fields.filter((field) => field.type !== "nested");
|
|
74
46
|
|
|
@@ -102,10 +74,10 @@ export function ColumnSelector({
|
|
|
102
74
|
|
|
103
75
|
<DropdownMenuContent align="start" className="w-64">
|
|
104
76
|
<div className="flex gap-1 border-b px-2 py-1.5">
|
|
105
|
-
<Button variant="ghost" size="sm" onClick={
|
|
77
|
+
<Button variant="ghost" size="sm" onClick={selectAllFields}>
|
|
106
78
|
全選択
|
|
107
79
|
</Button>
|
|
108
|
-
<Button variant="ghost" size="sm" onClick={
|
|
80
|
+
<Button variant="ghost" size="sm" onClick={deselectAllFields}>
|
|
109
81
|
全解除
|
|
110
82
|
</Button>
|
|
111
83
|
</div>
|
|
@@ -119,7 +91,7 @@ export function ColumnSelector({
|
|
|
119
91
|
>
|
|
120
92
|
<Checkbox
|
|
121
93
|
checked={selectedFields.includes(field.name)}
|
|
122
|
-
onCheckedChange={() =>
|
|
94
|
+
onCheckedChange={() => toggleField(field.name)}
|
|
123
95
|
/>
|
|
124
96
|
<span
|
|
125
97
|
className="truncate"
|
|
@@ -135,108 +107,103 @@ export function ColumnSelector({
|
|
|
135
107
|
</div>
|
|
136
108
|
|
|
137
109
|
{/* ManyToOne Relations with submenu for field expansion */}
|
|
138
|
-
{manyToOneRelations.length > 0 &&
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const selectedExpandedFields =
|
|
154
|
-
expandedRelationFields[relation.fieldName] ?? [];
|
|
110
|
+
{manyToOneRelations.length > 0 && metadata && (
|
|
111
|
+
<>
|
|
112
|
+
<DropdownMenuSeparator />
|
|
113
|
+
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
|
114
|
+
リレーション (1対1)
|
|
115
|
+
</DropdownMenuLabel>
|
|
116
|
+
<div className="space-y-1">
|
|
117
|
+
{manyToOneRelations.map((relation) => {
|
|
118
|
+
const targetTable = metadata[relation.targetTable];
|
|
119
|
+
const targetFields =
|
|
120
|
+
targetTable?.fields.filter(
|
|
121
|
+
(f) => f.type !== "nested" && f.name !== "id",
|
|
122
|
+
) ?? [];
|
|
123
|
+
const selectedExpandedFields =
|
|
124
|
+
expandedRelationFields[relation.fieldName] ?? [];
|
|
155
125
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
key={relation.fieldName}
|
|
129
|
+
className="flex items-center gap-1"
|
|
130
|
+
>
|
|
131
|
+
{/* Inline toggle checkbox */}
|
|
132
|
+
<label className="hover:bg-accent flex flex-1 cursor-pointer items-center gap-2 rounded-sm px-2 py-1 text-sm">
|
|
133
|
+
<Checkbox
|
|
134
|
+
checked={selectedRelations.includes(
|
|
135
|
+
relation.fieldName,
|
|
136
|
+
)}
|
|
137
|
+
onCheckedChange={() =>
|
|
138
|
+
toggleRelation(relation.fieldName)
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
141
|
+
<span className="truncate" title={relation.fieldName}>
|
|
142
|
+
{relation.fieldName}
|
|
143
|
+
</span>
|
|
144
|
+
<span className="text-muted-foreground text-xs">
|
|
145
|
+
(1)
|
|
146
|
+
</span>
|
|
147
|
+
</label>
|
|
178
148
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<div className="
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
149
|
+
{/* Submenu for field expansion */}
|
|
150
|
+
{targetTable && (
|
|
151
|
+
<DropdownMenuSub>
|
|
152
|
+
<DropdownMenuSubTrigger className="h-7 px-2">
|
|
153
|
+
<Columns3 className="size-3" />
|
|
154
|
+
{selectedExpandedFields.length > 0 && (
|
|
155
|
+
<span className="text-muted-foreground ml-1 text-xs">
|
|
156
|
+
({selectedExpandedFields.length})
|
|
157
|
+
</span>
|
|
158
|
+
)}
|
|
159
|
+
</DropdownMenuSubTrigger>
|
|
160
|
+
<DropdownMenuSubContent className="w-56">
|
|
161
|
+
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
|
162
|
+
{relation.targetTable} のフィールドを列として展開
|
|
163
|
+
</DropdownMenuLabel>
|
|
164
|
+
<DropdownMenuSeparator />
|
|
165
|
+
<div className="max-h-60 overflow-auto p-1">
|
|
166
|
+
<div className="space-y-1">
|
|
167
|
+
{targetFields.map((field) => (
|
|
168
|
+
<label
|
|
169
|
+
key={field.name}
|
|
170
|
+
className="hover:bg-accent flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1 text-sm"
|
|
171
|
+
>
|
|
172
|
+
<Checkbox
|
|
173
|
+
checked={isExpandedRelationFieldSelected(
|
|
174
|
+
relation.fieldName,
|
|
175
|
+
field.name,
|
|
176
|
+
)}
|
|
177
|
+
onCheckedChange={() =>
|
|
178
|
+
toggleExpandedRelationField(
|
|
179
|
+
relation.fieldName,
|
|
180
|
+
field.name,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
className="size-3.5"
|
|
184
|
+
/>
|
|
185
|
+
<span
|
|
186
|
+
className="truncate"
|
|
187
|
+
title={field.description ?? field.name}
|
|
202
188
|
>
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
field.name,
|
|
208
|
-
) ?? false
|
|
209
|
-
}
|
|
210
|
-
onCheckedChange={() =>
|
|
211
|
-
onToggleExpandedRelationField(
|
|
212
|
-
relation.fieldName,
|
|
213
|
-
field.name,
|
|
214
|
-
)
|
|
215
|
-
}
|
|
216
|
-
className="size-3.5"
|
|
217
|
-
/>
|
|
218
|
-
<span
|
|
219
|
-
className="truncate"
|
|
220
|
-
title={field.description ?? field.name}
|
|
221
|
-
>
|
|
222
|
-
{field.name}
|
|
223
|
-
</span>
|
|
224
|
-
</label>
|
|
225
|
-
))}
|
|
226
|
-
</div>
|
|
189
|
+
{field.name}
|
|
190
|
+
</span>
|
|
191
|
+
</label>
|
|
192
|
+
))}
|
|
227
193
|
</div>
|
|
228
|
-
</
|
|
229
|
-
</
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
194
|
+
</div>
|
|
195
|
+
</DropdownMenuSubContent>
|
|
196
|
+
</DropdownMenuSub>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
})}
|
|
201
|
+
</div>
|
|
202
|
+
</>
|
|
203
|
+
)}
|
|
237
204
|
|
|
238
205
|
{/* OneToMany Relations (inline toggle only) */}
|
|
239
|
-
{oneToManyRelations.length > 0 &&
|
|
206
|
+
{oneToManyRelations.length > 0 && (
|
|
240
207
|
<>
|
|
241
208
|
<DropdownMenuSeparator />
|
|
242
209
|
<DropdownMenuLabel className="text-muted-foreground text-xs">
|
|
@@ -250,9 +217,7 @@ export function ColumnSelector({
|
|
|
250
217
|
>
|
|
251
218
|
<Checkbox
|
|
252
219
|
checked={selectedRelations.includes(relation.fieldName)}
|
|
253
|
-
onCheckedChange={() =>
|
|
254
|
-
onToggleRelation(relation.fieldName)
|
|
255
|
-
}
|
|
220
|
+
onCheckedChange={() => toggleRelation(relation.fieldName)}
|
|
256
221
|
/>
|
|
257
222
|
<span className="truncate" title={relation.fieldName}>
|
|
258
223
|
{relation.fieldName}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
type DataViewerInitialData,
|
|
4
|
+
type DataViewerProviderProps,
|
|
5
|
+
} from "./data-viewer-context";
|
|
6
|
+
import type { TableMetadataMap } from "../../generator/metadata-generator";
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Mock metadata with `as const` for type-safe tests
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
const mockMetadata = {
|
|
13
|
+
user: {
|
|
14
|
+
name: "user",
|
|
15
|
+
pluralForm: "users",
|
|
16
|
+
readAllowedRoles: ["admin"],
|
|
17
|
+
fields: [
|
|
18
|
+
{ name: "id", type: "uuid", required: true },
|
|
19
|
+
{ name: "name", type: "string", required: true },
|
|
20
|
+
{ name: "email", type: "string", required: true },
|
|
21
|
+
{
|
|
22
|
+
name: "role",
|
|
23
|
+
type: "enum",
|
|
24
|
+
required: true,
|
|
25
|
+
enumValues: ["admin", "member"],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
relations: [
|
|
29
|
+
{
|
|
30
|
+
fieldName: "posts",
|
|
31
|
+
targetTable: "post",
|
|
32
|
+
relationType: "oneToMany",
|
|
33
|
+
foreignKeyField: "authorId",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
post: {
|
|
38
|
+
name: "post",
|
|
39
|
+
pluralForm: "posts",
|
|
40
|
+
readAllowedRoles: ["admin", "member"],
|
|
41
|
+
fields: [
|
|
42
|
+
{ name: "id", type: "uuid", required: true },
|
|
43
|
+
{ name: "title", type: "string", required: true },
|
|
44
|
+
{ name: "content", type: "string", required: false },
|
|
45
|
+
{ name: "authorId", type: "uuid", required: true },
|
|
46
|
+
],
|
|
47
|
+
relations: [
|
|
48
|
+
{
|
|
49
|
+
fieldName: "author",
|
|
50
|
+
targetTable: "user",
|
|
51
|
+
relationType: "manyToOne",
|
|
52
|
+
foreignKeyField: "authorId",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
fieldName: "comments",
|
|
56
|
+
targetTable: "comment",
|
|
57
|
+
relationType: "oneToMany",
|
|
58
|
+
foreignKeyField: "postId",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
comment: {
|
|
63
|
+
name: "comment",
|
|
64
|
+
pluralForm: "comments",
|
|
65
|
+
readAllowedRoles: ["admin", "member"],
|
|
66
|
+
fields: [
|
|
67
|
+
{ name: "id", type: "uuid", required: true },
|
|
68
|
+
{ name: "body", type: "string", required: true },
|
|
69
|
+
{ name: "postId", type: "uuid", required: true },
|
|
70
|
+
],
|
|
71
|
+
relations: [
|
|
72
|
+
{
|
|
73
|
+
fieldName: "post",
|
|
74
|
+
targetTable: "post",
|
|
75
|
+
relationType: "manyToOne",
|
|
76
|
+
foreignKeyField: "postId",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
} as const;
|
|
81
|
+
|
|
82
|
+
type MockMetadata = typeof mockMetadata;
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// Type-level tests for type safety
|
|
86
|
+
// These tests verify that the types compile correctly.
|
|
87
|
+
// If the types are incorrect, TypeScript will fail to compile this file.
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
describe("DataViewerProvider type safety", () => {
|
|
91
|
+
describe("type-level tests (compile-time checks)", () => {
|
|
92
|
+
it("should correctly extract field names from metadata", () => {
|
|
93
|
+
// These are compile-time checks - if they compile, the types are correct
|
|
94
|
+
// TypeScript will error if invalid field names are used
|
|
95
|
+
|
|
96
|
+
// Valid user fields
|
|
97
|
+
const userInitialData: DataViewerInitialData<MockMetadata, "user"> = {
|
|
98
|
+
selectedFields: ["id", "name", "email", "role"],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Valid post fields
|
|
102
|
+
const postInitialData: DataViewerInitialData<MockMetadata, "post"> = {
|
|
103
|
+
selectedFields: ["id", "title", "content", "authorId"],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Valid comment fields
|
|
107
|
+
const commentInitialData: DataViewerInitialData<MockMetadata, "comment"> =
|
|
108
|
+
{
|
|
109
|
+
selectedFields: ["id", "body", "postId"],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
expect(userInitialData.selectedFields).toHaveLength(4);
|
|
113
|
+
expect(postInitialData.selectedFields).toHaveLength(4);
|
|
114
|
+
expect(commentInitialData.selectedFields).toHaveLength(3);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should correctly extract relation names from metadata", () => {
|
|
118
|
+
// User has "posts" relation
|
|
119
|
+
const userInitialData: DataViewerInitialData<MockMetadata, "user"> = {
|
|
120
|
+
selectedRelations: ["posts"],
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Post has "author" and "comments" relations
|
|
124
|
+
const postInitialData: DataViewerInitialData<MockMetadata, "post"> = {
|
|
125
|
+
selectedRelations: ["author", "comments"],
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
expect(userInitialData.selectedRelations).toEqual(["posts"]);
|
|
129
|
+
expect(postInitialData.selectedRelations).toEqual(["author", "comments"]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should correctly type tableName in props", () => {
|
|
133
|
+
// Valid table names
|
|
134
|
+
const userProps: DataViewerProviderProps<MockMetadata, "user"> = {
|
|
135
|
+
children: null,
|
|
136
|
+
tableName: "user",
|
|
137
|
+
metadata: mockMetadata,
|
|
138
|
+
appUri: "https://example.com",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const postProps: DataViewerProviderProps<MockMetadata, "post"> = {
|
|
142
|
+
children: null,
|
|
143
|
+
tableName: "post",
|
|
144
|
+
metadata: mockMetadata,
|
|
145
|
+
appUri: "https://example.com",
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
expect(userProps.tableName).toBe("user");
|
|
149
|
+
expect(postProps.tableName).toBe("post");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should allow valid field names in initialData", () => {
|
|
153
|
+
// This should compile without errors
|
|
154
|
+
const validInitialData: DataViewerInitialData<MockMetadata, "user"> = {
|
|
155
|
+
selectedFields: ["id", "name", "email"],
|
|
156
|
+
selectedRelations: ["posts"],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
expect(validInitialData.selectedFields).toEqual(["id", "name", "email"]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should allow valid expanded relation fields", () => {
|
|
163
|
+
// Post has relations: author (-> user) and comments (-> comment)
|
|
164
|
+
const validInitialData: DataViewerInitialData<MockMetadata, "post"> = {
|
|
165
|
+
selectedFields: ["title", "content"],
|
|
166
|
+
selectedRelations: ["author", "comments"],
|
|
167
|
+
expandedRelationFields: {
|
|
168
|
+
author: ["name", "email"], // user fields
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
expect(validInitialData.expandedRelationFields?.author).toEqual([
|
|
173
|
+
"name",
|
|
174
|
+
"email",
|
|
175
|
+
]);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should work with regular TableMetadataMap (non-const)", () => {
|
|
179
|
+
// When using regular TableMetadataMap (not `as const`), it should accept any string
|
|
180
|
+
type RegularInitialData = DataViewerInitialData<TableMetadataMap, string>;
|
|
181
|
+
|
|
182
|
+
// Should accept any string array
|
|
183
|
+
const initialData: RegularInitialData = {
|
|
184
|
+
selectedFields: ["anyField", "anotherField"],
|
|
185
|
+
selectedRelations: ["anyRelation"],
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
expect(initialData.selectedFields).toBeDefined();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|