@cnamts/synapse 1.0.22 → 1.0.23

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 (190) hide show
  1. package/dist/{DateFilter-B5n-ZkLi.js → DateFilter-Dc-gSGwk.js} +1 -1
  2. package/dist/{NumberFilter-CtiZ9uj8.js → NumberFilter-vP38Wp6j.js} +1 -1
  3. package/dist/{PeriodFilter-DzqiMb-b.js → PeriodFilter-Ba1uYUnT.js} +1 -1
  4. package/dist/{SelectFilter-BOYlF7rX.js → SelectFilter-BioGT6Nn.js} +1 -1
  5. package/dist/{TextFilter-BOFRNfcX.js → TextFilter-B84dpnoq.js} +1 -1
  6. package/dist/components/Accordion/Accordion.d.ts +13 -2
  7. package/dist/components/Accordion/composables/useAccordionState.d.ts +2 -1
  8. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +7 -7
  9. package/dist/components/Amelipro/AmeliproCheckbox/AmeliproCheckbox.d.ts +1 -1
  10. package/dist/components/Amelipro/AmeliproCustomSelector/AmeliproCustomSelector.d.ts +1 -1
  11. package/dist/components/Amelipro/AmeliproPostalAddressField/AmeliproPostalAddressField.d.ts +1 -1
  12. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +7 -7
  13. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +16 -16
  14. package/dist/components/Amelipro/AmeliproTextArea/AmeliproTextArea.d.ts +1 -1
  15. package/dist/components/Amelipro/AmeliproTextField/AmeliproTextField.d.ts +1 -1
  16. package/dist/components/Customs/Selects/SyAutocomplete/SyAutocomplete.d.ts +22 -1
  17. package/dist/components/Customs/Selects/SyAutocomplete/locales.d.ts +5 -0
  18. package/dist/components/Customs/Selects/SyInputSelect/SyInputSelect.d.ts +1 -1
  19. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +1 -1
  20. package/dist/components/Customs/Selects/SySelect/locales.d.ts +1 -0
  21. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +1 -1
  22. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +1 -1
  23. package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +1 -1
  24. package/dist/components/Customs/SyTextField/SyTextField.d.ts +5 -2
  25. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +13 -9
  26. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +7 -5
  27. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +2 -1
  28. package/dist/components/ErrorPage/ErrorPage.d.ts +3 -1
  29. package/dist/components/FileList/UploadItem/UploadItem.d.ts +6 -0
  30. package/dist/components/FileList/UploadItem/locales.d.ts +1 -4
  31. package/dist/components/FileUpload/FileUploadContent.d.ts +2 -0
  32. package/dist/components/FileUpload/validateFiles.d.ts +2 -1
  33. package/dist/components/HeaderBar/HeaderBar.d.ts +2 -1
  34. package/dist/components/HeaderBar/HeaderLogo/HeaderLogo.d.ts +2 -1
  35. package/dist/components/HeaderNavigationBar/HeaderNavigationBar.d.ts +2 -1
  36. package/dist/components/MonthPicker/MonthPicker.d.ts +1939 -0
  37. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +1899 -0
  38. package/dist/components/MonthPicker/MonthPickerText/useTextField.d.ts +21 -0
  39. package/dist/components/MonthPicker/MonthPickerVisual/MonthPickerVisual.d.ts +21 -0
  40. package/dist/components/MonthPicker/MonthPickerVisual/MonthPickerVisualProps.d.ts +12 -0
  41. package/dist/components/MonthPicker/MonthPickerVisual/MonthSelector.d.ts +11 -0
  42. package/dist/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.d.ts +6 -0
  43. package/dist/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.d.ts +14 -0
  44. package/dist/components/MonthPicker/MonthPickerVisual/YearSelector.d.ts +14 -0
  45. package/dist/components/MonthPicker/MonthPickerVisual/useMonthGrid.d.ts +9 -0
  46. package/dist/components/MonthPicker/MonthPickerVisual/useYearGrid.d.ts +8 -0
  47. package/dist/components/MonthPicker/MonthPickerVisual/utils.d.ts +8 -0
  48. package/dist/components/MonthPicker/locales.d.ts +12 -0
  49. package/dist/components/MonthPicker/useMonthPickerValidation.d.ts +25 -0
  50. package/dist/components/NirField/NirField.d.ts +3 -1
  51. package/dist/components/NotificationBar/Notification/Notification.d.ts +3 -0
  52. package/dist/components/PasswordField/PasswordField.d.ts +1 -1
  53. package/dist/components/PeriodField/PeriodField.d.ts +29 -21
  54. package/dist/components/PhoneField/PhoneField.d.ts +2 -1
  55. package/dist/components/SyBtnMenu/SyBtnMenu.d.ts +1 -1
  56. package/dist/components/SyHeading/SyHeading.a11y.test.d.ts +1 -0
  57. package/dist/components/SyHeading/SyHeading.d.ts +4 -2
  58. package/dist/components/SyHeading/SyHeading.test.d.ts +1 -0
  59. package/dist/components/SyTextArea/SyTextArea.d.ts +1 -1
  60. package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +4 -4
  61. package/dist/components/Tables/SyTable/SyTable.d.ts +4 -4
  62. package/dist/components/Tables/common/SyTablePagination.d.ts +6 -6
  63. package/dist/components/index.d.ts +1 -0
  64. package/dist/design-system-v3.js +102 -99
  65. package/dist/design-system-v3.umd.cjs +126 -126
  66. package/dist/designTokens/tokens/cnam/cnamContextual.d.ts +5 -0
  67. package/dist/{main-CEl4J8_T.js → main-aLKwdMi1.js} +11167 -10522
  68. package/dist/main.d.ts +1 -0
  69. package/dist/style.css +1 -1
  70. package/package.json +10 -4
  71. package/src/assets/apTokens.scss +2 -2
  72. package/src/assets/overrides/_btns.scss +8 -0
  73. package/src/assets/overrides/_forms.scss +9 -0
  74. package/src/assets/overrides/_icons.scss +38 -9
  75. package/src/assets/overrides/_tables.scss +19 -0
  76. package/src/components/Accordion/Accordion.mdx +23 -9
  77. package/src/components/Accordion/Accordion.stories.ts +153 -3
  78. package/src/components/Accordion/Accordion.vue +7 -6
  79. package/src/components/Accordion/composables/__tests__/useAccordionState.spec.ts +40 -12
  80. package/src/components/Accordion/composables/useAccordionState.ts +3 -4
  81. package/src/components/Accordion/tests/accordion.spec.ts +131 -19
  82. package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.mdx +3 -1
  83. package/src/components/Amelipro/AmeliproPagination/AmeliproPagination.stories.ts +8 -0
  84. package/src/components/BackBtn/accessibilite/Accessibility.mdx +62 -10
  85. package/src/components/BackToTopBtn/BackToTopBtn.stories.ts +9 -3
  86. package/src/components/BackToTopBtn/accessibilite/Accessibility.mdx +86 -6
  87. package/src/components/Captcha/tests/Captcha.spec.ts +0 -29
  88. package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +2 -110
  89. package/src/components/Customs/Selects/SelectBtnField/accessibilite/Accessibility.mdx +133 -10
  90. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.stories.ts +379 -93
  91. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.vue +144 -83
  92. package/src/components/Customs/Selects/SyAutocomplete/accessibilite/Accessibilite.stories.ts +40 -1
  93. package/src/components/Customs/Selects/SyAutocomplete/accessibilite/Accessibility.mdx +7 -1
  94. package/src/components/Customs/Selects/SyAutocomplete/locales.ts +5 -0
  95. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.a11y.spec.ts +96 -0
  96. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.spec.ts +234 -9
  97. package/src/components/Customs/Selects/SyAutocomplete/utils/ariaManager.ts +13 -3
  98. package/src/components/Customs/Selects/SyAutocomplete/utils/useSelectionLogic.ts +9 -10
  99. package/src/components/Customs/Selects/SySelect/SySelect.vue +46 -3
  100. package/src/components/Customs/Selects/SySelect/locales.ts +1 -0
  101. package/src/components/Customs/SyIcon/SyIcon.vue +1 -1
  102. package/src/components/Customs/SyIcon/tests/SyIcon.a11y.spec.ts +20 -0
  103. package/src/components/Customs/SyIconButton/SyIconButton.mdx +46 -0
  104. package/src/components/Customs/SyIconButton/SyIconButton.stories.ts +184 -0
  105. package/src/components/Customs/SyIconButton/SyIconButton.vue +38 -0
  106. package/src/components/Customs/SyIconButton/accessibilite/Accessibility.mdx +64 -0
  107. package/src/components/Customs/SyIconButton/tests/SyIconButton.a11y.spec.ts +87 -0
  108. package/src/components/Customs/SyIconButton/tests/SyIconButton.spec.ts +152 -0
  109. package/src/components/Customs/SyIconButton/tests/__snapshots__/SyIconButton.spec.ts.snap +61 -0
  110. package/src/components/Customs/SyPagination/SyPagination.vue +5 -5
  111. package/src/components/Customs/SyTextField/SyTextField.vue +20 -2
  112. package/src/components/Customs/SyTextField/accessibilite/Accessibility.mdx +67 -9
  113. package/src/components/Customs/SyTextField/tests/SyTextField.a11y.spec.ts +15 -0
  114. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +36 -0
  115. package/src/components/DataList/accessibilite/Accessibility.mdx +79 -11
  116. package/src/components/DataListGroup/accessibilite/Accessibility.mdx +80 -11
  117. package/src/components/DownloadBtn/tests/DownloadBtn.a11y.spec.ts +25 -0
  118. package/src/components/ErrorPage/ErrorPage.stories.ts +113 -19
  119. package/src/components/ErrorPage/ErrorPage.vue +17 -2
  120. package/src/components/ErrorPage/tests/ErrorPage.a11y.spec.ts +17 -0
  121. package/src/components/ErrorPage/tests/ErrorPage.spec.ts +21 -1
  122. package/src/components/ErrorPage/tests/__snapshots__/ErrorPage.spec.ts.snap +0 -1
  123. package/src/components/ExternalLinks/tests/ExternalLinks.a11y.spec.ts +23 -0
  124. package/src/components/FileList/FileList.stories.ts +51 -1
  125. package/src/components/FileList/UploadItem/UploadItem.vue +13 -6
  126. package/src/components/FileList/UploadItem/locales.ts +3 -12
  127. package/src/components/FileList/accessibilite/Accessibility.mdx +3 -0
  128. package/src/components/FileUpload/FileUpload.vue +2 -1
  129. package/src/components/FileUpload/FileUploadContent.vue +2 -1
  130. package/src/components/FileUpload/tests/FileUpload.spec.ts +47 -0
  131. package/src/components/FileUpload/validateFiles.ts +5 -2
  132. package/src/components/FranceConnectBtn/accessibilite/Accessibility.mdx +62 -9
  133. package/src/components/HeaderBar/HeaderBar.vue +2 -1
  134. package/src/components/HeaderBar/HeaderLogo/HeaderLogo.vue +2 -1
  135. package/src/components/HeaderNavigationBar/HeaderNavigationBar.vue +2 -1
  136. package/src/components/LunarCalendar/accessibilite/Accessibility.mdx +74 -8
  137. package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +163 -0
  138. package/src/components/MaintenancePage/MaintenancePage.vue +1 -1
  139. package/src/components/MaintenancePage/tests/MaintenancePage.spec.ts +4 -5
  140. package/src/components/MaintenancePage/tests/__snapshots__/MaintenancePage.spec.ts.snap +0 -1
  141. package/src/components/MonthPicker/MonthPicker.mdx +35 -0
  142. package/src/components/MonthPicker/MonthPicker.stories.ts +527 -0
  143. package/src/components/MonthPicker/MonthPicker.vue +79 -0
  144. package/src/components/MonthPicker/MonthPickerText/MonthPickerInput.vue +89 -0
  145. package/src/components/MonthPicker/MonthPickerText/useTextField.ts +27 -0
  146. package/src/components/MonthPicker/MonthPickerVisual/MonthPickerVisual.vue +154 -0
  147. package/src/components/MonthPicker/MonthPickerVisual/MonthPickerVisualProps.ts +13 -0
  148. package/src/components/MonthPicker/MonthPickerVisual/MonthSelector.vue +137 -0
  149. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.vue +60 -0
  150. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.vue +149 -0
  151. package/src/components/MonthPicker/MonthPickerVisual/YearSelector.vue +143 -0
  152. package/src/components/MonthPicker/MonthPickerVisual/useMonthGrid.ts +45 -0
  153. package/src/components/MonthPicker/MonthPickerVisual/useYearGrid.ts +45 -0
  154. package/src/components/MonthPicker/MonthPickerVisual/utils.ts +17 -0
  155. package/src/components/MonthPicker/accessibilite/Accessibility.mdx +59 -0
  156. package/src/components/MonthPicker/locales.ts +12 -0
  157. package/src/components/MonthPicker/tests/MonthPicker.a11y.spec.ts +71 -0
  158. package/src/components/MonthPicker/tests/MonthPicker.spec.ts +1248 -0
  159. package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +2545 -0
  160. package/src/components/MonthPicker/useMonthPickerValidation.ts +30 -0
  161. package/src/components/NirField/NirField.mdx +1 -2
  162. package/src/components/NirField/NirField.stories.ts +66 -6
  163. package/src/components/NotFoundPage/tests/NotFoundPage.spec.ts +2 -3
  164. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +22 -14
  165. package/src/components/NotificationBar/Notification/Notification.vue +3 -1
  166. package/src/components/NotificationBar/NotificationBar.stories.ts +154 -0
  167. package/src/components/NotificationBar/tests/NotificationBar.a11y.spec.ts +26 -0
  168. package/src/components/NotificationBar/tests/NotificationBar.spec.ts +60 -0
  169. package/src/components/RangeField/accessibilite/Accessibility.mdx +79 -11
  170. package/src/components/SkipLink/tests/SkipLink.a11y.spec.ts +23 -0
  171. package/src/components/StatusPage/StatusPage.stories.ts +118 -0
  172. package/src/components/StatusPage/StatusPage.vue +5 -3
  173. package/src/components/StatusPage/tests/StatusPage.a11y.spec.ts +22 -0
  174. package/src/components/StatusPage/tests/StatusPage.spec.ts +22 -0
  175. package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +22 -14
  176. package/src/components/SubHeader/tests/SubHeader.a11y.spec.ts +20 -0
  177. package/src/components/SyAlert/SyAlert.vue +1 -0
  178. package/src/components/SyAlert/accessibilite/Accessibility.mdx +79 -9
  179. package/src/components/SyAlert/tests/SyAlert.a11y.spec.ts +23 -0
  180. package/src/components/SyHeading/SyHeading.a11y.test.ts +149 -0
  181. package/src/components/SyHeading/SyHeading.test.ts +115 -0
  182. package/src/components/SyHeading/SyHeading.vue +5 -3
  183. package/src/components/SyTextArea/accessibilite/Accessibility.mdx +80 -8
  184. package/src/components/SyTextArea/tests/SyTextArea.a11y.spec.ts +151 -0
  185. package/src/components/ToolbarContainer/tests/ToolbarContainer.a11y.spec.ts +126 -0
  186. package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +2 -2
  187. package/src/components/index.ts +1 -0
  188. package/src/composables/useFormFieldErrorHandling.ts +11 -2
  189. package/src/designTokens/tokens/cnam/cnamContextual.ts +6 -1
  190. package/src/main.ts +2 -0
