@fiscozen/tab 0.1.12 → 2.0.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.
@@ -1,8 +1,8 @@
1
- import { mount } from '@vue/test-utils'
2
- import { describe, it, expect, beforeEach, vi } from 'vitest'
3
- import { h } from 'vue'
4
- import { FzTab, FzTabs } from '..'
5
- import { FzTabProps, FzTabsProps } from '../types'
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
3
+ import { h } from "vue";
4
+ import { FzTab, FzTabs } from "..";
5
+ import { FzTabProps, FzTabsProps } from "../types";
6
6
 
7
7
  const createWrapper = async (
8
8
  props: FzTabsProps,
@@ -10,9 +10,12 @@ const createWrapper = async (
10
10
  tab2Props: FzTabProps,
11
11
  tab3Props?: FzTabProps,
12
12
  ) => {
13
- const tabs = [h(FzTab, tab1Props, 'Content tab1'), h(FzTab, tab2Props, 'Content tab2')]
13
+ const tabs = [
14
+ h(FzTab, tab1Props, "Content tab1"),
15
+ h(FzTab, tab2Props, "Content tab2"),
16
+ ];
14
17
  if (tab3Props) {
15
- tabs.push(h(FzTab, tab3Props, 'Content tab3'))
18
+ tabs.push(h(FzTab, tab3Props, "Content tab3"));
16
19
  }
17
20
 
18
21
  const content = mount(FzTabs, {
@@ -20,19 +23,29 @@ const createWrapper = async (
20
23
  slots: {
21
24
  default: () => tabs,
22
25
  },
23
- })
26
+ });
24
27
 
25
- await content.vm.$nextTick()
26
- return content
27
- }
28
+ await content.vm.$nextTick();
29
+ return content;
30
+ };
28
31
 
29
- describe('FzTabs', () => {
32
+ describe("FzTabs", () => {
30
33
  beforeEach(() => {
31
- globalThis.HTMLElement.prototype.scrollIntoView = vi.fn()
32
- vi.spyOn(console, 'warn').mockImplementation(() => {})
34
+ globalThis.HTMLElement.prototype.scrollIntoView = vi.fn();
35
+ vi.spyOn(console, "warn").mockImplementation(() => {});
36
+
37
+ // Mock IntersectionObserver
38
+ Object.defineProperty(window, "IntersectionObserver", {
39
+ writable: true,
40
+ value: vi.fn().mockImplementation(() => ({
41
+ observe: vi.fn(),
42
+ unobserve: vi.fn(),
43
+ disconnect: vi.fn(),
44
+ })),
45
+ });
33
46
 
34
47
  // Mock matchMedia for useMediaQuery composable
35
- Object.defineProperty(window, 'matchMedia', {
48
+ Object.defineProperty(window, "matchMedia", {
36
49
  writable: true,
37
50
  value: vi.fn().mockImplementation((query) => ({
38
51
  matches: false,
@@ -44,1150 +57,1417 @@ describe('FzTabs', () => {
44
57
  removeEventListener: vi.fn(),
45
58
  dispatchEvent: vi.fn(),
46
59
  })),
47
- })
48
- })
60
+ });
61
+ });
62
+
63
+ afterEach(() => {
64
+ vi.restoreAllMocks();
65
+ });
49
66
 
50
67
  // ============================================
51
68
  // RENDERING TESTS
52
69
  // ============================================
53
- describe('Rendering', () => {
54
- it('should render with default props', async () => {
70
+ describe("Rendering", () => {
71
+ it("should render with default props", async () => {
55
72
  const wrapper = await createWrapper(
56
73
  {
57
- size: 'sm',
74
+ size: "sm",
58
75
  },
59
76
  {
60
- title: 'tab1',
77
+ title: "tab1",
61
78
  },
62
79
  {
63
- title: 'tab2',
80
+ title: "tab2",
64
81
  },
65
- )
82
+ );
66
83
 
67
- expect(wrapper.exists()).toBe(true)
68
- expect(wrapper.find('.tab-container').exists()).toBe(true)
69
- })
84
+ expect(wrapper.exists()).toBe(true);
85
+ expect(wrapper.find(".tab-container").exists()).toBe(true);
86
+ });
70
87
 
71
- it('should render tab buttons', async () => {
88
+ it("should render tab buttons", async () => {
72
89
  const wrapper = await createWrapper(
73
90
  {
74
- size: 'sm',
91
+ size: "sm",
75
92
  },
76
93
  {
77
- title: 'tab1',
94
+ title: "tab1",
78
95
  },
79
96
  {
80
- title: 'tab2',
97
+ title: "tab2",
81
98
  },
82
- )
99
+ );
83
100
 
84
- const buttons = wrapper.findAll('button')
85
- expect(buttons.length).toBeGreaterThanOrEqual(2)
86
- expect(buttons[0].attributes('title')).toBe('tab1')
87
- expect(buttons[1].attributes('title')).toBe('tab2')
88
- })
101
+ const buttons = wrapper.findAll("button");
102
+ expect(buttons.length).toBeGreaterThanOrEqual(2);
103
+ expect(buttons[0].attributes("title")).toBe("tab1");
104
+ expect(buttons[1].attributes("title")).toBe("tab2");
105
+ });
89
106
 
90
- it('should render tab content for selected tab', async () => {
107
+ it("should render tab content for selected tab", async () => {
91
108
  const wrapper = await createWrapper(
92
109
  {
93
- size: 'sm',
110
+ size: "sm",
94
111
  },
95
112
  {
96
- title: 'tab1',
113
+ title: "tab1",
97
114
  },
98
115
  {
99
- title: 'tab2',
116
+ title: "tab2",
100
117
  },
101
- )
118
+ );
102
119
 
103
- expect(wrapper.text()).toContain('Content tab1')
104
- expect(wrapper.text()).not.toContain('Content tab2')
105
- })
120
+ expect(wrapper.text()).toContain("Content tab1");
121
+ expect(wrapper.text()).not.toContain("Content tab2");
122
+ });
106
123
 
107
- it('should render tabs-container-end slot', async () => {
124
+ it("should render tabs-container-end slot", async () => {
108
125
  const wrapper = mount(FzTabs, {
109
126
  props: {
110
- size: 'sm',
127
+ size: "sm",
111
128
  },
112
129
  slots: {
113
- default: () => [h(FzTab, { title: 'tab1' }, 'Content tab1')],
114
- 'tabs-container-end': '<div class="custom-end">End</div>',
130
+ default: () => [h(FzTab, { title: "tab1" }, "Content tab1")],
131
+ "tabs-container-end": '<div class="custom-end">End</div>',
115
132
  },
116
- })
133
+ });
117
134
 
118
- await wrapper.vm.$nextTick()
119
- expect(wrapper.find('.custom-end').exists()).toBe(true)
120
- })
135
+ await wrapper.vm.$nextTick();
136
+ expect(wrapper.find(".custom-end").exists()).toBe(true);
137
+ });
121
138
 
122
- it('should render tabs-end slot', async () => {
139
+ it("should render tabs-end slot", async () => {
123
140
  const wrapper = mount(FzTabs, {
124
141
  props: {
125
- size: 'sm',
142
+ size: "sm",
126
143
  },
127
144
  slots: {
128
- default: () => [h(FzTab, { title: 'tab1' }, 'Content tab1')],
129
- 'tabs-end': '<div class="custom-tabs-end">Tabs End</div>',
145
+ default: () => [h(FzTab, { title: "tab1" }, "Content tab1")],
146
+ "tabs-end": '<div class="custom-tabs-end">Tabs End</div>',
130
147
  },
131
- })
148
+ });
132
149
 
133
- await wrapper.vm.$nextTick()
134
- expect(wrapper.find('.custom-tabs-end').exists()).toBe(true)
135
- })
150
+ await wrapper.vm.$nextTick();
151
+ expect(wrapper.find(".custom-tabs-end").exists()).toBe(true);
152
+ });
136
153
 
137
- it('should render selected tab content slot', async () => {
154
+ it("should render selected tab content slot", async () => {
138
155
  const wrapper = await createWrapper(
139
156
  {
140
- size: 'sm',
157
+ size: "sm",
141
158
  },
142
159
  {
143
- title: 'tab1',
160
+ title: "tab1",
144
161
  },
145
162
  {
146
- title: 'tab2',
163
+ title: "tab2",
147
164
  },
148
- )
165
+ );
149
166
 
150
167
  const slotWrapper = mount(FzTabs, {
151
168
  props: {
152
- size: 'sm',
169
+ size: "sm",
153
170
  },
154
171
  slots: {
155
- default: () => [h(FzTab, { title: 'tab1' }, 'Content tab1')],
172
+ default: () => [h(FzTab, { title: "tab1" }, "Content tab1")],
156
173
  },
157
174
  scopedSlots: {
158
- selected: (props: { selected: string }) => `Selected: ${props.selected}`,
175
+ selected: (props: { selected: string }) =>
176
+ `Selected: ${props.selected}`,
159
177
  },
160
- })
178
+ });
161
179
 
162
- await slotWrapper.vm.$nextTick()
180
+ await slotWrapper.vm.$nextTick();
163
181
  // Note: scopedSlots may not work exactly like this in Vue 3, but testing structure
164
- expect(slotWrapper.exists()).toBe(true)
165
- })
166
- })
182
+ expect(slotWrapper.exists()).toBe(true);
183
+ });
184
+ });
167
185
 
