fcad-core-dragon 2.2.0-beta.1 → 2.2.0-beta.2

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/CHANGELOG CHANGED
@@ -1,3 +1,10 @@
1
+ 2.2.0-beta.2(8 janvier 2026)
2
+ Correction du comportement de la playbar du lecteur vidéo (elle reste toujours visible, sauf en plein écran)
3
+
4
+ Correction de l’affichage du contenu de la section info (notes et sources) quand il n’y a que des notes ou que des sources
5
+
6
+ Correction du rappel de réponse ("skeleton" qui restait bloqué)
7
+
1
8
  2.2.0-beta.1(19 décembre 2025) ***Changements majeurs*** main.js et i18n.js doivent être remplacés dans vos cours
2
9
  Correction texte troué et texte troué listes déroulantes (fin de texte manquante)
3
10
 
@@ -82,4 +82,4 @@ Error generating stack: `+n.message+`
82
82
  <div id='root'></div>
83
83
  </body>
84
84
  </html>
85
- <script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,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</script>
85
+ <script id="playwrightReportBase64" type="application/zip">data:application/zip;base64,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</script>
package/package.json CHANGED
@@ -65,5 +65,5 @@
65
65
  "watch": "nodemon -e js,vue,html,json -x yalc publish --push"
66
66
  },
67
67
  "type": "module",
68
- "version": "2.2.0-beta.1"
68
+ "version": "2.2.0-beta.2"
69
69
  }
@@ -250,6 +250,7 @@ export default {
250
250
  this.isCard = this.cards && Object.keys(this.cards).length != 0
251
251
  this.idActivity = this.$router.currentRoute.value.meta.activity_ref
252
252
  this.branchs = this.$router.currentRoute.value.meta.children
253
+
253
254
  this.getbranchsData()
254
255
  },
255
256
  methods: {
@@ -271,7 +272,7 @@ export default {
271
272
  branchData.progression = progression
272
273
  if (isNaN(branchData.progression)) branchData.progression = 0
273
274
 
274
- if (this.isCard) {
275
+ if (this.isCard && this.getMatchingElement(branchData.id)) {
275
276
  const { title, text, imgFile, imgAlt, btnTitle } =
276
277
  this.getMatchingElement(branchData.id)
277
278
 
@@ -476,12 +477,14 @@ export default {
476
477
  * @return {Object} Matching element from
477
478
  */
478
479
  getMatchingElement(id) {
480
+ let matchingBranch = null
479
481
  if (this.isCustomButton) {
480
- return this.customButtons.find((c) => c.brchName === id)
482
+ matchingBranch = this.customButtons.find((c) => c.brchName === id)
481
483
  }
482
484
  if (this.isCard) {
483
- return this.cards.find((c) => c.brchName === id)
485
+ matchingBranch = this.cards.find((c) => c.brchName === id)
484
486
  }
487
+ return matchingBranch ? matchingBranch : null
485
488
  }
486
489
  }
487
490
  }
@@ -26,7 +26,7 @@
26
26
  :error-text="`Vous avez une/des erreur(s) dans la création des notes/crédits.`"
27
27
  ></app-base-error-display>
28
28
  <template v-else>
29
- <div v-if="notes" class="ctn-note">
29
+ <div v-if="hasNotes" class="ctn-note">
30
30
  <p class="t-note">{{ $t('text.title_note') }}</p>
31
31
  <div id="notes-list">
32
32
  <template
@@ -62,7 +62,7 @@
62
62
  </template>
63
63
  </div>
64
64
  </div>
65
- <div v-if="credits" class="ctn-credit">
65
+ <div v-if="hasCredits" class="ctn-credit">
66
66
  <p class="t-crdt">{{ $t('text.title_credit') }}</p>
67
67
 
68
68
  <ul id="credits-list">
@@ -109,7 +109,13 @@ export default {
109
109
  'getCurrentPage',
110
110
  'getNotes',
111
111
  'getCredits'
112
- ])
112
+ ]),
113
+ hasNotes() {
114
+ return this.notes && this.notes.length
115
+ },
116
+ hasCredits() {
117
+ return this.credits && this.credits.length
118
+ }
113
119
  },
