@cnamts/synapse 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/dist/{AutocompleteFilter-DXd4szWO.js → AutocompleteFilter-CGF33skz.js} +1 -1
  2. package/dist/{DateFilter-BD59Kgwf.js → DateFilter-D7-MsKtx.js} +1 -1
  3. package/dist/{NumberFilter-BSMZE7uw.js → NumberFilter-bjQPPfsj.js} +1 -1
  4. package/dist/{PeriodFilter-keUdSSk0.js → PeriodFilter-B3wJpK8-.js} +1 -1
  5. package/dist/{SelectFilter-Dhvvwazl.js → SelectFilter-BN6DbKAV.js} +1 -1
  6. package/dist/{TextFilter-CU8FpXz0.js → TextFilter-BffP0J2f.js} +1 -1
  7. package/dist/{apLightTheme2026-DbS7BPUf.js → apLightTheme2026-C4ygwMHC.js} +11 -11
  8. package/dist/components/Amelipro/AmeliproAutoCompleteField/AmeliproAutoCompleteField.d.ts +6 -6
  9. package/dist/components/Amelipro/AmeliproSelect/AmeliproSelect.d.ts +6 -6
  10. package/dist/components/Amelipro/AmeliproTabs/AmeliproTabs.d.ts +6 -6
  11. package/dist/components/Captcha/Captcha.d.ts +27 -16
  12. package/dist/components/Captcha/CaptchaForm.d.ts +29 -3
  13. package/dist/components/Captcha/types.d.ts +14 -0
  14. package/dist/components/Captcha/useCaptchaValidation.d.ts +37 -0
  15. package/dist/components/Customs/Selects/SelectBtnField/SelectBtnField.d.ts +33 -13
  16. package/dist/components/Customs/Selects/SelectBtnField/composables/useSelectBtnFieldValidation.d.ts +23 -0
  17. package/dist/components/Customs/Selects/SyAutocomplete/composables/useSyAutocompleteValidation.d.ts +2 -2
  18. package/dist/components/Customs/Selects/SySelect/composables/useSySelectValidation.d.ts +2 -2
  19. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +17 -48
  20. package/dist/components/Customs/SyCheckBoxGroup/composables/useSyCheckBoxGroupValidation.d.ts +29 -0
  21. package/dist/components/Customs/SyCheckBoxGroup/types.d.ts +46 -0
  22. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +16 -51
  23. package/dist/components/Customs/SyCheckbox/composables/useSyCheckboxValidation.d.ts +27 -0
  24. package/dist/components/Customs/SyCheckbox/types.d.ts +49 -0
  25. package/dist/components/Customs/SyTextField/FieldState.d.ts +5 -0
  26. package/dist/components/Customs/SyTextField/useSyTextFieldValidation.d.ts +3 -3
  27. package/dist/components/DialogBox/DialogBox.d.ts +2 -0
  28. package/dist/components/DialogBox/locales.d.ts +1 -0
  29. package/dist/components/FilterSideBar/FilterSideBar.d.ts +4 -0
  30. package/dist/components/LunarCalendar/LunarCalendar.d.ts +43 -14
  31. package/dist/components/LunarCalendar/types.d.ts +35 -0
  32. package/dist/components/LunarCalendar/useLunarCalendarValidation.d.ts +11 -12
  33. package/dist/components/MonthPicker/MonthPicker.d.ts +72 -1747
  34. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +21 -1733
  35. package/dist/components/MonthPicker/MonthPickerText/useTextField.d.ts +5 -0
  36. package/dist/components/MonthPicker/locales.d.ts +1 -0
  37. package/dist/components/MonthPicker/types.d.ts +11 -0
  38. package/dist/components/MonthPicker/useMonthPickerValidation.d.ts +37 -24
  39. package/dist/components/NirField/NirField.d.ts +6 -4
  40. package/dist/components/NirField/useNirValidation.d.ts +7 -5
  41. package/dist/components/PageContainer/PageContainer.d.ts +8 -0
  42. package/dist/components/PasswordField/PasswordField.d.ts +2 -2
  43. package/dist/components/PasswordField/usePasswordFieldValidation.d.ts +2 -2
  44. package/dist/components/PhoneField/PhoneField.d.ts +960 -1938
  45. package/dist/components/PhoneField/indicatifs.d.ts +715 -8
  46. package/dist/components/PhoneField/locales.d.ts +7 -0
  47. package/dist/components/PhoneField/types.d.ts +29 -0
  48. package/dist/components/PhoneField/usePhoneFieldValidation.d.ts +45 -0
  49. package/dist/components/PhoneField/usePhoneIndicatifs.d.ts +947 -0
  50. package/dist/components/SyTextArea/composables/useSyTextAreaValidation.d.ts +2 -2
  51. package/dist/composables/unifyValidation/documentationValidationProps.d.ts +1 -1
  52. package/dist/composables/unifyValidation/useValidation.d.ts +4 -5
  53. package/dist/design-system-v3.js +2 -2
  54. package/dist/designTokens/tokens/amelipro/apLightTheme.d.ts +10 -10
  55. package/dist/designTokens/tokens/baseTokens.d.ts +18 -18
  56. package/dist/designTokens/tokens/cnam/cnamLightTheme.d.ts +10 -10
  57. package/dist/designTokens/tokens/pa/paLightTheme.d.ts +10 -10
  58. package/dist/designTokens/tokens/semanticTokens.d.ts +14 -14
  59. package/dist/{main-D8ryUoS5.js → main-C4wAktOs.js} +13718 -12991
  60. package/dist/synapse.css +1 -1
  61. package/dist/vuetifyConfig.js +1 -1
  62. package/package.json +7 -7
  63. package/src/assets/compat/_legacy-tokens.scss +91 -0
  64. package/src/assets/overrides/_utilities.scss +23 -0
  65. package/src/components/Accordion/Accordion.stories.ts +121 -1
  66. package/src/components/BackBtn/BackBtn.mdx +1 -1
  67. package/src/components/BackToTopBtn/BackToTopBtn.mdx +0 -1
  68. package/src/components/Captcha/Captcha.stories.ts +134 -31
  69. package/src/components/Captcha/Captcha.vue +95 -28
  70. package/src/components/Captcha/CaptchaForm.vue +51 -22
  71. package/src/components/Captcha/tests/Captcha.focus.spec.ts +214 -0
  72. package/src/components/Captcha/tests/Captcha.spec.ts +233 -24
  73. package/src/components/Captcha/tests/CaptchaForm.spec.ts +82 -0
  74. package/src/components/Captcha/tests/__snapshots__/Captcha.spec.ts.snap +16 -42
  75. package/src/components/Captcha/types.ts +15 -0
  76. package/src/components/Captcha/useCaptchaValidation.ts +87 -0
  77. package/src/components/Captcha/validation/validation.stories.ts +1194 -0
  78. package/src/components/ChipList/ChipList.mdx +0 -1
  79. package/src/components/CollapsibleList/CollapsibleList.mdx +0 -1
  80. package/src/components/CookieBanner/CookieBanner.mdx +0 -1
  81. package/src/components/CopyBtn/CopyBtn.mdx +0 -1
  82. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.stories.ts +123 -439
  83. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.vue +147 -41
  84. package/src/components/Customs/Selects/SelectBtnField/Validation/Validation.stories.ts +600 -0
  85. package/src/components/Customs/Selects/SelectBtnField/composables/useSelectBtnFieldValidation.ts +87 -0
  86. package/src/components/Customs/Selects/SelectBtnField/tests/SelectBtnField.spec.ts +402 -33
  87. package/src/components/Customs/Selects/SelectBtnField/tests/__snapshots__/SelectBtnField.spec.ts.snap +52 -38
  88. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +342 -162
  89. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +77 -129
  90. package/src/components/Customs/SyCheckBoxGroup/Validation/Validation.stories.ts +1008 -0
  91. package/src/components/Customs/SyCheckBoxGroup/composables/useSyCheckBoxGroupValidation.ts +107 -0
  92. package/src/components/Customs/SyCheckBoxGroup/tests/SyCheckBoxGroup.spec.ts +180 -7
  93. package/src/components/Customs/SyCheckBoxGroup/types.ts +49 -0
  94. package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +41 -161
  95. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +71 -148
  96. package/src/components/Customs/SyCheckbox/Validation/Validation.stories.ts +654 -0
  97. package/src/components/Customs/SyCheckbox/composables/useSyCheckboxValidation.ts +105 -0
  98. package/src/components/Customs/SyCheckbox/tests/SyCheckbox.spec.ts +106 -0
  99. package/src/components/Customs/SyCheckbox/tests/useSyCheckboxValidation.spec.ts +98 -0
  100. package/src/components/Customs/SyCheckbox/types.ts +51 -0
  101. package/src/components/Customs/SyTextField/FieldState.vue +50 -0
  102. package/src/components/Customs/SyTextField/SyTextField.vue +12 -9
  103. package/src/components/Customs/SyTextField/useSyTextFieldValidation.ts +2 -11
  104. package/src/components/DataList/DataList.mdx +0 -1
  105. package/src/components/DataListGroup/DataListGroup.mdx +0 -1
  106. package/src/components/DiacriticPicker/DiacriticPicker.mdx +0 -1
  107. package/src/components/DialogBox/DialogBox.mdx +0 -1
  108. package/src/components/DialogBox/DialogBox.stories.ts +399 -4
  109. package/src/components/DialogBox/DialogBox.vue +20 -0
  110. package/src/components/DialogBox/locales.ts +1 -0
  111. package/src/components/DialogBox/tests/DialogBox.spec.ts +73 -0
  112. package/src/components/DialogBox/tests/DialogBox.visual.cy.ts +24 -0
  113. package/src/components/ErrorPage/ErrorPage.mdx +1 -1
  114. package/src/components/ExternalLinks/ExternalLinks.mdx +0 -1
  115. package/src/components/FileList/FileList.mdx +0 -1
  116. package/src/components/FilterInline/FilterInline.mdx +0 -1
  117. package/src/components/FilterSideBar/FilterSideBar.mdx +8 -1
  118. package/src/components/FilterSideBar/FilterSideBar.stories.ts +133 -1
  119. package/src/components/FilterSideBar/FilterSideBar.vue +19 -2
  120. package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +55 -0
  121. package/src/components/FooterBar/FooterBar.mdx +0 -1
  122. package/src/components/FranceConnectBtn/FranceConnectBtn.mdx +0 -1
  123. package/src/components/HeaderBar/HeaderBar.mdx +0 -1
  124. package/src/components/HeaderLoading/HeaderLoading.mdx +0 -1
  125. package/src/components/LangBtn/LangBtn.mdx +0 -1
  126. package/src/components/Logo/Logo.mdx +1 -1
  127. package/src/components/LunarCalendar/LunarCalendar.mdx +6 -9
  128. package/src/components/LunarCalendar/LunarCalendar.stories.ts +243 -46
  129. package/src/components/LunarCalendar/LunarCalendar.vue +61 -26
  130. package/src/components/LunarCalendar/Validation/Validation.stories.ts +717 -0
  131. package/src/components/LunarCalendar/tests/LunarCalendar.a11y.spec.ts +1 -1
  132. package/src/components/LunarCalendar/tests/LunarCalendar.spec.ts +197 -6
  133. package/src/components/LunarCalendar/tests/useLunarCalendarValidation.spec.ts +287 -0
  134. package/src/components/LunarCalendar/types.ts +39 -0
  135. package/src/components/LunarCalendar/useLunarCalendarValidation.ts +115 -39
  136. package/src/components/MonthPicker/MonthPicker.stories.ts +38 -281
  137. package/src/components/MonthPicker/MonthPicker.vue +66 -17
  138. package/src/components/MonthPicker/MonthPickerText/MonthPickerInput.vue +44 -20
  139. package/src/components/MonthPicker/MonthPickerText/useTextField.ts +5 -0
  140. package/src/components/MonthPicker/Validation/Validation.stories.ts +1117 -0
  141. package/src/components/MonthPicker/locales.ts +1 -0
  142. package/src/components/MonthPicker/tests/MonthPicker.spec.ts +353 -2
  143. package/src/components/MonthPicker/tests/__snapshots__/MonthPicker.spec.ts.snap +12 -8
  144. package/src/components/MonthPicker/types.ts +16 -0
  145. package/src/components/MonthPicker/useMonthPickerValidation.ts +64 -27
  146. package/src/components/NirField/NirField.mdx +120 -66
  147. package/src/components/NirField/NirField.stories.ts +216 -0
  148. package/src/components/NirField/useNirValidation.ts +16 -17
  149. package/src/components/NotFoundPage/tests/__snapshots__/NotFoundPage.spec.ts.snap +263 -245
  150. package/src/components/NotificationBar/NotificationBar.mdx +0 -1
  151. package/src/components/PageContainer/PageContainer.mdx +0 -1
  152. package/src/components/PageContainer/PageContainer.stories.ts +170 -2
  153. package/src/components/PageContainer/PageContainer.vue +63 -8
  154. package/src/components/PageContainer/tests/__snapshots__/PageContainer.spec.ts.snap +19 -11
  155. package/src/components/PaginatedTable/PaginatedTable.mdx +0 -1
  156. package/src/components/PeriodField/PeriodField.mdx +0 -1
  157. package/src/components/PhoneField/PhoneField.mdx +2 -3
  158. package/src/components/PhoneField/PhoneField.stories.ts +227 -410
  159. package/src/components/PhoneField/PhoneField.vue +204 -438
  160. package/src/components/PhoneField/indicatifs.ts +1 -1
  161. package/src/components/PhoneField/locales.ts +7 -0
  162. package/src/components/PhoneField/tests/PhoneField.a11y.spec.ts +0 -1
  163. package/src/components/PhoneField/tests/PhoneField.spec.ts +517 -220
  164. package/src/components/PhoneField/types.ts +30 -0
  165. package/src/components/PhoneField/usePhoneFieldValidation.ts +119 -0
  166. package/src/components/PhoneField/usePhoneIndicatifs.ts +89 -0
  167. package/src/components/PhoneField/validation/validation.stories.ts +717 -0
  168. package/src/components/RangeField/RangeField.mdx +0 -1
  169. package/src/components/RatingPicker/RatingPicker.mdx +0 -1
  170. package/src/components/SocialMediaLinks/SocialMediaLinks.mdx +0 -1
  171. package/src/components/StatusPage/StatusPage.vue +1 -0
  172. package/src/components/StatusPage/tests/__snapshots__/StatusPage.spec.ts.snap +248 -230
  173. package/src/components/SubHeader/SubHeader.mdx +5 -6
  174. package/src/components/Tables/common/tests/SyTableFilter.spec.ts +11 -12
  175. package/src/components/UploadWorkflow/UploadWorkflow.mdx +0 -1
  176. package/src/components/UserMenuBtn/UserMenuBtn.mdx +0 -1
  177. package/src/components/UserMenuBtn/UserMenuBtn.stories.ts +177 -0
  178. package/src/composables/unifyValidation/documentationValidationProps.ts +1 -1
  179. package/src/composables/unifyValidation/tests/useValidation.spec.ts +13 -1
  180. package/src/composables/unifyValidation/useValidation.ts +37 -33
  181. package/src/composantsVuetify/VCard/VCard.mdx +4 -0
  182. package/src/composantsVuetify/VCard/v-card.stories.ts +93 -1
  183. package/src/composantsVuetify/VCarousel/VCarousel.mdx +74 -0
  184. package/src/composantsVuetify/VCarousel/v-carousel.stories.ts +531 -0
  185. package/src/composantsVuetify/VNavigationDrawer/VNavgationDrawer.mdx +53 -0
  186. package/src/composantsVuetify/VNavigationDrawer/v-navigation-drawer.stories.ts +310 -0
  187. package/src/composantsVuetify/VSlideGroup/VSlideGroup.mdx +105 -0
  188. package/src/composantsVuetify/VSlideGroup/v-slide-group.stories.ts +463 -0
  189. package/src/designTokens/tokens/baseColors.ts +1 -1
  190. package/src/designTokens/tokens/baseTokens.ts +18 -18
  191. package/src/stories/Components/Components.stories.ts +34 -1
  192. package/src/stories/Demarrer/Releases.stories.ts +16 -2
  193. package/src/stories/DesignTokens/Arrondis.mdx +1 -1
  194. package/src/stories/DesignTokens/Correspondances.mdx +219 -0
  195. package/src/stories/DesignTokens/UtiliserLesTokens.mdx +235 -0
  196. package/src/stories/DesignTokens/colors.stories.ts +569 -569
  197. package/src/stories/GuideDuDev/Amelipro.stories.ts +335 -267
  198. package/dist/components/LunarCalendar/useLunarCalendarRules.d.ts +0 -5
  199. package/dist/components/PhoneField/tests/types.d.ts +0 -18
  200. package/src/components/LunarCalendar/tests/useLunarCalendarRules.spec.ts +0 -184
  201. package/src/components/LunarCalendar/useLunarCalendarRules.ts +0 -96
  202. package/src/components/PhoneField/tests/types.d.ts +0 -19
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, vi, afterEach } from 'vitest'
2
- import { mount } from '@vue/test-utils'
2
+ import { flushPromises, mount, VueWrapper } from '@vue/test-utils'
3
3
  import Captcha from '../Captcha.vue'