168
186
  // ============================================
169
187
  // PROPS TESTS
170
188
  // ============================================
171
- describe('Props', () => {
172
- describe('size prop', () => {
173
- it('should apply sm size classes', async () => {
189
+ describe("Props", () => {
190
+ describe("size prop (deprecated)", () => {
191
+ it("should apply default size classes (size prop is deprecated)", async () => {
174
192
  const wrapper = await createWrapper(
175
193
  {
176
- size: 'sm',
194
+ size: "sm",
177
195
  },
178
196
  {
179
- title: 'tab1',
197
+ title: "tab1",
180
198
  },
181
199
  {
182
- title: 'tab2',
200
+ title: "tab2",
183
201
  },
184
- )
202
+ );
185
203
 
186
- const button = wrapper.find('button')
187
- expect(button.classes()).toContain('text-sm')
188
- expect(button.classes()).toContain('h-40')
189
- expect(button.classes()).toContain('gap-6')
190
- expect(button.classes()).toContain('py-8')
191
- expect(button.classes()).toContain('px-12')
192
- })
204
+ const button = wrapper.find("button");
205
+ // Size prop is deprecated - component uses default sizing
206
+ expect(button.classes()).toContain("text-md");
207
+ expect(button.classes()).toContain("h-40");
208
+ expect(button.classes()).toContain("gap-8");
209
+ expect(button.classes()).toContain("py-8");
210
+ expect(button.classes()).toContain("px-12");
211
+ });
193
212
 
194
- it('should apply md size classes', async () => {
213
+ it("should apply default size classes when md size is passed (deprecated)", async () => {
195
214
  const wrapper = await createWrapper(
196
215
  {
197
- size: 'md',
216
+ size: "md",
198
217
  },
199
218
  {
200
- title: 'tab1',
219
+ title: "tab1",
201
220
  },
202
221
  {
203
- title: 'tab2',
222
+ title: "tab2",
204
223
  },
205
- )
224
+ );
206
225
 
207
- const button = wrapper.find('button')
208
- expect(button.classes()).toContain('text-md')
209
- expect(button.classes()).toContain('gap-8')
210
- expect(button.classes()).toContain('py-12')
211
- expect(button.classes()).toContain('px-14')
212
- })
213
- })
226
+ const button = wrapper.find("button");
227
+ // Size prop is deprecated - component uses default sizing
228
+ expect(button.classes()).toContain("text-md");
229
+ expect(button.classes()).toContain("gap-8");
230
+ expect(button.classes()).toContain("py-8");
231
+ expect(button.classes()).toContain("px-12");
232
+ });
233
+ });
214
234
 
215
- describe('vertical prop', () => {
216
- it('should apply horizontal layout by default', async () => {
235
+ describe("vertical prop", () => {
236
+ it("should apply horizontal layout by default", async () => {
217
237
  const wrapper = await createWrapper(
218
238
  {
219
- size: 'sm',
239
+ size: "sm",
220
240
  vertical: false,
221
241
  },
222
242
  {
223
- title: 'tab1',
243
+ title: "tab1",
224
244
  },
225
245
  {
226
- title: 'tab2',
246
+ title: "tab2",
227
247
  },
228
- )
248
+ );
229
249
 
230
- const container = wrapper.find('.tab-container')
231
- expect(container.classes()).toContain('flex-row')
232
- expect(wrapper.find('.flex-col').exists()).toBe(true) // wrapper class
233
- })
250
+ const container = wrapper.find(".tab-container");
251
+ expect(container.classes()).toContain("flex-row");
252
+ expect(wrapper.find(".flex-col").exists()).toBe(true); // wrapper class
253
+ });
234
254
 
235
- it('should apply vertical layout when true', async () => {
255
+ it("should apply vertical layout when true", async () => {
236
256
  const wrapper = await createWrapper(
237
257
  {
238
- size: 'sm',
258
+ size: "sm",
239
259
  vertical: true,
240
260
  },
241
261
  {
242
- title: 'tab1',
243
- },
244
- {
245
- title: 'tab2',
246
- },
247
- )
248
-
249
- const container = wrapper.find('.tab-container')
250
- expect(container.classes()).toContain('flex-col')
251
- expect(wrapper.find('.flex-row').exists()).toBe(true) // wrapper class
252
- })
253
- })
254
-
255
- describe('horizontalOverflow prop', () => {
256
- it('should show tab buttons when horizontalOverflow is false and not overflowing', async () => {
257
- const wrapper = await createWrapper(
258
- {
259
- size: 'sm',
260
- horizontalOverflow: false,
261
- },
262
- {
263
- title: 'tab1',
262
+ title: "tab1",
264
263
  },
265
264
  {
266
- title: 'tab2',
265
+ title: "tab2",
267
266
  },
268
- )
267
+ );
269
268
 
270
- // When not overflowing, should show FzTabButton components
271
- const buttons = wrapper.findAll('button[title]')
272
- expect(buttons.length).toBeGreaterThanOrEqual(2)
273
- })
274
- })
269
+ const container = wrapper.find(".tab-container");
270
+ expect(container.classes()).toContain("flex-col");
271
+ expect(wrapper.find(".flex-row").exists()).toBe(true); // wrapper class
272
+ });
273
+ });
275
274
 
276
- describe('FzTab props', () => {
277
- it('should render tab with icon', async () => {
275
+ describe("FzTab props", () => {
276
+ it("should render tab with icon", async () => {
278
277
  const wrapper = await createWrapper(
279
278
  {
280
- size: 'sm',
279
+ size: "sm",
281
280
  },
282
281
  {
283
- title: 'tab1',
284
- icon: 'bell',
282
+ title: "tab1",
283
+ icon: "bell",
285
284
  },
286
285
  {
287
- title: 'tab2',
286
+ title: "tab2",
288
287
  },
289
- )
288
+ );
290
289
 
291
- const icon = wrapper.findComponent({ name: 'FzIcon' })
292
- expect(icon.exists()).toBe(true)
293
- })
290
+ const icon = wrapper.findComponent({ name: "FzIcon" });
291
+ expect(icon.exists()).toBe(true);
292
+ });
294
293
 
295
- it('should render tab with badgeContent', async () => {
294
+ it("should render tab with badgeContent", async () => {
296
295
  const wrapper = await createWrapper(
297
296
  {
298
- size: 'sm',
297
+ size: "sm",
299
298
  },
300
299
  {
301
- title: 'tab1',
302
- badgeContent: '5',
300
+ title: "tab1",
301
+ badgeContent: "5",
303
302
  },
304
303
  {
305
- title: 'tab2',
304
+ title: "tab2",
306
305
  },
307
- )
306
+ );
308
307
 
309
- const badge = wrapper.findComponent({ name: 'FzBadge' })
310
- expect(badge.exists()).toBe(true)
311
- expect(badge.text()).toBe('5')
312
- })
308
+ const badge = wrapper.findComponent({ name: "FzBadge" });
309
+ expect(badge.exists()).toBe(true);
310
+ expect(badge.text()).toBe("5");
311
+ });
313
312
 
314
- it('should render disabled tab', async () => {
313
+ it("should render disabled tab", async () => {
315
314
  const wrapper = await createWrapper(
316
315
  {
317
- size: 'sm',
316
+ size: "sm",
318
317
  },
319
318
  {
320
- title: 'tab1',
319
+ title: "tab1",
321
320
  disabled: true,
322
321
  },
323
322
  {
324
- title: 'tab2',
323
+ title: "tab2",
325
324
  },
326
- )
325
+ );
327
326
 
328
- const button = wrapper.find('button[title="tab1"]')
329
- expect(button.classes()).toContain('cursor-not-allowed')
330
- })
327
+ const button = wrapper.find('button[title="tab1"]');
328
+ expect(button.classes()).toContain("cursor-not-allowed");
329
+ });
331
330
 
332
- it('should select tab with initialSelected prop', async () => {
331
+ it("should select tab with initialSelected prop", async () => {
333
332
  const wrapper = await createWrapper(
334
333
  {
335
- size: 'sm',
334
+ size: "sm",
336
335
  },
337
336
  {
338
- title: 'tab1',
337
+ title: "tab1",
339
338
  },
340
339
  {
341
- title: 'tab2',
340
+ title: "tab2",
342
341
  initialSelected: true,
343
342
  },
344
- )
343
+ );
345
344
 
346
- expect(wrapper.text()).toContain('Content tab2')
347
- expect(wrapper.text()).not.toContain('Content tab1')
348
- })
349
- })
350
- })
345
+ expect(wrapper.text()).toContain("Content tab2");
346
+ expect(wrapper.text()).not.toContain("Content tab1");
347
+ });
348
+ });
349
+ });
351
350
 
