fcad-core-dragon 2.1.1 → 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.
Files changed (164) hide show
  1. package/.editorconfig +7 -7
  2. package/.gitlab-ci.yml +106 -0
  3. package/.prettierrc +11 -11
  4. package/.vscode/extensions.json +8 -8
  5. package/.vscode/settings.json +16 -16
  6. package/CHANGELOG +529 -520
  7. package/README.md +57 -57
  8. package/artifacts/playwright-report/index.html +85 -0
  9. package/documentation/.vitepress/config.js +114 -114
  10. package/documentation/api-examples.md +49 -49
  11. package/documentation/composants/app-base-button.md +58 -58
  12. package/documentation/composants/app-base-error-display.md +59 -59
  13. package/documentation/composants/app-base-popover.md +68 -68
  14. package/documentation/composants/app-comp-audio.md +75 -75
  15. package/documentation/composants/app-comp-branch-buttons.md +111 -111
  16. package/documentation/composants/app-comp-button-progress.md +53 -53
  17. package/documentation/composants/app-comp-carousel.md +53 -53
  18. package/documentation/composants/app-comp-container.md +53 -53
  19. package/documentation/composants/app-comp-input-checkbox-next.md +42 -42
  20. package/documentation/composants/app-comp-input-dropdown-next.md +34 -34
  21. package/documentation/composants/app-comp-input-radio-next.md +39 -39
  22. package/documentation/composants/app-comp-input-text-next.md +35 -35
  23. package/documentation/composants/app-comp-input-text-table-next.md +34 -34
  24. package/documentation/composants/app-comp-input-text-to-fill-dropdown-next.md +53 -53
  25. package/documentation/composants/app-comp-input-text-to-fill-next.md +31 -31
  26. package/documentation/composants/app-comp-jauge.md +31 -31
  27. package/documentation/composants/app-comp-menu-item.md +55 -55
  28. package/documentation/composants/app-comp-menu.md +29 -29
  29. package/documentation/composants/app-comp-navigation.md +41 -41
  30. package/documentation/composants/app-comp-note-call.md +53 -53
  31. package/documentation/composants/app-comp-note-credit.md +53 -53
  32. package/documentation/composants/app-comp-play-bar-next.md +53 -53
  33. package/documentation/composants/app-comp-pop-up-next.md +93 -93
  34. package/documentation/composants/app-comp-quiz-next.md +235 -235
  35. package/documentation/composants/app-comp-quiz-recall.md +53 -53
  36. package/documentation/composants/app-comp-svg-next.md +53 -53
  37. package/documentation/composants/app-comp-table-of-content.md +50 -50
  38. package/documentation/composants/app-comp-video-player.md +82 -82
  39. package/documentation/composants.md +46 -46
  40. package/documentation/composants_critiques/ModelPageComposant.md +53 -53
  41. package/documentation/composants_critiques/app-base-module.md +43 -43
  42. package/documentation/composants_critiques/app-base-page.md +48 -48
  43. package/documentation/composants_critiques/app-base.md +311 -311
  44. package/documentation/composants_critiques/main.md +15 -15
  45. package/documentation/demarrage.md +50 -50
  46. package/documentation/deploiement.md +57 -57
  47. package/documentation/index.md +33 -33
  48. package/documentation/markdown-examples.md +85 -85
  49. package/documentation/public/vite.svg +14 -14
  50. package/documentation/public/vuejs.svg +1 -1
  51. package/documentation/public/vuetify.svg +5 -5
  52. package/eslint.config.js +60 -60
  53. package/package.json +69 -59
  54. package/playwright/index.html +12 -0
  55. package/playwright/index.js +21 -0
  56. package/playwright-ct.config.js +95 -0
  57. package/src/$locales/en.json +157 -157
  58. package/src/$locales/fr.json +120 -120
  59. package/src/assets/data/onboardingMessages.json +47 -47
  60. package/src/components/AppBase.vue +1171 -1169
  61. package/src/components/AppBaseButton.vue +90 -95
  62. package/src/components/AppBaseErrorDisplay.vue +438 -438
  63. package/src/components/AppBaseFlipCard.vue +84 -84
  64. package/src/components/AppBaseModule.vue +1639 -1634
  65. package/src/components/AppBasePage.vue +862 -866
  66. package/src/components/AppBasePopover.vue +41 -41
  67. package/src/components/AppBaseSkeleton.vue +66 -66
  68. package/src/components/AppCompAudio.vue +261 -256
  69. package/src/components/AppCompBranchButtons.vue +508 -508
  70. package/src/components/AppCompButtonProgress.vue +137 -132
  71. package/src/components/AppCompCarousel.vue +342 -336
  72. package/src/components/AppCompContainer.vue +29 -29
  73. package/src/components/AppCompInputCheckBoxNx.vue +326 -323
  74. package/src/components/AppCompInputDropdownNx.vue +302 -299
  75. package/src/components/AppCompInputRadioNx.vue +288 -284
  76. package/src/components/AppCompInputTextNx.vue +154 -153
  77. package/src/components/AppCompInputTextTableNx.vue +205 -202
  78. package/src/components/AppCompInputTextToFillDropdownNx.vue +341 -340
  79. package/src/components/AppCompInputTextToFillNx.vue +293 -313
  80. package/src/components/AppCompJauge.vue +81 -81
  81. package/src/components/AppCompMenu.vue +6 -1
  82. package/src/components/AppCompMenuItem.vue +246 -240
  83. package/src/components/AppCompNavigation.vue +977 -972
  84. package/src/components/AppCompNoteCall.vue +167 -161
  85. package/src/components/AppCompNoteCredit.vue +496 -491
  86. package/src/components/AppCompPlayBarNext.vue +2288 -2288
  87. package/src/components/AppCompPopUpNext.vue +508 -504
  88. package/src/components/AppCompQuizNext.vue +515 -510
  89. package/src/components/AppCompQuizRecall.vue +365 -350
  90. package/src/components/AppCompSVGNext.vue +346 -346
  91. package/src/components/AppCompSettingsMenu.vue +177 -172
  92. package/src/components/AppCompTableOfContent.vue +433 -427
  93. package/src/components/AppCompVideoPlayer.vue +378 -377
  94. package/src/components/AppCompViewDisplay.vue +6 -6
  95. package/src/components/BaseModule.vue +55 -55
  96. package/src/composables/useIdleDetector.js +56 -56
  97. package/src/composables/useQuiz.js +89 -89
  98. package/src/composables/useTimer.js +172 -172
  99. package/src/directives/nvdaFix.js +53 -53
  100. package/src/externalComps/ModuleView.vue +22 -22
  101. package/src/externalComps/SummaryView.vue +91 -91
  102. package/src/main.js +506 -476
  103. package/src/module/stores/appStore.js +960 -947
  104. package/src/module/xapi/ADL.js +520 -520
  105. package/src/module/xapi/Crypto/Hasher.js +241 -241
  106. package/src/module/xapi/Crypto/WordArray.js +278 -278
  107. package/src/module/xapi/Crypto/algorithms/BufferedBlockAlgorithm.js +103 -103
  108. package/src/module/xapi/Crypto/algorithms/C_algo.js +315 -315
  109. package/src/module/xapi/Crypto/algorithms/HMAC.js +9 -9
  110. package/src/module/xapi/Crypto/algorithms/SHA1.js +9 -9
  111. package/src/module/xapi/Crypto/encoders/Base.js +105 -105
  112. package/src/module/xapi/Crypto/encoders/Base64.js +99 -99
  113. package/src/module/xapi/Crypto/encoders/Hex.js +61 -61
  114. package/src/module/xapi/Crypto/encoders/Latin1.js +61 -61
  115. package/src/module/xapi/Crypto/encoders/Utf8.js +45 -45
  116. package/src/module/xapi/Crypto/index.js +53 -53
  117. package/src/module/xapi/Statement/activity.js +47 -47
  118. package/src/module/xapi/Statement/agent.js +55 -55
  119. package/src/module/xapi/Statement/group.js +26 -26
  120. package/src/module/xapi/Statement/index.js +259 -259
  121. package/src/module/xapi/Statement/statement.js +253 -253
  122. package/src/module/xapi/Statement/statementRef.js +23 -23
  123. package/src/module/xapi/Statement/substatement.js +22 -22
  124. package/src/module/xapi/Statement/verb.js +36 -36
  125. package/src/module/xapi/activitytypes.js +17 -17
  126. package/src/module/xapi/launch.js +157 -157
  127. package/src/module/xapi/utils.js +167 -167
  128. package/src/module/xapi/verbs.js +294 -294
  129. package/src/module/xapi/wrapper.js +1895 -1895
  130. package/src/module/xapi/xapiStatement.js +444 -444
  131. package/src/plugins/analytics.js +34 -34
  132. package/src/plugins/bus.js +12 -8
  133. package/src/plugins/gsap.js +17 -17
  134. package/src/plugins/helper.js +355 -358
  135. package/src/plugins/i18n.js +29 -26
  136. package/src/plugins/idb.js +227 -227
  137. package/src/plugins/save.js +37 -37
  138. package/src/plugins/scorm.js +287 -287
  139. package/src/plugins/xapi.js +11 -11
  140. package/src/public/index.html +33 -33
  141. package/src/router/index.js +57 -57
  142. package/src/router/routes.js +312 -312
  143. package/src/shared/generalfuncs.js +344 -344
  144. package/src/shared/validators.js +1018 -1018
  145. package/tests/component/AppBaseButton.spec.js +53 -0
  146. package/tests/component/pinia.spec.js +24 -0
  147. package/{src/components/tests__ → tests/unit}/AppBaseButton.spec.js +53 -53
  148. package/tests/unit/AppCompAudio.spec.js +134 -0
  149. package/tests/unit/AppCompCarousel.spec.js +54 -0
  150. package/tests/unit/AppCompInputCheckBoxNx.spec.js +59 -0
  151. package/tests/unit/AppCompInputDropdownNx.spec.js +51 -0
  152. package/tests/unit/AppCompInputRadioNx.spec.js +59 -0
  153. package/tests/unit/AppCompInputTextNx.spec.js +44 -0
  154. package/tests/unit/AppCompInputTextTableNx.spec.js +77 -0
  155. package/tests/unit/AppCompInputTextToFillDropdownNx.spec.js +60 -0
  156. package/tests/unit/AppCompInputTextToFillNx.spec.js +45 -0
  157. package/tests/unit/AppCompNoteCredit.spec.js +58 -0
  158. package/tests/unit/AppCompQuizNext.spec.js +112 -0
  159. package/tests/unit/AppCompVideoPlayer.spec.js +169 -0
  160. package/tests/unit/useQuiz.spec.js +72 -0
  161. package/{src/components/tests__ → tests/unit}/useTimer.spec.js +91 -91
  162. package/vitest.config.js +42 -19
  163. package/vitest.setup.js +96 -0
  164. package/src/components/AppBaseButton.test.js +0 -21
