mtrl-addons 0.1.2 → 0.2.1
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/build.js +139 -86
- package/package.json +13 -4
- package/scripts/debug/vlist-selection.ts +121 -0
- package/src/components/index.ts +5 -41
- package/src/components/{list → vlist}/config.ts +66 -95
- package/src/components/vlist/constants.ts +23 -0
- package/src/components/vlist/features/api.ts +322 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +444 -0
- package/src/components/vlist/features/viewport.ts +65 -0
- package/src/components/vlist/index.ts +16 -0
- package/src/components/{list → vlist}/types.ts +104 -26
- package/src/components/vlist/vlist.ts +92 -0
- package/src/core/compose/features/gestures/index.ts +227 -0
- package/src/core/compose/features/gestures/longpress.ts +383 -0
- package/src/core/compose/features/gestures/pan.ts +424 -0
- package/src/core/compose/features/gestures/pinch.ts +475 -0
- package/src/core/compose/features/gestures/rotate.ts +485 -0
- package/src/core/compose/features/gestures/swipe.ts +492 -0
- package/src/core/compose/features/gestures/tap.ts +334 -0
- package/src/core/compose/features/index.ts +2 -38
- package/src/core/compose/index.ts +13 -29
- package/src/core/gestures/index.ts +31 -0
- package/src/core/gestures/longpress.ts +68 -0
- package/src/core/gestures/manager.ts +418 -0
- package/src/core/gestures/pan.ts +48 -0
- package/src/core/gestures/pinch.ts +58 -0
- package/src/core/gestures/rotate.ts +58 -0
- package/src/core/gestures/swipe.ts +66 -0
- package/src/core/gestures/tap.ts +45 -0
- package/src/core/gestures/types.ts +387 -0
- package/src/core/gestures/utils.ts +128 -0
- package/src/core/index.ts +27 -151
- package/src/core/layout/schema.ts +73 -35
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +140 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +882 -0
- package/src/core/viewport/features/events.ts +130 -0
- package/src/core/viewport/features/index.ts +20 -0
- package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +27 -30
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +260 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +568 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +618 -0
- package/src/core/viewport/features/utils.ts +88 -0
- package/src/core/viewport/features/virtual.ts +384 -0
- package/src/core/viewport/index.ts +31 -0
- package/src/core/viewport/types.ts +133 -0
- package/src/core/viewport/utils/speed-tracker.ts +79 -0
- package/src/core/viewport/viewport.ts +246 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +331 -0
- package/src/styles/index.scss +1 -1
- package/test/components/vlist-selection.test.ts +240 -0
- package/test/components/vlist.test.ts +63 -0
- package/test/core/collection/adapter.test.ts +161 -0
- package/bun.lock +0 -792
- package/src/components/list/api.ts +0 -314
- package/src/components/list/constants.ts +0 -56
- package/src/components/list/features/api.ts +0 -428
- package/src/components/list/features/index.ts +0 -31
- package/src/components/list/features/list-manager.ts +0 -502
- package/src/components/list/index.ts +0 -39
- package/src/components/list/list.ts +0 -234
- package/src/core/collection/base-collection.ts +0 -100
- package/src/core/collection/collection-composer.ts +0 -178
- package/src/core/collection/collection.ts +0 -745
- package/src/core/collection/constants.ts +0 -172
- package/src/core/collection/events.ts +0 -428
- package/src/core/collection/features/api/loading.ts +0 -279
- package/src/core/collection/features/operations/data-operations.ts +0 -147
- package/src/core/collection/index.ts +0 -104
- package/src/core/collection/state.ts +0 -497
- package/src/core/collection/types.ts +0 -404
- package/src/core/compose/features/collection.ts +0 -119
- package/src/core/compose/features/selection.ts +0 -213
- package/src/core/compose/features/styling.ts +0 -108
- package/src/core/list-manager/api.ts +0 -599
- package/src/core/list-manager/config.ts +0 -593
- package/src/core/list-manager/constants.ts +0 -268
- package/src/core/list-manager/features/api.ts +0 -58
- package/src/core/list-manager/features/collection/collection.ts +0 -705
- package/src/core/list-manager/features/collection/index.ts +0 -17
- package/src/core/list-manager/features/viewport/constants.ts +0 -42
- package/src/core/list-manager/features/viewport/index.ts +0 -16
- package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
- package/src/core/list-manager/features/viewport/rendering.ts +0 -575
- package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
- package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
- package/src/core/list-manager/features/viewport/template.ts +0 -220
- package/src/core/list-manager/features/viewport/viewport.ts +0 -654
- package/src/core/list-manager/features/viewport/virtual.ts +0 -309
- package/src/core/list-manager/index.ts +0 -279
- package/src/core/list-manager/list-manager.ts +0 -206
- package/src/core/list-manager/types.ts +0 -439
- package/src/core/list-manager/utils/calculations.ts +0 -290
- package/src/core/list-manager/utils/range-calculator.ts +0 -349
- package/src/core/list-manager/utils/speed-tracker.ts +0 -273
- package/src/styles/components/_list.scss +0 -244
- package/src/types/mtrl.d.ts +0 -6
- package/test/components/list.test.ts +0 -256
- package/test/core/collection/failed-ranges.test.ts +0 -270
- package/test/core/compose/features.test.ts +0 -183
- package/test/core/list-manager/features/collection.test.ts +0 -704
- package/test/core/list-manager/features/viewport.test.ts +0 -698
- package/test/core/list-manager/list-manager.test.ts +0 -593
- package/test/core/list-manager/utils/calculations.test.ts +0 -433
- package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
- package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
- package/tsconfig.build.json +0 -23
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// test/components/vlist-selection.test.ts
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
4
|
+
import { createVList } from "../../src/components/vlist";
|
|
5
|
+
import type { VListComponent } from "../../src/components/vlist/types";
|
|
6
|
+
|
|
7
|
+
describe("VList Selection", () => {
|
|
8
|
+
let container: HTMLElement;
|
|
9
|
+
let vlist: VListComponent<any>;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Create container
|
|
13
|
+
container = document.createElement("div");
|
|
14
|
+
container.style.height = "400px";
|
|
15
|
+
container.style.width = "300px";
|
|
16
|
+
document.body.appendChild(container);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
// Cleanup
|
|
21
|
+
if (vlist?.destroy) {
|
|
22
|
+
vlist.destroy();
|
|
23
|
+
}
|
|
24
|
+
document.body.removeChild(container);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("should initialize without selection when not enabled", () => {
|
|
28
|
+
const items = Array.from({ length: 10 }, (_, i) => ({
|
|
29
|
+
id: `item-${i}`,
|
|
30
|
+
name: `Item ${i}`,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vlist = createVList({
|
|
34
|
+
container,
|
|
35
|
+
items,
|
|
36
|
+
template: (item) => {
|
|
37
|
+
const div = document.createElement("div");
|
|
38
|
+
div.className = "list-item";
|
|
39
|
+
div.textContent = item.name;
|
|
40
|
+
return div;
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Selection methods should not be available
|
|
45
|
+
expect(vlist.selectItems).toBeUndefined();
|
|
46
|
+
expect(vlist.getSelectedItems).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("should initialize with selection when enabled", () => {
|
|
50
|
+
const items = Array.from({ length: 10 }, (_, i) => ({
|
|
51
|
+
id: `item-${i}`,
|
|
52
|
+
name: `Item ${i}`,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
vlist = createVList({
|
|
56
|
+
container,
|
|
57
|
+
items,
|
|
58
|
+
selection: {
|
|
59
|
+
enabled: true,
|
|
60
|
+
mode: "single",
|
|
61
|
+
},
|
|
62
|
+
template: (item) => {
|
|
63
|
+
const div = document.createElement("div");
|
|
64
|
+
div.className = "list-item";
|
|
65
|
+
div.textContent = item.name;
|
|
66
|
+
return div;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Selection methods should be available
|
|
71
|
+
expect(vlist.selectItems).toBeDefined();
|
|
72
|
+
expect(vlist.getSelectedItems).toBeDefined();
|
|
73
|
+
expect(vlist.clearSelection).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("should handle single selection mode", () => {
|
|
77
|
+
const items = Array.from({ length: 10 }, (_, i) => ({
|
|
78
|
+
id: `item-${i}`,
|
|
79
|
+
name: `Item ${i}`,
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
vlist = createVList({
|
|
83
|
+
container,
|
|
84
|
+
items,
|
|
85
|
+
selection: {
|
|
86
|
+
enabled: true,
|
|
87
|
+
mode: "single",
|
|
88
|
+
},
|
|
89
|
+
template: (item) => {
|
|
90
|
+
const div = document.createElement("div");
|
|
91
|
+
div.className = "list-item";
|
|
92
|
+
div.textContent = item.name;
|
|
93
|
+
return div;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Select first item
|
|
98
|
+
vlist.selectItems([0]);
|
|
99
|
+
expect(vlist.getSelectedIndices()).toEqual([0]);
|
|
100
|
+
expect(vlist.getSelectedItems()).toEqual([items[0]]);
|
|
101
|
+
|
|
102
|
+
// Select another item - should replace selection
|
|
103
|
+
vlist.selectItems([2]);
|
|
104
|
+
expect(vlist.getSelectedIndices()).toEqual([2]);
|
|
105
|
+
expect(vlist.getSelectedItems()).toEqual([items[2]]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("should handle multiple selection mode", () => {
|
|
109
|
+
const items = Array.from({ length: 10 }, (_, i) => ({
|
|
110
|
+
id: `item-${i}`,
|
|
111
|
+
name: `Item ${i}`,
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
vlist = createVList({
|
|
115
|
+
container,
|
|
116
|
+
items,
|
|
117
|
+
selection: {
|
|
118
|
+
enabled: true,
|
|
119
|
+
mode: "multiple",
|
|
120
|
+
},
|
|
121
|
+
template: (item) => {
|
|
122
|
+
const div = document.createElement("div");
|
|
123
|
+
div.className = "list-item";
|
|
124
|
+
div.textContent = item.name;
|
|
125
|
+
return div;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Select multiple items
|
|
130
|
+
vlist.selectItems([0, 2, 4]);
|
|
131
|
+
expect(vlist.getSelectedIndices().sort()).toEqual([0, 2, 4]);
|
|
132
|
+
|
|
133
|
+
// Deselect one item
|
|
134
|
+
vlist.deselectItems([2]);
|
|
135
|
+
expect(vlist.getSelectedIndices().sort()).toEqual([0, 4]);
|
|
136
|
+
|
|
137
|
+
// Clear selection
|
|
138
|
+
vlist.clearSelection();
|
|
139
|
+
expect(vlist.getSelectedIndices()).toEqual([]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("should handle click selection", async () => {
|
|
143
|
+
const items = Array.from({ length: 10 }, (_, i) => ({
|
|
144
|
+
id: `item-${i}`,
|
|
145
|
+
name: `Item ${i}`,
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
vlist = createVList({
|
|
149
|
+
container,
|
|
150
|
+
items,
|
|
151
|
+
selection: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
mode: "single",
|
|
154
|
+
},
|
|
155
|
+
template: (item) => {
|
|
156
|
+
const div = document.createElement("div");
|
|
157
|
+
div.className = "list-item";
|
|
158
|
+
div.textContent = item.name;
|
|
159
|
+
return div;
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Wait for render
|
|
164
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
165
|
+
|
|
166
|
+
// Find first item element
|
|
167
|
+
const firstItem = container.querySelector(
|
|
168
|
+
'[data-index="0"]'
|
|
169
|
+
) as HTMLElement;
|
|
170
|
+
expect(firstItem).toBeTruthy();
|
|
171
|
+
|
|
172
|
+
// Click on first item
|
|
173
|
+
firstItem.click();
|
|
174
|
+
|
|
175
|
+
// Check selection
|
|
176
|
+
expect(vlist.getSelectedIndices()).toEqual([0]);
|
|
177
|
+
expect(firstItem.classList.contains("mtrl-list-item--selected")).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("should emit selection events", async () => {
|
|
181
|
+
const items = Array.from({ length: 10 }, (_, i) => ({
|
|
182
|
+
id: `item-${i}`,
|
|
183
|
+
name: `Item ${i}`,
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
let selectionChangeEvent: any = null;
|
|
187
|
+
|
|
188
|
+
vlist = createVList({
|
|
189
|
+
container,
|
|
190
|
+
items,
|
|
191
|
+
selection: {
|
|
192
|
+
enabled: true,
|
|
193
|
+
mode: "single",
|
|
194
|
+
onSelectionChange: (selectedItems, selectedIndices) => {
|
|
195
|
+
selectionChangeEvent = { selectedItems, selectedIndices };
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
template: (item) => {
|
|
199
|
+
const div = document.createElement("div");
|
|
200
|
+
div.className = "list-item";
|
|
201
|
+
div.textContent = item.name;
|
|
202
|
+
return div;
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Select an item
|
|
207
|
+
vlist.selectItems([3]);
|
|
208
|
+
|
|
209
|
+
// Check event was fired
|
|
210
|
+
expect(selectionChangeEvent).toBeTruthy();
|
|
211
|
+
expect(selectionChangeEvent.selectedIndices).toEqual([3]);
|
|
212
|
+
expect(selectionChangeEvent.selectedItems).toEqual([items[3]]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("should handle initial selection", () => {
|
|
216
|
+
const items = Array.from({ length: 10 }, (_, i) => ({
|
|
217
|
+
id: `item-${i}`,
|
|
218
|
+
name: `Item ${i}`,
|
|
219
|
+
}));
|
|
220
|
+
|
|
221
|
+
vlist = createVList({
|
|
222
|
+
container,
|
|
223
|
+
items,
|
|
224
|
+
selection: {
|
|
225
|
+
enabled: true,
|
|
226
|
+
mode: "multiple",
|
|
227
|
+
selectedIndices: [1, 3, 5],
|
|
228
|
+
},
|
|
229
|
+
template: (item) => {
|
|
230
|
+
const div = document.createElement("div");
|
|
231
|
+
div.className = "list-item";
|
|
232
|
+
div.textContent = item.name;
|
|
233
|
+
return div;
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Check initial selection
|
|
238
|
+
expect(vlist.getSelectedIndices().sort()).toEqual([1, 3, 5]);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// test/components/vlist.test.ts
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach } from "bun:test";
|
|
4
|
+
import { JSDOM } from "jsdom";
|
|
5
|
+
|
|
6
|
+
// Mock DOM environment for testing
|
|
7
|
+
const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");
|
|
8
|
+
global.document = dom.window.document;
|
|
9
|
+
global.HTMLElement = dom.window.HTMLElement;
|
|
10
|
+
global.window = dom.window as any;
|
|
11
|
+
global.navigator = dom.window.navigator;
|
|
12
|
+
global.requestAnimationFrame = (cb: FrameRequestCallback) => {
|
|
13
|
+
setTimeout(cb, 0);
|
|
14
|
+
return 0;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Import VList after DOM setup
|
|
18
|
+
import { createVList } from "../../src/components/vlist";
|
|
19
|
+
|
|
20
|
+
describe("VList Component", () => {
|
|
21
|
+
let container: HTMLElement;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
container = document.createElement("div");
|
|
25
|
+
document.body.appendChild(container);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should create a VList component", () => {
|
|
29
|
+
const vlist = createVList({
|
|
30
|
+
container,
|
|
31
|
+
items: ["Item 1", "Item 2", "Item 3"],
|
|
32
|
+
template: (item) => `<div>${item}</div>`,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(vlist).toBeDefined();
|
|
36
|
+
expect(vlist.element).toBeDefined();
|
|
37
|
+
expect(vlist.element.tagName).toBe("DIV");
|
|
38
|
+
expect(vlist.element.className).toContain("mtrl-vlist");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should have viewport functionality", () => {
|
|
42
|
+
const vlist = createVList({
|
|
43
|
+
container,
|
|
44
|
+
items: Array.from({ length: 100 }, (_, i) => `Item ${i}`),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(vlist.viewport).toBeDefined();
|
|
48
|
+
expect(typeof vlist.viewport.scrollToIndex).toBe("function");
|
|
49
|
+
expect(typeof vlist.viewport.getVisibleRange).toBe("function");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should have public API methods", () => {
|
|
53
|
+
const vlist = createVList({ container });
|
|
54
|
+
|
|
55
|
+
expect(typeof vlist.setItems).toBe("function");
|
|
56
|
+
expect(typeof vlist.getItems).toBe("function");
|
|
57
|
+
expect(typeof vlist.scrollToIndex).toBe("function");
|
|
58
|
+
expect(typeof vlist.scrollToTop).toBe("function");
|
|
59
|
+
expect(typeof vlist.scrollToBottom).toBe("function");
|
|
60
|
+
expect(typeof vlist.getState).toBe("function");
|
|
61
|
+
expect(typeof vlist.destroy).toBe("function");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// test/core/collection/adapter.test.ts
|
|
2
|
+
|
|
3
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
4
|
+
import {
|
|
5
|
+
createRouteAdapter,
|
|
6
|
+
withRouteAdapter,
|
|
7
|
+
} from "../../../src/core/collection/features/adapter";
|
|
8
|
+
import { createCollection } from "../../../src/core/collection";
|
|
9
|
+
|
|
10
|
+
describe("Route Adapter", () => {
|
|
11
|
+
let fetchMock: any;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Mock fetch
|
|
15
|
+
fetchMock = {
|
|
16
|
+
calls: [] as any[],
|
|
17
|
+
response: {
|
|
18
|
+
items: [
|
|
19
|
+
{ id: "1", name: "Item 1" },
|
|
20
|
+
{ id: "2", name: "Item 2" },
|
|
21
|
+
],
|
|
22
|
+
meta: {
|
|
23
|
+
total: 100,
|
|
24
|
+
hasNext: true,
|
|
25
|
+
cursor: "next-cursor",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
global.fetch = async (url: any, options: any) => {
|
|
31
|
+
fetchMock.calls.push({ url, options });
|
|
32
|
+
return {
|
|
33
|
+
ok: true,
|
|
34
|
+
headers: {
|
|
35
|
+
get: () => "application/json",
|
|
36
|
+
},
|
|
37
|
+
json: async () => fetchMock.response,
|
|
38
|
+
} as any;
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("createRouteAdapter", () => {
|
|
43
|
+
test("creates adapter with default config", () => {
|
|
44
|
+
const adapter = createRouteAdapter();
|
|
45
|
+
expect(adapter).toBeDefined();
|
|
46
|
+
expect(adapter.read).toBeDefined();
|
|
47
|
+
expect(adapter.disconnect).toBeDefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("makes API request with correct parameters", async () => {
|
|
51
|
+
const adapter = createRouteAdapter({
|
|
52
|
+
base: "/api",
|
|
53
|
+
endpoints: { list: "/users" },
|
|
54
|
+
headers: { Authorization: "Bearer token" },
|
|
55
|
+
pagination: { strategy: "offset" }, // Use offset strategy for this test
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const result = await adapter.read({
|
|
59
|
+
offset: 0,
|
|
60
|
+
limit: 20,
|
|
61
|
+
search: "john",
|
|
62
|
+
sort: "name",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(fetchMock.calls).toHaveLength(1);
|
|
66
|
+
const [call] = fetchMock.calls;
|
|
67
|
+
expect(call.url).toContain("/api/users");
|
|
68
|
+
expect(call.url).toContain("offset=0");
|
|
69
|
+
expect(call.url).toContain("limit=20");
|
|
70
|
+
expect(call.url).toContain("search=john");
|
|
71
|
+
expect(call.url).toContain("sort=name");
|
|
72
|
+
expect(call.options.headers["Authorization"]).toBe("Bearer token");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("handles different pagination strategies", async () => {
|
|
76
|
+
// Cursor pagination
|
|
77
|
+
const cursorAdapter = createRouteAdapter({
|
|
78
|
+
base: "/api",
|
|
79
|
+
endpoints: { list: "/items" },
|
|
80
|
+
pagination: { strategy: "cursor" },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await cursorAdapter.read({ cursor: "abc123", limit: 10 });
|
|
84
|
+
expect(fetchMock.calls[0].url).toContain("cursor=abc123");
|
|
85
|
+
|
|
86
|
+
// Page pagination
|
|
87
|
+
const pageAdapter = createRouteAdapter({
|
|
88
|
+
base: "/api",
|
|
89
|
+
endpoints: { list: "/items" },
|
|
90
|
+
pagination: { strategy: "page" },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await pageAdapter.read({ page: 3, limit: 10 });
|
|
94
|
+
expect(fetchMock.calls[1].url).toContain("page=3");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("transforms query operators", async () => {
|
|
98
|
+
const adapter = createRouteAdapter({
|
|
99
|
+
base: "/api",
|
|
100
|
+
endpoints: { list: "/items" },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await adapter.read({
|
|
104
|
+
filters: {
|
|
105
|
+
age: { GT: 18, LTE: 65 },
|
|
106
|
+
status: { EQ: "active" },
|
|
107
|
+
tags: { IN: ["premium", "verified"] },
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const url = fetchMock.calls[0].url;
|
|
112
|
+
expect(url).toContain("age_gt=18");
|
|
113
|
+
expect(url).toContain("age_lte=65");
|
|
114
|
+
expect(url).toContain("status_eq=active");
|
|
115
|
+
expect(url).toContain("tags_in=premium");
|
|
116
|
+
expect(url).toContain("tags_in=verified");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("handles caching", async () => {
|
|
120
|
+
const adapter = createRouteAdapter({
|
|
121
|
+
base: "/api",
|
|
122
|
+
endpoints: { list: "/items" },
|
|
123
|
+
cache: true,
|
|
124
|
+
pagination: { strategy: "offset" }, // Use offset strategy to test offset params
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// First call
|
|
128
|
+
await adapter.read({ offset: 0, limit: 10 });
|
|
129
|
+
expect(fetchMock.calls).toHaveLength(1);
|
|
130
|
+
|
|
131
|
+
// Second call with same params - should use cache
|
|
132
|
+
await adapter.read({ offset: 0, limit: 10 });
|
|
133
|
+
expect(fetchMock.calls).toHaveLength(1);
|
|
134
|
+
|
|
135
|
+
// Different params - should make new request
|
|
136
|
+
await adapter.read({ offset: 10, limit: 10 });
|
|
137
|
+
expect(fetchMock.calls).toHaveLength(2);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("withRouteAdapter", () => {
|
|
142
|
+
test("adds route adapter to collection", async () => {
|
|
143
|
+
const collection = createCollection({
|
|
144
|
+
pageSize: 20,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const enhancedCollection = withRouteAdapter({
|
|
148
|
+
base: "/api",
|
|
149
|
+
endpoints: { list: "/users" },
|
|
150
|
+
})(collection);
|
|
151
|
+
|
|
152
|
+
expect(enhancedCollection.disconnect).toBeDefined();
|
|
153
|
+
|
|
154
|
+
// Load data
|
|
155
|
+
await enhancedCollection.loadPage(1);
|
|
156
|
+
|
|
157
|
+
expect(fetchMock.calls).toHaveLength(1);
|
|
158
|
+
expect(fetchMock.calls[0].url).toContain("/api/users");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|