352
351
  // ============================================
353
352
  // EVENTS TESTS
354
353
  // ============================================
355
- describe('Events', () => {
356
- it('should emit change event when tab is clicked', async () => {
354
+ describe("Events", () => {
355
+ it("should emit change event when tab is clicked", async () => {
357
356
  const wrapper = await createWrapper(
358
357
  {
359
- size: 'sm',
358
+ size: "sm",
360
359
  },
361
360
  {
362
- title: 'tab1',
361
+ title: "tab1",
363
362
  },
364
363
  {
365
- title: 'tab2',
364
+ title: "tab2",
366
365
  },
367
- )
366
+ );
368
367
 
369
- const tab2Button = wrapper.find('button[title="tab2"]')
370
- await tab2Button.trigger('click')
368
+ const tab2Button = wrapper.find('button[title="tab2"]');
369
+ await tab2Button.trigger("click");
371
370
 
372
- expect(wrapper.emitted('change')).toBeTruthy()
371
+ expect(wrapper.emitted("change")).toBeTruthy();
373
372
  // Change event is emitted on mount (tab1) and on click (tab2)
374
- const changeEvents = wrapper.emitted('change')!
375
- expect(changeEvents[changeEvents.length - 1]).toEqual(['tab2'])
376
- })
373
+ // Event payload: [title, buttonElement]
374
+ const changeEvents = wrapper.emitted("change")!;
375
+ expect(changeEvents[changeEvents.length - 1][0]).toBe("tab2");
376
+ });
377
377
 
378
- it('should emit change event with correct tab title', async () => {
378
+ it("should emit change event with correct tab title", async () => {
379
379
  const wrapper = await createWrapper(
380
380
  {
381
- size: 'sm',
381
+ size: "sm",
382
382
  },
383
383
  {
384
- title: 'tab1',
384
+ title: "tab1",
385
385
  },
386
386
  {
387
- title: 'tab2',
387
+ title: "tab2",
388
388
  },
389
389
  {
390
- title: 'tab3',
390
+ title: "tab3",
391
391
  },
392
- )
392
+ );
393
393
 
394
- const tab3Button = wrapper.find('button[title="tab3"]')
395
- await tab3Button.trigger('click')
394
+ const tab3Button = wrapper.find('button[title="tab3"]');
395
+ await tab3Button.trigger("click");
396
396
 
397
397
  // Change event is emitted on mount (tab1) and on click (tab3)
398
- const changeEvents = wrapper.emitted('change')!
399
- expect(changeEvents[changeEvents.length - 1]).toEqual(['tab3'])
400
- })
398
+ // Event payload: [title, buttonElement]
399
+ const changeEvents = wrapper.emitted("change")!;
400
+ expect(changeEvents[changeEvents.length - 1][0]).toBe("tab3");
401
+ });
401
402
 
402
- it('should not emit change event when disabled tab is clicked', async () => {
403
+ it("should not emit change event when disabled tab is clicked", async () => {
403
404
  const wrapper = await createWrapper(
404
405
  {
405
- size: 'sm',
406
+ size: "sm",
406
407
  },
407
408
  {
408
- title: 'tab1',
409
+ title: "tab1",
409
410
  disabled: true,
410
411
  },
411
412
  {
412
- title: 'tab2',
413
+ title: "tab2",
413
414
  },
414
- )
415
+ );
415
416
 
416
- const tab1Button = wrapper.find('button[title="tab1"]')
417
- const initialEmitted = wrapper.emitted('change')?.length || 0
418
- await tab1Button.trigger('click')
417
+ const tab1Button = wrapper.find('button[title="tab1"]');
418
+ const initialEmitted = wrapper.emitted("change")?.length || 0;
419
+ await tab1Button.trigger("click");
419
420
 
420
421
  // Should not emit change event for disabled tab
421
- const finalEmitted = wrapper.emitted('change')?.length || 0
422
- expect(finalEmitted).toBe(initialEmitted)
423
- })
422
+ const finalEmitted = wrapper.emitted("change")?.length || 0;
423
+ expect(finalEmitted).toBe(initialEmitted);
424
+ });
424
425
 
425
- it('should update selected tab content when change event is emitted', async () => {
426
+ it("should update selected tab content when change event is emitted", async () => {
426
427
  const wrapper = await createWrapper(
427
428
  {
428
- size: 'sm',
429
+ size: "sm",
429
430
  },
430
431
  {
431
- title: 'tab1',
432
+ title: "tab1",
432
433
  },
433
434
  {
434
- title: 'tab2',
435
+ title: "tab2",
435
436
  },
436
- )
437
+ );
437
438
 
438
- expect(wrapper.text()).toContain('Content tab1')
439
- expect(wrapper.text()).not.toContain('Content tab2')
439
+ expect(wrapper.text()).toContain("Content tab1");
440
+ expect(wrapper.text()).not.toContain("Content tab2");
440
441
 
441
- const tab2Button = wrapper.find('button[title="tab2"]')
442
- await tab2Button.trigger('click')
443
- await wrapper.vm.$nextTick()
442
+ const tab2Button = wrapper.find('button[title="tab2"]');
443
+ await tab2Button.trigger("click");
444
+ await wrapper.vm.$nextTick();
444
445
 
445
- expect(wrapper.text()).toContain('Content tab2')
446
- expect(wrapper.text()).not.toContain('Content tab1')
447
- })
446
+ expect(wrapper.text()).toContain("Content tab2");
447
+ expect(wrapper.text()).not.toContain("Content tab1");
448
+ });
448
449
 
449
- it('should emit change event on mount with initial selected tab', async () => {
450
+ it("should emit change event on mount with initial selected tab", async () => {
450
451
  const wrapper = await createWrapper(
451
452
  {
452
- size: 'sm',
453
+ size: "sm",
453
454
  },
454
455
  {
455
- title: 'tab1',
456
+ title: "tab1",
456
457
  },
457
458
  {
458
- title: 'tab2',
459
+ title: "tab2",
459
460
  },
460
- )
461
+ );
461
462
 
462
463
  // Change event should be emitted on mount with the initially selected tab
463
- expect(wrapper.emitted('change')).toBeTruthy()
464
- const changeEvents = wrapper.emitted('change')!
465
- expect(changeEvents[0]).toEqual(['tab1'])
466
- })
464
+ // Event payload: [title, buttonElement]
465
+ expect(wrapper.emitted("change")).toBeTruthy();
466
+ const changeEvents = wrapper.emitted("change")!;
467
+ expect(changeEvents[0][0]).toBe("tab1");
468
+ });
467
469
 
468
- it('should emit change event with initialSelected tab on mount', async () => {
470
+ it("should emit change event with initialSelected tab on mount", async () => {
469
471
  const wrapper = await createWrapper(
470
472
  {
471
- size: 'sm',
473
+ size: "sm",
472
474
  },
473
475
  {
474
- title: 'tab1',
476
+ title: "tab1",
475
477
  },
476
478
  {
477
- title: 'tab2',
479
+ title: "tab2",
478
480
  initialSelected: true,
479
481
  },
480
- )
482
+ );
481
483
 
482
484
  // Change event should be emitted on mount with the tab marked as initialSelected
483
- expect(wrapper.emitted('change')).toBeTruthy()
484
- const changeEvents = wrapper.emitted('change')!
485
- expect(changeEvents[0]).toEqual(['tab2'])
486
- })
485
+ // Event payload: [title, buttonElement]
486
+ expect(wrapper.emitted("change")).toBeTruthy();
487
+ const changeEvents = wrapper.emitted("change")!;
488
+ expect(changeEvents[0][0]).toBe("tab2");
489
+ });
487
490
 
488
- it('should emit change event payload as string (tab title)', async () => {
491
+ it("should emit change event payload with title and button element", async () => {
489
492
  const wrapper = await createWrapper(
490
493
  {
491
- size: 'sm',
494
+ size: "sm",
492
495
  },
493
496
  {
494
- title: 'tab1',
497
+ title: "tab1",
495
498
  },
496
499
  {
497
- title: 'tab2',
500
+ title: "tab2",
498
501
  },
499
- )
502
+ );
500
503
 
501
- const tab2Button = wrapper.find('button[title="tab2"]')
502
- await tab2Button.trigger('click')
504
+ const tab2Button = wrapper.find('button[title="tab2"]');
505
+ await tab2Button.trigger("click");
503
506
 
504
- const changeEvents = wrapper.emitted('change')!
505
- const lastEvent = changeEvents[changeEvents.length - 1]
506
- expect(lastEvent).toHaveLength(1)
507
- expect(typeof lastEvent[0]).toBe('string')
508
- expect(lastEvent[0]).toBe('tab2')
509
- })
507
+ const changeEvents = wrapper.emitted("change")!;
508
+ const lastEvent = changeEvents[changeEvents.length - 1];
509
+ // Event payload: [title, buttonElement]
510
+ expect(lastEvent).toHaveLength(2);
511
+ expect(typeof lastEvent[0]).toBe("string");
512
+ expect(lastEvent[0]).toBe("tab2");
513
+ });
510
514
 
