@cnamts/synapse 0.0.11-alpha → 0.0.12-alpha

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 (108) hide show
  1. package/dist/design-system-v3.js +3878 -3189
  2. package/dist/design-system-v3.umd.cjs +1 -1
  3. package/dist/src/components/Amelipro/types/languages.d.ts +6 -0
  4. package/dist/src/components/Amelipro/types/types.d.ts +65 -0
  5. package/dist/src/components/CookieBanner/CookieBanner.d.ts +1 -1
  6. package/dist/src/components/Customs/SyInputSelect/SyInputSelect.d.ts +2 -0
  7. package/dist/src/components/Customs/SyTextField/SyTextField.d.ts +29 -23
  8. package/dist/src/components/Customs/SyTextField/types.d.ts +1 -0
  9. package/dist/src/components/DatePicker/DatePicker.d.ts +70 -59
  10. package/dist/src/components/DatePicker/DateTextInput.d.ts +67 -56
  11. package/dist/src/components/ErrorPage/ErrorPage.d.ts +1 -1
  12. package/dist/src/components/FileList/FileList.d.ts +1 -0
  13. package/dist/src/components/FileList/UploadItem/UploadItem.d.ts +1 -1
  14. package/dist/src/components/FilterSideBar/FilterSideBar.d.ts +31 -0
  15. package/dist/src/components/FilterSideBar/locales.d.ts +7 -0
  16. package/dist/src/components/FilterSideBar/tests/FilterSideBar.spec.d.ts +1 -0
  17. package/dist/src/components/LangBtn/LangBtn.d.ts +2 -2
  18. package/dist/src/components/NirField/NirField.d.ts +940 -0
  19. package/dist/src/components/NotificationBar/NotificationBar.d.ts +1 -1
  20. package/dist/src/components/PasswordField/PasswordField.d.ts +40 -8
  21. package/dist/src/components/PeriodField/PeriodField.d.ts +142 -120
  22. package/dist/src/components/PhoneField/PhoneField.d.ts +11 -2
  23. package/dist/src/components/RatingPicker/EmotionPicker/EmotionPicker.d.ts +1 -1
  24. package/dist/src/components/RatingPicker/NumberPicker/NumberPicker.d.ts +1 -1
  25. package/dist/src/components/RatingPicker/StarsPicker/StarsPicker.d.ts +1 -1
  26. package/dist/src/components/UploadWorkflow/config.d.ts +29 -0
  27. package/dist/src/components/UploadWorkflow/locales.d.ts +7 -0
  28. package/dist/src/components/UploadWorkflow/tests/UploadWorkflow.spec.d.ts +1 -0
  29. package/dist/src/components/UploadWorkflow/types.d.ts +19 -0
  30. package/dist/src/components/UploadWorkflow/useFileList.d.ts +10 -0
  31. package/dist/src/components/UploadWorkflow/useFileUploadJourney.d.ts +9 -0
  32. package/dist/src/components/index.d.ts +2 -0
  33. package/dist/src/composables/rules/useFieldValidation.d.ts +1 -0
  34. package/dist/src/composables/validation/tests/useValidation.spec.d.ts +1 -0
  35. package/dist/src/composables/validation/useValidation.d.ts +39 -0
  36. package/dist/src/designTokens/index.d.ts +3 -1
  37. package/dist/src/vuetifyConfig.d.ts +81 -0
  38. package/dist/style.css +1 -1
  39. package/package.json +1 -1
  40. package/src/assets/_elevations.scss +89 -0
  41. package/src/assets/_fonts.scss +6 -0
  42. package/src/assets/_radius.scss +86 -0
  43. package/src/assets/_spacers.scss +149 -0
  44. package/src/assets/settings.scss +7 -3
  45. package/src/assets/tokens.scss +32 -29
  46. package/src/components/Amelipro/types/languages.d.ts +6 -0
  47. package/src/components/Amelipro/types/types.d.ts +65 -0
  48. package/src/components/Customs/SyInputSelect/SyInputSelect.stories.ts +65 -0
  49. package/src/components/Customs/SyInputSelect/SyInputSelect.vue +13 -3
  50. package/src/components/Customs/SySelect/SySelect.stories.ts +88 -5
  51. package/src/components/Customs/SySelect/SySelect.vue +36 -10
  52. package/src/components/Customs/SySelect/tests/SySelect.spec.ts +135 -2
  53. package/src/components/Customs/SyTextField/SyTextField.stories.ts +576 -85
  54. package/src/components/Customs/SyTextField/SyTextField.vue +132 -104
  55. package/src/components/Customs/SyTextField/tests/SyTextField.spec.ts +190 -38
  56. package/src/components/Customs/SyTextField/types.d.ts +1 -0
  57. package/src/components/DatePicker/DatePicker.vue +405 -137
  58. package/src/components/DatePicker/DateTextInput.vue +15 -0
  59. package/src/components/DatePicker/tests/DatePicker.spec.ts +8 -15
  60. package/src/components/FileList/FileList.vue +2 -1
  61. package/src/components/FileList/UploadItem/UploadItem.vue +10 -0
  62. package/src/components/FileUpload/FileUpload.stories.ts +84 -0
  63. package/src/components/FileUpload/FileUpload.vue +1 -0
  64. package/src/components/FileUpload/tests/FileUpload.spec.ts +4 -4
  65. package/src/components/FilterInline/FilterInline.mdx +180 -34
  66. package/src/components/FilterInline/FilterInline.stories.ts +363 -6
  67. package/src/components/FilterSideBar/FilterSideBar.mdx +237 -0
  68. package/src/components/FilterSideBar/FilterSideBar.stories.ts +798 -0
  69. package/src/components/FilterSideBar/FilterSideBar.vue +193 -0
  70. package/src/components/FilterSideBar/locales.ts +8 -0
  71. package/src/components/FilterSideBar/tests/FilterSideBar.spec.ts +305 -0
  72. package/src/components/FilterSideBar/tests/__snapshots__/FilterSideBar.spec.ts.snap +39 -0
  73. package/src/components/HeaderBar/Usages.mdx +1 -1
  74. package/src/components/NirField/NirField.stories.ts +573 -29
  75. package/src/components/NirField/NirField.vue +397 -359
  76. package/src/components/NirField/tests/NirField.spec.ts +88 -52
  77. package/src/components/NirField/tests//342/200/257dataset/342/200/257.md +12 -0
  78. package/src/components/NotificationBar/Accessibilite.stories.ts +4 -0
  79. package/src/components/NotificationBar/NotificationBar.stories.ts +18 -13
  80. package/src/components/PasswordField/PasswordField.mdx +129 -47
  81. package/src/components/PasswordField/PasswordField.stories.ts +924 -120
  82. package/src/components/PasswordField/PasswordField.vue +209 -99
  83. package/src/components/PasswordField/tests/PasswordField.spec.ts +138 -9
  84. package/src/components/PeriodField/PeriodField.vue +55 -54
  85. package/src/components/PhoneField/PhoneField.stories.ts +69 -0
  86. package/src/components/PhoneField/PhoneField.vue +3 -0
  87. package/src/components/PhoneField/indicatifs.ts +1 -1
  88. package/src/components/UploadWorkflow/UploadWorkflow.mdx +75 -0
  89. package/src/components/UploadWorkflow/UploadWorkflow.stories.ts +943 -0
  90. package/src/components/UploadWorkflow/UploadWorkflow.vue +230 -0
  91. package/src/components/UploadWorkflow/config.ts +29 -0
  92. package/src/components/UploadWorkflow/locales.ts +8 -0
  93. package/src/components/UploadWorkflow/tests/UploadWorkflow.spec.ts +257 -0
  94. package/src/components/UploadWorkflow/tests/__snapshots__/UploadWorkflow.spec.ts.snap +54 -0
  95. package/src/components/UploadWorkflow/types.ts +21 -0
  96. package/src/components/UploadWorkflow/useFileList.ts +84 -0
  97. package/src/components/UploadWorkflow/useFileUploadJourney.ts +18 -0
  98. package/src/components/index.ts +2 -0
  99. package/src/composables/rules/useFieldValidation.ts +5 -2
  100. package/src/composables/validation/tests/useValidation.spec.ts +154 -0
  101. package/src/composables/validation/useValidation.ts +165 -0
  102. package/src/designTokens/index.ts +4 -0
  103. package/src/stories/Demarrer/Accueil.mdx +1 -1
  104. package/src/stories/DesignTokens/ThemePA.mdx +4 -30
  105. package/src/stories/GuideDuDev/UtiliserLesRules.mdx +319 -76
  106. package/src/stories/GuideDuDev/moduleDeNotification.mdx +1 -1
  107. package/src/vuetifyConfig.ts +61 -0
  108. package/src/composables/useFilterable/__snapshots__/useFilterable.spec.ts.snap +0 -3
