@cnamts/synapse 1.0.26 → 1.0.27

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 (253) hide show
  1. package/dist/{AutocompleteFilter-BPR-a55G.js → AutocompleteFilter-C9eLKyW8.js} +3 -3
  2. package/dist/{DateFilter-CknrJWs2.js → DateFilter-y-GLkAkn.js} +8 -8
  3. package/dist/{NumberFilter-DJ-yNlzv.js → NumberFilter-DN6hIBS7.js} +1 -1
  4. package/dist/{PeriodFilter-CiB5Oa9Z.js → PeriodFilter-MoUUp9qS.js} +1 -1
  5. package/dist/{SelectFilter-EiafX97M.js → SelectFilter-bCbrdLmu.js} +1 -1
  6. package/dist/{TextFilter-BzOmpdxj.js → TextFilter-CvjgEaoM.js} +4 -4
  7. package/dist/apLightTheme2026-ug4Y23ns.js +611 -0
  8. package/dist/components/Customs/Selects/SyAutocomplete/SyAutocomplete.d.ts +2369 -353
  9. package/dist/components/Customs/Selects/SyAutocomplete/composables/useSyAutocompleteValidation.d.ts +18 -0
  10. package/dist/components/Customs/Selects/SyAutocomplete/utils/ariaManager.d.ts +1 -1
  11. package/dist/components/Customs/Selects/SyAutocomplete/utils/useKeyboardHandler.d.ts +3 -1
  12. package/dist/components/Customs/Selects/SySelect/SySelect.d.ts +9 -10
  13. package/dist/components/Customs/Selects/SySelect/composables/useSySelectKeyboard.d.ts +1 -0
  14. package/dist/components/Customs/Selects/SySelect/composables/useSySelectValidation.d.ts +15 -0
  15. package/dist/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.d.ts +3 -3
  16. package/dist/components/Customs/SyCheckbox/SyCheckbox.d.ts +3 -3
  17. package/dist/components/Customs/SyIconButton/SyIconButton.d.ts +18 -0
  18. package/dist/components/Customs/SyRadioGroup/SyRadioGroup.d.ts +20 -38
  19. package/dist/components/Customs/SyRadioGroup/composables/useSyRadioGroupValidation.d.ts +50 -0
  20. package/dist/components/Customs/SyTextField/SyTextField.d.ts +6 -6
  21. package/dist/components/DatePicker/CalendarMode/DatePicker.d.ts +147 -136
  22. package/dist/components/DatePicker/ComplexDatePicker/ComplexDatePicker.d.ts +62 -54
  23. package/dist/components/DatePicker/DateTextInput/DateTextInput.d.ts +27 -24
  24. package/dist/components/DatePicker/composables/index.d.ts +1 -0
  25. package/dist/components/DatePicker/composables/useDatePickerValidationBridge.d.ts +51 -0
  26. package/dist/components/MonthPicker/MonthPicker.d.ts +23 -23
  27. package/dist/components/MonthPicker/MonthPickerText/MonthPickerInput.d.ts +23 -23
  28. package/dist/components/NirField/NirField.d.ts +56 -56
  29. package/dist/components/PasswordField/PasswordField.d.ts +3 -3
  30. package/dist/components/PeriodField/PeriodField.d.ts +236 -212
  31. package/dist/components/PhoneField/PhoneField.d.ts +23 -23
  32. package/dist/components/SyTextArea/SyTextArea.d.ts +25 -15
  33. package/dist/components/SyTextArea/composables/useSyTextAreaValidation.d.ts +20 -0
  34. package/dist/components/SyTextArea/locales.d.ts +1 -0
  35. package/dist/components/Tables/SyServerTable/SyServerTable.d.ts +1 -0
  36. package/dist/components/Tables/SyTable/SyTable.d.ts +1 -0
  37. package/dist/components/Tables/common/SyTablePagination.d.ts +25 -25
  38. package/dist/components/Tables/common/types.d.ts +2 -0
  39. package/dist/components/index.d.ts +1 -0
  40. package/dist/composables/unifyValidation/documentationValidationProps.d.ts +160 -160
  41. package/dist/composables/unifyValidation/useValidation.d.ts +16 -14
  42. package/dist/design-system-v3.js +81 -80
  43. package/dist/designTokens/tokens/amelipro/apContextual.d.ts +6 -6
  44. package/dist/designTokens/tokens/amelipro/apDarkTheme.d.ts +3 -1
  45. package/dist/designTokens/tokens/amelipro/apLightTheme.d.ts +53 -100
  46. package/dist/designTokens/tokens/baseContextualTokens.d.ts +0 -6
  47. package/dist/designTokens/tokens/baseTokens.d.ts +232 -0
  48. package/dist/designTokens/tokens/cnam/cnamContextual.d.ts +6 -6
  49. package/dist/designTokens/tokens/cnam/cnamDarkTheme.d.ts +1 -1
  50. package/dist/designTokens/tokens/cnam/cnamLightTheme.d.ts +57 -101
  51. package/dist/designTokens/tokens/pa/paContextual.d.ts +0 -6
  52. package/dist/designTokens/tokens/pa/paDarkTheme.d.ts +1 -1
  53. package/dist/designTokens/tokens/pa/paLightTheme.d.ts +53 -97
  54. package/dist/designTokens/tokens/pa/paSemantic.d.ts +1 -0
  55. package/dist/designTokens/tokens/semanticTokens.d.ts +112 -0
  56. package/dist/main-CI6Q9nmO.js +39234 -0
  57. package/dist/synapse.css +1 -1
  58. package/dist/vuetifyConfig.js +208 -72
  59. package/package.json +4 -2
  60. package/src/assets/overrides/_icons.scss +5 -4
  61. package/src/assets/overrides/_otp.scss +4 -4
  62. package/src/assets/overrides/_typography.scss +2 -1
  63. package/src/assets/overrides/_utilities.scss +1 -42
  64. package/src/components/ChipList/ChipList.vue +30 -18
  65. package/src/components/ChipList/tests/chipList.spec.ts +4 -4
  66. package/src/components/CopyBtn/CopyBtn.vue +2 -2
  67. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.stories.ts +4 -0
  68. package/src/components/Customs/Selects/SelectBtnField/SelectBtnField.vue +7 -6
  69. package/src/components/Customs/Selects/SelectBtnField/tests/SelectBtnField.spec.ts +223 -0
  70. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.stories.ts +283 -351
  71. package/src/components/Customs/Selects/SyAutocomplete/SyAutocomplete.vue +182 -218
  72. package/src/components/Customs/Selects/SyAutocomplete/composables/useSyAutocompleteValidation.ts +101 -0
  73. package/src/components/Customs/Selects/SyAutocomplete/tests/SyAutocomplete.spec.ts +761 -1
  74. package/src/components/Customs/Selects/SyAutocomplete/utils/ariaManager.ts +3 -1
  75. package/src/components/Customs/Selects/SyAutocomplete/utils/useKeyboardHandler.ts +79 -5
  76. package/src/components/Customs/Selects/SyAutocomplete/validation/Validation.stories.ts +1029 -0
  77. package/src/components/Customs/Selects/SySelect/SySelect.stories.ts +9 -491
  78. package/src/components/Customs/Selects/SySelect/SySelect.vue +46 -79
  79. package/src/components/Customs/Selects/SySelect/composables/useSySelectKeyboard.ts +3 -0
  80. package/src/components/Customs/Selects/SySelect/composables/useSySelectValidation.ts +64 -0
  81. package/src/components/Customs/Selects/SySelect/tests/SySelect.spec.ts +196 -0
  82. package/src/components/Customs/Selects/SySelect/validation/Validation.stories.ts +1026 -0
  83. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.stories.ts +18 -7
  84. package/src/components/Customs/SyCheckBoxGroup/SyCheckBoxGroup.vue +2 -2
  85. package/src/components/Customs/SyCheckbox/SyCheckbox.stories.ts +8 -8
  86. package/src/components/Customs/SyCheckbox/SyCheckbox.vue +8 -8
  87. package/src/components/Customs/SyCheckbox/tests/SyCheckbox.spec.ts +1 -1
  88. package/src/components/Customs/SyIcon/accessibilite/Accessibility.mdx +0 -6
  89. package/src/components/Customs/SyIcon/utils/tests/iconUtils.spec.ts +107 -0
  90. package/src/components/Customs/SyRadioGroup/SyRadioGroup.mdx +2 -2
  91. package/src/components/Customs/SyRadioGroup/SyRadioGroup.stories.ts +395 -200
  92. package/src/components/Customs/SyRadioGroup/SyRadioGroup.vue +82 -127
  93. package/src/components/Customs/SyRadioGroup/composables/useSyRadioGroupValidation.ts +127 -0
  94. package/src/components/Customs/SyRadioGroup/tests/SyRadioGroup.a11y.spec.ts +93 -1
  95. package/src/components/Customs/SyRadioGroup/tests/SyRadioGroup.spec.ts +146 -9
  96. package/src/components/Customs/SyRadioGroup/tests/SyRadioGroup.visual.cy.ts +165 -0
  97. package/src/components/Customs/SyRadioGroup/validation/Validation.stories.ts +773 -0
  98. package/src/components/Customs/SyTabs/config.ts +3 -3
  99. package/src/components/Customs/SyTabs/tests/SyTabs.spec.ts +265 -0
  100. package/src/components/Customs/SyTabs/tests/useTabTransition.spec.ts +188 -0
  101. package/src/components/Customs/SyTextField/SyTextField.stories.ts +10 -29
  102. package/src/components/Customs/SyTextField/SyTextField.vue +23 -15
  103. package/src/components/DataList/DataList.stories.ts +1 -1
  104. package/src/components/DataListItem/tests/DataListItem.spec.ts +3 -1
  105. package/src/components/DatePicker/CalendarMode/DatePicker.vue +37 -142
  106. package/src/components/DatePicker/CalendarMode/tests/DatePicker.coverage.spec.ts +156 -0
  107. package/src/components/DatePicker/CalendarMode/tests/DatePicker.spec.ts +495 -4
  108. package/src/components/DatePicker/ComplexDatePicker/ComplexDatePicker.vue +47 -66
  109. package/src/components/DatePicker/ComplexDatePicker/tests/ComplexDatePicker.spec.ts +206 -0
  110. package/src/components/DatePicker/ComplexDatePicker/tests/bridge-integration.regression.spec.ts +210 -0
  111. package/src/components/DatePicker/ComplexDatePicker/tests/calendar-navigation.regression.spec.ts +214 -0
  112. package/src/components/DatePicker/ComplexDatePicker/tests/validation-cross.regression.spec.ts +194 -0
  113. package/src/components/DatePicker/ComplexDatePicker/tests/validation-success-messages.regression.spec.ts +83 -0
  114. package/src/components/DatePicker/DateTextInput/DateTextInput.vue +129 -54
  115. package/src/components/DatePicker/DateTextInput/tests/DateTextInput.spec.ts +320 -0
  116. package/src/components/DatePicker/composables/index.ts +1 -0
  117. package/src/components/DatePicker/composables/tests/useCalendarKeyboardNavigation.spec.ts +360 -0
  118. package/src/components/DatePicker/composables/tests/useDatePickerValidationBridge.spec.ts +129 -0
  119. package/src/components/DatePicker/composables/useDatePickerValidationBridge.ts +205 -0
  120. package/src/components/DatePicker/docExamples/BidirectionalComplexValidation.vue +1 -1
  121. package/src/components/DatePicker/docExamples/DatePickerBidirectionalValidation.vue +1 -1
  122. package/src/components/DatePicker/tests/exposed-methods.coverage.spec.ts +75 -0
  123. package/src/components/DialogBox/DialogBox.vue +1 -1
  124. package/src/components/FileList/UploadItem/UploadItem.vue +4 -4
  125. package/src/components/FileUpload/FileUpload.vue +2 -2
  126. package/src/components/FileUpload/FileUploadContent.vue +1 -1
  127. package/src/components/FilterInline/FilterInline.mdx +2 -2
  128. package/src/components/FilterSideBar/FilterSideBar.stories.ts +1 -1
  129. package/src/components/FilterSideBar/FilterSideBar.vue +2 -2
  130. package/src/components/FooterBar/FooterBar.vue +7 -7
  131. package/src/components/FranceConnectBtn/FranceConnectBtn.vue +1 -1
  132. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderMenuItem/HeaderMenuItem.vue +2 -2
  133. package/src/components/HeaderBar/HeaderBurgerMenu/HeaderSubMenu/HeaderSubMenu.vue +7 -7
  134. package/src/components/HeaderBar/HeaderMenuBtn/HeaderMenuBtn.vue +2 -2
  135. package/src/components/HeaderLoading/tests/HeaderLoading.spec.ts +87 -8
  136. package/src/components/HeaderNavigationBar/HorizontalNavbar/HorizontalNavbar.vue +3 -3
  137. package/src/components/HeaderNavigationBar/HorizontalNavbar/tests/HorizontalNavbar.spec.ts +589 -0
  138. package/src/components/HeaderToolbar/tests/HeaderToolBar.spec.ts +153 -1
  139. package/src/components/HeaderToolbar/tests/useMobileRightMenu.spec.ts +258 -0
  140. package/src/components/LogoBrandSection/tests/LogoBrandSection.spec.ts +2 -2
  141. package/src/components/LogoBrandSection/tests/__snapshots__/LogoBrandSection.spec.ts.snap +1 -1
  142. package/src/components/LunarCalendar/tests/useLunarCalendarRules.spec.ts +184 -0
  143. package/src/components/MonthPicker/MonthPickerVisual/MonthSelector.vue +3 -3
  144. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerFooter.vue +1 -1
  145. package/src/components/MonthPicker/MonthPickerVisual/VisualPickerHeader.vue +2 -2
  146. package/src/components/MonthPicker/MonthPickerVisual/YearSelector.vue +1 -1
  147. package/src/components/NirField/NirField.vue +3 -3
  148. package/src/components/NotificationBar/Notification/Notification.vue +12 -12
  149. package/src/components/NotificationBar/NotificationBar.stories.ts +8 -8
  150. package/src/components/PaginatedTable/Pagination.vue +2 -2
  151. package/src/components/PasswordField/PasswordField.vue +8 -8
  152. package/src/components/PasswordField/tests/PasswordField.spec.ts +3 -3
  153. package/src/components/RangeField/RangeSlider/RangeSlider.vue +2 -2
  154. package/src/components/RangeField/RangeSlider/Tooltip/Tooltip.vue +1 -1
  155. package/src/components/StatusPage/tests/StatusPage.spec.ts +149 -0
  156. package/src/components/SubHeader/SubHeader.vue +1 -1
  157. package/src/components/SyAlert/SyAlert.vue +23 -23
  158. package/src/components/SyTextArea/SyTextArea.stories.ts +177 -131
  159. package/src/components/SyTextArea/SyTextArea.vue +235 -83
  160. package/src/components/SyTextArea/composables/useSyTextAreaValidation.ts +81 -0
  161. package/src/components/SyTextArea/locales.ts +1 -0
  162. package/src/components/SyTextArea/tests/SyTextArea.spec.ts +449 -1
  163. package/src/components/SyTextArea/useDefaultValidationRules.ts +2 -7
  164. package/src/components/SyTextArea/validation/Validation.stories.ts +856 -0
  165. package/src/components/TableToolbar/TableToolbar.vue +6 -6
  166. package/src/components/TableToolbar/accessibilite/Accessibility.mdx +81 -7
  167. package/src/components/Tables/SyServerTable/SyServerTable.stories.ts +163 -0
  168. package/src/components/Tables/SyServerTable/SyServerTable.vue +2 -1
  169. package/src/components/Tables/SyServerTable/tests/SyServerTable.spec.ts +67 -0
  170. package/src/components/Tables/SyTable/SyTable.stories.ts +94 -0
  171. package/src/components/Tables/SyTable/SyTable.vue +2 -1
  172. package/src/components/Tables/SyTable/tests/SyTable.spec.ts +64 -0
  173. package/src/components/Tables/common/TableHeader.vue +2 -2
  174. package/src/components/Tables/common/filters/logics/tests/NumberFilterLogic.spec.ts +176 -0
  175. package/src/components/Tables/common/filters/logics/tests/SelectFilterLogic.spec.ts +111 -0
  176. package/src/components/Tables/common/tableStyles.scss +6 -6
  177. package/src/components/Tables/common/types.ts +2 -0
  178. package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +2 -0
  179. package/src/components/index.ts +1 -0
  180. package/src/composables/date/tests/useDateFormatDayjs.spec.ts +31 -0
  181. package/src/composables/date/tests/useHolidayDay.spec.ts +109 -0
  182. package/src/composables/rules/tests/useFieldValidation.spec.ts +374 -0
  183. package/src/composables/tests/useError.spec.ts +30 -0
  184. package/src/composables/tests/useFormFieldErrorHandling.spec.ts +234 -0
  185. package/src/composables/unifyValidation/documentationValidationProps.ts +5 -5
  186. package/src/composables/unifyValidation/tests/documentationValidationProps.spec.ts +177 -0
  187. package/src/composables/unifyValidation/tests/useCustomValidation.spec.ts +30 -0
  188. package/src/composables/unifyValidation/tests/useValidation.spec.ts +6 -2
  189. package/src/composables/unifyValidation/useCustomValidation.ts +19 -9
  190. package/src/composables/unifyValidation/useValidation.ts +18 -21
  191. package/src/composables/useFilterable/useFilterable.spec.ts +42 -0
  192. package/src/composables/useFilterable/useFilterable.ts +11 -7
  193. package/src/composables/useFormFieldErrorHandling.ts +2 -2
  194. package/src/composantsVuetify/VBtn/VBtn.mdx +9 -39
  195. package/src/composantsVuetify/VBtn/v-btn.stories.ts +26 -86
  196. package/src/designTokens/tokens/amelipro/apContextual.ts +6 -0
  197. package/src/designTokens/tokens/amelipro/apDarkTheme.ts +2 -2
  198. package/src/designTokens/tokens/amelipro/apLightTheme.ts +72 -103
  199. package/src/designTokens/tokens/amelipro/apSemantic.ts +1 -1
  200. package/src/designTokens/tokens/baseContextualTokens.ts +1 -6
  201. package/src/designTokens/tokens/baseTokens.ts +232 -0
  202. package/src/designTokens/tokens/cnam/cnamContextual.ts +6 -0
  203. package/src/designTokens/tokens/cnam/cnamDarkTheme.ts +2 -2
  204. package/src/designTokens/tokens/cnam/cnamLightTheme.ts +76 -104
  205. package/src/designTokens/tokens/pa/paDarkTheme.ts +2 -2
  206. package/src/designTokens/tokens/pa/paLightTheme.ts +73 -99
  207. package/src/designTokens/tokens/pa/paSemantic.ts +2 -0
  208. package/src/designTokens/tokens/semanticTokens.ts +114 -0
  209. package/src/stories/Components/Components.stories.ts +7 -3
  210. package/src/stories/DesignTokens/ColorIntegrationExample.vue +2 -3
  211. package/src/stories/DesignTokens/Colors.mdx +6 -8
  212. package/src/stories/DesignTokens/colors.stories.ts +244 -1081
  213. package/src/utils/amelipro/toKebabCase/tests/toKebabCase.spec.ts +52 -0
  214. package/src/utils/formatNir/tests/formatNir.spec.ts +34 -0
  215. package/src/utils/tests/insertAt.spec.ts +44 -0
  216. package/dist/apLightTheme-DS0Uy44H.js +0 -954
  217. package/dist/components/RatingPicker/tests/RatingPicker.a11y.spect.d.ts +0 -1
  218. package/dist/main-BsJ9ec3i.js +0 -38954
  219. package/src/components/BackBtn/tests/__snapshots__/back-btn-custom-bg.snap.png +0 -0
  220. package/src/components/BackBtn/tests/__snapshots__/back-btn-dark-mode.snap.png +0 -0
  221. package/src/components/BackBtn/tests/__snapshots__/back-btn-default.snap.png +0 -0
  222. package/src/components/BackBtn/tests/__snapshots__/back-btn-no-icon.snap.png +0 -0
  223. package/src/components/DatePicker/CalendarMode/tests/DatePicker.events.spec.ts +0 -178
  224. package/src/components/DialogBox/tests/__snapshots__/dialog-box-custom-texts.snap.png +0 -0
  225. package/src/components/DialogBox/tests/__snapshots__/dialog-box-default.snap.png +0 -0
  226. package/src/components/DialogBox/tests/__snapshots__/dialog-box-no-actions.snap.png +0 -0
  227. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/header-burger-menu-generated-submenu-open.snap.png +0 -0
  228. package/src/components/HeaderBar/HeaderBurgerMenu/tests/__snapshots__/header-burger-menu-generated.snap.png +0 -0
  229. package/src/components/HeaderBar/tests/__snapshots__/header-bar-custom-width.snap.png +0 -0
  230. package/src/components/HeaderBar/tests/__snapshots__/header-bar-default.snap.png +0 -0
  231. package/src/components/HeaderBar/tests/__snapshots__/header-bar-no-sticky.snap.png +0 -0
  232. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-prepend.snap.png +0 -0
  233. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-side.snap.png +0 -0
  234. package/src/components/HeaderBar/tests/__snapshots__/header-bar-with-subtitle.snap.png +0 -0
  235. package/src/components/Logo/tests/__snapshots__/logo-avatar.snap.png +0 -0
  236. package/src/components/Logo/tests/__snapshots__/logo-dark.snap.png +0 -0
  237. package/src/components/Logo/tests/__snapshots__/logo-default.snap.png +0 -0
  238. package/src/components/Logo/tests/__snapshots__/logo-no-organism.snap.png +0 -0
  239. package/src/components/Logo/tests/__snapshots__/logo-no-signature.snap.png +0 -0
  240. package/src/components/Logo/tests/__snapshots__/logo-risque-pro.snap.png +0 -0
  241. package/src/components/RangeField/tests/__snapshots__/range-field-custom-bg.snap.png +0 -0
  242. package/src/components/RangeField/tests/__snapshots__/range-field-custom-range.snap.png +0 -0
  243. package/src/components/RangeField/tests/__snapshots__/range-field-default.snap.png +0 -0
  244. package/src/components/RangeField/tests/__snapshots__/range-field-step.snap.png +0 -0
  245. package/src/components/RangeField/tests/__snapshots__/range-field-with-label.snap.png +0 -0
  246. package/src/components/SyAlert/tests/__snapshots__/sy-alert-closable.snap.png +0 -0
  247. package/src/components/SyAlert/tests/__snapshots__/sy-alert-error.snap.png +0 -0
  248. package/src/components/SyAlert/tests/__snapshots__/sy-alert-info.snap.png +0 -0
  249. package/src/components/SyAlert/tests/__snapshots__/sy-alert-success.snap.png +0 -0
  250. package/src/components/SyAlert/tests/__snapshots__/sy-alert-variant-outlined.snap.png +0 -0
  251. package/src/components/SyAlert/tests/__snapshots__/sy-alert-variant-tonal.snap.png +0 -0
  252. package/src/components/SyAlert/tests/__snapshots__/sy-alert-warning.snap.png +0 -0
  253. /package/src/components/RatingPicker/tests/{RatingPicker.a11y.spect.ts → RatingPicker.a11y.spec.ts} +0 -0
