fcad-core-dragon 2.1.2 → 2.2.0-beta.1
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/.gitlab-ci.yml +24 -42
- package/.vscode/settings.json +0 -30
- package/CHANGELOG +9 -0
- package/artifacts/playwright-report/index.html +85 -0
- package/package.json +29 -26
- package/src/components/AppBasePage.vue +3 -8
- package/src/components/AppCompAudio.vue +3 -3
- package/src/components/AppCompInputCheckBoxNx.vue +2 -1
- package/src/components/AppCompInputDropdownNx.vue +5 -5
- package/src/components/AppCompInputRadioNx.vue +2 -1
- package/src/components/AppCompInputTextNx.vue +0 -2
- package/src/components/AppCompInputTextTableNx.vue +4 -4
- package/src/components/AppCompInputTextToFillDropdownNx.vue +4 -6
- package/src/components/AppCompInputTextToFillNx.vue +1 -24
- package/src/components/AppCompPlayBarNext.vue +6 -8
- package/src/components/AppCompQuizRecall.vue +12 -2
- package/src/components/AppCompVideoPlayer.vue +2 -1
- package/src/main.js +18 -5
- package/src/plugins/i18n.js +8 -6
- package/tests/unit/AppCompAudio.spec.js +134 -0
- package/tests/unit/AppCompCarousel.spec.js +54 -0
- package/tests/unit/AppCompNoteCredit.spec.js +58 -0
- package/tests/unit/AppCompQuizNext.spec.js +1 -3
- package/tests/unit/AppCompVideoPlayer.spec.js +92 -100
- package/tests/unit/useQuiz.spec.js +72 -0
- package/vitest.config.js +36 -22
- package/vitest.setup.js +68 -0
- package/junit-report.xml +0 -182
package/package.json
CHANGED
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "fcad-core-dragon",
|
|
3
|
-
"version": "2.1.2",
|
|
4
|
-
"private": false,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./src/main.js",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "vite build",
|
|
9
|
-
"dev": "vite",
|
|
10
|
-
"docs:build": "vitepress build documentation",
|
|
11
|
-
"docs:dev": "vitepress dev documentation",
|
|
12
|
-
"docs:preview": "vitepress preview documentation",
|
|
13
|
-
"lintfix": "eslint --fix src",
|
|
14
|
-
"lintreport": "eslint ./src",
|
|
15
|
-
"preview": "vite preview",
|
|
16
|
-
"reset": "rm -rf ./node_modules package-lock.json .cache dist && npm i && npm run watch",
|
|
17
|
-
"test:unit": "vitest",
|
|
18
|
-
"vue": "vue",
|
|
19
|
-
"watch": "nodemon -e js,vue,html,json -x yalc publish --push",
|
|
20
|
-
"format": "prettier --write src/",
|
|
21
|
-
"test-ct": "playwright test -c playwright-ct.config.js"
|
|
22
|
-
},
|
|
23
|
-
"config": {
|
|
24
|
-
"projname": ""
|
|
25
|
-
},
|
|
26
2
|
"browserslist": [
|
|
27
3
|
"> 1%",
|
|
28
4
|
"last 2 versions",
|
|
29
5
|
"not dead"
|
|
30
6
|
],
|
|
7
|
+
"config": {
|
|
8
|
+
"projname": ""
|
|
9
|
+
},
|
|
31
10
|
"dependencies": {
|
|
32
11
|
"axios": "^1.6.8",
|
|
33
12
|
"gsap": "^3.12.5",
|
|
@@ -44,23 +23,47 @@
|
|
|
44
23
|
"@playwright/test": "^1.56.1",
|
|
45
24
|
"@types/node": "^24.10.0",
|
|
46
25
|
"@vitejs/plugin-vue": "^6.0.1",
|
|
26
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
47
27
|
"@vue/eslint-config-prettier": "^10.2.0",
|
|
48
28
|
"@vue/test-utils": "^2.4.6",
|
|
49
29
|
"eslint": "^9.38.0",
|
|
50
30
|
"eslint-plugin-cypress": "^5.1.0",
|
|
51
31
|
"eslint-plugin-vue": "~10.3.0",
|
|
52
32
|
"globals": "^16.4.0",
|
|
33
|
+
"happy-dom": "^20.0.10",
|
|
53
34
|
"jsdom": "^25.0.1",
|
|
54
35
|
"nodemon": "^3.1.0",
|
|
55
36
|
"prettier": "^3.6.2",
|
|
56
37
|
"sass-embedded": "^1.91.0",
|
|
57
38
|
"vitepress": "^1.6.3",
|
|
58
|
-
"vitest": "^
|
|
39
|
+
"vitest": "^4.0.15",
|
|
59
40
|
"vue-i18n": "^11.1.12",
|
|
60
41
|
"vue-router": "^4.4.5",
|
|
61
42
|
"vuetify": "^3.10.10"
|
|
62
43
|
},
|
|
63
44
|
"engines": {
|
|
64
45
|
"node": ">0.11.9"
|
|
65
|
-
}
|
|
46
|
+
},
|
|
47
|
+
"main": "./src/main.js",
|
|
48
|
+
"name": "fcad-core-dragon",
|
|
49
|
+
"private": false,
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "vite build",
|
|
52
|
+
"dev": "vite",
|
|
53
|
+
"docs:build": "vitepress build documentation",
|
|
54
|
+
"docs:dev": "vitepress dev documentation",
|
|
55
|
+
"docs:preview": "vitepress preview documentation",
|
|
56
|
+
"format": "prettier --write src/",
|
|
57
|
+
"lintfix": "eslint --fix src",
|
|
58
|
+
"lintreport": "eslint ./src",
|
|
59
|
+
"preview": "vite preview",
|
|
60
|
+
"reset": "rm -rf ./node_modules package-lock.json .cache dist && npm i && npm run watch",
|
|
61
|
+
"test-ct": "playwright test -c playwright-ct.config.js",
|
|
62
|
+
"test:unit": "vitest",
|
|
63
|
+
"test:unit:coverage": "vitest --coverage",
|
|
64
|
+
"vue": "vue",
|
|
65
|
+
"watch": "nodemon -e js,vue,html,json -x yalc publish --push"
|
|
66
|
+
},
|
|
67
|
+
"type": "module",
|
|
68
|
+
"version": "2.2.0-beta.1"
|
|
66
69
|
}
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
<div class="ctn">
|
|
12
12
|
Activity info: Route : {{ $route.path }}
|
|
13
13
|
<br />
|
|
14
|
-
|
|
15
14
|
Activity id : {{ getDebugModeInfo.id }}
|
|
16
15
|
<br />
|
|
17
16
|
Nombre de page : {{ getDebugModeInfo.size }}
|
|
@@ -96,6 +95,7 @@ export default {
|
|
|
96
95
|
}
|
|
97
96
|
},
|
|
98
97
|
setup(props) {
|
|
98
|
+
//console.log('dans setup')
|
|
99
99
|
const store = useAppStore()
|
|
100
100
|
const { activityRef, id: pageID, type: pageType } = props.pageData
|
|
101
101
|
const { t } = useI18n()
|
|
@@ -167,35 +167,29 @@ export default {
|
|
|
167
167
|
getModuleInfo() {
|
|
168
168
|
return this.store.getModuleInfo
|
|
169
169
|
},
|
|
170
|
-
|
|
171
170
|
getAllActivities() {
|
|
172
171
|
return this.store.getAllActivities()
|
|
173
172
|
},
|
|
174
173
|
getConnectionInfo() {
|
|
175
174
|
return this.store.getConnectionInfo
|
|
176
175
|
},
|
|
177
|
-
|
|
178
176
|
getAnchorsForActivity() {
|
|
179
177
|
return this.store.getAnchorsForActivity()
|
|
180
178
|
},
|
|
181
179
|
getBifChoice() {
|
|
182
180
|
return this.store.getBifChoice
|
|
183
181
|
},
|
|
184
|
-
|
|
185
182
|
isBranchingPage() {
|
|
186
183
|
return this.$route.meta.type === 'branching' && this.type !== 'pg_branch'
|
|
187
184
|
},
|
|
188
|
-
|
|
189
185
|
//================================================
|
|
190
186
|
settingsOptions() {
|
|
191
187
|
return this.settingsOptionsELPlus
|
|
192
188
|
},
|
|
193
|
-
|
|
194
189
|
settingsSelected() {
|
|
195
190
|
const setting = this.getApplicationSettings
|
|
196
191
|
return setting
|
|
197
192
|
},
|
|
198
|
-
|
|
199
193
|
errorPage() {
|
|
200
194
|
let err = false
|
|
201
195
|
if (import.meta.env.DEV) {
|
|
@@ -380,7 +374,8 @@ export default {
|
|
|
380
374
|
/**
|
|
381
375
|
* Observe changes in the number of poperties to dispatch updates in the userdata in the Store
|
|
382
376
|
*/
|
|
383
|
-
|
|
377
|
+
// console.log('ici')
|
|
378
|
+
//console.log(this.userInteraction)
|
|
384
379
|
if (newValue && Object.entries(this.userInteraction).length) {
|
|
385
380
|
await this.store.updateUserMetaData({
|
|
386
381
|
activityRef: this.pageData.activityRef,
|
|
@@ -107,11 +107,11 @@ export default {
|
|
|
107
107
|
...mapState(useAppStore, ['getCurrentBrowser', 'getCurrentPage']),
|
|
108
108
|
$audElement() {
|
|
109
109
|
if (!this.isSet) return null
|
|
110
|
+
const { id, mTranscript } = this.audData
|
|
110
111
|
return {
|
|
111
|
-
id
|
|
112
|
-
mTranscript
|
|
112
|
+
id,
|
|
113
|
+
mTranscript,
|
|
113
114
|
mType: 'audio',
|
|
114
|
-
mSubtitles: null,
|
|
115
115
|
mElement: this.$refs['m-audio']
|
|
116
116
|
}
|
|
117
117
|
},
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
:items="getItemOptions(index)"
|
|
30
30
|
:open-text="`${$t('message.dropdown_list')}_${singleDropdown.ennonce}`"
|
|
31
31
|
:aria-describedby="`${id}_${singleDropdown.id}-msg-erreur`"
|
|
32
|
+
menu-icon="mdi-chevron-down"
|
|
32
33
|
@update:model-value="onSelectUpdate($event, index)"
|
|
33
34
|
></v-select>
|
|
34
35
|
|
|
@@ -169,11 +170,10 @@ export default {
|
|
|
169
170
|
null,
|
|
170
171
|
`liste déroulante #${i + 1}`
|
|
171
172
|
)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
173
|
+
if (!e || !e.errorList || !e.errorInConsole) return null
|
|
174
|
+
const { errorList, errorInConsole } = e
|
|
175
|
+
if (errorInConsole.length) errors.errorInConsole.push(errorInConsole[0])
|
|
176
|
+
if (errorList.length) errors.errorList.push(errorList[0])
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
return errors
|
|
@@ -129,10 +129,10 @@ export default {
|
|
|
129
129
|
`texte tableau #${i + 1}`
|
|
130
130
|
)
|
|
131
131
|
|
|
132
|
-
if (e
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
if (!e || !e.errorList || !e.errorInConsole) return null
|
|
133
|
+
const { errorList, errorInConsole } = e
|
|
134
|
+
if (errorInConsole.length) errors.errorInConsole.push(errorInConsole[0])
|
|
135
|
+
if (errorList.length) errors.errorList.push(errorList[0])
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
return errors
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
:items="getItemOptions(textInput.index)"
|
|
35
35
|
:aria-describedby="`${id}_${textInput.id}-msg-erreur`"
|
|
36
36
|
:aria-labelledby="`${id}_${textInput.id}-label`"
|
|
37
|
+
menu-icon="mdi-chevron-down"
|
|
37
38
|
@update:model-value="onSelectUpdate($event, textInput.index)"
|
|
38
39
|
/>
|
|
39
40
|
<span
|
|
@@ -173,7 +174,7 @@ export default {
|
|
|
173
174
|
let splittedText = str.split(regex)
|
|
174
175
|
let listInput = []
|
|
175
176
|
|
|
176
|
-
for (let i = 0; i < splittedText.length
|
|
177
|
+
for (let i = 0; i < splittedText.length; i++) {
|
|
177
178
|
listInput.push({
|
|
178
179
|
id: `text-${i}`,
|
|
179
180
|
type: 'text',
|
|
@@ -183,9 +184,10 @@ export default {
|
|
|
183
184
|
if (i < splittedText.length - 1)
|
|
184
185
|
listInput.push({ id: `select-${i}`, type: 'select', index: i })
|
|
185
186
|
}
|
|
187
|
+
|
|
186
188
|
let lI = listInput.findLast((element) => element.type == 'select')
|
|
187
|
-
this.inputCount = lI.index + 1
|
|
188
189
|
|
|
190
|
+
this.inputCount = lI.index + 1
|
|
189
191
|
this.inputElements = listInput
|
|
190
192
|
},
|
|
191
193
|
/**
|
|
@@ -299,14 +301,11 @@ fieldset {
|
|
|
299
301
|
div.texteatrou {
|
|
300
302
|
display: inline !important;
|
|
301
303
|
|
|
302
|
-
|
|
303
304
|
.cnt-input {
|
|
304
305
|
display: inline;
|
|
305
306
|
.v-input {
|
|
306
307
|
display: inline-block !important;
|
|
307
308
|
|
|
308
|
-
|
|
309
|
-
|
|
310
309
|
:deep(.v-input__control),
|
|
311
310
|
:deep(.v-field) {
|
|
312
311
|
width: 240px !important;
|
|
@@ -339,5 +338,4 @@ div.texteatrou {
|
|
|
339
338
|
}
|
|
340
339
|
}
|
|
341
340
|
}
|
|
342
|
-
|
|
343
341
|
</style>
|
|
@@ -132,29 +132,6 @@ export default {
|
|
|
132
132
|
mounted() {},
|
|
133
133
|
|
|
134
134
|
methods: {
|
|
135
|
-
/**
|
|
136
|
-
* @description validate the raw data received by the component to render is view
|
|
137
|
-
* @returns {Object} errors - errorList: to display in view and errorConsole, to be displayed in console
|
|
138
|
-
*/
|
|
139
|
-
validateInputData() {
|
|
140
|
-
let errors = null //array for errors dectected
|
|
141
|
-
let stringType = ['id', 'value']
|
|
142
|
-
|
|
143
|
-
if (!this.inputData.length) return errors
|
|
144
|
-
for (let i = 0; i < this.inputData.length; i++) {
|
|
145
|
-
errors = validateObjType(
|
|
146
|
-
this.inputData[i],
|
|
147
|
-
{ stringType },
|
|
148
|
-
null,
|
|
149
|
-
`choix_reponse #${i + 1}`
|
|
150
|
-
)
|
|
151
|
-
const { errorList, errorInConsole } = errors
|
|
152
|
-
|
|
153
|
-
if (errorList.length || errorInConsole.length) return errors
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return errors
|
|
157
|
-
},
|
|
158
135
|
/**
|
|
159
136
|
* @description create the object to genate the text and inputs
|
|
160
137
|
* @param {String} str the text with holes to fill
|
|
@@ -165,7 +142,7 @@ export default {
|
|
|
165
142
|
let splittedText = str.split(regex)
|
|
166
143
|
let listInput = []
|
|
167
144
|
|
|
168
|
-
for (let i = 0; i < splittedText.length
|
|
145
|
+
for (let i = 0; i < splittedText.length; i++) {
|
|
169
146
|
listInput.push({
|
|
170
147
|
id: `text-${i}`,
|
|
171
148
|
type: 'text',
|
|
@@ -1628,7 +1628,7 @@ export default {
|
|
|
1628
1628
|
*/
|
|
1629
1629
|
toggleFullScreen() {
|
|
1630
1630
|
const fullscreenElement = this.mediaContainer
|
|
1631
|
-
console.log(fullscreenElement)
|
|
1631
|
+
//console.log(fullscreenElement)
|
|
1632
1632
|
|
|
1633
1633
|
if (document.fullscreenElement) {
|
|
1634
1634
|
// exitFullscreen is only available on the Document object.
|
|
@@ -1795,12 +1795,10 @@ export default {
|
|
|
1795
1795
|
</script>
|
|
1796
1796
|
<style lang="scss" scoped>
|
|
1797
1797
|
.pb-container.video {
|
|
1798
|
-
position: absolute;
|
|
1799
1798
|
height: 100%;
|
|
1800
1799
|
width: 100%;
|
|
1801
1800
|
justify-content: center;
|
|
1802
1801
|
display: flex;
|
|
1803
|
-
margin-bottom: 78px;
|
|
1804
1802
|
}
|
|
1805
1803
|
|
|
1806
1804
|
.playback-button {
|
|
@@ -1808,7 +1806,7 @@ export default {
|
|
|
1808
1806
|
bottom: 0;
|
|
1809
1807
|
left: 0;
|
|
1810
1808
|
width: 100%;
|
|
1811
|
-
height:
|
|
1809
|
+
height: 100%;
|
|
1812
1810
|
display: flex;
|
|
1813
1811
|
flex-flow: column wrap;
|
|
1814
1812
|
justify-content: center;
|
|
@@ -1837,8 +1835,6 @@ export default {
|
|
|
1837
1835
|
width: 100%;
|
|
1838
1836
|
display: flex;
|
|
1839
1837
|
flex-direction: column;
|
|
1840
|
-
position: absolute;
|
|
1841
|
-
bottom: -78px;
|
|
1842
1838
|
opacity: 0;
|
|
1843
1839
|
z-index: 2;
|
|
1844
1840
|
|
|
@@ -2260,8 +2256,10 @@ export default {
|
|
|
2260
2256
|
|
|
2261
2257
|
.FS {
|
|
2262
2258
|
&:fullscreen {
|
|
2263
|
-
.pb-
|
|
2264
|
-
|
|
2259
|
+
.pb-container {
|
|
2260
|
+
position: absolute;
|
|
2261
|
+
bottom: 0;
|
|
2262
|
+
height: 78px;
|
|
2265
2263
|
}
|
|
2266
2264
|
}
|
|
2267
2265
|
}
|
|
@@ -103,19 +103,26 @@ export default {
|
|
|
103
103
|
//Watch for user interaction change to get quizRecall answer
|
|
104
104
|
getUserInteraction: {
|
|
105
105
|
async handler(newValue) {
|
|
106
|
+
//console.log('???')
|
|
106
107
|
if (!this.getUserInteraction) return
|
|
107
|
-
|
|
108
|
+
//console.log('--------------------')
|
|
108
109
|
const { activityId, pageId } = this.quizRecallData
|
|
109
110
|
const interaction = this.getPageInteraction(
|
|
110
111
|
activityId,
|
|
111
112
|
pageId
|
|
112
113
|
).userInteraction
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
//console.log(interaction)
|
|
116
|
+
if (!interaction) return //{
|
|
117
|
+
// this.quizRecall.done == false
|
|
118
|
+
// }
|
|
119
|
+
|
|
115
120
|
//Get quizRecall answer
|
|
116
121
|
if (this.quizRecallData && this.quizData) {
|
|
122
|
+
//console.log('ici????')
|
|
117
123
|
await this.getQuizRecallAnswer(interaction)
|
|
118
124
|
} else {
|
|
125
|
+
//console.log('laaaaaa ???? ')
|
|
119
126
|
this.quizRecall.done == false
|
|
120
127
|
}
|
|
121
128
|
},
|
|
@@ -216,6 +223,7 @@ export default {
|
|
|
216
223
|
if (hypertext_done) {
|
|
217
224
|
this.quizRecall.hypertext_done = hypertext_done
|
|
218
225
|
}
|
|
226
|
+
|
|
219
227
|
if (hypertext_undone) {
|
|
220
228
|
this.quizRecall.hypertext_undone = hypertext_undone
|
|
221
229
|
}
|
|
@@ -231,6 +239,7 @@ export default {
|
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
241
|
//Get the quiz answers from userData
|
|
242
|
+
|
|
234
243
|
const answers = userData.quizAnswers || {}
|
|
235
244
|
let quizAnswer = answers[quizId] || null
|
|
236
245
|
|
|
@@ -301,6 +310,7 @@ export default {
|
|
|
301
310
|
)
|
|
302
311
|
this.hasError.push(msgErr)
|
|
303
312
|
}
|
|
313
|
+
|
|
304
314
|
//Validate if all required properties are present in quizRecallData
|
|
305
315
|
if (missingRequired.length > 0) {
|
|
306
316
|
let msgErr = `QuizRecallData missing required ${missingRequired} property`
|
|
@@ -282,6 +282,7 @@ export default {
|
|
|
282
282
|
resizeVideo(size, container) {
|
|
283
283
|
let defaultSize = 100
|
|
284
284
|
if (size == 'sm') defaultSize = 68
|
|
285
|
+
|
|
285
286
|
//const videoElement = document.querySelector('.__media-container')
|
|
286
287
|
const videoElement = this.$vidElement.mMediaContainer
|
|
287
288
|
setTimeout(() => {
|
|
@@ -304,7 +305,7 @@ $widthVideo: 100%;
|
|
|
304
305
|
|
|
305
306
|
.app-video-player {
|
|
306
307
|
display: flex;
|
|
307
|
-
flex-direction:
|
|
308
|
+
flex-direction: column;
|
|
308
309
|
flex-wrap: wrap;
|
|
309
310
|
position: relative;
|
|
310
311
|
align-items: center;
|
package/src/main.js
CHANGED
|
@@ -28,7 +28,7 @@ import GsapPlugin from './plugins/gsap'
|
|
|
28
28
|
import eventBus from './plugins/bus'
|
|
29
29
|
import helper from './plugins/helper'
|
|
30
30
|
import analytics from './plugins/analytics'
|
|
31
|
-
import
|
|
31
|
+
import initLocalisation from './plugins/i18n'
|
|
32
32
|
import { scormPlugin } from './plugins/scorm'
|
|
33
33
|
import { xapiPlugin } from './plugins/xapi'
|
|
34
34
|
import { $idb } from './plugins/idb'
|
|
@@ -41,19 +41,33 @@ import VueSafeTeleport from 'vue-safe-teleport'
|
|
|
41
41
|
import { FocusTrap } from 'focus-trap-vue'
|
|
42
42
|
import nvdaFix from './directives/nvdaFix.js'
|
|
43
43
|
import pckg from '../package.json'
|
|
44
|
+
import fcadRouter from './router/index.js'
|
|
45
|
+
import '@mdi/font/css/materialdesignicons.css'
|
|
46
|
+
import 'vuetify/styles'
|
|
47
|
+
import { createVuetify } from 'vuetify'
|
|
48
|
+
import { fr, en } from 'vuetify/locale'
|
|
49
|
+
|
|
44
50
|
const pinia = createPinia()
|
|
45
51
|
|
|
46
52
|
export default {
|
|
47
53
|
install(app, options) {
|
|
54
|
+
app.use(fcadRouter)
|
|
48
55
|
app.use(pinia)
|
|
49
56
|
app.use(scormPlugin)
|
|
50
57
|
app.use(GsapPlugin)
|
|
51
58
|
app.use(xapiPlugin)
|
|
52
59
|
app.use(VueSafeTeleport)
|
|
53
60
|
app.component('FocusTrap', FocusTrap)
|
|
54
|
-
|
|
55
61
|
app.use(eventBus)
|
|
56
62
|
app.use($idb)
|
|
63
|
+
const vuetify = createVuetify({
|
|
64
|
+
locale: {
|
|
65
|
+
locale: 'fr',
|
|
66
|
+
fallback: 'en',
|
|
67
|
+
messages: { fr, en }
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
app.use(vuetify)
|
|
57
71
|
|
|
58
72
|
app.component('AppBase', AppBase)
|
|
59
73
|
app.component('AppBaseButton', AppBaseButton)
|
|
@@ -332,7 +346,6 @@ export default {
|
|
|
332
346
|
|
|
333
347
|
let server = remote ? specification : 'local'
|
|
334
348
|
//falback to idb when scorm and origin is localhost
|
|
335
|
-
//falback to idb when scorm and origin is localhost
|
|
336
349
|
if (
|
|
337
350
|
(specification == 'scorm' && window.location.hostname == 'localhost') ||
|
|
338
351
|
!window.location.hostname.includes('cegepadistance.ca')
|
|
@@ -477,8 +490,8 @@ export default {
|
|
|
477
490
|
appStore.applicationSettings = settingsOptions
|
|
478
491
|
//=================================END SETTING PREFERENCES ====================================//
|
|
479
492
|
|
|
480
|
-
|
|
481
|
-
|
|
493
|
+
const i18n = initLocalisation()
|
|
494
|
+
app.use(i18n)
|
|
482
495
|
app.use(helper)
|
|
483
496
|
app.use(analytics)
|
|
484
497
|
|
package/src/plugins/i18n.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
|
+
import { createI18n } from 'vue-i18n'
|
|
2
3
|
/**
|
|
3
4
|
* Merge locales message in project.
|
|
4
5
|
* @summary To merge the locales dictionnary of this library with the locales that user will create
|
|
5
|
-
* @param {Object}
|
|
6
|
+
* @param {Object} templateMessages - messages (JSON) received from the template
|
|
6
7
|
*/
|
|
7
|
-
export default function
|
|
8
|
+
export default function initLocalisation(templateMessages) {
|
|
9
|
+
const i18n = createI18n({ legacy: false })
|
|
10
|
+
|
|
8
11
|
const thisLocales = import.meta.glob('../$locales/*.json', {
|
|
9
12
|
import: 'default',
|
|
10
13
|
eager: true
|
|
@@ -15,13 +18,12 @@ export default function mergeLocales(i18n) {
|
|
|
15
18
|
const locale = matched[1]
|
|
16
19
|
// Using lodash to deeply merge objects
|
|
17
20
|
const coreMessages = JSON.parse(JSON.stringify(thisLocales[path]))
|
|
18
|
-
const templateMessages = JSON.parse(
|
|
19
|
-
JSON.stringify(i18n.messages[locale] || i18n.messages.value[locale]) //cover both legacy mode and non legacy mode of vue-i18n
|
|
20
|
-
)
|
|
21
21
|
const mergedMessages = _.merge(coreMessages, templateMessages)
|
|
22
22
|
|
|
23
23
|
// Since merLocalMessage will do a shallow merge of the object. We will use setLocalMessage to replace the message with new message object
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
i18n.global.setLocaleMessage(locale, mergedMessages)
|
|
25
26
|
}
|
|
26
27
|
}
|
|
28
|
+
return i18n
|
|
27
29
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { describe, it, test, expect } from 'vitest'
|
|
3
|
+
|
|
4
|
+
let dummyProps = {
|
|
5
|
+
id: 'P01',
|
|
6
|
+
activityRef: 'A03',
|
|
7
|
+
title: 'Lecteurs médias',
|
|
8
|
+
type: 'pg_normal',
|
|
9
|
+
audiosData: [
|
|
10
|
+
{
|
|
11
|
+
id: 'aud1',
|
|
12
|
+
mTitle: 'Et si... Annie Ernaux nous parlait',
|
|
13
|
+
mSources: [
|
|
14
|
+
{
|
|
15
|
+
type: 'mp3',
|
|
16
|
+
src: ' exemple_audio.mp3'
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
mPoster: 'audio_poster.png',
|
|
20
|
+
mAlt: "Portrait de l'autrice Annie Ernaux",
|
|
21
|
+
mTranscript:
|
|
22
|
+
'<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>'
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
import AppCompAudioPlayer from '@/components/AppCompAudio.vue'
|
|
28
|
+
import { validateAudioData } from '@/shared/validators.js'
|
|
29
|
+
|
|
30
|
+
describe('AppCompAudioPlayer', () => {
|
|
31
|
+
test('Validate received props', () => {
|
|
32
|
+
validateAudioData.mockReturnValue([])
|
|
33
|
+
const { audiosData } = dummyProps
|
|
34
|
+
mount(AppCompAudioPlayer, {
|
|
35
|
+
props: { audData: audiosData[0] }
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
expect(validateAudioData).toHaveBeenCalledWith({
|
|
39
|
+
id: 'aud1',
|
|
40
|
+
mTitle: 'Et si... Annie Ernaux nous parlait',
|
|
41
|
+
mSources: [
|
|
42
|
+
{
|
|
43
|
+
type: 'mp3',
|
|
44
|
+
src: ' exemple_audio.mp3'
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
mPoster: 'audio_poster.png',
|
|
48
|
+
mAlt: "Portrait de l'autrice Annie Ernaux",
|
|
49
|
+
mTranscript:
|
|
50
|
+
'<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>'
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('renders ErrorDisplay When validator returns errors', () => {
|
|
55
|
+
// simulate invalid props
|
|
56
|
+
validateAudioData.mockReturnValue([
|
|
57
|
+
'WARNING!>>> audio: 💥 Invalid declaration for audio element'
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
const wrapper = mount(AppCompAudioPlayer, {
|
|
61
|
+
props: { audData: { id: null } }
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(
|
|
65
|
+
wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
|
|
66
|
+
).toBe(true)
|
|
67
|
+
expect(wrapper.find('audio').exists()).toBe(false)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('It renders HTML audio Element', () => {
|
|
71
|
+
validateAudioData.mockReturnValue([]) // no Error
|
|
72
|
+
const wrapper = mount(AppCompAudioPlayer, {
|
|
73
|
+
props: { audData: { id: null } }
|
|
74
|
+
})
|
|
75
|
+
expect(
|
|
76
|
+
wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
|
|
77
|
+
).toBe(false)
|
|
78
|
+
expect(wrapper.find('audio').exists()).toBe(true)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('audio Element has correct sources', async () => {
|
|
82
|
+
const { audiosData } = dummyProps
|
|
83
|
+
|
|
84
|
+
validateAudioData.mockReturnValue([]) // no Error
|
|
85
|
+
const wrapper = mount(AppCompAudioPlayer, {
|
|
86
|
+
props: { audData: audiosData[0] }
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(wrapper.vm.mSources).toEqual(audiosData[0].mSources)
|
|
90
|
+
|
|
91
|
+
//sources are rendered in the DOM
|
|
92
|
+
await wrapper.vm.$nextTick()
|
|
93
|
+
const sources = wrapper.findAll('source')
|
|
94
|
+
expect(sources.length).toBe(1)
|
|
95
|
+
|
|
96
|
+
// --- 3) valider les valeurs réelles rendues ---
|
|
97
|
+
expect(sources[0].attributes().src).toBe(audiosData[0].mSources[0].src)
|
|
98
|
+
expect(sources[0].attributes().type).toBe('audio/mp3')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('computed $auElement returns correct data structure', async () => {
|
|
102
|
+
validateAudioData.mockReturnValue([])
|
|
103
|
+
const { audiosData } = dummyProps
|
|
104
|
+
const wrapper = mount(AppCompAudioPlayer, {
|
|
105
|
+
props: { audData: audiosData[0] }
|
|
106
|
+
})
|
|
107
|
+
// Simulate refs
|
|
108
|
+
const mockaudioRef = { tagName: 'audio' }
|
|
109
|
+
const mockContainerRef = { className: '__media-container' }
|
|
110
|
+
wrapper.vm.$refs['m-audio'] = mockaudioRef
|
|
111
|
+
wrapper.vm.$refs['$media-container'] = mockContainerRef
|
|
112
|
+
// Simulate refs isSet (audio ready) to true
|
|
113
|
+
wrapper.vm.isSet = true
|
|
114
|
+
await wrapper.vm.$nextTick()
|
|
115
|
+
// get audElement object
|
|
116
|
+
const obj = wrapper.vm.$audElement
|
|
117
|
+
const id = wrapper.vm.$props.audData.id
|
|
118
|
+
// expected data structure to be returned
|
|
119
|
+
const audElement = {
|
|
120
|
+
id,
|
|
121
|
+
mType: 'audio',
|
|
122
|
+
mElement: { localName: 'audio' },
|
|
123
|
+
mTranscript:
|
|
124
|
+
'<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
expect(obj.id).toEqual(audElement.id)
|
|
128
|
+
expect(obj.mType).toBe(audElement.mType)
|
|
129
|
+
expect(obj.mElement.tagName.toLowerCase()).toBe(
|
|
130
|
+
audElement.mElement.localName
|
|
131
|
+
)
|
|
132
|
+
expect(obj.mTranscript).toBe(audElement.mTranscript)
|
|
133
|
+
})
|
|
134
|
+
})
|