@@ -2,26 +2,54 @@
2
2
  import { ref, computed, watch } from 'vue'
3
3
  import { config } from './config'
4
4
  import { locales } from './locales'
5
- import { useFieldValidation } from '@/composables/rules/useFieldValidation'
6
- import { mdiEye, mdiEyeOff } from '@mdi/js'
7
- // import deepMerge from 'deepmerge'
5
+ import { useValidation, type ValidationRule } from '@/composables/validation/useValidation'
6
+ import {
7
+ mdiEye,
8
+ mdiEyeOff,
9
+ mdiAlertCircle,
10
+ mdiAlert,
11
+ mdiCheckCircle,
12
+ } from '@mdi/js'
8
13
  import useCustomizableOptions, { type CustomizableOptions } from '@/composables/useCustomizableOptions'
9
-
10
- type Rule = (value: string | null) => { error?: string, success?: string }
14
+ import SyTextField from '@/components/Customs/SyTextField/SyTextField.vue'
15
+ import type { ColorType } from '@/components/Customs/SyTextField/types'
11
16
 
12
17
  const props = withDefaults(defineProps<{
13
18
  modelValue?: string | null
14
- outlined?: boolean
19
+ variantStyle?: 'outlined' | 'underlined'
20
+ color?: ColorType
21
+ label?: string
15
22
  required?: boolean
23
+ errorMessages?: string[] | null
24
+ warningMessages?: string[] | null
25
+ successMessages?: string[] | null
26
+ isReadOnly?: boolean
27
+ isDisabled?: boolean
28
+ placeholder?: string
29
+ customRules?: ValidationRule[]
30
+ customWarningRules?: ValidationRule[]
31
+ customSuccessRules?: ValidationRule[]
32
+ showSuccessMessages?: boolean
33
+ displayAsterisk?: boolean
16
34
  isValidateOnBlur?: boolean
17
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a generic type
18
- customRules?: any
19
35
  } & CustomizableOptions>(), {
20
36
  modelValue: null,
21
- outlined: true,
37
+ variantStyle: 'outlined',
38
+ color: 'primary',
39
+ label: undefined,
22
40
  required: false,
41
+ errorMessages: null,
42
+ warningMessages: null,
43
+ successMessages: null,
44
+ isReadOnly: false,
45
+ isDisabled: false,
46
+ placeholder: undefined,
47
+ customRules: () => [],
48
+ customWarningRules: () => [],
49
+ customSuccessRules: () => [],
50
+ showSuccessMessages: true,
51
+ displayAsterisk: false,
23
52
  isValidateOnBlur: true,
24
- customRules: [],
25
53
  })