511
- it('should emit change event multiple times when switching between tabs', async () => {
515
+ it("should emit change event multiple times when switching between tabs", async () => {
512
516
  const wrapper = await createWrapper(
513
517
  {
514
- size: 'sm',
518
+ size: "sm",
515
519
  },
516
520
  {
517
- title: 'tab1',
521
+ title: "tab1",
518
522
  },
519
523
  {
520
- title: 'tab2',
524
+ title: "tab2",
521
525
  },
522
526
  {
523
- title: 'tab3',
527
+ title: "tab3",
524
528
  },
525
- )
529
+ );
526
530
 
527
- const initialCount = wrapper.emitted('change')?.length || 0
531
+ const initialCount = wrapper.emitted("change")?.length || 0;
528
532
 
529
- const tab2Button = wrapper.find('button[title="tab2"]')
530
- await tab2Button.trigger('click')
533
+ const tab2Button = wrapper.find('button[title="tab2"]');
534
+ await tab2Button.trigger("click");
531
535
 
532
- const tab3Button = wrapper.find('button[title="tab3"]')
533
- await tab3Button.trigger('click')
536
+ const tab3Button = wrapper.find('button[title="tab3"]');
537
+ await tab3Button.trigger("click");
534
538
 
535
- const tab1Button = wrapper.find('button[title="tab1"]')
536
- await tab1Button.trigger('click')
539
+ const tab1Button = wrapper.find('button[title="tab1"]');
540
+ await tab1Button.trigger("click");
537
541
 
538
- const finalCount = wrapper.emitted('change')?.length || 0
539
- expect(finalCount).toBe(initialCount + 3)
542
+ const finalCount = wrapper.emitted("change")?.length || 0;
543
+ expect(finalCount).toBe(initialCount + 3);
540
544
 
541
- const changeEvents = wrapper.emitted('change')!
542
- expect(changeEvents[changeEvents.length - 3]).toEqual(['tab2'])
543
- expect(changeEvents[changeEvents.length - 2]).toEqual(['tab3'])
544
- expect(changeEvents[changeEvents.length - 1]).toEqual(['tab1'])
545
- })
546
- })
545
+ // Event payload: [title, buttonElement]
546
+ const changeEvents = wrapper.emitted("change")!;
547
+ expect(changeEvents[changeEvents.length - 3][0]).toBe("tab2");
548
+ expect(changeEvents[changeEvents.length - 2][0]).toBe("tab3");
549
+ expect(changeEvents[changeEvents.length - 1][0]).toBe("tab1");
550
+ });
551
+ });
547
552
 
548
553
  // ============================================
549
554
  // ACCESSIBILITY TESTS
550
555
  // ============================================
551
- describe('Accessibility', () => {
552
- describe('ARIA attributes', () => {
556
+ describe("Accessibility", () => {
557
+ describe("ARIA attributes", () => {
553
558
  it('should have role="tablist" on tab container', async () => {
554
559
  const wrapper = await createWrapper(
555
560
  {
556
- size: 'sm',
561
+ size: "sm",
557
562
  },
558
563
  {
559
- title: 'tab1',
564
+ title: "tab1",
560
565
  },
561
566
  {
562
- title: 'tab2',
567
+ title: "tab2",
563
568
  },
564
- )
569
+ );
565
570
 
566
- const container = wrapper.find('.tab-container')
571
+ const container = wrapper.find(".tab-container");
567
572
  // Note: Current implementation may not have role="tablist" - documenting expected behavior
568
573
  // This test documents the expected ARIA pattern for tabs
569
- expect(container.exists()).toBe(true)
570
- })
574
+ expect(container.exists()).toBe(true);
575
+ });
571
576
 
572
577
  it('should have role="tab" on tab buttons', async () => {
573
578
  const wrapper = await createWrapper(
574
579
  {
575
- size: 'sm',
580
+ size: "sm",
576
581
  },
577
582
  {
578
- title: 'tab1',
583
+ title: "tab1",
579
584
  },
580
585
  {
581
- title: 'tab2',
586
+ title: "tab2",
582
587
  },
583
- )
588
+ );
584
589
 
585
- const buttons = wrapper.findAll('button[title]')
590
+ const buttons = wrapper.findAll("button[title]");
586
591
  // Note: Current implementation uses title attribute - documenting expected role="tab"
587
- expect(buttons.length).toBeGreaterThanOrEqual(2)
588
- })
592
+ expect(buttons.length).toBeGreaterThanOrEqual(2);
593
+ });
589
594
 
590
595
  it('should have aria-selected="true" on selected tab', async () => {
591
596
  const wrapper = await createWrapper(
592
597
  {
593
- size: 'sm',
598
+ size: "sm",
594
599
  },
595
600
  {
596
- title: 'tab1',
601
+ title: "tab1",
597
602
  },
598
603
  {
599
- title: 'tab2',
604
+ title: "tab2",
600
605
  },
601
- )
606
+ );
602
607
 
603
- const selectedButton = wrapper.find('button[title="tab1"]')
608
+ const selectedButton = wrapper.find('button[title="tab1"]');
604
609
  // Note: Current implementation may not have aria-selected - documenting expected behavior
605
- expect(selectedButton.exists()).toBe(true)
606
- })
610
+ expect(selectedButton.exists()).toBe(true);
611
+ });
607
612
 
608
613
  it('should have aria-selected="false" on unselected tabs', async () => {
609
614
  const wrapper = await createWrapper(
610
615
  {
611
- size: 'sm',
616
+ size: "sm",
612
617
  },
613
618
  {
614
- title: 'tab1',
619
+ title: "tab1",
615
620
  },
616
621
  {
617
- title: 'tab2',
622
+ title: "tab2",
618
623
  },
619
- )
624
+ );
620
625
 
621
- const unselectedButton = wrapper.find('button[title="tab2"]')
626
+ const unselectedButton = wrapper.find('button[title="tab2"]');
622
627
  // Note: Current implementation may not have aria-selected - documenting expected behavior
623
- expect(unselectedButton.exists()).toBe(true)
624
- })
628
+ expect(unselectedButton.exists()).toBe(true);
629
+ });
625
630
 
626
- it('should have aria-controls linking to tabpanel', async () => {
631
+ it("should have aria-controls linking to tabpanel", async () => {
627
632
  const wrapper = await createWrapper(
628
633
  {
629
- size: 'sm',
634
+ size: "sm",
630
635
  },
631
636
  {
632
- title: 'tab1',
637
+ title: "tab1",
633
638
  },
634
639
  {
635
- title: 'tab2',
640
+ title: "tab2",
636
641
  },
637
- )
642
+ );
638
643
 
639
- const button = wrapper.find('button[title="tab1"]')
644
+ const button = wrapper.find('button[title="tab1"]');
640
645
  // Note: Current implementation may not have aria-controls - documenting expected behavior
641
- expect(button.exists()).toBe(true)
642
- })
646
+ expect(button.exists()).toBe(true);
647
+ });
643
648
 
644
649
  it('should have role="tabpanel" on tab content', async () => {
645
650
  const wrapper = await createWrapper(
646
651
  {
647
- size: 'sm',
652
+ size: "sm",
648
653
  },
649
654
  {
650
- title: 'tab1',
655
+ title: "tab1",
651
656
  },
652
657
  {
653
- title: 'tab2',
658
+ title: "tab2",
654
659
  },
655
- )
660
+ );
656
661
 
657
662
  // Note: Current implementation may not have role="tabpanel" - documenting expected behavior
658
- expect(wrapper.text()).toContain('Content tab1')
659
- })
663
+ expect(wrapper.text()).toContain("Content tab1");
664
+ });
660
665
 
661
- it('should have aria-disabled on disabled tabs', async () => {
666
+ it("should have aria-disabled on disabled tabs", async () => {
662
667
  const wrapper = await createWrapper(
663
668
  {
664
- size: 'sm',
669
+ size: "sm",
665
670
  },
666
671
  {
667
- title: 'tab1',
672
+ title: "tab1",
668
673
  disabled: true,
669
674
  },
670
675
  {
671
- title: 'tab2',
676
+ title: "tab2",
672
677
  },
673
- )
678
+ );
674
679
 
675
- const disabledButton = wrapper.find('button[title="tab1"]')
680
+ const disabledButton = wrapper.find('button[title="tab1"]');
676
681
  // Note: Current implementation may not have aria-disabled - documenting expected behavior
677
- expect(disabledButton.exists()).toBe(true)
678
- expect(disabledButton.classes()).toContain('cursor-not-allowed')
679
- })
682
+ expect(disabledButton.exists()).toBe(true);
683
+ expect(disabledButton.classes()).toContain("cursor-not-allowed");
684
+ });
680
685
 