114
120
  created() {},
115
121
  beforeUnmount() {
@@ -414,10 +420,6 @@ export default {
414
420
  }
415
421
  }
416
422
 
417
- // .note-txt {
418
- // pointer-events: none;
419
- // }
420
-
421
423
  .box-nc {
422
424
  margin-top: 54px;
423
425
  overflow-y: auto;
@@ -460,6 +460,7 @@ export default {
460
460
  const timer = reactive(new Timer(id)) // Making Timer instance reactive to be able to track changes
461
461
  const { t } = useI18n()
462
462
  return { id, store, timer, t }
463
+ //return { id, store, timer }
463
464
  },
464
465
  data() {
465
466
  return {
@@ -913,7 +914,7 @@ export default {
913
914
  * @description - handle all the listeners for medias
914
915
  */
915
916
  setMediaHandlers() {
916
- if (this.mediaElement) {
917
+ if (this.mediaElement && this.mediaElement.addEventListener) {
917
918
  //Prevent default on keys used for navigation in the player (prevent scrollbar to be trigger when changing volume)
918
919
  this.pbContainer.addEventListener('keydown', this.keysPreventDefault)
919
920
  //progressBar events
@@ -921,7 +922,7 @@ export default {
921
922
  //window handlers for playbar progress
922
923
  window.addEventListener('mousemove', this.progressWindowMove)
923
924
  window.addEventListener('mouseup', this.progressWindowUp)
924
-
925
+ // console.log('⚠️--- TEST AppCompPlayBarNext---', this.mediaElement)
925
926
  //Update data when media as a timeupdate/ended
926
927
  this.mediaElement.addEventListener(
927
928
  'timeupdate',
@@ -1580,7 +1581,10 @@ export default {
1580
1581
  */
1581
1582
  setVideoHandlers() {
1582
1583
  //Tooltip
1584
+ if (!this.progressArea || !this.progressArea.addEventListener) return
1583
1585
  this.progressArea.addEventListener('mousemove', this.updateSeekTooltip)
1586
+
1587
+ if (!this.mediaContainer || !this.mediaContainer.addEventListener) return
1584
1588
  //Fullscreen
1585
1589
  this.mediaContainer.addEventListener(
1586
1590
  'fullscreenchange',
@@ -1594,7 +1598,10 @@ export default {
1594
1598
  */
1595
1599
  removeVideoHandlers() {
1596
1600
  //Tooltip
1601
+ if (!this.progressArea || !this.progressArea.addEventListener) return
1597
1602
  this.progressArea.removeEventListener('mousemove', this.updateSeekTooltip)
1603
+ if (!this.mediaContainer || !this.mediaContainer.removeEventListener)
1604
+ return
1598
1605
  //Fullscreen
1599
1606
  this.mediaContainer.removeEventListener(
1600
1607
  'fullscreenchange',
@@ -1777,7 +1784,8 @@ export default {
1777
1784
  */
1778
1785
 
1779
1786
  hideControls() {
1780
- if (this.mediaElement.paused) return
1787
+ //Always show playbar when paused or not in fullscreen mode
1788
+ if (this.mediaElement.paused || !document.fullscreenElement) return
1781
1789
 
1782
1790
  this.hideTimer = setTimeout(() => {
1783
1791
  this.showControlsValue = false
@@ -103,27 +103,15 @@ export default {
103
103
  //Watch for user interaction change to get quizRecall answer
104
104
  getUserInteraction: {
105
105
  async handler(newValue) {
106
- //console.log('???')
107
106
  if (!this.getUserInteraction) return
108
- //console.log('--------------------')
109
107
  const { activityId, pageId } = this.quizRecallData
110
108
  const interaction = this.getPageInteraction(
111
109
  activityId,
112
110
  pageId
113
111
  ).userInteraction
114
-
115
- //console.log(interaction)
116
- if (!interaction) return //{
117
- // this.quizRecall.done == false
118
- // }
119
-
120
112
  //Get quizRecall answer
121
113
  if (this.quizRecallData && this.quizData) {
122
- //console.log('ici????')
123
114
  await this.getQuizRecallAnswer(interaction)
124
- } else {
125
- //console.log('laaaaaa ???? ')
126
- this.quizRecall.done == false
127
115
  }
128
116
  },
129
117
  immediate: true,
@@ -216,6 +204,13 @@ export default {
216
204
  //Get datas from quizRecallData and userData and add them to quizRecall object
217
205
  async getQuizRecallAnswer(userData) {
218
206
  await this.$nextTick() //wait for the DOM to update
207
+ //userData will be undefined if the page containing the target quiz was never visited
208
+ if (typeof userData === 'undefined') {
209
+ this.quizRecall.done = false //set quiz recall message (quiz not completed)
210
+ this.isReady = true //hide the skeleton
211
+ return
212
+ }
213
+
219
214
  const { quizId, hypertext_done, hypertext_undone, title, titletag } =
220
215
  this.quizRecallData
221
216
 
@@ -252,7 +247,6 @@ export default {
252
247
  this.quizRecall.answer = quizAnswer.value[0].filled || ''
253
248
  this.quizRecall.ennonce = this.quizData.ennonce
254
249
  }
255
- this.quizRecall
256
250
  this.isReady = true //Set isReady to true to display the component
257
251
  },
258
252
 
package/src/main.js CHANGED
@@ -489,8 +489,9 @@ export default {
489
489
 
490
490
  appStore.applicationSettings = settingsOptions
491
491
  //=================================END SETTING PREFERENCES ====================================//
492
-
493
- const i18n = initLocalisation()
492
+ // const appi18b = options.i18n
493
+ // console.log('i18n options received in appBase plugin...', appi18b)
494
+ const i18n = initLocalisation(options.i18n)
494
495
  app.use(i18n)
495
496
  app.use(helper)
496
497
  app.use(analytics)
@@ -7,7 +7,6 @@ import { createI18n } from 'vue-i18n'
7
7
  */
8
8
  export default function initLocalisation(templateMessages) {
9
9
  const i18n = createI18n({ legacy: false })
10
-
11
10
  const thisLocales = import.meta.glob('../$locales/*.json', {
12
11
  import: 'default',
13
12
  eager: true
@@ -18,7 +17,7 @@ export default function initLocalisation(templateMessages) {
18
17
  const locale = matched[1]
19
18
  // Using lodash to deeply merge objects
20
19
  const coreMessages = JSON.parse(JSON.stringify(thisLocales[path]))
21
- const mergedMessages = _.merge(coreMessages, templateMessages)
20
+ const mergedMessages = _.merge(coreMessages, templateMessages[locale])
22
21
 
23
22
  // Since merLocalMessage will do a shallow merge of the object. We will use setLocalMessage to replace the message with new message object
24
23
 
@@ -0,0 +1,2 @@
1
+ export const routes = []
2
+ export const mappedFiles = []
@@ -1,5 +1,10 @@
1
1
  import { mount } from '@vue/test-utils'
2
- import { describe, it, test, expect } from 'vitest'
2
+ import { describe, it, test, expect, vi } from 'vitest'
3
+
4
+ //Mock store appStore
5
+ vi.mock('@/module/stores/appStore', () => ({
6
+ useAppStore: () => ({})
7
+ }))
3
8
 
4
9
  let dummyProps = {
5
10
  id: 'P01',
@@ -0,0 +1,172 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { createTestingPinia } from '@pinia/testing'
4
+ import { colors } from '../utility/colors'
5
+
6
+ //Mock store appStore
7
+ vi.mock('@/module/stores/appStore', () => ({
8
+ useAppStore: () => ({
9
+ getAllCompleted: { A01: [] }
10
+ })
11
+ }))
12
+
13
+ import AppCompBranchButtons from '@/components/AppCompBranchButtons.vue'
14
+
15
+ const dummyProps = {
16
+ cards: [
17
+ {
18
+ imgFile: 'https://loremflickr.com/500/500/kitten',
19
+ imgAlt: 'Test img 1',
20
+ title: 'banane',
21
+ text: 'Ceci est un embranchement factice, affiché sous la forme de carte.',
22
+ brchName: 'P02_E01'
23
+ },
24
+ {
25
+ imgFile: 'https://loremflickr.com/500/500/kitten',
26
+ imgAlt: 'Test img 2',
27
+ title: 'Titre embranchement 2',
28
+ text: 'Ceci est un embranchement factice, affiché sous la forme de carte, pour toto',
29
+ brchName: 'P02_E02'
30
+ },
31
+ {
32
+ imgFile: 'https://loremflickr.com/500/500/kitten',
33
+ imgAlt: 'Test img 3',
34
+ title: 'Titre embranchement 3',
35
+ text: 'Ceci est un embranchement factice, affiché sous la forme de carte, pour toto',
36
+ brchName: 'P02_E03'
37
+ }
38
+ ],
39
+ customButtons: [
40
+ { name: 'bouton_A02_P01_E01', brchName: 'P02_E01' },
41
+ { name: 'bouton_A02_P01_E02', brchName: 'P02_E02' }
42
+ ]
43
+ }
44
+
45
+ describe('AppCompBranchButtons.vue', () => {
46
+ let globalConfig
47
+
48
+ beforeEach(() => {
49
+ globalConfig = {
50
+ plugins: [createTestingPinia({ createSpy: vi.fn })],
51
+ mocks: {
52
+ $router: {
53
+ currentRoute: {
54
+ value: {
55
+ meta: {
56
+ activity_ref: 'A01',
57
+ children: [
58
+ {
59
+ _ref: 'P02_E01',
60
+ _path: 'branche-1',
61
+ _namedRoute: 'activite_3.page_2.branche_1'
62
+ },
63
+ {
64
+ _ref: 'P02_E02',
65
+ _path: 'branche-2',
66
+ _namedRoute: 'activite_3.page_2.branche_2'
67
+ },
68
+ {
69
+ _ref: 'P02_E03',
70
+ _path: 'branche-3',
71
+ _namedRoute: 'activite_3.page_2.branche_3'
72
+ }
73
+ ]
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ vi.clearAllMocks()
81
+ })
82
+
83
+ it('Renders ErrorDisplay when cards has errors', async () => {
84
+ const wrapper = mount(AppCompBranchButtons, {
85
+ global: globalConfig,
86
+ props: {
87
+ cards: [dummyProps.cards[0], dummyProps.cards[1]] // Only 2 cards passed instead of 3
88
+ }
89
+ })
90
+
91
+ await wrapper.vm.$nextTick() // Must wait for DOM updates to display AppBaseErrorDisplay
92
+
93
+ if (wrapper.vm.hasErrors) {
94
+ const cards = wrapper.vm.cards
95
+ console.log(colors.green, `\n ✅ Current cards: ${JSON.stringify(cards)}`)
96
+ console.log(
97
+ '✅ wrapper.vm.hasErrors:',
98
+ wrapper.vm.hasErrors,
99
+ colors.reset
100
+ )
101
+ }
102
+ // Error should be displayed
103
+ expect(wrapper.vm.hasErrors.length).toBeGreaterThan(0)
104
+ expect(
105
+ wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
106
+ ).toBe(true)
107
+ })
108
+
109
+ it('Renders ErrorDisplay when customButtons has errors', async () => {
110
+ const wrapper = mount(AppCompBranchButtons, {
111
+ global: globalConfig,
112
+ props: {
113
+ customButtons: [
114
+ { brchName: 'BRANCH_1' }, // missing required key 'name' here
115
+ { name: 'bouton_A02_P01_E01', brchName: 'P02_E02' },
116
+ { name: 'bouton_A02_P01_E01', brchName: 'P02_E03' }
117
+ ]
118
+ }
119
+ })
120
+ await wrapper.vm.$nextTick() // Must wait for DOM updates to display AppBaseErrorDisplay
121
+
122
+ if (wrapper.vm.hasErrors) {
123
+ const el = wrapper.vm.customButtons[0]
124
+ console.log(colors.green, `\n ✅ Current cards: ${JSON.stringify(el)}`)
125
+ console.log(
126
+ '✅ wrapper.vm.hasErrors:',
127
+ wrapper.vm.hasErrors,
128
+ colors.reset
129
+ )
130
+ }
131
+ // hasError should have a message
132
+ expect(wrapper.vm.hasErrors.length).toBeGreaterThan(0)
133
+ // Error should be displayed
134
+ expect(
135
+ wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
136
+ ).toBe(true)
137
+ })
138
+
139
+ //=====================================================================
140
+ it('It renders the buttons when no errors', async () => {
141
+ const wrapper = mount(AppCompBranchButtons, {
142
+ global: {
143
+ ...globalConfig
144
+ },
145
+ props: {
146
+ cards: dummyProps.cards
147
+ }
148
+ })
149
+
150
+ await wrapper.vm.$nextTick() // Must wait for DOM updates to display AppBaseErrorDisplay
151
+
152
+ if (wrapper.vm.hasErrors) {
153
+ console.log(colors.red)
154
+ console.log(
155
+ '\n wrapper.vm.hasErrors:',
156
+ wrapper.vm.hasErrors,
157
+ colors.reset
158
+ )
159
+ }
160
+
161
+ // expect hasErrors to return false
162
+ expect(wrapper.vm.hasErrors).toBe(false)
163
+ //error display not to be in DOM
164
+ expect(
165
+ wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
166
+ ).not.toBe(true)
167
+ // element to rendered correctly in DOM
168
+ expect(wrapper.find('#branch-buttons-component').exists()).toBe(true)
169
+ //should display 3 buttons
170
+ expect(wrapper.findAll('.branch-btn').length).toBe(3)
171
+ })
172
+ })
@@ -1,7 +1,12 @@
1
1
  import { mount } from '@vue/test-utils'
2
- import AppCompCarousel from '@/components/AppCompCarousel.vue'
3
- import AppBaseButton from '@/components/AppBaseButton.vue'
4
- import { beforeEach, describe, expect, it } from 'vitest'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
+
4
+ //Mock store appStore
5
+ vi.mock('@/module/stores/appStore', () => ({
6
+ useAppStore: () => ({
7
+ getAppConfigs: { lang: 'FR' }
8
+ })
9
+ }))
5
10
 
6
11
  const dummyProps = {
7
12
  slides: [
@@ -28,6 +33,9 @@ const dummyProps = {
28
33
  name: 'Mon super carousel'
29
34
  }
30
35
 
36
+ import AppCompCarousel from '@/components/AppCompCarousel.vue'
37
+ import AppBaseButton from '@/components/AppBaseButton.vue'
38
+
31
39
  describe('AppCompCarousel.vue', () => {
32
40
  let wrapper = null
33
41
  beforeEach(() => {
@@ -1,6 +1,6 @@
1
1
  import { mount } from '@vue/test-utils'
2
- import AppCompNoteCredit from '../../src/components/AppCompNoteCredit.vue'
3
2
  import { beforeEach, describe, expect, vi, test } from 'vitest'
3
+ import { createTestingPinia } from '@pinia/testing'
4
4
 
5
5
  const dummyProps = [
6
6
  {
@@ -13,33 +13,24 @@ const dummyProps = [
13
13
  }
14
14
  ]
15
15
 
16
+ //Mock store appStore
17
+ vi.mock('@/module/stores/appStore', () => ({
18
+ useAppStore: () => ({
19
+ getDataNoteCredit: dummyProps,
20
+ getAllActivities: 'A01',
21
+ getCurrentPage: 'P01'
22
+ })
23
+ }))
24
+ // Import of components must happen after mocks to prevent side effects
25
+ import AppCompNoteCredit from '../../src/components/AppCompNoteCredit.vue'
26
+
16
27
  describe('AppCompNoteCredit', () => {
17
28
  let wrapper = null
18
29
 
19
30
  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
31
  wrapper = mount(AppCompNoteCredit, {
37
- global: {
38
- stubs: {
39
- 'app-base-button': true,
40
- 'app-base-error-display': true
41
- }
42
- }
32
+ plugins: [createTestingPinia({ createSpy: vi.fn })],
33
+ global
43
34
  })
44
35
  })
45
36
 
@@ -1,5 +1,10 @@
1
1
  import { mount } from '@vue/test-utils'
2
- import { describe, it, test, expect } from 'vitest'
2
+ import { describe, it, test, expect, vi } from 'vitest'
3
+
4
+ //Mock store appStore
5
+ vi.mock('@/module/stores/appStore', () => ({
6
+ useAppStore: () => ({})
7
+ }))
3
8
 
4
9
  let dummyProps = {
5
10
  id: 'P01',
@@ -99,8 +104,6 @@ describe('AppCompVideoPlayer', () => {
99
104
  await wrapper.vm.$nextTick()
100
105
  const sources = wrapper.findAll('source')
101
106
  expect(sources.length).toBe(1)
102
-
103
- // --- 3) valider les valeurs réelles rendues ---
104
107
  expect(sources[0].attributes().src).toBe(videosData[0].mSources[0].src)
105
108
  expect(sources[0].attributes().type).toBe('video/mp4')
106
109
  })
@@ -0,0 +1,10 @@
1
+ export const colors = {
2
+ red: '\x1b[31m',
3
+ green: '\x1b[32m',
4
+ yellow: '\x1b[33m',
5
+ blue: '\x1b[34m',
6
+ magenta: '\x1b[35m',
7
+ cyan: '\x1b[36m',
8
+ white: '\x1b[37m',
9
+ reset: '\x1b[0m'
10
+ }
package/vitest.config.js CHANGED
@@ -13,30 +13,42 @@ export default defineConfig({
13
13
  }
14
14
  }
15
15
  },
16
+
17
+ resolve: {
18
+ alias: {
19
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
20
+ '@/router/routes.js': fileURLToPath(
21
+ new URL('./tests/mocks/routes.mock.js', import.meta.url)
22
+ )
23
+ }
24
+ },
25
+
16
26
  test: {
17
27
  setupFiles: ['vitest.setup.js'],
18
- alias: {
19
- '@': fileURLToPath(new URL('./src', import.meta.url))
20
- },
28
+ environment: 'happy-dom',
29
+
21
30
  server: {
22
31
  deps: {
23
32
  inline: ['vuetify']
24
33
  }
25
34
  },
26
- environment: 'happy-dom',
35
+
27
36
  exclude: [...configDefaults.exclude, 'tests/component/**', '**/_*.spec.js'],
37
+
28
38
  root: fileURLToPath(new URL('./', import.meta.url)),
39
+
29
40
  coverage: {
30
41
  provider: 'v8',
31
42
  reporter: ['text', 'json', 'cobertura'],
32
43
  reportsDirectory: './coverage',
33
44
  include: ['src/**/*.{js,vue}'],
34
45
  exclude: ['tests/**/*'],
35
- lines: 0, // test must cover at least 80% of lines
36
- branches: 0, // test must cover at least 80% of branches (if, else, switch, case...)
37
- functions: 0, // test must cover at least 80% of functions
38
- statements: 0 // test must cover at least 80% of statements/instructions
46
+ lines: 0,
47
+ branches: 0,
48
+ functions: 0,
49
+ statements: 0
39
50
  }
40
51
  },
52
+
41
53
  plugins: [vue()]
42
54
  })
package/vitest.setup.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //Mock Vuetify, vue-i18n and event bus
2
- import { vi } from 'vitest'
2
+ import { vi, beforeEach } from 'vitest'
3
3
  import { config } from '@vue/test-utils'
4
4
  import { createI18n } from 'vue-i18n'
5
5
  import { createVuetify } from 'vuetify'
@@ -8,6 +8,7 @@ import * as directives from 'vuetify/directives'
8
8
  import * as frMessages from './src/$locales/fr.json'
9
9
  import * as enMessages from './src/$locales/en.json'
10
10
  import bus from './src/plugins/bus'
11
+ import { createPinia, setActivePinia } from 'pinia'
11
12
 
12
13
  //create vuetify instance
13
14
  const vuetify = createVuetify({
@@ -25,38 +26,20 @@ const i18n = createI18n({
25
26
  }
26
27
  })
27
28
 
28
- // Suppress console warnings and errors during tests
29
29
  vi.spyOn(console, 'warn').mockImplementation(() => {})
30
30
  vi.spyOn(console, 'error').mockImplementation(() => {})
31
31
 
32
32
  // Global mock of Router
33
33
  // =========================================
34
- vi.mock('@/router/index.js', () => ({
35
- default: {
34
+ vi.mock('vue-router', () => ({
35
+ useRouter: () => ({
36
36
  push: vi.fn(),
37
- back: vi.fn(),
38
- currentRoute: { value: { meta: {}, params: {}, query: {} } }
39
- }
40
- }))
41
-
42
- // Global mock of Pinia store
43
- // =========================================
44
- vi.mock('@/module/stores/appStore', () => ({
45
- useAppStore: () => ({
46
- getAppConfigs: {
47
- lang: 'fr'
48
- },
49
-
50
- getCurrentBrowser: 'Chrome',
51
-
52
- getCurrentPage: {
53
- id: 'P01',
54
- activityRef: 'A03'
55
- },
56
-
57
- updateCurrentMediaElements: vi.fn(() => Promise.resolve()),
58
- getPageInteraction: {},
59
- getUserInteraction: {}
37
+ back: vi.fn()
38
+ }),
39
+ useRoute: () => ({
40
+ meta: {},
41
+ params: {},
42
+ query: {}
60
43
  })
61
44
  }))
62
45
 
@@ -67,13 +50,28 @@ vi.mock('@/shared/validators.js', () => ({
67
50
  validateString: vi.fn(() => true),
68
51
  validateNumber: vi.fn(() => true),
69
52
  validateVideoData: vi.fn(() => []),
70
- validateAudioData: vi.fn(() => [])
53
+ validateAudioData: vi.fn(() => []),
54
+ validateProp: vi.fn(() => [])
71
55
  }))
72
56
 
73
57
  //setup as global plugins used by all tests
74
- config.global.plugins = [i18n, vuetify, bus]
58
+ config.global.plugins.push(i18n, vuetify, bus)
59
+
60
+ //Setup a new pinia instance before each test
61
+ //=========================================
62
+ //NOTE:
63
+ // Do NOT use createTestingPinia() globally
64
+ // Pinia mocking must be done per-test using createTestingPinia()
65
+ // to avoid hidden side effects and false-positive tests.
66
+ // https://pinia.vuejs.org/cookbook/testing.html#using-the-testing-plugin
67
+ // https://pinia.vuejs.org/cookbook/testing.html
68
+
69
+ beforeEach(() => {
70
+ setActivePinia(createPinia())
71
+ })
75
72
 
76
73
  // Global mock of components
74
+ //=========================================
77
75
  config.global.stubs = {
78
76
  AppBaseErrorDisplay: true,
79
77
  AppBaseSkeleton: true,
@@ -92,5 +90,8 @@ config.global.mocks = {
92
90
  $on: vi.fn(),
93
91
  $emit: vi.fn(),
94
92
  $off: vi.fn()
93
+ },
94
+ $helper: {
95
+ formatTime: vi.fn((t) => `00:${t}`)
95
96
  }
96
97
  }
@@ -1,112 +0,0 @@
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
- })