@izumisy-tailor/tailor-data-viewer 0.2.18 → 0.2.20
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/package.json +1 -1
- package/src/component/collection/collection-provider.tsx +1 -1
- package/src/component/column-selector.test.tsx +120 -0
- package/src/component/column-selector.tsx +30 -32
- package/src/component/components.test.tsx +88 -36
- package/src/component/csv-button.test.tsx +122 -0
- package/src/component/csv-button.tsx +8 -7
- package/src/component/data-table/data-table-context.tsx +22 -9
- package/src/component/data-table/{index.tsx → data-table.tsx} +141 -117
- package/src/component/data-table/use-data-table.test.ts +6 -29
- package/src/component/data-table/use-data-table.ts +25 -56
- package/src/component/index.ts +1 -8
- package/src/component/pagination.test.tsx +129 -0
- package/src/component/pagination.tsx +12 -10
- package/src/component/search-filter-form.test.tsx +222 -0
- package/src/component/search-filter-form.tsx +20 -15
- package/src/component/types.ts +8 -53
- package/src/tests/helpers.tsx +131 -0
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useDataTableContext } from "./data-table/data-table-context";
|
|
2
|
+
import { useCollectionContext } from "./collection/collection-provider";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Pagination controls for cursor-based navigation.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* Reads `pageInfo` from `DataTableContext` and pagination actions
|
|
8
|
+
* from `CollectionContext`. Must be rendered inside `DataTable.Provider`.
|
|
7
9
|
*
|
|
8
10
|
* @example
|
|
9
11
|
* ```tsx
|
|
10
|
-
* <
|
|
12
|
+
* <DataTable.Provider value={table}>
|
|
13
|
+
* <Pagination />
|
|
14
|
+
* </DataTable.Provider>
|
|
11
15
|
* ```
|
|
12
16
|
*/
|
|
13
|
-
export function Pagination({
|
|
14
|
-
pageInfo
|
|
15
|
-
nextPage,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
hasNextPage,
|
|
19
|
-
}: PaginationProps) {
|
|
17
|
+
export function Pagination() {
|
|
18
|
+
const { pageInfo } = useDataTableContext();
|
|
19
|
+
const { nextPage, prevPage, hasPrevPage, hasNextPage } =
|
|
20
|
+
useCollectionContext();
|
|
21
|
+
|
|
20
22
|
return (
|
|
21
23
|
<div className="flex items-center justify-end gap-2 py-2">
|
|
22
24
|
<button
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import type { Column, Filter } from "./types";
|
|
5
|
+
import { createTestProviders } from "../tests/helpers";
|
|
6
|
+
import { SearchFilterForm } from "./search-filter-form";
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Fixtures
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
type TestRow = { id: string; name: string; status: string; priority: number };
|
|
13
|
+
|
|
14
|
+
const testColumns: Column<TestRow>[] = [
|
|
15
|
+
{
|
|
16
|
+
kind: "field",
|
|
17
|
+
dataKey: "name",
|
|
18
|
+
label: "Name",
|
|
19
|
+
sort: { type: "string" },
|
|
20
|
+
filter: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
kind: "field",
|
|
24
|
+
dataKey: "status",
|
|
25
|
+
label: "Status",
|
|
26
|
+
filter: {
|
|
27
|
+
type: "enum",
|
|
28
|
+
options: [
|
|
29
|
+
{ value: "active", label: "Active" },
|
|
30
|
+
{ value: "inactive", label: "Inactive" },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
kind: "field",
|
|
36
|
+
dataKey: "priority",
|
|
37
|
+
label: "Priority",
|
|
38
|
+
sort: { type: "number" },
|
|
39
|
+
filter: { type: "number" },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
kind: "display",
|
|
43
|
+
id: "actions",
|
|
44
|
+
label: "Actions",
|
|
45
|
+
render: (row) => <button>Edit {row.name}</button>,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const testRows: TestRow[] = [
|
|
50
|
+
{ id: "1", name: "Alice", status: "active", priority: 1 },
|
|
51
|
+
{ id: "2", name: "Bob", status: "inactive", priority: 2 },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const TestProviders = createTestProviders({
|
|
55
|
+
columns: testColumns,
|
|
56
|
+
rows: testRows,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Tests
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
describe("SearchFilterForm", () => {
|
|
64
|
+
it("renders the search trigger button", () => {
|
|
65
|
+
render(
|
|
66
|
+
<TestProviders>
|
|
67
|
+
<SearchFilterForm />
|
|
68
|
+
</TestProviders>,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(screen.getByText("Search")).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("shows active filter count badge", () => {
|
|
75
|
+
const filters: Filter[] = [
|
|
76
|
+
{ field: "name", operator: "eq", value: "Alice" },
|
|
77
|
+
];
|
|
78
|
+
render(
|
|
79
|
+
<TestProviders collection={{ filters }}>
|
|
80
|
+
<SearchFilterForm />
|
|
81
|
+
</TestProviders>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(screen.getByText("1")).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("renders custom trigger when provided", () => {
|
|
88
|
+
render(
|
|
89
|
+
<TestProviders>
|
|
90
|
+
<SearchFilterForm trigger={<span>Custom Trigger</span>} />
|
|
91
|
+
</TestProviders>,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(screen.getByText("Custom Trigger")).toBeInTheDocument();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("shows filterable field options in the dropdown", () => {
|
|
98
|
+
render(
|
|
99
|
+
<TestProviders>
|
|
100
|
+
<SearchFilterForm />
|
|
101
|
+
</TestProviders>,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(screen.getByText("Select field...")).toBeInTheDocument();
|
|
105
|
+
const options = screen.getAllByRole("option");
|
|
106
|
+
const optionTexts = options.map((o) => o.textContent);
|
|
107
|
+
expect(optionTexts).toContain("Name");
|
|
108
|
+
expect(optionTexts).toContain("Status");
|
|
109
|
+
expect(optionTexts).toContain("Priority");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("displays active filters with remove buttons", () => {
|
|
113
|
+
const filters: Filter[] = [
|
|
114
|
+
{ field: "name", operator: "eq", value: "Alice" },
|
|
115
|
+
];
|
|
116
|
+
const removeFilter = vi.fn();
|
|
117
|
+
render(
|
|
118
|
+
<TestProviders collection={{ filters, removeFilter }}>
|
|
119
|
+
<SearchFilterForm />
|
|
120
|
+
</TestProviders>,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(screen.getByText("Active filters")).toBeInTheDocument();
|
|
124
|
+
expect(screen.getByText("Name = Alice")).toBeInTheDocument();
|
|
125
|
+
|
|
126
|
+
fireEvent.click(screen.getByText("✕"));
|
|
127
|
+
expect(removeFilter).toHaveBeenCalledWith("name");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("calls clearFilters when Clear all is clicked", () => {
|
|
131
|
+
const filters: Filter[] = [
|
|
132
|
+
{ field: "name", operator: "eq", value: "test" },
|
|
133
|
+
];
|
|
134
|
+
const clearFilters = vi.fn();
|
|
135
|
+
render(
|
|
136
|
+
<TestProviders collection={{ filters, clearFilters }}>
|
|
137
|
+
<SearchFilterForm />
|
|
138
|
+
</TestProviders>,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
fireEvent.click(screen.getByText("Clear all"));
|
|
142
|
+
expect(clearFilters).toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("calls addFilter when a filter is submitted", async () => {
|
|
146
|
+
const user = userEvent.setup();
|
|
147
|
+
const addFilter = vi.fn();
|
|
148
|
+
render(
|
|
149
|
+
<TestProviders collection={{ addFilter }}>
|
|
150
|
+
<SearchFilterForm />
|
|
151
|
+
</TestProviders>,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Select field
|
|
155
|
+
await user.selectOptions(
|
|
156
|
+
screen.getByDisplayValue("Select field..."),
|
|
157
|
+
"name",
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Type value
|
|
161
|
+
await user.type(screen.getByPlaceholderText("Enter value..."), "Alice");
|
|
162
|
+
|
|
163
|
+
// Click Add
|
|
164
|
+
await user.click(screen.getByText("Add"));
|
|
165
|
+
|
|
166
|
+
expect(addFilter).toHaveBeenCalledWith("name", "eq", "Alice");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("shows 'No filterable fields' when no columns have filter config", () => {
|
|
170
|
+
const columnsWithoutFilter: Column<TestRow>[] = [
|
|
171
|
+
{ kind: "field", dataKey: "name", label: "Name" },
|
|
172
|
+
];
|
|
173
|
+
render(
|
|
174
|
+
<TestProviders dataTable={{ columns: columnsWithoutFilter }}>
|
|
175
|
+
<SearchFilterForm />
|
|
176
|
+
</TestProviders>,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(screen.getByText("No filterable fields")).toBeInTheDocument();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("shows 'All fields have filters applied' when every field is filtered", () => {
|
|
183
|
+
const filters: Filter[] = [
|
|
184
|
+
{ field: "name", operator: "eq", value: "a" },
|
|
185
|
+
{ field: "status", operator: "eq", value: "active" },
|
|
186
|
+
{ field: "priority", operator: "eq", value: "1" },
|
|
187
|
+
];
|
|
188
|
+
render(
|
|
189
|
+
<TestProviders collection={{ filters }}>
|
|
190
|
+
<SearchFilterForm />
|
|
191
|
+
</TestProviders>,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(
|
|
195
|
+
screen.getByText("All fields have filters applied"),
|
|
196
|
+
).toBeInTheDocument();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("supports custom labels", () => {
|
|
200
|
+
render(
|
|
201
|
+
<TestProviders>
|
|
202
|
+
<SearchFilterForm
|
|
203
|
+
labels={{
|
|
204
|
+
search: "検索",
|
|
205
|
+
searchFilter: "検索フィルタ",
|
|
206
|
+
selectField: "フィールド選択...",
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
209
|
+
</TestProviders>,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
expect(screen.getByText("検索")).toBeInTheDocument();
|
|
213
|
+
expect(screen.getByText("検索フィルタ")).toBeInTheDocument();
|
|
214
|
+
expect(screen.getByText("フィールド選択...")).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("throws when rendered outside provider", () => {
|
|
218
|
+
console.error = vi.fn();
|
|
219
|
+
expect(() => render(<SearchFilterForm />)).toThrow();
|
|
220
|
+
console.error = globalThis.console.error;
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -4,38 +4,43 @@ import {
|
|
|
4
4
|
useEffect,
|
|
5
5
|
useRef,
|
|
6
6
|
type KeyboardEvent,
|
|
7
|
+
type ReactNode,
|
|
7
8
|
} from "react";
|
|
8
|
-
import type {
|
|
9
|
-
FilterOperator,
|
|
10
|
-
SearchFilterFormProps,
|
|
11
|
-
FilterConfig,
|
|
12
|
-
} from "./types";
|
|
9
|
+
import type { FilterOperator, FilterConfig, SearchFilterLabels } from "./types";
|
|
13
10
|
import { OPERATORS_BY_FILTER_TYPE, DEFAULT_OPERATOR_LABELS } from "./types";
|
|
11
|
+
import { useDataTableContext } from "./data-table/data-table-context";
|
|
12
|
+
import { useCollectionContext } from "./collection/collection-provider";
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Composite search filter form.
|
|
17
16
|
*
|
|
18
17
|
* Renders a dropdown panel with type-specific filter inputs, operator
|
|
19
|
-
* selectors, and active filter badges.
|
|
20
|
-
*
|
|
18
|
+
* selectors, and active filter badges. Reads `columns` from
|
|
19
|
+
* `DataTableContext` and filter state from `CollectionContext`.
|
|
20
|
+
* Must be rendered inside `DataTable.Provider`.
|
|
21
21
|
*
|
|
22
22
|
* All text is customisable through the optional `labels` prop (defaults
|
|
23
23
|
* to English).
|
|
24
24
|
*
|
|
25
25
|
* @example
|
|
26
26
|
* ```tsx
|
|
27
|
-
* <
|
|
27
|
+
* <DataTable.Provider value={table}>
|
|
28
|
+
* <SearchFilterForm />
|
|
29
|
+
* </DataTable.Provider>
|
|
28
30
|
* ```
|
|
29
31
|
*/
|
|
30
|
-
export function SearchFilterForm
|
|
31
|
-
columns,
|
|
32
|
-
filters,
|
|
33
|
-
addFilter,
|
|
34
|
-
removeFilter,
|
|
35
|
-
clearFilters,
|
|
32
|
+
export function SearchFilterForm({
|
|
36
33
|
labels,
|
|
37
34
|
trigger,
|
|
38
|
-
}:
|
|
35
|
+
}: {
|
|
36
|
+
/** Localizable labels (defaults to English) */
|
|
37
|
+
labels?: SearchFilterLabels;
|
|
38
|
+
/** Custom trigger element. When provided, replaces the default trigger button content. */
|
|
39
|
+
trigger?: ReactNode;
|
|
40
|
+
} = {}) {
|
|
41
|
+
const { columns } = useDataTableContext();
|
|
42
|
+
const { filters, addFilter, removeFilter, clearFilters } =
|
|
43
|
+
useCollectionContext();
|
|
39
44
|
const filterableColumns = columns.filter(
|
|
40
45
|
(col) => col.kind === "field" && col.filter,
|
|
41
46
|
);
|
package/src/component/types.ts
CHANGED
|
@@ -511,63 +511,10 @@ export interface RowOperations<TRow extends Record<string, unknown>> {
|
|
|
511
511
|
insertRow: (row: TRow) => { rollback: () => void };
|
|
512
512
|
}
|
|
513
513
|
|
|
514
|
-
/**
|
|
515
|
-
* Props for `DataTable.Root` component (generated by `useDataTable`).
|
|
516
|
-
*/
|
|
517
|
-
export interface DataTableRootProps<TRow extends Record<string, unknown>> {
|
|
518
|
-
/** Visible column definitions */
|
|
519
|
-
columns: Column<TRow>[];
|
|
520
|
-
/** Row data */
|
|
521
|
-
rows: TRow[];
|
|
522
|
-
/** Loading state */
|
|
523
|
-
loading?: boolean;
|
|
524
|
-
/** Error */
|
|
525
|
-
error?: Error | null;
|
|
526
|
-
/** Sort handler (connected to collection) */
|
|
527
|
-
onSort?: (field: string, direction?: "Asc" | "Desc") => void;
|
|
528
|
-
/** Current sort states */
|
|
529
|
-
sortStates?: SortState[];
|
|
530
|
-
/** Row operations (provided to children via Context) */
|
|
531
|
-
rowOperations?: RowOperations<TRow>;
|
|
532
|
-
children: ReactNode;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Props for `DataTable.Row` component.
|
|
537
|
-
*/
|
|
538
|
-
export interface DataTableRowProps<TRow extends Record<string, unknown>> {
|
|
539
|
-
/** The row data */
|
|
540
|
-
row: TRow;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Props for `DataTable.Cell` component.
|
|
545
|
-
*/
|
|
546
|
-
export interface DataTableCellProps<TRow extends Record<string, unknown>> {
|
|
547
|
-
/** The row data */
|
|
548
|
-
row: TRow;
|
|
549
|
-
/** The column definition */
|
|
550
|
-
column: Column<TRow>;
|
|
551
|
-
/** The row index */
|
|
552
|
-
rowIndex: number;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
514
|
/**
|
|
556
515
|
* Return type of `useDataTable` hook.
|
|
557
516
|
*/
|
|
558
517
|
export interface UseDataTableReturn<TRow extends Record<string, unknown>> {
|
|
559
|
-
// Props generators (for spreading)
|
|
560
|
-
/** Props for DataTable.Root */
|
|
561
|
-
rootProps: DataTableRootProps<TRow>;
|
|
562
|
-
/** Get props for a DataTable.Row */
|
|
563
|
-
getRowProps: (row: TRow) => DataTableRowProps<TRow>;
|
|
564
|
-
/** Get props for a DataTable.Cell */
|
|
565
|
-
getCellProps: (
|
|
566
|
-
row: TRow,
|
|
567
|
-
column: Column<TRow>,
|
|
568
|
-
rowIndex: number,
|
|
569
|
-
) => DataTableCellProps<TRow>;
|
|
570
|
-
|
|
571
518
|
// Data
|
|
572
519
|
/** Row data extracted from collection result */
|
|
573
520
|
rows: TRow[];
|
|
@@ -575,6 +522,10 @@ export interface UseDataTableReturn<TRow extends Record<string, unknown>> {
|
|
|
575
522
|
loading: boolean;
|
|
576
523
|
/** Error */
|
|
577
524
|
error: Error | null;
|
|
525
|
+
/** Current sort states */
|
|
526
|
+
sortStates: SortState[];
|
|
527
|
+
/** Sort handler (connected to collection) */
|
|
528
|
+
onSort?: (field: string, direction?: "Asc" | "Desc") => void;
|
|
578
529
|
|
|
579
530
|
// Pagination (delegated from collection)
|
|
580
531
|
/** Page info from GraphQL response */
|
|
@@ -609,6 +560,10 @@ export interface UseDataTableReturn<TRow extends Record<string, unknown>> {
|
|
|
609
560
|
deleteRow: (rowId: string) => { rollback: () => void; deletedRow: TRow };
|
|
610
561
|
/** Optimistically insert a row */
|
|
611
562
|
insertRow: (row: TRow) => { rollback: () => void };
|
|
563
|
+
|
|
564
|
+
// Collection (passthrough for DataTable.Provider)
|
|
565
|
+
/** Collection state passed through from options */
|
|
566
|
+
collection: UseCollectionReturn<string, unknown> | undefined;
|
|
612
567
|
}
|
|
613
568
|
|
|
614
569
|
// =============================================================================
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
import type { Column, UseCollectionReturn } from "../component/types";
|
|
4
|
+
import { DataTableContext } from "../component/data-table/data-table-context";
|
|
5
|
+
import type { DataTableContextValue } from "../component/data-table/data-table-context";
|
|
6
|
+
import { CollectionProvider } from "../component/collection/collection-provider";
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Mock factory: DataTableContext
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
export function createMockDataTableContext<T extends Record<string, unknown>>(
|
|
13
|
+
defaults: { columns: Column<T>[]; rows: T[] },
|
|
14
|
+
overrides?: Partial<DataTableContextValue<T>>,
|
|
15
|
+
): DataTableContextValue<T> {
|
|
16
|
+
return {
|
|
17
|
+
columns: defaults.columns,
|
|
18
|
+
rows: defaults.rows,
|
|
19
|
+
loading: false,
|
|
20
|
+
error: null,
|
|
21
|
+
sortStates: [],
|
|
22
|
+
onSort: vi.fn(),
|
|
23
|
+
updateRow: vi.fn(() => ({ rollback: vi.fn() })),
|
|
24
|
+
deleteRow: vi.fn(() => ({
|
|
25
|
+
rollback: vi.fn(),
|
|
26
|
+
deletedRow: defaults.rows[0],
|
|
27
|
+
})),
|
|
28
|
+
insertRow: vi.fn(() => ({ rollback: vi.fn() })),
|
|
29
|
+
visibleColumns: defaults.columns,
|
|
30
|
+
isColumnVisible: vi.fn(() => true),
|
|
31
|
+
toggleColumn: vi.fn(),
|
|
32
|
+
showAllColumns: vi.fn(),
|
|
33
|
+
hideAllColumns: vi.fn(),
|
|
34
|
+
pageInfo: {
|
|
35
|
+
hasNextPage: false,
|
|
36
|
+
endCursor: null,
|
|
37
|
+
hasPreviousPage: false,
|
|
38
|
+
startCursor: null,
|
|
39
|
+
},
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// =============================================================================
|
|
45
|
+
// Mock factory: CollectionContext
|
|
46
|
+
// =============================================================================
|
|
47
|
+
|
|
48
|
+
export function createMockCollectionContext(
|
|
49
|
+
overrides?: Partial<UseCollectionReturn<string, unknown>>,
|
|
50
|
+
): UseCollectionReturn<string, unknown> {
|
|
51
|
+
return {
|
|
52
|
+
toQueryArgs: vi.fn(() => ({ query: null, variables: {} })),
|
|
53
|
+
filters: [],
|
|
54
|
+
addFilter: vi.fn(),
|
|
55
|
+
setFilters: vi.fn(),
|
|
56
|
+
removeFilter: vi.fn(),
|
|
57
|
+
clearFilters: vi.fn(),
|
|
58
|
+
sortStates: [],
|
|
59
|
+
setSort: vi.fn(),
|
|
60
|
+
clearSort: vi.fn(),
|
|
61
|
+
pageSize: 20,
|
|
62
|
+
cursor: null,
|
|
63
|
+
paginationDirection: "forward",
|
|
64
|
+
nextPage: vi.fn(),
|
|
65
|
+
prevPage: vi.fn(),
|
|
66
|
+
resetPage: vi.fn(),
|
|
67
|
+
hasPrevPage: false,
|
|
68
|
+
hasNextPage: false,
|
|
69
|
+
setPageInfo: vi.fn(),
|
|
70
|
+
...overrides,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// TestProviders factory
|
|
76
|
+
// =============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates a `TestProviders` wrapper component bound to specific fixture data.
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* const TestProviders = createTestProviders({
|
|
83
|
+
* columns: testColumns,
|
|
84
|
+
* rows: testRows,
|
|
85
|
+
* dataTableDefaults: { pageInfo: { hasNextPage: true, ... } },
|
|
86
|
+
* collectionDefaults: { hasPrevPage: true },
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* render(
|
|
90
|
+
* <TestProviders dataTable={{ loading: true }}>
|
|
91
|
+
* <MyComponent />
|
|
92
|
+
* </TestProviders>,
|
|
93
|
+
* );
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function createTestProviders<
|
|
97
|
+
T extends Record<string, unknown>,
|
|
98
|
+
>(defaults: {
|
|
99
|
+
columns: Column<T>[];
|
|
100
|
+
rows: T[];
|
|
101
|
+
dataTableDefaults?: Partial<DataTableContextValue<T>>;
|
|
102
|
+
collectionDefaults?: Partial<UseCollectionReturn<string, unknown>>;
|
|
103
|
+
}) {
|
|
104
|
+
return function TestProviders({
|
|
105
|
+
children,
|
|
106
|
+
dataTable,
|
|
107
|
+
collection,
|
|
108
|
+
}: {
|
|
109
|
+
children: ReactNode;
|
|
110
|
+
dataTable?: Partial<DataTableContextValue<T>>;
|
|
111
|
+
collection?: Partial<UseCollectionReturn<string, unknown>>;
|
|
112
|
+
}) {
|
|
113
|
+
return (
|
|
114
|
+
<CollectionProvider
|
|
115
|
+
value={createMockCollectionContext({
|
|
116
|
+
...defaults.collectionDefaults,
|
|
117
|
+
...collection,
|
|
118
|
+
})}
|
|
119
|
+
>
|
|
120
|
+
<DataTableContext.Provider
|
|
121
|
+
value={createMockDataTableContext(defaults, {
|
|
122
|
+
...defaults.dataTableDefaults,
|
|
123
|
+
...dataTable,
|
|
124
|
+
})}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</DataTableContext.Provider>
|
|
128
|
+
</CollectionProvider>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
}
|