681
- it('should hide decorative icons from screen readers', async () => {
686
+ it("should hide decorative icons from screen readers", async () => {
682
687
  const wrapper = await createWrapper(
683
688
  {
684
- size: 'sm',
689
+ size: "sm",
685
690
  },
686
691
  {
687
- title: 'tab1',
688
- icon: 'bell',
692
+ title: "tab1",
693
+ icon: "bell",
689
694
  },
690
695
  {
691
- title: 'tab2',
696
+ title: "tab2",
692
697
  },
693
- )
698
+ );
694
699
 
695
- const icon = wrapper.findComponent({ name: 'FzIcon' })
700
+ const icon = wrapper.findComponent({ name: "FzIcon" });
696
701
  if (icon.exists()) {
697
702
  // FzIcon should handle aria-hidden internally, but documenting expectation
698
- expect(icon.exists()).toBe(true)
703
+ expect(icon.exists()).toBe(true);
699
704
  }
700
- })
701
- })
705
+ });
706
+ });
702
707
 
703
- describe('Keyboard navigation', () => {
704
- it('should be focusable when not disabled', async () => {
708
+ describe("Keyboard navigation", () => {
709
+ it("should be focusable when not disabled", async () => {
705
710
  const wrapper = await createWrapper(
706
711
  {
707
- size: 'sm',
712
+ size: "sm",
708
713
  },
709
714
  {
710
- title: 'tab1',
715
+ title: "tab1",
711
716
  },
712
717
  {
713
- title: 'tab2',
718
+ title: "tab2",
714
719
  },
715
- )
720
+ );
716
721
 
717
- const button = wrapper.find('button[title="tab1"]')
722
+ const button = wrapper.find('button[title="tab1"]');
718
723
  // Buttons are naturally focusable
719
- expect(button.element.tagName).toBe('BUTTON')
720
- })
724
+ expect(button.element.tagName).toBe("BUTTON");
725
+ });
721
726
 
722
- it('should support arrow key navigation between tabs', async () => {
727
+ it("should support arrow key navigation between tabs", async () => {
723
728
  const wrapper = await createWrapper(
724
729
  {
725
- size: 'sm',
730
+ size: "sm",
726
731
  },
727
732
  {
728
- title: 'tab1',
733
+ title: "tab1",
729
734
  },
730
735
  {
731
- title: 'tab2',
736
+ title: "tab2",
732
737
  },
733
- )
738
+ );
734
739
 
735
- const tab1Button = wrapper.find('button[title="tab1"]')
740
+ const tab1Button = wrapper.find('button[title="tab1"]');
736
741
  // Note: Current implementation may not support arrow keys - documenting expected behavior
737
- expect(tab1Button.exists()).toBe(true)
738
- })
742
+ expect(tab1Button.exists()).toBe(true);
743
+ });
739
744
 
740
- it('should support Enter key to activate tab', async () => {
745
+ it("should support Enter key to activate tab", async () => {
741
746
  const wrapper = await createWrapper(
742
747
  {
743
- size: 'sm',
748
+ size: "sm",
744
749
  },
745
750
  {
746
- title: 'tab1',
751
+ title: "tab1",
747
752
  },
748
753
  {
749
- title: 'tab2',
754
+ title: "tab2",
750
755
  },
751
- )
756
+ );
752
757
 
753
- const tab2Button = wrapper.find('button[title="tab2"]')
754
- await tab2Button.trigger('keydown', { key: 'Enter' })
758
+ const tab2Button = wrapper.find('button[title="tab2"]');
759
+ await tab2Button.trigger("keydown", { key: "Enter" });
755
760
  // Enter key on button should trigger click
756
- expect(tab2Button.exists()).toBe(true)
757
- })
761
+ expect(tab2Button.exists()).toBe(true);
762
+ });
758
763
 
759
- it('should support Space key to activate tab', async () => {
764
+ it("should support Space key to activate tab", async () => {
760
765
  const wrapper = await createWrapper(
761
766
  {
762
- size: 'sm',
767
+ size: "sm",
763
768
  },
764
769
  {
765
- title: 'tab1',
770
+ title: "tab1",
766
771
  },
767
772
  {
768
- title: 'tab2',
773
+ title: "tab2",
769
774
  },
770
- )
775
+ );
771
776
 
772
- const tab2Button = wrapper.find('button[title="tab2"]')
773
- await tab2Button.trigger('keydown', { key: ' ' })
777
+ const tab2Button = wrapper.find('button[title="tab2"]');
778
+ await tab2Button.trigger("keydown", { key: " " });
774
779
  // Space key on button should trigger click
775
- expect(tab2Button.exists()).toBe(true)
776
- })
777
- })
780
+ expect(tab2Button.exists()).toBe(true);
781
+ });
782
+ });
778
783
 
779
- describe('Semantic HTML structure', () => {
780
- it('should use semantic button elements for tabs', async () => {
784
+ describe("Semantic HTML structure", () => {
785
+ it("should use semantic button elements for tabs", async () => {
781
786
  const wrapper = await createWrapper(
782
787
  {
783
- size: 'sm',
788
+ size: "sm",
784
789
  },
785
790
  {
786
- title: 'tab1',
791
+ title: "tab1",
787
792
  },
788
793
  {
789
- title: 'tab2',
794
+ title: "tab2",
790
795
  },
791
- )
796
+ );
792
797
 
793
- const buttons = wrapper.findAll('button')
794
- expect(buttons.length).toBeGreaterThanOrEqual(2)
795
- })
798
+ const buttons = wrapper.findAll("button");
799
+ expect(buttons.length).toBeGreaterThanOrEqual(2);
800
+ });
796
801
 
797
- it('should have accessible labels via title attribute', async () => {
802
+ it("should have accessible labels via title attribute", async () => {
798
803
  const wrapper = await createWrapper(
799
804
  {
800
- size: 'sm',
805
+ size: "sm",
801
806
  },
802
807
  {
803
- title: 'tab1',
808
+ title: "tab1",
804
809
  },
805
810
  {
806
- title: 'tab2',
811
+ title: "tab2",
807
812
  },
808
- )
813
+ );
809
814
 
810
- const button = wrapper.find('button[title="tab1"]')
811
- expect(button.attributes('title')).toBe('tab1')
812
- })
813
- })
814
- })
815
+ const button = wrapper.find('button[title="tab1"]');
816
+ expect(button.attributes("title")).toBe("tab1");
817
+ });
818
+ });
819
+ });
815
820
 
816
821
  // ============================================
817
822
  // CSS CLASSES TESTS
818
823
  // ============================================
