@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.
Files changed (210) hide show
  1. package/README.md +119 -0
  2. package/dist/fabric.cjs.js +18109 -0
  3. package/dist/fabric.css +2180 -0
  4. package/dist/fabric.esm.js +18062 -0
  5. package/dist/fabric.min.js +18112 -0
  6. package/dist/types/components/atoms/FAvatar/FAvatar.test.d.ts +1 -0
  7. package/dist/types/components/atoms/FBadge/FBadge.test.d.ts +1 -0
  8. package/dist/types/components/atoms/FButton/FButton.test.d.ts +1 -0
  9. package/dist/types/components/atoms/FCheckbox/FCheckbox.test.d.ts +1 -0
  10. package/dist/types/components/atoms/FDivider/FDivider.test.d.ts +1 -0
  11. package/dist/types/components/atoms/FIcon/FIcon.test.d.ts +1 -0
  12. package/dist/types/components/atoms/FInput/FInput.test.d.ts +1 -0
  13. package/dist/types/components/atoms/FLoader/FLoader.test.d.ts +1 -0
  14. package/dist/types/components/atoms/FRadio/FRadio.test.d.ts +1 -0
  15. package/dist/types/components/atoms/FTextarea/FTextarea.test.d.ts +1 -0
  16. package/dist/types/components/atoms/FToggle/FToggle.test.d.ts +1 -0
  17. package/dist/types/components/atoms/FTypography/FTypography.test.d.ts +1 -0
  18. package/dist/types/components/atoms/index.d.ts +13 -0
  19. package/dist/types/components/molecules/FAccordionItem/FAccordionItem.test.d.ts +1 -0
  20. package/dist/types/components/molecules/FAlert/FAlert.test.d.ts +1 -0
  21. package/dist/types/components/molecules/FBreadcrumb/FBreadcrumb.test.d.ts +1 -0
  22. package/dist/types/components/molecules/FButtonGroup/FButtonGroup.test.d.ts +1 -0
  23. package/dist/types/components/molecules/FCard/FCard.test.d.ts +1 -0
  24. package/dist/types/components/molecules/FDatePicker/FDatePicker.test.d.ts +1 -0
  25. package/dist/types/components/molecules/FEmptyState/FEmptyState.test.d.ts +1 -0
  26. package/dist/types/components/molecules/FFilePreview/FFilePreview.test.d.ts +1 -0
  27. package/dist/types/components/molecules/FFormField/FFormField.test.d.ts +1 -0
  28. package/dist/types/components/molecules/FListItem/FListItem.test.d.ts +1 -0
  29. package/dist/types/components/molecules/FPagination/FPagination.test.d.ts +1 -0
  30. package/dist/types/components/molecules/FSearchBar/FSearchBar.test.d.ts +1 -0
  31. package/dist/types/components/molecules/FSelect/FSelect.test.d.ts +1 -0
  32. package/dist/types/components/molecules/FStatCard/FStatCard.test.d.ts +1 -0
  33. package/dist/types/components/molecules/FTabs/FTabs.test.d.ts +1 -0
  34. package/dist/types/components/molecules/FToast/FToast.test.d.ts +1 -0
  35. package/dist/types/components/molecules/index.d.ts +18 -0
  36. package/dist/types/components/organisms/FActivityFeed/FActivityFeed.test.d.ts +1 -0
  37. package/dist/types/components/organisms/FDataTable/FDataTable.test.d.ts +1 -0
  38. package/dist/types/components/organisms/FDrawer/FDrawer.test.d.ts +1 -0
  39. package/dist/types/components/organisms/FFileUpload/FFileUpload.test.d.ts +1 -0
  40. package/dist/types/components/organisms/FFilterSidebar/FFilterSidebar.test.d.ts +1 -0
  41. package/dist/types/components/organisms/FForm/FForm.test.d.ts +1 -0
  42. package/dist/types/components/organisms/FModal/FModal.test.d.ts +1 -0
  43. package/dist/types/components/organisms/FNavigationSidebar/FNavigationSidebar.test.d.ts +1 -0
  44. package/dist/types/components/organisms/FOnboardingStepper/FOnboardingStepper.test.d.ts +1 -0
  45. package/dist/types/components/organisms/FOnboardingStepper/FStepperProgress.test.d.ts +1 -0
  46. package/dist/types/components/organisms/FPageHeader/FPageHeader.test.d.ts +1 -0
  47. package/dist/types/components/organisms/FProfileSection/FProfileSection.test.d.ts +1 -0
  48. package/dist/types/components/organisms/FToastProvider/FToastProvider.test.d.ts +1 -0
  49. package/dist/types/components/organisms/FUserMenu/FUserMenu.test.d.ts +1 -0
  50. package/dist/types/components/organisms/index.d.ts +14 -0
  51. package/dist/types/components/utils/FThemeProvider.test.d.ts +1 -0
  52. package/dist/types/components/utils/index.d.ts +2 -0
  53. package/dist/types/components.d.ts +602 -0
  54. package/dist/types/composables/index.d.ts +12 -0
  55. package/dist/types/composables/useDataTableState.d.ts +106 -0
  56. package/dist/types/composables/useDataTableState.test.d.ts +1 -0
  57. package/dist/types/composables/useFormValidation.d.ts +49 -0
  58. package/dist/types/composables/useFormValidation.test.d.ts +1 -0
  59. package/dist/types/composables/useSidebarState.d.ts +65 -0
  60. package/dist/types/composables/useSidebarState.test.d.ts +1 -0
  61. package/dist/types/index.d.ts +19 -0
  62. package/dist/types/types.d.ts +529 -0
  63. package/package.json +100 -0
  64. package/src/components/atoms/FAvatar/FAvatar.stories.js +100 -0
  65. package/src/components/atoms/FAvatar/FAvatar.test.ts +95 -0
  66. package/src/components/atoms/FAvatar/FAvatar.vue +190 -0
  67. package/src/components/atoms/FBadge/FBadge.stories.js +129 -0
  68. package/src/components/atoms/FBadge/FBadge.test.ts +93 -0
  69. package/src/components/atoms/FBadge/FBadge.vue +103 -0
  70. package/src/components/atoms/FButton/FButton.stories.js +122 -0
  71. package/src/components/atoms/FButton/FButton.test.ts +98 -0
  72. package/src/components/atoms/FButton/FButton.vue +147 -0
  73. package/src/components/atoms/FCheckbox/FCheckbox.stories.js +96 -0
  74. package/src/components/atoms/FCheckbox/FCheckbox.test.ts +64 -0
  75. package/src/components/atoms/FCheckbox/FCheckbox.vue +76 -0
  76. package/src/components/atoms/FDivider/FDivider.stories.js +104 -0
  77. package/src/components/atoms/FDivider/FDivider.test.ts +80 -0
  78. package/src/components/atoms/FDivider/FDivider.vue +117 -0
  79. package/src/components/atoms/FIcon/FIcon.stories.js +189 -0
  80. package/src/components/atoms/FIcon/FIcon.test.ts +99 -0
  81. package/src/components/atoms/FIcon/FIcon.vue +192 -0
  82. package/src/components/atoms/FInput/FInput.stories.js +119 -0
  83. package/src/components/atoms/FInput/FInput.test.ts +79 -0
  84. package/src/components/atoms/FInput/FInput.vue +88 -0
  85. package/src/components/atoms/FLoader/FLoader.stories.js +109 -0
  86. package/src/components/atoms/FLoader/FLoader.test.ts +66 -0
  87. package/src/components/atoms/FLoader/FLoader.vue +97 -0
  88. package/src/components/atoms/FRadio/FRadio.stories.js +105 -0
  89. package/src/components/atoms/FRadio/FRadio.test.ts +75 -0
  90. package/src/components/atoms/FRadio/FRadio.vue +119 -0
  91. package/src/components/atoms/FTextarea/FTextarea.stories.js +126 -0
  92. package/src/components/atoms/FTextarea/FTextarea.test.ts +94 -0
  93. package/src/components/atoms/FTextarea/FTextarea.vue +156 -0
  94. package/src/components/atoms/FToggle/FToggle.stories.js +108 -0
  95. package/src/components/atoms/FToggle/FToggle.test.ts +96 -0
  96. package/src/components/atoms/FToggle/FToggle.vue +123 -0
  97. package/src/components/atoms/FTypography/FTypography.stories.js +127 -0
  98. package/src/components/atoms/FTypography/FTypography.test.ts +93 -0
  99. package/src/components/atoms/FTypography/FTypography.vue +78 -0
  100. package/src/components/atoms/index.ts +27 -0
  101. package/src/components/molecules/FAccordionItem/FAccordionItem.stories.js +71 -0
  102. package/src/components/molecules/FAccordionItem/FAccordionItem.test.ts +61 -0
  103. package/src/components/molecules/FAccordionItem/FAccordionItem.vue +105 -0
  104. package/src/components/molecules/FAlert/FAlert.stories.js +87 -0
  105. package/src/components/molecules/FAlert/FAlert.test.ts +59 -0
  106. package/src/components/molecules/FAlert/FAlert.vue +108 -0
  107. package/src/components/molecules/FBreadcrumb/FBreadcrumb.stories.js +90 -0
  108. package/src/components/molecules/FBreadcrumb/FBreadcrumb.test.ts +76 -0
  109. package/src/components/molecules/FBreadcrumb/FBreadcrumb.vue +117 -0
  110. package/src/components/molecules/FButtonGroup/FButtonGroup.stories.js +82 -0
  111. package/src/components/molecules/FButtonGroup/FButtonGroup.test.ts +44 -0
  112. package/src/components/molecules/FButtonGroup/FButtonGroup.vue +31 -0
  113. package/src/components/molecules/FCard/FCard.stories.js +136 -0
  114. package/src/components/molecules/FCard/FCard.test.ts +87 -0
  115. package/src/components/molecules/FCard/FCard.vue +75 -0
  116. package/src/components/molecules/FDatePicker/FDatePicker.stories.js +305 -0
  117. package/src/components/molecules/FDatePicker/FDatePicker.test.ts +282 -0
  118. package/src/components/molecules/FDatePicker/FDatePicker.vue +750 -0
  119. package/src/components/molecules/FEmptyState/FEmptyState.stories.js +98 -0
  120. package/src/components/molecules/FEmptyState/FEmptyState.test.ts +82 -0
  121. package/src/components/molecules/FEmptyState/FEmptyState.vue +89 -0
  122. package/src/components/molecules/FFilePreview/FFilePreview.stories.js +130 -0
  123. package/src/components/molecules/FFilePreview/FFilePreview.test.ts +70 -0
  124. package/src/components/molecules/FFilePreview/FFilePreview.vue +125 -0
  125. package/src/components/molecules/FFormField/FFormField.stories.js +149 -0
  126. package/src/components/molecules/FFormField/FFormField.test.ts +85 -0
  127. package/src/components/molecules/FFormField/FFormField.vue +107 -0
  128. package/src/components/molecules/FListItem/FListItem.stories.js +158 -0
  129. package/src/components/molecules/FListItem/FListItem.test.ts +93 -0
  130. package/src/components/molecules/FListItem/FListItem.vue +113 -0
  131. package/src/components/molecules/FPagination/FPagination.stories.js +132 -0
  132. package/src/components/molecules/FPagination/FPagination.test.ts +79 -0
  133. package/src/components/molecules/FPagination/FPagination.vue +206 -0
  134. package/src/components/molecules/FSearchBar/FSearchBar.stories.js +129 -0
  135. package/src/components/molecules/FSearchBar/FSearchBar.test.ts +81 -0
  136. package/src/components/molecules/FSearchBar/FSearchBar.vue +180 -0
  137. package/src/components/molecules/FSelect/FSelect.stories.js +333 -0
  138. package/src/components/molecules/FSelect/FSelect.test.ts +478 -0
  139. package/src/components/molecules/FSelect/FSelect.vue +551 -0
  140. package/src/components/molecules/FStatCard/FStatCard.stories.js +144 -0
  141. package/src/components/molecules/FStatCard/FStatCard.test.ts +78 -0
  142. package/src/components/molecules/FStatCard/FStatCard.vue +106 -0
  143. package/src/components/molecules/FTabs/FTab.vue +63 -0
  144. package/src/components/molecules/FTabs/FTabs.stories.js +277 -0
  145. package/src/components/molecules/FTabs/FTabs.test.ts +264 -0
  146. package/src/components/molecules/FTabs/FTabs.vue +273 -0
  147. package/src/components/molecules/FToast/FToast.stories.js +150 -0
  148. package/src/components/molecules/FToast/FToast.test.ts +157 -0
  149. package/src/components/molecules/FToast/FToast.vue +283 -0
  150. package/src/components/molecules/index.ts +37 -0
  151. package/src/components/organisms/FActivityFeed/FActivityFeed.stories.js +217 -0
  152. package/src/components/organisms/FActivityFeed/FActivityFeed.test.ts +134 -0
  153. package/src/components/organisms/FActivityFeed/FActivityFeed.vue +589 -0
  154. package/src/components/organisms/FDataTable/FDataTable.stories.js +370 -0
  155. package/src/components/organisms/FDataTable/FDataTable.test.ts +248 -0
  156. package/src/components/organisms/FDataTable/FDataTable.vue +808 -0
  157. package/src/components/organisms/FDrawer/FDrawer.stories.js +296 -0
  158. package/src/components/organisms/FDrawer/FDrawer.test.ts +142 -0
  159. package/src/components/organisms/FDrawer/FDrawer.vue +303 -0
  160. package/src/components/organisms/FFileUpload/FFileUpload.stories.js +162 -0
  161. package/src/components/organisms/FFileUpload/FFileUpload.test.ts +103 -0
  162. package/src/components/organisms/FFileUpload/FFileUpload.vue +616 -0
  163. package/src/components/organisms/FFilterSidebar/FFilterSidebar.stories.js +161 -0
  164. package/src/components/organisms/FFilterSidebar/FFilterSidebar.test.ts +92 -0
  165. package/src/components/organisms/FFilterSidebar/FFilterSidebar.vue +458 -0
  166. package/src/components/organisms/FForm/FForm.stories.js +270 -0
  167. package/src/components/organisms/FForm/FForm.test.ts +63 -0
  168. package/src/components/organisms/FForm/FForm.vue +19 -0
  169. package/src/components/organisms/FModal/FModal.stories.js +227 -0
  170. package/src/components/organisms/FModal/FModal.test.ts +181 -0
  171. package/src/components/organisms/FModal/FModal.vue +319 -0
  172. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.stories.js +176 -0
  173. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.test.ts +95 -0
  174. package/src/components/organisms/FNavigationSidebar/FNavigationSidebar.vue +577 -0
  175. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.stories.js +197 -0
  176. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.test.ts +114 -0
  177. package/src/components/organisms/FOnboardingStepper/FOnboardingStepper.vue +212 -0
  178. package/src/components/organisms/FOnboardingStepper/FStepperProgress.stories.js +122 -0
  179. package/src/components/organisms/FOnboardingStepper/FStepperProgress.test.ts +130 -0
  180. package/src/components/organisms/FOnboardingStepper/FStepperProgress.vue +146 -0
  181. package/src/components/organisms/FPageHeader/FPageHeader.stories.js +142 -0
  182. package/src/components/organisms/FPageHeader/FPageHeader.test.ts +83 -0
  183. package/src/components/organisms/FPageHeader/FPageHeader.vue +241 -0
  184. package/src/components/organisms/FProfileSection/FProfileSection.stories.js +190 -0
  185. package/src/components/organisms/FProfileSection/FProfileSection.test.ts +85 -0
  186. package/src/components/organisms/FProfileSection/FProfileSection.vue +562 -0
  187. package/src/components/organisms/FToastProvider/FToastProvider.stories.js +290 -0
  188. package/src/components/organisms/FToastProvider/FToastProvider.test.ts +215 -0
  189. package/src/components/organisms/FToastProvider/FToastProvider.vue +214 -0
  190. package/src/components/organisms/FUserMenu/FUserMenu.stories.js +170 -0
  191. package/src/components/organisms/FUserMenu/FUserMenu.test.ts +102 -0
  192. package/src/components/organisms/FUserMenu/FUserMenu.vue +407 -0
  193. package/src/components/organisms/index.ts +29 -0
  194. package/src/components/utils/FThemeProvider.stories.js +236 -0
  195. package/src/components/utils/FThemeProvider.test.ts +244 -0
  196. package/src/components/utils/FThemeProvider.vue +191 -0
  197. package/src/components/utils/index.ts +3 -0
  198. package/src/components.d.ts +602 -0
  199. package/src/composables/README.md +233 -0
  200. package/src/composables/index.ts +25 -0
  201. package/src/composables/useDataTableState.test.ts +378 -0
  202. package/src/composables/useDataTableState.ts +361 -0
  203. package/src/composables/useFormValidation.test.ts +198 -0
  204. package/src/composables/useFormValidation.ts +178 -0
  205. package/src/composables/useSidebarState.test.ts +307 -0
  206. package/src/composables/useSidebarState.ts +201 -0
  207. package/src/env.d.ts +14 -0
  208. package/src/index.ts +167 -0
  209. package/src/styles/tailwind.css +173 -0
  210. package/src/types.ts +740 -0
