richie-education 2.25.0-b2.dev155 → 2.25.0-b2.dev160

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.
@@ -1,7 +1,7 @@
1
1
  import { renderHook, act, waitFor } from '@testing-library/react';
2
2
  import fetchMock from 'fetch-mock';
3
3
  import queryString from 'query-string';
4
- import { PaginatedResourceQuery } from 'types/Joanie';
4
+ import { PaginatedResourceQuery, PaginatedResponse } from 'types/Joanie';
5
5
  import { Deferred } from 'utils/test/deferred';
6
6
  import { noop } from 'utils';
7
7
  import { mockPaginatedResponse } from 'utils/test/mockPaginatedResponse';
@@ -281,7 +281,15 @@ describe('useUnionResource', () => {
281
281
  });
282
282
 
283
283
  it('should refetch data when filters change', async () => {
284
- fetchMock.get('http://data.a/?page=1', mockPaginatedResponse([dataAList[0]], 1, false));
284
+ fetchMock.get(
285
+ 'http://data.a/?page=1',
286
+ mockPaginatedResponse([dataAList[0], dataAList[1], dataAList[2]], 7, true),
287
+ );
288
+ fetchMock.get(
289
+ 'http://data.a/?page=2',
290
+ mockPaginatedResponse([dataAList[3], dataAList[4], dataAList[5]], 7, true),
291
+ );
292
+ fetchMock.get('http://data.a/?page=3', mockPaginatedResponse([dataAList[6]], 7, false));
285
293
  fetchMock.get('http://data.b/?page=1', mockPaginatedResponse([], 0, false));
286
294
 
287
295
  const { result, rerender } = renderHook(
@@ -297,9 +305,24 @@ describe('useUnionResource', () => {
297
305
  );
298
306
 
299
307
  await waitFor(() => expect(result.current.isLoading).toBe(false));
308
+ // two page of 3 item have been fetch but only 3 item are displayed.
309
+ act(() => {
310
+ // display one more page for a total of 6 item displayed
311
+ result.current.next();
312
+ });
313
+ act(() => {
314
+ // display one more page for a total of 7 item displayed
315
+ result.current.next();
316
+ });
317
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
318
+ expect(result.current.hasMore).toBe(false);
319
+
300
320
  let calledUrls = fetchMock.calls().map((call) => call[0]);
301
- expect(calledUrls).toHaveLength(2);
321
+ let expectedQueries = 4;
322
+ expect(calledUrls).toHaveLength(expectedQueries);
302
323
  expect(calledUrls).toContain('http://data.a/?page=1');
324
+ expect(calledUrls).toContain('http://data.a/?page=2');
325
+ expect(calledUrls).toContain('http://data.a/?page=3');
303
326
  expect(calledUrls).toContain('http://data.b/?page=1');
304
327
 
305
328
  queryAConfig.filters = { isFiltered: true };
@@ -312,8 +335,10 @@ describe('useUnionResource', () => {
312
335
  await waitFor(() => expect(result.current.isLoading).toBe(false));
313
336
 
314
337
  calledUrls = fetchMock.calls().map((call) => call[0]);
315
- expect(calledUrls).toHaveLength(4);
338
+ expectedQueries += 1; // fetch dataA first page
339
+ expect(calledUrls).toHaveLength(expectedQueries);
316
340
  expect(calledUrls).toContain('http://data.a/?isFiltered=true&page=1');
341
+ expect(result.current.hasMore).toBe(false);
317
342
  });
318
343
 
319
344
  it.each([
@@ -366,4 +391,112 @@ describe('useUnionResource', () => {
366
391
  expect(calledUrls.filter((url) => url === 'http://data.a/?page=1')).toHaveLength(2);
367
392
  expect(calledUrls.filter((url) => url === 'http://data.b/?page=1')).toHaveLength(1);
368
393
  });
394
+
395
+ it('should adapt eof to the total number of result when it change', async () => {
396
+ fetchMock.get(
397
+ 'http://data.a/?page=1',
398
+ mockPaginatedResponse([dataAList[0], dataAList[1], dataAList[2]], 6, true),
399
+ );
400
+ fetchMock.get(
401
+ 'http://data.a/?page=2',
402
+ mockPaginatedResponse([dataAList[3], dataAList[4], dataAList[5]], 6, false),
403
+ );
404
+ fetchMock.get('http://data.b/?page=1', mockPaginatedResponse([], 0, false));
405
+
406
+ const queryClient = createTestQueryClient({ user: true });
407
+ const { result } = renderHook(
408
+ (queries: {
409
+ queryA?: QueryConfig<TestDataA, TestDataAFilters>;
410
+ queryB?: QueryConfig<TestDataB, PaginatedResourceQuery>;
411
+ }) =>
412
+ useUnionResource<TestDataA, TestDataB, TestDataAFilters, PaginatedResourceQuery>({
413
+ queryAConfig: queries?.queryA || queryAConfig,
414
+ queryBConfig: queries?.queryB || queryBConfig,
415
+ }),
416
+ {
417
+ wrapper: ({ children }) => (
418
+ <ReactQueryAppWrapper queryOptions={{ client: queryClient }}>
419
+ {children}
420
+ </ReactQueryAppWrapper>
421
+ ),
422
+ },
423
+ );
424
+
425
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
426
+ // two page of 3 item have been fetch but only 3 item are displayed.
427
+ act(() => {
428
+ // display one more page for a total of 6 item displayed
429
+ result.current.next();
430
+ });
431
+ expect(result.current.hasMore).toBe(false);
432
+
433
+ let calledUrls = fetchMock.calls().map((call) => call[0]);
434
+ expect(calledUrls).toHaveLength(3);
435
+ expect(calledUrls).toContain('http://data.a/?page=1');
436
+ expect(calledUrls).toContain('http://data.a/?page=2');
437
+ expect(calledUrls).toContain('http://data.b/?page=1');
438
+
439
+ // simulate backend adding items, totalResults move from 6 to 7
440
+ // it means that we should request one more page.
441
+ fetchMock.restore();
442
+ fetchMock.get(
443
+ 'http://data.a/?page=1',
444
+ mockPaginatedResponse([dataAList[0], dataAList[1], dataAList[2]], 7, true),
445
+ );
446
+ fetchMock.get(
447
+ 'http://data.a/?page=2',
448
+ mockPaginatedResponse([dataAList[3], dataAList[4], dataAList[5]], 7, true),
449
+ );
450
+ fetchMock.get('http://data.a/?page=3', mockPaginatedResponse([dataAList[6]], 7, false));
451
+ fetchMock.get('http://data.b/?page=1', mockPaginatedResponse([], 0, false));
452
+
453
+ // simulate cache expire
454
+ queryClient.invalidateQueries({ queryKey: ['resourceA'] });
455
+ queryClient.invalidateQueries({ queryKey: ['resourceB'] });
456
+
457
+ await waitFor(() => {
458
+ expect(
459
+ queryClient.getQueryState<PaginatedResponse<TestDataA>>(['resourceA']),
460
+ ).toBeUndefined();
461
+ expect(
462
+ queryClient.getQueryState<PaginatedResponse<TestDataB>>(['resourceB']),
463
+ ).toBeUndefined();
464
+ });
465
+
466
+ await waitFor(() => {
467
+ expect(result.current.isLoading).toBe(false);
468
+ expect(result.current.data).toHaveLength(3);
469
+ });
470
+
471
+ // two page of 3 item have been fetch but only 3 item are displayed.
472
+ act(() => {
473
+ // display one more page for a total of 6 item displayed
474
+ result.current.next();
475
+ });
476
+ act(() => {
477
+ expect(result.current.isLoading).toBe(false);
478
+ expect(result.current.data).toHaveLength(6);
479
+ });
480
+
481
+ act(() => {
482
+ // display one more page for a total of 7 item displayed
483
+ result.current.next();
484
+ });
485
+ await waitFor(() => {
486
+ expect(result.current.isLoading).toBe(false);
487
+ expect(result.current.data).toHaveLength(7);
488
+ });
489
+
490
+ await waitFor(() => {
491
+ expect(result.current.hasMore).toBe(false);
492
+ });
493
+
494
+ calledUrls = fetchMock.calls().map((call) => call[0]);
495
+ expect(calledUrls).toHaveLength(4);
496
+
497
+ expect(calledUrls).toContain('http://data.a/?page=1');
498
+ expect(calledUrls).toContain('http://data.a/?page=2');
499
+ expect(calledUrls).toContain('http://data.a/?page=3');
500
+ expect(calledUrls).toContain('http://data.b/?page=1');
501
+ });
369
502
  });
@@ -94,7 +94,11 @@ const useUnionResource = <
94
94
 
95
95
  // to force execution of useEffect::fetchNewPage(),
96
96
  // reset need to generate a uniq key that is part of it's dependencies.
97
- const reset = () => {
97
+ const reset = (eofKey?: string) => {
98
+ if (eofKey) {
99
+ delete eofRef.current[eofKey];
100
+ }
101
+
98
102
  setStack([]);
99
103
  setPage(0);
100
104
  setTotalCount(undefined);
@@ -107,8 +111,12 @@ const useUnionResource = <
107
111
  // we manualy observe key invalidation to trigger new search
108
112
  // by generating a new update key
109
113
  if (refetchOnInvalidation) {
110
- useQueryKeyInvalidateListener(queryAConfig.queryKey, reset);
111
- useQueryKeyInvalidateListener(queryBConfig.queryKey, reset);
114
+ useQueryKeyInvalidateListener(queryAConfig.queryKey, () => {
115
+ reset(queryAConfig.queryKey.join('-'));
116
+ });
117
+ useQueryKeyInvalidateListener(queryBConfig.queryKey, () => {
118
+ reset(queryBConfig.queryKey.join('-'));
119
+ });
112
120
  }
113
121
 
114
122
  // filters have changes, new results will be fetch.
@@ -65,14 +65,11 @@ export const fetchEntity = async <
65
65
  try {
66
66
  const res = await fn(filters);
67
67
  queryClient.setQueryData(QUERY_KEY, res);
68
-
69
- // If we reached the end of the list, we set the eof flag to prevent future requests.
70
- if (!res.next) {
71
- eofRef.current = { ...eofRef.current, [queryKeyString]: filters.page! };
72
- // Eof is cached based, the same way, we cache the fetching data. Otherwise there would
73
- // be request to non existing pages after reload.
74
- queryClient.setQueryData(eofQueryKey, eofRef.current);
75
- }
68
+ // Eof is cached based, the same way, we cache the fetching data. Otherwise there would
69
+ // be request to non existing pages after reload.
70
+ const totalPages = Math.ceil(res.count / perPage);
71
+ eofRef.current = { ...eofRef.current, [queryKeyString]: totalPages };
72
+ queryClient.setQueryData(eofQueryKey, eofRef.current);
76
73
  return res;
77
74
  } catch (err) {
78
75
  if (isHttpError(err)) {
@@ -60,7 +60,7 @@ export const DashboardBreadcrumbs = () => {
60
60
  <RouterButton
61
61
  href={backPath}
62
62
  size="nano"
63
- color="tertiary"
63
+ color="tertiary-text"
64
64
  icon={<span className="material-icons">chevron_left</span>}
65
65
  >
66
66
  <FormattedMessage {...messages.back} />
@@ -69,7 +69,7 @@ export const DashboardBreadcrumbs = () => {
69
69
 
70
70
  {breadcrumbs.map((breadcrumb) => (
71
71
  <li key={breadcrumb.pathname}>
72
- <RouterButton href={breadcrumb.pathname} size="nano" color="tertiary">
72
+ <RouterButton href={breadcrumb.pathname} size="nano" color="tertiary-text">
73
73
  {breadcrumb.name}
74
74
  </RouterButton>
75
75
  </li>
@@ -59,7 +59,7 @@ describe('<DashboardItemOrder/> Contract', () => {
59
59
  // overwrite useOmniscientOrders call
60
60
  fetchMock.get(
61
61
  'https://joanie.endpoint/api/v1.0/orders/',
62
- { results: [order], next: null, previous: null, count: null },
62
+ { results: [order], next: null, previous: null, count: 1 },
63
63
  { overwriteRoutes: true },
64
64
  );
65
65
 
@@ -68,12 +68,12 @@ describe('<DashboardItemOrder/> Contract', () => {
68
68
  results: [],
69
69
  next: null,
70
70
  previous: null,
71
- count: null,
71
+ count: 0,
72
72
  });
73
73
 
74
74
  fetchMock.get(
75
75
  'https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=50',
76
- { results: [order], next: null, previous: null, count: null },
76
+ { results: [order], next: null, previous: null, count: 1 },
77
77
  );
78
78
 
79
79
  const submitDeferred = new Deferred();
@@ -238,7 +238,7 @@ describe('<DashboardItemOrder/> Contract', () => {
238
238
  results: [signedOrder],
239
239
  next: null,
240
240
  previous: null,
241
- count: null,
241
+ count: 1,
242
242
  });
243
243
 
244
244
  // Contract signing is removed.
@@ -249,7 +249,7 @@ describe('<DashboardItemOrder/> Contract', () => {
249
249
  // Go back to the list view to make sure the sign button is not shown anymore.
250
250
  fetchMock.get(
251
251
  'https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=50',
252
- { results: [signedOrder], next: null, previous: null, count: null },
252
+ { results: [signedOrder], next: null, previous: null, count: 1 },
253
253
  { overwriteRoutes: true },
254
254
  );
255
255
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "2.25.0-b2.dev155",
3
+ "version": "2.25.0-b2.dev160",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {