@signalium/query 0.1.0 → 1.0.0
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/CHANGELOG.md +15 -0
- package/dist/cjs/EntityMap.js +2 -2
- package/dist/cjs/EntityMap.js.map +1 -1
- package/dist/cjs/NetworkManager.js +105 -0
- package/dist/cjs/NetworkManager.js.map +1 -0
- package/dist/cjs/QueryClient.js +390 -76
- package/dist/cjs/QueryClient.js.map +1 -1
- package/dist/cjs/QueryStore.js +295 -3
- package/dist/cjs/QueryStore.js.map +1 -1
- package/dist/cjs/index.js +16 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/parseEntities.js +3 -0
- package/dist/cjs/parseEntities.js.map +1 -1
- package/dist/cjs/proxy.js +19 -0
- package/dist/cjs/proxy.js.map +1 -1
- package/dist/cjs/query.js +40 -2
- package/dist/cjs/query.js.map +1 -1
- package/dist/cjs/stores/async.js +6 -0
- package/dist/cjs/stores/async.js.map +1 -0
- package/dist/cjs/stores/sync.js +7 -0
- package/dist/cjs/stores/sync.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/cjs/type-utils.js +3 -0
- package/dist/cjs/type-utils.js.map +1 -0
- package/dist/cjs/types.js +19 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/EntityMap.js +3 -3
- package/dist/esm/EntityMap.js.map +1 -1
- package/dist/esm/NetworkManager.d.ts +48 -0
- package/dist/esm/NetworkManager.d.ts.map +1 -0
- package/dist/esm/NetworkManager.js +101 -0
- package/dist/esm/NetworkManager.js.map +1 -0
- package/dist/esm/QueryClient.d.ts +81 -25
- package/dist/esm/QueryClient.d.ts.map +1 -1
- package/dist/esm/QueryClient.js +390 -76
- package/dist/esm/QueryClient.js.map +1 -1
- package/dist/esm/QueryStore.d.ts +64 -2
- package/dist/esm/QueryStore.d.ts.map +1 -1
- package/dist/esm/QueryStore.js +293 -2
- package/dist/esm/QueryStore.js.map +1 -1
- package/dist/esm/index.d.ts +5 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/parseEntities.d.ts.map +1 -1
- package/dist/esm/parseEntities.js +3 -0
- package/dist/esm/parseEntities.js.map +1 -1
- package/dist/esm/proxy.d.ts +6 -0
- package/dist/esm/proxy.d.ts.map +1 -1
- package/dist/esm/proxy.js +18 -0
- package/dist/esm/proxy.js.map +1 -1
- package/dist/esm/query.d.ts +30 -29
- package/dist/esm/query.d.ts.map +1 -1
- package/dist/esm/query.js +39 -3
- package/dist/esm/query.js.map +1 -1
- package/dist/esm/stores/async.d.ts +2 -0
- package/dist/esm/stores/async.d.ts.map +1 -0
- package/dist/esm/stores/async.js +2 -0
- package/dist/esm/stores/async.js.map +1 -0
- package/dist/esm/stores/sync.d.ts +2 -0
- package/dist/esm/stores/sync.d.ts.map +1 -0
- package/dist/esm/stores/sync.js +2 -0
- package/dist/esm/stores/sync.js.map +1 -0
- package/dist/esm/type-utils.d.ts +12 -0
- package/dist/esm/type-utils.d.ts.map +1 -0
- package/dist/esm/type-utils.js +2 -0
- package/dist/esm/type-utils.js.map +1 -0
- package/dist/esm/types.d.ts +62 -5
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +18 -0
- package/dist/esm/types.js.map +1 -1
- package/index.d.ts +1 -0
- package/package.json +25 -4
- package/stores/async.d.ts +1 -0
- package/stores/async.js +15 -0
- package/stores/sync.d.ts +1 -0
- package/stores/sync.js +15 -0
- package/.turbo/turbo-build.log +0 -12
- package/ENTITY_STORE_DESIGN.md +0 -386
- package/dist/tsconfig.esm.tsbuildinfo +0 -1
- package/src/EntityMap.ts +0 -63
- package/src/QueryClient.ts +0 -482
- package/src/QueryStore.ts +0 -322
- package/src/__tests__/caching-persistence.test.ts +0 -983
- package/src/__tests__/entity-system.test.ts +0 -556
- package/src/__tests__/gc-time.test.ts +0 -327
- package/src/__tests__/mock-fetch.test.ts +0 -186
- package/src/__tests__/parse-entities.test.ts +0 -425
- package/src/__tests__/path-interpolation.test.ts +0 -225
- package/src/__tests__/reactivity.test.ts +0 -424
- package/src/__tests__/refetch-interval.test.ts +0 -262
- package/src/__tests__/rest-query-api.test.ts +0 -568
- package/src/__tests__/stale-time.test.ts +0 -357
- package/src/__tests__/type-to-string.test.ts +0 -129
- package/src/__tests__/utils.ts +0 -258
- package/src/__tests__/validation-edge-cases.test.ts +0 -821
- package/src/errors.ts +0 -124
- package/src/index.ts +0 -7
- package/src/parseEntities.ts +0 -213
- package/src/pathInterpolator.ts +0 -74
- package/src/proxy.ts +0 -257
- package/src/query.ts +0 -164
- package/src/react/__tests__/basic.test.tsx +0 -926
- package/src/react/__tests__/component.test.tsx +0 -984
- package/src/react/__tests__/utils.tsx +0 -71
- package/src/typeDefs.ts +0 -351
- package/src/types.ts +0 -132
- package/src/utils.ts +0 -66
- package/tsconfig.cjs.json +0 -14
- package/tsconfig.esm.json +0 -13
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -65
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
|
2
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
3
|
-
import { SyncQueryStore, MemoryPersistentStore, valueKeyFor } from '../QueryStore.js';
|
|
4
|
-
import { QueryClient } from '../QueryClient.js';
|
|
5
|
-
import { query } from '../query.js';
|
|
6
|
-
import { t, entity } from '../typeDefs.js';
|
|
7
|
-
import { createMockFetch, testWithClient, sleep } from './utils.js';
|
|
8
|
-
import { hashValue } from 'signalium/utils';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* GC Time Tests
|
|
12
|
-
*
|
|
13
|
-
* Tests gcTime-based garbage collection with sorted queue management,
|
|
14
|
-
* LRU interaction, and subscriber-aware eviction
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
describe('GC Time', () => {
|
|
18
|
-
let client: QueryClient;
|
|
19
|
-
let mockFetch: ReturnType<typeof createMockFetch>;
|
|
20
|
-
let kv: any;
|
|
21
|
-
let store: any;
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
kv = new MemoryPersistentStore();
|
|
25
|
-
store = new SyncQueryStore(kv);
|
|
26
|
-
mockFetch = createMockFetch();
|
|
27
|
-
client = new QueryClient(store, { fetch: mockFetch as any, evictionMultiplier: 0.001 });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
client?.destroy();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('Basic GC', () => {
|
|
35
|
-
it('should evict queries from disk after gcTime expires', async () => {
|
|
36
|
-
const getItem = query(t => ({
|
|
37
|
-
path: '/item/[id]',
|
|
38
|
-
response: { id: t.number, name: t.string },
|
|
39
|
-
cache: { gcTime: 100, staleTime: 50 }, // 1 second
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
mockFetch.get('/item/1', { id: 1, name: 'Item 1' });
|
|
43
|
-
|
|
44
|
-
await testWithClient(client, async () => {
|
|
45
|
-
const relay = getItem({ id: '1' });
|
|
46
|
-
expect(relay.value).toEqual(undefined);
|
|
47
|
-
await relay;
|
|
48
|
-
expect(relay.value).toEqual({ id: 1, name: 'Item 1' });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
await sleep(75);
|
|
52
|
-
|
|
53
|
-
mockFetch.get('/item/1', { id: 1, name: 'Item 1 updated' }, { delay: 50 });
|
|
54
|
-
await testWithClient(client, async () => {
|
|
55
|
-
const relay = getItem({ id: '1' });
|
|
56
|
-
expect(relay.value).toEqual({ id: 1, name: 'Item 1' });
|
|
57
|
-
await relay;
|
|
58
|
-
expect(relay.value).toEqual({ id: 1, name: 'Item 1' });
|
|
59
|
-
|
|
60
|
-
await sleep(60);
|
|
61
|
-
expect(relay.value).toEqual({ id: 1, name: 'Item 1 updated' });
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
await sleep(200);
|
|
65
|
-
|
|
66
|
-
await testWithClient(client, async () => {
|
|
67
|
-
const relay = getItem({ id: '1' });
|
|
68
|
-
expect(relay.value).toEqual(undefined);
|
|
69
|
-
await relay;
|
|
70
|
-
expect(relay.value).toEqual({ id: 1, name: 'Item 1 updated' });
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should NOT evict queries with active subscribers', async () => {
|
|
75
|
-
const getItem = query(t => ({
|
|
76
|
-
path: '/active',
|
|
77
|
-
response: { data: t.string },
|
|
78
|
-
cache: { gcTime: 50 },
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
mockFetch.get('/active', { data: 'test' });
|
|
82
|
-
|
|
83
|
-
// Keep query active
|
|
84
|
-
await testWithClient(client, async () => {
|
|
85
|
-
const relay = getItem();
|
|
86
|
-
await relay;
|
|
87
|
-
|
|
88
|
-
const queryKey = hashValue(['GET:/active', undefined]);
|
|
89
|
-
|
|
90
|
-
// Wait past GC time
|
|
91
|
-
await sleep(60);
|
|
92
|
-
|
|
93
|
-
// Should still be in memory because it's active (has subscriber)
|
|
94
|
-
expect(client.queryInstances.has(queryKey)).toBe(true);
|
|
95
|
-
});
|
|
96
|
-
}, 3000);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('GC with LRU', () => {
|
|
100
|
-
it('should work alongside LRU cache eviction', async () => {
|
|
101
|
-
const User = entity(() => ({
|
|
102
|
-
__typename: t.typename('User'),
|
|
103
|
-
id: t.id,
|
|
104
|
-
name: t.string,
|
|
105
|
-
}));
|
|
106
|
-
|
|
107
|
-
const getUser = query(t => ({
|
|
108
|
-
path: '/users/[id]',
|
|
109
|
-
response: { user: User },
|
|
110
|
-
cache: {
|
|
111
|
-
maxCount: 2, // LRU size
|
|
112
|
-
gcTime: 5000, // 5 seconds
|
|
113
|
-
},
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
mockFetch.get('/users/1', { user: { __typename: 'User', id: 1, name: 'User 1' } });
|
|
117
|
-
mockFetch.get('/users/2', { user: { __typename: 'User', id: 2, name: 'User 2' } });
|
|
118
|
-
mockFetch.get('/users/3', { user: { __typename: 'User', id: 3, name: 'User 3' } });
|
|
119
|
-
|
|
120
|
-
await testWithClient(client, async () => {
|
|
121
|
-
// Fetch 3 users - third should evict first from disk via LRU
|
|
122
|
-
const relay1 = getUser({ id: '1' });
|
|
123
|
-
await relay1;
|
|
124
|
-
|
|
125
|
-
const relay2 = getUser({ id: '2' });
|
|
126
|
-
await relay2;
|
|
127
|
-
|
|
128
|
-
const relay3 = getUser({ id: '3' });
|
|
129
|
-
await relay3;
|
|
130
|
-
|
|
131
|
-
const query1Key = hashValue(['GET:/users/[id]', { id: '1' }]);
|
|
132
|
-
const query2Key = hashValue(['GET:/users/[id]', { id: '2' }]);
|
|
133
|
-
const query3Key = hashValue(['GET:/users/[id]', { id: '3' }]);
|
|
134
|
-
|
|
135
|
-
// All should be in memory initially
|
|
136
|
-
expect(client.queryInstances.has(query1Key)).toBe(true);
|
|
137
|
-
expect(client.queryInstances.has(query2Key)).toBe(true);
|
|
138
|
-
expect(client.queryInstances.has(query3Key)).toBe(true);
|
|
139
|
-
|
|
140
|
-
// First query should be evicted from DISK by LRU (but still in memory)
|
|
141
|
-
expect(kv.getString(valueKeyFor(query1Key))).toBeUndefined();
|
|
142
|
-
expect(kv.getString(valueKeyFor(query2Key))).toBeDefined();
|
|
143
|
-
expect(kv.getString(valueKeyFor(query3Key))).toBeDefined();
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe('GC Queue Management', () => {
|
|
149
|
-
it('should add queries to GC queue when deactivated', async () => {
|
|
150
|
-
const getItem = query(t => ({
|
|
151
|
-
path: '/item',
|
|
152
|
-
response: { value: t.string },
|
|
153
|
-
cache: { gcTime: 2000 },
|
|
154
|
-
}));
|
|
155
|
-
|
|
156
|
-
mockFetch.get('/item', { value: 'test' });
|
|
157
|
-
|
|
158
|
-
const queryKey = hashValue(['GET:/item', undefined]);
|
|
159
|
-
|
|
160
|
-
await testWithClient(client, async () => {
|
|
161
|
-
const relay = getItem();
|
|
162
|
-
await relay;
|
|
163
|
-
|
|
164
|
-
expect(client.queryInstances.has(queryKey)).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// After context ends, query should be scheduled for GC
|
|
168
|
-
// In a real implementation, we'd check the GC queue
|
|
169
|
-
// For now, we verify the query is still in memory
|
|
170
|
-
expect(client.queryInstances.has(queryKey)).toBe(true);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should remove queries from GC queue when reactivated', async () => {
|
|
174
|
-
const getItem = query(t => ({
|
|
175
|
-
path: '/reactivate',
|
|
176
|
-
response: { n: t.number },
|
|
177
|
-
cache: { gcTime: 1000 },
|
|
178
|
-
}));
|
|
179
|
-
|
|
180
|
-
mockFetch.get('/reactivate', { n: 1 });
|
|
181
|
-
|
|
182
|
-
const queryKey = hashValue(['GET:/reactivate', undefined]);
|
|
183
|
-
|
|
184
|
-
await testWithClient(client, async () => {
|
|
185
|
-
const relay = getItem();
|
|
186
|
-
await relay;
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// Query deactivated, scheduled for GC
|
|
190
|
-
await sleep(40);
|
|
191
|
-
|
|
192
|
-
// Reactivate before GC
|
|
193
|
-
mockFetch.get('/reactivate', { n: 2 });
|
|
194
|
-
await testWithClient(client, async () => {
|
|
195
|
-
const relay = getItem();
|
|
196
|
-
relay.value; // Access it
|
|
197
|
-
await sleep(60);
|
|
198
|
-
|
|
199
|
-
// Should still be in memory
|
|
200
|
-
expect(client.queryInstances.has(queryKey)).toBe(true);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Even after original GC time, should not be evicted due to reactivation
|
|
204
|
-
await sleep(40);
|
|
205
|
-
expect(client.queryInstances.has(queryKey)).toBe(true);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
describe('GC with Entities', () => {
|
|
210
|
-
it("should handle entity cleanup when query is GC'd", async () => {
|
|
211
|
-
const Post = entity(() => ({
|
|
212
|
-
__typename: t.typename('Post'),
|
|
213
|
-
id: t.id,
|
|
214
|
-
title: t.string,
|
|
215
|
-
}));
|
|
216
|
-
|
|
217
|
-
const User = entity(() => ({
|
|
218
|
-
__typename: t.typename('User'),
|
|
219
|
-
id: t.id,
|
|
220
|
-
name: t.string,
|
|
221
|
-
post: Post,
|
|
222
|
-
}));
|
|
223
|
-
|
|
224
|
-
const getUser = query(t => ({
|
|
225
|
-
path: '/user',
|
|
226
|
-
response: { user: User },
|
|
227
|
-
cache: { gcTime: 1000 },
|
|
228
|
-
}));
|
|
229
|
-
|
|
230
|
-
mockFetch.get('/user', {
|
|
231
|
-
user: {
|
|
232
|
-
__typename: 'User',
|
|
233
|
-
id: 1,
|
|
234
|
-
name: 'Alice',
|
|
235
|
-
post: {
|
|
236
|
-
__typename: 'Post',
|
|
237
|
-
id: 10,
|
|
238
|
-
title: 'Test Post',
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
await testWithClient(client, async () => {
|
|
244
|
-
const relay = getUser();
|
|
245
|
-
await relay;
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const userKey = hashValue('User:1');
|
|
249
|
-
const postKey = hashValue('Post:10');
|
|
250
|
-
|
|
251
|
-
// Entities should exist in store
|
|
252
|
-
expect(kv.getString(valueKeyFor(userKey))).toBeDefined();
|
|
253
|
-
expect(kv.getString(valueKeyFor(postKey))).toBeDefined();
|
|
254
|
-
|
|
255
|
-
// Note: The actual GC of entities is handled by the LRU system
|
|
256
|
-
// when queries are evicted. The gcTime affects when queries
|
|
257
|
-
// are removed from memory, but disk cleanup is done by LRU.
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
describe('Edge Cases', () => {
|
|
262
|
-
it('should handle queries without gcTime', async () => {
|
|
263
|
-
const getItem = query(t => ({
|
|
264
|
-
path: '/no-gc',
|
|
265
|
-
response: { data: t.string },
|
|
266
|
-
// No gcTime configured
|
|
267
|
-
}));
|
|
268
|
-
|
|
269
|
-
mockFetch.get('/no-gc', { data: 'test' });
|
|
270
|
-
|
|
271
|
-
const queryKey = hashValue(['GET:/no-gc', undefined]);
|
|
272
|
-
|
|
273
|
-
await testWithClient(client, async () => {
|
|
274
|
-
const relay = getItem();
|
|
275
|
-
await relay;
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Should remain in memory indefinitely
|
|
279
|
-
await sleep(100);
|
|
280
|
-
expect(client.queryInstances.has(queryKey)).toBe(true);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('should handle very short gcTime', async () => {
|
|
284
|
-
const getItem = query(t => ({
|
|
285
|
-
path: '/short-gc',
|
|
286
|
-
response: { value: t.number },
|
|
287
|
-
cache: { gcTime: 100 }, // Very short
|
|
288
|
-
}));
|
|
289
|
-
|
|
290
|
-
mockFetch.get('/short-gc', { value: 42 });
|
|
291
|
-
|
|
292
|
-
const queryKey = hashValue(['GET:/short-gc', undefined]);
|
|
293
|
-
|
|
294
|
-
await testWithClient(client, async () => {
|
|
295
|
-
const relay = getItem();
|
|
296
|
-
await relay;
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
// Should be scheduled for GC quickly
|
|
300
|
-
// Note: Actual eviction timing depends on GC interval
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('should handle very long gcTime', async () => {
|
|
304
|
-
const getItem = query(t => ({
|
|
305
|
-
path: '/long-gc',
|
|
306
|
-
response: { data: t.string },
|
|
307
|
-
cache: { gcTime: 1000 * 60 * 60 }, // 1 hour
|
|
308
|
-
}));
|
|
309
|
-
|
|
310
|
-
mockFetch.get('/long-gc', { data: 'persisted' });
|
|
311
|
-
|
|
312
|
-
const queryKey = hashValue(['GET:/long-gc', undefined]);
|
|
313
|
-
|
|
314
|
-
await testWithClient(client, async () => {
|
|
315
|
-
const relay = getItem();
|
|
316
|
-
await relay;
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// Should remain in memory for a while, then be evicted
|
|
320
|
-
await sleep(40);
|
|
321
|
-
expect(client.queryInstances.has(queryKey)).toBe(true);
|
|
322
|
-
|
|
323
|
-
await sleep(100);
|
|
324
|
-
expect(client.queryInstances.has(queryKey)).toBe(false);
|
|
325
|
-
});
|
|
326
|
-
});
|
|
327
|
-
});
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createMockFetch } from './utils.js';
|
|
3
|
-
|
|
4
|
-
describe('createMockFetch', () => {
|
|
5
|
-
it('should handle GET requests', async () => {
|
|
6
|
-
const mockFetch = createMockFetch();
|
|
7
|
-
mockFetch.get('/users/123', { id: 123, name: 'Alice' });
|
|
8
|
-
|
|
9
|
-
const response = await mockFetch('/users/123', { method: 'GET' });
|
|
10
|
-
const data = await response.json();
|
|
11
|
-
|
|
12
|
-
expect(data).toEqual({ id: 123, name: 'Alice' });
|
|
13
|
-
expect(response.status).toBe(200);
|
|
14
|
-
expect(response.ok).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should handle POST requests with custom status', async () => {
|
|
18
|
-
const mockFetch = createMockFetch();
|
|
19
|
-
mockFetch.post('/users', { id: 456, name: 'Bob' }, { status: 201 });
|
|
20
|
-
|
|
21
|
-
const response = await mockFetch('/users', { method: 'POST' });
|
|
22
|
-
const data = await response.json();
|
|
23
|
-
|
|
24
|
-
expect(data).toEqual({ id: 456, name: 'Bob' });
|
|
25
|
-
expect(response.status).toBe(201);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should handle PUT requests', async () => {
|
|
29
|
-
const mockFetch = createMockFetch();
|
|
30
|
-
mockFetch.put('/users/123', { id: 123, name: 'Updated Alice' });
|
|
31
|
-
|
|
32
|
-
const response = await mockFetch('/users/123', { method: 'PUT' });
|
|
33
|
-
const data = await response.json();
|
|
34
|
-
|
|
35
|
-
expect(data).toEqual({ id: 123, name: 'Updated Alice' });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should handle DELETE requests', async () => {
|
|
39
|
-
const mockFetch = createMockFetch();
|
|
40
|
-
mockFetch.delete('/users/123', { success: true });
|
|
41
|
-
|
|
42
|
-
const response = await mockFetch('/users/123', { method: 'DELETE' });
|
|
43
|
-
const data = await response.json();
|
|
44
|
-
|
|
45
|
-
expect(data).toEqual({ success: true });
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should handle PATCH requests', async () => {
|
|
49
|
-
const mockFetch = createMockFetch();
|
|
50
|
-
mockFetch.patch('/users/123', { id: 123, email: 'new@example.com' });
|
|
51
|
-
|
|
52
|
-
const response = await mockFetch('/users/123', { method: 'PATCH' });
|
|
53
|
-
const data = await response.json();
|
|
54
|
-
|
|
55
|
-
expect(data).toEqual({ id: 123, email: 'new@example.com' });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should support custom headers', async () => {
|
|
59
|
-
const mockFetch = createMockFetch();
|
|
60
|
-
mockFetch.get(
|
|
61
|
-
'/users/123',
|
|
62
|
-
{ id: 123 },
|
|
63
|
-
{
|
|
64
|
-
headers: { 'X-Custom-Header': 'test-value' },
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const response = await mockFetch('/users/123', { method: 'GET' });
|
|
69
|
-
|
|
70
|
-
expect(response.headers.get('X-Custom-Header')).toBe('test-value');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should support delays', async () => {
|
|
74
|
-
const mockFetch = createMockFetch();
|
|
75
|
-
mockFetch.get('/users/123', { id: 123 }, { delay: 100 });
|
|
76
|
-
|
|
77
|
-
const start = Date.now();
|
|
78
|
-
await mockFetch('/users/123', { method: 'GET' });
|
|
79
|
-
const duration = Date.now() - start;
|
|
80
|
-
|
|
81
|
-
expect(duration).toBeGreaterThanOrEqual(90); // Allow some margin
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should match path parameters', async () => {
|
|
85
|
-
const mockFetch = createMockFetch();
|
|
86
|
-
mockFetch.get('/users/[id]', { id: 123, name: 'Alice' });
|
|
87
|
-
|
|
88
|
-
const response = await mockFetch('/users/123', { method: 'GET' });
|
|
89
|
-
const data = await response.json();
|
|
90
|
-
|
|
91
|
-
expect(data).toEqual({ id: 123, name: 'Alice' });
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should match multiple path parameters', async () => {
|
|
95
|
-
const mockFetch = createMockFetch();
|
|
96
|
-
mockFetch.get('/users/[userId]/posts/[postId]', { userId: 5, postId: 10 });
|
|
97
|
-
|
|
98
|
-
const response = await mockFetch('/users/5/posts/10', { method: 'GET' });
|
|
99
|
-
const data = await response.json();
|
|
100
|
-
|
|
101
|
-
expect(data).toEqual({ userId: 5, postId: 10 });
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should throw error for unmocked routes', async () => {
|
|
105
|
-
const mockFetch = createMockFetch();
|
|
106
|
-
|
|
107
|
-
await expect(mockFetch('/users/123', { method: 'GET' })).rejects.toThrow(
|
|
108
|
-
'No mock response configured for GET /users/123',
|
|
109
|
-
);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should track all calls', async () => {
|
|
113
|
-
const mockFetch = createMockFetch();
|
|
114
|
-
mockFetch.get('/users/123', { id: 123 });
|
|
115
|
-
mockFetch.post('/users', { id: 456 });
|
|
116
|
-
|
|
117
|
-
await mockFetch('/users/123', { method: 'GET' });
|
|
118
|
-
await mockFetch('/users', { method: 'POST', body: '{}' });
|
|
119
|
-
|
|
120
|
-
expect(mockFetch.calls).toHaveLength(2);
|
|
121
|
-
expect(mockFetch.calls[0].url).toBe('/users/123');
|
|
122
|
-
expect(mockFetch.calls[1].url).toBe('/users');
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should reset routes and calls', async () => {
|
|
126
|
-
const mockFetch = createMockFetch();
|
|
127
|
-
mockFetch.get('/users/123', { id: 123 });
|
|
128
|
-
|
|
129
|
-
await mockFetch('/users/123', { method: 'GET' });
|
|
130
|
-
expect(mockFetch.calls).toHaveLength(1);
|
|
131
|
-
|
|
132
|
-
mockFetch.reset();
|
|
133
|
-
|
|
134
|
-
expect(mockFetch.calls).toHaveLength(0);
|
|
135
|
-
await expect(mockFetch('/users/123', { method: 'GET' })).rejects.toThrow('No mock response configured');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should default to GET method', async () => {
|
|
139
|
-
const mockFetch = createMockFetch();
|
|
140
|
-
mockFetch.get('/users/123', { id: 123 });
|
|
141
|
-
|
|
142
|
-
const response = await mockFetch('/users/123');
|
|
143
|
-
const data = await response.json();
|
|
144
|
-
|
|
145
|
-
expect(data).toEqual({ id: 123 });
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should handle query parameters in URLs', async () => {
|
|
149
|
-
const mockFetch = createMockFetch();
|
|
150
|
-
mockFetch.get('/users', { users: [] });
|
|
151
|
-
|
|
152
|
-
const response = await mockFetch('/users?page=1&limit=10', { method: 'GET' });
|
|
153
|
-
const data = await response.json();
|
|
154
|
-
|
|
155
|
-
expect(data).toEqual({ users: [] });
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it('should reuse the last match when no unused mocks remain', async () => {
|
|
159
|
-
const mockFetch = createMockFetch();
|
|
160
|
-
mockFetch.get('/users/123', { id: 123, name: 'Alice' });
|
|
161
|
-
|
|
162
|
-
// First call should succeed
|
|
163
|
-
const response1 = await mockFetch('/users/123', { method: 'GET' });
|
|
164
|
-
const data1 = await response1.json();
|
|
165
|
-
expect(data1).toEqual({ id: 123, name: 'Alice' });
|
|
166
|
-
|
|
167
|
-
// Second call should reuse the same mock since there are no unused ones
|
|
168
|
-
const response2 = await mockFetch('/users/123', { method: 'GET' });
|
|
169
|
-
const data2 = await response2.json();
|
|
170
|
-
expect(data2).toEqual({ id: 123, name: 'Alice' });
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should allow multiple setups for repeated calls', async () => {
|
|
174
|
-
const mockFetch = createMockFetch();
|
|
175
|
-
mockFetch.get('/users/123', { id: 123, name: 'Alice' });
|
|
176
|
-
mockFetch.get('/users/123', { id: 123, name: 'Updated Alice' });
|
|
177
|
-
|
|
178
|
-
const response1 = await mockFetch('/users/123', { method: 'GET' });
|
|
179
|
-
const data1 = await response1.json();
|
|
180
|
-
expect(data1).toEqual({ id: 123, name: 'Alice' });
|
|
181
|
-
|
|
182
|
-
const response2 = await mockFetch('/users/123', { method: 'GET' });
|
|
183
|
-
const data2 = await response2.json();
|
|
184
|
-
expect(data2).toEqual({ id: 123, name: 'Updated Alice' });
|
|
185
|
-
});
|
|
186
|
-
});
|