@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
@@ -1,8 +1,7 @@
1
1
  <script lang="ts" setup>
2
2
  import { computed, ref, watch } from 'vue'
3
3
  import type { IconType, VariantStyle, ColorType } from './types'
4
- import { useFieldValidation } from '@/composables/rules/useFieldValidation'
5
- import type { RuleOptions } from '@/composables/rules/useFieldValidation'
4
+ import { useValidation, type ValidationRule } from '@/composables/validation/useValidation'
6
5
  import {
7
6
  mdiAlertOutline,
8
7
  mdiCheck,
@@ -19,6 +18,9 @@
19
18
  appendIcon?: IconType
20
19
  prependInnerIcon?: IconType
21
20
  appendInnerIcon?: IconType
21
+ prependTooltip?: string
22
+ appendTooltip?: string
23
+ tooltipLocation?: 'top' | 'bottom' | 'start' | 'end'
22
24
  variantStyle?: VariantStyle
23
25
  color?: ColorType
24
26
  isClearable?: boolean
@@ -70,9 +72,11 @@
70
72
  width?: string | number
71
73
  displayAsterisk?: boolean
72
74
  noIcon?: boolean
73
- customRules?: { type: string, options: RuleOptions }[]
74
- customWarningRules?: { type: string, options: RuleOptions }[]
75
+ customRules?: ValidationRule[]
76
+ customWarningRules?: ValidationRule[]
77
+ customSuccessRules?: ValidationRule[]
75
78
  showSuccessMessages?: boolean
79
+ isValidateOnBlur?: boolean
76
80
  }>(),
77
81
  {
78
82
  modelValue: undefined,
@@ -80,6 +84,9 @@
80
84
  appendIcon: undefined,
81
85
  appendInnerIcon: undefined,
82
86
  prependInnerIcon: undefined,
87
+ prependTooltip: undefined,
88
+ appendTooltip: undefined,
89
+ tooltipLocation: 'top',
83
90
  variantStyle: 'outlined',
84
91
  color: 'primary',
85
92
  label: 'custom label',
@@ -106,7 +113,7 @@
106
113
  hint: undefined,
107
114
  id: undefined,
108
115
  loading: false,
109
- maxErrors: 2,
116
+ maxErrors: undefined,
110
117
  maxWidth: undefined,
111
118
  messages: undefined,
112
119
  minWidth: undefined,
@@ -130,7 +137,9 @@
130
137
  noIcon: false,
131
138
  customRules: () => [],
132
139
  customWarningRules: () => [],
140
+ customSuccessRules: () => [],
133
141
  showSuccessMessages: true,
142
+ isValidateOnBlur: true,
134
143
  },
135
144
  )
136
145
 
@@ -143,137 +152,118 @@
143
152
  calendar: mdiCalendar,
144
153
  }
145
154
 
