@coorpacademy/app-review 0.1.4 → 0.1.7

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.
Files changed (103) hide show
  1. package/es/actions/data/progression.d.ts +30 -0
  2. package/es/actions/data/progression.js +12 -0
  3. package/es/actions/data/slides.d.ts +19 -15
  4. package/es/actions/data/slides.js +5 -0
  5. package/es/actions/data/token.d.ts +2 -2
  6. package/es/actions/data/token.js +1 -1
  7. package/es/actions/index.d.ts +2 -1
  8. package/es/configure-store.d.ts +2 -10
  9. package/es/configure-store.js +2 -1
  10. package/es/index.d.ts +1 -1
  11. package/es/index.js +43 -26
  12. package/es/index.test.js +5 -5
  13. package/es/middlewares/on-receive-progression.d.ts +6 -0
  14. package/es/middlewares/on-receive-progression.js +22 -0
  15. package/es/middlewares/on-receive-progression.test.d.ts +1 -0
  16. package/es/middlewares/on-receive-progression.test.js +43 -0
  17. package/es/middlewares/on-start-app.js +13 -5
  18. package/es/middlewares/on-start-app.test.d.ts +1 -0
  19. package/es/middlewares/on-start-app.test.js +53 -0
  20. package/es/reducers/data/index.d.ts +1 -1
  21. package/es/reducers/data/progression.d.ts +14 -8
  22. package/es/reducers/data/progression.js +17 -54
  23. package/es/reducers/data/slides.js +11 -6
  24. package/es/reducers/index.d.ts +1 -1
  25. package/es/services/fetch-skills.js +1 -2
  26. package/es/services/fetch-slide.d.ts +8 -0
  27. package/es/services/fetch-slide.js +26 -0
  28. package/es/services/post-progression.d.ts +8 -0
  29. package/es/services/post-progression.js +33 -0
  30. package/es/services/post-progression.mock.d.ts +5 -0
  31. package/es/services/post-progression.mock.js +31 -0
  32. package/es/services/post-progression.test.d.ts +1 -0
  33. package/es/services/post-progression.test.js +30 -0
  34. package/es/services/tools/fetch-responses.d.ts +1 -1
  35. package/es/services/tools/sleep.d.ts +1 -0
  36. package/es/services/tools/sleep.js +3 -0
  37. package/es/types/common.d.ts +8 -2
  38. package/es/types/ui-progression.d.ts +10 -0
  39. package/es/types/ui-progression.js +1 -0
  40. package/lib/actions/data/progression.d.ts +30 -0
  41. package/lib/actions/data/progression.js +12 -0
  42. package/lib/actions/data/slides.d.ts +19 -15
  43. package/lib/actions/data/slides.js +5 -0
  44. package/lib/actions/data/token.d.ts +2 -2
  45. package/lib/actions/data/token.js +1 -1
  46. package/lib/actions/index.d.ts +2 -1
  47. package/lib/configure-store.d.ts +2 -10
  48. package/lib/configure-store.js +2 -1
  49. package/lib/index.d.ts +1 -1
  50. package/lib/index.js +43 -26
  51. package/lib/index.test.js +5 -7
  52. package/lib/middlewares/on-receive-progression.d.ts +6 -0
  53. package/lib/middlewares/on-receive-progression.js +27 -0
  54. package/lib/middlewares/on-receive-progression.test.d.ts +1 -0
  55. package/lib/middlewares/on-receive-progression.test.js +88 -0
  56. package/lib/middlewares/on-start-app.js +15 -7
  57. package/lib/middlewares/on-start-app.test.d.ts +1 -0
  58. package/lib/middlewares/on-start-app.test.js +98 -0
  59. package/lib/reducers/data/index.d.ts +1 -1
  60. package/lib/reducers/data/progression.d.ts +14 -8
  61. package/lib/reducers/data/progression.js +17 -54
  62. package/lib/reducers/data/slides.js +11 -6
  63. package/lib/reducers/index.d.ts +1 -1
  64. package/lib/services/fetch-skills.js +2 -5
  65. package/lib/services/fetch-slide.d.ts +8 -0
  66. package/lib/services/fetch-slide.js +64 -0
  67. package/lib/services/post-progression.d.ts +8 -0
  68. package/lib/services/post-progression.js +77 -0
  69. package/lib/services/post-progression.mock.d.ts +5 -0
  70. package/lib/services/post-progression.mock.js +31 -0
  71. package/lib/services/post-progression.test.d.ts +1 -0
  72. package/lib/services/post-progression.test.js +84 -0
  73. package/lib/services/tools/fetch-responses.d.ts +1 -1
  74. package/lib/services/tools/sleep.d.ts +1 -0
  75. package/lib/services/tools/sleep.js +5 -0
  76. package/lib/types/common.d.ts +8 -2
  77. package/lib/types/ui-progression.d.ts +10 -0
  78. package/lib/types/ui-progression.js +1 -0
  79. package/package.json +4 -5
  80. package/readme.md +4 -5
  81. package/src/actions/data/progression.ts +50 -0
  82. package/src/actions/data/slides.ts +25 -17
  83. package/src/actions/data/token.ts +2 -2
  84. package/src/actions/index.ts +4 -1
  85. package/src/configure-store.ts +5 -13
  86. package/src/index.test.tsx +5 -7
  87. package/src/index.tsx +54 -29
  88. package/src/middlewares/on-receive-progression.test.ts +60 -0
  89. package/src/middlewares/on-receive-progression.ts +42 -0
  90. package/src/middlewares/on-start-app.test.ts +68 -0
  91. package/src/middlewares/on-start-app.ts +13 -5
  92. package/src/reducers/data/progression.ts +34 -69
  93. package/src/reducers/data/slides.ts +13 -8
  94. package/src/services/fetch-skills.ts +1 -2
  95. package/src/services/fetch-slide.ts +43 -0
  96. package/src/services/post-progression.mock.ts +35 -0
  97. package/src/services/post-progression.test.ts +41 -0
  98. package/src/services/post-progression.ts +50 -0
  99. package/src/services/tools/fetch-responses.ts +1 -1
  100. package/src/services/tools/sleep.ts +4 -0
  101. package/src/types/common.ts +8 -2
  102. package/src/types/globals.d.ts +1 -0
  103. package/src/types/ui-progression.ts +25 -0