4
+ import type { ComponentPublicInstance } from 'vue/dist/vue.js'
4
5
 
5
6
  describe('Captcha', () => {
6
7
  afterEach(() => {
@@ -24,11 +25,9 @@ describe('Captcha', () => {
24
25
  })
25
26
 
26
27
  // Wait for the component to fully mount and initialize
27
- await wrapper.vm.$nextTick()
28
- await wrapper.vm.$nextTick()
29
28
 
30
29
  // Allow additional time for async initialization
31
- await new Promise(resolve => setTimeout(resolve, 50))
30
+ await flushPromises()
32
31
 
33
32
  expect(fetch).toHaveBeenCalledTimes(1)
34
33
 
@@ -64,11 +63,9 @@ describe('Captcha', () => {
64
63
  })
65
64
 
66
65
  // Wait for the component to fully mount and initialize
67
- await wrapper.vm.$nextTick()
68
- await wrapper.vm.$nextTick()
69
66
 
70
67
  // Allow additional time for async initialization
71
- await new Promise(resolve => setTimeout(resolve, 50))
68
+ await flushPromises()
72
69
 
73
70
  expect(fetch).toHaveBeenCalledTimes(1)
74
71
 
@@ -156,12 +153,8 @@ describe('Captcha', () => {
156
153
  },
157
154
  })
158
155
 
159
- await wrapper.vm.$nextTick()
160
- await wrapper.vm.$nextTick()
161
-
162
156
  // Simulate text input change
163
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
- await (wrapper.vm as any).emitChangeValueEvent('new-text-value')
157
+ await (wrapper.vm as ComponentPublicInstance<typeof Captcha>).emitChangeValueEvent('new-text-value')
165
158
 
166
159
  expect(wrapper.emitted('update:modelValue')).toBeTruthy()
167
160
  expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['new-text-value'])
@@ -183,9 +176,7 @@ describe('Captcha', () => {
183
176
  },
184
177
  })
185
178
 
186
- await wrapper.vm.$nextTick()
187
- await wrapper.vm.$nextTick()
188
- await new Promise(resolve => setTimeout(resolve, 50))
179
+ await flushPromises()
189
180
 
190
181
  expect(fetch).toHaveBeenCalledWith('/captcha/captcha.json', expect.any(Object))
191
182
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -200,7 +191,7 @@ describe('Captcha', () => {
200
191
  } as any
201
192
  vi.stubGlobal('fetch', vi.fn().mockResolvedValue(response))
202
193
 
203
- const wrapper = mount(Captcha, {
194
+ const wrapper = mount<typeof Captcha>(Captcha, {
204
195
  props: {
205
196
  urlCreate: '/captcha/captcha.json',
206
197
  urlGetImage: '/captcha/captcha.png',
@@ -212,11 +203,10 @@ describe('Captcha', () => {
212
203
  await wrapper.vm.$nextTick()
213
204
 
214
205
  // Change modelValue prop
215
- await wrapper.setProps({ modelValue: 'updated-value' })
206
+ await (wrapper as VueWrapper<ComponentPublicInstance<typeof Captcha>>).setProps({ modelValue: 'updated-value' })
216
207
  await wrapper.vm.$nextTick()
217
208
 
218
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
- expect((wrapper.vm as any).text).toBe('updated-value')
209
+ expect((wrapper.vm as ComponentPublicInstance<typeof Captcha>).text).toBe('updated-value')
220
210
  })
221
211
 
222
212
  it('handles helpDesk prop correctly', async () => {
@@ -236,9 +226,7 @@ describe('Captcha', () => {
236
226
  },
237
227
  })
238
228
 
239
- await wrapper.vm.$nextTick()
240
- await wrapper.vm.$nextTick()
241
- await new Promise(resolve => setTimeout(resolve, 50))
229
+ await flushPromises()
242
230
 
243
231
  expect(wrapper.text()).toContain('1234')
244
232
  })
@@ -260,10 +248,231 @@ describe('Captcha', () => {
260
248
  },
261
249
  })
262
250
 
251
+ await flushPromises()
252
+
253
+ expect(wrapper.find('.captcha-helpdesk').exists()).toBe(false)
254
+ })
255
+
256
+ it('displays required validation error on blur when captcha text is empty', async () => {
257
+ const response = {
258
+ ok: true,
259
+ json: async () => ({ id: 'captcha-id' }),
260
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
+ } as any
262
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(response))
263
+
264
+ const wrapper = mount(Captcha, {
265
+ props: {
266
+ urlCreate: '/captcha/captcha.json',
267
+ urlGetImage: '/captcha/captcha.png',
268
+ urlGetAudio: '/captcha/captcha.mp3',
269
+ required: true,
270
+ modelValue: '',
271
+ },
272
+ })
273
+
274
+ await flushPromises()
275
+
276
+ const input = wrapper.find('input')
277
+ expect(input.exists()).toBe(true)
278
+
279
+ await input.trigger('focus')
280
+ await input.trigger('blur')
281
+
282
+ expect(wrapper.text()).toContain('est requis')
283
+ })
284
+
285
+ it('applies customRules and displays custom error message on blur', async () => {
286
+ const response = {
287
+ ok: true,
288
+ json: async () => ({ id: 'captcha-id' }),
289
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
290
+ } as any
291
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(response))
292
+
293
+ const wrapper = mount(Captcha, {
294
+ props: {
295
+ urlCreate: '/captcha/captcha.json',
296
+ urlGetImage: '/captcha/captcha.png',
297
+ urlGetAudio: '/captcha/captcha.mp3',
298
+ modelValue: 'abc',
299
+ customRules: [{
300
+ type: 'custom',
301
+ options: {
302
+ validate: () => false,
303
+ message: 'Erreur custom captcha',
304
+ },
305
+ }],
306
+ },
307
+ })
308
+
309
+ await flushPromises()
310
+
311
+ const input = wrapper.find('input')
312
+ expect(input.exists()).toBe(true)
313
+
314
+ await input.trigger('focus')
315
+ await input.trigger('blur')
316
+
317
+ expect(wrapper.text()).toContain('Erreur custom captcha')
318
+ })
319
+
320
+ it('applies customWarningRules and displays custom warning message on blur', async () => {
321
+ const response = {
322
+ ok: true,
323
+ json: async () => ({ id: 'captcha-id' }),
324
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
325
+ } as any
326
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(response))
327
+
328
+ const wrapper = mount(Captcha, {
329
+ props: {
330
+ urlCreate: '/captcha/captcha.json',
331
+ urlGetImage: '/captcha/captcha.png',
332
+ urlGetAudio: '/captcha/captcha.mp3',
333
+ modelValue: 'abc',
334
+ customWarningRules: [{
335
+ type: 'custom',
336
+ options: {
337
+ validate: () => false,
338
+ isWarning: true,
339
+ warningMessage: 'Warning custom captcha',
340
+ },
341
+ }],
342
+ },
343
+ })
344
+
263
345
  await wrapper.vm.$nextTick()
264
346
  await wrapper.vm.$nextTick()
265
- await new Promise(resolve => setTimeout(resolve, 50))
347
+ await flushPromises()
266
348
 
267
- expect(wrapper.find('.captcha-helpdesk').exists()).toBe(false)
349
+ const input = wrapper.find('input')
350
+ expect(input.exists()).toBe(true)
351
+
352
+ await input.trigger('focus')
353
+ await input.trigger('blur')
354
+ await wrapper.vm.$nextTick()
355
+ await wrapper.vm.$nextTick()
356
+
357
+ expect(wrapper.text()).toContain('Warning custom captcha')
358
+ })
359
+
360
+ it('applies customSuccessRules and displays custom success message on blur', async () => {
361
+ const response = {
362
+ ok: true,
363
+ json: async () => ({ id: 'captcha-id' }),
364
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
365
+ } as any
366
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(response))
367
+
368
+ const wrapper = mount(Captcha, {
369
+ props: {
370
+ urlCreate: '/captcha/captcha.json',
371
+ urlGetImage: '/captcha/captcha.png',
372
+ urlGetAudio: '/captcha/captcha.mp3',
373
+ modelValue: 'abc',
374
+ showSuccessMessages: true,
375
+ customSuccessRules: [{
376
+ type: 'custom',
377
+ options: {
378
+ validate: () => true,
379
+ successMessage: 'Succès custom captcha',
380
+ },
381
+ }],
382
+ },
383
+ })
384
+
385
+ await wrapper.vm.$nextTick()
386
+ await wrapper.vm.$nextTick()
387
+ await flushPromises()
388
+
389
+ const input = wrapper.find('input')
390
+ expect(input.exists()).toBe(true)
391
+
392
+ await input.trigger('focus')
393
+ await input.trigger('blur')
394
+ await wrapper.vm.$nextTick()
395
+ await wrapper.vm.$nextTick()
396
+
397
+ expect(wrapper.text()).toContain('Succès custom captcha')
398
+ })
399
+
400
+ it('resets the validation when the exposed clearValidation method is called', async () => {
401
+ const response = {
402
+ ok: true,
403
+ json: async () => ({ id: 'captcha-id' }),
404
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
405
+ } as any
406
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(response))
407
+
408
+ const wrapper = mount(Captcha, {
409
+ props: {
410
+ urlCreate: '/captcha/captcha.json',
411
+ urlGetImage: '/captcha/captcha.png',
412
+ urlGetAudio: '/captcha/captcha.mp3',
413
+ required: true,
414
+ modelValue: '',
415
+ },
416
+ })
417
+
418
+ await wrapper.vm.$nextTick()
419
+ await wrapper.vm.$nextTick()
420
+ await flushPromises()
421
+
422
+ const input = wrapper.find('input')
423
+ expect(input.exists()).toBe(true)
424
+
425
+ await input.trigger('focus')
426
+ await input.trigger('blur')
427
+ await wrapper.vm.$nextTick()
428
+ await wrapper.vm.$nextTick()
429
+
430
+ expect(wrapper.text()).toContain('est requis')
431
+
432
+ // Call the exposed clearValidation method
433
+ await (wrapper.vm as ComponentPublicInstance<typeof Captcha>).clearValidation()
434
+
435
+ expect(wrapper.text()).not.toContain('est requis')
436
+ })
437
+
438
+ it('resets the field and the validation when the the exposed reset method is called', async () => {
439
+ const response = {
440
+ ok: true,
441
+ json: async () => ({ id: 'captcha-id' }),
442
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
443
+ } as any
444
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(response))
445
+ const wrapper = mount(Captcha, {
446
+ props: {
447
+ urlCreate: '/captcha/captcha.json',
448
+ urlGetImage: '/captcha/captcha.png',
449
+ urlGetAudio: '/captcha/captcha.mp3',
450
+ required: true,
451
+ modelValue: '',
452
+ customRules: [{
453
+ type: 'custom',
454
+ options: {
455
+ validate: () => false,
456
+ message: 'Erreur custom captcha',
457
+ },
458
+ }],
459
+ },
460
+ })
461
+
462
+ await flushPromises()
463
+
464
+ const input = wrapper.find('input')
465
+ expect(input.exists()).toBe(true)
466
+
467
+ await input.trigger('focus')
468
+ await input.setValue('abc')
469
+ await input.trigger('blur')
470
+
471
+ expect(wrapper.text()).toContain('Erreur custom captcha')
472
+
473
+ await (wrapper.vm as ComponentPublicInstance<typeof Captcha>).reset()
474
+
475
+ expect(wrapper.text()).not.toContain('Erreur custom captcha')
476
+ expect(input.element.value).toBe('')
268
477
  })