@@ -0,0 +1,1248 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { describe, afterEach, expect, it, vi } from 'vitest'
3
+ import MonthPicker from '../MonthPicker.vue'
4
+ import { nextTick } from 'vue'
5
+
6
+ describe('mounthpicker', () => {
7
+ it('should render mounthpicker', () => {
8
+ const wrapper = mount(MonthPicker, {
9
+ props: {
10
+ label: 'Début du projet',
11
+ modelValue: '11/2025',
12
+ },
13
+ attachTo: document.body,
14
+ })
15
+
16
+ expect(wrapper.find('.month-picker').exists()).toBeTruthy()
17
+ expect(wrapper).toMatchSnapshot()
18
+ wrapper.unmount()
19
+ })
20
+
21
+ it('should open the menu when clicking on the input', async () => {
22
+ vi.useFakeTimers()
23
+ const wrapper = mount(MonthPicker, {
24
+ props: {
25
+ label: 'Début du projet',
26
+ modelValue: '12/2026',
27
+ },
28
+ attachTo: document.body,
29
+ })
30
+
31
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
32
+ await nextTick()
33
+ await nextTick()
34
+
35
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
36
+ await toggleBtn.trigger('click')
37
+
38
+ const monthPickerVisualWrapper = document.body.querySelector('.month-picker-menu') as HTMLElement
39
+ expect(monthPickerVisualWrapper).toBeTruthy()
40
+ expect(monthPickerVisualWrapper).toMatchSnapshot()
41
+
42
+ wrapper.unmount()
43
+ })
44
+
45
+ describe('MonthPickerInput', () => {
46
+ it('should emit update:modelValue when the input value changes', async () => {
47
+ const wrapper = mount(MonthPicker, {
48
+ props: {
49
+ label: 'Début du projet',
50
+ },
51
+ })
52
+
53
+ const input = wrapper.find('input')
54
+ await input.setValue('01/2027')
55
+ expect(wrapper.emitted('update:modelValue')).toEqual([['01/2027']])
56
+
57
+ wrapper.unmount()
58
+ })
59
+
60
+ it('should emit multiple update:modelValue when the input value changes multiple times', async () => {
61
+ const wrapper = mount(MonthPicker, {
62
+ props: {
63
+ label: 'Début du projet',
64
+ },
65
+ })
66
+ const input = wrapper.find('input')
67
+
68
+ await input.setValue('01/2027')
69
+ await input.setValue('02/2027')
70
+ await input.setValue('03/2030')
71
+
72
+ expect(wrapper.emitted('update:modelValue')).toEqual([['01/2027'], ['02/2027'], ['03/2030']])
73
+
74
+ wrapper.unmount()
75
+ })
76
+
77
+ it('shows the correct value in the input when modelValue prop changes', async () => {
78
+ const wrapper = mount(MonthPicker, {
79
+ props: {
80
+ label: 'Début du projet',
81
+ modelValue: '11/2025',
82
+ },
83
+ })
84
+
85
+ expect(wrapper.find('input').element.value).toBe('11/2025')
86
+
87
+ await wrapper.setProps({ modelValue: '12/2026' })
88
+ expect(wrapper.find('input').element.value).toBe('12/2026')
89
+
90
+ wrapper.unmount()
91
+ })
92
+ })
93
+
94
+ describe('MonthPickerVisual', () => {
95
+ it('should emit update:modelValue when a month is selected and a year is selected', async () => {
96
+ const wrapper = mount(MonthPicker, {
97
+ props: {
98
+ label: 'Début du projet',
99
+ modelValue: '11/2025',
100
+ },
101
+ attachTo: document.body,
102
+ })
103
+
104
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
105
+ await nextTick()
106
+ await nextTick()
107
+
108
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
109
+ await toggleBtn.trigger('click')
110
+
111
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
112
+ await monthButton.trigger('click')
113
+
114
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2024')
115
+ await yearButton.trigger('click')
116
+
117
+ expect(wrapper.emitted('update:modelValue')).toEqual([['01/2024']]) // The month part of the value should be '01'
118
+
119
+ wrapper.unmount()
120
+ })
121
+
122
+ it('do not emit update:modelValue when only a month is selected', async () => {
123
+ const wrapper = mount(MonthPicker, {
124
+ props: {
125
+ label: 'Début du projet',
126
+ modelValue: '11/2025',
127
+ },
128
+ attachTo: document.body,
129
+ })
130
+
131
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
132
+ await nextTick()
133
+ await nextTick()
134
+
135
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
136
+ await toggleBtn.trigger('click')
137
+
138
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
139
+ await monthButton.trigger('click')
140
+
141
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy() // The value should not be emitted when only a month is selected
142
+
143
+ wrapper.unmount()
144
+ })
145
+
146
+ it('do not emit update:modelValue when only a month is selected', async () => {
147
+ const wrapper = mount(MonthPicker, {
148
+ props: {
149
+ label: 'Début du projet',
150
+ },
151
+ attachTo: document.body,
152
+ })
153
+
154
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
155
+ await nextTick()
156
+ await nextTick()
157
+
158
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
159
+ await toggleBtn.trigger('click')
160
+
161
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
162
+ await monthButton.trigger('click')
163
+
164
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy() // The value should not be emitted when only a month is selected
165
+
166
+ wrapper.unmount()
167
+ })
168
+
169
+ it('do not emit update:modelValue when only a year is selected', async () => {
170
+ const wrapper = mount(MonthPicker, {
171
+ props: {
172
+ label: 'Début du projet',
173
+ },
174
+ attachTo: document.body,
175
+ })
176
+
177
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
178
+ await nextTick()
179
+ await nextTick()
180
+
181
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
182
+ await toggleBtn.trigger('click')
183
+
184
+ const switchViewBtn = wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-year-btn')
185
+ await switchViewBtn.trigger('click')
186
+
187
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2024')
188
+ await yearButton.trigger('click')
189
+
190
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy() // The value should not be emitted when only a year is selected
191
+
192
+ wrapper.unmount()
193
+ })
194
+
195
+ it('shows the year picker after the month is selected', async () => {
196
+ const wrapper = mount(MonthPicker, {
197
+ props: {
198
+ label: 'Début du projet',
199
+ modelValue: '11/2025',
200
+ },
201
+ attachTo: document.body,
202
+ })
203
+
204
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
205
+ await nextTick()
206
+ await nextTick()
207
+
208
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
209
+ await toggleBtn.trigger('click')
210
+
211
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
212
+ await monthButton.trigger('click')
213
+
214
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).exists()).toBeFalsy()
215
+ expect(wrapper.findComponent({ name: 'YearSelector' }).isVisible()).toBeTruthy()
216
+ expect(wrapper.findComponent({ name: 'YearSelector' })).toMatchSnapshot()
217
+
218
+ wrapper.unmount()
219
+ })
220
+
221
+ it('shows the month picker after the year is selected', async () => {
222
+ const wrapper = mount(MonthPicker, {
223
+ props: {
224
+ label: 'Début du projet',
225
+ modelValue: '11/2025',
226
+ },
227
+ attachTo: document.body,
228
+ })
229
+
230
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
231
+ await nextTick()
232
+ await nextTick()
233
+
234
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
235
+ await toggleBtn.trigger('click')
236
+
237
+ // Switch to year selector
238
+ const switchViewBtn = wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-year-btn')
239
+ await switchViewBtn.trigger('click')
240
+
241
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2024')
242
+ await yearButton.trigger('click')
243
+
244
+ expect(wrapper.findComponent({ name: 'YearSelector' }).exists()).toBeFalsy()
245
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).isVisible()).toBeTruthy()
246
+ expect(wrapper.findComponent({ name: 'MonthSelector' })).toMatchSnapshot()
247
+
248
+ wrapper.unmount()
249
+ })
250
+
251
+ it ('should close the menu after selecting a year and then a month', async () => {
252
+ const wrapper = mount(MonthPicker, {
253
+ props: {
254
+ label: 'Début du projet',
255
+ modelValue: '11/2025',
256
+ },
257
+ attachTo: document.body,
258
+ })
259
+
260
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
261
+ await nextTick()
262
+ await nextTick()
263
+
264
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
265
+ await toggleBtn.trigger('click')
266
+
267
+ // Switch to year selector
268
+ const switchViewBtn = wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-year-btn')
269
+ await switchViewBtn.trigger('click')
270
+
271
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2024')
272
+ await yearButton.trigger('click')
273
+
274
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
275
+ await monthButton.trigger('click')
276
+
277
+ const monthPickerVisualWrapper = wrapper.findComponent({ name: 'MonthPickerVisual' }).find('.month-picker-menu')
278
+ expect(monthPickerVisualWrapper.exists()).toBeFalsy()
279
+
280
+ expect(wrapper.emitted('update:modelValue')).toEqual([['01/2024']]) // The value should be '01/2024' after selecting January and 2024
281
+ expect(wrapper.emitted('update:open')).toEqual([[true], [false]]) // The menu should be closed after selecting a month and a year
282
+
283
+ wrapper.unmount()
284
+ })
285
+
286
+ it('show the selected month in the visual month picker', async () => {
287
+ const wrapper = mount(MonthPicker, {
288
+ props: {
289
+ label: 'Début du projet',
290
+ modelValue: '11/2025',
291
+ },
292
+ attachTo: document.body,
293
+ })
294
+
295
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
296
+ await nextTick()
297
+ await nextTick()
298
+
299
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
300
+ await toggleBtn.trigger('click')
301
+
302
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-11') // November button
303
+ expect(monthButton.classes()).toContain('month-selector__month--active')
304
+
305
+ wrapper.unmount()
306
+ })
307
+
308
+ it('show the correct year in the visual year picker', async () => {
309
+ const wrapper = mount(MonthPicker, {
310
+ props: {
311
+ label: 'Début du projet',
312
+ modelValue: '11/2025',
313
+ },
314
+ attachTo: document.body,
315
+ })
316
+
317
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
318
+ await nextTick()
319
+ await nextTick()
320
+
321
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
322
+ await toggleBtn.trigger('click')
323
+
324
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
325
+ await monthButton.trigger('click')
326
+
327
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2025')
328
+ expect(yearButton.classes()).toContain('year-selector__year--active')
329
+
330
+ wrapper.unmount()
331
+ })
332
+
333
+ describe('keyboard navigation', () => {
334
+ it('should navigate through months using arrow keys', async () => {
335
+ const wrapper = mount(MonthPicker, {
336
+ props: {
337
+ label: 'Début du projet',
338
+ modelValue: '11/2025',
339
+ },
340
+ attachTo: document.body,
341
+ })
342
+
343
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
344
+ await nextTick()
345
+ await nextTick()
346
+
347
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
348
+ await toggleBtn.trigger('click')
349
+
350
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-11')
351
+ expect(monthButton.attributes('tabindex')).toBe('0')
352
+
353
+ await monthButton.trigger('keydown', { key: 'ArrowLeft' })
354
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).find('.month-10').attributes('tabindex')).toBe('0')
355
+
356
+ await monthButton.trigger('keydown', { key: 'ArrowRight' })
357
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).find('.month-11').attributes('tabindex')).toBe('0')
358
+
359
+ await monthButton.trigger('keydown', { key: 'ArrowUp' })
360
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).find('.month-9').attributes('tabindex')).toBe('0')
361
+
362
+ await monthButton.trigger('keydown', { key: 'ArrowDown' })
363
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).find('.month-11').attributes('tabindex')).toBe('0')
364
+
365
+ wrapper.unmount()
366
+ })
367
+
368
+ it('should navigate through years using arrow keys', async () => {
369
+ const wrapper = mount(MonthPicker, {
370
+ props: {
371
+ label: 'Début du projet',
372
+ modelValue: '11/2025',
373
+ },
374
+ attachTo: document.body,
375
+ })
376
+
377
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
378
+ await nextTick()
379
+ await nextTick()
380
+
381
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
382
+ await toggleBtn.trigger('click')
383
+
384
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
385
+ await monthButton.trigger('click')
386
+
387
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2025')
388
+ expect(yearButton.attributes('tabindex')).toBe('0')
389
+
390
+ await yearButton.trigger('keydown', { key: 'ArrowUp' })
391
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2022').attributes('tabindex')).toBe('0')
392
+
393
+ await yearButton.trigger('keydown', { key: 'ArrowDown' })
394
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2025').attributes('tabindex')).toBe('0')
395
+
396
+ await yearButton.trigger('keydown', { key: 'ArrowLeft' })
397
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2024').attributes('tabindex')).toBe('0')
398
+
399
+ await yearButton.trigger('keydown', { key: 'ArrowRight' })
400
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2025').attributes('tabindex')).toBe('0')
401
+
402
+ wrapper.unmount()
403
+ })
404
+
405
+ it('should navigate through years using arrow keys according to the yearsOrder prop', async () => {
406
+ const wrapper = mount(MonthPicker, {
407
+ props: {
408
+ label: 'Début du projet',
409
+ modelValue: '11/2025',
410
+ yearsOrder: 'desc',
411
+ },
412
+ attachTo: document.body,
413
+ })
414
+
415
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
416
+ await nextTick()
417
+ await nextTick()
418
+
419
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
420
+ await toggleBtn.trigger('click')
421
+
422
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
423
+ await monthButton.trigger('click')
424
+
425
+ const yearSelector = wrapper.findComponent({ name: 'YearSelector' })
426
+
427
+ const yearButton = yearSelector.find('.year-2025')
428
+ await yearButton.trigger('keydown', { key: 'ArrowUp' })
429
+ expect(yearSelector.find('.year-2028').attributes('tabindex')).toBe('0')
430
+ await yearButton.trigger('keydown', { key: 'ArrowDown' })
431
+ expect(yearSelector.find('.year-2025').attributes('tabindex')).toBe('0')
432
+ await yearButton.trigger('keydown', { key: 'ArrowLeft' })
433
+ expect(yearSelector.find('.year-2026').attributes('tabindex')).toBe('0')
434
+ await yearButton.trigger('keydown', { key: 'ArrowRight' })
435
+ expect(yearSelector.find('.year-2025').attributes('tabindex')).toBe('0')
436
+
437
+ await wrapper.setProps({ yearsOrder: 'asc' })
438
+ expect(yearSelector.find('.year-2025').attributes('tabindex')).toBe('0')
439
+ await yearButton.trigger('keydown', { key: 'ArrowUp' })
440
+ expect(yearSelector.find('.year-2022').attributes('tabindex')).toBe('0')
441
+ await yearButton.trigger('keydown', { key: 'ArrowDown' })
442
+ expect(yearSelector.find('.year-2025').attributes('tabindex')).toBe('0')
443
+ await yearButton.trigger('keydown', { key: 'ArrowLeft' })
444
+ expect(yearSelector.find('.year-2024').attributes('tabindex')).toBe('0')
445
+ await yearButton.trigger('keydown', { key: 'ArrowRight' })
446
+ expect(yearSelector.find('.year-2025').attributes('tabindex')).toBe('0')
447
+
448
+ wrapper.unmount()
449
+ })
450
+ })
451
+
452
+ it ('show the years in the correct order according to the yearsOrder prop', async () => {
453
+ const wrapper = mount(MonthPicker, {
454
+ props: {
455
+ label: 'Début du projet',
456
+ modelValue: '11/2025',
457
+ yearsOrder: 'desc',
458
+ },
459
+ attachTo: document.body,
460
+ })
461
+
462
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
463
+ await nextTick()
464
+ await nextTick()
465
+
466
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
467
+ await toggleBtn.trigger('click')
468
+
469
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
470
+ await monthButton.trigger('click')
471
+
472
+ const yearButtons = wrapper.findComponent({ name: 'YearSelector' }).findAll('.year-selector__year')
473
+ expect(yearButtons[0]!.text()).toBe('2100')
474
+ expect(yearButtons[yearButtons.length - 1]!.text()).toBe('1900')
475
+
476
+ await wrapper.setProps({ yearsOrder: 'asc' })
477
+
478
+ const newYearButtons = wrapper.findComponent({ name: 'YearSelector' }).findAll('.year-selector__year')
479
+ expect(newYearButtons[0]!.text()).toBe('1900')
480
+ expect(newYearButtons[newYearButtons.length - 1]!.text()).toBe('2100')
481
+
482
+ wrapper.unmount()
483
+ })
484
+ })
485
+
486
+ describe('select current month btn', () => {
487
+ it('should select the current month and year when a month is already selected', async () => {
488
+ // mock current date to 15th March 2023
489
+ const mockDate = new Date(2023, 2, 15)
490
+ vi.setSystemTime(mockDate)
491
+ const wrapper = mount(MonthPicker, {
492
+ props: {
493
+ label: 'Début du projet',
494
+ modelValue: '11/2025',
495
+ },
496
+ attachTo: document.body,
497
+ })
498
+
499
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
500
+ await nextTick()
501
+ await nextTick()
502
+
503
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
504
+ await toggleBtn.trigger('click')
505
+ const modal = wrapper.findComponent({ name: 'VisualPickerFooter' })
506
+
507
+ const currentMonthBtn = modal.find('.month-picker-footer__current-month-btn')
508
+ await currentMonthBtn.trigger('click')
509
+
510
+ expect(wrapper.emitted('update:modelValue')).toEqual([['03/2023']]) // The value should be '03/2023' (March 2023)
511
+
512
+ wrapper.unmount()
513
+ })
514
+
515
+ it('should select the current month and year when no month is selected', async () => {
516
+ // mock current date to 15th March 2023
517
+ const mockDate = new Date(2023, 2, 15)
518
+ vi.setSystemTime(mockDate)
519
+ const wrapper = mount(MonthPicker, {
520
+ props: {
521
+ label: 'Début du projet',
522
+ modelValue: undefined,
523
+ },
524
+ attachTo: document.body,
525
+ })
526
+
527
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
528
+ await nextTick()
529
+ await nextTick()
530
+
531
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
532
+ await toggleBtn.trigger('click')
533
+ const modal = wrapper.findComponent({ name: 'VisualPickerFooter' })
534
+
535
+ const currentMonthBtn = modal.find('.month-picker-footer__current-month-btn')
536
+ await currentMonthBtn.trigger('click')
537
+
538
+ expect(wrapper.emitted('update:modelValue')).toEqual([['03/2023']]) // The value should be '03/2023' (March 2023)
539
+
540
+ wrapper.unmount()
541
+ })
542
+ })
543
+
544
+ describe('view switcher', () => {
545
+ it('should switch to year selector when clicking on the switch view button in month selector', async () => {
546
+ const wrapper = mount(MonthPicker, {
547
+ props: {
548
+ label: 'Début du projet',
549
+ modelValue: '11/2025',
550
+ },
551
+ attachTo: document.body,
552
+ })
553
+
554
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
555
+ await nextTick()
556
+ await nextTick()
557
+
558
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
559
+ await toggleBtn.trigger('click')
560
+
561
+ const switchViewBtn = wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-year-btn')
562
+ await switchViewBtn.trigger('click')
563
+
564
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).exists()).toBeFalsy()
565
+ expect(wrapper.findComponent({ name: 'YearSelector' }).isVisible()).toBeTruthy()
566
+
567
+ wrapper.unmount()
568
+ })
569
+
570
+ it('should switch to month selector when clicking on the switch view button in year selector', async () => {
571
+ const wrapper = mount(MonthPicker, {
572
+ props: {
573
+ label: 'Début du projet',
574
+ modelValue: '11/2025',
575
+ },
576
+ attachTo: document.body,
577
+ })
578
+
579
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
580
+ await nextTick()
581
+ await nextTick()
582
+
583
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
584
+ await toggleBtn.trigger('click')
585
+
586
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
587
+ await monthButton.trigger('click')
588
+
589
+ const switchViewBtn = wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-year-btn')
590
+ await switchViewBtn.trigger('click')
591
+
592
+ expect(wrapper.findComponent({ name: 'YearSelector' }).exists()).toBeFalsy()
593
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).isVisible()).toBeTruthy()
594
+
595
+ wrapper.unmount()
596
+ })
597
+
598
+ it('shows the year selector when the initialView prop is set to year', async () => {
599
+ const wrapper = mount(MonthPicker, {
600
+ props: {
601
+ label: 'Début du projet',
602
+ initialView: 'years',
603
+ },
604
+ attachTo: document.body,
605
+ })
606
+
607
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
608
+ await nextTick()
609
+ await nextTick()
610
+
611
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
612
+ await toggleBtn.trigger('click')
613
+
614
+ expect(wrapper.findComponent({ name: 'YearSelector' }).isVisible()).toBeTruthy()
615
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).exists()).toBeFalsy()
616
+
617
+ expect(document.activeElement?.classList).toContain('year-selector__year')
618
+ wrapper.unmount()
619
+ })
620
+
621
+ it('shows the month selector when the initialView prop is set to month', async () => {
622
+ const wrapper = mount(MonthPicker, {
623
+ props: {
624
+ label: 'Début du projet',
625
+ initialView: 'months',
626
+ },
627
+ attachTo: document.body,
628
+ })
629
+
630
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
631
+ await nextTick()
632
+ await nextTick()
633
+
634
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
635
+ await toggleBtn.trigger('click')
636
+
637
+ expect(wrapper.findComponent({ name: 'MonthSelector' }).isVisible()).toBeTruthy()
638
+ expect(wrapper.findComponent({ name: 'YearSelector' }).exists()).toBeFalsy()
639
+
640
+ expect(document.activeElement?.classList).toContain('month-selector__month')
641
+ wrapper.unmount()
642
+ })
643
+
644
+ it ('focuses the lowerst year when opening the year selector if no year is selected and the order is ascending', async () => {
645
+ const wrapper = mount(MonthPicker, {
646
+ props: {
647
+ label: 'Début du projet',
648
+ initialView: 'years',
649
+ minYear: 2000,
650
+ maxYear: 2020,
651
+ },
652
+ attachTo: document.body,
653
+ })
654
+
655
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
656
+ await nextTick()
657
+ await nextTick()
658
+
659
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
660
+ await toggleBtn.trigger('click')
661
+
662
+ expect(document.activeElement?.classList).toContain('year-2000')
663
+
664
+ wrapper.unmount()
665
+ })
666
+
667
+ it ('focuses the highest year when opening the year selector if no year is selected and the order is descending', async () => {
668
+ const wrapper = mount(MonthPicker, {
669
+ props: {
670
+ label: 'Début du projet',
671
+ initialView: 'years',
672
+ yearsOrder: 'desc',
673
+ minYear: 2000,
674
+ maxYear: 2020,
675
+ },
676
+ attachTo: document.body,
677
+ })
678
+
679
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
680
+ await nextTick()
681
+ await nextTick()
682
+
683
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
684
+ await toggleBtn.trigger('click')
685
+
686
+ expect(document.activeElement?.classList).toContain('year-2020')
687
+
688
+ wrapper.unmount()
689
+ })
690
+ })
691
+
692
+ describe('minYear and maxYear props', () => {
693
+ it('should not display years inferior to minYear', async () => {
694
+ const wrapper = mount(MonthPicker, {
695
+ props: {
696
+ label: 'Début du projet',
697
+ modelValue: '11/2025',
698
+ minYear: 2020,
699
+ },
700
+ attachTo: document.body,
701
+ })
702
+
703
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
704
+ await nextTick()
705
+ await nextTick()
706
+
707
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
708
+ await toggleBtn.trigger('click')
709
+
710
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
711
+ await monthButton.trigger('click')
712
+
713
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2019').exists()).toBeFalsy()
714
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2020').exists()).toBeTruthy()
715
+
716
+ wrapper.unmount()
717
+ })
718
+
719
+ it('should not display years superior to maxYear', async () => {
720
+ const wrapper = mount(MonthPicker, {
721
+ props: {
722
+ label: 'Début du projet',
723
+ modelValue: '11/2025',
724
+ maxYear: 2025,
725
+ },
726
+ attachTo: document.body,
727
+ })
728
+
729
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
730
+ await nextTick()
731
+ await nextTick()
732
+
733
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
734
+ await toggleBtn.trigger('click')
735
+
736
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
737
+ await monthButton.trigger('click')
738
+
739
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2026').exists()).toBeFalsy()
740
+ expect(wrapper.findComponent({ name: 'YearSelector' }).find('.year-2025').exists()).toBeTruthy()
741
+
742
+ wrapper.unmount()
743
+ })
744
+ })
745
+
746
+ describe('visual/text input synchronization', () => {
747
+ it('should update the text input when a month and year are selected in the visual picker', async () => {
748
+ const wrapper = mount(MonthPicker, {
749
+ props: {
750
+ label: 'Début du projet',
751
+ modelValue: '11/2025',
752
+ },
753
+ attachTo: document.body,
754
+ })
755
+
756
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
757
+ await nextTick()
758
+ await nextTick()
759
+
760
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
761
+ await toggleBtn.trigger('click')
762
+
763
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
764
+ await monthButton.trigger('click')
765
+
766
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2024')
767
+ await yearButton.trigger('click')
768
+
769
+ expect(wrapper.find('input').element.value).toBe('01/2024') // The input value should be '01/2024'
770
+
771
+ wrapper.unmount()
772
+ })
773
+
774
+ it('should update the visual picker when the text input value changes', async () => {
775
+ const wrapper = mount(MonthPicker, {
776
+ props: {
777
+ label: 'Début du projet',
778
+ modelValue: '11/2025',
779
+ },
780
+ attachTo: document.body,
781
+ })
782
+
783
+ const input = wrapper.find('input')
784
+ await input.setValue('02/2026')
785
+
786
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
787
+ await toggleBtn.trigger('click')
788
+
789
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-2') // February button
790
+ expect(monthButton.classes()).toContain('month-selector__month--active')
791
+
792
+ await wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-year-btn').trigger('click')
793
+
794
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2026')
795
+ expect(yearButton.classes()).toContain('year-selector__year--active')
796
+
797
+ wrapper.unmount()
798
+ })
799
+
800
+ it('handle correctly month and year that start with 0 in the text input', async () => {
801
+ vi.setSystemTime(new Date(2023, 5, 15))
802
+ const wrapper = mount(MonthPicker, {
803
+ props: {
804
+ label: 'Début du projet',
805
+ modelValue: '01/2025',
806
+ },
807
+ attachTo: document.body,
808
+ })
809
+
810
+ const input = wrapper.find('input')
811
+ await input.setValue('02/025')
812
+
813
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
814
+ await toggleBtn.trigger('click')
815
+
816
+ const activeMonthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-2') // February button
817
+ expect(activeMonthButton.classes()).toContain('month-selector__month--active')
818
+
819
+ await wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-year-btn').trigger('click')
820
+
821
+ const activeYearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-selector__year--active')
822
+ expect(activeYearButton.text()).toBe('2023') // the selector fallback to the current year if the year is not in the range
823
+ expect(activeYearButton.classes()).toContain('year-2023')
824
+
825
+ await toggleBtn.trigger('click') // close the menu
826
+
827
+ expect(wrapper.emitted('update:modelValue')).toEqual([['02/025']])
828
+ expect(wrapper.find('input').element.value).toBe('02/025') // The input value should still be '02/025'
829
+
830
+ wrapper.unmount()
831
+ vi.useRealTimers()
832
+ })
833
+ })
834
+
835
+ describe('visual picker header', () => {
836
+ it('displays the current month and year when none is selected', async () => {
837
+ // mock current date to 15th March 2023
838
+ const mockDate = new Date(2023, 2, 15)
839
+ vi.setSystemTime(mockDate)
840
+ const wrapper = mount(MonthPicker, {
841
+ props: {
842
+ label: 'Début du projet',
843
+ modelValue: undefined,
844
+ },
845
+ attachTo: document.body,
846
+ })
847
+
848
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
849
+ await nextTick()
850
+ await nextTick()
851
+
852
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
853
+ await toggleBtn.trigger('click')
854
+
855
+ const headerText = wrapper
856
+ .findComponent({ name: 'VisualPickerHeader' })
857
+ .find('.visual-picker-header__date')
858
+ .text()
859
+
860
+ expect(headerText).toContain('March')
861
+ expect(headerText).toContain('2023')
862
+
863
+ wrapper.unmount()
864
+ })
865
+
866
+ it('displays the selected month and year', async () => {
867
+ const wrapper = mount(MonthPicker, {
868
+ props: {
869
+ label: 'Début du projet',
870
+ modelValue: '11/2025',
871
+ },
872
+ attachTo: document.body,
873
+ })
874
+
875
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
876
+ await nextTick()
877
+ await nextTick()
878
+
879
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
880
+ await toggleBtn.trigger('click')
881
+
882
+ const headerText = wrapper
883
+ .findComponent({ name: 'VisualPickerHeader' })
884
+ .find('.visual-picker-header__date')
885
+ .text()
886
+ expect(headerText).toBe('November 2025')
887
+
888
+ wrapper.unmount()
889
+ })
890
+
891
+ it('displays the current month and year when the model value is invalid', async () => {
892
+ // mock current date to 15th March 2023
893
+ const mockDate = new Date(2023, 2, 15)
894
+ vi.setSystemTime(mockDate)
895
+ const wrapper = mount(MonthPicker, {
896
+ props: {
897
+ label: 'Début du projet',
898
+ modelValue: '00/2025',
899
+ },
900
+ attachTo: document.body,
901
+ })
902
+
903
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
904
+ await nextTick()
905
+ await nextTick()
906
+
907
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
908
+ await toggleBtn.trigger('click')
909
+
910
+ const headerText = wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-header__date').text()
911
+ expect(headerText).toBe('March 2023')
912
+
913
+ wrapper.unmount()
914
+ })
915
+
916
+ it ('displays the current month and year when the model value year is not set', async () => {
917
+ // mock current date to 15th March 2023
918
+ const mockDate = new Date(2023, 2, 15)
919
+ vi.setSystemTime(mockDate)
920
+ const wrapper = mount(MonthPicker, {
921
+ props: {
922
+ label: 'Début du projet',
923
+ modelValue: '05/',
924
+ },
925
+ attachTo: document.body,
926
+ })
927
+
928
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
929
+ await nextTick()
930
+ await nextTick()
931
+
932
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
933
+ await toggleBtn.trigger('click')
934
+
935
+ const headerText = wrapper.findComponent({ name: 'VisualPickerHeader' }).find('.visual-picker-header__date').text()
936
+ expect(headerText).toBe('March 2023')
937
+
938
+ wrapper.unmount()
939
+ })
940
+ })
941
+
942
+ describe('disabled and readonly state', () => {
943
+ it('does not update the model value when the field is disabled', async () => {
944
+ const wrapper = mount(MonthPicker, {
945
+ props: {
946
+ label: 'Début du projet',
947
+ modelValue: '11/2025',
948
+ disabled: true,
949
+ },
950
+ })
951
+
952
+ const input = wrapper.find('input')
953
+ expect(input.attributes('disabled')).toBeDefined()
954
+
955
+ await input.setValue('01/2027')
956
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy() // The value should not be emitted when the field is disabled
957
+
958
+ wrapper.unmount()
959
+ })
960
+
961
+ it('does not open the visual picker when the field is disabled', async () => {
962
+ const wrapper = mount(MonthPicker, {
963
+ props: {
964
+ label: 'Début du projet',
965
+ modelValue: '11/2025',
966
+ disabled: true,
967
+ },
968
+ attachTo: document.body,
969
+ })
970
+
971
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
972
+ await nextTick()
973
+ await nextTick()
974
+
975
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
976
+ expect(toggleBtn.attributes('disabled')).toBeDefined()
977
+
978
+ await toggleBtn.trigger('click')
979
+
980
+ const modal = wrapper.findComponent({ name: 'MonthPickerVisual' }).find('.month-picker-menu')
981
+ expect(modal.exists()).toBeFalsy() // The month buttons should not be rendered when the field is disabled
982
+
983
+ wrapper.unmount()
984
+ })
985
+
986
+ it('does not update the model value when the field is readonly', async () => {
987
+ const wrapper = mount(MonthPicker, {
988
+ props: {
989
+ label: 'Début du projet',
990
+ modelValue: '11/2025',
991
+ readonly: true,
992
+ },
993
+ })
994
+
995
+ const input = wrapper.find('input')
996
+ expect(input.attributes('readonly')).toBeDefined()
997
+
998
+ await input.setValue('01/2027')
999
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy() // The value should not be emitted when the field is readonly
1000
+
1001
+ wrapper.unmount()
1002
+ })
1003
+
1004
+ it('does not update the model value when the visual picker is used and the field is readonly', async () => {
1005
+ const wrapper = mount(MonthPicker, {
1006
+ props: {
1007
+ label: 'Début du projet',
1008
+ modelValue: '11/2025',
1009
+ readonly: true,
1010
+ },
1011
+ attachTo: document.body,
1012
+ })
1013
+
1014
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
1015
+ await nextTick()
1016
+ await nextTick()
1017
+
1018
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
1019
+ await toggleBtn.trigger('click')
1020
+
1021
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1')
1022
+ await monthButton.trigger('click')
1023
+
1024
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2024')
1025
+ await yearButton.trigger('click')
1026
+
1027
+ expect(wrapper.emitted('update:modelValue')).toBeFalsy()
1028
+ expect(wrapper.emitted('update:open')).toEqual([[true], [false]])
1029
+ expect(wrapper.find('input').element.value).toBe('11/2025')
1030
+
1031
+ wrapper.unmount()
1032
+ })
1033
+ })
1034
+
1035
+ describe('localization', () => {
1036
+ afterEach(() => {
1037
+ // reset navigator.language to default value after each test
1038
+ Object.defineProperty(navigator, 'language', {
1039
+ value: 'en-US',
1040
+ writable: true,
1041
+ })
1042
+ })
1043
+
1044
+ it('should display month names in english when the browser locale is english', async () => {
1045
+ const wrapper = mount(MonthPicker, {
1046
+ props: {
1047
+ label: 'Start of the project',
1048
+ modelValue: '11/2025',
1049
+ },
1050
+ attachTo: document.body,
1051
+ })
1052
+
1053
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
1054
+ await nextTick()
1055
+ await nextTick()
1056
+
1057
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
1058
+ await toggleBtn.trigger('click')
1059
+
1060
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
1061
+ expect(monthButton.attributes('aria-label')).toBe('January')
1062
+ expect(monthButton.text()).toBe('Jan')
1063
+ })
1064
+
1065
+ it('should display month names in french when the browser locale is french', async () => {
1066
+ // mock navigator.language to french
1067
+ Object.defineProperty(navigator, 'language', {
1068
+ value: 'fr-FR',
1069
+ writable: true,
1070
+ })
1071
+ const wrapper = mount(MonthPicker, {
1072
+ props: {
1073
+ label: 'Début du projet',
1074
+ modelValue: '11/2025',
1075
+ },
1076
+ attachTo: document.body,
1077
+ })
1078
+
1079
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
1080
+ await nextTick()
1081
+ await nextTick()
1082
+
1083
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
1084
+ await toggleBtn.trigger('click')
1085
+
1086
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
1087
+ expect(monthButton.attributes('aria-label')).toBe('janvier')
1088
+ expect(monthButton.text()).toBe('janv.')
1089
+
1090
+ wrapper.unmount()
1091
+ })
1092
+
1093
+ it('display the date in the header according to the locale', async () => {
1094
+ // mock navigator.language to french
1095
+ Object.defineProperty(navigator, 'language', {
1096
+ value: 'fr-FR',
1097
+ writable: true,
1098
+ })
1099
+ const wrapper = mount(MonthPicker, {
1100
+ props: {
1101
+ label: 'Début du projet',
1102
+ modelValue: '11/2025',
1103
+ },
1104
+ attachTo: document.body,
1105
+ })
1106
+
1107
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
1108
+ await nextTick()
1109
+ await nextTick()
1110
+
1111
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
1112
+ await toggleBtn.trigger('click')
1113
+
1114
+ const headerText = wrapper
1115
+ .findComponent({ name: 'VisualPickerHeader' })
1116
+ .find('.visual-picker-header__date')
1117
+ .text()
1118
+
1119
+ expect(headerText).toBe('novembre 2025') // The header should display the date in the format "month year" in lowercase and in french
1120
+
1121
+ wrapper.unmount()
1122
+ })
1123
+
1124
+ it('display the date in the header according to the locale', async () => {
1125
+ // mock navigator.language to German
1126
+ Object.defineProperty(navigator, 'language', {
1127
+ value: 'de-DE',
1128
+ writable: true,
1129
+ })
1130
+ const wrapper = mount(MonthPicker, {
1131
+ props: {
1132
+ label: 'Projektbeginn',
1133
+ modelValue: '02/2025',
1134
+ },
1135
+ attachTo: document.body,
1136
+ })
1137
+
1138
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
1139
+ await nextTick()
1140
+ await nextTick()
1141
+
1142
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
1143
+ await toggleBtn.trigger('click')
1144
+
1145
+ const headerText = wrapper
1146
+ .findComponent({ name: 'VisualPickerHeader' })
1147
+ .find('.visual-picker-header__date')
1148
+ .text()
1149
+
1150
+ expect(headerText).toBe('Februar 2025') // The header should display the date in the format "month year" in German
1151
+
1152
+ wrapper.unmount()
1153
+ })
1154
+ })
1155
+ describe('validation', () => {
1156
+ it('show the error message when the input value is invalid', async () => {
1157
+ const wrapper = mount(MonthPicker, {
1158
+ props: {
1159
+ label: 'Début du projet',
1160
+ customRules: [{
1161
+ type: 'custom',
1162
+ options: {
1163
+ validate: (value: string) => {
1164
+ const regex = /^(0[1-9]|1[0-2])\/\d{4}$/
1165
+ return regex.test(value) || 'Invalid month/year format. Use MM/YYYY.'
1166
+ },
1167
+ message: 'Invalid month/year format. Use MM/YYYY.',
1168
+ },
1169
+ }],
1170
+ },
1171
+ })
1172
+
1173
+ const input = wrapper.find('input')
1174
+ await input.setValue('99/2025')
1175
+ await input.trigger('blur')
1176
+
1177
+ expect(wrapper.find('.v-field--error').exists()).toBe(true)
1178
+ expect(wrapper.find('.v-input__details').text()).toBe('Invalid month/year format. Use MM/YYYY.')
1179
+
1180
+ wrapper.unmount()
1181
+ })
1182
+
1183
+ it('should not show an error message when the input value is valid', async () => {
1184
+ const wrapper = mount(MonthPicker, {
1185
+ props: {
1186
+ label: 'Début du projet',
1187
+ customRules: [{
1188
+ type: 'custom',
1189
+ options: {
1190
+ validate: (value: string) => {
1191
+ const regex = /^(0[1-9]|1[0-2])\/\d{4}$/
1192
+ return regex.test(value) || 'Invalid month/year format. Use MM/YYYY.'
1193
+ },
1194
+ message: 'Invalid month/year format. Use MM/YYYY.',
1195
+ },
1196
+ }],
1197
+ },
1198
+ })
1199
+
1200
+ const input = wrapper.find('input')
1201
+ await input.setValue('12/2025')
1202
+ await input.trigger('blur')
1203
+ await input.trigger('focus')
1204
+
1205
+ expect(wrapper.find('.v-field--error').exists()).toBe(false)
1206
+ expect(wrapper.find('.v-input__details').text()).toBe('Format MM/AAAA')
1207
+
1208
+ wrapper.unmount()
1209
+ })
1210
+
1211
+ it ('show the error when the value is selected in the visual picker', async () => {
1212
+ const wrapper = mount(MonthPicker, {
1213
+ props: {
1214
+ label: 'Début du projet',
1215
+ customRules: [{
1216
+ type: 'custom',
1217
+ options: {
1218
+ validate: (value: string) => {
1219
+ const year = value.split('/')[1]
1220
+ return !!year && parseInt(year!) >= 2026
1221
+ },
1222
+ message: 'The year must be 2026 or later.',
1223
+ },
1224
+ }],
1225
+ },
1226
+ attachTo: document.body,
1227
+ })
1228
+
1229
+ // Wait for the VMenu to resolve the differents ref used to find the activator element
1230
+ await nextTick()
1231
+ await nextTick()
1232
+
1233
+ const toggleBtn = wrapper.find('.month-picker-input__toggle-btn')
1234
+ await toggleBtn.trigger('click')
1235
+
1236
+ const monthButton = wrapper.findComponent({ name: 'MonthSelector' }).find('.month-1') // January button
1237
+ await monthButton.trigger('click')
1238
+
1239
+ const yearButton = wrapper.findComponent({ name: 'YearSelector' }).find('.year-2025')
1240
+ await yearButton.trigger('click')
1241
+
1242
+ expect(wrapper.find('.v-field--error').exists()).toBe(true)
1243
+ expect(wrapper.find('.v-input__details').text()).toBe('The year must be 2026 or later.')
1244
+
1245
+ wrapper.unmount()
1246
+ })
1247
+ })
1248
+ })