@pyreweb/fabric 1.2.6
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.
- package/README.md +119 -0
- package/dist/fabric.cjs.js +18109 -0
- package/dist/fabric.css +2180 -0
- package/dist/fabric.esm.js +18062 -0
- package/dist/fabric.min.js +18112 -0
- package/dist/types/components/atoms/FAvatar/FAvatar.test.d.ts +1 -0
- package/dist/types/components/atoms/FBadge/FBadge.test.d.ts +1 -0
- package/dist/types/components/atoms/FButton/FButton.test.d.ts +1 -0
- package/dist/types/components/atoms/FCheckbox/FCheckbox.test.d.ts +1 -0
- package/dist/types/components/atoms/FDivider/FDivider.test.d.ts +1 -0
- package/dist/types/components/atoms/FIcon/FIcon.test.d.ts +1 -0
- package/dist/types/components/atoms/FInput/FInput.test.d.ts +1 -0
- package/dist/types/components/atoms/FLoader/FLoader.test.d.ts +1 -0
- package/dist/types/components/atoms/FRadio/FRadio.test.d.ts +1 -0
- package/dist/types/components/atoms/FTextarea/FTextarea.test.d.ts +1 -0
- package/dist/types/components/atoms/FToggle/FToggle.test.d.ts +1 -0
- package/dist/types/components/atoms/FTypography/FTypography.test.d.ts +1 -0
- package/dist/types/components/atoms/index.d.ts +13 -0
- package/dist/types/components/molecules/FAccordionItem/FAccordionItem.test.d.ts +1 -0
- package/dist/types/components/molecules/FAlert/FAlert.test.d.ts +1 -0
- package/dist/types/components/molecules/FBreadcrumb/FBreadcrumb.test.d.ts +1 -0
- package/dist/types/components/molecules/FButtonGroup/FButtonGroup.test.d.ts +1 -0
- package/dist/types/components/molecules/FCard/FCard.test.d.ts +1 -0
- package/dist/types/components/molecules/FDatePicker/FDatePicker.test.d.ts +1 -0
- package/dist/types/components/molecules/FEmptyState/FEmptyState.test.d.ts +1 -0
- package/dist/types/components/molecules/FFilePreview/FFilePreview.test.d.ts +1 -0
- package/dist/types/components/molecules/FFormField/FFormField.test.d.ts +1 -0
- package/dist/types/components/molecules/FListItem/FListItem.test.d.ts +1 -0
- package/dist/types/components/molecules/FPagination/FPagination.test.d.ts +1 -0
- package/dist/types/components/molecules/FSearchBar/FSearchBar.test.d.ts +1 -0
- package/dist/types/components/molecules/FSelect/FSelect.test.d.ts +1 -0
- package/dist/types/components/molecules/FStatCard/FStatCard.test.d.ts +1 -0
- package/dist/types/components/molecules/FTabs/FTabs.test.d.ts +1 -0
- package/dist/types/components/molecules/FToast/FToast.test.d.ts +1 -0
- package/dist/types/components/molecules/index.d.ts +18 -0
- package/dist/types/components/organisms/FActivityFeed/FActivityFeed.test.d.ts +1 -0
- package/dist/types/components/organisms/FDataTable/FDataTable.test.d.ts +1 -0
- package/dist/types/components/organisms/FDrawer/FDrawer.test.d.ts +1 -0
- package/dist/types/components/organisms/FFileUpload/FFileUpload.test.d.ts +1 -0
- package/dist/types/components/organisms/FFilterSidebar/FFilterSidebar.test.d.ts +1 -0
- package/dist/types/components/organisms/FForm/FForm.test.d.ts +1 -0
- package/dist/types/components/organisms/FModal/FModal.test.d.ts +1 -0
- package/dist/types/components/organisms/FNavigationSidebar/FNavigationSidebar.test.d.ts +1 -0
- package/dist/types/components/organisms/FOnboardingStepper/FOnboardingStepper.test.d.ts +1 -0
- package/dist/types/components/organisms/FOnboardingStepper/FStepperProgress.test.d.ts +1 -0
- package/dist/types/components/organisms/FPageHeader/FPageHeader.test.d.ts +1 -0
- package/dist/types/components/organisms/FProfileSection/FProfileSection.test.d.ts +1 -0
- package/dist/types/components/organisms/FToastProvider/FToastProvider.test.d.ts +1 -0
- package/dist/types/components/organisms/FUserMenu/FUserMenu.test.d.ts +1 -0
- package/dist/types/components/organisms/index.d.ts +14 -0
- package/dist/types/components/utils/FThemeProvider.test.d.ts +1 -0
- package/dist/types/components/utils/index.d.ts +2 -0
- package/dist/types/components.d.ts +602 -0
- package/dist/types/composables/index.d.ts +12 -0
- package/dist/types/composables/useDataTableState.d.ts +106 -0
- package/dist/types/composables/useDataTableState.test.d.ts +1 -0
- package/dist/types/composables/useFormValidation.d.ts +49 -0
- package/dist/types/composables/useFormValidation.test.d.ts +1 -0
- package/dist/types/composables/useSidebarState.d.ts +65 -0
- package/dist/types/composables/useSidebarState.test.d.ts +1 -0
- package/dist/types/index.d.ts +19 -0
- package/dist/types/types.d.ts +529 -0
- package/package.json +100 -0
- package/src/components/atoms/FAvatar/FAvatar.stories.js +100 -0
- package/src/components/atoms/FAvatar/FAvatar.test.ts +95 -0
- package/src/components/atoms/FAvatar/FAvatar.vue +190 -0
- package/src/components/atoms/FBadge/FBadge.stories.js +129 -0
- package/src/components/atoms/FBadge/FBadge.test.ts +93 -0
- package/src/components/atoms/FBadge/FBadge.vue +103 -0
- package/src/components/atoms/FButton/FButton.stories.js +122 -0
- package/src/components/atoms/FButton/FButton.test.ts +98 -0
- package/src/components/atoms/FButton/FButton.vue +147 -0
- package/src/components/atoms/FCheckbox/FCheckbox.stories.js +96 -0
- package/src/components/atoms/FCheckbox/FCheckbox.test.ts +64 -0
- package/src/components/atoms/FCheckbox/FCheckbox.vue +76 -0
- package/src/components/atoms/FDivider/FDivider.stories.js +104 -0
- package/src/components/atoms/FDivider/FDivider.test.ts +80 -0
- package/src/components/atoms/FDivider/FDivider.vue +117 -0
- package/src/components/atoms/FIcon/FIcon.stories.js +189 -0
- package/src/components/atoms/FIcon/FIcon.test.ts +99 -0
- package/src/components/atoms/FIcon/FIcon.vue +192 -0
- package/src/components/atoms/FInput/FInput.stories.js +119 -0
- package/src/components/atoms/FInput/FInput.test.ts +79 -0
- package/src/components/atoms/FInput/FInput.vue +88 -0
- package/src/components/atoms/FLoader/FLoader.stories.js +109 -0
- package/src/components/atoms/FLoader/FLoader.test.ts +66 -0
- package/src/components/atoms/FLoader/FLoader.vue +97 -0
- package/src/components/atoms/FRadio/FRadio.stories.js +105 -0
- package/src/components/atoms/FRadio/FRadio.test.ts +75 -0
- package/src/components/atoms/FRadio/FRadio.vue +119 -0
- package/src/components/atoms/FTextarea/FTextarea.stories.js +126 -0
- package/src/components/atoms/FTextarea/FTextarea.test.ts +94 -0
- package/src/components/atoms/FTextarea/FTextarea.vue +156 -0
- package/src/components/atoms/FToggle/FToggle.stories.js +108 -0
- package/src/components/atoms/FToggle/FToggle.test.ts +96 -0
- package/src/components/atoms/FToggle/FToggle.vue +123 -0
- package/src/components/atoms/FTypography/FTypography.stories.js +127 -0
- package/src/components/atoms/FTypography/FTypography.test.ts +93 -0
- package/src/components/atoms/FTypography/FTypography.vue +78 -0
- package/src/components/atoms/index.ts +27 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.stories.js +71 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.test.ts +61 -0
- package/src/components/molecules/FAccordionItem/FAccordionItem.vue +105 -0
- package/src/components/molecules/FAlert/FAlert.stories.js +87 -0
- package/src/components/molecules/FAlert/FAlert.test.ts +59 -0
- package/src/components/molecules/FAlert/FAlert.vue +108 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.stories.js +90 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.test.ts +76 -0
- package/src/components/molecules/FBreadcrumb/FBreadcrumb.vue +117 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.stories.js +82 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.test.ts +44 -0
- package/src/components/molecules/FButtonGroup/FButtonGroup.vue +31 -0
- package/src/components/molecules/FCard/FCard.stories.js +136 -0
- package/src/components/molecules/FCard/FCard.test.ts +87 -0
- package/src/components/molecules/FCard/FCard.vue +75 -0
- package/src/components/molecules/FDatePicker/FDatePicker.stories.js +305 -0
- package/src/components/molecules/FDatePicker/FDatePicker.test.ts +282 -0
- package/src/components/molecules/FDatePicker/FDatePicker.vue +750 -0
- package/src/components/molecules/FEmptyState/FEmptyState.stories.js +98 -0
- package/src/components/molecules/FEmptyState/FEmptyState.test.ts +82 -0
- package/src/components/molecules/FEmptyState/FEmptyState.vue +89 -0
- package/src/components/molecules/FFilePreview/FFilePreview.stories.js +130 -0
- package/src/components/molecules/FFilePreview/FFilePreview.test.ts +70 -0
- package/src/components/molecules/FFilePreview/FFilePreview.vue +125 -0
- package/src/components/molecules/FFormField/FFormField.stories.js +149 -0
- package/src/components/molecules/FFormField/FFormField.test.ts +85 -0
- package/src/components/molecules/FFormField/FFormField.vue +107 -0
- package/src/components/molecules/FListItem/FListItem.stories.js +158 -0
- package/src/components/molecules/FListItem/FListItem.test.ts +93 -0
- package/src/components/molecules/FListItem/FListItem.vue +113 -0
- package/src/components/molecules/FPagination/FPagination.stories.js +132 -0
- package/src/components/molecules/FPagination/FPagination.test.ts +79 -0
- package/src/components/molecules/FPagination/FPagination.vue +206 -0
- package/src/components/molecules/FSearchBar/FSearchBar.stories.js +129 -0
- package/src/components/molecules/FSearchBar/FSearchBar.test.ts +81 -0
- package/src/components/molecules/FSearchBar/FSearchBar.vue +180 -0
- package/src/components/molecules/FSelect/FSelect.stories.js +333 -0
- package/src/components/molecules/FSelect/FSelect.test.ts +478 -0
- package/src/components/molecules/FSelect/FSelect.vue +551 -0
- package/src/components/molecules/FStatCard/FStatCard.stories.js +144 -0
- package/src/components/molecules/FStatCard/FStatCard.test.ts +78 -0
- package/src/components/molecules/FStatCard/FStatCard.vue +106 -0
- package/src/components/molecules/FTabs/FTab.vue +63 -0
- package/src/components/molecules/FTabs/FTabs.stories.js +277 -0
- package/src/components/molecules/FTabs/FTabs.test.ts +264 -0
- package/src/components/molecules/FTabs/FTabs.vue +273 -0
- package/src/components/molecules/FToast/FToast.stories.js +150 -0
- package/src/components/molecules/FToast/FToast.test.ts +157 -0
- package/src/components/molecules/FToast/FToast.vue +283 -0
- package/src/components/molecules/index.ts +37 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.stories.js +217 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.test.ts +134 -0
- package/src/components/organisms/FActivityFeed/FActivityFeed.vue +589 -0
- package/src/components/organisms/FDataTable/FDataTable.stories.js +370 -0
- package/src/components/organisms/FDataTable/FDataTable.test.ts +248 -0
- package/src/components/organisms/FDataTable/FDataTable.vue +808 -0
- package/src/components/organisms/FDrawer/FDrawer.stories.js +296 -0
- package/src/components/organisms/FDrawer/FDrawer.test.ts +142 -0
- package/src/components/organisms/FDrawer/FDrawer.vue +303 -0
- package/src/components/organisms/FFileUpload/FFileUpload.stories.js +162 -0
- package/src/components/organisms/FFileUpload/FFileUpload.test.ts +103 -0
- package/src/components/organisms/FFileUpload/FFileUpload.vue +616 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.stories.js +161 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.test.ts +92 -0
- package/src/components/organisms/FFilterSidebar/FFilterSidebar.vue +458 -0
- package/src/components/organisms/FForm/FForm.stories.js +270 -0
- package/src/components/organisms/FForm/FForm.test.ts +63 -0
- package/src/components/organisms/FForm/FForm.vue +19 -0
- package/src/components/organisms/FModal/FModal.stories.js +227 -0
- package/src/components/organisms/FModal/FModal.test.ts +181 -0
- package/src/components/organisms/FModal/FModal.vue +319 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.stories.js +176 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.test.ts +95 -0
- package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.vue +577 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.stories.js +197 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.test.ts +114 -0
- package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.vue +212 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.stories.js +122 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.test.ts +130 -0
- package/src/components/organisms/FOnboardingStepper/FStepperProgress.vue +146 -0
- package/src/components/organisms/FPageHeader/FPageHeader.stories.js +142 -0
- package/src/components/organisms/FPageHeader/FPageHeader.test.ts +83 -0
- package/src/components/organisms/FPageHeader/FPageHeader.vue +241 -0
- package/src/components/organisms/FProfileSection/FProfileSection.stories.js +190 -0
- package/src/components/organisms/FProfileSection/FProfileSection.test.ts +85 -0
- package/src/components/organisms/FProfileSection/FProfileSection.vue +562 -0
- package/src/components/organisms/FToastProvider/FToastProvider.stories.js +290 -0
- package/src/components/organisms/FToastProvider/FToastProvider.test.ts +215 -0
- package/src/components/organisms/FToastProvider/FToastProvider.vue +214 -0
- package/src/components/organisms/FUserMenu/FUserMenu.stories.js +170 -0
- package/src/components/organisms/FUserMenu/FUserMenu.test.ts +102 -0
- package/src/components/organisms/FUserMenu/FUserMenu.vue +407 -0
- package/src/components/organisms/index.ts +29 -0
- package/src/components/utils/FThemeProvider.stories.js +236 -0
- package/src/components/utils/FThemeProvider.test.ts +244 -0
- package/src/components/utils/FThemeProvider.vue +191 -0
- package/src/components/utils/index.ts +3 -0
- package/src/components.d.ts +602 -0
- package/src/composables/README.md +233 -0
- package/src/composables/index.ts +25 -0
- package/src/composables/useDataTableState.test.ts +378 -0
- package/src/composables/useDataTableState.ts +361 -0
- package/src/composables/useFormValidation.test.ts +198 -0
- package/src/composables/useFormValidation.ts +178 -0
- package/src/composables/useSidebarState.test.ts +307 -0
- package/src/composables/useSidebarState.ts +201 -0
- package/src/env.d.ts +14 -0
- package/src/index.ts +167 -0
- package/src/styles/tailwind.css +173 -0
- package/src/types.ts +740 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Composables Fabric
|
|
2
|
+
|
|
3
|
+
Les composables Fabric exposent la logique métier des composants "organismes" sous forme de fonctions réutilisables basées sur l'API de Composition de Vue.
|
|
4
|
+
|
|
5
|
+
## Vue d'ensemble
|
|
6
|
+
|
|
7
|
+
Les composables permettent de:
|
|
8
|
+
- **Séparer les préoccupations**: Isoler la logique d'état de la logique de présentation
|
|
9
|
+
- **Améliorer la réutilisabilité**: Réutiliser la logique dans des composants personnalisés
|
|
10
|
+
- **Faciliter les tests**: Tester la logique métier de manière isolée
|
|
11
|
+
|
|
12
|
+
## Composables disponibles
|
|
13
|
+
|
|
14
|
+
### `useDataTableState`
|
|
15
|
+
|
|
16
|
+
Gère l'état et la logique d'un tableau de données avec recherche, tri, pagination et sélection.
|
|
17
|
+
|
|
18
|
+
#### Exemple d'utilisation
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { useDataTableState } from '@pyreweb/fabric';
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
name: 'MyCustomTable',
|
|
25
|
+
setup(props, { emit }) {
|
|
26
|
+
const tableState = useDataTableState(
|
|
27
|
+
{
|
|
28
|
+
data: props.data,
|
|
29
|
+
columns: props.columns,
|
|
30
|
+
perPage: 10,
|
|
31
|
+
paginated: true,
|
|
32
|
+
defaultSortKey: 'name'
|
|
33
|
+
},
|
|
34
|
+
emit
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
...tableState
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
#### Options
|
|
45
|
+
|
|
46
|
+
- `data` (Array, required): Tableau de données à afficher
|
|
47
|
+
- `columns` (Array, required): Définitions des colonnes
|
|
48
|
+
- `rowKey` (String, default: 'id'): Propriété unique dans les objets de données
|
|
49
|
+
- `initialPage` (Number, default: 1): Numéro de page initial
|
|
50
|
+
- `perPage` (Number, default: 10): Éléments par page
|
|
51
|
+
- `paginated` (Boolean, default: true): Activer la pagination
|
|
52
|
+
- `virtual` (Boolean, default: false): Activer la virtualisation (désactive la pagination)
|
|
53
|
+
- `defaultSortKey` (String, default: null): Clé de tri par défaut
|
|
54
|
+
- `defaultSortDirection` (String, default: 'asc'): Direction de tri par défaut ('asc' | 'desc')
|
|
55
|
+
- `serverMode` (Boolean, default: false): Mode serveur (récupération externe des données)
|
|
56
|
+
- `totalItems` (Number, default: null): Total d'éléments pour la pagination côté serveur
|
|
57
|
+
- `selected` (Array, default: []): Clés de lignes initialement sélectionnées
|
|
58
|
+
|
|
59
|
+
#### Valeurs retournées
|
|
60
|
+
|
|
61
|
+
**État:**
|
|
62
|
+
- `searchQuery`: Query de recherche (Ref<string>)
|
|
63
|
+
- `sortKey`: Clé de tri actuelle (Ref<string | null>)
|
|
64
|
+
- `sortDirection`: Direction de tri (Ref<'asc' | 'desc'>)
|
|
65
|
+
- `internalPage`: Page actuelle (Ref<number>)
|
|
66
|
+
- `selectedKeys`: Clés des lignes sélectionnées (Ref<any[]>)
|
|
67
|
+
- `selectedItems`: Items sélectionnés (ComputedRef<any[]>)
|
|
68
|
+
- `isAllSelected`: Toutes les lignes sont-elles sélectionnées (ComputedRef<boolean>)
|
|
69
|
+
|
|
70
|
+
**Données traitées:**
|
|
71
|
+
- `filteredData`: Données filtrées (ComputedRef<any[]>)
|
|
72
|
+
- `sortedData`: Données triées (ComputedRef<any[]>)
|
|
73
|
+
- `processedData`: Données traitées (ComputedRef<any[]>)
|
|
74
|
+
- `paginatedData`: Données paginées (ComputedRef<any[]>)
|
|
75
|
+
- `totalPages`: Nombre total de pages (ComputedRef<number>)
|
|
76
|
+
- `paginationInfo`: Texte d'info de pagination (ComputedRef<string>)
|
|
77
|
+
|
|
78
|
+
**Méthodes:**
|
|
79
|
+
- `getCellValue(row, key)`: Obtenir la valeur d'une cellule
|
|
80
|
+
- `getRowKey(row, index?)`: Obtenir la clé d'une ligne
|
|
81
|
+
- `handleSort(key)`: Gérer le tri
|
|
82
|
+
- `isRowSelected(row)`: Vérifier si une ligne est sélectionnée
|
|
83
|
+
- `handleRowSelect(row, checked)`: Gérer la sélection d'une ligne
|
|
84
|
+
- `handleSelectAll(checked)`: Sélectionner/désélectionner toutes les lignes
|
|
85
|
+
- `clearSelection()`: Effacer la sélection
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### `useSidebarState`
|
|
90
|
+
|
|
91
|
+
Gère l'état et la logique d'une barre latérale de navigation avec sous-menus.
|
|
92
|
+
|
|
93
|
+
#### Exemple d'utilisation
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { useSidebarState } from '@pyreweb/fabric';
|
|
97
|
+
|
|
98
|
+
export default {
|
|
99
|
+
name: 'MyCustomSidebar',
|
|
100
|
+
setup(props, { emit }) {
|
|
101
|
+
const sidebarState = useSidebarState(
|
|
102
|
+
{
|
|
103
|
+
items: props.items,
|
|
104
|
+
activeRoute: '/dashboard',
|
|
105
|
+
initialCollapsed: false
|
|
106
|
+
},
|
|
107
|
+
emit
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Initialiser les sous-menus ouverts basés sur la route active
|
|
111
|
+
sidebarState.initializeOpenSubmenus();
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...sidebarState
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Options
|
|
121
|
+
|
|
122
|
+
- `items` (Array, required): Configuration des éléments de navigation
|
|
123
|
+
- `initialCollapsed` (Boolean, default: false): État initial réduit
|
|
124
|
+
- `activeRoute` (String, default: ''): Chemin de route actif actuel
|
|
125
|
+
|
|
126
|
+
#### Valeurs retournées
|
|
127
|
+
|
|
128
|
+
**État:**
|
|
129
|
+
- `collapsed`: État réduit de la sidebar (Ref<boolean>)
|
|
130
|
+
- `openSubmenus`: Liste des sous-menus ouverts (Ref<string[]>)
|
|
131
|
+
- `navigationItems`: Éléments de navigation filtrés (ComputedRef<NavigationItem[]>)
|
|
132
|
+
|
|
133
|
+
**Méthodes:**
|
|
134
|
+
- `toggleCollapsed()`: Basculer l'état réduit
|
|
135
|
+
- `isSubmenuOpen(item)`: Vérifier si un sous-menu est ouvert
|
|
136
|
+
- `toggleSubmenu(item)`: Basculer l'état d'un sous-menu
|
|
137
|
+
- `isItemActive(item)`: Vérifier si un élément est actif
|
|
138
|
+
- `hasActiveChild(item)`: Vérifier si un élément a un enfant actif
|
|
139
|
+
- `initializeOpenSubmenus()`: Initialiser les sous-menus ouverts basés sur la route active
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### `useFormValidation`
|
|
144
|
+
|
|
145
|
+
Gère la validation et la soumission de formulaires avec gestion d'état.
|
|
146
|
+
|
|
147
|
+
#### Exemple d'utilisation
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { useFormValidation } from '@pyreweb/fabric';
|
|
151
|
+
|
|
152
|
+
export default {
|
|
153
|
+
name: 'MyCustomForm',
|
|
154
|
+
setup(props, { emit }) {
|
|
155
|
+
const formState = useFormValidation(
|
|
156
|
+
{
|
|
157
|
+
initialData: {
|
|
158
|
+
name: '',
|
|
159
|
+
email: ''
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
emit
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const onSubmit = async (event) => {
|
|
166
|
+
await formState.handleSubmit(event, async (data) => {
|
|
167
|
+
// Validation personnalisée
|
|
168
|
+
if (!data.email.includes('@')) {
|
|
169
|
+
formState.setFieldError('email', 'Email invalide');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Soumettre au serveur
|
|
174
|
+
await api.submitForm(data);
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
...formState,
|
|
180
|
+
onSubmit
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Options
|
|
187
|
+
|
|
188
|
+
- `initialData` (Object, default: {}): Données initiales du formulaire
|
|
189
|
+
|
|
190
|
+
#### Valeurs retournées
|
|
191
|
+
|
|
192
|
+
**État:**
|
|
193
|
+
- `formData`: Données du formulaire (Ref<Record<string, any>>)
|
|
194
|
+
- `errors`: Erreurs de validation (Ref<Record<string, string>>)
|
|
195
|
+
- `isValid`: Le formulaire est-il valide (Ref<boolean>)
|
|
196
|
+
- `isSubmitting`: Le formulaire est-il en cours de soumission (Ref<boolean>)
|
|
197
|
+
|
|
198
|
+
**Méthodes:**
|
|
199
|
+
- `handleSubmit(event, callback?)`: Gérer la soumission du formulaire
|
|
200
|
+
- `setFieldValue(field, value)`: Définir la valeur d'un champ
|
|
201
|
+
- `setFieldError(field, error)`: Définir l'erreur d'un champ
|
|
202
|
+
- `clearFieldError(field)`: Effacer l'erreur d'un champ
|
|
203
|
+
- `clearErrors()`: Effacer toutes les erreurs
|
|
204
|
+
- `reset()`: Réinitialiser le formulaire à l'état initial
|
|
205
|
+
- `validate()`: Valider le formulaire (peut être étendu avec une logique personnalisée)
|
|
206
|
+
|
|
207
|
+
## Compatibilité
|
|
208
|
+
|
|
209
|
+
Les composables sont compatibles avec:
|
|
210
|
+
- Vue 2.7 (avec API de Composition)
|
|
211
|
+
- Vue 3.x
|
|
212
|
+
|
|
213
|
+
## Tests
|
|
214
|
+
|
|
215
|
+
Tous les composables sont testés de manière exhaustive avec 73 tests unitaires:
|
|
216
|
+
- `useDataTableState`: 33 tests
|
|
217
|
+
- `useSidebarState`: 23 tests
|
|
218
|
+
- `useFormValidation`: 17 tests
|
|
219
|
+
|
|
220
|
+
Pour exécuter les tests:
|
|
221
|
+
```bash
|
|
222
|
+
npm test -- src/composables
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Notes d'implémentation
|
|
226
|
+
|
|
227
|
+
Les composables sont des fonctions pures qui:
|
|
228
|
+
1. Acceptent des options et une fonction `emit` comme paramètres
|
|
229
|
+
2. Retournent un objet contenant l'état réactif et les méthodes
|
|
230
|
+
3. Utilisent l'API de Composition de Vue (`ref`, `computed`, `watch`)
|
|
231
|
+
4. Sont complètement typés avec TypeScript
|
|
232
|
+
|
|
233
|
+
Les composants existants de la librairie (FForm, FDataTable, FNavigationSidebar) continuent d'utiliser l'API Options pour la compatibilité avec le build toolchain.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composables for Fabric component library
|
|
3
|
+
*
|
|
4
|
+
* These composables extract the core logic from organism components
|
|
5
|
+
* and make it reusable across applications and custom components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { useDataTableState } from './useDataTableState';
|
|
9
|
+
export type {
|
|
10
|
+
DataTableStateOptions,
|
|
11
|
+
DataTableState
|
|
12
|
+
} from './useDataTableState';
|
|
13
|
+
|
|
14
|
+
export { useSidebarState } from './useSidebarState';
|
|
15
|
+
export type {
|
|
16
|
+
NavigationItem,
|
|
17
|
+
SidebarStateOptions,
|
|
18
|
+
SidebarState
|
|
19
|
+
} from './useSidebarState';
|
|
20
|
+
|
|
21
|
+
export { useFormValidation } from './useFormValidation';
|
|
22
|
+
export type {
|
|
23
|
+
FormValidationOptions,
|
|
24
|
+
FormValidationState
|
|
25
|
+
} from './useFormValidation';
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { useDataTableState } from './useDataTableState';
|
|
3
|
+
|
|
4
|
+
describe('useDataTableState', () => {
|
|
5
|
+
const columns = [
|
|
6
|
+
{ key: 'name', label: 'Name' },
|
|
7
|
+
{ key: 'email', label: 'Email' },
|
|
8
|
+
{ key: 'role', label: 'Role' }
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const data = [
|
|
12
|
+
{ id: 1, name: 'Alice', email: 'alice@test.com', role: 'Admin' },
|
|
13
|
+
{ id: 2, name: 'Bob', email: 'bob@test.com', role: 'User' },
|
|
14
|
+
{ id: 3, name: 'Charlie', email: 'charlie@test.com', role: 'User' },
|
|
15
|
+
{ id: 4, name: 'David', email: 'david@test.com', role: 'Manager' }
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
it('initializes with default state', () => {
|
|
19
|
+
const emit = vi.fn();
|
|
20
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
21
|
+
|
|
22
|
+
expect(state.searchQuery.value).toBe('');
|
|
23
|
+
expect(state.sortKey.value).toBe(null);
|
|
24
|
+
expect(state.sortDirection.value).toBe('asc');
|
|
25
|
+
expect(state.internalPage.value).toBe(1);
|
|
26
|
+
expect(state.selectedKeys.value).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('initializes with custom options', () => {
|
|
30
|
+
const emit = vi.fn();
|
|
31
|
+
const state = useDataTableState(
|
|
32
|
+
{
|
|
33
|
+
data,
|
|
34
|
+
columns,
|
|
35
|
+
initialPage: 2,
|
|
36
|
+
defaultSortKey: 'name',
|
|
37
|
+
defaultSortDirection: 'desc',
|
|
38
|
+
selected: [1, 2]
|
|
39
|
+
},
|
|
40
|
+
emit
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(state.internalPage.value).toBe(2);
|
|
44
|
+
expect(state.sortKey.value).toBe('name');
|
|
45
|
+
expect(state.sortDirection.value).toBe('desc');
|
|
46
|
+
expect(state.selectedKeys.value).toEqual([1, 2]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('getCellValue', () => {
|
|
50
|
+
it('gets direct property value', () => {
|
|
51
|
+
const emit = vi.fn();
|
|
52
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
53
|
+
const value = state.getCellValue(data[0], 'name');
|
|
54
|
+
expect(value).toBe('Alice');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('gets nested property value', () => {
|
|
58
|
+
const emit = vi.fn();
|
|
59
|
+
const nestedData = [{ user: { profile: { name: 'Test' } } }];
|
|
60
|
+
const state = useDataTableState({ data: nestedData, columns }, emit);
|
|
61
|
+
const value = state.getCellValue(nestedData[0], 'user.profile.name');
|
|
62
|
+
expect(value).toBe('Test');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('getRowKey', () => {
|
|
67
|
+
it('gets row key using default rowKey prop', () => {
|
|
68
|
+
const emit = vi.fn();
|
|
69
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
70
|
+
const key = state.getRowKey(data[0]);
|
|
71
|
+
expect(key).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('uses index when row key is missing', () => {
|
|
75
|
+
const emit = vi.fn();
|
|
76
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
77
|
+
const key = state.getRowKey({}, 5);
|
|
78
|
+
expect(key).toBe(5);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('uses custom rowKey', () => {
|
|
82
|
+
const emit = vi.fn();
|
|
83
|
+
const customData = [{ customId: 'abc', name: 'Test' }];
|
|
84
|
+
const state = useDataTableState(
|
|
85
|
+
{ data: customData, columns, rowKey: 'customId' },
|
|
86
|
+
emit
|
|
87
|
+
);
|
|
88
|
+
const key = state.getRowKey(customData[0]);
|
|
89
|
+
expect(key).toBe('abc');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('filtering', () => {
|
|
94
|
+
it('filters data based on search query', () => {
|
|
95
|
+
const emit = vi.fn();
|
|
96
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
97
|
+
|
|
98
|
+
expect(state.filteredData.value.length).toBe(4);
|
|
99
|
+
|
|
100
|
+
state.searchQuery.value = 'alice';
|
|
101
|
+
expect(state.filteredData.value.length).toBe(1);
|
|
102
|
+
expect(state.filteredData.value[0].name).toBe('Alice');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('filters across multiple columns', () => {
|
|
106
|
+
const emit = vi.fn();
|
|
107
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
108
|
+
|
|
109
|
+
state.searchQuery.value = 'test.com';
|
|
110
|
+
expect(state.filteredData.value.length).toBe(4);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('is case insensitive', () => {
|
|
114
|
+
const emit = vi.fn();
|
|
115
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
116
|
+
|
|
117
|
+
state.searchQuery.value = 'ALICE';
|
|
118
|
+
expect(state.filteredData.value.length).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('does not filter in server mode', () => {
|
|
122
|
+
const emit = vi.fn();
|
|
123
|
+
const state = useDataTableState(
|
|
124
|
+
{ data, columns, serverMode: true },
|
|
125
|
+
emit
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
state.searchQuery.value = 'alice';
|
|
129
|
+
expect(state.filteredData.value.length).toBe(4);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('resets to first page when search changes', async () => {
|
|
133
|
+
const emit = vi.fn();
|
|
134
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
135
|
+
|
|
136
|
+
state.internalPage.value = 2;
|
|
137
|
+
state.searchQuery.value = 'alice';
|
|
138
|
+
// Wait for watcher to trigger
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
140
|
+
expect(state.internalPage.value).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('sorting', () => {
|
|
145
|
+
it('sorts data in ascending order', () => {
|
|
146
|
+
const emit = vi.fn();
|
|
147
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
148
|
+
|
|
149
|
+
state.handleSort('name');
|
|
150
|
+
expect(state.sortedData.value[0].name).toBe('Alice');
|
|
151
|
+
expect(state.sortedData.value[3].name).toBe('David');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('sorts data in descending order', () => {
|
|
155
|
+
const emit = vi.fn();
|
|
156
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
157
|
+
|
|
158
|
+
state.handleSort('name');
|
|
159
|
+
state.handleSort('name');
|
|
160
|
+
expect(state.sortedData.value[0].name).toBe('David');
|
|
161
|
+
expect(state.sortedData.value[3].name).toBe('Alice');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('emits sort event', () => {
|
|
165
|
+
const emit = vi.fn();
|
|
166
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
167
|
+
|
|
168
|
+
state.handleSort('name');
|
|
169
|
+
expect(emit).toHaveBeenCalledWith('sort', {
|
|
170
|
+
key: 'name',
|
|
171
|
+
direction: 'asc'
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('does not sort in server mode', () => {
|
|
176
|
+
const emit = vi.fn();
|
|
177
|
+
const state = useDataTableState(
|
|
178
|
+
{ data, columns, serverMode: true },
|
|
179
|
+
emit
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
state.handleSort('name');
|
|
183
|
+
expect(state.sortedData.value).toEqual(data);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('handles null and undefined values', () => {
|
|
187
|
+
const emit = vi.fn();
|
|
188
|
+
const dataWithNulls = [
|
|
189
|
+
{ id: 1, name: 'Alice' },
|
|
190
|
+
{ id: 2, name: null },
|
|
191
|
+
{ id: 3, name: 'Bob' }
|
|
192
|
+
];
|
|
193
|
+
const state = useDataTableState({ data: dataWithNulls, columns }, emit);
|
|
194
|
+
|
|
195
|
+
state.handleSort('name');
|
|
196
|
+
// Nulls should be sorted to the end
|
|
197
|
+
expect(state.sortedData.value[2].name).toBe(null);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('pagination', () => {
|
|
202
|
+
it('calculates total pages correctly', () => {
|
|
203
|
+
const emit = vi.fn();
|
|
204
|
+
const state = useDataTableState({ data, columns, perPage: 2 }, emit);
|
|
205
|
+
|
|
206
|
+
expect(state.totalPages.value).toBe(2);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('paginates data correctly', () => {
|
|
210
|
+
const emit = vi.fn();
|
|
211
|
+
const state = useDataTableState({ data, columns, perPage: 2 }, emit);
|
|
212
|
+
|
|
213
|
+
expect(state.paginatedData.value.length).toBe(2);
|
|
214
|
+
expect(state.paginatedData.value[0].name).toBe('Alice');
|
|
215
|
+
|
|
216
|
+
state.internalPage.value = 2;
|
|
217
|
+
expect(state.paginatedData.value.length).toBe(2);
|
|
218
|
+
expect(state.paginatedData.value[0].name).toBe('Charlie');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('generates pagination info text', () => {
|
|
222
|
+
const emit = vi.fn();
|
|
223
|
+
const state = useDataTableState({ data, columns, perPage: 2 }, emit);
|
|
224
|
+
|
|
225
|
+
expect(state.paginationInfo.value).toBe('1 - 2 sur 4');
|
|
226
|
+
|
|
227
|
+
state.internalPage.value = 2;
|
|
228
|
+
expect(state.paginationInfo.value).toBe('3 - 4 sur 4');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('disables pagination when virtual mode is enabled', () => {
|
|
232
|
+
const emit = vi.fn();
|
|
233
|
+
const state = useDataTableState({ data, columns, virtual: true }, emit);
|
|
234
|
+
|
|
235
|
+
expect(state.effectivePaginated.value).toBe(false);
|
|
236
|
+
expect(state.paginatedData.value.length).toBe(4);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('emits page change events', async () => {
|
|
240
|
+
const emit = vi.fn();
|
|
241
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
242
|
+
|
|
243
|
+
state.internalPage.value = 2;
|
|
244
|
+
// Wait for watcher to trigger
|
|
245
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
246
|
+
expect(emit).toHaveBeenCalledWith('update:page', 2);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('selection', () => {
|
|
251
|
+
it('selects a single row', () => {
|
|
252
|
+
const emit = vi.fn();
|
|
253
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
254
|
+
|
|
255
|
+
state.handleRowSelect(data[0], true);
|
|
256
|
+
expect(state.selectedKeys.value).toContain(1);
|
|
257
|
+
expect(emit).toHaveBeenCalledWith('select', {
|
|
258
|
+
row: data[0],
|
|
259
|
+
selected: true
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('deselects a row', () => {
|
|
264
|
+
const emit = vi.fn();
|
|
265
|
+
const state = useDataTableState({ data, columns, selected: [1] }, emit);
|
|
266
|
+
|
|
267
|
+
state.handleRowSelect(data[0], false);
|
|
268
|
+
expect(state.selectedKeys.value).not.toContain(1);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('checks if row is selected', () => {
|
|
272
|
+
const emit = vi.fn();
|
|
273
|
+
const state = useDataTableState(
|
|
274
|
+
{ data, columns, selected: [1, 2] },
|
|
275
|
+
emit
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(state.isRowSelected(data[0])).toBe(true);
|
|
279
|
+
expect(state.isRowSelected(data[2])).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('selects all rows on current page', () => {
|
|
283
|
+
const emit = vi.fn();
|
|
284
|
+
const state = useDataTableState({ data, columns, perPage: 2 }, emit);
|
|
285
|
+
|
|
286
|
+
state.handleSelectAll(true);
|
|
287
|
+
expect(state.selectedKeys.value).toContain(1);
|
|
288
|
+
expect(state.selectedKeys.value).toContain(2);
|
|
289
|
+
expect(state.selectedKeys.value).not.toContain(3);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('deselects all rows on current page', () => {
|
|
293
|
+
const emit = vi.fn();
|
|
294
|
+
const state = useDataTableState(
|
|
295
|
+
{ data, columns, perPage: 2, selected: [1, 2] },
|
|
296
|
+
emit
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
state.handleSelectAll(false);
|
|
300
|
+
expect(state.selectedKeys.value).toEqual([]);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('determines if all rows are selected', () => {
|
|
304
|
+
const emit = vi.fn();
|
|
305
|
+
const state = useDataTableState({ data, columns, perPage: 2 }, emit);
|
|
306
|
+
|
|
307
|
+
expect(state.isAllSelected.value).toBe(false);
|
|
308
|
+
|
|
309
|
+
state.handleSelectAll(true);
|
|
310
|
+
expect(state.isAllSelected.value).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('clears selection', () => {
|
|
314
|
+
const emit = vi.fn();
|
|
315
|
+
const state = useDataTableState(
|
|
316
|
+
{ data, columns, selected: [1, 2, 3] },
|
|
317
|
+
emit
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
state.clearSelection();
|
|
321
|
+
expect(state.selectedKeys.value).toEqual([]);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('emits selection change events', async () => {
|
|
325
|
+
const emit = vi.fn();
|
|
326
|
+
const state = useDataTableState({ data, columns }, emit);
|
|
327
|
+
|
|
328
|
+
state.selectedKeys.value = [1];
|
|
329
|
+
// Wait for next tick for watcher to trigger
|
|
330
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
331
|
+
expect(emit).toHaveBeenCalledWith('update:selected', [1]);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('provides selected items', () => {
|
|
335
|
+
const emit = vi.fn();
|
|
336
|
+
const state = useDataTableState(
|
|
337
|
+
{ data, columns, selected: [1, 3] },
|
|
338
|
+
emit
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(state.selectedItems.value.length).toBe(2);
|
|
342
|
+
expect(state.selectedItems.value[0].name).toBe('Alice');
|
|
343
|
+
expect(state.selectedItems.value[1].name).toBe('Charlie');
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('server mode', () => {
|
|
348
|
+
it('uses external total items for pagination', () => {
|
|
349
|
+
const emit = vi.fn();
|
|
350
|
+
const state = useDataTableState(
|
|
351
|
+
{
|
|
352
|
+
data: data.slice(0, 2),
|
|
353
|
+
columns,
|
|
354
|
+
serverMode: true,
|
|
355
|
+
totalItems: 100,
|
|
356
|
+
perPage: 2
|
|
357
|
+
},
|
|
358
|
+
emit
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
expect(state.computedTotalItems.value).toBe(100);
|
|
362
|
+
expect(state.totalPages.value).toBe(50);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('does not filter or sort in server mode', () => {
|
|
366
|
+
const emit = vi.fn();
|
|
367
|
+
const state = useDataTableState(
|
|
368
|
+
{ data, columns, serverMode: true },
|
|
369
|
+
emit
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
state.searchQuery.value = 'alice';
|
|
373
|
+
state.handleSort('name');
|
|
374
|
+
|
|
375
|
+
expect(state.processedData.value).toEqual(data);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
});
|