@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,7 +1,41 @@
1
- import { mount, VueWrapper } from '@vue/test-utils'
1
+ import { mount as baseMount, VueWrapper } from '@vue/test-utils'
2
2
  import PhoneField from '../PhoneField.vue'
3
3
  import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
4
4
  import { indicatifs } from '../indicatifs'
5
+ import type { ComponentPublicInstance } from 'vue'
6
+ import { locales } from '../locales'
7
+ import SyForm from '@/components/Customs/SyForm/SyForm.vue'
8
+
9
+ interface PhoneFieldInstance extends ComponentPublicInstance {
10
+ phoneNumber: string
11
+ internalDialCode: { code: string, country: string, abbreviation: string, phoneLength: number, mask: string, displayText?: string }
12
+ dialCodeList: unknown[]
13
+ hasError: boolean
14
+ errors: string[]
15
+ validation: {
16
+ clearValidation: () => void
17
+ errors: string[]
18
+ warnings: string[]
19
+ successes: string[]
20
+ hasError: boolean
21
+ hasWarning: boolean
22
+ hasSuccess: boolean
23
+ }
24
+ validateOnSubmit: () => Promise<boolean>
25
+ phoneMask: string
26
+ clearValidation: () => void
27
+ }
28
+
29
+ type IndicatifLike = {
30
+ code: string
31
+ country: string
32
+ abbreviation: string
33
+ phoneLength: number
34
+ mask: string
35
+ displayText?: string
36
+ }
37
+
38
+ const mount = (component: unknown, options?: Record<string, unknown>) => baseMount(component as never, options as never) as unknown as VueWrapper<PhoneFieldInstance>
5
39
 