@@ -0,0 +1,478 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { mount } from '@vue/test-utils';
3
+ import FSelect from './FSelect.vue';
4
+
5
+ describe('FSelect', () => {
6
+ let wrapper;
7
+
8
+ const simpleOptions = ['Option 1', 'Option 2', 'Option 3'];
9
+ const objectOptions = [
10
+ { value: 1, label: 'Paris' },
11
+ { value: 2, label: 'Lyon' },
12
+ { value: 3, label: 'Marseille' }
13
+ ];
14
+
15
+ afterEach(() => {
16
+ if (wrapper) {
17
+ wrapper.destroy();
18
+ }
19
+ });
20
+
21
+ describe('Rendering', () => {
22
+ it('renders correctly with default props', () => {
23
+ wrapper = mount(FSelect, {
24
+ propsData: {
25
+ options: simpleOptions
26
+ }
27
+ });
28
+ expect(wrapper.find('button[type="button"]').exists()).toBe(true);
29
+ });
30
+
31
+ it('displays placeholder when no value is selected', () => {
32
+ wrapper = mount(FSelect, {
33
+ propsData: {
34
+ options: simpleOptions,
35
+ placeholder: 'Choose an option'
36
+ }
37
+ });
38
+ expect(wrapper.find('button').text()).toContain('Choose an option');
39
+ });
40
+
41
+ it('displays selected value', () => {
42
+ wrapper = mount(FSelect, {
43
+ propsData: {
44
+ options: simpleOptions,
45
+ value: 'Option 2'
46
+ }
47
+ });
48
+ expect(wrapper.find('button').text()).toContain('Option 2');
49
+ });
50
+ });
51
+
52
+ describe('Simple Selection', () => {
53
+ beforeEach(() => {
54
+ wrapper = mount(FSelect, {
55
+ propsData: {
56
+ options: simpleOptions
57
+ }
58
+ });
59
+ });
60
+
61
+ it('opens dropdown on trigger click', async () => {
62
+ await wrapper.find('button[type="button"]').trigger('click');
63
+ expect(wrapper.vm.isOpen).toBe(true);
64
+ expect(wrapper.emitted('open')).toBeTruthy();
65
+ });
66
+
67
+ it('closes dropdown on second click', async () => {
68
+ await wrapper.find('button[type="button"]').trigger('click');
69
+ expect(wrapper.vm.isOpen).toBe(true);
70
+ await wrapper.find('button[type="button"]').trigger('click');
71
+ expect(wrapper.vm.isOpen).toBe(false);
72
+ });
73
+
74
+ it('emits input event when option is clicked', async () => {
75
+ await wrapper.find('button[type="button"]').trigger('click');
76
+ await wrapper.vm.$nextTick();
77
+
78
+ const options = wrapper.findAll('[role="option"]');
79
+ await options.at(1).trigger('click');
80
+
81
+ expect(wrapper.emitted('input')).toBeTruthy();
82
+ expect(wrapper.emitted('input')[0][0]).toBe('Option 2');
83
+ });
84
+
85
+ it('closes dropdown after selecting an option', async () => {
86
+ await wrapper.find('button[type="button"]').trigger('click');
87
+ await wrapper.vm.$nextTick();
88
+
89
+ const options = wrapper.findAll('[role="option"]');
90
+ await options.at(0).trigger('click');
91
+
92
+ expect(wrapper.vm.isOpen).toBe(false);
93
+ });
94
+ });
95
+
96
+ describe('Multiple Selection', () => {
97
+ beforeEach(() => {
98
+ wrapper = mount(FSelect, {
99
+ propsData: {
100
+ options: simpleOptions,
101
+ multiple: true,
102
+ value: []
103
+ }
104
+ });
105
+ });
106
+
107
+ it('allows multiple selections', async () => {
108
+ await wrapper.find('button[type="button"]').trigger('click');
109
+ await wrapper.vm.$nextTick();
110
+
111
+ const options = wrapper.findAll('[role="option"]');
112
+ await options.at(0).trigger('click');
113
+ await options.at(1).trigger('click');
114
+
115
+ expect(wrapper.emitted('input')).toBeTruthy();
116
+ expect(wrapper.emitted('input').length).toBe(2);
117
+ });
118
+
119
+ it('does not close dropdown after selecting an option', async () => {
120
+ await wrapper.find('button[type="button"]').trigger('click');
121
+ await wrapper.vm.$nextTick();
122
+
123
+ const options = wrapper.findAll('[role="option"]');
124
+ await options.at(0).trigger('click');
125
+
126
+ expect(wrapper.vm.isOpen).toBe(true);
127
+ });
128
+
129
+ it('deselects option when clicked again', async () => {
130
+ wrapper = mount(FSelect, {
131
+ propsData: {
132
+ options: simpleOptions,
133
+ multiple: true,
134
+ value: ['Option 1']
135
+ }
136
+ });
137
+
138
+ await wrapper.find('button[type="button"]').trigger('click');
139
+ await wrapper.vm.$nextTick();
140
+
141
+ const options = wrapper.findAll('[role="option"]');
142
+ await options.at(0).trigger('click');
143
+
144
+ const emittedValue = wrapper.emitted('input')[0][0];
145
+ expect(emittedValue).toHaveLength(0);
146
+ });
147
+ });
148
+
149
+ describe('Searchable', () => {
150
+ beforeEach(() => {
151
+ wrapper = mount(FSelect, {
152
+ propsData: {
153
+ options: objectOptions,
154
+ searchable: true
155
+ }
156
+ });
157
+ });
158
+
159
+ it('shows search input when searchable is true', async () => {
160
+ await wrapper.find('button[type="button"]').trigger('click');
161
+ await wrapper.vm.$nextTick();
162
+
163
+ expect(wrapper.find('input[type="text"]').exists()).toBe(true);
164
+ });
165
+
166
+ it('filters options based on search query', async () => {
167
+ await wrapper.find('button[type="button"]').trigger('click');
168
+ await wrapper.vm.$nextTick();
169
+
170
+ const searchInput = wrapper.find('input[type="text"]');
171
+ await searchInput.setValue('Lyon');
172
+ await wrapper.vm.$nextTick();
173
+
174
+ const options = wrapper.findAll('[role="option"]');
175
+ expect(options.length).toBe(1);
176
+ expect(options.at(0).text()).toContain('Lyon');
177
+ });
178
+
179
+ it('shows empty state when no options match', async () => {
180
+ await wrapper.find('button[type="button"]').trigger('click');
181
+ await wrapper.vm.$nextTick();
182
+
183
+ const searchInput = wrapper.find('input[type="text"]');
184
+ await searchInput.setValue('XYZ');
185
+ await wrapper.vm.$nextTick();
186
+
187
+ expect(wrapper.text()).toContain('Aucune option trouvée');
188
+ });
189
+
190
+ it('filters are case insensitive', async () => {
191
+ await wrapper.find('button[type="button"]').trigger('click');
192
+ await wrapper.vm.$nextTick();
193
+
194
+ const searchInput = wrapper.find('input[type="text"]');
195
+ await searchInput.setValue('PARIS');
196
+ await wrapper.vm.$nextTick();
197
+
198
+ const options = wrapper.findAll('[role="option"]');
199
+ expect(options.length).toBe(1);
200
+ });
201
+ });
202
+
203
+ describe('Object Options', () => {
204
+ it('displays label from object options', () => {
205
+ wrapper = mount(FSelect, {
206
+ propsData: {
207
+ options: objectOptions,
208
+ value: objectOptions[0]
209
+ }
210
+ });
211
+ expect(wrapper.find('button').text()).toContain('Paris');
212
+ });
213
+
214
+ it('uses custom option keys', () => {
215
+ const customOptions = [
216
+ { id: 1, name: 'First' },
217
+ { id: 2, name: 'Second' }
218
+ ];
219
+
220
+ wrapper = mount(FSelect, {
221
+ propsData: {
222
+ options: customOptions,
223
+ optionKey: 'id',
224
+ optionLabel: 'name',
225
+ value: customOptions[0]
226
+ }
227
+ });
228
+
229
+ expect(wrapper.find('button').text()).toContain('First');
230
+ });
231
+ });
232
+
233
+ describe('States', () => {
234
+ it('applies disabled state', () => {
235
+ wrapper = mount(FSelect, {
236
+ propsData: {
237
+ options: simpleOptions,
238
+ disabled: true
239
+ }
240
+ });
241
+ expect(wrapper.find('button').attributes('disabled')).toBeDefined();
242
+ expect(wrapper.find('button').classes()).toContain('cursor-not-allowed');
243
+ });
244
+
245
+ it('applies error styles', () => {
246
+ wrapper = mount(FSelect, {
247
+ propsData: {
248
+ options: simpleOptions,
249
+ error: true
250
+ }
251
+ });
252
+ expect(wrapper.find('button').classes().join(' ')).toContain(
253
+ 'border-danger'
254
+ );
255
+ });
256
+
257
+ it('shows loading state', async () => {
258
+ wrapper = mount(FSelect, {
259
+ propsData: {
260
+ options: [],
261
+ loading: true,
262
+ loadingText: 'Loading...'
263
+ }
264
+ });
265
+
266
+ await wrapper.find('button[type="button"]').trigger('click');
267
+ await wrapper.vm.$nextTick();
268
+
269
+ expect(wrapper.text()).toContain('Loading...');
270
+ });
271
+ });
272
+
273
+ describe('Sizes', () => {
274
+ it('applies small size classes', () => {
275
+ wrapper = mount(FSelect, {
276
+ propsData: {
277
+ options: simpleOptions,
278
+ size: 'small'
279
+ }
280
+ });
281
+ expect(wrapper.find('button').classes()).toContain('text-xs');
282
+ });
283
+
284
+ it('applies medium size classes', () => {
285
+ wrapper = mount(FSelect, {
286
+ propsData: {
287
+ options: simpleOptions,
288
+ size: 'medium'
289
+ }
290
+ });
291
+ expect(wrapper.find('button').classes()).toContain('text-sm');
292
+ });
293
+
294
+ it('applies large size classes', () => {
295
+ wrapper = mount(FSelect, {
296
+ propsData: {
297
+ options: simpleOptions,
298
+ size: 'large'
299
+ }
300
+ });
301
+ expect(wrapper.find('button').classes()).toContain('text-base');
302
+ });
303
+ });
304
+
305
+ describe('Accessibility', () => {
306
+ beforeEach(() => {
307
+ wrapper = mount(FSelect, {
308
+ propsData: {
309
+ options: simpleOptions,
310
+ labelId: 'test-label'
311
+ }
312
+ });
313
+ });
314
+
315
+ it('has correct ARIA attributes on trigger', () => {
316
+ const trigger = wrapper.find('button[type="button"]');
317
+ expect(trigger.attributes('aria-haspopup')).toBe('listbox');
318
+ expect(trigger.attributes('aria-expanded')).toBe('false');
319
+ });
320
+
321
+ it('updates aria-expanded when opened', async () => {
322
+ await wrapper.find('button[type="button"]').trigger('click');
323
+ const trigger = wrapper.find('button[type="button"]');
324
+ expect(trigger.attributes('aria-expanded')).toBe('true');
325
+ });
326
+
327
+ it('has listbox role on dropdown', async () => {
328
+ await wrapper.find('button[type="button"]').trigger('click');
329
+ await wrapper.vm.$nextTick();
330
+ expect(wrapper.find('[role="listbox"]').exists()).toBe(true);
331
+ });
332
+
333
+ it('has option role on each option', async () => {
334
+ await wrapper.find('button[type="button"]').trigger('click');
335
+ await wrapper.vm.$nextTick();
336
+ const options = wrapper.findAll('[role="option"]');
337
+ expect(options.length).toBe(simpleOptions.length);
338
+ });
339
+
340
+ it('sets aria-selected on selected option', async () => {
341
+ wrapper = mount(FSelect, {
342
+ propsData: {
343
+ options: simpleOptions,
344
+ value: 'Option 1'
345
+ }
346
+ });
347
+
348
+ await wrapper.find('button[type="button"]').trigger('click');
349
+ await wrapper.vm.$nextTick();
350
+
351
+ const selectedOption = wrapper.find('[aria-selected="true"]');
352
+ expect(selectedOption.exists()).toBe(true);
353
+ });
354
+ });
355
+
356
+ describe('Keyboard Navigation', () => {
357
+ beforeEach(() => {
358
+ wrapper = mount(FSelect, {
359
+ propsData: {
360
+ options: simpleOptions,
361
+ searchable: true
362
+ }
363
+ });
364
+ });
365
+
366
+ it('opens dropdown on arrow down', async () => {
367
+ await wrapper.find('button[type="button"]').trigger('keydown.down');
368
+ expect(wrapper.vm.isOpen).toBe(true);
369
+ });
370
+
371
+ it('opens dropdown on arrow up', async () => {
372
+ await wrapper.find('button[type="button"]').trigger('keydown.up');
373
+ expect(wrapper.vm.isOpen).toBe(true);
374
+ });
375
+
376
+ it('closes dropdown on escape', async () => {
377
+ await wrapper.find('button[type="button"]').trigger('click');
378
+ expect(wrapper.vm.isOpen).toBe(true);
379
+ await wrapper.trigger('keydown.escape');
380
+ expect(wrapper.vm.isOpen).toBe(false);
381
+ });
382
+ });
383
+
384
+ describe('Disabled Options', () => {
385
+ const optionsWithDisabled = [
386
+ { value: 1, label: 'Enabled', disabled: false },
387
+ { value: 2, label: 'Disabled', disabled: true }
388
+ ];
389
+
390
+ it('does not select disabled option', async () => {
391
+ wrapper = mount(FSelect, {
392
+ propsData: {
393
+ options: optionsWithDisabled
394
+ }
395
+ });
396
+
397
+ await wrapper.find('button[type="button"]').trigger('click');
398
+ await wrapper.vm.$nextTick();
399
+
400
+ const options = wrapper.findAll('[role="option"]');
401
+ await options.at(1).trigger('click');
402
+
403
+ expect(wrapper.emitted('input')).toBeFalsy();
404
+ });
405
+
406
+ it('applies disabled styling to disabled options', async () => {
407
+ wrapper = mount(FSelect, {
408
+ propsData: {
409
+ options: optionsWithDisabled
410
+ }
411
+ });
412
+
413
+ await wrapper.find('button[type="button"]').trigger('click');
414
+ await wrapper.vm.$nextTick();
415
+
416
+ const options = wrapper.findAll('[role="option"]');
417
+ expect(options.at(1).classes()).toContain('cursor-not-allowed');
418
+ });
419
+ });
420
+
421
+ describe('Custom Filter Method', () => {
422
+ it('uses custom filter method when provided', async () => {
423
+ const customFilter = vi.fn((query, options) => {
424
+ return options.filter((opt) => opt.value === 1);
425
+ });
426
+
427
+ wrapper = mount(FSelect, {
428
+ propsData: {
429
+ options: objectOptions,
430
+ searchable: true,
431
+ filterMethod: customFilter
432
+ }
433
+ });
434
+
435
+ await wrapper.find('button[type="button"]').trigger('click');
436
+ await wrapper.vm.$nextTick();
437
+
438
+ const searchInput = wrapper.find('input[type="text"]');
439
+ await searchInput.setValue('test');
440
+ await wrapper.vm.$nextTick();
441
+
442
+ expect(customFilter).toHaveBeenCalledWith('test', objectOptions);
443
+ const options = wrapper.findAll('[role="option"]');
444
+ expect(options.length).toBe(1);
445
+ });
446
+ });
447
+
448
+ describe('Events', () => {
449
+ beforeEach(() => {
450
+ wrapper = mount(FSelect, {
451
+ propsData: {
452
+ options: simpleOptions
453
+ }
454
+ });
455
+ });
456
+
457
+ it('emits open event when dropdown opens', async () => {
458
+ await wrapper.find('button[type="button"]').trigger('click');
459
+ expect(wrapper.emitted('open')).toBeTruthy();
460
+ });
461
+
462
+ it('emits close event when dropdown closes', async () => {
463
+ await wrapper.find('button[type="button"]').trigger('click');
464
+ await wrapper.find('button[type="button"]').trigger('click');
465
+ expect(wrapper.emitted('close')).toBeTruthy();
466
+ });
467
+
468
+ it('emits change event when value changes', async () => {
469
+ await wrapper.find('button[type="button"]').trigger('click');
470
+ await wrapper.vm.$nextTick();
471
+
472
+ const options = wrapper.findAll('[role="option"]');
473
+ await options.at(0).trigger('click');
474
+
475
+ expect(wrapper.emitted('change')).toBeTruthy();
476
+ });
477
+ });
478
+ });