@@ -0,0 +1,60 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import AppCompInputTextToFillDropdownNx from '@/components/AppCompInputTextToFillDropdownNx.vue'
3
+ import { beforeEach, describe, expect, test } from 'vitest'
4
+ import { VSelect } from 'vuetify/components/VSelect'
5
+
6
+ const dummyProps = {
7
+ modelValue: ['rouge', 'rouge', 'jaune', 'jaune', 'vert', 'magenta'],
8
+ inputData: [
9
+ {
10
+ un: ['rouge', 'vert', 'bleu', 'jaune', 'magenta', 'cyan']
11
+ },
12
+ {
13
+ deux: ['rouge', 'vert', 'bleu', 'jaune', 'magenta', 'cyan']
14
+ },
15
+ {
16
+ trois: ['rouge', 'vert', 'bleu', 'jaune', 'magenta', 'cyan']
17
+ },
18
+ {
19
+ quatre: ['rouge', 'vert', 'bleu', 'jaune', 'magenta', 'cyan']
20
+ },
21
+ {
22
+ cinq: ['rouge', 'vert', 'bleu', 'jaune', 'magenta', 'cyan']
23
+ },
24
+ {
25
+ six: ['rouge', 'vert', 'bleu', 'jaune', 'magenta', 'cyan']
26
+ }
27
+ ],
28
+ textBase:
29
+ '$%un%$ Bacon ipsum dolor amet ground round shank turkey buffalo flank, doner pork chop pork belly ham hock corned beef. Sirloin bresaola kielbasa pig flank $%deux%$ landjaeger biltong porchetta. Pork belly prosciutto sirloin shoulder, leberkas bresaola chicken pastrami pork ground round alcatra chislic ham. Prosciutto corned beef filet mignon chislic $%trois%$ pancetta salami bresaola chicken. Chislic kielbasa alcatra, porchetta cow hamburger $%quatre%$ Lorem Ipsum Dolor $%cinq%$ cupim chuck sirloin turkey spare ribs jowl. Spare ribs swine alcatra picanha beef corned beef tenderloin bresaola shankle. Chislic jowl frankfurter flank leberkas filet mignon ground round strip steak.$%six%$',
30
+ solution: null,
31
+ id: 'A02_P02_Q4'
32
+ }
33
+ describe('AppCompInputTextToFillDropdownNx.vue', () => {
34
+ let wrapper = null
35
+ beforeEach(() => {
36
+ wrapper = mount(AppCompInputTextToFillDropdownNx, {
37
+ props: dummyProps
38
+ })
39
+ })
40
+ test(`renders a list of vuetify v-select components`, () => {
41
+ expect(wrapper.findAllComponents(VSelect).length).toBe(
42
+ dummyProps.inputData.length
43
+ )
44
+ })
45
+ test(`has the right initial data to display (modelValue prop)`, () => {
46
+ expect(wrapper.vm.inputsValue).toStrictEqual(dummyProps.modelValue)
47
+ })
48
+ test('emits enable-submit and update:modelValue events on input change', () => {
49
+ wrapper.vm.inputsValue = [
50
+ 'jaune',
51
+ 'rouge',
52
+ 'rouge',
53
+ 'jaune',
54
+ 'vert',
55
+ 'magenta'
56
+ ]
57
+ expect(wrapper.emitted()).toHaveProperty('enable-submit')
58
+ expect(wrapper.emitted()).toHaveProperty('update:modelValue')
59
+ })
60
+ })
@@ -0,0 +1,45 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import AppCompInputTextToFillNx from '@/components/AppCompInputTextToFillNx.vue'
3
+ import { beforeEach, describe, expect, test } from 'vitest'
4
+ import { VTextField } from 'vuetify/components/VTextField'
5
+
6
+ const dummyProps = {
7
+ modelValue: ['Hello', 'world', 'lorem', 'ipsum', 'good', 'bye'],
8
+ textBase:
9
+ '$%un%$ Bacon ipsum dolor amet groun doner pork. Sirloin bresaola kielbasa pig flank $%deux%$ landjaege. Pork belly prosciutto sirloin shoulder, pork ground round alcatra chislic ham. Prosciutto corned chislic $%trois%$ pancetta salami bresaola chicken. Chislic, porchetta cow hamburger $%quatre%$ $%cinq%$ cupim chuck sirloin turkey spare ribs jowl. Spare ribs swine alcatra picanha beef corned beef tenderloin bresaola shankle. Chislic jowl frankfurter flank leberkas filet mignon ground round strip steak.$%six%$',
10
+ inputData: null,
11
+ solution: null,
12
+ id: 'A02_P02_Q4'
13
+ }
14
+ describe('AppCompInputTextToFillNx.vue', () => {
15
+ let wrapper = null
16
+
17
+ beforeEach(() => {
18
+ wrapper = mount(AppCompInputTextToFillNx, {
19
+ props: dummyProps
20
+ })
21
+ })
22
+ test(`renders a paragraph with multiple vuetify v-text-field`, () => {
23
+ const textfields = wrapper.findAllComponents(VTextField)
24
+ expect(textfields.length).toBeGreaterThan(0)
25
+ })
26
+
27
+ test(`displays the user saved answers`, () => {
28
+ const textfields = wrapper.findAll('input[type="text"]')
29
+ textfields.forEach((textfield, index) => {
30
+ expect(textfield.element.value).toBe(dummyProps.modelValue[index])
31
+ })
32
+ })
33
+
34
+ test('accepts some input text', async () => {
35
+ const textfield = wrapper.find('input[type="text"]')
36
+ await textfield.setValue('new value')
37
+ expect(textfield.element.value).toBe('new value')
38
+ })
39
+ test('emits enable-submit and update:modelValue events on input change', async () => {
40
+ const textfield = wrapper.find('input[type="text"]')
41
+ await textfield.setValue('new value')
42
+ expect(wrapper.emitted()).toHaveProperty('enable-submit')
43
+ expect(wrapper.emitted()).toHaveProperty('update:modelValue')
44
+ })
45
+ })
@@ -0,0 +1,58 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import AppCompNoteCredit from '../../src/components/AppCompNoteCredit.vue'
3
+ import { beforeEach, describe, expect, vi, test } from 'vitest'
4
+
5
+ const dummyProps = [
6
+ {
7
+ id: 'nt_1',
8
+ text: `note 1`
9
+ },
10
+ {
11
+ id: 'nt_2',
12
+ text: `note 2`
13
+ }
14
+ ]
15
+
16
+ describe('AppCompNoteCredit', () => {
17
+ let wrapper = null
18
+
19
+ beforeEach(() => {
20
+ vi.mock('@/shared/generalfuncs.js', () => ({
21
+ fileAssets: { getActivities: () => [] }
22
+ }))
23
+
24
+ vi.mock('@/router/routes.js', () => ({
25
+ mappedFiles: []
26
+ }))
27
+
28
+ vi.mock('@/stores/myStore', () => ({
29
+ useMyStore: vi.fn(() => ({
30
+ getDataNoteCredit: dummyProps,
31
+ getAllActivities: 'A01',
32
+ getCurrentPage: 'P01'
33
+ }))
34
+ }))
35
+
36
+ wrapper = mount(AppCompNoteCredit, {
37
+ global: {
38
+ stubs: {
39
+ 'app-base-button': true,
40
+ 'app-base-error-display': true
41
+ }
42
+ }
43
+ })
44
+ })
45
+
46
+ test('It renders a the pop-up', () => {
47
+ const wrapper = mount(AppCompNoteCredit)
48
+
49
+ expect(wrapper.exists()).toBe(true)
50
+
51
+ const div = wrapper.find('#noteCredit')
52
+ expect(div.exists()).toBe(true)
53
+ })
54
+
55
+ test('It renders a list of ${dummyProps.length}', () => {
56
+ expect(wrapper.findAll('div').length).toBe(dummyProps.length)
57
+ })
58
+ })
@@ -0,0 +1,112 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { beforeEach, describe, it, expect, vi } from 'vitest'
3
+ import { createTestingPinia } from '@pinia/testing'
4
+
5
+ // Les mocks doivent être "hoistés" (déclarés avant l'import du composant)
6
+ beforeEach(() => {
7
+ vi.mock('@/shared/generalfuncs.js', () => ({
8
+ fileAssets: { getActivities: () => [] }
9
+ }))
10
+ vi.mock('@/router/routes.js', () => ({
11
+ mappedFiles: []
12
+ }))
13
+ vi.mock('@/router/index.js', () => ({
14
+ default: { push: vi.fn(), currentRoute: { value: {} } }
15
+ }))
16
+ vi.mock('@/shared/validators.js', () => ({
17
+ validateObjType: () => true,
18
+ validateString: () => true,
19
+ validateQuizData: () => ({ errorInConsole: [] }),
20
+ validateNumber: () => true
21
+ }))
22
+ })
23
+
24
+ import AppCompQuizNext from '@/components/AppCompQuizNext.vue'
25
+
26
+ describe('AppCompQuizNext', () => {
27
+ const stubs = {
28
+ AppCompInputRadio: {
29
+ template: '<div class="stub-radio"></div>',
30
+ props: ['modelValue', 'inputData', 'id', 'solution']
31
+ },
32
+ AppCompInputTextNx: {
33
+ template: '<input class="stub-text" />',
34
+ props: ['modelValue']
35
+ },
36
+ AppBaseErrorDisplay: {
37
+ template: '<div class="stub-error"></div>',
38
+ props: {
39
+ /* ... (props omises pour la clarté) ... */
40
+ }
41
+ },
42
+ AppBaseSkeleton: { template: '<div class="stub-skeleton"></div>' },
43
+ AppBaseButton: { template: '<button class="stub-button"></button>' }
44
+ }
45
+
46
+ it('rend un input radio si la question est de type choix_unique', () => {
47
+ const quizData = {
48
+ id: 1,
49
+ type_question: 'choix_unique',
50
+ ennonce: 'Quel est la deuxieme lettre de alphabet',
51
+ mode: ['A', 'B', 'C'],
52
+ solution: ['B']
53
+ }
54
+
55
+ const wrapper = mount(AppCompQuizNext, {
56
+ props: {
57
+ modelValue: ['A', 'B', 'C'],
58
+ consigne: false,
59
+ shuffleAnswers: false,
60
+ quizData: quizData // Passez la variable ici
61
+ },
62
+ global: {
63
+ stubs,
64
+ plugins: [createTestingPinia({ createSpy: vi.fn })],
65
+ provide: {
66
+ userInteraction: {
67
+ recordAnswer: vi.fn(),
68
+ hasInteracted: vi.fn(),
69
+ quizAnswers: {
70
+ [quizData.id]: { value: ['A'], total_attempts: 0 }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ })
76
+
77
+ expect(wrapper.find('.stub-radio').exists()).toBe(true)
78
+ })
79
+
80
+ // it('rend un input texte si la question est de type text', async() => {
81
+ // const wrapper = mount(AppCompQuizNext, {
82
+ // props: {
83
+ // question: {
84
+ // id: 2,
85
+ // type: 'text'
86
+ // },
87
+ // modelValue: ''
88
+ // },
89
+ // global: { stubs }
90
+ // })
91
+ // expect(wrapper.find('.stub-text').exists()).toBe(true)
92
+ // })
93
+
94
+ // it('émet update:modelValue quand l’utilisateur répond', async () => {
95
+ // const wrapper = mount(AppCompQuizNext, {
96
+ // props: {
97
+ // question: { id: 1, type: 'text' },
98
+ // modelValue: ''
99
+ // },
100
+ // global: {
101
+ // stubs: {
102
+ // AppCompInputTextNx: {
103
+ // template: `<input @input="$emit('update:modelValue', 'hello')" />`
104
+ // }
105
+ // }
106
+ // }
107
+ // })
108
+ // await wrapper.find('input').trigger('input')
109
+
110
+ // expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['hello'])
111
+ // })
112
+ })
@@ -0,0 +1,169 @@
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
+ videosData: [
10
+ {
11
+ id: 'vid1',
12
+ mSources: [
13
+ {
14
+ type: 'mp4',
15
+ src: 'exemple_video.mp4'
16
+ }
17
+ ],
18
+ mSubtitles: [
19
+ {
20
+ label: 'Français',
21
+ src: 'exemple_soustitres.vtt',
22
+ srclang: 'fr'
23
+ }
24
+ ],
25
+ mPoster: 'video_poster.jpg',
26
+ mTranscript: 'exemple_transcript.html' // The file MUST be host in the public folder of the project
27
+ }
28
+ ]
29
+ }
30
+
31
+ import AppCompVideoPlayer from '@/components/AppCompVideoPlayer.vue'
32
+ import { validateVideoData } from '@/shared/validators.js'
33
+
34
+ describe('AppCompVideoPlayer', () => {
35
+ test('Validate received props', () => {
36
+ validateVideoData.mockReturnValue([])
37
+ const { videosData } = dummyProps
38
+ mount(AppCompVideoPlayer, {
39
+ props: { vidData: videosData[0] }
40
+ })
41
+
42
+ expect(validateVideoData).toHaveBeenCalledWith({
43
+ id: 'vid1',
44
+ mSources: [
45
+ {
46
+ type: 'mp4',
47
+ src: 'exemple_video.mp4'
48
+ }
49
+ ],
50
+ mSubtitles: [
51
+ {
52
+ label: 'Français',
53
+ src: 'exemple_soustitres.vtt',
54
+ srclang: 'fr'
55
+ }
56
+ ],
57
+ mPoster: 'video_poster.jpg',
58
+ mTranscript: 'exemple_transcript.html' // The file MUST be host in the public folder of the project
59
+ })
60
+ })
61
+
62
+ it('renders ErrorDisplay When validator returns errors', () => {
63
+ // simulate invalid props
64
+ validateVideoData.mockReturnValue([
65
+ 'WARNING!>>> VIDEO: 💥 Invalid declaration for video element'
66
+ ])
67
+
68
+ const wrapper = mount(AppCompVideoPlayer, {
69
+ props: { vidData: { id: null } }
70
+ })
71
+
72
+ expect(
73
+ wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
74
+ ).toBe(true)
75
+ expect(wrapper.find('video').exists()).toBe(false)
76
+ })
77
+
78
+ it('It renders HTML video Element', () => {
79
+ validateVideoData.mockReturnValue([]) // no Error
80
+ const wrapper = mount(AppCompVideoPlayer, {
81
+ props: { vidData: { id: null } }
82
+ })
83
+ expect(
84
+ wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
85
+ ).toBe(false)
86
+ expect(wrapper.find('video').exists()).toBe(true)
87
+ })
88
+
89
+ it('Video Element has correct sources', async () => {
90
+ const { videosData } = dummyProps
91
+ validateVideoData.mockReturnValue([]) // no Error
92
+ const wrapper = mount(AppCompVideoPlayer, {
93
+ props: { vidData: videosData[0] }
94
+ })
95
+
96
+ expect(wrapper.vm.vidSources).toEqual(videosData[0].mSources)
97
+
98
+ //sources are rendered in the DOM
99
+ await wrapper.vm.$nextTick()
100
+ const sources = wrapper.findAll('source')
101
+ expect(sources.length).toBe(1)
102
+
103
+ // --- 3) valider les valeurs réelles rendues ---
104
+ expect(sources[0].attributes().src).toBe(videosData[0].mSources[0].src)
105
+ expect(sources[0].attributes().type).toBe('video/mp4')
106
+ })
107
+
108
+ it('computed $vidElement returns correct data structure', async () => {
109
+ const { videosData } = dummyProps
110
+ validateVideoData.mockReturnValue([])
111
+
112
+ const wrapper = mount(AppCompVideoPlayer, {
113
+ props: { vidData: videosData[0] }
114
+ })
115
+
116
+ // Simulate refs
117
+ const mockVideoRef = { tagName: 'VIDEO' }
118
+ const mockContainerRef = { className: '__media-container' }
119
+
120
+ wrapper.vm.$refs['m-video'] = mockVideoRef
121
+ wrapper.vm.$refs['$media-container'] = mockContainerRef
122
+
123
+ // Simulate refs isSet (video ready) to true
124
+ wrapper.vm.isSet = true
125
+ await wrapper.vm.$nextTick()
126
+
127
+ // get vidElement object
128
+ const obj = wrapper.vm.$vidElement
129
+ const id = wrapper.vm.$props.vidData.id
130
+
131
+ // expected data structure to be returned
132
+ const vidElement = {
133
+ id,
134
+ mType: 'video',
135
+ mElement: { id, localName: 'video' },
136
+ mMediaContainer: {
137
+ id: `video_${id}`,
138
+ ...mockContainerRef,
139
+ localName: 'section'
140
+ },
141
+ mTranscript: 'exemple_transcript.html',
142
+ mSubtitles: [
143
+ {
144
+ label: 'Français',
145
+ src: 'exemple_soustitres.vtt',
146
+ srclang: 'fr'
147
+ }
148
+ ]
149
+ }
150
+ // validate returned object
151
+ expect(obj.id).toEqual(vidElement.id)
152
+ expect(obj.mType).toBe(vidElement.mType)
153
+ expect(obj.mElement.id).toBe(vidElement.mElement.id)
154
+ expect(obj.mElement.tagName.toLowerCase()).toBe(
155
+ vidElement.mElement.localName
156
+ )
157
+ expect(obj.mMediaContainer.tagName.toLowerCase()).toBe(
158
+ vidElement.mMediaContainer.localName
159
+ )
160
+ expect(obj.mMediaContainer.id).toBe(vidElement.mMediaContainer.id)
161
+ expect(
162
+ obj.mMediaContainer.classList.contains(
163
+ vidElement.mMediaContainer.className
164
+ )
165
+ ).toBe(true)
166
+ expect(obj.mTranscript).toBe(vidElement.mTranscript)
167
+ expect(obj.mSubtitles).toEqual(vidElement.mSubtitles)
168
+ })
169
+ })
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { useQuiz } from '../../src/composables/useQuiz.js'
3
+
4
+ // Mock de useI18n pour éviter les erreurs liées à $t()
5
+ vi.mock('vue-i18n', () => ({
6
+ useI18n: () => ({ t: (key) => key })
7
+ }))
8
+
9
+ describe('useQuiz composable', () => {
10
+ const { shuffleArray, addRetroStyle, resetRetroStyle, retroType } = useQuiz()
11
+
12
+ it('shuffleArray should shuffle array without losing elements', () => {
13
+ const arr = [1, 2, 3, 4]
14
+ const shuffled = shuffleArray(arr)
15
+ expect(shuffled).toHaveLength(arr.length)
16
+ expect(shuffled.sort()).toEqual(arr.sort())
17
+ })
18
+
19
+ it('addRetroStyle should return correct classes and messages for correct answers', () => {
20
+ const solution = [{ id: 1 }, { id: 2 }]
21
+ const reponse = [{ correct: true }, { correct: true }]
22
+ const result = addRetroStyle(solution, reponse, reponse.length)
23
+ expect(result.classRetro).toEqual(['goodAnswer', 'goodAnswer'])
24
+ expect(result.mesA11y).toEqual([
25
+ 'quizState.goodAnswer',
26
+ 'quizState.goodAnswer'
27
+ ])
28
+ })
29
+
30
+ it('addRetroStyle should return neutral style when solution is null', () => {
31
+ const result = addRetroStyle(null, [], 0)
32
+ expect(result.classRetro).toEqual(['NeutralAnswer'])
33
+ expect(result.mesA11y).toEqual(['quizState.neutralAnswer'])
34
+ })
35
+
36
+ it('retroType should return retro_positive when all answers are correct', () => {
37
+ const solution = [{ id: 1 }, { id: 2 }]
38
+ const reponse = [{ correct: true }, { correct: true }]
39
+ expect(retroType(solution, reponse)).toBe('retro_positive')
40
+ })
41
+
42
+ it('retroType should return retro_negative when some answers are wrong', () => {
43
+ const solution = ['Données primaires', 'Données secondaires']
44
+ const reponse = [
45
+ {
46
+ selected: 'Données primaires',
47
+ correct: true
48
+ },
49
+ {
50
+ selected: 'Données primaires',
51
+ correct: false
52
+ }
53
+ ]
54
+ expect(retroType(solution, reponse)).toBe('retro_negative')
55
+ })
56
+
57
+ it('retroType should return retro_neutre when solution is null', () => {
58
+ expect(retroType(null, [])).toBe('retro_neutre')
59
+ })
60
+
61
+ it('resetRetroStyle should clear arrays and emit event', () => {
62
+ const fakeBus = { $emit: vi.fn() }
63
+ const fakeEl = {}
64
+ const context = { $bus: fakeBus, $el: fakeEl }
65
+ const arr1 = [1, 2]
66
+ const arr2 = ['a']
67
+ resetRetroStyle.call(context, [arr1, arr2])
68
+ expect(arr1.length).toBe(0)
69
+ expect(arr2.length).toBe(0)
70
+ expect(fakeBus.$emit).toHaveBeenCalledWith('hide-retro', fakeEl, false)
71
+ })
72
+ })