richie-education 2.25.0-b2.dev136 → 2.25.0-b2.dev139

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.
@@ -8,6 +8,7 @@ import { mockPaginatedResponse } from 'utils/test/mockPaginatedResponse';
8
8
  import { PER_PAGE } from 'settings';
9
9
  import { HttpError, HttpStatusCode } from 'utils/errors/HttpError';
10
10
  import { BaseAppWrapper } from 'utils/test/wrappers/BaseAppWrapper';
11
+ import { createTestQueryClient } from 'utils/test/createTestQueryClient';
11
12
  import { QueryConfig, FetchDataFunction } from './utils/fetchEntity';
12
13
  import useUnionResource from '.';
13
14
 
@@ -314,4 +315,53 @@ describe('useUnionResource', () => {
314
315
  expect(calledUrls).toHaveLength(4);
315
316
  expect(calledUrls).toContain('http://data.a/?isFiltered=true&page=1');
316
317
  });
318
+
319
+ it.each([
320
+ {
321
+ testLabel: 'with some results',
322
+ testDataAList: [{ name: 'TestDataA', id: '1', created_on: '2022-01-01' }],
323
+ },
324
+ {
325
+ testLabel: 'without results',
326
+ testDataAList: [],
327
+ },
328
+ ])('should refetch data when a query $testLabel is invalidate', async ({ testDataAList }) => {
329
+ fetchMock.get(
330
+ 'http://data.a/?page=1',
331
+ mockPaginatedResponse(testDataAList, testDataAList.length, false),
332
+ );
333
+ fetchMock.get('http://data.b/?page=1', mockPaginatedResponse([], 0, false));
334
+
335
+ const queryClient = createTestQueryClient({ user: true });
336
+ const { result, rerender } = renderHook(
337
+ (queries: {
338
+ queryA?: QueryConfig<TestDataA, TestDataAFilters>;
339
+ queryB?: QueryConfig<TestDataB, PaginatedResourceQuery>;
340
+ }) =>
341
+ useUnionResource<TestDataA, TestDataB, TestDataAFilters, PaginatedResourceQuery>({
342
+ queryAConfig: queries?.queryA || queryAConfig,
343
+ queryBConfig: queries?.queryB || queryBConfig,
344
+ }),
345
+ {
346
+ wrapper: ({ children }) => (
347
+ <BaseAppWrapper queryOptions={{ client: queryClient }}>{children}</BaseAppWrapper>
348
+ ),
349
+ },
350
+ );
351
+
352
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
353
+ let calledUrls = fetchMock.calls().map((call) => call[0]);
354
+ expect(calledUrls).toHaveLength(2);
355
+ expect(calledUrls).toContain('http://data.a/?page=1');
356
+ expect(calledUrls).toContain('http://data.b/?page=1');
357
+
358
+ queryClient.invalidateQueries({ queryKey: queryAConfig.queryKey });
359
+ rerender({ queryA: { ...queryAConfig }, queryB: queryBConfig });
360
+ await waitFor(() => {
361
+ calledUrls = fetchMock.calls().map((call) => call[0]);
362
+ expect(calledUrls).toHaveLength(3);
363
+ });
364
+ expect(calledUrls.filter((url) => url === 'http://data.a/?page=1')).toHaveLength(2);
365
+ expect(calledUrls.filter((url) => url === 'http://data.b/?page=1')).toHaveLength(1);
366
+ });
317
367
  });
@@ -90,8 +90,10 @@ const useUnionResource = <
90
90
  const eofRef = useRef<Record<string, number>>(queryClient.getQueryData(eofQueryKey) ?? {});
91
91
  log('eof', eofRef.current);
92
92
 
93
- const [unionQueryKey, setUnionQueryKey] = useState<string>();
93
+ const [update, setForceUpdate] = useState<number>();
94
94
 
95
+ // to force execution of useEffect::fetchNewPage(),
96
+ // reset need to generate a uniq key that is part of it's dependencies.
95
97
  const reset = () => {
96
98
  setStack([]);
97
99
  setPage(0);
@@ -99,23 +101,21 @@ const useUnionResource = <
99
101
  setError(undefined);
100
102
  setCursor(perPage);
101
103
  setIntegrityCount(0);
104
+ setForceUpdate(new Date().getTime());
102
105
  };
103
106
 
