@fiscozen/icons 0.2.0 → 1.0.1

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.
@@ -0,0 +1,514 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+ import { mount, VueWrapper } from '@vue/test-utils'
3
+ import FzIcon from '../FzIcon.vue'
4
+
5
+ // Mock byPrefixAndName to avoid runtime errors
6
+ vi.mock('@awesome.me/kit-8137893ad3/icons', () => ({
7
+ byPrefixAndName: {
8
+ fas: { bell: ['fas', 'bell'], 'user-circle': ['fas', 'user-circle'] },
9
+ far: { bell: ['far', 'bell'], 'user-circle': ['far', 'user-circle'] },
10
+ fal: { bell: ['fal', 'bell'] },
11
+ fat: { bell: ['fat', 'bell'] },
12
+ fad: { bell: ['fad', 'bell'] },
13
+ fass: { bell: ['fass', 'bell'] },
14
+ fasr: { bell: ['fasr', 'bell'] },
15
+ fasl: { bell: ['fasl', 'bell'] },
16
+ fast: { bell: ['fast', 'bell'] },
17
+ fak: { bell: ['fak', 'bell'] }
18
+ }
19
+ }))
20
+
21
+ // Mock FontAwesome icon component - properly handle class binding
22
+ const mockFontAwesomeIcon = {
23
+ name: 'FontAwesomeIcon',
24
+ template: '<svg :class="$attrs.class" data-testid="fa-icon" />',
25
+ props: ['icon', 'size', 'spin']
26
+ }
27
+
28
+ describe('FzIcon', () => {
29
+ beforeEach(() => {
30
+ vi.spyOn(console, 'warn').mockImplementation(() => {})
31
+ })
32
+
33
+ // ============================================
34
+ // RENDERING TESTS
35
+ // ============================================
36
+ describe('Rendering', () => {
37
+ it('should render with default props', () => {
38
+ const wrapper = mount(FzIcon, {
39
+ props: {
40
+ name: 'bell'
41
+ },
42
+ global: {
43
+ stubs: {
44
+ 'font-awesome-icon': mockFontAwesomeIcon
45
+ }
46
+ }
47
+ })
48
+ expect(wrapper.exists()).toBe(true)
49
+ expect(wrapper.find('span').exists()).toBe(true)
50
+ })
51
+
52
+ it('should render with name prop', () => {
53
+ const wrapper = mount(FzIcon, {
54
+ props: {
55
+ name: 'bell'
56
+ },
57
+ global: {
58
+ stubs: {
59
+ 'font-awesome-icon': mockFontAwesomeIcon
60
+ }
61
+ }
62
+ })
63
+ const icon = wrapper.find('svg')
64
+ expect(icon.exists()).toBe(true)
65
+ })
66
+
67
+ it('should render correct container size', () => {
68
+ const wrapper = mount(FzIcon, {
69
+ props: {
70
+ name: 'bell',
71
+ size: 'lg'
72
+ },
73
+ global: {
74
+ stubs: {
75
+ 'font-awesome-icon': mockFontAwesomeIcon
76
+ }
77
+ }
78
+ })
79
+ const container = wrapper.find('span')
80
+ expect(container.classes()).toContain('w-[25px]')
81
+ expect(container.classes()).toContain('h-[25px]')
82
+ })
83
+
84
+ it('should apply custom class when provided', () => {
85
+ const wrapper = mount(FzIcon, {
86
+ props: {
87
+ name: 'bell'
88
+ },
89
+ attrs: {
90
+ class: 'custom-class'
91
+ },
92
+ global: {
93
+ stubs: {
94
+ 'font-awesome-icon': mockFontAwesomeIcon
95
+ }
96
+ }
97
+ })
98
+ const container = wrapper.find('span')
99
+ expect(container.classes()).toContain('custom-class')
100
+ })
101
+ })
102
+
103
+ // ============================================
104
+ // PROPS TESTS
105
+ // ============================================
106
+ describe('Props', () => {
107
+ describe('name prop', () => {
108
+ it('should accept string name', () => {
109
+ const wrapper = mount(FzIcon, {
110
+ props: {
111
+ name: 'bell'
112
+ },
113
+ global: {
114
+ stubs: {
115
+ 'font-awesome-icon': mockFontAwesomeIcon
116
+ }
117
+ }
118
+ })
119
+ expect(wrapper.props('name')).toBe('bell')
120
+ })
121
+
122
+ it('should accept array name', () => {
123
+ const wrapper = mount(FzIcon, {
124
+ props: {
125
+ name: ['fas', 'bell']
126
+ },
127
+ global: {
128
+ stubs: {
129
+ 'font-awesome-icon': mockFontAwesomeIcon
130
+ }
131
+ }
132
+ })
133
+ expect(wrapper.props('name')).toEqual(['fas', 'bell'])
134
+ })
135
+ })
136
+
137
+ describe('size prop', () => {
138
+ it.each([
139
+ ['xs', 'size-[12.5px]', 'h-[10px]'],
140
+ ['sm', 'w-[15px]', 'h-[12px]'],
141
+ ['md', 'w-[20px]', 'h-[16px]'],
142
+ ['lg', 'w-[25px]', 'h-[20px]'],
143
+ ['xl', 'w-[32px]', 'h-[24px]'],
144
+ ['2xl', 'w-[40px]', 'h-[32px]']
145
+ ])('should apply correct classes for %s size', (size, containerClass, iconClass) => {
146
+ const wrapper = mount(FzIcon, {
147
+ props: {
148
+ name: 'bell',
149
+ size: size as any
150
+ },
151
+ global: {
152
+ stubs: {
153
+ 'font-awesome-icon': mockFontAwesomeIcon
154
+ }
155
+ }
156
+ })
157
+ const container = wrapper.find('span')
158
+ expect(container.classes()).toContain(containerClass)
159
+
160
+ // Verify icon class is passed via :class binding to FontAwesome component
161
+ const fontAwesomeComponent = wrapper.findComponent({ name: 'FontAwesomeIcon' })
162
+ expect(fontAwesomeComponent.exists()).toBe(true)
163
+ // Check that the class attribute is passed (it may be in $attrs.class)
164
+ const iconElement = wrapper.find('[data-testid="fa-icon"]')
165
+ const classAttr = iconElement.attributes('class') || ''
166
+ // The class should be applied via :class binding
167
+ expect(classAttr.includes(iconClass) || iconElement.classes().includes(iconClass)).toBe(true)
168
+ })
169
+
170
+ it('should default to lg size', () => {
171
+ const wrapper = mount(FzIcon, {
172
+ props: {
173
+ name: 'bell'
174
+ },
175
+ global: {
176
+ stubs: {
177
+ 'font-awesome-icon': mockFontAwesomeIcon
178
+ }
179
+ }
180
+ })
181
+ const container = wrapper.find('span')
182
+ expect(container.classes()).toContain('w-[25px]')
183
+ expect(container.classes()).toContain('h-[25px]')
184
+ })
185
+ })
186
+
187
+ describe('variant prop', () => {
188
+ it('should default to far variant', () => {
189
+ const wrapper = mount(FzIcon, {
190
+ props: {
191
+ name: 'bell'
192
+ },
193
+ global: {
194
+ stubs: {
195
+ 'font-awesome-icon': mockFontAwesomeIcon
196
+ }
197
+ }
198
+ })
199
+ expect(wrapper.props('variant')).toBe('far')
200
+ })
201
+
202
+ it.each([
203
+ ['fas'],
204
+ ['far'],
205
+ ['fal'],
206
+ ['fat'],
207
+ ['fad']
208
+ ])('should accept %s variant', (variant) => {
209
+ const wrapper = mount(FzIcon, {
210
+ props: {
211
+ name: 'bell',
212
+ variant: variant as any
213
+ },
214
+ global: {
215
+ stubs: {
216
+ 'font-awesome-icon': mockFontAwesomeIcon
217
+ }
218
+ }
219
+ })
220
+ expect(wrapper.props('variant')).toBe(variant)
221
+ })
222
+ })
223
+
224
+ describe('spin prop', () => {
225
+ it('should not spin by default', () => {
226
+ const wrapper = mount(FzIcon, {
227
+ props: {
228
+ name: 'bell'
229
+ },
230
+ global: {
231
+ stubs: {
232
+ 'font-awesome-icon': mockFontAwesomeIcon
233
+ }
234
+ }
235
+ })
236
+ // spin defaults to undefined/false when not provided
237
+ const spinProp = wrapper.props('spin')
238
+ expect(spinProp === undefined || spinProp === false).toBe(true)
239
+ })
240
+
241
+ it('should apply spin when true', () => {
242
+ const wrapper = mount(FzIcon, {
243
+ props: {
244
+ name: 'bell',
245
+ spin: true
246
+ },
247
+ global: {
248
+ stubs: {
249
+ 'font-awesome-icon': mockFontAwesomeIcon
250
+ }
251
+ }
252
+ })
253
+ expect(wrapper.props('spin')).toBe(true)
254
+ // Verify spin prop is passed to FontAwesome component
255
+ const fontAwesomeComponent = wrapper.findComponent({ name: 'FontAwesomeIcon' })
256
+ expect(fontAwesomeComponent.exists()).toBe(true)
257
+ expect(fontAwesomeComponent.props('spin')).toBe(true)
258
+ })
259
+ })
260
+ })
261
+
262
+ // ============================================
263
+ // CSS CLASSES TESTS
264
+ // ============================================
265
+ describe('CSS Classes', () => {
266
+ it('should apply static base classes', () => {
267
+ const wrapper = mount(FzIcon, {
268
+ props: {
269
+ name: 'bell'
270
+ },
271
+ global: {
272
+ stubs: {
273
+ 'font-awesome-icon': mockFontAwesomeIcon
274
+ }
275
+ }
276
+ })
277
+ const container = wrapper.find('span')
278
+ expect(container.classes()).toContain('flex')
279
+ expect(container.classes()).toContain('items-center')
280
+ expect(container.classes()).toContain('justify-center')
281
+ })
282
+
283
+ it('should apply size-specific container classes', () => {
284
+ const wrapper = mount(FzIcon, {
285
+ props: {
286
+ name: 'bell',
287
+ size: 'md'
288
+ },
289
+ global: {
290
+ stubs: {
291
+ 'font-awesome-icon': mockFontAwesomeIcon
292
+ }
293
+ }
294
+ })
295
+ const container = wrapper.find('span')
296
+ expect(container.classes()).toContain('w-[20px]')
297
+ expect(container.classes()).toContain('h-[20px]')
298
+ })
299
+
300
+ it('should apply size-specific icon classes', () => {
301
+ const wrapper = mount(FzIcon, {
302
+ props: {
303
+ name: 'bell',
304
+ size: 'xl'
305
+ },
306
+ global: {
307
+ stubs: {
308
+ 'font-awesome-icon': mockFontAwesomeIcon
309
+ }
310
+ }
311
+ })
312
+ // Verify the class is passed to FontAwesome component
313
+ const fontAwesomeComponent = wrapper.findComponent({ name: 'FontAwesomeIcon' })
314
+ expect(fontAwesomeComponent.exists()).toBe(true)
315
+ const icon = wrapper.find('[data-testid="fa-icon"]')
316
+ const classAttr = icon.attributes('class') || ''
317
+ expect(classAttr.includes('h-[24px]') || icon.classes().includes('h-[24px]')).toBe(true)
318
+ })
319
+ })
320
+
321
+ // ============================================
322
+ // ACCESSIBILITY TESTS
323
+ // ============================================
324
+ describe('Accessibility', () => {
325
+ describe('ARIA attributes', () => {
326
+ it('should have aria-hidden for decorative icons by default', () => {
327
+ const wrapper = mount(FzIcon, {
328
+ props: {
329
+ name: 'bell'
330
+ },
331
+ global: {
332
+ stubs: {
333
+ 'font-awesome-icon': mockFontAwesomeIcon
334
+ }
335
+ }
336
+ })
337
+ // FontAwesome icons are decorative by default unless aria-label is provided
338
+ // The component itself doesn't set aria-hidden, but FontAwesome does
339
+ // We verify the icon exists and is rendered
340
+ const icon = wrapper.find('svg')
341
+ expect(icon.exists()).toBe(true)
342
+ })
343
+
344
+ it('should support aria-label when provided', () => {
345
+ const wrapper = mount(FzIcon, {
346
+ props: {
347
+ name: 'bell'
348
+ },
349
+ attrs: {
350
+ 'aria-label': 'Notification bell'
351
+ },
352
+ global: {
353
+ stubs: {
354
+ 'font-awesome-icon': mockFontAwesomeIcon
355
+ }
356
+ }
357
+ })
358
+ const container = wrapper.find('span')
359
+ expect(container.attributes('aria-label')).toBe('Notification bell')
360
+ })
361
+
362
+ it('should support role="img" when accessible', () => {
363
+ const wrapper = mount(FzIcon, {
364
+ props: {
365
+ name: 'bell'
366
+ },
367
+ attrs: {
368
+ role: 'img',
369
+ 'aria-label': 'Notification bell'
370
+ },
371
+ global: {
372
+ stubs: {
373
+ 'font-awesome-icon': mockFontAwesomeIcon
374
+ }
375
+ }
376
+ })
377
+ const container = wrapper.find('span')
378
+ expect(container.attributes('role')).toBe('img')
379
+ })
380
+ })
381
+
382
+ describe('Decorative elements', () => {
383
+ it('should render icon without accessibility attributes when decorative', () => {
384
+ const wrapper = mount(FzIcon, {
385
+ props: {
386
+ name: 'bell'
387
+ },
388
+ global: {
389
+ stubs: {
390
+ 'font-awesome-icon': mockFontAwesomeIcon
391
+ }
392
+ }
393
+ })
394
+ // Decorative: root has role="presentation", no aria-label
395
+ const container = wrapper.find('span')
396
+ expect(container.attributes('role')).toBe('presentation')
397
+ expect(container.attributes('aria-label')).toBeUndefined()
398
+ })
399
+ })
400
+ })
401
+
402
+ // ============================================
403
+ // EDGE CASES
404
+ // ============================================
405
+ describe('Edge Cases', () => {
406
+ it('should handle different icon name formats', () => {
407
+ const wrapper = mount(FzIcon, {
408
+ props: {
409
+ name: 'user-circle'
410
+ },
411
+ global: {
412
+ stubs: {
413
+ 'font-awesome-icon': mockFontAwesomeIcon
414
+ }
415
+ }
416
+ })
417
+ expect(wrapper.exists()).toBe(true)
418
+ })
419
+
420
+ it('should handle array icon format', () => {
421
+ const wrapper = mount(FzIcon, {
422
+ props: {
423
+ name: ['fas', 'bell']
424
+ },
425
+ global: {
426
+ stubs: {
427
+ 'font-awesome-icon': mockFontAwesomeIcon
428
+ }
429
+ }
430
+ })
431
+ expect(wrapper.exists()).toBe(true)
432
+ })
433
+ })
434
+
435
+ // ============================================
436
+ // SNAPSHOTS
437
+ // ============================================
438
+ describe('Snapshots', () => {
439
+ it('should match snapshot - default state', () => {
440
+ const wrapper = mount(FzIcon, {
441
+ props: {
442
+ name: 'bell'
443
+ },
444
+ global: {
445
+ stubs: {
446
+ 'font-awesome-icon': mockFontAwesomeIcon
447
+ }
448
+ }
449
+ })
450
+ expect(wrapper.html()).toMatchSnapshot()
451
+ })
452
+
453
+ it('should match snapshot - small size', () => {
454
+ const wrapper = mount(FzIcon, {
455
+ props: {
456
+ name: 'bell',
457
+ size: 'sm'
458
+ },
459
+ global: {
460
+ stubs: {
461
+ 'font-awesome-icon': mockFontAwesomeIcon
462
+ }
463
+ }
464
+ })
465
+ expect(wrapper.html()).toMatchSnapshot()
466
+ })
467
+
468
+ it('should match snapshot - large size', () => {
469
+ const wrapper = mount(FzIcon, {
470
+ props: {
471
+ name: 'bell',
472
+ size: 'xl'
473
+ },
474
+ global: {
475
+ stubs: {
476
+ 'font-awesome-icon': mockFontAwesomeIcon
477
+ }
478
+ }
479
+ })
480
+ expect(wrapper.html()).toMatchSnapshot()
481
+ })
482
+
483
+ it('should match snapshot - with spin', () => {
484
+ const wrapper = mount(FzIcon, {
485
+ props: {
486
+ name: 'bell',
487
+ spin: true
488
+ },
489
+ global: {
490
+ stubs: {
491
+ 'font-awesome-icon': mockFontAwesomeIcon
492
+ }
493
+ }
494
+ })
495
+ expect(wrapper.html()).toMatchSnapshot()
496
+ })
497
+
498
+ it('should match snapshot - with variant', () => {
499
+ const wrapper = mount(FzIcon, {
500
+ props: {
501
+ name: 'bell',
502
+ variant: 'fas'
503
+ },
504
+ global: {
505
+ stubs: {
506
+ 'font-awesome-icon': mockFontAwesomeIcon
507
+ }
508
+ }
509
+ })
510
+ expect(wrapper.html()).toMatchSnapshot()
511
+ })
512
+ })
513
+ })
514
+