819
- describe('CSS Classes', () => {
820
- it('should apply static base classes to container', async () => {
824
+ describe("CSS Classes", () => {
825
+ it("should apply static base classes to container", async () => {
821
826
  const wrapper = await createWrapper(
822
827
  {
823
- size: 'sm',
828
+ size: "sm",
824
829
  },
825
830
  {
826
- title: 'tab1',
831
+ title: "tab1",
827
832
  },
828
833
  {
829
- title: 'tab2',
834
+ title: "tab2",
830
835
  },
831
- )
836
+ );
832
837
 
833
- const container = wrapper.find('.tab-container')
834
- expect(container.classes()).toContain('flex')
835
- expect(container.classes()).toContain('rounded-lg')
836
- expect(container.classes()).toContain('bg-grey-100')
837
- })
838
+ const container = wrapper.find(".tab-container");
839
+ expect(container.classes()).toContain("flex");
840
+ expect(container.classes()).toContain("rounded-lg");
841
+ expect(container.classes()).toContain("bg-grey-100");
842
+ });
838
843
 
839
- it('should apply selected tab classes', async () => {
844
+ it("should apply selected tab classes", async () => {
840
845
  const wrapper = await createWrapper(
841
846
  {
842
- size: 'sm',
847
+ size: "sm",
843
848
  },
844
849
  {
845
- title: 'tab1',
850
+ title: "tab1",
846
851
  },
847
852
  {
848
- title: 'tab2',
853
+ title: "tab2",
849
854
  },
850
- )
855
+ );
851
856
 
852
- const selectedButton = wrapper.find('button[title="tab1"]')
853
- expect(selectedButton.classes()).toContain('bg-white')
854
- expect(selectedButton.classes()).toContain('text-blue-500')
855
- })
857
+ const selectedButton = wrapper.find('button[title="tab1"]');
858
+ expect(selectedButton.classes()).toContain("bg-white");
859
+ expect(selectedButton.classes()).toContain("text-blue-500");
860
+ });
856
861
 
857
- it('should apply unselected tab classes', async () => {
862
+ it("should apply unselected tab classes", async () => {
858
863
  const wrapper = await createWrapper(
859
864
  {
860
- size: 'sm',
865
+ size: "sm",
861
866
  },
862
867
  {
863
- title: 'tab1',
868
+ title: "tab1",
864
869
  },
865
870
  {
866
- title: 'tab2',
871
+ title: "tab2",
867
872
  },
868
- )
873
+ );
869
874
 
870
- const unselectedButton = wrapper.find('button[title="tab2"]')
871
- expect(unselectedButton.classes()).toContain('text-grey-500')
872
- expect(unselectedButton.classes()).toContain('bg-grey-100')
873
- })
875
+ const unselectedButton = wrapper.find('button[title="tab2"]');
876
+ expect(unselectedButton.classes()).toContain("text-grey-500");
877
+ expect(unselectedButton.classes()).toContain("bg-grey-100");
878
+ });
874
879
 
875
- it('should apply disabled tab classes', async () => {
880
+ it("should apply disabled tab classes", async () => {
876
881
  const wrapper = await createWrapper(
877
882
  {
878
- size: 'sm',
883
+ size: "sm",
879
884
  },
880
885
  {
881
- title: 'tab1',
886
+ title: "tab1",
882
887
  disabled: true,
883
888
  },
884
889
  {
885
- title: 'tab2',
890
+ title: "tab2",
886
891
  },
887
- )
892
+ );
888
893
 
889
- const disabledButton = wrapper.find('button[title="tab1"]')
890
- expect(disabledButton.classes()).toContain('cursor-not-allowed')
891
- })
894
+ const disabledButton = wrapper.find('button[title="tab1"]');
895
+ expect(disabledButton.classes()).toContain("cursor-not-allowed");
896
+ });
892
897
 
893
- it('should apply vertical layout classes when vertical prop is true', async () => {
898
+ it("should apply vertical layout classes when vertical prop is true", async () => {
894
899
  const wrapper = await createWrapper(
895
900
  {
896
- size: 'sm',
901
+ size: "sm",
897
902
  vertical: true,
898
903
  },
899
904
  {
900
- title: 'tab1',
905
+ title: "tab1",
901
906
  },
902
907
  {
903
- title: 'tab2',
908
+ title: "tab2",
904
909
  },
905
- )
910
+ );
906
911
 
907
- const container = wrapper.find('.tab-container')
908
- expect(container.classes()).toContain('flex-col')
909
- })
912
+ const container = wrapper.find(".tab-container");
913
+ expect(container.classes()).toContain("flex-col");
914
+ });
910
915
 
911
- it('should apply horizontal layout classes when vertical prop is false', async () => {
916
+ it("should apply horizontal layout classes when vertical prop is false", async () => {
912
917
  const wrapper = await createWrapper(
913
918
  {
914
- size: 'sm',
919
+ size: "sm",
915
920
  vertical: false,
916
921
  },
917
922
  {
918
- title: 'tab1',
923
+ title: "tab1",
919
924
  },
920
925
  {
921
- title: 'tab2',
926
+ title: "tab2",
922
927
  },
923
- )
928
+ );
924
929
 
925
- const container = wrapper.find('.tab-container')
926
- expect(container.classes()).toContain('flex-row')
927
- })
928
- })
930
+ const container = wrapper.find(".tab-container");
931
+ expect(container.classes()).toContain("flex-row");
932
+ });
933
+ });
929
934
 
930
935
  // ============================================
931
936
  // EDGE CASES
932
937
  // ============================================
933
- describe('Edge Cases', () => {
934
- it('should handle empty tabs array gracefully', async () => {
938
+ describe("Edge Cases", () => {
939
+ it("should handle empty tabs array gracefully", async () => {
935
940
  const wrapper = mount(FzTabs, {
936
941
  props: {
937
- size: 'sm',
942
+ size: "sm",
943
+ isDebug: true,
938
944
  },
939
945
  slots: {
940
946
  default: () => [],
941
947
  },
942
- })
948
+ });
943
949
 
944
- await wrapper.vm.$nextTick()
945
- expect(wrapper.exists()).toBe(true)
950
+ await wrapper.vm.$nextTick();
951
+ expect(wrapper.exists()).toBe(true);
946
952
  expect(console.warn).toHaveBeenCalledWith(
947
- expect.stringContaining('FzTabs must have at least one FzTab child'),
948
- )
949
- })
953
+ expect.stringContaining("FzTabs must have at least one FzTab child"),
954
+ );
955
+ });
950
956
 
951
- it('should handle duplicate tab titles with warning', async () => {
957
+ it("should handle duplicate tab titles with warning", async () => {
952
958
  const wrapper = await createWrapper(
953
959
  {
954
- size: 'sm',
960
+ size: "sm",
961
+ isDebug: true,
955
962
  },
956
963
  {
957
- title: 'tab1',
964
+ title: "tab1",
958
965
  },
959
966
  {
960
- title: 'tab1',
967
+ title: "tab1",
961
968
  },
962
- )
969
+ );
963
970
 
964
- expect(wrapper.exists()).toBe(true)
971
+ expect(wrapper.exists()).toBe(true);
965
972
  expect(console.warn).toHaveBeenCalledWith(
966
- expect.stringContaining('duplicate titles'),
967
- )
968
- })
973
+ expect.stringContaining("duplicate titles"),
974
+ );
975
+ });
969
976
 
970
- it('should select first tab when no initialSelected is provided', async () => {
977
+ it("should select first tab when no initialSelected is provided", async () => {
971
978
  const wrapper = await createWrapper(
972
979
  {
973
- size: 'sm',
980
+ size: "sm",
974
981
  },
975
982
  {
976
- title: 'tab1',
983
+ title: "tab1",
977
984
  },
978
985
  {
979
- title: 'tab2',
986
+ title: "tab2",
980
987
  },
981
- )
988
+ );
982
989
 
983
- expect(wrapper.text()).toContain('Content tab1')
984
- expect(wrapper.text()).not.toContain('Content tab2')
985
- })
990
+ expect(wrapper.text()).toContain("Content tab1");
991
+ expect(wrapper.text()).not.toContain("Content tab2");
992
+ });
986
993
 
987
- it('should handle tab with very long title', async () => {
994
+ it("should handle tab with very long title", async () => {
988
995
  const wrapper = await createWrapper(
989
996
  {
990
- size: 'sm',
997
+ size: "sm",
991
998
  },
992
999
  {
993
- title: 'This is a very long tab title that might overflow',
1000
+ title: "This is a very long tab title that might overflow",
994
1001
  },
995
1002
  {
996
- title: 'tab2',
1003
+ title: "tab2",
997
1004
  },
998
- )
1005
+ );
999
1006
 
1000
- const button = wrapper.find('button[title="This is a very long tab title that might overflow"]')
1001
- expect(button.exists()).toBe(true)
1002
- })
1007
+ const button = wrapper.find(
1008
+ 'button[title="This is a very long tab title that might overflow"]',
1009
+ );
1010
+ expect(button.exists()).toBe(true);
1011
+ });
1003
1012
 
1004
- it('should handle rapid tab switching', async () => {
1013
+ it("should handle rapid tab switching", async () => {
1005
1014
  const wrapper = await createWrapper(
1006
1015
  {
1007
- size: 'sm',
1016
+ size: "sm",
1008
1017
  },
1009
1018
  {
1010
- title: 'tab1',
1019
+ title: "tab1",
1011
1020
  },
1012
1021
  {
1013
- title: 'tab2',
1022
+ title: "tab2",
1014
1023
  },
1015
1024
  {
1016
- title: 'tab3',
1025
+ title: "tab3",
1017
1026
  },
1018
- )
1027
+ );
1019
1028
 
1020
- const tab2Button = wrapper.find('button[title="tab2"]')
1021
- const tab3Button = wrapper.find('button[title="tab3"]')
1029
+ const tab2Button = wrapper.find('button[title="tab2"]');
1030
+ const tab3Button = wrapper.find('button[title="tab3"]');
1022
1031
 
1023
- await tab2Button.trigger('click')
1024
- await wrapper.vm.$nextTick()
1025
- await tab3Button.trigger('click')
1026
- await wrapper.vm.$nextTick()
1032
+ await tab2Button.trigger("click");
1033
+ await wrapper.vm.$nextTick();
1034
+ await tab3Button.trigger("click");
1035
+ await wrapper.vm.$nextTick();
1027
1036
 
1028
- expect(wrapper.emitted('change')).toBeTruthy()
1029
- expect(wrapper.emitted('change')!.length).toBeGreaterThanOrEqual(2)
1030
- })
1037
+ expect(wrapper.emitted("change")).toBeTruthy();
1038
+ expect(wrapper.emitted("change")!.length).toBeGreaterThanOrEqual(2);
1039
+ });
1031
1040
 