26
54
 
27
55
  const options = useCustomizableOptions(config, props)
@@ -43,147 +71,229 @@
43
71
  },
44
72
  )
45
73
 
46
- const { generateRules } = useFieldValidation()
74
+ // Construction des règles de validation
75
+ const defaultRules = computed<ValidationRule[]>(() => {
76
+ const rules: ValidationRule[] = []
47
77
 
48
- const defaultRules = [
49
- ...(props.required
50
- ? [{
78
+ if (props.required) {
79
+ rules.push({
51
80
  type: 'required',
52
- options: { message: 'Le mot de passe est requis.', fieldIdentifier: 'password' },
53
- }]
54
- : []),
55
- ]
56
-
57
- const rules = computed(() => {
58
- const baseRules = (props.required ? defaultRules : [])
59
- return props.customRules ? generateRules([...baseRules, ...props.customRules]) : generateRules(baseRules)
60
- })
61
-
62
- const errors = ref<string[]>([])
63
- const successes = ref<string[]>([])
81
+ options: {
82
+ message: 'Le mot de passe est requis',
83
+ fieldIdentifier: props.label || 'password',
84
+ },
85
+ })
86
+ }
64
87
 
65
- const isValidating = ref(false)
88
+ // Règle pour le message de succès
89
+ rules.push({
90
+ type: 'custom',
91
+ options: {
92
+ validate: (value: string) => value ? true : 'Ce champ est requis',
93
+ successMessage: 'Mot de passe fort',
94
+ fieldIdentifier: props.label || 'password',
95
+ },
96
+ })
66
97
 
67
- watch(() => password.value, () => {
68
- validateFields()
69
- }, { immediate: true })
98
+ return rules
99
+ })
70
100
 