155
+ const emit = defineEmits([
156
+ 'update:modelValue',
157
+ 'clear',
158
+ 'prepend-icon-click',
159
+ 'append-icon-click',
160
+ ])
161
+
146
162
  const model = computed({
147
- get: () => props.modelValue,
148
- set: (value) => {
149
- emit('update:model-value', value)
163
+ get() {
164
+ return props.modelValue
165
+ },
166
+ set(value) {
167
+ emit('update:modelValue', value)
150
168
  },
151
169
  })
152
170
 
153
171
  const isBlurred = ref(false)
154
- const errors = ref<string[]>([])
155
- const warnings = ref<string[]>([])
156
- const successes = ref<string[]>([])
157
172
 
173
+ // Initialisation du composable de validation
174
+ const validation = useValidation({
175
+ customRules: props.customRules,
176
+ warningRules: props.customWarningRules,
177
+ successRules: props.customSuccessRules,
178
+ showSuccessMessages: props.showSuccessMessages,
179
+ fieldIdentifier: props.label,
180
+ })
181
+
182
+ // Synchronisation des messages externes
158
183
  watch(() => props.errorMessages, (newVal) => {
159
- errors.value = newVal || []
184
+ validation.errors.value = newVal || []
160
185
  }, { immediate: true })
161
186
 
162
187
  watch(() => props.warningMessages, (newVal) => {
163
- warnings.value = newVal || []
188
+ validation.warnings.value = newVal || []
164
189
  }, { immediate: true })
165
190
 
166
191
  watch(() => props.successMessages, (newVal) => {
167
- successes.value = newVal || []
192
+ validation.successes.value = newVal || []
168
193
  }, { immediate: true })
169
194
 
170
- type Rule = { type: string, options?: RuleOptions }
171
-
172
- const customRules = ref<Rule[]>(props.customRules || [])
173
- const customWarningRules = ref<Rule[]>(props.customWarningRules || [])
174
-
175
- const { generateRules } = useFieldValidation()
176
-
177
- const validationRules = computed(() => {
178
- const defaultRules: Rule[] = props.required
179
- ? [{
180
- type: 'required',
181
- options: {
182
- message: `Le champ ${props.label || 'ce champ'} est requis.`,
183
- fieldIdentifier: props.label,
184
- },
185
- }]
186
- : []
187
-
188
- return generateRules([...defaultRules, ...customRules.value])
189
- })
190
-
191
- const warningValidationRules = computed(() => {
192
- const rulesWithWarning = customWarningRules.value.map(rule => ({
193
- type: rule.type,
194
- options: { ...(rule.options || {}), isWarning: true },
195
- }))
196
- return generateRules(rulesWithWarning)
197
- })
195
+ // Construction des règles de validation
196
+ const defaultRules = computed<ValidationRule[]>(() => props.required
197
+ ? [{
198
+ type: 'required',
199
+ options: {
200
+ message: `Le champ ${props.label || 'ce champ'} est requis.`,
201
+ fieldIdentifier: props.label,
202
+ },
203
+ }]
204
+ : [],
205
+ )
198
206
 
199
207
  const validateField = (value: string | number | null) => {
200
- errors.value = []
201
- warnings.value = []
202
- successes.value = []
203
-
204
208
  // Si le champ est vide et non requis, on ne fait pas de validation
205
209
  if (!value && !props.required) {
210
+ validation.clearValidation()
206
211
  return true
207
212
  }
208
213
 
209
- let hasSuccess = false
210
-
211
- // Validation des règles standard
212
- validationRules.value.forEach((rule) => {
213
- const result = rule(value)
214
- if (result.error) {
215
- errors.value.push(result.error)
216
- }
217
- else if (props.showSuccessMessages && result.success && !errors.value.length && !hasSuccess) {
218
- successes.value = [result.success]
219
- hasSuccess = true
220
- }
221
- })
222
-
223
- // Si on a des erreurs, on n'affiche pas les warnings ni les succès
224
- if (errors.value.length) {
225
- warnings.value = []
226
- successes.value = []
227
- return false
228
- }
229
-
230
- // Validation des règles d'avertissement
231
- warningValidationRules.value.forEach((rule) => {
232
- const result = rule(value)
233
- if (result.warning) {
234
- warnings.value.push(result.warning)
235
- }
236
- })
237
-
238
- // Si on a des warnings, on n'affiche pas les succès
239
- if (warnings.value.length) {
240
- successes.value = []
241
- }
214
+ const result = validation.validateField(
215
+ value,
216
+ [...defaultRules.value, ...props.customRules],
217
+ props.customWarningRules,
218
+ )
242
219
 
243
- return true
220
+ return !result.hasError
244
221
  }
245
222
 
246
223
  const validateOnSubmit = () => {
247
224
  isBlurred.value = true
248
- // On s'assure que model.value n'est pas undefined
249
225
  return validateField(model.value ?? null)
250
226
  }
251
227
 
252
- const hasError = computed(() => errors.value.length > 0)
253
- const hasWarning = computed(() => warnings.value.length > 0)
254
- const hasSuccess = computed(() => successes.value.length > 0 && !hasError.value && !hasWarning.value)
255
-
256
228
  const checkErrorOnBlur = () => {
257
229
  isBlurred.value = true
258
230
  validateField(model.value ?? null)
231
+ emit('update:modelValue', model.value)
259
232
  }
260
233
 
261
234
  watch(model, (newValue) => {
262
- validateField(newValue ?? null)
235
+ if (!props.isValidateOnBlur) {
236
+ validateField(newValue ?? null)
237
+ }
263
238
  if (props.isClearable && newValue === '') {
264
239
  emit('clear')
265
240
  }
266
241
  })
267
242
 
243
+ // Computed pour l'affichage des états
244
+ const hasError = computed(() => validation.hasError.value)
245
+ const hasWarning = computed(() => validation.hasWarning.value)
246
+ const hasSuccess = computed(() => validation.hasSuccess.value)
247
+
248
+ const errors = computed(() => validation.errors.value)
249
+ const warnings = computed(() => validation.warnings.value)
250
+ const successes = computed(() => validation.successes.value)
251
+
252
+ // Computed pour les icônes
268
253
  const appendInnerIconColor = computed(() => {
269
- if (hasError.value) return 'error'
270
- if (hasWarning.value) return 'warning'
271
- if (hasSuccess.value) return 'success'
272
- return props.appendInnerIcon === 'error' || props.appendInnerIcon === 'success' || props.appendInnerIcon === 'warning'
273
- ? props.appendInnerIcon
274
- : 'black'
254
+ if (props.appendInnerIcon === 'error') return 'error'
255
+ if (props.appendInnerIcon === 'success') return 'success'
256
+ return 'rgba(0, 0, 0, 1)'
275
257
  })
276
258
 
259
+ const handlePrependIconClick = () => {
260
+ emit('prepend-icon-click')
261
+ }
262
+
263
+ const handleAppendIconClick = () => {
264
+ emit('append-icon-click')
265
+ }
266
+
277
267
  const validationIcon = computed(() => {
278
268
  if (hasError.value) return ICONS['error']
279
269
  if (hasWarning.value) return ICONS['warning']
@@ -296,14 +286,10 @@
296
286
  opacity: '1',
297
287
  }
298
288
 
299
- const emit = defineEmits(['update:model-value', 'clear', 'prepend-icon-click', 'append-icon-click'])
300
-
301
289
  defineExpose({
302
- appendInnerIconColor,
290
+ validation,
303
291
  validateOnSubmit,
304
- errors,
305
- warnings,
306
- successes,
292
+ checkErrorOnBlur,
307
293
  })
308
294
  </script>
309
295
 
@@ -312,6 +298,7 @@
312
298
  :id="props.id"
313
299
  v-model="model"
314
300
  :active="props.isActive"
301
+ :title="props.label"
315
302
  :aria-label="props.label"
316
303
  :base-color="props.baseColor"
317
304
  :bg-color="props.bgColor"
@@ -336,7 +323,7 @@
336
323
  :loading="props.loading"
337
324
  :max-errors="props.maxErrors"
338
325
  :max-width="props.maxWidth"
339
- :messages="hasWarning ? warnings : (hasSuccess && props.showSuccessMessages ? successes : [])"
326
+ :messages="hasError ? errors : (hasWarning ? warnings : (hasSuccess && props.showSuccessMessages ? successes : []))"
340
327
  :min-width="props.minWidth"
341
328
  :name="props.name"
342
329
  :no-icon="props.noIcon"
@@ -360,35 +347,70 @@
360
347
  :class="{
361
348
  'error-field': hasError,
362
349
  'warning-field': hasWarning,
363
- 'success-field': hasSuccess
350
+ 'success-field': hasSuccess,
351
+ 'basic-field': !hasError && !hasWarning && !hasSuccess
364
352
  }"
365
353
  @blur="checkErrorOnBlur"
366
354
  >
367
355
  <template
368
- v-if="props.prependIcon && !props.noIcon"
356
+ v-if="props.prependIcon || props.prependTooltip"
369
357
  #prepend
370
358
  >
371
359
  <slot name="prepend">
360
+ <template v-if="props.prependTooltip">
361
+ <VTooltip
362
+ :text="props.prependTooltip"
363
+ :location="props.tooltipLocation"
364
+ >
365
+ <template #activator="{ props: tooltipProps }">
366
+ <VIcon
367
+ v-bind="tooltipProps"
368
+ :aria-label="props.label ? `${props.label} - info` : 'Info'"
369
+ :color="appendInnerIconColor"
370
+ :icon="ICONS.info"
371
+ role="button"
372
+ />
373
+ </template>
374
+ </VTooltip>
375
+ </template>
372
376
  <VIcon
377
+ v-else-if="props.prependIcon"
373
378
  :aria-label="props.label ? `${props.label} - bouton ${props.prependIcon}` : `Bouton ${props.prependIcon}`"
374
379
  :color="appendInnerIconColor"
375
380
  :icon="ICONS[props.prependIcon]"
376
381
  role="button"
377
- @click="$emit('prepend-icon-click')"
382
+ @click="handlePrependIconClick"
378
383
  />
379
384
  </slot>
380
385
  </template>
381
386
  <template
382
- v-if="props.appendIcon && !props.noIcon"
387
+ v-if="props.appendIcon || props.appendTooltip"
383
388
  #append
384
389
  >
385
390
  <slot name="append">
391
+ <template v-if="props.appendTooltip">
392
+ <VTooltip
393
+ :text="props.appendTooltip"
394
+ :location="props.tooltipLocation"
395
+ >
396
+ <template #activator="{ props: tooltipProps }">
397
+ <VIcon
398
+ v-bind="tooltipProps"
399
+ :aria-label="props.label ? `${props.label} - info` : 'Info'"
400
+ :color="appendInnerIconColor"
401
+ :icon="ICONS.info"
402
+ role="button"
403
+ />
404
+ </template>
405
+ </VTooltip>
406
+ </template>
386
407
  <VIcon
408
+ v-else-if="props.appendIcon"
387
409
  :aria-label="props.label ? `${props.label} - bouton ${props.appendIcon}` : `Bouton ${props.appendIcon}`"
388
410
  :color="appendInnerIconColor"
389
411
  :icon="ICONS[props.appendIcon]"
390
412
  role="button"
391
- @click="$emit('append-icon-click')"
413
+ @click="handleAppendIconClick"
392
414
  />
393
415
  </slot>
394
416
  </template>
@@ -494,4 +516,10 @@
494
516
  }