6
40
  describe('PhoneField', () => {
7
41
  afterEach(() => {
@@ -18,7 +52,6 @@ describe('PhoneField', () => {
18
52
  const input = wrapper.find('input')
19
53
  await input.setValue('1234567890')
20
54
  expect(wrapper.emitted('update:modelValue')).toBeTruthy()
21
- // change est émis au blur, pas à chaque frappe
22
55
  await input.trigger('blur')
23
56
  expect(wrapper.emitted('change')).toBeTruthy()
24
57
  })
@@ -28,7 +61,7 @@ describe('PhoneField', () => {
28
61
  props: {
29
62
  required: true,
30
63
  modelValue: '01 23 45 67 89',
31
- isValidatedOnBlur: true,
64
+ isValidateOnBlur: true,
32
65
  },
33
66
  })
34
67
 
@@ -42,11 +75,13 @@ describe('PhoneField', () => {
42
75
  props: {
43
76
  required: true,
44
77
  modelValue: '',
45
- isValidatedOnBlur: true,
78
+ isValidateOnBlur: true,
79
+ showSuccessMessages: true,
46
80
  },
47
81
  })
48
82
 
49
83
  const input = wrapper.find('input')
84
+ await input.trigger('focus')
50
85
  await input.setValue('123456')
51
86
  await input.trigger('blur')
52
87
 
@@ -59,7 +94,7 @@ describe('PhoneField', () => {
59
94
  const wrapper = mount(PhoneField, {
60
95
  props: {
61
96
  withCountryCode: true,
62
- isValidatedOnBlur: false,
97
+ isValidateOnBlur: false,
63
98
  showSuccessMessages: true,
64
99
  modelValue: '',
65
100
  },
@@ -91,8 +126,7 @@ describe('PhoneField', () => {
91
126
  },
92
127
  })
93
128
 
94
- wrapper.vm.dialCode = { code: '+27', abbreviation: 'ZA', country: 'South Africa', phoneLength: 9, mask: '### ### ###' }
95
- await wrapper.vm.$nextTick()
129
+ await wrapper.setProps({ dialCodeModel: '+27' })
96
130
 
97
131
  const textField = wrapper.findComponent({ name: 'SyTextField' })
98
132
  const input = textField.find('input')
@@ -112,8 +146,7 @@ describe('PhoneField', () => {
112
146
  },
113
147
  })
114
148
 
115
- wrapper.vm.dialCode = { code: '+27', abbreviation: 'ZA', country: 'South Africa', phoneLength: 9, mask: '### ### ###' }
116
- await wrapper.vm.$nextTick()
149
+ await wrapper.setProps({ dialCodeModel: '+27' })
117
150
 
118
151
  const textField = wrapper.findComponent({ name: 'SyTextField' })
119
152
  expect(textField.props('counter')).toBe(9)
@@ -123,7 +156,8 @@ describe('PhoneField', () => {
123
156
  const wrapper = mount(PhoneField, {
124
157
  props: { modelValue: '0619123456' },
125
158
  })
126
- expect(wrapper.vm.computedValue).toBe('06 19 12 34 56')
159
+ await wrapper.vm.$nextTick()
160
+ expect(wrapper.find('input').element.value).toBe('06 19 12 34 56')
127
161
  })
128
162
 
129
163
  it('renders SySelect when withCountryCode is true', () => {
@@ -133,61 +167,18 @@ describe('PhoneField', () => {
133
167
  expect(wrapper.findComponent({ name: 'SySelect' }).exists()).toBe(true)
134
168
  })
135
169
 
136
- it('validates country code when countryCodeRequired is true', async () => {
137
- const wrapper = mount(PhoneField, {
138
- props: {
139
- withCountryCode: true,
140
- countryCodeRequired: true,
141
- modelValue: '0123456789',
142
- },
143
- })
144
-
145
- // Vider le code pays manuellement (France est sélectionnée par défaut)
146
- wrapper.vm.dialCode = ''
147
- await wrapper.vm.$nextTick()
148
-
149
- const result = await wrapper.vm.validateOnSubmit()
150
-
151
- expect(result).toBe(false)
152
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Nécessaire pour accéder à errors
153
- expect((wrapper.vm as any).errors.length).toBeGreaterThan(0)
154
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Nécessaire pour accéder à errors
155
- expect((wrapper.vm as any).errors[0]).toContain('est requis')
156
- })
157
-
158
170
  it('updates phone mask and counter when dialCode changes', async () => {
159
171
  const wrapper = mount(PhoneField, {
160
172
  props: { withCountryCode: true },
161
173
  })
162
- wrapper.vm.dialCode = { code: '+1', phoneLength: 10, mask: '###-###-####' }
163
- await wrapper.vm.$nextTick()
174
+ await wrapper.setProps({ dialCodeModel: '+1' })
164
175
  // dialCode is normalized against the canonical indicatifs list by code
165
176
  expect(wrapper.vm.phoneMask).toBe('### ### ####')
166
- expect(wrapper.vm.counter).toBe(10)
167
- })
168
-
169
- it('validates phone number with country code on blur', async () => {
170
- const wrapper = mount(PhoneField, {
171
- props: {
172
- required: true,
173
- countryCodeRequired: true,
174
- modelValue: '1234567890',
175
- withCountryCode: true,
176
- isValidatedOnBlur: true,
177
- },
178
- })
179
-
180
- wrapper.vm.dialCode = { code: '+1', phoneLength: 10, mask: '###-###-####' }
181
- await wrapper.vm.$nextTick()
182
-
183
- const input = wrapper.find('input')
184
- await input.trigger('blur')
185
-
186
- expect(wrapper.vm.hasError).toBe(false)
177
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBe(10)
187
178
  })
188
179
 
189
- it('uses only custom indicatifs when useCustomIndicatifsOnly is true', () => {
190
- const customIndicatifs = [{ code: '+99', abbreviation: 'XX', country: 'Testland', phoneLength: 10 }]
180
+ it('uses only custom indicatifs when useCustomIndicatifsOnly is true', async () => {
181
+ const customIndicatifs = [{ code: '+99', abbreviation: 'XX', country: 'Testland', phoneLength: 10, displayText: '+99' }]
191
182
  const wrapper = mount(PhoneField, {
192
183
  props: {
193
184
  useCustomIndicatifsOnly: true,
@@ -195,27 +186,7 @@ describe('PhoneField', () => {
195
186
  },
196
187
  })
197
188
 
198
- expect(wrapper.vm.mergedDialCodes).toEqual(customIndicatifs)
199
- })
200
-
201
- it('validates phone number with valid country code on blur', async () => {
202
- const wrapper = mount(PhoneField, {
203
- props: {
204
- required: true,
205
- countryCodeRequired: true,
206
- modelValue: '1234567890',
207
- withCountryCode: true,
208
- isValidatedOnBlur: true,
209
- },
210
- })
211
-
212
- wrapper.vm.dialCode = { code: '+1', phoneLength: 10, mask: '###-###-####' }
213
- await wrapper.vm.$nextTick()
214
-
215
- const input = wrapper.find('input')
216
- await input.trigger('blur')
217
-
218
- expect(wrapper.vm.hasError).toBe(false)
189
+ expect(wrapper.vm.dialCodeList).toEqual(customIndicatifs.map(ind => expect.objectContaining(ind)))
219
190
  })
220
191
 
221
192
  it('renders VTextField with outlined variant when outlined prop is true', () => {
@@ -252,8 +223,9 @@ describe('PhoneField', () => {
252
223
 
253
224
  await wrapper.vm.$nextTick()
254
225
 
255
- expect(wrapper.vm.dialCode).toBeDefined()
256
- expect(typeof wrapper.vm.dialCode).toBe('object')
226
+ const select = wrapper.findComponent({ name: 'SySelect' })
227
+ expect(select.exists()).toBe(true)
228
+ expect(typeof select.props('modelValue')).toBe('object')
257
229
 
258
230
  type Indicatif = {
259
231
  code: string
@@ -263,7 +235,7 @@ describe('PhoneField', () => {
263
235
  mask: string
264
236
  displayText?: string
265
237
  }
266
- const dialCode = wrapper.vm.dialCode as Indicatif
238
+ const dialCode = select.props('modelValue') as Indicatif
267
239
 
268
240
  expect(dialCode.code).toBe('+33')
269
241
  expect(dialCode.country).toBe('France')
@@ -275,74 +247,37 @@ describe('PhoneField', () => {
275
247
  expect(typeof dialCode.displayText).toBe('string')
276
248
  })
277
249
 
278
- it('formats phone number correctly', () => {
250
+ it('formats phone number correctly', async () => {
279
251
  const wrapper = mount(PhoneField, {
280
252
  props: {
281
253
  modelValue: '0123456789',
282
254
  },
283
255
  })
284
-
285
- expect(wrapper.vm.computedValue).toBe('01 23 45 67 89')
256
+ await wrapper.vm.$nextTick()
257
+ const input = wrapper.find('input')
258
+ expect(input.element.value).toBe('01 23 45 67 89')
286
259
  })
287
260
 
288
- it('emits update:selectedDialCode when dialCode changes', async () => {
261
+ it('emits update:dialCodeModel when dialCode changes', async () => {
289
262
  const wrapper = mount(PhoneField, {
290
263
  props: {
291
264
  withCountryCode: true,
292
265
  },
293
266
  })
294
267
 
295
- const dialCodeValue = { code: '+34', abbreviation: 'ES', country: 'Spain', phoneLength: 9, mask: '### ### ###' }
296
- wrapper.vm.dialCode = dialCodeValue
297
- await wrapper.vm.$nextTick()
268
+ await wrapper.setProps({ dialCodeModel: '+34' })
298
269
 
299
- expect(wrapper.emitted('update:selectedDialCode')).toBeTruthy()
300
- const emittedEvents = wrapper.emitted('update:selectedDialCode')
270
+ expect(wrapper.emitted('update:dialCodeModel')).toBeTruthy()
271
+ const emittedEvents = wrapper.emitted('update:dialCodeModel')
301
272
  const lastEmitted = emittedEvents && emittedEvents[emittedEvents.length - 1]?.[0]
302
- expect(lastEmitted).toHaveProperty('code', dialCodeValue.code)
303
- })
304
-
305
- it('validates phone number on submit', async () => {
306
- const wrapper = mount(PhoneField, {
307
- props: {
308
- required: true,
309
- modelValue: '',
310
- },
311
- })
312
-
313
- const result = await wrapper.vm.validateOnSubmit()
314
- console.log('Validation result:', result)
315
-
316
- expect(result).toBe(false)
317
- expect(wrapper.vm.hasError).toBe(true)
318
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Nécessaire pour accéder à errors
319
- expect((wrapper.vm as any).errors.length).toBeGreaterThan(0)
320
- })
321
-
322
- it('validates phone number successfully on submit with valid input', async () => {
323
- const wrapper = mount(PhoneField, {
324
- props: {
325
- required: true,
326
- modelValue: '0123456789',
327
- },
328
- })
329
-
330
- const result = await wrapper.vm.validateOnSubmit()
331
-
332
- expect(result).toBe(true)
333
- expect(wrapper.vm.hasError).toBe(false)
273
+ expect(lastEmitted).toHaveProperty('code', '+34')
334
274
  })
335
275
 
336
276
  it('exposes necessary properties and methods', () => {
337
277
  const wrapper = mount(PhoneField)
338
278
 
339
- expect(wrapper.vm.computedValue).toBeDefined()
340
- expect(wrapper.vm.dialCode).toBeDefined()
341
279
  expect(wrapper.vm.phoneMask).toBeDefined()
342
- expect(wrapper.vm.counter).toBeDefined()
343
- expect(wrapper.vm.hasError).toBeDefined()
344
- expect(wrapper.vm.phoneNumber).toBeDefined()
345
- expect(wrapper.vm.mergedDialCodes).toBeDefined()
280
+ expect(wrapper.vm.dialCodeList).toBeDefined()
346
281
  expect(wrapper.vm.validation).toBeDefined()
347
282
  expect(wrapper.vm.validateOnSubmit).toBeDefined()
348
283
  })
@@ -355,13 +290,14 @@ describe('PhoneField', () => {
355
290
  },
356
291
  })
357
292
 
358
- expect(wrapper.vm.counter).toBe(10)
359
-
360
- wrapper.vm.dialCode = { code: '+44', abbreviation: 'UK', country: 'United Kingdom', phoneLength: 11, mask: '### ### #####' }
293
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBe(10)
294
+ await wrapper.setProps({
295
+ dialCodeModel: { code: '+44', abbreviation: 'UK', country: 'United Kingdom', phoneLength: 11, mask: '### ### #####' },
296
+ })
361
297
  await wrapper.vm.$nextTick()
362
298
 
363
- // dialCode is normalized against the canonical indicatifs list by code
364
- expect(wrapper.vm.counter).toBe(10)
299
+ // In the indicatifs list, +44 is associed with phoneLength 10
300
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBe(10)
365
301
  })
366
302
 
367
303
  it('handles disabled state correctly', async () => {
@@ -394,27 +330,62 @@ describe('PhoneField', () => {
394
330
  expect(select.props('readonly')).toBe(true)
395
331
  })
396
332
 
397
- it('verifies SyTextField and SySelect props are correctly passed', async () => {
333
+ it('forwards hideDetails prop to SyTextField', () => {
398
334
  const wrapper = mount(PhoneField, {
399
335
  props: {
400
- withCountryCode: true,
401
- dialCodeModel: { code: '+33', abbreviation: 'FR', country: 'France', phoneLength: 10, mask: '## ## ## ## ##' },
336
+ hideDetails: true,
337
+ },
338
+ })
339
+
340
+ const textField = wrapper.findComponent({ name: 'SyTextField' })
341
+ expect(textField.props('hideDetails')).toBe(true)
342
+ })
343
+
344
+ it('shows and handles clear button when isClearable is true', async () => {
345
+ const wrapper = mount(PhoneField, {
346
+ props: {
347
+ isClearable: true,
348
+ modelValue: '0123456789',
402
349
  },
403
350
  })
404
351
 
405
352
  await wrapper.vm.$nextTick()
406
353
 
407
- expect(wrapper.vm.phoneMask).toBe('## ## ## ## ##')
408
- expect(wrapper.vm.counter).toBe(10)
354
+ const clearButton = wrapper.find(`button[aria-label="${locales.clearButtonAriaLabel}"]`)
355
+ expect(clearButton.exists()).toBe(true)
356
+
357
+ await clearButton.trigger('click')
358
+ await wrapper.vm.$nextTick()
359
+
360
+ expect(wrapper.vm.phoneNumber).toBe('')
361
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
362
+ expect(wrapper.emitted('update:modelValue')?.at(-1)?.[0]).toBe('')
363
+
364
+ await wrapper.setProps({ isClearable: false, modelValue: '0123456789' })
365
+ await wrapper.vm.$nextTick()
366
+ expect(wrapper.find(`button[aria-label="${locales.clearButtonAriaLabel}"]`).exists()).toBe(false)
367
+ })
409
368
 
369
+ it('verifies SyTextField and SySelect props are correctly passed', async () => {
370
+ const wrapper = mount(PhoneField, {
371
+ props: {
372
+ withCountryCode: true,
373
+ dialCodeModel: { code: '+33', abbreviation: 'FR', country: 'France', phoneLength: 10, mask: '## ## ## ## ##' },
374
+ },
375
+ })
376
+
377
+ const phoneInput = wrapper.find<HTMLInputElement>('input[type="tel"]')
378
+ await phoneInput.setValue('0123456789')
379
+ expect(phoneInput.element.value).toBe('01 23 45 67 89')
410
380
  const textField = wrapper.findComponent({ name: 'SyTextField' })
381
+
411
382
  expect(textField.exists()).toBe(true)
412
383
  expect(textField.props('counter')).toBe(10)
413
384
 
414
385
  const select = wrapper.findComponent({ name: 'SySelect' })
415
386
  expect(select.exists()).toBe(true)
416
387
  expect(select.props('returnObject')).toBe(true)
417
- expect(select.props('modelValue')).toEqual(wrapper.vm.dialCode)
388
+ expect((select.props('modelValue') as IndicatifLike).code).toBe('+33')
418
389
  })
419
390
 
420
391
  it('updates dialCode when dialCodeModel changes after mount', async () => {
@@ -425,7 +396,7 @@ describe('PhoneField', () => {
425
396
  })
426
397
 
427
398
  // France est sélectionnée par défaut quand withCountryCode=true
428
- expect(wrapper.vm.dialCode).toMatchObject({ code: '+33' })
399
+ expect((wrapper.findComponent({ name: 'SySelect' }).props('modelValue') as IndicatifLike).code).toBe('+33')
429
400
 
430
401
  await wrapper.setProps({
431
402
  dialCodeModel: { code: '+1', country: 'USA', abbreviation: 'US', phoneLength: 10, mask: '###-###-####' },
@@ -433,8 +404,9 @@ describe('PhoneField', () => {
433
404
 
434
405
  await wrapper.vm.$nextTick()
435
406
 
436
- expect(wrapper.vm.dialCode).toBeDefined()
437
- expect(typeof wrapper.vm.dialCode).toBe('object')
407
+ const select = wrapper.findComponent({ name: 'SySelect' })
408
+ expect(select.exists()).toBe(true)
409
+ expect(typeof select.props('modelValue')).toBe('object')
438
410
 
439
411
  type Indicatif = {
440
412
  code: string
@@ -444,12 +416,12 @@ describe('PhoneField', () => {
444
416
  mask: string
445
417
  displayText?: string
446
418
  }
447
- const dialCode = wrapper.vm.dialCode as Indicatif
419
+ const dialCode = select.props('modelValue') as Indicatif
448
420
 
449
421
  expect(dialCode.code).toBe('+1')
450
422
  expect(dialCode.country).toBe('USA/Canada')
451
423
  expect(wrapper.vm.phoneMask).toBe('### ### ####')
452
- expect(wrapper.vm.counter).toBe(10)
424
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBe(10)
453
425
  })
454
426
 
455
427
  it('handles dialCodeModel objects without displayText property', async () => {
@@ -470,7 +442,8 @@ describe('PhoneField', () => {
470
442
 
471
443
  await wrapper.vm.$nextTick()
472
444
 
473
- expect(wrapper.vm.dialCode).toBeDefined()
445
+ const select = wrapper.findComponent({ name: 'SySelect' })
446
+ expect(select.exists()).toBe(true)
474
447
 
475
448
  type Indicatif = {
476
449
  code: string
@@ -480,7 +453,7 @@ describe('PhoneField', () => {
480
453
  mask: string
481
454
  displayText?: string
482
455
  }
483
- const dialCode = wrapper.vm.dialCode as Indicatif
456
+ const dialCode = select.props('modelValue') as Indicatif
484
457
 
485
458
  expect(dialCode.code).toBe('+44')
486
459
  expect(dialCode.country).toBe('United Kingdom')
@@ -559,7 +532,7 @@ describe('PhoneField', () => {
559
532
  expect(phoneInput.attributes('autocomplete')).toBe('tel-national')
560
533
 
561
534
  // Check that country code select has default tel-country-code autocomplete
562
- const selectInput = wrapper.find('.custom-select input')
535
+ const selectInput = wrapper.find('.dial-code-select input')
563
536
  expect(selectInput.attributes('autocomplete')).toBe('tel-country-code')
564
537
  })
565
538
 
@@ -580,7 +553,7 @@ describe('PhoneField', () => {
580
553
  expect(phoneInput.attributes('autocomplete')).toBe('tel-extension')
581
554
 
582
555
  // Check that country code select has custom autocomplete
583
- const selectInput = wrapper.find('.custom-select input')
556
+ const selectInput = wrapper.find('.dial-code-select input')
584
557
  expect(selectInput.attributes('autocomplete')).toBe('tel-country-code')
585
558
  })
586
559
 
@@ -603,7 +576,7 @@ describe('PhoneField', () => {
603
576
  expect(telAutocomplete).toBe('tel-national')
604
577
 
605
578
  // Verify country select input has correct autocomplete
606
- const selectInput = wrapper.find('.custom-select input')
579
+ const selectInput = wrapper.find('.dial-code-select input')
607
580
  expect(selectInput.exists()).toBe(true)
608
581
  const selectAutocomplete = selectInput.attributes('autocomplete')
609
582
  expect(selectAutocomplete).toBe('tel-country-code')
@@ -625,7 +598,7 @@ describe('PhoneField', () => {
625
598
  expect(phoneInput.attributes('autocomplete')).toBe('tel')
626
599
 
627
600
  // Check that country code select doesn't exist
628
- const selectInput = wrapper.find('.custom-select input')
601
+ const selectInput = wrapper.find('.dial-code-select input')
629
602
  expect(selectInput.exists()).toBe(false)
630
603
  })
631
604
 
@@ -642,7 +615,8 @@ describe('PhoneField', () => {
642
615
 
643
616
  await wrapper.vm.$nextTick()
644
617
 
645
- expect(wrapper.vm.dialCode).toBeDefined()
618
+ const select = wrapper.findComponent({ name: 'SySelect' })
619
+ expect(select.exists()).toBe(true)
646
620
 
647
621
  type Indicatif = {
648
622
  code: string
@@ -652,50 +626,17 @@ describe('PhoneField', () => {
652
626
  mask: string
653
627
  displayText?: string
654
628
  }
655
- const dialCode = wrapper.vm.dialCode as Indicatif
629
+ const dialCode = select.props('modelValue') as Indicatif
656
630
 
657
631
  expect(dialCode.code).toBe('+33')
658
632
  expect(dialCode.country).toBe('France')
659
633
 
660
- const select = wrapper.findComponent({ name: 'SySelect' })
661
- expect(select.exists()).toBe(true)
662
- expect(select.props('modelValue')).toEqual(wrapper.vm.dialCode)
663
- })
664
-
665
- it('disables error handling when readonly is true', async () => {
666
- const wrapper = mount(PhoneField, {
667
- props: {
668
- required: true,
669
- modelValue: '',
670
- readonly: true,
671
- },
672
- })
673
-
674
- expect(wrapper.props('readonly')).toBe(true)
675
-
676
- const isValid = await wrapper.vm.validateOnSubmit()
677
-
678
- expect(isValid).toBe(true)
679
-
680
- expect(wrapper.vm.hasError).toBe(false)
681
- const wrapperNotReadonly = mount(PhoneField, {
682
- props: {
683
- required: true,
684
- modelValue: '',
685
- readonly: false,
686
- },
687
- })
688
-
689
- const isValidNotReadonly = await wrapperNotReadonly.vm.validateOnSubmit()
690
-
691
- expect(isValidNotReadonly).toBe(false)
692
-
693
- expect(wrapperNotReadonly.vm.hasError).toBe(true)
634
+ expect(select.props('modelValue')).toEqual(expect.objectContaining({ code: '+33' }))
694
635
  })
695
636
 
696
637
  // Tests pour les formats d'affichage avec abréviations encapsulées
697
638
  describe('Display formats with abbreviations', () => {
698
- let wrapper: VueWrapper<InstanceType<typeof PhoneField>>
639
+ let wrapper: VueWrapper<PhoneFieldInstance>
699
640
 
700
641
  beforeEach(() => {
701
642
  wrapper = mount(PhoneField, {
@@ -743,6 +684,72 @@ describe('PhoneField', () => {
743
684
  const expectedCountry = firstItem.countryFr || firstItem.country
744
685
  expect(firstItem.displayText).toBe(`<abbr title="${expectedCountry}">${firstItem.abbreviation}</abbr>`)
745
686
  })
687
+
688
+ it('escapes HTML special characters in country name and abbreviation for code-abbreviation format', async () => {
689
+ const maliciousIndicatif = {
690
+ code: '+99',
691
+ country: 'Bad"Country<script>',
692
+ abbreviation: 'B&D',
693
+ phoneLength: 10,
694
+ mask: '## ## ## ##',
695
+ }
696
+ await wrapper.setProps({
697
+ displayFormat: 'code-abbreviation',
698
+ customIndicatifs: [maliciousIndicatif],
699
+ useCustomIndicatifsOnly: true,
700
+ })
701
+ const select = wrapper.findComponent({ name: 'SySelect' })
702
+ const item = select.props('items')[0]
703
+ expect(item.displayText).not.toContain('<script>')
704
+ expect(item.displayText).not.toContain('"Country')
705
+ expect(item.displayText).toContain('&quot;Country')
706
+ expect(item.displayText).toContain('&lt;')
707
+ expect(item.displayText).toContain('&amp;')
708
+ expect(item.plainDisplayText).toBe(`${maliciousIndicatif.code} (${maliciousIndicatif.abbreviation})`)
709
+ })
710
+
711
+ it('escapes HTML special characters in country name and abbreviation for abbreviation format', async () => {
712
+ const maliciousIndicatif = {
713
+ code: '+99',
714
+ country: 'Evil<img src=x onerror=alert(1)>',
715
+ abbreviation: '<XSS>',
716
+ phoneLength: 10,
717
+ mask: '## ## ## ##',
718
+ }
719
+ await wrapper.setProps({
720
+ displayFormat: 'abbreviation',
721
+ customIndicatifs: [maliciousIndicatif],
722
+ useCustomIndicatifsOnly: true,
723
+ })
724
+ const select = wrapper.findComponent({ name: 'SySelect' })
725
+ const item = select.props('items')[0]
726
+ expect(item.displayText).not.toContain('<img')
727
+ expect(item.displayText).not.toContain('<XSS>')
728
+ expect(item.displayText).toContain('&lt;XSS&gt;')
729
+ expect(item.displayText).toContain('&lt;img')
730
+ expect(item.plainDisplayText).toBe(maliciousIndicatif.abbreviation)
731
+ })
732
+
733
+ it('escapes HTML in country name for non-abbreviation formats (code-country, country)', async () => {
734
+ const maliciousIndicatif = {
735
+ code: '+99',
736
+ country: '<img src=x onerror=alert(1)>',
737
+ abbreviation: 'XX',
738
+ phoneLength: 10,
739
+ mask: '## ## ## ##',
740
+ }
741
+ for (const format of ['code-country', 'country'] as const) {
742
+ await wrapper.setProps({
743
+ displayFormat: format,
744
+ customIndicatifs: [maliciousIndicatif],
745
+ useCustomIndicatifsOnly: true,
746
+ })
747
+ const select = wrapper.findComponent({ name: 'SySelect' })
748
+ const item = select.props('items')[0]
749
+ expect(item.displayText).not.toContain('<img')
750
+ expect(item.displayText).toContain('&lt;img')
751
+ }
752
+ })
746
753
  })
747
754
 
748
755
  // Tests pour l'initialisation avec un dialCode par défaut
@@ -757,15 +764,16 @@ describe('PhoneField', () => {
757
764
  })
758
765
 
759
766
  await wrapper.vm.$nextTick()
767
+ const select = wrapper.findComponent({ name: 'SySelect' })
760
768
 
761
769
  // Vérifier que le dialCode est correctement initialisé
762
- expect(wrapper.vm.dialCode).toBeDefined()
770
+ expect(select.exists()).toBe(true)
763
771
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
764
- expect((wrapper.vm.dialCode as any).code).toBe('+44')
772
+ expect((select.props('modelValue') as any).code).toBe('+44')
765
773
  // Vérifier que le masque est appliqué (le format exact peut varier)
766
774
  expect(wrapper.vm.phoneMask).toBeDefined()
767
775
  // Vérifier que le counter est défini selon la phoneLength
768
- expect(wrapper.vm.counter).toBeDefined()
776
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBeDefined()
769
777
  })
770
778
 
771
779
  it('initializes with a default dialCode string', async () => {
@@ -777,32 +785,162 @@ describe('PhoneField', () => {
777
785
  })
778
786
 
779
787
  await wrapper.vm.$nextTick()
788
+ const select = wrapper.findComponent({ name: 'SySelect' })
780
789
 
781
790
  // Vérifier que le dialCode est correctement initialisé
782
- expect(wrapper.vm.dialCode).toBe('+33')
791
+ expect(select.props('modelValue')).toEqual(expect.objectContaining({ code: '+33' }))
783
792
  })
784
793
  })
785
794
 
786
- // Tests pour la désactivation de la gestion des erreurs
787
- describe('Error handling', () => {
795
+ // Tests de validation
796
+ describe('Validation', () => {
797
+ it('cleans spaces from phone number before validation', async () => {
798
+ const wrapper = mount(PhoneField, {
799
+ props: {
800
+ required: true,
801
+ modelValue: '01 23 45 67 89',
802
+ },
803
+ })
804
+
805
+ const isValid = await wrapper.vm.validateOnSubmit()
806
+ expect(isValid).toBe(true)
807
+ expect(wrapper.vm.hasError).toBe(false)
808
+ })
809
+
810
+ it('validates phone number and country code on blur', async () => {
811
+ const wrapper = mount(PhoneField, {
812
+ props: {
813
+ required: true,
814
+ modelValue: '12345',
815
+ isValidateOnBlur: true,
816
+ },
817
+ })
818
+
819
+ const input = wrapper.find('input')
820
+ await input.trigger('focus')
821
+ await input.trigger('blur')
822
+ await wrapper.vm.$nextTick()
823
+
824
+ expect(wrapper.vm.hasError).toBe(true)
825
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Required to access errors
826
+ expect((wrapper.vm as any).errors[0]).toContain(locales.errorLength(10))
827
+ })
828
+
829
+ it('keeps a consistent success message before and after blur when withCountryCode is true', async () => {
830
+ const wrapper = mount(PhoneField, {
831
+ props: {
832
+ withCountryCode: true,
833
+ isValidateOnBlur: false,
834
+ modelValue: '',
835
+ showSuccessMessages: true,
836
+ },
837
+ })
838
+
839
+ const textField = wrapper.findComponent({ name: 'SyTextField' })
840
+ const input = textField.find('input')
841
+ await input.setValue('0123456789')
842
+ await wrapper.vm.$nextTick()
843
+
844
+ const messageBeforeBlur = textField.find('.v-messages__message')
845
+ expect(messageBeforeBlur.exists()).toBe(true)
846
+ expect(messageBeforeBlur.text()).toBe('Le champ Numéro de téléphone sans indicatif est valide.')
847
+ expect(messageBeforeBlur.text()).not.toBe('Le champ Numéro de téléphone est valide.')
848
+
849
+ await input.trigger('blur')
850
+ await wrapper.vm.$nextTick()
851
+
852
+ const messageAfterBlur = textField.find('.v-messages__message')
853
+ expect(messageAfterBlur.exists()).toBe(true)
854
+ expect(messageAfterBlur.text()).toBe('Le champ Numéro de téléphone sans indicatif est valide.')
855
+ })
856
+
857
+ it('falls back to default country code when provided code is invalid', async () => {
858
+ const wrapper = mount(PhoneField, {
859
+ props: {
860
+ withCountryCode: true,
861
+ modelValue: '0123456789',
862
+ dialCodeModel: '+9999',
863
+ },
864
+ })
865
+
866
+ const result = await wrapper.vm.validateOnSubmit()
867
+
868
+ expect(result).toBe(true)
869
+ expect((wrapper.findComponent({ name: 'SySelect' }).props('modelValue') as IndicatifLike).code).toBe('+33')
870
+ })
871
+
872
+ it('validates phone number on submit', async () => {
873
+ const wrapper = mount(PhoneField, {
874
+ props: {
875
+ required: true,
876
+ modelValue: '',
877
+ },
878
+ })
879
+
880
+ const result = await wrapper.vm.validateOnSubmit()
881
+
882
+ expect(result).toBe(false)
883
+ expect(wrapper.vm.hasError).toBe(true)
884
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Required to access errors
885
+ expect((wrapper.vm as any).errors.length).toBeGreaterThan(0)
886
+ })
887
+
888
+ it('validates phone number successfully on submit with valid input', async () => {
889
+ const wrapper = mount(PhoneField, {
890
+ props: {
891
+ required: true,
892
+ modelValue: '0123456789',
893
+ },
894
+ })
895
+
896
+ const result = await wrapper.vm.validateOnSubmit()
897
+
898
+ expect(result).toBe(true)
899
+ expect(wrapper.vm.hasError).toBe(false)
900
+ })
901
+
902
+ it('disables error handling when readonly is true', async () => {
903
+ const wrapper = mount(PhoneField, {
904
+ props: {
905
+ required: true,
906
+ modelValue: '',
907
+ readonly: true,
908
+ },
909
+ })
910
+
911
+ const isValid = await wrapper.vm.validateOnSubmit()
912
+
913
+ expect(isValid).toBe(true)
914
+ expect(wrapper.vm.hasError).toBe(false)
915
+
916
+ const wrapperNotReadonly = mount(PhoneField, {
917
+ props: {
918
+ required: true,
919
+ modelValue: '',
920
+ readonly: false,
921
+ },
922
+ })
923
+
924
+ const isValidNotReadonly = await wrapperNotReadonly.vm.validateOnSubmit()
925
+ expect(isValidNotReadonly).toBe(false)
926
+ expect(wrapperNotReadonly.vm.hasError).toBe(true)
927
+ })
928
+
788
929
  it('displays error messages by default when validation fails', async () => {
789
930
  const wrapper = mount(PhoneField, {
790
931
  props: {
791
932
  required: true,
792
933
  modelValue: '',
793
- isValidatedOnBlur: true,
934
+ isValidateOnBlur: true,
794
935
  },
795
936
  })
796
937
 
797
- // Déclencher la validation
798
938
  await wrapper.vm.validateOnSubmit()
799
939
 
800
- // Vérifier que les erreurs sont affichées
801
940
  expect(wrapper.vm.hasError).toBe(true)
802
941
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
803
942
  expect((wrapper.vm as any).errors.length).toBeGreaterThan(0)
804
943
 
805
- // Vérifier que les erreurs sont passées au composant SyTextField
806
944
  const textField = wrapper.findComponent({ name: 'SyTextField' })
807
945
  expect(textField.props('errorMessages')).toBeTruthy()
808
946
  })
@@ -812,19 +950,14 @@ describe('PhoneField', () => {
812
950
  props: {
813
951
  required: true,
814
952
  modelValue: '',
815
- isValidatedOnBlur: true,
953
+ isValidateOnBlur: true,
816
954
  disableErrorHandling: true,
817
955
  },
818
956
  })
819
957
 
820
- // Vérifier que la propriété disableErrorHandling est bien prise en compte
821
- // en vérifiant qu'elle est passée lors de l'initialisation du composable useValidation
822
958
  expect(wrapper.vm.validation).toBeDefined()
823
959
  })
824
- })
825
960
 
826
- // Tests pour la validation dans un contexte de formulaire
827
- describe('Form validation', () => {
828
961
  it('validates as part of a form submission', async () => {
829
962
  const wrapper = mount(PhoneField, {
830
963
  props: {
@@ -833,11 +966,9 @@ describe('PhoneField', () => {
833
966
  },
834
967
  })
835
968
 
836
- // Simuler une soumission de formulaire avec un champ vide
837
969
  const isValid = await wrapper.vm.validateOnSubmit()
838
970
  expect(isValid).toBe(false)
839
971
 
840
- // Mettre à jour la valeur et valider à nouveau
841
972
  await wrapper.setProps({ modelValue: '0123456789' })
842
973
  const isValidAfterUpdate = await wrapper.vm.validateOnSubmit()
843
974
  expect(isValidAfterUpdate).toBe(true)
@@ -847,27 +978,129 @@ describe('PhoneField', () => {
847
978
  const wrapper = mount(PhoneField, {
848
979
  props: {
849
980
  required: true,
850
- modelValue: '0123456789',
981
+ modelValue: '012',
982
+ dialCodeModel: { code: 'test', abbreviation: 'TEST', country: 'Test', phoneLength: 3, mask: '###' },
983
+ customIndicatifs: [{ code: 'test', abbreviation: 'TEST', country: 'Test', phoneLength: 3, mask: '###' }],
851
984
  withCountryCode: true,
852
- countryCodeRequired: true,
853
985
  },
854
986
  })
855
987
 
856
- // Vider le code pays manuellement (France est sélectionnée par défaut)
857
- wrapper.vm.dialCode = ''
858
988
  await wrapper.vm.$nextTick()
859
989
 
860
- // Sans code pays, la validation échoue
861
990
  const isValidWithoutCountry = await wrapper.vm.validateOnSubmit()
862
- expect(isValidWithoutCountry).toBe(false)
991
+ expect(isValidWithoutCountry).toBe(true)
863
992
 
864
- // Ajouter un code pays et valider à nouveau
865
- wrapper.vm.dialCode = { code: '+33', abbreviation: 'FR', country: 'France', phoneLength: 10, mask: '## ## ## ## ##' }
866
993
  await wrapper.vm.$nextTick()
867
994
 
868
995
  const isValidWithCountry = await wrapper.vm.validateOnSubmit()
869
996
  expect(isValidWithCountry).toBe(true)
870
997
  })
998
+
999
+ describe('Validation with SyForm', () => {
1000
+ it('validates as part of SyForm submission', async () => {
1001
+ const wrapper = baseMount({
1002
+ components: { PhoneField, SyForm },
1003
+ template: `
1004
+ <SyForm>
1005
+ <PhoneField with-country-code required />
1006
+ <button type="submit">Submit</button>
1007
+ </SyForm>
1008
+ `,
1009
+ })
1010
+
1011
+ const syForm = wrapper.findComponent(SyForm)
1012
+ const form = syForm.vm as unknown as { validate: () => Promise<boolean> }
1013
+ const isValidWithoutPhone = await form.validate()
1014
+ await wrapper.vm.$nextTick()
1015
+
1016
+ const phoneField = wrapper.find('.phone-field')
1017
+ expect(syForm.exists()).toBe(true)
1018
+ expect(isValidWithoutPhone).toBe(false)
1019
+ expect(phoneField.classes()).toContain('error-field')
1020
+
1021
+ const input = wrapper.find('input[type="tel"]')
1022
+ await input.setValue('0123456789')
1023
+ await wrapper.vm.$nextTick()
1024
+
1025
+ const isValidWithPhone = await form.validate()
1026
+ await wrapper.vm.$nextTick()
1027
+
1028
+ expect(isValidWithPhone).toBe(true)
1029
+ expect(phoneField.classes()).not.toContain('error-field')
1030
+ })
1031
+
1032
+ it('blocks SyForm submission when a Vuetify custom rule fails', async () => {
1033
+ const wrapper = baseMount({
1034
+ components: { PhoneField, SyForm },
1035
+ setup() {
1036
+ const rules = [(v: unknown) => String(v ?? '').replace(/\D/g, '').startsWith('06') || 'Le numéro doit commencer par 06']
1037
+ return { rules }
1038
+ },
1039
+ template: `
1040
+ <SyForm>
1041
+ <PhoneField :rules="rules" use-vuetify-validation />
1042
+ <button type="submit">Submit</button>
1043
+ </SyForm>
1044
+ `,
1045
+ })
1046
+
1047
+ const syForm = wrapper.findComponent(SyForm)
1048
+ const form = syForm.vm as unknown as { validate: () => Promise<boolean> }
1049
+
1050
+ // A number that does not start with 06 — the Vuetify rule rejects it
1051
+ const input = wrapper.find('input[type="tel"]')
1052
+ await input.setValue('0123456789')
1053
+ await wrapper.vm.$nextTick()
1054
+
1055
+ const isInvalid = await form.validate()
1056
+ await wrapper.vm.$nextTick()
1057
+
1058
+ expect(isInvalid).toBe(false)
1059
+ expect(wrapper.find('.phone-field').classes()).toContain('error-field')
1060
+
1061
+ // Fix the value so the Vuetify rule passes
1062
+ await input.setValue('0612345678')
1063
+ await wrapper.vm.$nextTick()
1064
+
1065
+ const isValid = await form.validate()
1066
+ await wrapper.vm.$nextTick()
1067
+
1068
+ expect(isValid).toBe(true)
1069
+ expect(wrapper.find('.phone-field').classes()).not.toContain('error-field')
1070
+ })
1071
+
1072
+ it('passes SyForm submission when all Vuetify rules are satisfied', async () => {
1073
+ const wrapper = baseMount({
1074
+ components: { PhoneField, SyForm },
1075
+ setup() {
1076
+ const rules = [
1077
+ (v: unknown) => !!v || 'Champ requis',
1078
+ (v: unknown) => String(v ?? '').replace(/\D/g, '').length === 10 || 'Le numéro doit contenir 10 chiffres',
1079
+ ]
1080
+ return { rules }
1081
+ },
1082
+ template: `
1083
+ <SyForm>
1084
+ <PhoneField :rules="rules" use-vuetify-validation />
1085
+ <button type="submit">Submit</button>
1086
+ </SyForm>
1087
+ `,
1088
+ })
1089
+
1090
+ const syForm = wrapper.findComponent(SyForm)
1091
+ const form = syForm.vm as unknown as { validate: () => Promise<boolean> }
1092
+
1093
+ const input = wrapper.find('input[type="tel"]')
1094
+ await input.setValue('0612345678')
1095
+ await wrapper.vm.$nextTick()
1096
+
1097
+ const isValid = await form.validate()
1098
+ await wrapper.vm.$nextTick()
1099
+
1100
+ expect(isValid).toBe(true)
1101
+ expect(wrapper.find('.phone-field').classes()).not.toContain('error-field')
1102
+ })
1103
+ })
871
1104
  })
872
1105
 
873
1106
  // Tests pour la gestion des indicatifs personnalisés
@@ -882,8 +1115,8 @@ describe('PhoneField', () => {
882
1115
  })
883
1116
 
884
1117
  // Vérifier que les indicatifs personnalisés sont ajoutés aux indicatifs standards
885
- expect(wrapper.vm.mergedDialCodes.length).toBe(indicatifs.length + customIndicatifs.length)
886
- expect(wrapper.vm.mergedDialCodes).toContainEqual(customIndicatifs[0])
1118
+ expect(wrapper.vm.dialCodeList.length).toBe(indicatifs.length + customIndicatifs.length)
1119
+ expect(wrapper.vm.dialCodeList).toContainEqual(expect.objectContaining(customIndicatifs[0]))
887
1120
  })
888
1121
 
889
1122
  it('uses only custom indicatifs when useCustomIndicatifsOnly is true', () => {
@@ -897,8 +1130,8 @@ describe('PhoneField', () => {
897
1130
  })
898
1131
 
899
1132
  // Vérifier que seuls les indicatifs personnalisés sont utilisés
900
- expect(wrapper.vm.mergedDialCodes.length).toBe(customIndicatifs.length)
901
- expect(wrapper.vm.mergedDialCodes).toEqual(customIndicatifs)
1133
+ expect(wrapper.vm.dialCodeList.length).toBe(customIndicatifs.length)
1134
+ expect(wrapper.vm.dialCodeList).toEqual(expect.arrayContaining(customIndicatifs.map(ind => expect.objectContaining(ind))))
902
1135
  })
903
1136
 
904
1137
  it('updates phone mask and counter based on selected custom indicatif', async () => {
@@ -911,13 +1144,77 @@ describe('PhoneField', () => {
911
1144
  })
912
1145
 
913
1146
  // Sélectionner l'indicatif personnalisé
914
- wrapper.vm.dialCode = customIndicatifs[0]!
915
- await wrapper.vm.$nextTick()
1147
+ await wrapper.setProps({ dialCodeModel: '+999' })
916
1148
 
917
1149
  // Vérifier que le masque et le counter sont mis à jour
918
- expect(wrapper.vm.counter).toBe(8)
1150
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBe(8)
919
1151
  expect(wrapper.vm.phoneMask).toBe('## ## ## ##')
920
1152
  })
1153
+
1154
+ it('updates the dialCode when customIndicatifs prop changes', async () => {
1155
+ const initialCustomIndicatifs = [{ code: '+999', abbreviation: 'XX', country: 'Test Country', phoneLength: 8, mask: '## ## ## ##' }]
1156
+ const wrapper = mount(PhoneField, {
1157
+ props: {
1158
+ customIndicatifs: initialCustomIndicatifs,
1159
+ useCustomIndicatifsOnly: true,
1160
+ withCountryCode: true,
1161
+ },
1162
+ })
1163
+
1164
+ // Vérifier que le dialCode est initialisé avec l'indicatif personnalisé
1165
+ const select = wrapper.findComponent({ name: 'SySelect' })
1166
+ expect(select.props('modelValue')).toEqual(expect.objectContaining(initialCustomIndicatifs[0]))
1167
+
1168
+ const newCustomIndicatifs = [{ code: '+998', abbreviation: 'YY', country: 'New Test Country', phoneLength: 9, mask: '### ### ###' }]
1169
+ await wrapper.setProps({ customIndicatifs: newCustomIndicatifs })
1170
+ await wrapper.vm.$nextTick()
1171
+
1172
+ // Vérifier que le dialCode est mis à jour avec le nouvel indicatif personnalisé
1173
+ expect(select.props('modelValue')).toEqual(expect.objectContaining(newCustomIndicatifs[0]))
1174
+
1175
+ // Vérifier que le masque et le counter sont mis à jour avec le nouvel indicatif personnalisé
1176
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBe(9)
1177
+ expect(wrapper.vm.phoneMask).toBe('### ### ###')
1178
+
1179
+ // Vérifier que les indicatifs affichés dans le select sont mis à jour avec les nouveaux indicatifs personnalisés
1180
+ const items = select.props('items')
1181
+ expect(items).toEqual(expect.arrayContaining(newCustomIndicatifs.map(ind => expect.objectContaining(ind))))
1182
+
1183
+ // Vérifier que les anciens indicatifs personnalisés ne sont plus présents
1184
+ expect(items).not.toEqual(expect.arrayContaining(initialCustomIndicatifs.map(ind => expect.objectContaining(ind))))
1185
+
1186
+ // Vérifier que l'indicatif sélectionné est mis à jour
1187
+ expect(select.props('modelValue')).toEqual(expect.objectContaining(newCustomIndicatifs[0]))
1188
+ })
1189
+
1190
+ it('does not keep selected dial code when it is no longer available', async () => {
1191
+ const initialCustomIndicatifs = [
1192
+ { code: '+999', abbreviation: 'XX', country: 'Test Country', phoneLength: 8, mask: '## ## ## ##' },
1193
+ { code: '+998', abbreviation: 'YY', country: 'Fallback Country', phoneLength: 9, mask: '### ### ###' },
1194
+ ]
1195
+
1196
+ const wrapper = mount(PhoneField, {
1197
+ props: {
1198
+ customIndicatifs: initialCustomIndicatifs,
1199
+ useCustomIndicatifsOnly: true,
1200
+ withCountryCode: true,
1201
+ dialCodeModel: '+999',
1202
+ },
1203
+ })
1204
+
1205
+ const select = wrapper.findComponent({ name: 'SySelect' })
1206
+ expect((select.props('modelValue') as IndicatifLike).code).toBe('+999')
1207
+
1208
+ await wrapper.setProps({
1209
+ customIndicatifs: [{ code: '+998', abbreviation: 'YY', country: 'Fallback Country', phoneLength: 9, mask: '### ### ###' }],
1210
+ })
1211
+ await wrapper.vm.$nextTick()
1212
+
1213
+ const currentDialCode = select.props('modelValue') as IndicatifLike
1214
+ expect(currentDialCode.code).toBe('+998')
1215
+ expect(currentDialCode.code).not.toBe('+999')
1216
+ expect(wrapper.findComponent({ name: 'SyTextField' }).props('counter')).toBe(9)
1217
+ })
921
1218
  })
922
1219
 
923
1220
  describe('Fieldset rendering', () => {