@@ -0,0 +1,42 @@
1
+ import type {Middleware, Store} from 'redux';
2
+ import {Dispatch} from 'redux';
3
+ import get from 'lodash/fp/get';
4
+
5
+ // eslint-disable-next-line import/no-named-as-default
6
+ import fetchSlide from '../services/fetch-slide';
7
+
8
+ import {
9
+ ErrorFetchingSlide,
10
+ errorFetchingSlide,
11
+ StoreFirstSlide,
12
+ storeFirstSlide
13
+ } from '../actions/data/slides';
14
+ import {ReceiveProgression, RECEIVE_PROGRESSION} from '../actions/data/progression';
15
+
16
+ const onReceiveProgression: Middleware<
17
+ {},
18
+ Store,
19
+ Dispatch<ReceiveProgression | StoreFirstSlide | ErrorFetchingSlide>
20
+ > =
21
+ ({dispatch}) =>
22
+ next =>
23
+ (action: ReceiveProgression) => {
24
+ if (action.type === RECEIVE_PROGRESSION) {
25
+ const {progression, token} = action.payload;
26
+ const slideRef: string = get('state.nextContent.ref', progression);
27
+ // TOASK: do we need to check for exit nodes before fetching??
28
+
29
+ // TODO: To implement fetchSlide in next ticket
30
+ fetchSlide(slideRef, token)
31
+ .then(slide => {
32
+ return dispatch(storeFirstSlide(slide));
33
+ })
34
+ .catch(err => {
35
+ return dispatch(errorFetchingSlide(err));
36
+ });
37
+ }
38
+
39
+ return next(action);
40
+ };
41
+
42
+ export default onReceiveProgression;
@@ -0,0 +1,68 @@
1
+ import test from 'ava';
2
+ import {AnyAction, Dispatch, MiddlewareAPI} from 'redux';
3
+ import {STORE_TOKEN} from '../actions/data/token';
4
+ import {RECEIVE_PROGRESSION} from '../actions/data/progression';
5
+ import {Action} from '../actions';
6
+ import {AppOptions} from '../types/common';
7
+ import {NAVIGATE_TO, startApp} from '../actions/ui/navigation';
8
+ import {mockedProgression} from '../services/post-progression.mock';
9
+ import {VIEWS} from '../common';
10
+ import {sleep} from '../services/tools/sleep';
11
+ import onStartApp from './on-start-app';
12
+
13
+ const appOptions: AppOptions = {
14
+ token: process.env.API_TEST_TOKEN || '',
15
+ skillRef: 'skill_NJC0jFKoH'
16
+ };
17
+
18
+ const otherAction = (
19
+ payload: string
20
+ ): {
21
+ type: string;
22
+ payload: string;
23
+ } => ({
24
+ type: '@@other/OTHER',
25
+ payload
26
+ });
27
+
28
+ test('should not dispatch anything if action is not START_APP', t => {
29
+ t.plan(1);
30
+ const dispatch = (): void => {
31
+ t.fail();
32
+ };
33
+ const next = (): boolean => {
34
+ return t.pass();
35
+ };
36
+
37
+ onStartApp({dispatch} as unknown as MiddlewareAPI)(next as Dispatch<AnyAction>)(
38
+ otherAction('other')
39
+ );
40
+ });
41
+
42
+ test('should dispatch actions: STORE_TOKEN && RECEIVE_PROGRESSION if an skillRef is given, on a successful response', async t => {
43
+ t.plan(5);
44
+ const dispatch = (action: Action): void => {
45
+ switch (action.type) {
46
+ case STORE_TOKEN:
47
+ t.is(action.payload, appOptions.token);
48
+ return;
49
+ case RECEIVE_PROGRESSION: {
50
+ const {token, progression} = action.payload;
51
+ t.is(token, appOptions.token);
52
+ t.deepEqual(progression, JSON.parse(JSON.stringify(mockedProgression)));
53
+ return;
54
+ }
55
+ case NAVIGATE_TO:
56
+ t.is(action.payload, VIEWS.slides);
57
+ return;
58
+ default:
59
+ t.fail();
60
+ break;
61
+ }
62
+ };
63
+ const next = (): boolean => {
64
+ return t.pass();
65
+ };
66
+ onStartApp({dispatch} as MiddlewareAPI)(next as Dispatch<AnyAction>)(startApp(appOptions));
67
+ await sleep(1000); // wait for the .then to fire
68
+ });
@@ -5,23 +5,31 @@ import {VIEWS} from '../common';
5
5
  import {Action} from '../actions';
