@fiscozen/card 1.0.1 → 1.1.0

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,899 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect, vi, beforeEach } from "vitest";
3
+ import FzCard from "../FzCard.vue";
4
+
5
+ beforeEach(() => {
6
+ // Mock matchMedia for useMediaQuery composable
7
+ Object.defineProperty(window, "matchMedia", {
8
+ writable: true,
9
+ value: vi.fn().mockImplementation((query) => ({
10
+ matches: false,
11
+ media: query,
12
+ onchange: null,
13
+ addListener: vi.fn(),
14
+ removeListener: vi.fn(),
15
+ addEventListener: vi.fn(),
16
+ removeEventListener: vi.fn(),
17
+ dispatchEvent: vi.fn(),
18
+ })),
19
+ });
20
+ });
21
+
22
+ describe("FzCard", () => {
23
+ // ============================================
24
+ // RENDERING TESTS
25
+ // ============================================
26
+ describe("Rendering", () => {
27
+ it("should render with default props", async () => {
28
+ const wrapper = mount(FzCard);
29
+ await wrapper.vm.$nextTick();
30
+ expect(wrapper.exists()).toBe(true);
31
+ expect(wrapper.find("section").exists()).toBe(true);
32
+ expect(wrapper.find("article").exists()).toBe(true);
33
+ });
34
+
35
+ it("should render title when provided", async () => {
36
+ const wrapper = mount(FzCard, {
37
+ props: {
38
+ title: "Test Card Title",
39
+ },
40
+ });
41
+ await wrapper.vm.$nextTick();
42
+ expect(wrapper.find("h2").exists()).toBe(true);
43
+ expect(wrapper.find("h2").text()).toBe("Test Card Title");
44
+ });
45
+
46
+ it("should render default slot content", async () => {
47
+ const wrapper = mount(FzCard, {
48
+ props: {
49
+ title: "Test Card",
50
+ },
51
+ slots: {
52
+ default: "<p>Test content</p>",
53
+ } as any,
54
+ });
55
+ await wrapper.vm.$nextTick();
56
+ expect(wrapper.html()).toContain("Test content");
57
+ });
58
+
59
+ it("should render header slot content", async () => {
60
+ const wrapper = mount(FzCard, {
61
+ props: {
62
+ title: "Test Card",
63
+ },
64
+ slots: {
65
+ header: "<span>Header Slot</span>",
66
+ } as any,
67
+ });
68
+ await wrapper.vm.$nextTick();
69
+ expect(wrapper.html()).toContain("Header Slot");
70
+ });
71
+
72
+ it("should render header-content slot", async () => {
73
+ const wrapper = mount(FzCard, {
74
+ props: {
75
+ title: "Test Card",
76
+ },
77
+ slots: {
78
+ "header-content": "<div>Header Content</div>",
79
+ } as any,
80
+ });
81
+ await wrapper.vm.$nextTick();
82
+ expect(wrapper.html()).toContain("Header Content");
83
+ });
84
+
85
+ it("should render footer slot content", async () => {
86
+ const wrapper = mount(FzCard, {
87
+ props: {
88
+ title: "Test Card",
89
+ },
90
+ slots: {
91
+ footer: "<div>Custom Footer</div>",
92
+ } as any,
93
+ });
94
+ await wrapper.vm.$nextTick();
95
+ expect(wrapper.find("footer").exists()).toBe(true);
96
+ expect(wrapper.html()).toContain("Custom Footer");
97
+ });
98
+ });
99
+
100
+ // ============================================
101
+ // PROPS TESTS
102
+ // ============================================
103
+ describe("Props", () => {
104
+ describe("title prop", () => {
105
+ it("should render title when provided", async () => {
106
+ const wrapper = mount(FzCard, {
107
+ props: {
108
+ title: "Card Title",
109
+ },
110
+ });
111
+ await wrapper.vm.$nextTick();
112
+ expect(wrapper.find("h2").text()).toBe("Card Title");
113
+ });
114
+
115
+ it("should hide header when title is not provided and no slots", async () => {
116
+ const wrapper = mount(FzCard);
117
+ await wrapper.vm.$nextTick();
118
+ expect(wrapper.find("header").exists()).toBe(false);
119
+ });
120
+ });
121
+
122
+ describe("color prop", () => {
123
+ it("should apply default background color when no color prop", async () => {
124
+ const wrapper = mount(FzCard, {
125
+ props: {
126
+ title: "Test Card",
127
+ },
128
+ });
129
+ await wrapper.vm.$nextTick();
130
+ expect(wrapper.find("section").classes()).toContain("bg-core-white");
131
+ });
132
+
133
+ it("should apply default background color when color='default'", async () => {
134
+ const wrapper = mount(FzCard, {
135
+ props: {
136
+ title: "Test Card",
137
+ color: "default",
138
+ },
139
+ });
140
+ await wrapper.vm.$nextTick();
141
+ expect(wrapper.find("section").classes()).toContain("bg-core-white");
142
+ });
143
+
144
+ it.each([
145
+ ["blue", "bg-background-alice-blue"],
146
+ ["orange", "bg-background-seashell"],
147
+ ["purple", "bg-background-pale-purple"],
148
+ ["grey", "bg-background-white-smoke"],
149
+ ["yellow", "bg-semantic-warning-50"],
150
+ ["red", "bg-semantic-error-50"],
151
+ ])("should apply %s background color", async (color, expectedClass) => {
152
+ const wrapper = mount(FzCard, {
153
+ props: {
154
+ title: "Test Card",
155
+ color: color as any,
156
+ },
157
+ });
158
+ await wrapper.vm.$nextTick();
159
+ expect(wrapper.find("section").classes()).toContain(expectedClass);
160
+ });
161
+
162
+ it("should map deprecated aliceblue to blue and show warning", async () => {
163
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
164
+
165
+ const wrapper = mount(FzCard, {
166
+ props: {
167
+ title: "Test Card",
168
+ color: "aliceblue",
169
+ },
170
+ });
171
+
172
+ await wrapper.vm.$nextTick();
173
+
174
+ expect(wrapper.find("section").classes()).toContain(
175
+ "bg-background-alice-blue",
176
+ );
177
+
178
+ expect(consoleSpy).toHaveBeenCalledTimes(1);
179
+ const warningMessage = consoleSpy.mock.calls[0][0] as string;
180
+ expect(warningMessage).toContain(
181
+ "[FzCard] The color prop value 'aliceblue' is deprecated",
182
+ );
183
+ expect(warningMessage).toContain("Please use 'blue' instead");
184
+
185
+ consoleSpy.mockRestore();
186
+ });
187
+ });
188
+
189
+ describe("collapsible prop", () => {
190
+ it("should show content by default when not collapsible", async () => {
191
+ const wrapper = mount(FzCard, {
192
+ props: {
193
+ title: "Test Card",
194
+ },
195
+ });
196
+ await wrapper.vm.$nextTick();
197
+ expect(wrapper.find("article").exists()).toBe(true);
198
+ expect(wrapper.find("article").isVisible()).toBe(true);
199
+ });
200
+
201
+ it("should hide content initially when collapsible and not expanded", async () => {
202
+ const wrapper = mount(FzCard, {
203
+ props: {
204
+ title: "Test Card",
205
+ collapsible: true,
206
+ defaultExpanded: false,
207
+ },
208
+ });
209
+ await wrapper.vm.$nextTick();
210
+ expect(wrapper.find("article").exists()).toBe(false);
211
+ });
212
+
213
+ it("should show content when collapsible and defaultExpanded is true", async () => {
214
+ const wrapper = mount(FzCard, {
215
+ props: {
216
+ title: "Test Card",
217
+ collapsible: true,
218
+ defaultExpanded: true,
219
+ },
220
+ });
221
+ await wrapper.vm.$nextTick();
222
+ expect(wrapper.find("article").exists()).toBe(true);
223
+ expect(wrapper.find("article").isVisible()).toBe(true);
224
+ });
225
+
226
+ it("should add cursor-pointer class to header when collapsible", async () => {
227
+ const wrapper = mount(FzCard, {
228
+ props: {
229
+ title: "Test Card",
230
+ collapsible: true,
231
+ },
232
+ });
233
+ await wrapper.vm.$nextTick();
234
+ expect(wrapper.find("header").classes()).toContain("cursor-pointer");
235
+ });
236
+ });
237
+
238
+ describe("alwaysAlive prop", () => {
239
+ it("should keep content alive when alwaysAlive is true and collapsed", async () => {
240
+ const wrapper = mount(FzCard, {
241
+ props: {
242
+ title: "Test Card",
243
+ collapsible: true,
244
+ alwaysAlive: true,
245
+ defaultExpanded: false,
246
+ },
247
+ });
248
+ await wrapper.vm.$nextTick();
249
+ expect(wrapper.find("article").exists()).toBe(true);
250
+ expect(wrapper.find("article").isVisible()).toBe(false);
251
+ });
252
+ });
253
+
254
+ describe("primaryAction prop", () => {
255
+ it("should render footer with primaryAction", async () => {
256
+ const wrapper = mount(FzCard, {
257
+ props: {
258
+ title: "Test Card",
259
+ primaryAction: {
260
+ label: "Save",
261
+ },
262
+ },
263
+ });
264
+ await wrapper.vm.$nextTick();
265
+ expect(wrapper.find("footer").exists()).toBe(true);
266
+ expect(wrapper.html()).toContain("Save");
267
+ });
268
+ });
269
+
270
+ describe("secondaryAction prop", () => {
271
+ it("should render footer with secondaryAction", async () => {
272
+ const wrapper = mount(FzCard, {
273
+ props: {
274
+ title: "Test Card",
275
+ primaryAction: {
276
+ label: "Save",
277
+ },
278
+ secondaryAction: {
279
+ label: "Cancel",
280
+ },
281
+ },
282
+ });
283
+ await wrapper.vm.$nextTick();
284
+ expect(wrapper.find("footer").exists()).toBe(true);
285
+ expect(wrapper.html()).toContain("Cancel");
286
+ });
287
+ });
288
+
289
+ describe("tertiaryAction prop", () => {
290
+ it("should render footer with tertiaryAction", async () => {
291
+ const wrapper = mount(FzCard, {
292
+ props: {
293
+ title: "Test Card",
294
+ primaryAction: {
295
+ label: "Save",
296
+ },
297
+ secondaryAction: {
298
+ label: "Cancel",
299
+ },
300
+ tertiaryAction: {
301
+ icon: "bell",
302
+ },
303
+ },
304
+ });
305
+ await wrapper.vm.$nextTick();
306
+ expect(wrapper.find("footer").exists()).toBe(true);
307
+ });
308
+ });
309
+
310
+ describe("hasInfoIcon prop", () => {
311
+ it("should not show info icon when hasInfoIcon is false", async () => {
312
+ const wrapper = mount(FzCard, {
313
+ props: {
314
+ title: "Test Card",
315
+ hasInfoIcon: false,
316
+ },
317
+ });
318
+ await wrapper.vm.$nextTick();
319
+ const buttons = wrapper.findAll("button");
320
+ expect(buttons.length).toBe(0);
321
+ });
322
+
323
+ it("should show info icon when hasInfoIcon is true", async () => {
324
+ const wrapper = mount(FzCard, {
325
+ props: {
326
+ title: "Test Card",
327
+ hasInfoIcon: true,
328
+ },
329
+ });
330
+ await wrapper.vm.$nextTick();
331
+ const buttons = wrapper.findAll("button");
332
+ expect(buttons.length).toBe(1);
333
+ });
334
+ });
335
+
336
+ describe("environment prop", () => {
337
+ it("should default to frontoffice environment", async () => {
338
+ const wrapper = mount(FzCard, {
339
+ props: {
340
+ title: "Test Card",
341
+ primaryAction: {
342
+ label: "Save",
343
+ },
344
+ },
345
+ });
346
+ await wrapper.vm.$nextTick();
347
+ const button = wrapper.findComponent({ name: "FzButton" });
348
+ expect(button.props("environment")).toBe("frontoffice");
349
+ });
350
+
351
+ it("should apply backoffice environment", async () => {
352
+ const wrapper = mount(FzCard, {
353
+ props: {
354
+ title: "Test Card",
355
+ environment: "backoffice",
356
+ primaryAction: {
357
+ label: "Save",
358
+ },
359
+ },
360
+ });
361
+ await wrapper.vm.$nextTick();
362
+ const button = wrapper.findComponent({ name: "FzButton" });
363
+ expect(button.props("environment")).toBe("backoffice");
364
+ });
365
+ });
366
+
367
+ describe("contentClass prop", () => {
368
+ it("should apply custom content class", async () => {
369
+ const wrapper = mount(FzCard, {
370
+ props: {
371
+ title: "Test Card",
372
+ contentClass: "custom-content-class",
373
+ },
374
+ });
375
+ await wrapper.vm.$nextTick();
376
+ expect(wrapper.find("article").classes()).toContain("custom-content-class");
377
+ });
378
+ });
379
+ });
380
+
381
+ // ============================================
382
+ // EVENTS TESTS
383
+ // ============================================
384
+ describe("Events", () => {
385
+ it("should emit fzprimary:click when primaryAction is clicked", async () => {
386
+ const wrapper = mount(FzCard, {
387
+ props: {
388
+ title: "Test Card",
389
+ primaryAction: {
390
+ label: "Save",
391
+ },
392
+ },
393
+ });
394
+ await wrapper.vm.$nextTick();
395
+
396
+ const buttons = wrapper.findAll("button");
397
+ await buttons[buttons.length - 1].trigger("click");
398
+
399
+ expect(wrapper.emitted("fzprimary:click")).toBeTruthy();
400
+ expect(wrapper.emitted("fzprimary:click")).toHaveLength(1);
401
+ });
402
+
403
+ it("should emit fzsecondary:click when secondaryAction is clicked", async () => {
404
+ const wrapper = mount(FzCard, {
405
+ props: {
406
+ title: "Test Card",
407
+ primaryAction: {
408
+ label: "Save",
409
+ },
410
+ secondaryAction: {
411
+ label: "Cancel",
412
+ },
413
+ },
414
+ });
415
+ await wrapper.vm.$nextTick();
416
+
417
+ const buttons = wrapper.findAll("button");
418
+ await buttons[buttons.length - 2].trigger("click");
419
+
420
+ expect(wrapper.emitted("fzsecondary:click")).toBeTruthy();
421
+ expect(wrapper.emitted("fzsecondary:click")).toHaveLength(1);
422
+ });
423
+
424
+ it("should emit fztertiary:click when tertiaryAction is clicked", async () => {
425
+ const wrapper = mount(FzCard, {
426
+ props: {
427
+ title: "Test Card",
428
+ primaryAction: {
429
+ label: "Save",
430
+ },
431
+ secondaryAction: {
432
+ label: "Cancel",
433
+ },
434
+ tertiaryAction: {
435
+ icon: "bell",
436
+ },
437
+ },
438
+ });
439
+ await wrapper.vm.$nextTick();
440
+
441
+ const buttons = wrapper.findAll("button");
442
+ await buttons[0].trigger("click");
443
+
444
+ expect(wrapper.emitted("fztertiary:click")).toBeTruthy();
445
+ expect(wrapper.emitted("fztertiary:click")).toHaveLength(1);
446
+ });
447
+
448
+ it("should emit fzcard:click-info when info icon is clicked", async () => {
449
+ const wrapper = mount(FzCard, {
450
+ props: {
451
+ title: "Test Card",
452
+ hasInfoIcon: true,
453
+ },
454
+ });
455
+ await wrapper.vm.$nextTick();
456
+
457
+ const buttons = wrapper.findAll("button");
458
+ await buttons[0].trigger("click");
459
+
460
+ expect(wrapper.emitted("fzcard:click-info")).toBeTruthy();
461
+ expect(wrapper.emitted("fzcard:click-info")).toHaveLength(1);
462
+ });
463
+
464
+ it("should toggle content visibility when collapsible header is clicked", async () => {
465
+ const wrapper = mount(FzCard, {
466
+ props: {
467
+ title: "Test Card",
468
+ collapsible: true,
469
+ defaultExpanded: false,
470
+ },
471
+ });
472
+ await wrapper.vm.$nextTick();
473
+
474
+ expect(wrapper.find("article").exists()).toBe(false);
475
+
476
+ await wrapper.find("header").trigger("click");
477
+ await wrapper.vm.$nextTick();
478
+
479
+ expect(wrapper.find("article").exists()).toBe(true);
480
+ expect(wrapper.find("article").isVisible()).toBe(true);
481
+ });
482
+
483
+ it("should not toggle when header is clicked and not collapsible", async () => {
484
+ const wrapper = mount(FzCard, {
485
+ props: {
486
+ title: "Test Card",
487
+ collapsible: false,
488
+ },
489
+ });
490
+ await wrapper.vm.$nextTick();
491
+
492
+ const initialVisible = wrapper.find("article").isVisible();
493
+
494
+ await wrapper.find("header").trigger("click");
495
+ await wrapper.vm.$nextTick();
496
+
497
+ expect(wrapper.find("article").isVisible()).toBe(initialVisible);
498
+ });
499
+ });
500
+
501
+ // ============================================
502
+ // ACCESSIBILITY TESTS
503
+ // ============================================
504
+ describe("Accessibility", () => {
505
+ describe("Semantic HTML structure", () => {
506
+ it("should use semantic section element", async () => {
507
+ const wrapper = mount(FzCard, {
508
+ props: {
509
+ title: "Test Card",
510
+ },
511
+ });
512
+ await wrapper.vm.$nextTick();
513
+ expect(wrapper.find("section").exists()).toBe(true);
514
+ });
515
+
516
+ it("should use semantic header element when title or slots are present", async () => {
517
+ const wrapper = mount(FzCard, {
518
+ props: {
519
+ title: "Test Card",
520
+ },
521
+ });
522
+ await wrapper.vm.$nextTick();
523
+ expect(wrapper.find("header").exists()).toBe(true);
524
+ });
525
+
526
+ it("should use semantic article element for content", async () => {
527
+ const wrapper = mount(FzCard, {
528
+ props: {
529
+ title: "Test Card",
530
+ },
531
+ });
532
+ await wrapper.vm.$nextTick();
533
+ const article = wrapper.find("article");
534
+ expect(article.exists()).toBe(true);
535
+ });
536
+
537
+ it("should use semantic footer element when actions or footer slot are present", async () => {
538
+ const wrapper = mount(FzCard, {
539
+ props: {
540
+ title: "Test Card",
541
+ primaryAction: {
542
+ label: "Save",
543
+ },
544
+ },
545
+ });
546
+ await wrapper.vm.$nextTick();
547
+ expect(wrapper.find("footer").exists()).toBe(true);
548
+ });
549
+
550
+ it("should use h2 element for title", async () => {
551
+ const wrapper = mount(FzCard, {
552
+ props: {
553
+ title: "Test Card Title",
554
+ },
555
+ });
556
+ await wrapper.vm.$nextTick();
557
+ const h2 = wrapper.find("h2");
558
+ expect(h2.exists()).toBe(true);
559
+ expect(h2.text()).toBe("Test Card Title");
560
+ });
561
+ });
562
+
563
+ describe("ARIA attributes", () => {
564
+ it("should have title attribute on h2 matching title prop", async () => {
565
+ const wrapper = mount(FzCard, {
566
+ props: {
567
+ title: "Test Card Title",
568
+ },
569
+ });
570
+ await wrapper.vm.$nextTick();
571
+ expect(wrapper.find("h2").attributes("title")).toBe("Test Card Title");
572
+ });
573
+
574
+ it("should have accessible header when collapsible", async () => {
575
+ const wrapper = mount(FzCard, {
576
+ props: {
577
+ title: "Test Card",
578
+ collapsible: true,
579
+ },
580
+ });
581
+ await wrapper.vm.$nextTick();
582
+ const header = wrapper.find("header");
583
+ expect(header.exists()).toBe(true);
584
+ expect(header.classes()).toContain("cursor-pointer");
585
+ });
586
+ });
587
+
588
+ describe("Keyboard navigation", () => {
589
+ it("should be focusable when collapsible", async () => {
590
+ const wrapper = mount(FzCard, {
591
+ props: {
592
+ title: "Test Card",
593
+ collapsible: true,
594
+ },
595
+ });
596
+ await wrapper.vm.$nextTick();
597
+ const header = wrapper.find("header");
598
+ expect(header.exists()).toBe(true);
599
+ // Header should be clickable/focusable when collapsible
600
+ });
601
+ });
602
+
603
+ describe("Screen reader support", () => {
604
+ it("should provide meaningful title text for screen readers", async () => {
605
+ const wrapper = mount(FzCard, {
606
+ props: {
607
+ title: "Card Title",
608
+ },
609
+ });
610
+ await wrapper.vm.$nextTick();
611
+ const h2 = wrapper.find("h2");
612
+ expect(h2.text()).toBe("Card Title");
613
+ expect(h2.attributes("title")).toBe("Card Title");
614
+ });
615
+ });
616
+ });
617
+
618
+ // ============================================
619
+ // CSS CLASSES TESTS
620
+ // ============================================
621
+ describe("CSS Classes", () => {
622
+ it("should apply static base classes to section", async () => {
623
+ const wrapper = mount(FzCard, {
624
+ props: {
625
+ title: "Test Card",
626
+ },
627
+ });
628
+ await wrapper.vm.$nextTick();
629
+ const section = wrapper.find("section");
630
+ expect(section.classes()).toContain("border-1");
631
+ expect(section.classes()).toContain("border-solid");
632
+ expect(section.classes()).toContain("rounded");
633
+ expect(section.classes()).toContain("flex");
634
+ expect(section.classes()).toContain("flex-col");
635
+ });
636
+
637
+ it("should apply color-specific background classes", async () => {
638
+ const wrapper = mount(FzCard, {
639
+ props: {
640
+ title: "Test Card",
641
+ color: "blue",
642
+ },
643
+ });
644
+ await wrapper.vm.$nextTick();
645
+ expect(wrapper.find("section").classes()).toContain("bg-background-alice-blue");
646
+ });
647
+
648
+ it.each([
649
+ ["blue", "border-background-alice-blue"],
650
+ ["orange", "border-background-seashell"],
651
+ ["purple", "border-background-pale-purple"],
652
+ ["grey", "border-background-white-smoke"],
653
+ ["yellow", "border-semantic-warning-50"],
654
+ ["red", "border-semantic-error-50"],
655
+ ])("should apply %s border color", async (color, expectedClass) => {
656
+ const wrapper = mount(FzCard, {
657
+ props: {
658
+ title: "Test Card",
659
+ color: color as any,
660
+ primaryAction: {
661
+ label: "Save",
662
+ },
663
+ },
664
+ });
665
+ await wrapper.vm.$nextTick();
666
+ expect(wrapper.find("section").classes()).toContain(expectedClass);
667
+ });
668
+
669
+ it("should apply text color class", async () => {
670
+ const wrapper = mount(FzCard, {
671
+ props: {
672
+ title: "Test Card",
673
+ },
674
+ });
675
+ await wrapper.vm.$nextTick();
676
+ expect(wrapper.find("section").classes()).toContain("text-core-black");
677
+ });
678
+
679
+ it("should apply cursor-pointer class to header when collapsible", async () => {
680
+ const wrapper = mount(FzCard, {
681
+ props: {
682
+ title: "Test Card",
683
+ collapsible: true,
684
+ },
685
+ });
686
+ await wrapper.vm.$nextTick();
687
+ expect(wrapper.find("header").classes()).toContain("cursor-pointer");
688
+ });
689
+
690
+ it("should apply contentClass to article", async () => {
691
+ const wrapper = mount(FzCard, {
692
+ props: {
693
+ title: "Test Card",
694
+ contentClass: "custom-class",
695
+ },
696
+ });
697
+ await wrapper.vm.$nextTick();
698
+ expect(wrapper.find("article").classes()).toContain("custom-class");
699
+ });
700
+ });
701
+
702
+ // ============================================
703
+ // EDGE CASES
704
+ // ============================================
705
+ describe("Edge Cases", () => {
706
+ it("should handle card without any props", async () => {
707
+ const wrapper = mount(FzCard);
708
+ await wrapper.vm.$nextTick();
709
+ expect(wrapper.find("section").exists()).toBe(true);
710
+ expect(wrapper.find("article").exists()).toBe(true);
711
+ });
712
+
713
+ it("should handle undefined title gracefully", async () => {
714
+ const wrapper = mount(FzCard, {
715
+ props: {
716
+ title: undefined,
717
+ },
718
+ });
719
+ await wrapper.vm.$nextTick();
720
+ expect(wrapper.find("header").exists()).toBe(false);
721
+ });
722
+
723
+ it("should handle empty string title", async () => {
724
+ const wrapper = mount(FzCard, {
725
+ props: {
726
+ title: "",
727
+ },
728
+ });
729
+ await wrapper.vm.$nextTick();
730
+ // Empty string title should still show header if other conditions are met
731
+ expect(wrapper.find("section").exists()).toBe(true);
732
+ });
733
+
734
+ it("should handle very long title text", async () => {
735
+ const longTitle = "A".repeat(200);
736
+ const wrapper = mount(FzCard, {
737
+ props: {
738
+ title: longTitle,
739
+ },
740
+ });
741
+ await wrapper.vm.$nextTick();
742
+ expect(wrapper.find("h2").text()).toBe(longTitle);
743
+ expect(wrapper.find("h2").classes()).toContain("break-words");
744
+ expect(wrapper.find("h2").classes()).toContain("overflow-wrap-anywhere");
745
+ });
746
+
747
+ it("should warn when tertiaryAction is set without primaryAction and secondaryAction", () => {
748
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
749
+ mount(FzCard, {
750
+ props: {
751
+ title: "Test Card",
752
+ tertiaryAction: {
753
+ icon: "bell",
754
+ },
755
+ },
756
+ });
757
+ expect(warnSpy).toHaveBeenCalledWith(
758
+ "[Fiscozen Design System]: You should set primaryAction and secondaryAction if you want to set tertiaryAction",
759
+ );
760
+ warnSpy.mockRestore();
761
+ });
762
+
763
+ it("should warn when secondaryAction is set without primaryAction", () => {
764
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
765
+ mount(FzCard, {
766
+ props: {
767
+ title: "Test Card",
768
+ secondaryAction: {
769
+ label: "Cancel",
770
+ },
771
+ },
772
+ });
773
+ expect(warnSpy).toHaveBeenCalledWith(
774
+ "[Fiscozen Design System]: You should set primaryAction if you want to set secondaryAction",
775
+ );
776
+ warnSpy.mockRestore();
777
+ });
778
+
779
+ it("should expose toggleOpen method", async () => {
780
+ const wrapper = mount(FzCard, {
781
+ props: {
782
+ title: "Test Card",
783
+ collapsible: true,
784
+ defaultExpanded: false,
785
+ },
786
+ });
787
+ await wrapper.vm.$nextTick();
788
+
789
+ expect(wrapper.find("article").exists()).toBe(false);
790
+
791
+ (wrapper.vm as any).toggleOpen();
792
+ await wrapper.vm.$nextTick();
793
+
794
+ expect(wrapper.find("article").exists()).toBe(true);
795
+ });
796
+
797
+ it("should handle multiple rapid toggle operations", async () => {
798
+ const wrapper = mount(FzCard, {
799
+ props: {
800
+ title: "Test Card",
801
+ collapsible: true,
802
+ defaultExpanded: false,
803
+ },
804
+ });
805
+ await wrapper.vm.$nextTick();
806
+
807
+ expect(wrapper.find("article").exists()).toBe(false);
808
+
809
+ await wrapper.find("header").trigger("click");
810
+ await wrapper.vm.$nextTick();
811
+ expect(wrapper.find("article").exists()).toBe(true);
812
+
813
+ await wrapper.find("header").trigger("click");
814
+ await wrapper.vm.$nextTick();
815
+ expect(wrapper.find("article").exists()).toBe(false);
816
+
817
+ await wrapper.find("header").trigger("click");
818
+ await wrapper.vm.$nextTick();
819
+ expect(wrapper.find("article").exists()).toBe(true);
820
+ });
821
+ });
822
+
823
+ // ============================================
824
+ // SNAPSHOTS
825
+ // ============================================
826
+ describe("Snapshots", () => {
827
+ it("should match snapshot - default state", () => {
828
+ const wrapper = mount(FzCard, {
829
+ props: {
830
+ title: "Test Card",
831
+ },
832
+ });
833
+ expect(wrapper.html()).toMatchSnapshot();
834
+ });
835
+
836
+ it("should match snapshot - with all color variants", () => {
837
+ const wrapper = mount(FzCard, {
838
+ props: {
839
+ title: "Test Card",
840
+ color: "blue",
841
+ },
842
+ });
843
+ expect(wrapper.html()).toMatchSnapshot();
844
+ });
845
+
846
+ it("should match snapshot - collapsible", () => {
847
+ const wrapper = mount(FzCard, {
848
+ props: {
849
+ title: "Test Card",
850
+ collapsible: true,
851
+ defaultExpanded: true,
852
+ },
853
+ });
854
+ expect(wrapper.html()).toMatchSnapshot();
855
+ });
856
+
857
+ it("should match snapshot - with all actions", () => {
858
+ const wrapper = mount(FzCard, {
859
+ props: {
860
+ title: "Test Card",
861
+ primaryAction: {
862
+ label: "Save",
863
+ },
864
+ secondaryAction: {
865
+ label: "Cancel",
866
+ },
867
+ tertiaryAction: {
868
+ icon: "bell",
869
+ },
870
+ },
871
+ });
872
+ expect(wrapper.html()).toMatchSnapshot();
873
+ });
874
+
875
+ it("should match snapshot - with info icon", () => {
876
+ const wrapper = mount(FzCard, {
877
+ props: {
878
+ title: "Test Card",
879
+ hasInfoIcon: true,
880
+ },
881
+ });
882
+ expect(wrapper.html()).toMatchSnapshot();
883
+ });
884
+
885
+ it("should match snapshot - backoffice environment", () => {
886
+ const wrapper = mount(FzCard, {
887
+ props: {
888
+ title: "Test Card",
889
+ environment: "backoffice",
890
+ primaryAction: {
891
+ label: "Save",
892
+ },
893
+ },
894
+ });
895
+ expect(wrapper.html()).toMatchSnapshot();
896
+ });
897
+ });
898
+ });
899
+