495
517
  }
496
518
  }
519
+
520
+ .basic-field {
521
+ :deep(.v-icon__svg) {
522
+ fill: rgb(0 0 0 / 100%);
523
+ }
524
+ }
497
525
  </style>
@@ -1,36 +1,56 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest'
1
2
  import { mount } from '@vue/test-utils'
2
- import SyTextField from '../SyTextField.vue'
3
- import { expect, describe, it } from 'vitest'
3
+ import { createVuetify } from 'vuetify'
4
+ import * as components from 'vuetify/components'
5
+ import * as directives from 'vuetify/directives'
4
6
  import { VIcon } from 'vuetify/components'
5
- import { vuetify } from '@tests/unit/setup'
6
7
 
7
- describe('SyTextField', () => {
8
- const factory = (props = {}, slots = {}) => {
9
- return mount(SyTextField, {
10
- props,
11
- slots,
8
+ import SyTextField from '../SyTextField.vue'
9
+ import type { IconType } from '../types'
10
+
11
+ const vuetify = createVuetify({
12
+ components,
13
+ directives,
14
+ })
15
+
16
+ describe('SyTextField.vue', () => {
17
+ let wrapper: ReturnType<typeof mount<typeof SyTextField>>
18
+
19
+ beforeEach(() => {
20
+ wrapper = mount(SyTextField, {
12
21
  global: {
13
22
  plugins: [vuetify],
14
23
  },
24
+ props: {
25
+ modelValue: undefined,
26
+ required: true,
27
+ showSuccessMessages: true,
28
+ outlined: true,
29
+ },
15
30
  })
16
- }
31
+ })
17
32
 
18
33
  it('renders correctly with default props', () => {
19
- const wrapper = factory()
20
34
  expect(wrapper.exists()).toBe(true)
21
- expect(wrapper.findComponent(VIcon).exists()).toBe(false) // No icons by default
35
+ expect(wrapper.findComponent({ name: 'VIcon' }).exists()).toBe(false) // No icons by default
22
36
  })
23
37
 
24
38
  it('applies the correct variant style', () => {
25
- const wrapper = factory({ variantStyle: 'filled' })
39
+ wrapper = mount(SyTextField, {
40
+ global: { plugins: [vuetify] },
41
+ props: { variantStyle: 'filled' },
42
+ })
26
43
  const textField = wrapper.findComponent({ name: 'VTextField' })
27
44
  expect(textField.props('variant')).toBe('filled')
28
45
  })
29
46
 
30
47
  it('renders default slots correctly', () => {
31
- const wrapper = factory({}, {
32
- prepend: '<div data-testid="prepend-slot">Prepend Slot Content</div>',
33
- append: '<div data-testid="append-slot">Append Slot Content</div>',
48
+ wrapper = mount(SyTextField, {
49
+ global: { plugins: [vuetify] },
50
+ slots: {
51
+ prepend: '<div data-testid="prepend-slot">Prepend Slot Content</div>',
52
+ append: '<div data-testid="append-slot">Append Slot Content</div>',
53
+ },
34
54
  })
35
55
 
36
56
  const prependSlot = wrapper.find('.v-field--prepended')
@@ -41,9 +61,12 @@ describe('SyTextField', () => {
41
61
  })
42
62
 
43
63
  it('renders inner slots correctly', () => {
44
- const wrapper = factory({}, {
45
- 'prepend-inner': '<div data-testid="prepend-inner-slot">Prepend Inner Slot Content</div>',
46
- 'append-inner': '<div data-testid="append-inner-slot">Append Inner Slot Content</div>',
64
+ wrapper = mount(SyTextField, {
65
+ global: { plugins: [vuetify] },
66
+ slots: {
67
+ 'prepend-inner': '<div data-testid="prepend-inner-slot">Prepend Inner Slot Content</div>',
68
+ 'append-inner': '<div data-testid="append-inner-slot">Append Inner Slot Content</div>',
69
+ },
47
70
  })
48
71
 
49
72
  const prependInnerSlot = wrapper.find('[data-testid="prepend-inner-slot"]')
@@ -55,38 +78,167 @@ describe('SyTextField', () => {
55
78
  expect(appendInnerSlot.text()).toBe('Append Inner Slot Content')
56
79
  })
57
80
 
58
- it('matches snapshot', () => {
81
+ it('should update icon when validation state changes', async () => {
82
+ wrapper = mount(SyTextField, {
83
+ global: { plugins: [vuetify] },
84
+ props: { appendInnerIcon: 'success' as IconType },
85
+ })
86
+ expect(wrapper.props('appendInnerIcon')).toBe('success')
87
+ })
88
+
89
+ it('should update icon when validation state changes with warning', async () => {
90
+ wrapper = mount(SyTextField, {
91
+ global: { plugins: [vuetify] },
92
+ props: { appendInnerIcon: 'warning' as IconType },
93
+ })
94
+ expect(wrapper.props('appendInnerIcon')).toBe('warning')
95
+ })
96
+
97
+ it('should update icon when validation state changes with error', async () => {
98
+ wrapper = mount(SyTextField, {
99
+ global: { plugins: [vuetify] },
100
+ props: { appendInnerIcon: 'error' as IconType },
101
+ })
102
+ expect(wrapper.props('appendInnerIcon')).toBe('error')
103
+ })
104
+
105
+ it('should update icon when validation state changes with success', async () => {
106
+ wrapper = mount(SyTextField, {
107
+ global: { plugins: [vuetify] },
108
+ props: { appendInnerIcon: 'success' as IconType },
109
+ })
110
+ expect(wrapper.props('appendInnerIcon')).toBe('success')
111
+ })
112
+
113
+ it('emits prepend-icon-click event when prepend icon is clicked', async () => {
114
+ const wrapper = mount(SyTextField, {
115
+ global: { plugins: [vuetify] },
116
+ props: { prependIcon: 'info' as IconType },
117
+ })
118
+
119
+ await wrapper.vm.$nextTick()
120
+ const prependIcon = wrapper.findComponent(VIcon)
121
+ expect(prependIcon.exists()).toBe(true)
122
+ await prependIcon.trigger('click')
123
+ await wrapper.vm.$nextTick()
124
+ expect(wrapper.emitted('prepend-icon-click')).toBeTruthy()
125
+ })
126
+
127
+ it('emits append-icon-click event when append icon is clicked', async () => {
128
+ const wrapper = mount(SyTextField, {
129
+ global: { plugins: [vuetify] },
130
+ props: { appendIcon: 'info' as IconType },
131
+ })
132
+
133
+ await wrapper.vm.$nextTick()
134
+ const appendIcon = wrapper.findComponent(VIcon)
135
+ expect(appendIcon.exists()).toBe(true)
136
+ await appendIcon.trigger('click')
137
+ await wrapper.vm.$nextTick()
138
+ expect(wrapper.emitted('append-icon-click')).toBeTruthy()
139
+ })
140
+
141
+ it('shows validation error message', async () => {
59
142
  const wrapper = mount(SyTextField, {
143
+ global: { plugins: [vuetify] },
60
144
  props: {
61
- label: 'custom label',
62
- modelValue: '',
63
- errorMessages: ['Test error message'],
145
+ required: true,
146
+ label: 'Test Field',
64
147
  },
65
- global: {
66
- plugins: [vuetify],
148
+ })
149
+ await wrapper.find('input').trigger('blur')
150
+ await wrapper.vm.$nextTick()
151
+ expect(wrapper.find('.v-messages').text()).toContain('Le champ Test Field est requis')
152
+ })
153
+
154
+ it('validates field with custom rules', async () => {
155
+ const customRule = {
156
+ type: 'custom',
157
+ options: {
158
+ validate: (value: string) => value.length > 2,
159
+ message: 'Test error message',
67
160
  },
161
+ }
162
+
163
+ wrapper = mount(SyTextField, {
164
+ global: { plugins: [vuetify] },
165
+ props: { customRules: [customRule] },
68
166
  })
69
167
 
70
- expect(wrapper.html()).toMatchSnapshot()
71
- })
168
+ await wrapper.setProps({ modelValue: 'ab' })
169
+ await wrapper.find('input').trigger('blur')
170
+ await wrapper.vm.$nextTick()
72
171
 
73
- it('returns error color for appendInnerIcon when value is error', () => {
74
- const wrapper = factory({ appendInnerIcon: 'error' })
75
- expect(wrapper.vm.appendInnerIconColor).toBe('error')
172
+ const messages = wrapper.find('.v-messages')
173
+ expect(messages.text()).toContain('Test error message')
76
174
  })
77
175
 
78
- it('returns success color for appendInnerIcon when value is success', () => {
79
- const wrapper = factory({ appendInnerIcon: 'success' })
80
- expect(wrapper.vm.appendInnerIconColor).toBe('success')
176
+ it('validates field with custom warning rules', async () => {
177
+ const warningRule = {
178
+ type: 'custom',
179
+ options: {
180
+ validate: (value: string) => value.length <= 3,
181
+ message: 'Test warning message',
182
+ isWarning: true,
183
+ },
184
+ }
185
+
186
+ const wrapper = mount(SyTextField, {
187
+ global: { plugins: [vuetify] },
188
+ props: {
189
+ modelValue: 'test',
190
+ customWarningRules: [warningRule],
191
+ showSuccessMessages: true,
192
+ label: 'Test Field',
193
+ },
194
+ })
195
+
196
+ await wrapper.vm.$nextTick()
197
+ await wrapper.find('input').trigger('blur')
198
+ await wrapper.vm.$nextTick()
199
+
200
+ const messages = wrapper.find('.v-messages')
201
+ expect(messages.exists()).toBe(true)
202
+ expect(messages.text()).toContain('Attention : Test Field peut contenir une erreur')
81
203
  })
82
204
 
83
- it('returns default color for appendInnerIcon when value is info', () => {
84
- const wrapper = factory({ appendInnerIcon: 'info' })
85
- expect(wrapper.vm.appendInnerIconColor).toBe('black')
205
+ it('maintains input value without validation rules', async () => {
206
+ wrapper = mount(SyTextField, {
207
+ global: { plugins: [vuetify] },
208
+ })
209
+ const input = wrapper.find('input')
210
+
211
+ await input.setValue('test value')
212
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['test value'])
86
213
  })
87
214
 
88
- it('returns default color for appendInnerIcon when value is undefined', () => {
89
- const wrapper = factory({ appendInnerIcon: undefined })
90
- expect(wrapper.vm.appendInnerIconColor).toBe('black')
215
+ it('validates field immediately when isValidateOnBlur is false', async () => {
216
+ const customRule = {
217
+ type: 'custom',
218
+ options: {
219
+ validate: (value: string) => value.length > 2,
220
+ message: 'Test error message',
221
+ },
222
+ }
223
+
224
+ wrapper = mount(SyTextField, {
225
+ global: { plugins: [vuetify] },
226
+ props: {
227
+ customRules: [customRule],
228
+ isValidateOnBlur: false,
229
+ },
230
+ })
231
+
232
+ await wrapper.setProps({ modelValue: 'ab' })
233
+ await wrapper.vm.$nextTick()
234
+
235
+ const messages = wrapper.find('.v-messages')
236
+ expect(messages.text()).toContain('Test error message')
237
+
238
+ // Vérifie que l'erreur disparaît quand la valeur devient valide
239
+ await wrapper.setProps({ modelValue: 'abc' })
240
+ await wrapper.vm.$nextTick()
241
+
242
+ expect(messages.text()).not.toContain('Test error message')
91
243
  })
92
244
  })
@@ -1,3 +1,4 @@
1
1
  export type IconType = 'info' | 'success' | 'warning' | 'error' | 'close' | 'calendar' | undefined
2
2
  export type VariantStyle = 'outlined' | 'filled' | 'solo' | 'solo-inverted' | 'solo-filled' | 'underlined'
3
3
  export type ColorType = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error'
4
+ export type ValidateOnType = 'eager' | 'lazy' | 'blur' | 'input' | 'submit' | 'invalid-input' | 'blur lazy' | 'input lazy' | 'submit lazy' | 'invalid-input lazy' | 'blur eager' | 'input eager' | 'submit eager' | 'invalid-input eager' | 'lazy blur' | 'lazy input' | 'lazy submit' | 'lazy invalid-input' | 'eager blur' | 'eager input' | 'eager submit' | 'eager invalid-input' | undefined