269
478
  })
@@ -0,0 +1,82 @@
1
+ /* eslint-disable vue/one-component-per-file */
2
+ import { describe, expect, it } from 'vitest'
3
+ import { defineComponent } from 'vue'
4
+ import { mount } from '@vue/test-utils'
5
+ import CaptchaForm from '../CaptchaForm.vue'
6
+ import { locales } from '../locales'
7
+
8
+ const SyTextFieldStub = defineComponent({
9
+ name: 'SyTextField',
10
+ props: {
11
+ errorMessages: { type: Array, default: () => [] },
12
+ hasSuccess: { type: Boolean, default: false },
13
+ readonly: { type: Boolean, default: false },
14
+ isClearable: { type: Boolean, default: false },
15
+ disabled: { type: Boolean, default: false },
16
+ showSuccessMessages: { type: Boolean, default: true },
17
+ label: { type: String, default: '' },
18
+ customRules: { type: Array, default: () => [] },
19
+ },
20
+ emits: ['update:modelValue'],
21
+ template: '<div class="sy-text-field-stub" />',
22
+ })
23
+
24
+ const VSheetStub = defineComponent({
25
+ name: 'VSheet',
26
+ template: '<div class="v-sheet-stub"><slot /></div>',
27
+ })
28
+
29
+ describe('CaptchaForm', () => {
30
+ it('forwards current captcha props to SyTextField', () => {
31
+ const wrapper = mount(CaptchaForm, {
32
+ props: {
33
+ label: 'Captcha label',
34
+ locales,
35
+ state: 'rejected',
36
+ errorMessages: ['error message'],
37
+ success: true,
38
+ },
39
+ global: {
40
+ stubs: {
41
+ VSheet: VSheetStub,
42
+ SyTextField: SyTextFieldStub,
43
+ },
44
+ },
45
+ })
46
+
47
+ const textField = wrapper.findComponent({ name: 'SyTextField' })
48
+
49
+ expect(textField.exists()).toBe(true)
50
+ expect(textField.props('errorMessages')).toEqual(['error message'])
51
+ expect(textField.props('hasSuccess')).toBe(false)
52
+ expect(textField.props('readonly')).toBe(true)
53
+ expect(textField.props('isClearable')).toBe(false)
54
+ expect(textField.props('disabled')).toBe(true)
55
+ expect(textField.props('showSuccessMessages')).toBe(false)
56
+ expect(textField.props('label')).toBe('Captcha label')
57
+ expect(textField.props('customRules')).toEqual([])
58
+ })
59
+
60
+ it('emits update:modelValue when SyTextField updates the value', async () => {
61
+ const wrapper = mount(CaptchaForm, {
62
+ props: {
63
+ label: 'Captcha label',
64
+ locales,
65
+ errors: [],
66
+ success: false,
67
+ },
68
+ global: {
69
+ stubs: {
70
+ VSheet: VSheetStub,
71
+ SyTextField: SyTextFieldStub,
72
+ },
73
+ },
74
+ })
75
+
76
+ const textField = wrapper.findComponent({ name: 'SyTextField' })
77
+ await textField.vm.$emit('update:modelValue', 'abc123')
78
+
79
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
80
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['abc123'])
81
+ })
82
+ })
@@ -117,22 +117,22 @@ exports[`Captcha > renders correctly in audio mode 1`] = `
117
117
  v-field-label
118
118
  v-label
119
119
  "
120
- for="input-v-3"
121
- id="input-v-3-label"
120
+ for="input-v-4"
121
+ id="input-v-4-label"
122
122
  >
123
123
  <!---->
124
- Caractères de l’audio
124
+ Caractères de l’image
125
125
  </label>
126
126
  <!---->
127
127
  <input
128
- aria-describedby="input-v-3-messages"
129
- aria-label="Caractères de l’audio"
130
- aria-labelledby="input-v-3-label"
128
+ aria-label="Caractères de l’image"
129
+ aria-labelledby="input-v-4-label"
130
+ aria-required="false"
131
131
  autocomplete="off"
132
132
  class="v-field__input"
133
133
  direction="horizontal"
134
- id="input-v-3"
135
- title="Caractères de l’audio"
134
+ id="input-v-4"
135
+ title="Caractères de l’image"
136
136
  type="text"
137
137
  />
138
138
  <!---->
@@ -158,7 +158,7 @@ exports[`Captcha > renders correctly in audio mode 1`] = `
158
158
  "