@@ -0,0 +1,589 @@
1
+ import { mount, flushPromises } from '@vue/test-utils'
2
+ import { describe, it, expect, vi, afterEach } from 'vitest'
3
+ import { defineComponent } from 'vue'
4
+ import { createRouter, createMemoryHistory } from 'vue-router'
5
+ import HorizontalNavbar from '../HorizontalNavbar.vue'
6
+
7
+ const SyTabsStub = defineComponent({
8
+ name: 'SyTabs',
9
+ props: {
10
+ items: { type: Array, default: () => [] },
11
+ modelValue: { type: Number, default: -1 },
12
+ confirmTabChange: { type: Boolean, default: false },
13
+ confirmationMessage: { type: String, default: undefined },
14
+ },
15
+ emits: ['update:modelValue', 'cancel-navigation', 'confirm-tab-change'],
16
+ template: `
17
+ <div class="sy-tabs-stub">
18
+ <button
19
+ v-for="(item, i) in items"
20
+ :key="i"
21
+ :class="['tab-btn', { active: modelValue === i }]"
22
+ :data-index="i"
23
+ @click="$emit('update:modelValue', i)"
24
+ >{{ item.label }}</button>
25
+ </div>
26
+ `,
27
+ })
28
+
29
+ const stubs = {
30
+ RouterLink: true,
31
+ SyTabs: SyTabsStub,
32
+ }
33
+
34
+ const defaultItems = [
35
+ { label: 'Accueil', to: '/' },
36
+ { label: 'À propos', to: '/about' },
37
+ { label: 'Contact', to: '/contact' },
38
+ ]
39
+
40
+ describe('HorizontalNavbar', () => {
41
+ afterEach(() => {
42
+ vi.restoreAllMocks()
43
+ })
44
+
45
+ describe('rendu', () => {
46
+ it('rend le composant avec la classe horizontal-menu', () => {
47
+ const wrapper = mount(HorizontalNavbar, {
48
+ global: { stubs },
49
+ props: { items: defaultItems },
50
+ })
51
+ expect(wrapper.find('.horizontal-menu').exists()).toBe(true)
52
+ })
53
+
54
+ it('rend autant d\'onglets que d\'items', () => {
55
+ const wrapper = mount(HorizontalNavbar, {
56
+ global: { stubs },
57
+ props: { items: defaultItems },
58
+ })
59
+ expect(wrapper.findAll('.tab-btn')).toHaveLength(3)
60
+ })
61
+
62
+ it('rend sans items sans crash', () => {
63
+ const wrapper = mount(HorizontalNavbar, {
64
+ global: { stubs },
65
+ props: { items: [] },
66
+ })
67
+ expect(wrapper.find('.horizontal-menu').exists()).toBe(true)
68
+ expect(wrapper.findAll('.tab-btn')).toHaveLength(0)
69
+ })
70
+
71
+ it('applique la largeur personnalisée via le prop width', () => {
72
+ const wrapper = mount(HorizontalNavbar, {
73
+ global: { stubs },
74
+ props: { items: defaultItems, width: '1200px' },
75
+ })
76
+ expect(wrapper.html()).toBeTruthy()
77
+ })
78
+ })
79
+
80
+ describe('slots', () => {
81
+ it('rend le slot navigation-bar-prepend', () => {
82
+ const wrapper = mount(HorizontalNavbar, {
83
+ global: { stubs },
84
+ props: { items: defaultItems },
85
+ slots: {
86
+ 'navigation-bar-prepend': '<span class="prepend-content">Logo</span>',
87
+ 'navigation-bar-append': '',
88
+ 'default': '',
89
+ },
90
+ })
91
+ expect(wrapper.find('.prepend-content').exists()).toBe(true)
92
+ expect(wrapper.find('.prepend-content').text()).toBe('Logo')
93
+ })
94
+
95
+ it('rend le slot navigation-bar-append', () => {
96
+ const wrapper = mount(HorizontalNavbar, {
97
+ global: { stubs },
98
+ props: { items: defaultItems },
99
+ slots: {
100
+ 'navigation-bar-prepend': '',
101
+ 'navigation-bar-append': '<span class="append-content">Actions</span>',
102
+ 'default': '',
103
+ },
104
+ })
105
+ expect(wrapper.find('.append-content').exists()).toBe(true)
106
+ })
107
+ })
108
+
109
+ describe('handleTabChange', () => {
110
+ it('met à jour activeTab quand un onglet est cliqué', async () => {
111
+ const wrapper = mount(HorizontalNavbar, {
112
+ global: { stubs },
113
+ props: { items: defaultItems },
114
+ })
115
+
116
+ await wrapper.findAll('.tab-btn')[1]!.trigger('click')
117
+ await flushPromises()
118
+
119
+ const vm = wrapper.vm as unknown as { activeTab: number }
120
+ expect(vm.activeTab).toBe(1)
121
+ })
122
+
123
+ it('ne navigue pas via router.push si l\'item est désactivé', async () => {
124
+ const mockPush = vi.fn()
125
+ const items = [
126
+ { label: 'Accueil', to: '/' },
127
+ { label: 'Désactivé', to: '/disabled', disabled: true },
128
+ ]
129
+ const wrapper = mount(HorizontalNavbar, {
130
+ global: {
131
+ stubs,
132
+ mocks: { $router: { push: mockPush, currentRoute: { value: { path: '/' } } } },
133
+ },
134
+ props: { items },
135
+ })
136
+
137
+ await wrapper.findAll('.tab-btn')[1]!.trigger('click')
138
+ await flushPromises()
139
+
140
+ expect(mockPush).not.toHaveBeenCalled()
141
+ })
142
+
143
+ it('émet cancel-navigation depuis SyTabs', async () => {
144
+ const wrapper = mount(HorizontalNavbar, {
145
+ global: { stubs },
146
+ props: { items: defaultItems },
147
+ })
148
+
149
+ wrapper.findComponent(SyTabsStub).vm.$emit('cancel-navigation')
150
+ await flushPromises()
151
+
152
+ expect(wrapper.emitted('cancel-navigation')).toBeTruthy()
153
+ })
154
+ })
155
+
156
+ describe('confirmTabChange', () => {
157
+ it('transmet confirmTabChange=true à SyTabs', () => {
158
+ const wrapper = mount(HorizontalNavbar, {
159
+ global: { stubs },
160
+ props: { items: defaultItems, confirmTabChange: true },
161
+ })
162
+
163
+ const tabs = wrapper.findComponent(SyTabsStub)
164
+ expect(tabs.props('confirmTabChange')).toBe(true)
165
+ })
166
+
167
+ it('transmet un confirmationMessage string à SyTabs', () => {
168
+ const wrapper = mount(HorizontalNavbar, {
169
+ global: { stubs },
170
+ props: {
171
+ items: defaultItems,
172
+ confirmTabChange: true,
173
+ confirmationMessage: 'Voulez-vous continuer ?',
174
+ },
175
+ })
176
+
177
+ const tabs = wrapper.findComponent(SyTabsStub)
178
+ expect(tabs.props('confirmationMessage')).toBe('Voulez-vous continuer ?')
179
+ })
180
+
181
+ it('formattedConfirmationMessage est undefined si confirmationMessage est un booléen', () => {
182
+ const wrapper = mount(HorizontalNavbar, {
183
+ global: { stubs },
184
+ props: {
185
+ items: defaultItems,
186
+ confirmTabChange: true,
187
+ confirmationMessage: true,
188
+ },
189
+ })
190
+
191
+ const tabs = wrapper.findComponent(SyTabsStub)
192
+ expect(tabs.props('confirmationMessage')).toBeUndefined()
193
+ })
194
+
195
+ it('émet confirm-tab-change avec le message et le callback', async () => {
196
+ const wrapper = mount(HorizontalNavbar, {
197
+ global: { stubs },
198
+ props: { items: defaultItems, confirmTabChange: true },
199
+ })
200
+
201
+ const callback = vi.fn()
202
+ wrapper.findComponent(SyTabsStub).vm.$emit('confirm-tab-change', 'Message', callback)
203
+ await flushPromises()
204
+
205
+ expect(wrapper.emitted('confirm-tab-change')).toBeTruthy()
206
+ const [msg, cb] = wrapper.emitted('confirm-tab-change')![0] as [string, () => void]
207
+ expect(msg).toBe('Message')
208
+ expect(cb).toBe(callback)
209
+ })
210
+ })
211
+
212
+ describe('resetTabSelection', () => {
213
+ it('retourne activeTab=-1 et activeItemIndex=-1 si items est vide', () => {
214
+ const wrapper = mount(HorizontalNavbar, {
215
+ global: { stubs },
216
+ props: { items: [] },
217
+ })
218
+
219
+ const vm = wrapper.vm as unknown as { resetTabSelection: () => { activeTab: number, activeItemIndex: number } }
220
+ const result = vm.resetTabSelection()
221
+ expect(result.activeTab).toBe(-1)
222
+ expect(result.activeItemIndex).toBe(-1)
223
+ })
224
+
225
+ it('est exposée et appelable depuis l\'extérieur', () => {
226
+ const wrapper = mount(HorizontalNavbar, {
227
+ global: { stubs },
228
+ props: { items: defaultItems },
229
+ })
230
+
231
+ const vm = wrapper.vm as unknown as { resetTabSelection: () => unknown }
232
+ expect(typeof vm.resetTabSelection).toBe('function')
233
+ expect(() => vm.resetTabSelection()).not.toThrow()
234
+ })
235
+ })
236
+
237
+ describe('isActive', () => {
238
+ it('retourne false pour un item désactivé', () => {
239
+ const wrapper = mount(HorizontalNavbar, {
240
+ global: { stubs },
241
+ props: {
242
+ items: [{ label: 'Test', to: '/', disabled: true }],
243
+ },
244
+ })
245
+
246
+ const vm = wrapper.vm as unknown as {
247
+ isActive: (item: { label: string, to: string, disabled?: boolean }, index: number) => boolean
248
+ }
249
+ expect(vm.isActive({ label: 'Test', to: '/', disabled: true }, 0)).toBe(false)
250
+ })
251
+
252
+ it('retourne false pour un item avec to (string) quand pathname ne correspond pas', () => {
253
+ const wrapper = mount(HorizontalNavbar, {
254
+ global: { stubs },
255
+ props: { items: [{ label: 'À propos', to: '/about' }] },
256
+ })
257
+
258
+ const vm = wrapper.vm as unknown as {
259
+ isActive: (item: { label: string, to: string }, index: number) => boolean
260
+ }
261
+ // jsdom pathname est '/', '/about' ne correspond pas
262
+ expect(vm.isActive({ label: 'À propos', to: '/about' }, 0)).toBe(false)
263
+ })
264
+
265
+ it('retourne true pour un item avec to (string) correspondant à window.location.pathname', () => {
266
+ Object.defineProperty(window, 'location', {
267
+ value: { ...window.location, pathname: '/about', href: 'http://localhost/about' },
268
+ writable: true,
269
+ })
270
+ const wrapper = mount(HorizontalNavbar, {
271
+ global: { stubs },
272
+ props: { items: [{ label: 'À propos', to: '/about' }] },
273
+ })
274
+
275
+ const vm = wrapper.vm as unknown as {
276
+ isActive: (item: { label: string, to: string }, index: number) => boolean
277
+ }
278
+ expect(vm.isActive({ label: 'À propos', to: '/about' }, 0)).toBe(true)
279
+ Object.defineProperty(window, 'location', {
280
+ value: { ...window.location, pathname: '/', href: 'http://localhost/' },
281
+ writable: true,
282
+ })
283
+ })
284
+
285
+ it('retourne true pour un item avec to (string) sous-chemin de pathname', () => {
286
+ Object.defineProperty(window, 'location', {
287
+ value: { ...window.location, pathname: '/about/team', href: 'http://localhost/about/team' },
288
+ writable: true,
289
+ })
290
+ const wrapper = mount(HorizontalNavbar, {
291
+ global: { stubs },
292
+ props: { items: [{ label: 'À propos', to: '/about' }] },
293
+ })
294
+
295
+ const vm = wrapper.vm as unknown as {
296
+ isActive: (item: { label: string, to: string }, index: number) => boolean
297
+ }
298
+ expect(vm.isActive({ label: 'À propos', to: '/about' }, 0)).toBe(true)
299
+ Object.defineProperty(window, 'location', {
300
+ value: { ...window.location, pathname: '/', href: 'http://localhost/' },
301
+ writable: true,
302
+ })
303
+ })
304
+
305
+ it('retourne true pour un item avec to (objet) correspondant à pathname', () => {
306
+ Object.defineProperty(window, 'location', {
307
+ value: { ...window.location, pathname: '/contact', href: 'http://localhost/contact' },
308
+ writable: true,
309
+ })
310
+ const wrapper = mount(HorizontalNavbar, {
311
+ global: { stubs },
312
+ props: { items: [{ label: 'Contact', to: { path: '/contact' } }] },
313
+ })
314
+
315
+ const vm = wrapper.vm as unknown as {
316
+ isActive: (item: { label: string, to: { path: string } }, index: number) => boolean
317
+ }
318
+ expect(vm.isActive({ label: 'Contact', to: { path: '/contact' } }, 0)).toBe(true)
319
+ Object.defineProperty(window, 'location', {
320
+ value: { ...window.location, pathname: '/', href: 'http://localhost/' },
321
+ writable: true,
322
+ })
323
+ })
324
+
325
+ it('retourne false pour un item avec to (objet) non correspondant', () => {
326
+ const wrapper = mount(HorizontalNavbar, {
327
+ global: { stubs },
328
+ props: { items: [{ label: 'Contact', to: { path: '/contact' } }] },
329
+ })
330
+
331
+ const vm = wrapper.vm as unknown as {
332
+ isActive: (item: { label: string, to: { path: string } }, index: number) => boolean
333
+ }
334
+ // jsdom pathname est '/', '/contact' ne correspond pas
335
+ expect(vm.isActive({ label: 'Contact', to: { path: '/contact' } }, 0)).toBe(false)
336
+ })
337
+ })
338
+
339
+ describe('handleTabChange — navigation', () => {
340
+ it('met à jour activeTab au clic sur un onglet (sans router réel)', async () => {
341
+ const wrapper = mount(HorizontalNavbar, {
342
+ global: { stubs },
343
+ props: { items: defaultItems },
344
+ })
345
+
346
+ await wrapper.findAll('.tab-btn')[1]!.trigger('click')
347
+ await flushPromises()
348
+
349
+ const vm = wrapper.vm as unknown as { activeTab: number }
350
+ expect(vm.activeTab).toBe(1)
351
+ })
352
+
353
+ it('met à jour activeTab mais retourne tôt si confirmTabChange est true', async () => {
354
+ const wrapper = mount(HorizontalNavbar, {
355
+ global: { stubs },
356
+ props: { items: defaultItems, confirmTabChange: true },
357
+ })
358
+
359
+ await wrapper.findAll('.tab-btn')[1]!.trigger('click')
360
+ await flushPromises()
361
+
362
+ const vm = wrapper.vm as unknown as { activeTab: number }
363
+ expect(vm.activeTab).toBe(1)
364
+ })
365
+
366
+ it('navigue via window.location.href si item a un href', async () => {
367
+ const locationSpy = vi.spyOn(window, 'location', 'get').mockReturnValue({
368
+ ...window.location,
369
+ href: '',
370
+ } as Location)
371
+ const hrefSetter = vi.fn()
372
+ Object.defineProperty(window, 'location', {
373
+ value: { ...window.location, set href(v: string) { hrefSetter(v) } },
374
+ writable: true,
375
+ })
376
+
377
+ const items = [{ label: 'Externe', href: 'https://example.com' }]
378
+ const wrapper = mount(HorizontalNavbar, {
379
+ global: { stubs },
380
+ props: { items },
381
+ })
382
+
383
+ await wrapper.findAll('.tab-btn')[0]!.trigger('click')
384
+ await flushPromises()
385
+
386
+ locationSpy.mockRestore()
387
+ })
388
+
389
+ it('ne plante pas si l\'item cliqué n\'existe pas dans items', async () => {
390
+ const wrapper = mount(HorizontalNavbar, {
391
+ global: { stubs },
392
+ props: { items: defaultItems },
393
+ })
394
+
395
+ const vm = wrapper.vm as unknown as { handleTabChange: (index: number) => Promise<void> }
396
+ await expect(vm.handleTabChange(999)).resolves.toBeUndefined()
397
+ })
398
+ })
399
+
400
+ describe('resetTabSelection — avec items et route', () => {
401
+ it('retourne les valeurs courantes de activeTab et activeItemIndex', () => {
402
+ const wrapper = mount(HorizontalNavbar, {
403
+ global: { stubs },
404
+ props: { items: defaultItems },
405
+ })
406
+
407
+ const vm = wrapper.vm as unknown as {
408
+ resetTabSelection: () => { activeTab: number, activeItemIndex: number }
409
+ }
410
+ const result = vm.resetTabSelection()
411
+ expect(typeof result.activeTab).toBe('number')
412
+ expect(typeof result.activeItemIndex).toBe('number')
413
+ })
414
+
415
+ it('retourne -1 si aucun item ne correspond à la route', () => {
416
+ const wrapper = mount(HorizontalNavbar, {
417
+ global: { stubs },
418
+ props: { items: defaultItems },
419
+ })
420
+
421
+ const vm = wrapper.vm as unknown as {
422
+ resetTabSelection: () => { activeTab: number, activeItemIndex: number }
423
+ }
424
+ const result = vm.resetTabSelection()
425
+ expect(result.activeTab).toBe(-1)
426
+ })
427
+
428
+ it('met à jour activeTab après avoir appelé handleTabChange puis resetTabSelection', async () => {
429
+ const wrapper = mount(HorizontalNavbar, {
430
+ global: { stubs },
431
+ props: { items: defaultItems },
432
+ })
433
+
434
+ await wrapper.findAll('.tab-btn')[1]!.trigger('click')
435
+ await flushPromises()
436
+
437
+ const vm = wrapper.vm as unknown as {
438
+ activeTab: number
439
+ resetTabSelection: () => { activeTab: number, activeItemIndex: number }
440
+ }
441
+ expect(vm.activeTab).toBe(1)
442
+ })
443
+ })
444
+
445
+ describe('avec vue-router réel', () => {
446
+ function makeRouter() {
447
+ return createRouter({
448
+ history: createMemoryHistory(),
449
+ routes: [
450
+ { path: '/', component: { template: '<div/>' } },
451
+ { path: '/about', component: { template: '<div/>' } },
452
+ { path: '/contact', component: { template: '<div/>' } },
453
+ { path: '/about/team', component: { template: '<div/>' } },
454
+ ],
455
+ })
456
+ }
457
+
458
+ it('isActive retourne true quand la route correspond à un item to (string)', async () => {
459
+ const router = makeRouter()
460
+ await router.push('/about')
461
+ const wrapper = mount(HorizontalNavbar, {
462
+ global: { stubs, plugins: [router] },
463
+ props: { items: defaultItems },
464
+ })
465
+ await flushPromises()
466
+
467
+ const vm = wrapper.vm as unknown as {
468
+ isActive: (item: { label: string, to: string }, index: number) => boolean
469
+ }
470
+ expect(vm.isActive({ label: 'À propos', to: '/about' }, 1)).toBe(true)
471
+ })
472
+
473
+ it('resetTabSelection trouve l\'item actif avec une route correspondante', async () => {
474
+ const router = makeRouter()
475
+ await router.push('/about')
476
+ const wrapper = mount(HorizontalNavbar, {
477
+ global: { stubs, plugins: [router] },
478
+ props: { items: defaultItems },
479
+ })
480
+ await flushPromises()
481
+
482
+ const vm = wrapper.vm as unknown as {
483
+ resetTabSelection: () => { activeTab: number, activeItemIndex: number }
484
+ }
485
+ const result = vm.resetTabSelection()
486
+ expect(result.activeTab).toBe(1)
487
+ expect(result.activeItemIndex).toBe(1)
488
+ })
489
+
490
+ it('watcher currentPath met à jour activeTab lors d\'un changement de route', async () => {
491
+ const router = makeRouter()
492
+ await router.push('/')
493
+ const wrapper = mount(HorizontalNavbar, {
494
+ global: { stubs, plugins: [router] },
495
+ props: { items: defaultItems },
496
+ })
497
+ await flushPromises()
498
+
499
+ await router.push('/about')
500
+ await flushPromises()
501
+
502
+ const vm = wrapper.vm as unknown as { activeTab: number }
503
+ expect(vm.activeTab).toBe(1)
504
+ })
505
+
506
+ it('watcher currentPath remet activeTab à -1 si aucun item ne correspond', async () => {
507
+ const router = makeRouter()
508
+ await router.push('/about')
509
+ const itemsWithoutContact = [
510
+ { label: 'Accueil', to: '/home' },
511
+ { label: 'À propos', to: '/about' },
512
+ ]
513
+ const wrapper = mount(HorizontalNavbar, {
514
+ global: { stubs, plugins: [router] },
515
+ props: { items: itemsWithoutContact },
516
+ })
517
+ await flushPromises()
518
+
519
+ await router.push('/contact')
520
+ await flushPromises()
521
+
522
+ const vm = wrapper.vm as unknown as { activeTab: number }
523
+ expect(vm.activeTab).toBe(-1)
524
+ })
525
+
526
+ it('handleTabChange appelle router.push avec le to de l\'item', async () => {
527
+ const router = makeRouter()
528
+ const pushSpy = vi.spyOn(router, 'push')
529
+ await router.push('/')
530
+ const wrapper = mount(HorizontalNavbar, {
531
+ global: { stubs, plugins: [router] },
532
+ props: { items: defaultItems },
533
+ })
534
+ await flushPromises()
535
+
536
+ await wrapper.findAll('.tab-btn')[1]!.trigger('click')
537
+ await flushPromises()
538
+
539
+ expect(pushSpy).toHaveBeenCalledWith('/about')
540
+ })
541
+
542
+ it('isActive retourne true pour un item avec to (string) sous-chemin', async () => {
543
+ const router = makeRouter()
544
+ await router.push('/about/team')
545
+ const wrapper = mount(HorizontalNavbar, {
546
+ global: { stubs, plugins: [router] },
547
+ props: { items: defaultItems },
548
+ })
549
+ await flushPromises()
550
+
551
+ const vm = wrapper.vm as unknown as {
552
+ isActive: (item: { label: string, to: string }, index: number) => boolean
553
+ }
554
+ expect(vm.isActive({ label: 'À propos', to: '/about' }, 1)).toBe(true)
555
+ })
556
+ })
557
+
558
+ describe('tabItems computed', () => {
559
+ it('convertit les items en TabItem avec les bonnes propriétés', () => {
560
+ const wrapper = mount(HorizontalNavbar, {
561
+ global: { stubs },
562
+ props: {
563
+ items: [
564
+ { label: 'Accueil', to: '/', disabled: false },
565
+ { label: 'Externe', href: 'https://example.com' },
566
+ ],
567
+ },
568
+ })
569
+
570
+ const vm = wrapper.vm as unknown as {
571
+ tabItems: Array<{ label: string, value: number, href?: string, to?: string, disabled?: boolean }>
572
+ }
573
+ expect(vm.tabItems).toHaveLength(2)
574
+ expect(vm.tabItems[0]!.label).toBe('Accueil')
575
+ expect(vm.tabItems[0]!.value).toBe(0)
576
+ expect(vm.tabItems[1]!.href).toBe('https://example.com')
577
+ })
578
+
579
+ it('retourne un tableau vide si items n\'est pas un tableau', () => {
580
+ const wrapper = mount(HorizontalNavbar, {
581
+ global: { stubs },
582
+ props: { items: [] },
583
+ })
584
+
585
+ const vm = wrapper.vm as unknown as { tabItems: unknown[] }
586
+ expect(vm.tabItems).toHaveLength(0)
587
+ })
588
+ })
589
+ })