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.
@@ -0,0 +1,54 @@
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'
5
+
6
+ const dummyProps = {
7
+ slides: [
8
+ {
9
+ imgSrc: 'https://loremflickr.com/500/500/kitten?random=1',
10
+ imgAlt: 'ceci est un chat',
11
+ title: 'Mon premier chat',
12
+ hypertext:
13
+ '<h3>Test</h3><p>Voici un texte de test pour le premier chat.</p>'
14
+ },
15
+ {
16
+ imgSrc: 'https://loremflickr.com/500/500/kitten?random=10',
17
+ imgAlt: 'ceci est un autre chat',
18
+ title: 'Le deuxième chat',
19
+ hypertext: ''
20
+ },
21
+ {
22
+ imgSrc: 'https://loremflickr.com/500/500/kitten?random=100',
23
+ imgAlt: 'ceci est un encore chat',
24
+ title: 'Le troisième chat',
25
+ hypertext: ''
26
+ }
27
+ ],
28
+ name: 'Mon super carousel'
29
+ }
30
+
31
+ describe('AppCompCarousel.vue', () => {
32
+ let wrapper = null
33
+ beforeEach(() => {
34
+ wrapper = mount(AppCompCarousel, {
35
+ props: dummyProps
36
+ })
37
+ })
38
+
39
+ it(`displays the good amount of images`, () => {
40
+ expect(wrapper.findAll('img').length).toBe(dummyProps.slides.length)
41
+ })
42
+
43
+ it('renders two appBaseButton components as controls', () => {
44
+ const btnComponents = wrapper.findAllComponents(AppBaseButton)
45
+ expect(btnComponents.length).toBe(2)
46
+ })
47
+
48
+ it('updates the slide counter after nextSlide click', async () => {
49
+ const btnNext = wrapper.findAllComponents(AppBaseButton)[1]
50
+ await btnNext.trigger('click')
51
+ const counterTxt = wrapper.get('.carousel-index').text()
52
+ expect(counterTxt).toBe('2 / 3')
53
+ })
54
+ })
@@ -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
+ })
@@ -77,7 +77,7 @@ describe('AppCompQuizNext', () => {
77
77
  expect(wrapper.find('.stub-radio').exists()).toBe(true)
78
78
  })
79
79
 
80
- // it('rend un input texte si la question est de type text', () => {
80
+ // it('rend un input texte si la question est de type text', async() => {
81
81
  // const wrapper = mount(AppCompQuizNext, {
82
82
  // props: {
83
83
  // question: {