159
159
  >
160
160
  <!---->
161
- Caractères de l’audio
161
+ Caractères de l’image
162
162
  </label>
163
163
  </div>
164
164
  <div class="v-field__outline__end"></div>
@@ -167,20 +167,7 @@ exports[`Captcha > renders correctly in audio mode 1`] = `
167
167
  </div>
168
168
  </div>
169
169
  <!---->
170
- <div class="v-input__details">
171
- <transition-group-stub
172
- appear="false"
173
- class="v-messages"
174
- css="true"
175
- id="input-v-3-messages"
176
- name="slide-y-transition"
177
- persisted="false"
178
- tag="div"
179
- >
180
- <!---->
181
- </transition-group-stub>
182
- <!---->
183
- </div>
170
+ <!---->
184
171
  </div>
185
172
  <!-- v-if -->
186
173
  </div>
@@ -461,21 +448,21 @@ exports[`Captcha > renders correctly in image mode 1`] = `
461
448
  v-field-label
462
449
  v-label
463
450
  "
464
- for="input-v-2"
465
- id="input-v-2-label"
451
+ for="input-v-3"
452
+ id="input-v-3-label"
466
453
  >
467
454
  <!---->
468
455
  Caractères de l’image
469
456
  </label>
470
457
  <!---->
471
458
  <input