107
+ // we manualy observe key invalidation to trigger new search
108
+ // by generating a new update key
104
109
  if (refetchOnInvalidation) {
105
110
  useQueryKeyInvalidateListener(queryAConfig.queryKey, reset);
106
111
  useQueryKeyInvalidateListener(queryBConfig.queryKey, reset);
107
112
  }
108
113
 
114
+ // filters have changes, new results will be fetch.
115
+ // let's reset every previous fetches states
116
+ // and re-generate update
109
117
  useEffect(() => {
110
- // filters have changes, new results will be fetch.
111
- // let's reset every previous fetches states
112
118
  reset();
113
-
114
- // We need to fetch new results.
115
- // reset all states isn't enought. If we've a research without results
116
- // then the next reset would do nothing.
117
- // to force execution of useEffect::fetchNewPage(), we use a uniq key build with current queries filters.
118
- setUnionQueryKey(JSON.stringify(queryAConfig.filters) + JSON.stringify(queryBConfig.filters));
119
119
  }, [JSON.stringify(queryAConfig.filters), JSON.stringify(queryBConfig.filters)]);
120
120
 
121
121
  useEffect(() => {
@@ -165,11 +165,8 @@ const useUnionResource = <
165
165
  }
166
166
  }, [
167
167
  cursor,
168
- // FIXME(rlecellier): when stack.length === 0, invalidate the query will not refetch.
169
- // stack.length is added in the dependency array to force a new fetch on reset.
170
- stack.length,
171
- // unionQueryKey assure that we refetch data when query filters change.
172
- unionQueryKey,
168
+ // update assure that we refetch data on reset
169
+ update,
173
170
  ]);
174
171
 
175
172
  const cursorToUse = Math.min(cursor, integrityCount);
@@ -123,7 +123,7 @@ describe('<DashboardCourses/>', () => {
123
123
  enrollmentsDeferred.resolve({ results: [], next: null, previous: null, count: 0 });
124
124
 
125
125
  await expectNoSpinner('Loading orders and enrollments...');
126
- expectBannerInfo('You have no enrollments nor orders yet.');
126
+ await expectBannerInfo('You have no enrollments nor orders yet.');
127
127
  expect(screen.queryByRole('button', { name: 'Load more' })).not.toBeInTheDocument();
128
128
  });
129
129
 
@@ -1,34 +1,34 @@
1
1
  import { screen, waitFor } from '@testing-library/react';
2
2
  import { BannerType, getBannerTestId } from 'components/Banner';
3
3
 
4
- export const expectBannerError = async (message: string, rootElement: ParentNode = document) => {
4
+ export const expectBannerError = (message: string, rootElement: ParentNode = document) => {
5
5
  return expectBanner(BannerType.ERROR, message, rootElement);
6
6
  };
7
- export const expectBannerInfo = async (message: string, rootElement: ParentNode = document) => {
7
+ export const expectBannerInfo = (message: string, rootElement: ParentNode = document) => {
8
8
  return expectBanner(BannerType.INFO, message, rootElement);
9
9
  };
10
10
 
11
- export const expectBanner = async (
11
+ export const expectBanner = (
12
12
  type: BannerType,
13
13
  message: string,
14
14
  rootElement: ParentNode = document,
15
15
  ) => {
16
- await waitFor(async () => {
16
+ return waitFor(async () => {
17
17
  const banner = rootElement.querySelector('.banner--' + type) as HTMLElement;
18
18
  expect(banner).not.toBeNull();
19
19
  expect(banner).toHaveTextContent(message);
20
20
  });
21
21
  };
22
22
 
23
- export const expectNoBannerError = async (message: string) => {
23
+ export const expectNoBannerError = (message: string) => {
24
24
  return expectNoBanner(BannerType.ERROR, message);
25
25
  };
26
- export const expectNoBannerInfo = async (message: string) => {
26
+ export const expectNoBannerInfo = (message: string) => {
27
27
  return expectNoBanner(BannerType.INFO, message);
28
28
  };
29
29
 
30
- export const expectNoBanner = async (type: BannerType, message: string) => {
31
- await waitFor(() => {
30
+ export const expectNoBanner = (type: BannerType, message: string) => {
31
+ return waitFor(() => {
32
32
  expect(screen.queryByTestId(getBannerTestId(message, type))).toBeNull();
33
33
  });
34
34
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "2.25.0-b2.dev136",
3
+ "version": "2.25.0-b2.dev139",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {