fcad-core-dragon 2.1.2 → 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.
Files changed (33) hide show
  1. package/.gitlab-ci.yml +24 -42
  2. package/.vscode/settings.json +0 -30
  3. package/CHANGELOG +16 -0
  4. package/artifacts/playwright-report/index.html +85 -0
  5. package/package.json +29 -26
  6. package/src/components/AppBasePage.vue +3 -8
  7. package/src/components/AppCompAudio.vue +3 -3
  8. package/src/components/AppCompBranchButtons.vue +6 -3
  9. package/src/components/AppCompInputCheckBoxNx.vue +2 -1
  10. package/src/components/AppCompInputDropdownNx.vue +5 -5
  11. package/src/components/AppCompInputRadioNx.vue +2 -1
  12. package/src/components/AppCompInputTextNx.vue +0 -2
  13. package/src/components/AppCompInputTextTableNx.vue +4 -4
  14. package/src/components/AppCompInputTextToFillDropdownNx.vue +4 -6
  15. package/src/components/AppCompInputTextToFillNx.vue +1 -24
  16. package/src/components/AppCompNoteCredit.vue +9 -7
  17. package/src/components/AppCompPlayBarNext.vue +17 -11
  18. package/src/components/AppCompQuizRecall.vue +10 -6
  19. package/src/components/AppCompVideoPlayer.vue +2 -1
  20. package/src/main.js +20 -6
  21. package/src/plugins/i18n.js +8 -7
  22. package/tests/mocks/routes.mock.js +2 -0
  23. package/tests/unit/AppCompAudio.spec.js +139 -0
  24. package/tests/unit/AppCompBranchButtons.spec.js +172 -0
  25. package/tests/unit/AppCompCarousel.spec.js +62 -0
  26. package/tests/unit/AppCompNoteCredit.spec.js +49 -0
  27. package/tests/unit/AppCompVideoPlayer.spec.js +97 -102
  28. package/tests/unit/useQuiz.spec.js +72 -0
  29. package/tests/utility/colors.js +10 -0
  30. package/vitest.config.js +49 -23
  31. package/vitest.setup.js +70 -1
  32. package/junit-report.xml +0 -182
  33. package/tests/unit/AppCompQuizNext.spec.js +0 -114
@@ -1,10 +1,12 @@
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} i18n - vue-i18n internalization object.
6
+ * @param {Object} templateMessages - messages (JSON) received from the template
6
7
  */
