@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.
- package/es/actions/data/progression.d.ts +30 -0
- package/es/actions/data/progression.js +12 -0
- package/es/actions/data/slides.d.ts +19 -15
- package/es/actions/data/slides.js +5 -0
- package/es/actions/data/token.d.ts +2 -2
- package/es/actions/data/token.js +1 -1
- package/es/actions/index.d.ts +2 -1
- package/es/configure-store.d.ts +2 -10
- package/es/configure-store.js +2 -1
- package/es/index.d.ts +1 -1
- package/es/index.js +43 -26
- package/es/index.test.js +5 -5
- package/es/middlewares/on-receive-progression.d.ts +6 -0
- package/es/middlewares/on-receive-progression.js +22 -0
- package/es/middlewares/on-receive-progression.test.d.ts +1 -0
- package/es/middlewares/on-receive-progression.test.js +43 -0
- package/es/middlewares/on-start-app.js +13 -5
- package/es/middlewares/on-start-app.test.d.ts +1 -0
- package/es/middlewares/on-start-app.test.js +53 -0
- package/es/reducers/data/index.d.ts +1 -1
- package/es/reducers/data/progression.d.ts +14 -8
- package/es/reducers/data/progression.js +17 -54
- package/es/reducers/data/slides.js +11 -6
- package/es/reducers/index.d.ts +1 -1
- package/es/services/fetch-skills.js +1 -2
- package/es/services/fetch-slide.d.ts +8 -0
- package/es/services/fetch-slide.js +26 -0
- package/es/services/post-progression.d.ts +8 -0
- package/es/services/post-progression.js +33 -0
- package/es/services/post-progression.mock.d.ts +5 -0
- package/es/services/post-progression.mock.js +31 -0
- package/es/services/post-progression.test.d.ts +1 -0
- package/es/services/post-progression.test.js +30 -0
- package/es/services/tools/fetch-responses.d.ts +1 -1
- package/es/services/tools/sleep.d.ts +1 -0
- package/es/services/tools/sleep.js +3 -0
- package/es/types/common.d.ts +8 -2
- package/es/types/ui-progression.d.ts +10 -0
- package/es/types/ui-progression.js +1 -0
- package/lib/actions/data/progression.d.ts +30 -0
- package/lib/actions/data/progression.js +12 -0
- package/lib/actions/data/slides.d.ts +19 -15
- package/lib/actions/data/slides.js +5 -0
- package/lib/actions/data/token.d.ts +2 -2
- package/lib/actions/data/token.js +1 -1
- package/lib/actions/index.d.ts +2 -1
- package/lib/configure-store.d.ts +2 -10
- package/lib/configure-store.js +2 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +43 -26
- package/lib/index.test.js +5 -7
- package/lib/middlewares/on-receive-progression.d.ts +6 -0
- package/lib/middlewares/on-receive-progression.js +27 -0
- package/lib/middlewares/on-receive-progression.test.d.ts +1 -0
- package/lib/middlewares/on-receive-progression.test.js +88 -0
- package/lib/middlewares/on-start-app.js +15 -7
- package/lib/middlewares/on-start-app.test.d.ts +1 -0
- package/lib/middlewares/on-start-app.test.js +98 -0
- package/lib/reducers/data/index.d.ts +1 -1
- package/lib/reducers/data/progression.d.ts +14 -8
- package/lib/reducers/data/progression.js +17 -54
- package/lib/reducers/data/slides.js +11 -6
- package/lib/reducers/index.d.ts +1 -1
- package/lib/services/fetch-skills.js +2 -5
- package/lib/services/fetch-slide.d.ts +8 -0
- package/lib/services/fetch-slide.js +64 -0
- package/lib/services/post-progression.d.ts +8 -0
- package/lib/services/post-progression.js +77 -0
- package/lib/services/post-progression.mock.d.ts +5 -0
- package/lib/services/post-progression.mock.js +31 -0
- package/lib/services/post-progression.test.d.ts +1 -0
- package/lib/services/post-progression.test.js +84 -0
- package/lib/services/tools/fetch-responses.d.ts +1 -1
- package/lib/services/tools/sleep.d.ts +1 -0
- package/lib/services/tools/sleep.js +5 -0
- package/lib/types/common.d.ts +8 -2
- package/lib/types/ui-progression.d.ts +10 -0
- package/lib/types/ui-progression.js +1 -0
- package/package.json +4 -5
- package/readme.md +4 -5
- package/src/actions/data/progression.ts +50 -0
- package/src/actions/data/slides.ts +25 -17
- package/src/actions/data/token.ts +2 -2
- package/src/actions/index.ts +4 -1
- package/src/configure-store.ts +5 -13
- package/src/index.test.tsx +5 -7
- package/src/index.tsx +54 -29
- package/src/middlewares/on-receive-progression.test.ts +60 -0
- package/src/middlewares/on-receive-progression.ts +42 -0
- package/src/middlewares/on-start-app.test.ts +68 -0
- package/src/middlewares/on-start-app.ts +13 -5
- package/src/reducers/data/progression.ts +34 -69
- package/src/reducers/data/slides.ts +13 -8
- package/src/services/fetch-skills.ts +1 -2
- package/src/services/fetch-slide.ts +43 -0
- package/src/services/post-progression.mock.ts +35 -0
- package/src/services/post-progression.test.ts +41 -0
- package/src/services/post-progression.ts +50 -0
- package/src/services/tools/fetch-responses.ts +1 -1
- package/src/services/tools/sleep.ts +4 -0
- package/src/types/common.ts +8 -2
- package/src/types/globals.d.ts +1 -0
- 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 {
|
|
21
|
+
const {skillRef, token} = action.payload;
|
|
20
22
|
|
|
21
23
|
dispatch(storeToken(token));
|
|
22
24
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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] = {...
|
|
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.
|
|
100
|
-
_state[index] = {...previousValue.
|
|
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
|
-
|
|
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
|
-
|
|
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<
|
|
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);
|
package/src/types/common.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
12
|
+
templateContext?: TemplateContextValues;
|
|
13
|
+
skillRef?: string;
|
|
8
14
|
};
|
|
9
15
|
|
|
10
16
|
export type JWT = {
|
package/src/types/globals.d.ts
CHANGED
|
@@ -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;
|