richie-education 2.25.0-b2.dev138 → 2.25.0-b2.dev141

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.
@@ -19,9 +19,8 @@ describe('StepBreadcrumb', () => {
19
19
  it('renders visually a minimal manifest', () => {
20
20
  // If manifest's steps does not have `label` and `icon` property,
21
21
  // only a breadcrumb with the step index is displayed.
22
- type LastStep = 'step1';
23
- type Steps = 'step0' | LastStep;
24
- const manifest: Manifest<Steps, LastStep> = {
22
+ type Steps = 'step0' | 'step1';
23
+ const manifest: Manifest<Steps> = {
25
24
  start: 'step0',
26
25
  steps: {
27
26
  step0: {
@@ -78,9 +77,8 @@ describe('StepBreadcrumb', () => {
78
77
  it('renders visually a complete manifest', () => {
79
78
  // If manifest's steps has `label` and `icon` property,
80
79
  // this information was shown.
81
- type LastStep = 'step1';
82
- type Steps = 'step0' | LastStep;
83
- const manifest: Manifest<Steps, LastStep> = {
80
+ type Steps = 'step0' | 'step1';
81
+ const manifest: Manifest<Steps> = {
84
82
  start: 'step0',
85
83
  steps: {
86
84
  step0: {
@@ -155,9 +153,8 @@ describe('StepBreadcrumb', () => {
155
153
  });
156
154
 
157
155
  it('sorts manifest steps to guarantee the display order of steps', () => {
158
- type LastStep = 'step0';
159
- type Steps = 'step1' | LastStep;
160
- const manifest: Manifest<Steps, LastStep> = {
156
+ type Steps = 'step1' | 'step0';
157
+ const manifest: Manifest<Steps> = {
161
158
  start: 'step1',
162
159
  steps: {
163
160
  step0: {
@@ -189,9 +186,8 @@ describe('StepBreadcrumb', () => {
189
186
  });
190
187
 
191
188
  it('displays all active steps on mount if manifest does not start to the first step', () => {
192
- type LastStep = 'step1';
193
- type Steps = 'step0' | LastStep;
194
- const manifest: Manifest<Steps, LastStep> = {
189
+ type Steps = 'step0' | 'step1';
190
+ const manifest: Manifest<Steps> = {
195
191
  start: 'step1',
196
192
  steps: {
197
193
  step0: {
@@ -7,8 +7,8 @@ import { Nullable } from 'types/utils';
7
7
  import { Icon } from 'components/Icon';
8
8
  import { Manifest, Step, useStepManager } from 'hooks/useStepManager';
9
9
 
10
- interface Props<Keys extends PropertyKey, LastKey extends Keys> {
11
- manifest: Manifest<Keys, LastKey>;
10
+ interface Props<Keys extends PropertyKey> {
11
+ manifest: Manifest<Keys>;
12
12
  step: Nullable<PropertyKey>;
13
13
  }
14
14
 
@@ -33,12 +33,9 @@ function getActiveStepIndex(steps: FlattenStep[], step: Nullable<PropertyKey>) {
33
33
  * // MARK When IE 11 supports will be dropped, we can use a `Map`
34
34
  * to define `manifest.steps` then remove this sort function
35
35
  */
36
- function sortSteps<Keys extends PropertyKey, LastKey extends Keys>(
37
- firstStep: Keys,
38
- manifest: Manifest<Keys, LastKey>,
39
- ) {
36
+ function sortSteps<Keys extends PropertyKey>(firstStep: Keys, manifest: Manifest<Keys>) {
40
37
  const steps: FlattenStep[] = [];
41
- let step = firstStep;
38
+ let step: Nullable<Keys> = firstStep;
42
39
 
43
40
  while (step !== null) {
44
41
  steps.push([step, manifest.steps[step]]);
@@ -57,10 +54,7 @@ const messages = defineMessages({
57
54
  },
58
55
  });
59
56
 
60
- export const StepBreadcrumb = <Keys extends PropertyKey, LastKey extends Keys>({
61
- step,
62
- manifest,
63
- }: Props<Keys, LastKey>) => {
57
+ export const StepBreadcrumb = <Keys extends PropertyKey>({ step, manifest }: Props<Keys>) => {
64
58
  const { firstStep } = useStepManager(manifest);
65
59
  const orderedSteps = sortSteps(firstStep, manifest);
66
60
  const activeIndex = getActiveStepIndex(orderedSteps, step);
@@ -86,7 +86,7 @@ const SaleTunnel = ({
86
86
  productId: product.id,
87
87
  });
88
88
 
89
- const manifest: Manifest<TunnelSteps, 'resume'> = {
89
+ const manifest: Manifest<TunnelSteps> = {
90
90
  start: 'validation',
91
91
  steps: {
92
92
  validation: {
@@ -3,9 +3,8 @@ import { Manifest, useStepManager } from '.';
3
3
 
4
4
  describe('useStepManager', () => {
5
5
  it('reads the manifest', () => {
6
- type LastStep = 'step1';
7
- type Steps = 'step0' | LastStep;
8
- const manifest: Manifest<Steps, LastStep> = {
6
+ type Steps = 'step0' | 'step1';
7
+ const manifest: Manifest<Steps> = {
9
8
  start: 'step0',
10
9
  steps: {
11
10
  step0: {
@@ -35,9 +34,8 @@ describe('useStepManager', () => {
35
34
  });
36
35
 
37
36
  it('is able to reset the step process', () => {
38
- type LastStep = 'step1';
39
- type Steps = 'step0' | LastStep;
40
- const manifest: Manifest<Steps, LastStep> = {
37
+ type Steps = 'step0' | 'step1';
38
+ const manifest: Manifest<Steps> = {
41
39
  start: 'step0',
42
40
  steps: {
43
41
  step0: {
@@ -63,9 +61,8 @@ describe('useStepManager', () => {
63
61
  });
64
62
 
65
63
  it('is able to find the first step through steps if manifest does not have a start property', () => {
66
- type LastStep = 'finally';
67
- type Steps = 'first' | 'then' | LastStep;
68
- const manifest: Manifest<Steps, LastStep> = {
64
+ type Steps = 'first' | 'then' | 'finally';
65
+ const manifest: Manifest<Steps> = {
69
66
  steps: {
70
67
  then: {
71
68
  next: 'finally',
@@ -100,9 +97,8 @@ describe('useStepManager', () => {
100
97
  });
101
98
 
102
99
  it('triggers onEnter and onExit hooks on step transition', () => {
103
- type LastStep = 'step1';
104
- type Steps = 'step0' | LastStep;
105
- const manifest: Manifest<Steps, LastStep> = {
100
+ type Steps = 'step0' | 'step1';
101
+ const manifest: Manifest<Steps> = {
106
102
  start: 'step0',
107
103
  steps: {
108
104
  step0: {
@@ -10,20 +10,10 @@ export interface Step<Keys extends PropertyKey = PropertyKey> {
10
10
  onExit?: Function;
11
11
  }
12
12
 
13
- interface InStep<Keys extends PropertyKey> extends Step<Keys> {
14
- next: Keys;
15
- }
16
-
17
- interface LastStep extends Step {
18
- next: null;
19
- }
20
-
21
- type Steps<Keys extends PropertyKey, LastKey extends Keys> = Record<Keys, Step<Keys>> &
22
- Record<Exclude<Keys, LastKey>, InStep<Keys>> &
23
- Record<LastKey, LastStep>;
13
+ type Steps<Keys extends PropertyKey> = Record<Keys, Step<Keys>>;
24
14
 
25
- export interface Manifest<Keys extends PropertyKey, LastKey extends Keys = Keys> {
26
- steps: Steps<Keys, LastKey>;
15
+ export interface Manifest<Keys extends PropertyKey> {
16
+ steps: Steps<Keys>;
27
17
  start?: Keys;
28
18
  }
29
19
 
@@ -34,9 +24,7 @@ export interface Manifest<Keys extends PropertyKey, LastKey extends Keys = Keys>
34
24
  * @param {Manifest} manifest
35
25
  * @returns {Manifest.step} step
36
26
  */
37
- function findFirstStep<Keys extends PropertyKey, LastKey extends Keys>(
38
- manifest: Manifest<Keys, LastKey>,
39
- ): Keys {
27
+ function findFirstStep<Keys extends PropertyKey>(manifest: Manifest<Keys>): Keys {
40
28
  const steps = Object.keys(manifest.steps) as Keys[];
41
29
  const nextSteps = Object.values<Step<Keys>>(manifest.steps).map(({ next }) => next);
42
30
 
@@ -52,12 +40,10 @@ function findFirstStep<Keys extends PropertyKey, LastKey extends Keys>(
52
40
  *
53
41
  * Process terminates when step is null
54
42
  */
55
- export const useStepManager = <Keys extends PropertyKey, LastKey extends Keys>(
56
- manifest: Manifest<Keys, LastKey>,
57
- ) => {
58
- const firstStep = findFirstStep<Keys, LastKey>(manifest);
43
+ export const useStepManager = <Keys extends PropertyKey>(manifest: Manifest<Keys>) => {
44
+ const firstStep = findFirstStep<Keys>(manifest);
59
45
  const [step, setStep] = useState<Nullable<Keys>>(manifest.start || firstStep);
60
- const state = useMemo(() => (step ? manifest.steps[step] : null), [step]);
46
+ const currentStep = useMemo(() => (step ? manifest.steps[step] : null), [step]);
61
47
 
62
48
  const next = () => {
63
49
  if (step !== null) {
@@ -67,12 +53,12 @@ export const useStepManager = <Keys extends PropertyKey, LastKey extends Keys>(
67
53
  };
68
54
 
69
55
  useEffect(() => {
70
- if (state?.onEnter) state.onEnter();
56
+ if (currentStep?.onEnter) currentStep.onEnter();
71
57
 
72
58
  return () => {
73
- if (state?.onExit) state.onExit();
59
+ if (currentStep?.onExit) currentStep.onExit();
74
60
  };
75
- }, [state]);
61
+ }, [currentStep]);
76
62
 
77
63
  const reset = () => {
78
64
  setStep(manifest.start || firstStep);
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "2.25.0-b2.dev138",
3
+ "version": "2.25.0-b2.dev141",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {
@@ -50,8 +50,8 @@
50
50
  "@hookform/resolvers": "3.3.4",
51
51
  "@openfun/cunningham-react": "2.6.0",
52
52
  "@openfun/cunningham-tokens": "2.1.1",
53
- "@sentry/browser": "7.105.0",
54
- "@sentry/types": "7.105.0",
53
+ "@sentry/browser": "7.106.0",
54
+ "@sentry/types": "7.106.0",
55
55
  "@storybook/addon-actions": "7.6.17",
56
56
  "@storybook/addon-essentials": "7.6.17",
57
57
  "@storybook/addon-interactions": "7.6.17",
@@ -59,10 +59,10 @@
59
59
  "@storybook/react": "7.6.17",
60
60
  "@storybook/react-webpack5": "7.6.17",
61
61
  "@storybook/testing-library": "0.2.2",
62
- "@tanstack/query-core": "5.24.8",
63
- "@tanstack/query-sync-storage-persister": "5.24.8",
64
- "@tanstack/react-query": "5.24.8",
65
- "@tanstack/react-query-persist-client": "5.24.8",
62
+ "@tanstack/query-core": "5.25.0",
63
+ "@tanstack/query-sync-storage-persister": "5.25.0",
64
+ "@tanstack/react-query": "5.25.0",
65
+ "@tanstack/react-query-persist-client": "5.25.0",
66
66
  "@testing-library/dom": "9.3.4",
67
67
  "@testing-library/jest-dom": "6.4.2",
68
68
  "@testing-library/react": "14.2.1",
@@ -74,13 +74,13 @@
74
74
  "@types/lodash-es": "4.17.12",
75
75
  "@types/node-fetch": "2.6.11",
76
76
  "@types/query-string": "6.3.0",
77
- "@types/react": "18.2.61",
77
+ "@types/react": "18.2.64",
78
78
  "@types/react-autosuggest": "10.1.11",
79
- "@types/react-dom": "18.2.19",
79
+ "@types/react-dom": "18.2.21",
80
80
  "@types/react-modal": "3.16.3",
81
81
  "@types/uuid": "9.0.8",
82
- "@typescript-eslint/eslint-plugin": "7.1.0",
83
- "@typescript-eslint/parser": "7.1.0",
82
+ "@typescript-eslint/eslint-plugin": "7.1.1",
83
+ "@typescript-eslint/parser": "7.1.1",
84
84
  "babel-jest": "29.7.0",
85
85
  "babel-loader": "9.1.3",
86
86
  "babel-plugin-react-intl": "8.2.25",
@@ -113,7 +113,7 @@
113
113
  "js-cookie": "3.0.5",
114
114
  "lodash-es": "4.17.21",
115
115
  "mdn-polyfills": "5.20.0",
116
- "msw": "2.2.2",
116
+ "msw": "2.2.3",
117
117
  "node-fetch": ">2.6.6 <3",
118
118
  "nodemon": "3.1.0",
119
119
  "prettier": "3.2.5",
@@ -124,24 +124,24 @@
124
124
  "react-hook-form": "7.51.0",
125
125
  "react-intl": "6.6.2",
126
126
  "react-modal": "3.16.1",
127
- "react-router-dom": "6.22.2",
127
+ "react-router-dom": "6.22.3",
128
128
  "sass": "1.71.1",
129
129
  "source-map-loader": "5.0.0",
130
130
  "storybook": "7.6.17",
131
131
  "tsconfig-paths-webpack-plugin": "4.1.0",
132
- "typescript": "5.3.3",
132
+ "typescript": "5.4.2",
133
133
  "uuid": "9.0.1",
134
134
  "webpack": "5.90.3",
135
135
  "webpack-cli": "5.1.4",
136
136
  "whatwg-fetch": "3.6.20",
137
137
  "xhr-mock": "2.5.1",
138
138
  "yargs": "17.7.2",
139
- "yup": "1.3.3"
139
+ "yup": "1.4.0"
140
140
  },
141
141
  "resolutions": {
142
142
  "@testing-library/dom": "9.3.4",
143
- "@types/react": "18.2.61",
144
- "@types/react-dom": "18.2.19"
143
+ "@types/react": "18.2.64",
144
+ "@types/react-dom": "18.2.21"
145
145
  },
146
146
  "msw": {
147
147
  "workerDirectory": "../richie/static/richie/js"