71
- watch(
72
- () => props.isValidateOnBlur,
73
- () => {
74
- validateFields()
75
- },
76
- { immediate: true },
77
- )
101
+ // Initialisation du composable de validation
102
+ const { errors, warnings, successes, validateField } = useValidation({
103
+ customRules: defaultRules.value,
104
+ warningRules: props.customWarningRules || [],
105
+ successRules: props.customSuccessRules || [],
106
+ showSuccessMessages: props.showSuccessMessages,
107
+ fieldIdentifier: props.label || 'password',
108
+ })
78
109
 
79
- watch(
80
- () => props.required,
81
- () => {
82
- validateFields()
83
- },
84
- { immediate: true },
85
- )
110
+ // Computed pour les états de validation
111
+ const hasError = computed(() => errors.value.length > 0)
112
+ const hasWarning = computed(() => warnings.value.length > 0)
113
+ const hasSuccess = computed(() => successes.value.length > 0 && props.showSuccessMessages)
86
114
 
87
- function validateFieldSet(value: string | null, rules: Rule[]) {
88
- rules.forEach((rule) => {
89
- const { error, success } = rule(value)
90
- if (error) errors.value.push(error)
91
- if (success && success !== 'Le champ est valide.') successes.value.push(success)
92
- })
93
- }
115
+ const validationIcon = computed(() => {
116
+ if (hasError.value) return mdiAlertCircle
117
+ if (hasWarning.value) return mdiAlert
118
+ if (hasSuccess.value) return mdiCheckCircle
119
+ return undefined
120
+ })
94
121
 
95
- function validateFields(onBlur = false): void {
96
- errors.value = []
97
- successes.value = []
122
+ const validationColor = computed(() => {
123
+ if (hasError.value) return 'error'
124
+ if (hasWarning.value) return 'warning'
125
+ if (hasSuccess.value) return 'success'
126
+ return 'rgb(0 0 0 / 100%)'
127
+ })
98
128
 
99
- const shouldValidate = onBlur || !props.isValidateOnBlur
129
+ // Synchronisation des messages externes
130
+ watch(() => props.errorMessages, (newVal) => {
131
+ if (newVal) {
132
+ errors.value = newVal
133
+ }
134
+ }, { immediate: true })
100
135
 
101
- if (!shouldValidate) return
136
+ watch(() => props.warningMessages, (newVal) => {
137
+ if (newVal) {
138
+ warnings.value = newVal
139
+ }
140
+ }, { immediate: true })
102
141
 
103
- validateFieldSet(password.value, rules.value)
104
- }
142
+ watch(() => props.successMessages, (newVal) => {
143
+ if (newVal) {
144
+ successes.value = newVal
145
+ }
146
+ }, { immediate: true })
105
147
 
106
- function emitChangeEvent(value: string): void {
107
- emit('update:modelValue', value)
108
- validateFields()
109
- }
148
+ watch(() => password.value, () => {
149
+ validateField(password.value, [...defaultRules.value, ...(props.customRules || [])], props.customWarningRules || [], props.customSuccessRules || [])
150
+ emit('update:modelValue', password.value)
151
+ })
110
152
 
111
153
  function handleKeydown(event: KeyboardEvent): void {
112
154
  if (event.key === 'Enter') {
113
- emit('submit')
155
+ validateOnSubmit()
114
156
  }
115
157
  }
116
158
 
117
- function validateOnSubmit() {
118
- isValidating.value = true
119
- validateFields(true)
120
- return errors.value.length === 0
159
+ const validateOnSubmit = () => {
160
+ validateField(password.value, [...defaultRules.value, ...(props.customRules || [])], props.customWarningRules || [], props.customSuccessRules || [])
161
+ const isValid = errors.value.length === 0
162
+ if (isValid) {
163
+ emit('submit')
164
+ }
165
+ return isValid
121
166
  }
122
167
 
123
168
  defineExpose({
169
+ showEyeIcon,
170
+ errors,
171
+ warnings,
172
+ successes,
173
+ hasError,
174
+ hasWarning,
175
+ hasSuccess,
124
176
  validateOnSubmit,
125
177
  })
126
178
  </script>
127
179
 
128
180
  <template>
129
- <VTextField
181
+ <SyTextField
130
182
  v-model="password"
131
- :class="{
132
- 'v-messages__message--success': successes.length > 0
133
- }"
183
+ v-bind="options"
184
+ :variant-style="props.variantStyle"
185
+ :color="props.color"
186
+ :label="props.label"
187
+ :required="props.required"
134
188
  :error-messages="errors"