472
- aria-describedby="input-v-2-messages"
473
459
  aria-label="Caractères de l’image"
474
- aria-labelledby="input-v-2-label"
460
+ aria-labelledby="input-v-3-label"
461
+ aria-required="false"
475
462
  autocomplete="off"
476
463
  class="v-field__input"
477
464
  direction="horizontal"
478
- id="input-v-2"
465
+ id="input-v-3"
479
466
  title="Caractères de l’image"
480
467
  type="text"
481
468
  />
@@ -511,20 +498,7 @@ exports[`Captcha > renders correctly in image mode 1`] = `
511
498
  </div>
512
499
  </div>
513
500
  <!---->
514
- <div class="v-input__details">
515
- <transition-group-stub
516
- appear="false"
517
- class="v-messages"
518
- css="true"
519
- id="input-v-2-messages"
520
- name="slide-y-transition"
521
- persisted="false"
522
- tag="div"
523
- >
524
- <!---->
525
- </transition-group-stub>
526
- <!---->
527
- </div>
501
+ <!---->
528
502
  </div>
529
503
  <!-- v-if -->
530
504
  </div>
@@ -1,2 +1,17 @@
1
1
  export type CaptchaType = 'image' | 'audio' | 'choice'
2
2
  export type StateType = 'idle' | 'pending' | 'resolved' | 'rejected'
3
+ import type { FieldValidationProps } from '@/composables/unifyValidation/useValidation'
4
+ import type { locales as defaultLocales } from './locales'
5
+
6
+ export type CaptchaProps = FieldValidationProps & {
7
+ modelValue?: string | undefined
8
+ urlCreate: string
9
+ urlGetImage: string
10
+ urlGetAudio: string
11
+ type?: CaptchaType
12
+ tagTitle?: string
13
+ helpDesk?: string | false
14
+ isClearable?: boolean
15
+ locale?: string
16
+ locales?: typeof defaultLocales
17
+ }
@@ -0,0 +1,87 @@
1
+ import { computed, type Ref } from 'vue'
2
+ import { useValidation, type VuetifyValidationRule } from '@/composables/unifyValidation/useValidation'
3
+ import type { ValidationRule as SyValidationRule } from '@/composables/validation/useValidation'
4
+ import type { locales as defaultLocales } from './locales'
5
+
6
+ export function useCaptchaValidation(params: {
7
+ modelValue: Ref<string | null>
8
+ readonly: Ref<boolean>
9
+ disabled: Ref<boolean>
10
+ required: Ref<boolean>
11
+ isValidateOnBlur: Ref<boolean>
12
+ showSuccessMessages: Ref<boolean>
13
+ disableErrorHandling: Ref<boolean>
14
+ useVuetifyValidation: Ref<boolean>
15
+ label: Ref<string>
16
+ rules: Ref<VuetifyValidationRule[] | undefined>
17
+ customRules: Ref<SyValidationRule[] | undefined>
18
+ customWarningRules: Ref<SyValidationRule[] | undefined>
19
+ customSuccessRules: Ref<SyValidationRule[] | undefined>
20
+ errorMessages: Ref<string[] | undefined | null>
21
+ warningMessages: Ref<string[] | undefined | null>
22
+ successMessages: Ref<string[] | undefined | null>
23
+ hasErrorProp: Ref<boolean>
24
+ hasWarningProp: Ref<boolean>
25
+ hasSuccessProp: Ref<boolean>
26
+ maxErrors: Ref<number | undefined>
27
+ focused: Ref<boolean>
28
+ locales: Ref<typeof defaultLocales>
29
+ },
30
+ ) {
31
+ const defaultRules = computed<SyValidationRule[]>(() => params.required
32
+ ? [{
33
+ type: 'required',
34
+ options: {
35
+ message: params.locales.value.required,
36
+ fieldIdentifier: params.label.value,
37
+ },
38
+ }]
39
+ : [],
40
+ )
41
+
42
+ // Un captcha rempli n'est pas « valide » pour autant : la réponse doit être vérifiée.
43
+ // On n'autorise donc le succès que s'il existe une source de succès explicite
44
+ // (règle de succès, règle d'erreur portant un successMessage, message de succès injecté,
45
+ // ou état de succès forcé) — sinon le succès « champ rempli = valide » par défaut est ignoré.
46
+ const hasExplicitSuccessSource = computed(() =>
47
+ (params.customSuccessRules.value?.length ?? 0) > 0
48
+ || (params.customRules.value ?? []).some(rule => !!rule.options?.successMessage)
49
+ || (params.successMessages.value?.length ?? 0) > 0
50
+ || params.hasSuccessProp.value,
51
+ )
52
+
53
+ const { validate, clearValidation, errors, warnings, successes, hasError, hasWarning, hasSuccess } = useValidation({
54
+ modelValue: params.modelValue,
55
+ readonly: params.readonly,
56
+ disabled: params.disabled,
57
+ required: params.required,
58
+ isValidateOnBlur: params.isValidateOnBlur,
59
+ showSuccessMessages: params.showSuccessMessages,
60
+ disableErrorHandling: params.disableErrorHandling,
61
+ useVuetifyValidation: params.useVuetifyValidation,
62
+ label: params.label,
63
+ rules: params.rules ?? [],
64
+ customRules: computed(() => [...defaultRules.value, ...(params.customRules.value ?? [])]),
65
+ customWarningRules: computed(() => params.customWarningRules.value ?? []),
66
+ customSuccessRules: computed(() => params.customSuccessRules.value ?? []),
67
+ errorMessages: params.errorMessages,
68
+ warningMessages: params.warningMessages,
69
+ successMessages: params.successMessages,
70
+ hasErrorProp: params.hasErrorProp,
71
+ hasWarningProp: params.hasWarningProp,
72
+ hasSuccessProp: params.hasSuccessProp,
73
+ maxErrors: computed(() => params.maxErrors.value ?? 1),
74
+ focused: params.focused,
75
+ })
76
+
77
+ return {
78
+ validate,
79
+ clearValidation,
80
+ errors,
81
+ warnings,
82
+ successes: computed(() => hasExplicitSuccessSource.value ? successes.value : []),
83
+ hasError,
84
+ hasWarning,
85
+ hasSuccess: computed(() => hasExplicitSuccessSource.value && hasSuccess.value),
86
+ }
87
+ }