@@ -88,7 +88,6 @@ describe('AppCompQuizNext', () => {
88
88
  // },
89
89
  // global: { stubs }
90
90
  // })
91
-
92
91
  // expect(wrapper.find('.stub-text').exists()).toBe(true)
93
92
  // })
94
93
 
@@ -106,7 +105,6 @@ describe('AppCompQuizNext', () => {
106
105
  // }
107
106
  // }
108
107
  // })
109
-
110
108
  // await wrapper.find('input').trigger('input')
111
109
 
112
110
  // expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['hello'])
@@ -1,104 +1,37 @@
1
1
  import { mount } from '@vue/test-utils'
2
- import { beforeEach, describe, it, test, expect, vi } from 'vitest'
3
- import { createTestingPinia } from '@pinia/testing'
4
-
5
- let dummyProps = {}
6
-
7
- //TODO: Logic de test pour le composant videos
8
- // 1- valider que composant gerer bien les props reçu-OK
9
- // 2- valider que le composant produit une balise HTML video en cas de success -OK
10
- // 3- valider le rendu du composant en cas d'erreur (errorDysplay) -OK
11
- // 4- Valider que le validateur de video active bien sur les datas reçu -OK
12
- // 5- valider certaine methode clée fonctionne
13
-
14
- beforeEach(() => {
15
- dummyProps = {
16
- id: 'P01',
17
- activityRef: 'A03',
18
- title: 'Lecteurs médias',
19
- type: 'pg_normal',
20
- videosData: [
21
- {
22
- id: 'vid1',
23
- mSources: [
24
- {
25
- type: 'mp4',
26
- src: 'exemple_video.mp4'
27
- }
28
- ],
29
- mSubtitles: [
30
- {
31
- label: 'Français',
32
- src: 'exemple_soustitres.vtt',
33
- srclang: 'fr'
34
- }
35
- ],
36
- mPoster: 'video_poster.jpg',
37
- mTranscript: 'exemple_transcript.html' // The file MUST be host in the public folder of the project
38
- }
39
- ]
40
- }
41
- vi.mock('@/shared/generalfuncs.js', () => ({
42
- fileAssets: { getActivities: () => [] }
43
- }))
44
-
45
- vi.mock('@/shared/validators.js', () => ({
46
- validateObjType: () => true,
47
- validateString: () => true,
48
- validateVideoData: vi.fn(),
49
- validateNumber: () => true
50
- }))
51
-
52
- vi.mock('@/module/stores/appStore', () => {
53
- return {
54
- useAppStore: () => ({
55
- getCurrentPage: {
56
- id: 'P01',
57
- activityRef: 'A01',
58
- videosData: [
59
- {
60
- id: 'vid1',
61
- mSources: [
62
- {
63
- type: 'mp4',
64
- src: ''
65
- }
66
- ],
67
- mSubtitles: [
68
- {
69
- label: 'Français',
70
- src: '',
71
- srclang: 'fr'
72
- }
73
- ],
74
- mPoster: '',
75
- mTranscript: 'exemple_transcript.html' // The file MUST be host in the public folder of the project
76
- }
77
- ]
78
- },
79
- getPageInteraction: () => ({ ...dummyProps }),
80
- getUserInteraction: () => ({ ...dummyProps })
81
- })
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
82
27
  }
83
- })
84
- })
28
+ ]
29
+ }
85
30
 
86
31
  import AppCompVideoPlayer from '@/components/AppCompVideoPlayer.vue'
87
- // import AppBaseErrorDisplay from '@/components/AppBaseErrorDisplay.vue'
88
32
  import { validateVideoData } from '@/shared/validators.js'
89
33
 
90
34
  describe('AppCompVideoPlayer', () => {
91
- const stubs = {
92
- AppBaseErrorDisplay: {
93
- name: 'AppBaseErrorDisplay',
94
- template: '<div class="stub-error"></div>',
95
- props: {
96
- /* ... (props omises pour la clarté) ... */
97
- }
98
- },
99
- AppBaseSkeleton: { template: '<div class="stub-skeleton"></div>' },
100
- AppBaseButton: { template: '<button class="stub-button"></button>' }
101
- }
102
35
  test('Validate received props', () => {
103
36
  validateVideoData.mockReturnValue([])
104
37
  const { videosData } = dummyProps
@@ -133,8 +66,7 @@ describe('AppCompVideoPlayer', () => {
133
66
  ])
134
67
 
135
68
  const wrapper = mount(AppCompVideoPlayer, {
136
- props: { vidData: { id: null } },
137
- global: { stubs, plugins: [createTestingPinia({ createSpy: vi.fn })] }
69
+ props: { vidData: { id: null } }
138
70
  })
139
71
 
140
72
  expect(
@@ -146,8 +78,7 @@ describe('AppCompVideoPlayer', () => {
146
78
  it('It renders HTML video Element', () => {
147
79
  validateVideoData.mockReturnValue([]) // no Error
148
80
  const wrapper = mount(AppCompVideoPlayer, {
149
- props: { vidData: { id: null } },
150
- global: { stubs }
81
+ props: { vidData: { id: null } }
151
82
  })
152
83
  expect(
153
84
  wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
@@ -159,8 +90,7 @@ describe('AppCompVideoPlayer', () => {
159
90
  const { videosData } = dummyProps
160
91
  validateVideoData.mockReturnValue([]) // no Error
161
92
  const wrapper = mount(AppCompVideoPlayer, {
162
- props: { vidData: videosData[0] },
163
- global: { stubs }
93
+ props: { vidData: videosData[0] }
164
94
  })
165
95
 
166
96
  expect(wrapper.vm.vidSources).toEqual(videosData[0].mSources)
@@ -174,4 +104,66 @@ describe('AppCompVideoPlayer', () => {
174
104
  expect(sources[0].attributes().src).toBe(videosData[0].mSources[0].src)
175
105
  expect(sources[0].attributes().type).toBe('video/mp4')
176
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
+ })
177
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
+ })
package/vitest.config.js CHANGED
@@ -1,28 +1,42 @@
1
1
  /**
2
- * @vitest-environment jsdom
2
+ * @vitest-environment happy-dom
3
3
  */
4
4
  import { fileURLToPath, URL } from 'node:url'
5
- import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
5
+ import { defineConfig, configDefaults } from 'vitest/config'
6
6
  import vue from '@vitejs/plugin-vue'
7
- import viteConfig from './vitest.config'
8
7
 
9
- export default mergeConfig(
10
- viteConfig,
11
- defineConfig({
12
- test: {
13
- setupFiles: ['vitest.setup.js'],
14
- alias: {
15
- '@': fileURLToPath(new URL('./src', import.meta.url))
16
- },
17
- server: {
18
- deps: {
19
- inline: ['vuetify']
20
- }
21
- },
22
- environment: 'jsdom',
23
- exclude: [...configDefaults.exclude, 'tests/component/**', '**/_*.spec.js'],
24
- root: fileURLToPath(new URL('./', import.meta.url))
8
+ export default defineConfig({
9
+ css: {
10
+ preprocessorOptions: {
11
+ scss: {
12
+ api: 'modern'
13
+ }
14
+ }
15
+ },
16
+ test: {
17
+ setupFiles: ['vitest.setup.js'],
18
+ alias: {
19
+ '@': fileURLToPath(new URL('./src', import.meta.url))
25
20
  },
26
- plugins: [vue()]
27
- })
28
- )
21
+ server: {
22
+ deps: {
23
+ inline: ['vuetify']
24
+ }
25
+ },
26
+ environment: 'happy-dom',
27
+ exclude: [...configDefaults.exclude, 'tests/component/**', '**/_*.spec.js'],
28
+ root: fileURLToPath(new URL('./', import.meta.url)),
29
+ coverage: {
30
+ provider: 'v8',
31
+ reporter: ['text', 'json', 'cobertura'],
32
+ reportsDirectory: './coverage',
33
+ include: ['src/**/*.{js,vue}'],
34
+ 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
39
+ }
40
+ },
41
+ plugins: [vue()]
42
+ })
package/vitest.setup.js CHANGED
@@ -1,4 +1,5 @@
1
1
  //Mock Vuetify, vue-i18n and event bus
2
+ import { vi } from 'vitest'
2
3
  import { config } from '@vue/test-utils'
3
4
  import { createI18n } from 'vue-i18n'
4
5
  import { createVuetify } from 'vuetify'
@@ -24,5 +25,72 @@ const i18n = createI18n({
24
25
  }
25
26
  })
26
27
 
28
+ // Suppress console warnings and errors during tests
29
+ vi.spyOn(console, 'warn').mockImplementation(() => {})
30
+ vi.spyOn(console, 'error').mockImplementation(() => {})
31
+
32
+ // Global mock of Router
33
+ // =========================================
34
+ vi.mock('@/router/index.js', () => ({
35
+ default: {
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: {}
60
+ })
61
+ }))
62
+
63
+ // Global mock of validators
64
+ // =========================================
65
+ vi.mock('@/shared/validators.js', () => ({
66
+ validateObjType: vi.fn(() => true),
67
+ validateString: vi.fn(() => true),
68
+ validateNumber: vi.fn(() => true),
69
+ validateVideoData: vi.fn(() => []),
70
+ validateAudioData: vi.fn(() => [])
71
+ }))
72
+
27
73
  //setup as global plugins used by all tests
28
74
  config.global.plugins = [i18n, vuetify, bus]
75
+
76
+ // Global mock of components
77
+ config.global.stubs = {
78
+ AppBaseErrorDisplay: true,
79
+ AppBaseSkeleton: true,
80
+ AppBaseButton: true,
81
+ AppCompPlayBarNext: true
82
+ }
83
+
84
+ config.global.mocks = {
85
+ $route: {
86
+ meta: {
87
+ activity_ref: 'A03',
88
+ id: 'P01'
89
+ }
90
+ },
91
+ $bus: {
92
+ $on: vi.fn(),
93
+ $emit: vi.fn(),
94
+ $off: vi.fn()
95
+ }
96
+ }