135
- :messages="successes"
189
+ :warning-messages="warnings"
190
+ :success-messages="successes"
191
+ :is-read-only="props.isReadOnly"
192
+ :is-disabled="props.isDisabled"
193
+ :placeholder="props.placeholder"
136
194
  :type="showEyeIcon ? 'text' : 'password'"
137
- :variant="outlined ? 'outlined' : 'underlined'"
195
+ :display-asterisk="props.displayAsterisk"
196
+ :rules="[...defaultRules, ...props.customRules]"
138
197
  class="vd-password"
139
- color="primary"
140
- title="password"
141
- validate-on="blur lazy"
142
- @blur="validateFields(true)"
198
+ :validate-on="props.isValidateOnBlur ? 'blur lazy' : 'lazy'"
199
+ @blur="props.isValidateOnBlur ? validateField(password, [...defaultRules, ...(props.customRules || [])], props.customWarningRules || [], props.customSuccessRules || []) : () => {}"
143
200
  @keydown="handleKeydown"
144
- @update:model-value="emitChangeEvent"
145
201
  >
146
202
  <template #append-inner>
147
- <VBtn
148
- :aria-label="btnLabel"
149
- class="mx-auto"
203
+ <div
204
+ class="d-flex align-center"
150
205
  v-bind="options.btn"
151
- @click="showEyeIcon = !showEyeIcon"
152
206
  >
153
- <VIcon v-bind="options.icon">
154
- {{ showEyeIcon ? eyeIcon : eyeOffIcon }}
155
- </VIcon>
156
- </VBtn>
207
+ <VIcon
208
+ :icon="validationIcon"
209
+ :color="validationColor"
210
+ class="mr-2"
211
+ />
212
+ <VIcon
213
+ :icon="showEyeIcon ? eyeIcon : eyeOffIcon"
214
+ color="rgb(0 0 0 / 70%)"
215
+ :aria-label="btnLabel"
216
+ role="button"
217
+ @click="showEyeIcon = !showEyeIcon"
218
+ />
219
+ </div>
157
220
  </template>
158
- </VTextField>
221
+ </SyTextField>
159
222
  </template>
160
223
 
161
224
  <style lang="scss" scoped>
162
225
  @use '@/assets/tokens';
163
226
 