7
- export default function mergeLocales(i18n) {
8
+ export default function initLocalisation(templateMessages) {
9
+ const i18n = createI18n({ legacy: false })
8
10
  const thisLocales = import.meta.glob('../$locales/*.json', {
9
11
  import: 'default',
10
12
  eager: true
@@ -15,13 +17,12 @@ export default function mergeLocales(i18n) {
15
17
  const locale = matched[1]
16
18
  // Using lodash to deeply merge objects
17
19
  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
- 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
- i18n.setLocaleMessage(locale, mergedMessages)
23
+
24
+ i18n.global.setLocaleMessage(locale, mergedMessages)
25
25
  }
26
26
  }
27
+ return i18n
27
28
  }
@@ -0,0 +1,2 @@
1
+ export const routes = []
2
+ export const mappedFiles = []
@@ -0,0 +1,139 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { describe, it, test, expect, vi } from 'vitest'
3
+
4
+ //Mock store appStore
5
+ vi.mock('@/module/stores/appStore', () => ({
6
+ useAppStore: () => ({})
7
+ }))
8
+
9
+ let dummyProps = {
10
+ id: 'P01',
11
+ activityRef: 'A03',
12
+ title: 'Lecteurs médias',
13
+ type: 'pg_normal',
14
+ audiosData: [
15
+ {
16
+ id: 'aud1',
17
+ mTitle: 'Et si... Annie Ernaux nous parlait',
18
+ mSources: [
19
+ {
20
+ type: 'mp3',
21
+ src: ' exemple_audio.mp3'
22
+ }
23
+ ],
24
+ mPoster: 'audio_poster.png',
25
+ mAlt: "Portrait de l'autrice Annie Ernaux",
26
+ mTranscript:
27
+ '<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>'
28
+ }
29
+ ]
30
+ }
31
+
32
+ import AppCompAudioPlayer from '@/components/AppCompAudio.vue'
33
+ import { validateAudioData } from '@/shared/validators.js'
34
+
35
+ describe('AppCompAudioPlayer', () => {
36
+ test('Validate received props', () => {
37
+ validateAudioData.mockReturnValue([])
38
+ const { audiosData } = dummyProps
39
+ mount(AppCompAudioPlayer, {
40
+ props: { audData: audiosData[0] }
41
+ })
42
+
43
+ expect(validateAudioData).toHaveBeenCalledWith({
44
+ id: 'aud1',
45
+ mTitle: 'Et si... Annie Ernaux nous parlait',
46
+ mSources: [
47
+ {
48
+ type: 'mp3',
49
+ src: ' exemple_audio.mp3'
50
+ }
51
+ ],
52
+ mPoster: 'audio_poster.png',
53
+ mAlt: "Portrait de l'autrice Annie Ernaux",
54
+ mTranscript:
55
+ '<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>'
56
+ })
57
+ })
58
+
59
+ it('renders ErrorDisplay When validator returns errors', () => {
60
+ // simulate invalid props
61
+ validateAudioData.mockReturnValue([
62
+ 'WARNING!>>> audio: 💥 Invalid declaration for audio element'
63
+ ])
64
+
65
+ const wrapper = mount(AppCompAudioPlayer, {
66
+ props: { audData: { id: null } }
67
+ })
68
+
69
+ expect(
70
+ wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
71
+ ).toBe(true)
72
+ expect(wrapper.find('audio').exists()).toBe(false)
73
+ })
74
+
75
+ it('It renders HTML audio Element', () => {
76
+ validateAudioData.mockReturnValue([]) // no Error
77
+ const wrapper = mount(AppCompAudioPlayer, {
78
+ props: { audData: { id: null } }
79
+ })
80
+ expect(
81
+ wrapper.findComponent({ name: 'AppBaseErrorDisplay' }).exists()
82
+ ).toBe(false)
83
+ expect(wrapper.find('audio').exists()).toBe(true)
84
+ })
85
+
86
+ it('audio Element has correct sources', async () => {
87
+ const { audiosData } = dummyProps
88
+
89
+ validateAudioData.mockReturnValue([]) // no Error
90
+ const wrapper = mount(AppCompAudioPlayer, {
91
+ props: { audData: audiosData[0] }
92
+ })
93
+
94
+ expect(wrapper.vm.mSources).toEqual(audiosData[0].mSources)
95
+
96
+ //sources are rendered in the DOM
97
+ await wrapper.vm.$nextTick()
98
+ const sources = wrapper.findAll('source')
99
+ expect(sources.length).toBe(1)
100
+
101
+ // --- 3) valider les valeurs réelles rendues ---
102
+ expect(sources[0].attributes().src).toBe(audiosData[0].mSources[0].src)
103
+ expect(sources[0].attributes().type).toBe('audio/mp3')
104
+ })
105
+
106
+ it('computed $auElement returns correct data structure', async () => {
107
+ validateAudioData.mockReturnValue([])
108
+ const { audiosData } = dummyProps
109
+ const wrapper = mount(AppCompAudioPlayer, {
110
+ props: { audData: audiosData[0] }
111
+ })
112
+ // Simulate refs
113
+ const mockaudioRef = { tagName: 'audio' }
114
+ const mockContainerRef = { className: '__media-container' }
115
+ wrapper.vm.$refs['m-audio'] = mockaudioRef
116
+ wrapper.vm.$refs['$media-container'] = mockContainerRef
117
+ // Simulate refs isSet (audio ready) to true
118
+ wrapper.vm.isSet = true
119
+ await wrapper.vm.$nextTick()
120
+ // get audElement object
121
+ const obj = wrapper.vm.$audElement
122
+ const id = wrapper.vm.$props.audData.id
123
+ // expected data structure to be returned
124
+ const audElement = {
125
+ id,
126
+ mType: 'audio',
127
+ mElement: { localName: 'audio' },
128
+ mTranscript:
129
+ '<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>'
130
+ }
131
+
132
+ expect(obj.id).toEqual(audElement.id)
133
+ expect(obj.mType).toBe(audElement.mType)
134
+ expect(obj.mElement.tagName.toLowerCase()).toBe(
135
+ audElement.mElement.localName
136
+ )
137
+ expect(obj.mTranscript).toBe(audElement.mTranscript)
138
+ })
139
+ })
@@ -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
+ })
@@ -0,0 +1,62 @@
1
+ import { mount } from '@vue/test-utils'
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
+ }))
10
+
11
+ const dummyProps = {
12
+ slides: [
13
+ {
14
+ imgSrc: 'https://loremflickr.com/500/500/kitten?random=1',
15
+ imgAlt: 'ceci est un chat',
16
+ title: 'Mon premier chat',
17
+ hypertext:
18
+ '<h3>Test</h3><p>Voici un texte de test pour le premier chat.</p>'
19
+ },
20
+ {
21
+ imgSrc: 'https://loremflickr.com/500/500/kitten?random=10',
22
+ imgAlt: 'ceci est un autre chat',
23
+ title: 'Le deuxième chat',
24
+ hypertext: ''
25
+ },
26
+ {
27
+ imgSrc: 'https://loremflickr.com/500/500/kitten?random=100',
28
+ imgAlt: 'ceci est un encore chat',
29
+ title: 'Le troisième chat',
30
+ hypertext: ''
31
+ }
32
+ ],
33
+ name: 'Mon super carousel'
34
+ }
35
+
36
+ import AppCompCarousel from '@/components/AppCompCarousel.vue'
37
+ import AppBaseButton from '@/components/AppBaseButton.vue'
38
+
39
+ describe('AppCompCarousel.vue', () => {
40
+ let wrapper = null
41
+ beforeEach(() => {
42
+ wrapper = mount(AppCompCarousel, {
43
+ props: dummyProps
44
+ })
45
+ })
46
+
47
+ it(`displays the good amount of images`, () => {
48
+ expect(wrapper.findAll('img').length).toBe(dummyProps.slides.length)
49
+ })
50
+
51
+ it('renders two appBaseButton components as controls', () => {
52
+ const btnComponents = wrapper.findAllComponents(AppBaseButton)
53
+ expect(btnComponents.length).toBe(2)
54
+ })
55
+
56
+ it('updates the slide counter after nextSlide click', async () => {
57
+ const btnNext = wrapper.findAllComponents(AppBaseButton)[1]
58
+ await btnNext.trigger('click')
59
+ const counterTxt = wrapper.get('.carousel-index').text()
60
+ expect(counterTxt).toBe('2 / 3')
61
+ })
62
+ })
@@ -0,0 +1,49 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { beforeEach, describe, expect, vi, test } from 'vitest'
3
+ import { createTestingPinia } from '@pinia/testing'
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
+ //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
+
27
+ describe('AppCompNoteCredit', () => {
28
+ let wrapper = null
29
+
30
+ beforeEach(() => {
31
+ wrapper = mount(AppCompNoteCredit, {
32
+ plugins: [createTestingPinia({ createSpy: vi.fn })],
33
+ global
34
+ })
35
+ })
36
+
37
+ test('It renders a the pop-up', () => {
38
+ const wrapper = mount(AppCompNoteCredit)
39
+
40
+ expect(wrapper.exists()).toBe(true)
41
+
42
+ const div = wrapper.find('#noteCredit')
43
+ expect(div.exists()).toBe(true)
44
+ })
45
+
46
+ test('It renders a list of ${dummyProps.length}', () => {
47
+ expect(wrapper.findAll('div').length).toBe(dummyProps.length)
48
+ })
49
+ })