1032
- it('should handle undefined props gracefully', async () => {
1041
+ it("should handle undefined props gracefully", async () => {
1033
1042
  const wrapper = await createWrapper(
1034
1043
  {
1035
- size: 'sm',
1044
+ size: "sm",
1036
1045
  },
1037
1046
  {
1038
- title: 'tab1',
1047
+ title: "tab1",
1039
1048
  },
1040
1049
  {
1041
- title: 'tab2',
1050
+ title: "tab2",
1042
1051
  },
1043
- )
1052
+ );
1044
1053
 
1045
- expect(wrapper.exists()).toBe(true)
1046
- })
1054
+ expect(wrapper.exists()).toBe(true);
1055
+ });
1047
1056
 
1048
- it('should handle tab removal gracefully', async () => {
1057
+ it("should handle tab removal gracefully", async () => {
1049
1058
  const wrapper = await createWrapper(
1050
1059
  {
1051
- size: 'sm',
1060
+ size: "sm",
1052
1061
  },
1053
1062
  {
1054
- title: 'tab1',
1063
+ title: "tab1",
1055
1064
  },
1056
1065
  {
1057
- title: 'tab2',
1066
+ title: "tab2",
1058
1067
  },
1059
- )
1068
+ );
1060
1069
 
1061
1070
  // Simulate tab being removed (selected tab)
1062
- const tab1Button = wrapper.find('button[title="tab1"]')
1063
- await tab1Button.trigger('click')
1064
- await wrapper.vm.$nextTick()
1071
+ const tab1Button = wrapper.find('button[title="tab1"]');
1072
+ await tab1Button.trigger("click");
1073
+ await wrapper.vm.$nextTick();
1065
1074
 
1066
1075
  // Component should handle this gracefully
1067
- expect(wrapper.exists()).toBe(true)
1068
- })
1069
- })
1076
+ expect(wrapper.exists()).toBe(true);
1077
+ });
1078
+ });
1070
1079
 
1071
1080
  // ============================================
1072
1081
  // SNAPSHOTS
1073
1082
  // ============================================
1074
- describe('Snapshots', () => {
1075
- it('should match snapshot - default state', async () => {
1083
+ describe("Snapshots", () => {
1084
+ it("should match snapshot - default state", async () => {
1076
1085
  const wrapper = await createWrapper(
1077
1086
  {
1078
- size: 'sm',
1087
+ size: "sm",
1079
1088
  },
1080
1089
  {
1081
- title: 'tab1',
1090
+ title: "tab1",
1082
1091
  },
1083
1092
  {
1084
- title: 'tab2',
1093
+ title: "tab2",
1085
1094
  },
1086
- )
1095
+ );
1087
1096
 
1088
- expect(wrapper.html()).toMatchSnapshot()
1089
- })
1097
+ expect(wrapper.html()).toMatchSnapshot();
1098
+ });
1090
1099
 
1091
- it('should match snapshot - md size', async () => {
1100
+ it("should match snapshot - md size", async () => {
1092
1101
  const wrapper = await createWrapper(
1093
1102
  {
1094
- size: 'md',
1103
+ size: "md",
1095
1104
  },
1096
1105
  {
1097
- title: 'tab1',
1106
+ title: "tab1",
1098
1107
  },
1099
1108
  {
1100
- title: 'tab2',
1109
+ title: "tab2",
1101
1110
  },
1102
- )
1111
+ );
1103
1112
 
1104
- expect(wrapper.html()).toMatchSnapshot()
1105
- })
1113
+ expect(wrapper.html()).toMatchSnapshot();
1114
+ });
1106
1115
 
1107
- it('should match snapshot - with badgeContent', async () => {
1116
+ it("should match snapshot - with badgeContent", async () => {
1108
1117
  const wrapper = await createWrapper(
1109
1118
  {
1110
- size: 'sm',
1119
+ size: "sm",
1111
1120
  },
1112
1121
  {
1113
- title: 'tab1',
1114
- badgeContent: '1',
1122
+ title: "tab1",
1123
+ badgeContent: "1",
1115
1124
  },
1116
1125
  {
1117
- title: 'tab2',
1126
+ title: "tab2",
1118
1127
  },
1119
- )
1128
+ );
1120
1129
 
1121
- expect(wrapper.html()).toMatchSnapshot()
1122
- })
1130
+ expect(wrapper.html()).toMatchSnapshot();
1131
+ });
1123
1132
 
1124
- it('should match snapshot - with icon', async () => {
1133
+ it("should match snapshot - with icon", async () => {
1125
1134
  const wrapper = await createWrapper(
1126
1135
  {
1127
- size: 'sm',
1136
+ size: "sm",
1128
1137
  },
1129
1138
  {
1130
- title: 'tab1',
1131
- icon: 'bell',
1139
+ title: "tab1",
1140
+ icon: "bell",
1132
1141
  },
1133
1142
  {
1134
- title: 'tab2',
1143
+ title: "tab2",
1135
1144
  },
1136
- )
1145
+ );
1137
1146
 
1138
- expect(wrapper.html()).toMatchSnapshot()
1139
- })
1147
+ expect(wrapper.html()).toMatchSnapshot();
1148
+ });
1140
1149
 
1141
- it('should match snapshot - tab change', async () => {
1150
+ it("should match snapshot - tab change", async () => {
1142
1151
  const wrapper = await createWrapper(
1143
1152
  {
1144
- size: 'sm',
1153
+ size: "sm",
1145
1154
  },
1146
1155
  {
1147
- title: 'tab1',
1156
+ title: "tab1",
1148
1157
  },
1149
1158
  {
1150
- title: 'tab2',
1159
+ title: "tab2",
1151
1160
  },
1152
- )
1161
+ );
1153
1162
 
1154
- await wrapper.findAll('button[title="tab2"]')[0].trigger('click')
1155
- expect(wrapper.html()).toMatchSnapshot()
1156
- })
1163
+ await wrapper.findAll('button[title="tab2"]')[0].trigger("click");
1164
+ expect(wrapper.html()).toMatchSnapshot();
1165
+ });
1157
1166
 
1158
- it('should match snapshot - vertical direction', async () => {
1167
+ it("should match snapshot - vertical direction", async () => {
1159
1168
  const wrapper = await createWrapper(
1160
1169
  {
1161
- size: 'sm',
1170
+ size: "sm",
1162
1171
  vertical: true,
1163
1172
  },
1164
1173
  {
1165
- title: 'tab1',
1174
+ title: "tab1",
1166
1175
  },
1167
1176
  {
1168
- title: 'tab2',
1177
+ title: "tab2",
1169
1178
  },
1170
- )
1179
+ );
1171
1180
 
1172
- expect(wrapper.html()).toMatchSnapshot()
1173
- })
1181
+ expect(wrapper.html()).toMatchSnapshot();
1182
+ });
1174
1183
 