164
227
  .vd-password {
165
- .v-btn--icon.v-btn--density-default {
166
- width: var(--v-btn-height);
167
- height: var(--v-btn-height);
228
+ :deep(.v-field) {
229
+ .v-field__input {
230
+ padding-right: 48px;
231
+ }
232
+ }
233
+ }
234
+
235
+ .warning-field {
236
+ :deep(.v-input__details > .v-icon),
237
+ :deep(.v-input__prepend > .v-icon),
238
+ :deep(.v-input__append > .v-icon) {
239
+ opacity: 1 !important;
168
240
  }
169
241
 
170
- :deep(.v-field.v-field--variant-underlined .v-field__append-inner) {
171
- padding-top: 0;
172
- padding-bottom: 0;
173
- display: flex;
174
- align-items: center;
242
+ :deep(.v-field) {
243
+ color: tokens.$colors-border-warning !important;
244
+
245
+ .v-field__outline {
246
+ color: tokens.$colors-border-warning !important;
247
+ }
175
248
  }
176
249
 
177
- :deep(.v-field.v-field--variant-underlined .v-field__input) {
178
- padding-top: calc(var(--v-field-input-padding-top) - 15px);
250
+ :deep(.v-messages) {
251
+ opacity: 1 !important;
252
+
253
+ .v-messages__message {
254
+ color: tokens.$colors-border-warning !important;
255
+ }
179
256
  }
180
257
  }
181
258
 
182
- .v-messages__message--success {
183
- color: tokens.$colors-border-success !important;
259
+ .error-field {
260
+ :deep(.v-input__control),
261
+ :deep(.v-messages__message) {
262
+ color: tokens.$colors-text-error !important;
263
+ }
184
264
 
185
265
  .v-field--active & {
266
+ color: tokens.$colors-border-error !important;
267
+ }
268
+ }
269
+
270
+ .success-field {
271
+ :deep(.v-input__details > .v-icon),
272
+ :deep(.v-input__prepend > .v-icon),
273
+ :deep(.v-input__append > .v-icon) {
274
+ opacity: 1 !important;
275
+ }
276
+
277
+ :deep(.v-field) {
186
278
  color: tokens.$colors-border-success !important;
279
+
280
+ .v-field__outline {
281
+ color: tokens.$colors-border-success !important;
282
+ }
283
+ }
284
+
285
+ :deep(.v-messages) {
286
+ opacity: 1 !important;
287
+
288
+ .v-messages__message {
289
+ color: tokens.$colors-border-success !important;
290
+ }
291
+ }
292
+ }
293
+
294
+ .basic-field {
295
+ :deep(.v-icon__svg) {
296
+ fill: rgb(0 0 0 / 70%);
187
297
  }
188
298
  }
189
299
  </style>
@@ -7,8 +7,12 @@ import { createVuetify } from 'vuetify'
7
7
  interface PasswordFieldVM {
8
8
  showEyeIcon: boolean
9
9
  errors: string[]
10
- isValidating: boolean
11
10
  validateOnSubmit: () => boolean
11
+ hasError: boolean
12
+ hasWarning: boolean
13
+ hasSuccess: boolean
14
+ validationIcon: string
15
+ validationIconColor: string
12
16
  }
13
17
 
14
18
  describe('PasswordField.vue', () => {
@@ -36,7 +40,7 @@ describe('PasswordField.vue', () => {
36
40
  // 2. Cast wrapper.vm as your interface
37
41
  const vm = wrapper.vm as unknown as PasswordFieldVM
38
42
 
39
- const button = wrapper.find('button')
43
+ const button = wrapper.find('[role="button"]')
40
44
  expect(vm.showEyeIcon).toBe(false) // from your interface
41
45
  await button.trigger('click')
42
46
  expect(vm.showEyeIcon).toBe(true)
@@ -62,35 +66,160 @@ describe('PasswordField.vue', () => {
62
66
  },
63
67
  props: {
64
68
  required: true,
69
+ label: 'Password',
65
70
  },
66
71
  })
67
72
  const vm = wrapper.vm as unknown as PasswordFieldVM
68
73
 
69
74
  const input = wrapper.find('input')
70
75
  await input.trigger('blur')
71
- expect(vm.errors).toContain('Le mot de passe est requis.')
76
+ expect(vm.errors).toContain('Le mot de passe est requis')
72
77
  })
73
78
 
74
- it('validates fields on submit and sets validating flag', async () => {
79
+ it('validates fields on submit', async () => {
75
80
  const wrapper = mount(PasswordField, {
76
81
  global: {
77
82
  plugins: [vuetify],
78
83
  },
79
84
  props: {
80
85
  modelValue: '',
81
- outlined: false,
86
+ variantStyle: 'underlined',
82
87
  required: true,
88
+ label: 'Password',
83
89
  },
84
90
  })
85
91
  const vm = wrapper.vm as unknown as PasswordFieldVM
86
92
 
87
93
  const result = vm.validateOnSubmit()
88
- expect(vm.isValidating).toBe(true)
89
94
  expect(result).toBe(false)
95
+ expect(vm.errors).toContain('Le mot de passe est requis')
90
96
 
91
- // This awaits the Vue microtask queue to let any reactive data settle
92
- await wrapper.vm.$nextTick()
97
+ // Test avec un mot de passe valide
98
+ await wrapper.setProps({ modelValue: 'valid-password' })
99
+ const validResult = vm.validateOnSubmit()
100
+ expect(validResult).toBe(true)
101
+ expect(wrapper.emitted().submit).toBeTruthy()
102
+ })
103
+
104
+ it('displays warning and success messages', async () => {
105
+ const wrapper = mount(PasswordField, {
106
+ global: {
107
+ plugins: [vuetify],
108
+ },
109
+ props: {
110
+ modelValue: 'test',
111
+ warningMessages: ['Attention: mot de passe court'],
112
+ successMessages: ['Mot de passe valide'],
113
+ },
114
+ })
115
+
116
+ const messages = wrapper.findAll('.v-messages__message')
117
+ expect(messages.length).toBe(1) // SyTextField affiche soit les warnings, soit les succès
118
+ expect(messages[0].text()).toBe('Attention: mot de passe court')
119
+
120
+ // Simuler la suppression du warning pour voir le message de succès
121
+ await wrapper.setProps({ warningMessages: [] })
122
+ const successMessages = wrapper.findAll('.v-messages__message')
123
+ expect(successMessages.length).toBe(1)
124
+ expect(successMessages[0].text()).toBe('Mot de passe valide')
125
+ })
126
+
127
+ it('handles custom validation rules', async () => {
128
+ const wrapper = mount(PasswordField, {
129
+ global: {
130
+ plugins: [vuetify],
131
+ },
132
+ props: {
133
+ modelValue: 'test',
134
+ customRules: [{
135
+ type: 'custom',
136
+ options: {
137
+ message: 'Le mot de passe doit contenir au moins 8 caractères',
138
+ validate: (value: string) => value.length >= 8,
139
+ },
140
+ }],
141
+ customWarningRules: [{
142
+ type: 'custom',
143
+ options: {
144
+ warningMessage: 'Le mot de passe pourrait être plus fort',
145
+ validate: (value: string) => /[A-Z]/.test(value),
146
+ },
147
+ }],
148
+ customSuccessRules: [{
149
+ type: 'custom',
150
+ options: {
151
+ successMessage: 'Mot de passe fort',
152
+ validate: (value: string) => value.length >= 12,
153
+ },
154
+ }],
155
+ },
156
+ })
157
+
158
+ const vm = wrapper.vm as unknown as PasswordFieldVM
159
+
160
+ // Forcer la validation initiale
161
+ await wrapper.find('input').trigger('blur')
162
+ expect(vm.errors).toContain('Le mot de passe doit contenir au moins 8 caractères')
163
+
164
+ // Mettons un mot de passe plus long mais sans majuscule -> warning
165
+ await wrapper.setProps({ modelValue: 'testpassword' })
166
+ await wrapper.find('input').trigger('blur')
167
+ const messages = wrapper.findAll('.v-messages__message')
168
+ expect(messages[0].text()).toBe('Le mot de passe pourrait être plus fort')
169
+
170
+ // Mettons un mot de passe fort -> succès
171
+ await wrapper.setProps({ modelValue: 'TestPassword123' })
172
+ await wrapper.find('input').trigger('blur')
173
+ const successMessages = wrapper.findAll('.v-messages__message')
174
+ expect(successMessages[0].text()).toBe('Mot de passe fort')
175
+ })
176
+
177
+ it('displays validation states based on validation rules', async () => {
178
+ const wrapper = mount(PasswordField, {
179
+ global: {
180
+ plugins: [vuetify],
181
+ },
182
+ props: {
183
+ modelValue: 'test',
184
+ customRules: [{
185
+ type: 'custom',
186
+ options: {
187
+ message: 'Le mot de passe doit contenir au moins 8 caractères',
188
+ validate: (value: string) => value.length >= 8,
189
+ },
190
+ }],
191
+ customWarningRules: [{
192
+ type: 'custom',
193
+ options: {
194
+ warningMessage: 'Le mot de passe pourrait être plus fort',
195
+ validate: (value: string) => /[A-Z]/.test(value),
196
+ },
197
+ }],
198
+ customSuccessRules: [{
199
+ type: 'custom',
200
+ options: {
201
+ successMessage: 'Mot de passe fort',
202
+ validate: (value: string) => value.length >= 12,
203
+ },
204
+ }],
205
+ },
206
+ })
207
+
208
+ // Forcer la validation initiale
209
+ await wrapper.find('input').trigger('blur')
210
+
211
+ // État d'erreur
212
+ const vm = wrapper.vm as unknown as PasswordFieldVM
213
+ expect(vm.hasError).toBe(true)
214
+
215
+ // État d'avertissement
216
+ await wrapper.setProps({ modelValue: 'testpassword' })
217
+ await wrapper.find('input').trigger('blur')
218
+ expect(vm.hasWarning).toBe(true)
93
219
 
94
- expect(vm.errors).toContain('Le mot de passe est requis.')
220
+ // État de succès
221
+ await wrapper.setProps({ modelValue: 'TestPassword123' })
222
+ await wrapper.find('input').trigger('blur')
223
+ expect(vm.hasSuccess).toBe(true)
95
224
  })
96
225
  })