6
6
  import {storeToken} from '../actions/data/token';
7
7
  import {receivedSkills, errorFetchingSkills} from '../actions/data/skills';
8
- import {storeFirstSlide} from '../actions/data/slides';
9
8
  import {navigateTo, START_APP, ViewPath} from '../actions/ui/navigation';
10
9
 
11
10
  // eslint-disable-next-line import/no-named-as-default
12
11
  import fetchSkills from '../services/fetch-skills';
12
+ // eslint-disable-next-line import/no-named-as-default
13
+ import postProgression from '../services/post-progression';
14
+ import {errorCreatingProgression, receiveProgression} from '../actions/data/progression';
13
15
 
14
16
  const onStartApp: Middleware<{}, Store, Dispatch<Action>> =
15
17
  ({dispatch}) =>
16
18
  next =>
17
19
  (action: Action) => {
18
20
  if (action.type === START_APP) {
19
- const {slide, token} = action.payload;
21
+ const {skillRef, token} = action.payload;
20
22
 
21
23
  dispatch(storeToken(token));
22
24
 
23
- if (slide) {
24
- dispatch(storeFirstSlide(slide));
25
+ if (skillRef) {
26
+ postProgression(skillRef, token)
27
+ .then(progression => {
28
+ return dispatch(receiveProgression({progression, token}));
29
+ })
30
+ .catch(err => {
31
+ return dispatch(errorCreatingProgression(err));
32
+ });
25
33
  } else {
26
34
  fetchSkills(token)
27
35
  .then(skills => {
@@ -32,7 +40,7 @@ const onStartApp: Middleware<{}, Store, Dispatch<Action>> =
32
40
  });
33
41
  }
34
42
 
35
- const initialView: ViewPath = slide ? VIEWS.slides : VIEWS.skills;
43
+ const initialView: ViewPath = skillRef ? VIEWS.slides : VIEWS.skills;
36
44
  dispatch(navigateTo(initialView));
37
45
  }
38
46
 
@@ -1,24 +1,27 @@
1
- import {getOr, noop} from 'lodash/fp';
1
+ import {getOr} from 'lodash/fp';
2
+ import {ReceiveProgression, RECEIVE_PROGRESSION} from '../../actions/data/progression';
2
3
  import {ValidateSlide, VALIDATE_SLIDE} from '../../actions/data/slides';
3
- import {AnswerUI} from '../../types/slides';
4
4
 
5
5
  // -----------------------------------------------------------------------------
6
6
 
7
+ // TODO: transform this into UI data / fix on components' repo when
8
+ // fetch slide is addressed
7
9
  export type ProgressionState = {
8
- // virtual field
9
- slideNumber: number;
10
- isCorrect?: boolean;
11
- // pass down the successExitNode if all slides are finished, not before
12
- exitNode?: 'successExitNode' | 'failExitNode';
13
- // TODO: create another action, to retrieve the next slide
14
- // nextSlide should be renamed as nextContent when this new action is created
15
- // ex: nextContent: {ref: "sli_E1n2i2sBo", type: "slide"}
16
- // the middleware should trigger a nextSlide request once the result is
17
- // received && update the state accordingly
18
- nextSlide?: {
19
- questionText?: string;
20
- answerUI?: AnswerUI;
10
+ _id?: string;
11
+ state: {
12
+ isCorrect: boolean;
13
+ nextContent: {
14
+ ref: 'successExitNode' | string;
15
+ type: 'success' | 'slide';
16
+ };
17
+ pendingSlides: string[];
18
+ step: {
19
+ current: number;
20
+ };
21
21
  };
22
+ // TO refactor: (slideNumber)
23
+ // virtual field
24
+ slideNumber?: number;
22
25
  } | null;
23
26
 
24
27
  // -----------------------------------------------------------------------------
@@ -30,72 +33,34 @@ export const initialState: ProgressionState = null;
30
33
  const reducer = (
31
34
  // eslint-disable-next-line default-param-last
32
35
  state: ProgressionState = initialState,
33
- action: ValidateSlide
36
+ action: ValidateSlide | ReceiveProgression
34
37
  ): ProgressionState => {
35
38
  switch (action.type) {
36
39
  case VALIDATE_SLIDE: {
37
40
  const previousSlideNumber: number = getOr(-1, 'slideNumber', state);
38
41
  // hard coded for now
42
+ // TODO: fix on VALIDATE ticket
39
43
  return {
44
+ _id: '1234',
40
45
  // virtual field
41
46
  slideNumber: previousSlideNumber + 1,
42
- isCorrect: true,
43
- // exitNode
44
- // TODO: create another action, to retrieve the next slide
45
- // nextSlide should be corrected to nextContent, when this new action is created
46
- // ex: nextContent: {ref: "sli_E1n2i2sBo", type: "slide"}
47
- // the middleware should trigger a next content request once the result is
48
- // received && update the state accordingly,
49
- //
50
- nextSlide: {
51
- questionText: 'Test',
52
- answerUI: {
53
- model: {
54
- answers: [
55
- {
56
- title: 'There is no need for a password',
57
- // TODO: onClick of each answer should use a dispatcher to
58
- // update selected answers (in order to use them on validation)
59
- // in each retrieval, the answers of answerUI need to be augmented
60
- // for the onClick prop to achieve this.
61
- onClick: noop,
62
- selected: false,
63
- help: 'help'
64
- },
65
- {
66
- title: 'Lorem ipsum',
67
- onClick: noop,
68
- selected: false,
69
- help: 'help'
70
- },
71
- {
72
- title: 'Lorem',
73
- onClick: noop,
74
- selected: true,
75
- order: 1,
76
- help: 'help'
77
- },
78
- {
79
- title: 'You need to have a password',
80
- onClick: noop,
81
- selected: false,
82
- help: 'help'
83
- },
84
- {
85
- title: 'Pouet',
86
- onClick: noop,
87
- selected: true,
88
- order: 0,
89
- help: 'help'
90
- }
91
- ],
92
- type: 'qcmDrag'
93
- },
94
- help: 'Help text will appear here'
47
+ state: {
48
+ pendingSlides: [],
49
+ step: {
50
+ current: Number.NaN
51
+ },
52
+ isCorrect: true,
53
+ nextContent: {
54
+ ref: 'sli_V1gKpYYZ2',
55
+ type: 'slide'
95
56
  }
96
57
  }
97
58
  };
98
59
  }
60
+ case RECEIVE_PROGRESSION: {
61
+ const {progression} = action.payload;
62
+ return progression;
63
+ }
99
64
  default:
100
65
  return state;
101
66
  }
@@ -8,8 +8,9 @@ import {
8
8
  UPDATE_SLIDES_ON_VALIDATION,
9
9
  ValidationPayload
10
10
  } from '../../actions/data/slides';
11
- import {Slide as SlideFromAPI} from '../../types/slides';
11
+ import {Slide as UISlide} from '../../types/slides';
12
12
  import {HIGHEST_INDEX, slideNumbers, TOTAL_SLIDES_STACK} from '../../common';
13
+ import {slide as qcmSlide} from '../../fixtures/qcm-slide';
13
14
 
14
15
  // -----------------------------------------------------------------------------
15
16
 
@@ -35,7 +36,7 @@ export const initialState: SlidesState = getInitialState();
35
36
 
36
37
  // -----------------------------------------------------------------------------
37
38
 
38
- const setFirstSlide = (state: SlidesState, slide: SlideFromAPI): SlidesState => {
39
+ const setFirstSlide = (state: SlidesState, slide: UISlide): SlidesState => {
39
40
  const _state: SlidesState = {
40
41
  slideNumbers
41
42
  };
@@ -53,7 +54,7 @@ const setFirstSlide = (state: SlidesState, slide: SlideFromAPI): SlidesState =>
53
54
  // ||-------> Handles the updates of a given slide on validation,
54
55
  // & then updates de remaining slides if this given change should impact their content
55
56
  const stateUpdateOnValidation = (state: SlidesState, payload: ValidationPayload): SlidesState => {
56
- const {slideNumber, newSlideContent, numberOfFinishedSlides, nextSlide} = payload;
57
+ const {slideNumber, newSlideContent, numberOfFinishedSlides, nextContent} = payload;
57
58
  const _state: SlidesState = {
58
59
  slideNumbers
59
60
  };
@@ -67,7 +68,7 @@ const stateUpdateOnValidation = (state: SlidesState, payload: ValidationPayload)
67
68
  if (numberOfFinishedSlides === HIGHEST_INDEX) {
68
69
  _state[index] = {
69
70
  ...previousValue,
70
- nextSlide,
71
+ nextContent,
71
72
  position: newSlideContent.position,
72
73
  isCorrect: newSlideContent.isCorrect
73
74
  };
@@ -75,7 +76,7 @@ const stateUpdateOnValidation = (state: SlidesState, payload: ValidationPayload)
75
76
  } else {
76
77
  // updates the rest of the slides here
77
78
  const {hidden, position} = previousValue;
78
- _state[index] = {...nextSlide, endReview: newSlideContent.endReview, hidden, position};
79
+ _state[index] = {...nextContent, endReview: newSlideContent.endReview, hidden, position};
79
80
  }
80
81
  }, state.slideNumbers);
81
82
 
@@ -96,8 +97,8 @@ const stateUpdateOnNext = (state: SlidesState, payload: OnNextPayload): SlidesSt
96
97
  // the current slide
97
98
  if (index === slideNumber) {
98
99
  // when there is only one slide remaining, updates the slide value
99
- if (numberOfFinishedSlides === HIGHEST_INDEX && previousValue.nextSlide) {
100
- _state[index] = {...previousValue.nextSlide, position: previousValue.position};
100
+ if (numberOfFinishedSlides === HIGHEST_INDEX && previousValue.nextContent) {
101
+ _state[index] = {...previousValue.nextContent, position: previousValue.position};
101
102
  } else _state[slideNumber] = newSlideContent;
102
103
  } else {
103
104
  // updates the rest of the slides here
@@ -121,7 +122,11 @@ const stateUpdateOnNext = (state: SlidesState, payload: OnNextPayload): SlidesSt
121
122
  const reducer = (state: SlidesState = initialState, action: SlidesAction): SlidesState => {
122
123
  switch (action.type) {
123
124
  case STORE_FIRST_SLIDE: {
124
- return setFirstSlide(state, action.payload);
125
+ // TODO: An intermediate slide transform is needed here (or in fetchSlide)
126
+ // const apiSlide = action.payload;
127
+ // apiSlide adapt to uiSlide
128
+ const uiSlide = qcmSlide;
129
+ return setFirstSlide(state, uiSlide);
125
130
  }
126
131
  case UPDATE_SLIDES_ON_VALIDATION: {
127
132
  return stateUpdateOnValidation(state, action.payload);
@@ -20,8 +20,7 @@ const fetchSkills: CreateFetchSkills = fetch => async token => {
20
20
  headers: {authorization: token}
21
21
  });
22
22
 
23
- const skills = (await toJSON(response)) as Skills;
24
- return skills;
23
+ return toJSON<Skills>(response);
25
24
  };
26
25
 
27
26
  // -----------------------------------------------------------------------------
@@ -0,0 +1,43 @@
1
+ import crossFetch from 'cross-fetch';
2
+ // import decode from 'jwt-decode';
3
+
4
+ import {SlideFromAPI} from '../actions/data/slides';
5
+ // import {JWT} from '../types/common';
6
+ // import {toJSON} from './tools/fetch-responses';
7
+ import {okJSONResponse} from './tools/fetch.mocks';
8
+
9
+ // -----------------------------------------------------------------------------
10
+
11
+ type FetchSlide = (slideRef: string, token: string) => Promise<SlideFromAPI>;
12
+ type FetchSlideBuilder = (fetch: typeof crossFetch) => FetchSlide;
13
+ type DefaultFetchSlideBuilder = () => FetchSlide;
14
+
15
+ // -----------------------------------------------------------------------------
16
+
17
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, unicorn/consistent-function-scoping, require-await
18
+ const fetchSlide: FetchSlideBuilder = fetch => async (slideRef: string, token: string) => {
19
+ // const {host}: JWT = decode(token);
20
+ // const response = await fetch(`${host}/api/v2/slide/${slideRef}`, {
21
+ // headers: {authorization: token}
22
+ // });
23
+
24
+ // const slide = (await toJSON(response)) as SlideFromAPI;
25
+ // return slide;
26
+ return {} as SlideFromAPI;
27
+ };
28
+
29
+ // -----------------------------------------------------------------------------
30
+
31
+ const createFetchSlide: DefaultFetchSlideBuilder = () => {
32
+ if (process.env.SERVICES !== 'mocks') {
33
+ return fetchSlide(crossFetch);
34
+ }
35
+
36
+ // TODO: use an appropriate mock
37
+ return fetchSlide(okJSONResponse({}));
38
+ };
39
+
40
+ // -----------------------------------------------------------------------------
41
+
42
+ export {fetchSlide};
43
+ export default createFetchSlide();
@@ -0,0 +1,35 @@
1
+ import crossFetch from 'cross-fetch';
2
+ import {ProgressionFromAPI} from '../actions/data/progression';
3
+ import {okJSONResponse} from './tools/fetch.mocks';
4
+
5
+ export const mockedProgression: ProgressionFromAPI = {
6
+ _id: '62b1d1087aa12f00253f40ee',
7
+ state: {
8
+ // ------------------------------------------------------
9
+ // unused for now / to decide whether to use it when the
10
+ // validation-progression is plugged in
11
+ pendingSlides: [],
12
+ step: {
13
+ current: Number.NaN
14
+ },
15
+ // ------------------------------------------------------
16
+ isCorrect: true,
17
+ nextContent: {
18
+ ref: 'sli_N1XACJobn',
19
+ type: 'slide'
20
+ }
21
+ // slides: [
22
+ // 'sli_NJ3m5KtWh',
23
+ // 'sli_V1gKpYYZ2',
24
+ // 'sli_VJzOgotbh',
25
+ // 'sli_V1ug-dtZn',
26
+ // 'sli_VyZU-ejW3',
27
+ // 'sli_Vy8ChgjZh',
28
+ // 'sli_V1ijS1oWh',
29
+ // 'sli_N1XACJobn'
30
+ // ],
31
+ }
32
+ };
33
+
34
+ const mockCreateProgression: typeof crossFetch = okJSONResponse(mockedProgression);
35
+ export default mockCreateProgression;
@@ -0,0 +1,41 @@
1
+ import test from 'ava';
2
+ import {ProgressionFromAPI} from '../actions/data/progression';
3
+ import {postProgression} from './post-progression';
4
+ import {okJSONResponse, token} from './tools/fetch.mocks';
5
+
6
+ const skillRef = '_skill-ref';
7
+
8
+ test('should create progression with success', async t => {
9
+ const result: ProgressionFromAPI = {
10
+ _id: '123456123456',
11
+ state: {
12
+ // is "isCorrect" always true for a 1st progression?
13
+ isCorrect: true,
14
+ step: {
15
+ current: 1
16
+ },
17
+ nextContent: {
18
+ type: 'slide',
19
+ ref: 'sli_NJ3m5KtWh'
20
+ },
21
+ pendingSlides: []
22
+ }
23
+ };
24
+
25
+ const mockedPostProgression = postProgression(okJSONResponse(result));
26
+ const progression = await mockedPostProgression(skillRef, token);
27
+
28
+ t.deepEqual(result, progression);
29
+ });
30
+
31
+ test('should reject bad token', async t => {
32
+ const badToken = 'token is not a jwt';
33
+ const mockedPostProgression = postProgression(okJSONResponse(null));
34
+
35
+ const error = await t.throwsAsync(() => mockedPostProgression(skillRef, badToken));
36
+
37
+ t.is(
38
+ error?.message,
39
+ "Invalid token specified: Cannot read properties of undefined (reading 'replace')"
40
+ );
41
+ });
@@ -0,0 +1,50 @@
1
+ import crossFetch from 'cross-fetch';
2
+ import decode from 'jwt-decode';
3
+
4
+ import {ProgressionFromAPI} from '../actions/data/progression';
5
+ import {JWT} from '../types/common';
6
+ import {toJSON} from './tools/fetch-responses';
7
+ import mockPostProgression from './post-progression.mock';
8
+
9
+ // -----------------------------------------------------------------------------
10
+
11
+ type PostProgression = (skillRef: string, token: string) => Promise<ProgressionFromAPI>;
12
+ type CreatePostProgression = (fetch: typeof crossFetch) => PostProgression;
13
+ type DefaultCreatePostProgression = () => PostProgression;
14
+
15
+ // -----------------------------------------------------------------------------
16
+
17
+ const postProgression: CreatePostProgression = fetch => async (skillRef, token) => {
18
+ const {host}: JWT = decode(token);
19
+ const response = await fetch(`${host}/api/v2/progressions`, {
20
+ method: 'post',
21
+ headers: {authorization: token},
22
+ body: JSON.stringify({
23
+ content: {
24
+ ref: skillRef,
25
+ type: 'slide'
26
+ },
27
+ engine: {
28
+ ref: 'review',
29
+ version: '1'
30
+ }
31
+ })
32
+ });
33
+
34
+ return toJSON<ProgressionFromAPI>(response);
35
+ };
36
+
37
+ // -----------------------------------------------------------------------------
38
+
39
+ const createPostProgression: DefaultCreatePostProgression = () => {
40
+ if (process.env.SERVICES !== 'mocks') {
41
+ return postProgression(crossFetch);
42
+ }
43
+
44
+ return postProgression(mockPostProgression);
45
+ };
46
+
47
+ // -----------------------------------------------------------------------------
48
+
49
+ export {postProgression};
50
+ export default createPostProgression();
@@ -8,7 +8,7 @@ export class ResponseError extends Error {
8
8
  }
9
9
  }
10
10
 
11
- export const toJSON = async (response: Response): Promise<unknown> => {
11
+ export const toJSON = async <T = unknown>(response: Response): Promise<T> => {
12
12
  if (!response.ok) {
13
13
  const body = await response.text();
14
14
  throw new ResponseError(response.status, response.statusText, body);
@@ -0,0 +1,4 @@
1
+ export const sleep = (msToSleep: number): Promise<void> =>
2
+ new Promise(resolve => {
3
+ setTimeout(resolve, msToSleep);
4
+ });
@@ -1,10 +1,16 @@
1
- import {Slide} from './slides';
1
+ // TODO use type from components
2
+ type TemplateContextValues = {
3
+ brandTheme?: unknown;
4
+ theme?: unknown; // TODO type Theme
5
+ translations?: unknown; // TODO type Translations
6
+ };
2
7
 
3
8
  export type AppOptions = {
4
9
  token: string;
5
10
  theme?: unknown;
6
11
  translations?: unknown;
7
- slide?: Slide;
12
+ templateContext?: TemplateContextValues;
13
+ skillRef?: string;
8
14
  };
9
15
 
10
16
  export type JWT = {
@@ -1,6 +1,7 @@
1
1
  declare module '*.css';
2
2
  declare module 'browser-env';
3
3
  declare module '@coorpacademy/components/es/template/app-review';
4
+ declare module '@coorpacademy/components/es/template/app-review/template-context';
4
5
 
5
6
  // eslint-disable-next-line no-shadow
6
7
  declare interface Window {
@@ -0,0 +1,25 @@
1
+ export type UiProgression = {
2
+ _id?: string;
3
+ // virtual field
4
+ slideNumber?: number;
5
+ isCorrect?: boolean;
6
+ // pass down the successExitNode if all slides are finished, not before
7
+ exitNode?: 'successExitNode' | 'failExitNode';
8
+ // TODO: create another action, to retrieve the next slide
9
+ // ex: nextContent: {ref: "sli_E1n2i2sBo", type: "slide"}
10
+ // the middleware should trigger a nextContent request once the result is
11
+ // received && update the state accordingly
12
+ // nextContent?: {
13
+ // questionText?: string;
14
+ // answerUI?: AnswerUI;
15
+ // };
16
+ nextContent?: {
17
+ ref: 'successExitNode' | string;
18
+ type: 'success' | 'slide';
19
+ };
20
+ // unused for now / to decide whether to use it when the
21
+ // validation-progression is plugged in
22
+ // step: {
23
+ // current: number;
24
+ // };
25
+ } | null;