1175
- it('should match snapshot - disabled tab', async () => {
1184
+ it("should match snapshot - disabled tab", async () => {
1176
1185
  const wrapper = await createWrapper(
1177
1186
  {
1178
- size: 'sm',
1187
+ size: "sm",
1179
1188
  },
1180
1189
  {
1181
- title: 'tab1',
1190
+ title: "tab1",
1182
1191
  disabled: true,
1183
1192
  },
1184
1193
  {
1185
- title: 'tab2',
1194
+ title: "tab2",
1186
1195
  },
1187
- )
1196
+ );
1197
+
1198
+ expect(wrapper.html()).toMatchSnapshot();
1199
+ });
1200
+
1201
+ it("should match snapshot - tabStyle scroll", async () => {
1202
+ const wrapper = await createWrapper(
1203
+ { tabStyle: "scroll" },
1204
+ { title: "tab1" },
1205
+ { title: "tab2" },
1206
+ );
1207
+
1208
+ expect(wrapper.html()).toMatchSnapshot();
1209
+ });
1210
+
1211
+ it("should match snapshot - tabStyle picker", async () => {
1212
+ const wrapper = await createWrapper(
1213
+ { tabStyle: "picker" },
1214
+ { title: "tab1", initialSelected: true },
1215
+ { title: "tab2" },
1216
+ );
1217
+
1218
+ await wrapper.vm.$nextTick();
1219
+ expect(wrapper.html()).toMatchSnapshot();
1220
+ });
1221
+
1222
+ it("should match snapshot - environment backoffice", async () => {
1223
+ const wrapper = await createWrapper(
1224
+ { environment: "backoffice" },
1225
+ { title: "tab1" },
1226
+ { title: "tab2" },
1227
+ );
1228
+
1229
+ expect(wrapper.html()).toMatchSnapshot();
1230
+ });
1231
+
1232
+ it("should match snapshot - environment frontoffice", async () => {
1233
+ const wrapper = await createWrapper(
1234
+ { environment: "frontoffice" },
1235
+ { title: "tab1" },
1236
+ { title: "tab2" },
1237
+ );
1238
+
1239
+ expect(wrapper.html()).toMatchSnapshot();
1240
+ });
1241
+
1242
+ it("should match snapshot - tone neutral", async () => {
1243
+ const wrapper = await createWrapper(
1244
+ { tone: "neutral" },
1245
+ { title: "tab1" },
1246
+ { title: "tab2" },
1247
+ );
1248
+
1249
+ expect(wrapper.html()).toMatchSnapshot();
1250
+ });
1251
+
1252
+ it("should match snapshot - tone alert", async () => {
1253
+ const wrapper = await createWrapper(
1254
+ { tone: "alert" },
1255
+ { title: "tab1" },
1256
+ { title: "tab2" },
1257
+ );
1258
+
1259
+ expect(wrapper.html()).toMatchSnapshot();
1260
+ });
1261
+ });
1262
+
1263
+ // ============================================
1264
+ // DEPRECATION WARNINGS
1265
+ // ============================================
1266
+ describe("Deprecation Warnings", () => {
1267
+ beforeEach(() => {
1268
+ vi.spyOn(console, "warn").mockImplementation(() => {});
1269
+ });
1270
+
1271
+ afterEach(() => {
1272
+ vi.restoreAllMocks();
1273
+ });
1274
+
1275
+ it("should warn when size prop is used", async () => {
1276
+ await createWrapper(
1277
+ { size: "sm", isDebug: true },
1278
+ { title: "tab1" },
1279
+ { title: "tab2" },
1280
+ );
1281
+
1282
+ expect(console.warn).toHaveBeenCalledWith(
1283
+ expect.stringContaining('[FzTabs] The "size" prop is deprecated'),
1284
+ );
1285
+ });
1286
+
1287
+ it("should warn when horizontalOverflow prop is used", async () => {
1288
+ await createWrapper(
1289
+ { horizontalOverflow: true, isDebug: true },
1290
+ { title: "tab1" },
1291
+ { title: "tab2" },
1292
+ );
1293
+
1294
+ expect(console.warn).toHaveBeenCalledWith(
1295
+ expect.stringContaining(
1296
+ '[FzTabs] The "horizontalOverflow" prop is deprecated',
1297
+ ),
1298
+ );
1299
+ });
1300
+
1301
+ it("should not warn when deprecated props are not used", async () => {
1302
+ vi.clearAllMocks();
1303
+
1304
+ await createWrapper({}, { title: "tab1" }, { title: "tab2" });
1305
+
1306
+ const deprecationWarnings = (console.warn as any).mock.calls.filter(
1307
+ (call: any[]) => {
1308
+ const message = call[0];
1309
+ return (
1310
+ typeof message === "string" &&
1311
+ message.startsWith("[FzTabs]") &&
1312
+ message.includes("deprecated")
1313
+ );
1314
+ },
1315
+ );
1316
+ expect(deprecationWarnings.length).toBe(0);
1317
+ });
1318
+ });
1319
+
1320
+ // ============================================
1321
+ // TAB STYLE
1322
+ // ============================================
1323
+ describe("TabStyle", () => {
1324
+ it("should render with tabStyle scroll", async () => {
1325
+ const wrapper = await createWrapper(
1326
+ { tabStyle: "scroll" },
1327
+ { title: "tab1" },
1328
+ { title: "tab2" },
1329
+ );
1330
+
1331
+ const container = wrapper.find(".tab-container");
1332
+ expect(container.classes()).toContain("overflow-x-auto");
1333
+ });
1334
+
1335
+ it("should render with tabStyle picker", async () => {
1336
+ const wrapper = await createWrapper(
1337
+ { tabStyle: "picker" },
1338
+ { title: "tab1", initialSelected: true },
1339
+ { title: "tab2" },
1340
+ );
1341
+
1342
+ await wrapper.vm.$nextTick();
1343
+ expect(wrapper.findComponent({ name: "FzTabPicker" }).exists()).toBe(
1344
+ true,
1345
+ );
1346
+ });
1347
+
1348
+ it("should render with tabStyle scroll as default", async () => {
1349
+ const wrapper = await createWrapper(
1350
+ {},
1351
+ { title: "tab1" },
1352
+ { title: "tab2" },
1353
+ );
1354
+
1355
+ const container = wrapper.find(".tab-container");
1356
+ expect(container.classes()).toContain("overflow-x-auto");
1357
+ });
1358
+ });
1359
+
1360
+ // ============================================
1361
+ // RETROCOMPATIBILITY
1362
+ // ============================================
1363
+ describe("Retrocompatibility", () => {
1364
+ it("should map deprecated size to effectiveSize", async () => {
1365
+ const wrapper = await createWrapper(
1366
+ { size: "md" },
1367
+ { title: "tab1" },
1368
+ { title: "tab2" },
1369
+ );
1370
+
1371
+ expect(wrapper.html()).toContain("tab1");
1372
+ expect(wrapper.html()).toContain("tab2");
1373
+ });
1374
+
1375
+ it("should map deprecated horizontalOverflow to overflowMode scroll", async () => {
1376
+ const wrapper = await createWrapper(
1377
+ { horizontalOverflow: true },
1378
+ { title: "tab1" },
1379
+ { title: "tab2" },
1380
+ );
1381
+
1382
+ await wrapper.vm.$nextTick();
1383
+ const container = wrapper.find(".tab-container");
1384
+ expect(container.html()).toContain("overflow-x-auto");
1385
+ });
1386
+
1387
+ it("should map deprecated horizontalOverflow false to overflowMode none", async () => {
1388
+ const wrapper = await createWrapper(
1389
+ { horizontalOverflow: false },
1390
+ { title: "tab1" },
1391
+ { title: "tab2" },
1392
+ );
1188
1393
 
1189
- expect(wrapper.html()).toMatchSnapshot()
1190
- })
1191
- })
1192
- })
1394
+ const container = wrapper.find(".tab-container");
1395
+ expect(container.classes()).not.toContain("overflow-x-auto");
1396
+ });
1193
1397
 
1398
+ it("should prioritize tabStyle over horizontalOverflow", async () => {
1399
+ const wrapper = await createWrapper(
1400
+ { tabStyle: "picker", horizontalOverflow: true },
1401
+ { title: "tab1", initialSelected: true },
1402
+ { title: "tab2" },
1403
+ );
1404
+
1405
+ await wrapper.vm.$nextTick();
1406
+ expect(wrapper.findComponent({ name: "FzTabPicker" }).exists()).toBe(
1407
+ true,
1408
+ );
1409
+ });
1410
+ });
1411
+
1412
+ // ============================================
1413
+ // ENVIRONMENT
1414
+ // ============================================
1415
+ describe("Environment", () => {
1416
+ it("should render with environment backoffice", async () => {
1417
+ const wrapper = await createWrapper(
1418
+ { environment: "backoffice" },
1419
+ { title: "tab1" },
1420
+ { title: "tab2" },
1421
+ );
1422
+
1423
+ expect(wrapper.html()).toContain("tab1");
1424
+ expect(wrapper.html()).toContain("tab2");
1425
+ });
1426
+
1427
+ it("should render with environment frontoffice", async () => {
1428
+ const wrapper = await createWrapper(
1429
+ { environment: "frontoffice" },
1430
+ { title: "tab1" },
1431
+ { title: "tab2" },
1432
+ );
1433
+
1434
+ expect(wrapper.html()).toContain("tab1");
1435
+ expect(wrapper.html()).toContain("tab2");
1436
+ });
1437
+
1438
+ it("should prioritize environment over deprecated size", async () => {
1439
+ const wrapper = await createWrapper(
1440
+ { environment: "backoffice", size: "sm" },
1441
+ { title: "tab1" },
1442
+ { title: "tab2" },
1443
+ );
1444
+
1445
+ expect(wrapper.html()).toContain("tab1");
1446
+ });
1447
+ });
1448
+
1449
+ // ============================================
1450
+ // TONE
1451
+ // ============================================
1452
+ describe("Tone", () => {
1453
+ it("should render with tone neutral (default)", async () => {
1454
+ const wrapper = await createWrapper(
1455
+ { tone: "neutral" },
1456
+ { title: "tab1" },
1457
+ { title: "tab2" },
1458
+ );
1459
+
1460
+ expect(wrapper.exists()).toBe(true);
1461
+ });
1462
+
1463
+ it("should render with tone alert", async () => {
1464
+ const wrapper = await createWrapper(
1465
+ { tone: "alert" },
1466
+ { title: "tab1" },
1467
+ { title: "tab2" },
1468
+ );
1469
+
1470
+ expect(wrapper.exists()).toBe(true);
1471
+ });
1472
+ });
1473
+ });