shelflife-react-hooks 1.0.18 → 1.0.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/dist/index.cjs.js +23 -0
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +23 -0
- package/dist/index.esm.js.map +1 -1
- package/package.json +36 -36
- package/src/context/AuthContext.tsx +161 -161
- package/src/context/InviteContext.tsx +74 -74
- package/src/context/ProductContext.tsx +131 -121
- package/src/context/RunningLowContext.tsx +100 -100
- package/src/context/ShoppingListContext.tsx +76 -76
- package/src/context/StorageContext.tsx +105 -105
- package/src/context/StorageItemContext.tsx +157 -157
- package/src/context/StorageMemberContext.tsx +84 -84
- package/src/context/UserContext.tsx +109 -109
- package/src/context/__tests__/contexts.test.tsx +370 -370
- package/src/context/api/authApi.ts +155 -155
- package/src/context/api/inviteApi.ts +65 -65
- package/src/context/api/productApi.ts +223 -201
- package/src/context/api/requestState.ts +24 -24
- package/src/context/api/runningLowApi.ts +141 -141
- package/src/context/api/shoppingListApi.ts +161 -159
- package/src/context/api/storageApi.ts +166 -166
- package/src/context/api/storageItemApi.ts +260 -260
- package/src/context/api/storageMemberApi.ts +84 -84
- package/src/context/api/userApi.ts +161 -161
- package/src/context/http.ts +22 -22
- package/src/index.ts +21 -21
- package/src/type/PaginatedResponse.ts +8 -8
- package/src/type/auth.ts +79 -79
- package/src/type/base.ts +21 -21
- package/src/type/item.ts +12 -12
- package/src/type/member.ts +6 -6
- package/src/type/models.ts +56 -56
- package/src/type/product.ts +11 -11
- package/src/type/requests.ts +60 -60
- package/src/type/runninglow.ts +13 -13
- package/src/type/shoppingList.ts +13 -13
- package/src/type/storage.ts +7 -7
- package/src/type/user.ts +11 -11
- package/tsconfig.json +46 -46
- package/tsup.config.ts +10 -10
- package/vitest.config.ts +8 -8
|
@@ -1,370 +1,370 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import { render, waitFor } from '@testing-library/react';
|
|
3
|
-
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
AuthProvider,
|
|
7
|
-
InviteProvider,
|
|
8
|
-
ProductProvider,
|
|
9
|
-
RunningLowProvider,
|
|
10
|
-
StorageItemProvider,
|
|
11
|
-
StorageMemberProvider,
|
|
12
|
-
StorageProvider,
|
|
13
|
-
UserProvider,
|
|
14
|
-
useAuth,
|
|
15
|
-
useInvites,
|
|
16
|
-
useProducts,
|
|
17
|
-
useRunningLow,
|
|
18
|
-
useStorageItems,
|
|
19
|
-
useStorageMembers,
|
|
20
|
-
useStorages,
|
|
21
|
-
useUsers
|
|
22
|
-
} from '../../index.js';
|
|
23
|
-
|
|
24
|
-
const jsonResponse = (data: unknown, status = 200) => ({
|
|
25
|
-
ok: status >= 200 && status < 300,
|
|
26
|
-
status,
|
|
27
|
-
headers: {
|
|
28
|
-
get: () => 'application/json'
|
|
29
|
-
},
|
|
30
|
-
json: async () => data,
|
|
31
|
-
arrayBuffer: async () => new ArrayBuffer(4)
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const emptyResponse = (status = 200) => ({
|
|
35
|
-
ok: status >= 200 && status < 300,
|
|
36
|
-
status,
|
|
37
|
-
headers: {
|
|
38
|
-
get: () => null
|
|
39
|
-
},
|
|
40
|
-
json: async () => ({}),
|
|
41
|
-
arrayBuffer: async () => new ArrayBuffer(0)
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const mockFetchQueue = (responses: Array<ReturnType<typeof jsonResponse>>) => {
|
|
45
|
-
const queue = [...responses];
|
|
46
|
-
global.fetch = vi.fn(async () => queue.shift() ?? emptyResponse()) as unknown as typeof fetch;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const baseUrl = 'http://localhost:3000';
|
|
50
|
-
|
|
51
|
-
const AuthProbe = ({ onReady }: { onReady: (value: ReturnType<typeof useAuth>) => void }) => {
|
|
52
|
-
const value = useAuth();
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
onReady(value);
|
|
55
|
-
}, [onReady, value]);
|
|
56
|
-
return null;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const ProviderProbe = <T,>({
|
|
60
|
-
useHook,
|
|
61
|
-
onReady
|
|
62
|
-
}: {
|
|
63
|
-
useHook: () => T;
|
|
64
|
-
onReady: (value: T) => void;
|
|
65
|
-
}) => {
|
|
66
|
-
const value = useHook();
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
onReady(value);
|
|
69
|
-
}, [onReady, value]);
|
|
70
|
-
return null;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
describe('AuthContext', () => {
|
|
74
|
-
beforeEach(() => {
|
|
75
|
-
vi.restoreAllMocks();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('logs in and stores token + user', async () => {
|
|
79
|
-
mockFetchQueue([
|
|
80
|
-
jsonResponse({ token: 'token-123' }),
|
|
81
|
-
jsonResponse({ id: 1, username: 'jane', admin: false })
|
|
82
|
-
]);
|
|
83
|
-
|
|
84
|
-
const ctxRef: { current?: ReturnType<typeof useAuth> } = {};
|
|
85
|
-
|
|
86
|
-
render(
|
|
87
|
-
<AuthProvider baseUrl={baseUrl}>
|
|
88
|
-
<AuthProbe onReady={(value) => {
|
|
89
|
-
ctxRef.current = value;
|
|
90
|
-
}} />
|
|
91
|
-
</AuthProvider>
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
await waitFor(() => {
|
|
95
|
-
expect(ctxRef.current).toBeDefined();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const ctx = ctxRef.current;
|
|
99
|
-
if (!ctx) {
|
|
100
|
-
throw new Error('Auth context not ready');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
await ctx.login({ email: 'user@example.com', password: 'pass' });
|
|
104
|
-
|
|
105
|
-
await waitFor(() => {
|
|
106
|
-
expect(ctxRef.current?.token).toBe('token-123');
|
|
107
|
-
expect(ctxRef.current?.user).toEqual({ id: 1, username: 'jane', admin: false });
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('InviteContext', () => {
|
|
113
|
-
it('fetches invites', async () => {
|
|
114
|
-
mockFetchQueue([
|
|
115
|
-
jsonResponse([
|
|
116
|
-
{ id: 10, storage: { id: 1, name: 'Kitchen', owner: { id: 2, username: 'sam', admin: false } }, user: { id: 3, username: 'alex', admin: false }, accepted: false }
|
|
117
|
-
])
|
|
118
|
-
]);
|
|
119
|
-
|
|
120
|
-
const ctxRef: { current?: ReturnType<typeof useInvites> } = {};
|
|
121
|
-
|
|
122
|
-
render(
|
|
123
|
-
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
124
|
-
<InviteProvider baseUrl={baseUrl}>
|
|
125
|
-
<ProviderProbe useHook={useInvites} onReady={(value) => { ctxRef.current = value; }} />
|
|
126
|
-
</InviteProvider>
|
|
127
|
-
</AuthProvider>
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
await waitFor(() => {
|
|
131
|
-
expect(ctxRef.current).toBeDefined();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const ctx = ctxRef.current;
|
|
135
|
-
if (!ctx) {
|
|
136
|
-
throw new Error('Invite context not ready');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
await ctx.fetchInvites();
|
|
140
|
-
|
|
141
|
-
await waitFor(() => {
|
|
142
|
-
expect(ctxRef.current?.invites).toHaveLength(1);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe('ProductContext', () => {
|
|
148
|
-
it('fetches products', async () => {
|
|
149
|
-
mockFetchQueue([
|
|
150
|
-
jsonResponse([
|
|
151
|
-
{ id: 1, ownerId: 2, name: 'Milk', category: 'Dairy', expirationDaysDelta: 7, barcode: null }
|
|
152
|
-
])
|
|
153
|
-
]);
|
|
154
|
-
|
|
155
|
-
const ctxRef: { current?: ReturnType<typeof useProducts> } = {};
|
|
156
|
-
|
|
157
|
-
render(
|
|
158
|
-
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
159
|
-
<ProductProvider baseUrl={baseUrl}>
|
|
160
|
-
<ProviderProbe useHook={useProducts} onReady={(value) => { ctxRef.current = value; }} />
|
|
161
|
-
</ProductProvider>
|
|
162
|
-
</AuthProvider>
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
await waitFor(() => {
|
|
166
|
-
expect(ctxRef.current).toBeDefined();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const ctx = ctxRef.current;
|
|
170
|
-
if (!ctx) {
|
|
171
|
-
throw new Error('Product context not ready');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
await ctx.fetchProducts();
|
|
175
|
-
|
|
176
|
-
await waitFor(() => {
|
|
177
|
-
expect(ctxRef.current?.products).toHaveLength(1);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe('RunningLowContext', () => {
|
|
183
|
-
it('fetches running low settings', async () => {
|
|
184
|
-
mockFetchQueue([
|
|
185
|
-
jsonResponse([
|
|
186
|
-
{
|
|
187
|
-
id: 5,
|
|
188
|
-
storage: { id: 1, name: 'Pantry', owner: { id: 2, username: 'sam', admin: false } },
|
|
189
|
-
product: { id: 3, ownerId: 2, name: 'Rice', category: 'Grains', expirationDaysDelta: 365, barcode: null },
|
|
190
|
-
runningLow: 2
|
|
191
|
-
}
|
|
192
|
-
])
|
|
193
|
-
]);
|
|
194
|
-
|
|
195
|
-
const ctxRef: { current?: ReturnType<typeof useRunningLow> } = {};
|
|
196
|
-
|
|
197
|
-
render(
|
|
198
|
-
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
199
|
-
<RunningLowProvider baseUrl={baseUrl}>
|
|
200
|
-
<ProviderProbe useHook={useRunningLow} onReady={(value) => { ctxRef.current = value; }} />
|
|
201
|
-
</RunningLowProvider>
|
|
202
|
-
</AuthProvider>
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
await waitFor(() => {
|
|
206
|
-
expect(ctxRef.current).toBeDefined();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const ctx = ctxRef.current;
|
|
210
|
-
if (!ctx) {
|
|
211
|
-
throw new Error('Running low context not ready');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
await ctx.fetchSettings(1);
|
|
215
|
-
|
|
216
|
-
await waitFor(() => {
|
|
217
|
-
expect(ctxRef.current?.settings).toHaveLength(1);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe('StorageContext', () => {
|
|
223
|
-
it('fetches storages', async () => {
|
|
224
|
-
mockFetchQueue([
|
|
225
|
-
jsonResponse([
|
|
226
|
-
{ id: 1, name: 'Garage', owner: { id: 2, username: 'sam', admin: false } }
|
|
227
|
-
])
|
|
228
|
-
]);
|
|
229
|
-
|
|
230
|
-
const ctxRef: { current?: ReturnType<typeof useStorages> } = {};
|
|
231
|
-
|
|
232
|
-
render(
|
|
233
|
-
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
234
|
-
<StorageProvider baseUrl={baseUrl}>
|
|
235
|
-
<ProviderProbe useHook={useStorages} onReady={(value) => { ctxRef.current = value; }} />
|
|
236
|
-
</StorageProvider>
|
|
237
|
-
</AuthProvider>
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
await waitFor(() => {
|
|
241
|
-
expect(ctxRef.current).toBeDefined();
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const ctx = ctxRef.current;
|
|
245
|
-
if (!ctx) {
|
|
246
|
-
throw new Error('Storage context not ready');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
await ctx.fetchStorages();
|
|
250
|
-
|
|
251
|
-
await waitFor(() => {
|
|
252
|
-
expect(ctxRef.current?.storages).toHaveLength(1);
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe('StorageItemContext', () => {
|
|
258
|
-
it('fetches storage items', async () => {
|
|
259
|
-
mockFetchQueue([
|
|
260
|
-
jsonResponse([
|
|
261
|
-
{
|
|
262
|
-
id: 4,
|
|
263
|
-
product: { id: 2, ownerId: 1, name: 'Bread', category: 'Bakery', expirationDaysDelta: 3, barcode: null },
|
|
264
|
-
expiresAt: '2025-02-01',
|
|
265
|
-
createdAt: '2025-01-01T00:00:00'
|
|
266
|
-
}
|
|
267
|
-
])
|
|
268
|
-
]);
|
|
269
|
-
|
|
270
|
-
const ctxRef: { current?: ReturnType<typeof useStorageItems> } = {};
|
|
271
|
-
|
|
272
|
-
render(
|
|
273
|
-
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
274
|
-
<StorageItemProvider baseUrl={baseUrl}>
|
|
275
|
-
<ProviderProbe useHook={useStorageItems} onReady={(value) => { ctxRef.current = value; }} />
|
|
276
|
-
</StorageItemProvider>
|
|
277
|
-
</AuthProvider>
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
await waitFor(() => {
|
|
281
|
-
expect(ctxRef.current).toBeDefined();
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
const ctx = ctxRef.current;
|
|
285
|
-
if (!ctx) {
|
|
286
|
-
throw new Error('Storage item context not ready');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
await ctx.fetchItems(1);
|
|
290
|
-
|
|
291
|
-
await waitFor(() => {
|
|
292
|
-
expect(ctxRef.current?.items).toHaveLength(1);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
describe('StorageMemberContext', () => {
|
|
298
|
-
it('fetches members', async () => {
|
|
299
|
-
mockFetchQueue([
|
|
300
|
-
jsonResponse([
|
|
301
|
-
{
|
|
302
|
-
id: 7,
|
|
303
|
-
storage: { id: 1, name: 'Cellar', owner: { id: 2, username: 'sam', admin: false } },
|
|
304
|
-
user: { id: 3, username: 'alex', admin: false },
|
|
305
|
-
accepted: true
|
|
306
|
-
}
|
|
307
|
-
])
|
|
308
|
-
]);
|
|
309
|
-
|
|
310
|
-
const ctxRef: { current?: ReturnType<typeof useStorageMembers> } = {};
|
|
311
|
-
|
|
312
|
-
render(
|
|
313
|
-
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
314
|
-
<StorageMemberProvider baseUrl={baseUrl}>
|
|
315
|
-
<ProviderProbe useHook={useStorageMembers} onReady={(value) => { ctxRef.current = value; }} />
|
|
316
|
-
</StorageMemberProvider>
|
|
317
|
-
</AuthProvider>
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
await waitFor(() => {
|
|
321
|
-
expect(ctxRef.current).toBeDefined();
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const ctx = ctxRef.current;
|
|
325
|
-
if (!ctx) {
|
|
326
|
-
throw new Error('Storage member context not ready');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
await ctx.fetchMembers(1);
|
|
330
|
-
|
|
331
|
-
await waitFor(() => {
|
|
332
|
-
expect(ctxRef.current?.members).toHaveLength(1);
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
describe('UserContext', () => {
|
|
338
|
-
it('fetches users', async () => {
|
|
339
|
-
mockFetchQueue([
|
|
340
|
-
jsonResponse([
|
|
341
|
-
{ id: 1, username: 'sam', admin: false }
|
|
342
|
-
])
|
|
343
|
-
]);
|
|
344
|
-
|
|
345
|
-
const ctxRef: { current?: ReturnType<typeof useUsers> } = {};
|
|
346
|
-
|
|
347
|
-
render(
|
|
348
|
-
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
349
|
-
<UserProvider baseUrl={baseUrl}>
|
|
350
|
-
<ProviderProbe useHook={useUsers} onReady={(value) => { ctxRef.current = value; }} />
|
|
351
|
-
</UserProvider>
|
|
352
|
-
</AuthProvider>
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
await waitFor(() => {
|
|
356
|
-
expect(ctxRef.current).toBeDefined();
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const ctx = ctxRef.current;
|
|
360
|
-
if (!ctx) {
|
|
361
|
-
throw new Error('User context not ready');
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
await ctx.fetchUsers();
|
|
365
|
-
|
|
366
|
-
await waitFor(() => {
|
|
367
|
-
expect(ctxRef.current?.users).toHaveLength(1);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
});
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { render, waitFor } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AuthProvider,
|
|
7
|
+
InviteProvider,
|
|
8
|
+
ProductProvider,
|
|
9
|
+
RunningLowProvider,
|
|
10
|
+
StorageItemProvider,
|
|
11
|
+
StorageMemberProvider,
|
|
12
|
+
StorageProvider,
|
|
13
|
+
UserProvider,
|
|
14
|
+
useAuth,
|
|
15
|
+
useInvites,
|
|
16
|
+
useProducts,
|
|
17
|
+
useRunningLow,
|
|
18
|
+
useStorageItems,
|
|
19
|
+
useStorageMembers,
|
|
20
|
+
useStorages,
|
|
21
|
+
useUsers
|
|
22
|
+
} from '../../index.js';
|
|
23
|
+
|
|
24
|
+
const jsonResponse = (data: unknown, status = 200) => ({
|
|
25
|
+
ok: status >= 200 && status < 300,
|
|
26
|
+
status,
|
|
27
|
+
headers: {
|
|
28
|
+
get: () => 'application/json'
|
|
29
|
+
},
|
|
30
|
+
json: async () => data,
|
|
31
|
+
arrayBuffer: async () => new ArrayBuffer(4)
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const emptyResponse = (status = 200) => ({
|
|
35
|
+
ok: status >= 200 && status < 300,
|
|
36
|
+
status,
|
|
37
|
+
headers: {
|
|
38
|
+
get: () => null
|
|
39
|
+
},
|
|
40
|
+
json: async () => ({}),
|
|
41
|
+
arrayBuffer: async () => new ArrayBuffer(0)
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const mockFetchQueue = (responses: Array<ReturnType<typeof jsonResponse>>) => {
|
|
45
|
+
const queue = [...responses];
|
|
46
|
+
global.fetch = vi.fn(async () => queue.shift() ?? emptyResponse()) as unknown as typeof fetch;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const baseUrl = 'http://localhost:3000';
|
|
50
|
+
|
|
51
|
+
const AuthProbe = ({ onReady }: { onReady: (value: ReturnType<typeof useAuth>) => void }) => {
|
|
52
|
+
const value = useAuth();
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
onReady(value);
|
|
55
|
+
}, [onReady, value]);
|
|
56
|
+
return null;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const ProviderProbe = <T,>({
|
|
60
|
+
useHook,
|
|
61
|
+
onReady
|
|
62
|
+
}: {
|
|
63
|
+
useHook: () => T;
|
|
64
|
+
onReady: (value: T) => void;
|
|
65
|
+
}) => {
|
|
66
|
+
const value = useHook();
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
onReady(value);
|
|
69
|
+
}, [onReady, value]);
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
describe('AuthContext', () => {
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
vi.restoreAllMocks();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('logs in and stores token + user', async () => {
|
|
79
|
+
mockFetchQueue([
|
|
80
|
+
jsonResponse({ token: 'token-123' }),
|
|
81
|
+
jsonResponse({ id: 1, username: 'jane', admin: false })
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const ctxRef: { current?: ReturnType<typeof useAuth> } = {};
|
|
85
|
+
|
|
86
|
+
render(
|
|
87
|
+
<AuthProvider baseUrl={baseUrl}>
|
|
88
|
+
<AuthProbe onReady={(value) => {
|
|
89
|
+
ctxRef.current = value;
|
|
90
|
+
}} />
|
|
91
|
+
</AuthProvider>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
expect(ctxRef.current).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const ctx = ctxRef.current;
|
|
99
|
+
if (!ctx) {
|
|
100
|
+
throw new Error('Auth context not ready');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await ctx.login({ email: 'user@example.com', password: 'pass' });
|
|
104
|
+
|
|
105
|
+
await waitFor(() => {
|
|
106
|
+
expect(ctxRef.current?.token).toBe('token-123');
|
|
107
|
+
expect(ctxRef.current?.user).toEqual({ id: 1, username: 'jane', admin: false });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('InviteContext', () => {
|
|
113
|
+
it('fetches invites', async () => {
|
|
114
|
+
mockFetchQueue([
|
|
115
|
+
jsonResponse([
|
|
116
|
+
{ id: 10, storage: { id: 1, name: 'Kitchen', owner: { id: 2, username: 'sam', admin: false } }, user: { id: 3, username: 'alex', admin: false }, accepted: false }
|
|
117
|
+
])
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const ctxRef: { current?: ReturnType<typeof useInvites> } = {};
|
|
121
|
+
|
|
122
|
+
render(
|
|
123
|
+
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
124
|
+
<InviteProvider baseUrl={baseUrl}>
|
|
125
|
+
<ProviderProbe useHook={useInvites} onReady={(value) => { ctxRef.current = value; }} />
|
|
126
|
+
</InviteProvider>
|
|
127
|
+
</AuthProvider>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(ctxRef.current).toBeDefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const ctx = ctxRef.current;
|
|
135
|
+
if (!ctx) {
|
|
136
|
+
throw new Error('Invite context not ready');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await ctx.fetchInvites();
|
|
140
|
+
|
|
141
|
+
await waitFor(() => {
|
|
142
|
+
expect(ctxRef.current?.invites).toHaveLength(1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('ProductContext', () => {
|
|
148
|
+
it('fetches products', async () => {
|
|
149
|
+
mockFetchQueue([
|
|
150
|
+
jsonResponse([
|
|
151
|
+
{ id: 1, ownerId: 2, name: 'Milk', category: 'Dairy', expirationDaysDelta: 7, barcode: null }
|
|
152
|
+
])
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
const ctxRef: { current?: ReturnType<typeof useProducts> } = {};
|
|
156
|
+
|
|
157
|
+
render(
|
|
158
|
+
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
159
|
+
<ProductProvider baseUrl={baseUrl}>
|
|
160
|
+
<ProviderProbe useHook={useProducts} onReady={(value) => { ctxRef.current = value; }} />
|
|
161
|
+
</ProductProvider>
|
|
162
|
+
</AuthProvider>
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
await waitFor(() => {
|
|
166
|
+
expect(ctxRef.current).toBeDefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const ctx = ctxRef.current;
|
|
170
|
+
if (!ctx) {
|
|
171
|
+
throw new Error('Product context not ready');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await ctx.fetchProducts();
|
|
175
|
+
|
|
176
|
+
await waitFor(() => {
|
|
177
|
+
expect(ctxRef.current?.products).toHaveLength(1);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('RunningLowContext', () => {
|
|
183
|
+
it('fetches running low settings', async () => {
|
|
184
|
+
mockFetchQueue([
|
|
185
|
+
jsonResponse([
|
|
186
|
+
{
|
|
187
|
+
id: 5,
|
|
188
|
+
storage: { id: 1, name: 'Pantry', owner: { id: 2, username: 'sam', admin: false } },
|
|
189
|
+
product: { id: 3, ownerId: 2, name: 'Rice', category: 'Grains', expirationDaysDelta: 365, barcode: null },
|
|
190
|
+
runningLow: 2
|
|
191
|
+
}
|
|
192
|
+
])
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
const ctxRef: { current?: ReturnType<typeof useRunningLow> } = {};
|
|
196
|
+
|
|
197
|
+
render(
|
|
198
|
+
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
199
|
+
<RunningLowProvider baseUrl={baseUrl}>
|
|
200
|
+
<ProviderProbe useHook={useRunningLow} onReady={(value) => { ctxRef.current = value; }} />
|
|
201
|
+
</RunningLowProvider>
|
|
202
|
+
</AuthProvider>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
await waitFor(() => {
|
|
206
|
+
expect(ctxRef.current).toBeDefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const ctx = ctxRef.current;
|
|
210
|
+
if (!ctx) {
|
|
211
|
+
throw new Error('Running low context not ready');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await ctx.fetchSettings(1);
|
|
215
|
+
|
|
216
|
+
await waitFor(() => {
|
|
217
|
+
expect(ctxRef.current?.settings).toHaveLength(1);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('StorageContext', () => {
|
|
223
|
+
it('fetches storages', async () => {
|
|
224
|
+
mockFetchQueue([
|
|
225
|
+
jsonResponse([
|
|
226
|
+
{ id: 1, name: 'Garage', owner: { id: 2, username: 'sam', admin: false } }
|
|
227
|
+
])
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
const ctxRef: { current?: ReturnType<typeof useStorages> } = {};
|
|
231
|
+
|
|
232
|
+
render(
|
|
233
|
+
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
234
|
+
<StorageProvider baseUrl={baseUrl}>
|
|
235
|
+
<ProviderProbe useHook={useStorages} onReady={(value) => { ctxRef.current = value; }} />
|
|
236
|
+
</StorageProvider>
|
|
237
|
+
</AuthProvider>
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
await waitFor(() => {
|
|
241
|
+
expect(ctxRef.current).toBeDefined();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const ctx = ctxRef.current;
|
|
245
|
+
if (!ctx) {
|
|
246
|
+
throw new Error('Storage context not ready');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await ctx.fetchStorages();
|
|
250
|
+
|
|
251
|
+
await waitFor(() => {
|
|
252
|
+
expect(ctxRef.current?.storages).toHaveLength(1);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('StorageItemContext', () => {
|
|
258
|
+
it('fetches storage items', async () => {
|
|
259
|
+
mockFetchQueue([
|
|
260
|
+
jsonResponse([
|
|
261
|
+
{
|
|
262
|
+
id: 4,
|
|
263
|
+
product: { id: 2, ownerId: 1, name: 'Bread', category: 'Bakery', expirationDaysDelta: 3, barcode: null },
|
|
264
|
+
expiresAt: '2025-02-01',
|
|
265
|
+
createdAt: '2025-01-01T00:00:00'
|
|
266
|
+
}
|
|
267
|
+
])
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
const ctxRef: { current?: ReturnType<typeof useStorageItems> } = {};
|
|
271
|
+
|
|
272
|
+
render(
|
|
273
|
+
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
274
|
+
<StorageItemProvider baseUrl={baseUrl}>
|
|
275
|
+
<ProviderProbe useHook={useStorageItems} onReady={(value) => { ctxRef.current = value; }} />
|
|
276
|
+
</StorageItemProvider>
|
|
277
|
+
</AuthProvider>
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
await waitFor(() => {
|
|
281
|
+
expect(ctxRef.current).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const ctx = ctxRef.current;
|
|
285
|
+
if (!ctx) {
|
|
286
|
+
throw new Error('Storage item context not ready');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await ctx.fetchItems(1);
|
|
290
|
+
|
|
291
|
+
await waitFor(() => {
|
|
292
|
+
expect(ctxRef.current?.items).toHaveLength(1);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('StorageMemberContext', () => {
|
|
298
|
+
it('fetches members', async () => {
|
|
299
|
+
mockFetchQueue([
|
|
300
|
+
jsonResponse([
|
|
301
|
+
{
|
|
302
|
+
id: 7,
|
|
303
|
+
storage: { id: 1, name: 'Cellar', owner: { id: 2, username: 'sam', admin: false } },
|
|
304
|
+
user: { id: 3, username: 'alex', admin: false },
|
|
305
|
+
accepted: true
|
|
306
|
+
}
|
|
307
|
+
])
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
const ctxRef: { current?: ReturnType<typeof useStorageMembers> } = {};
|
|
311
|
+
|
|
312
|
+
render(
|
|
313
|
+
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
314
|
+
<StorageMemberProvider baseUrl={baseUrl}>
|
|
315
|
+
<ProviderProbe useHook={useStorageMembers} onReady={(value) => { ctxRef.current = value; }} />
|
|
316
|
+
</StorageMemberProvider>
|
|
317
|
+
</AuthProvider>
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
await waitFor(() => {
|
|
321
|
+
expect(ctxRef.current).toBeDefined();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const ctx = ctxRef.current;
|
|
325
|
+
if (!ctx) {
|
|
326
|
+
throw new Error('Storage member context not ready');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await ctx.fetchMembers(1);
|
|
330
|
+
|
|
331
|
+
await waitFor(() => {
|
|
332
|
+
expect(ctxRef.current?.members).toHaveLength(1);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe('UserContext', () => {
|
|
338
|
+
it('fetches users', async () => {
|
|
339
|
+
mockFetchQueue([
|
|
340
|
+
jsonResponse([
|
|
341
|
+
{ id: 1, username: 'sam', admin: false }
|
|
342
|
+
])
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
const ctxRef: { current?: ReturnType<typeof useUsers> } = {};
|
|
346
|
+
|
|
347
|
+
render(
|
|
348
|
+
<AuthProvider baseUrl={baseUrl} initialToken="token">
|
|
349
|
+
<UserProvider baseUrl={baseUrl}>
|
|
350
|
+
<ProviderProbe useHook={useUsers} onReady={(value) => { ctxRef.current = value; }} />
|
|
351
|
+
</UserProvider>
|
|
352
|
+
</AuthProvider>
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
await waitFor(() => {
|
|
356
|
+
expect(ctxRef.current).toBeDefined();
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const ctx = ctxRef.current;
|
|
360
|
+
if (!ctx) {
|
|
361
|
+
throw new Error('User context not ready');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await ctx.fetchUsers();
|
|
365
|
+
|
|
366
|
+
await waitFor(() => {
|
|
367
|
+
expect(ctxRef.current?.users).toHaveLength(1);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
});
|