@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.
- package/CHANGELOG.md +37 -0
- package/dist/src/__test__/FzTabs.test.d.ts +1 -0
- package/dist/tab.js +117 -117
- package/dist/tab.umd.cjs +1 -1
- package/package.json +6 -5
- package/src/FzTabs.vue +101 -57
- package/src/__tests__/FzTabs.spec.ts +851 -571
- package/src/__tests__/__snapshots__/FzTabs.spec.ts.snap +154 -46
- package/src/common.ts +35 -10
- package/src/components/FzTabButton.vue +72 -22
- package/src/components/FzTabPicker.vue +31 -25
- package/src/types.ts +29 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { mount } from
|
|
2
|
-
import { describe, it, expect, beforeEach, vi } from
|
|
3
|
-
import { h } from
|
|
4
|
-
import { FzTab, FzTabs } from
|
|
5
|
-
import { FzTabProps, FzTabsProps } from
|
|
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 = [
|
|
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,
|
|
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(
|
|
32
|
+
describe("FzTabs", () => {
|
|
30
33
|
beforeEach(() => {
|
|
31
|
-
globalThis.HTMLElement.prototype.scrollIntoView = vi.fn()
|
|
32
|
-
vi.spyOn(console,
|
|
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,
|
|
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(
|
|
54
|
-
it(
|
|
70
|
+
describe("Rendering", () => {
|
|
71
|
+
it("should render with default props", async () => {
|
|
55
72
|
const wrapper = await createWrapper(
|
|
56
73
|
{
|
|
57
|
-
size:
|
|
74
|
+
size: "sm",
|
|
58
75
|
},
|
|
59
76
|
{
|
|
60
|
-
title:
|
|
77
|
+
title: "tab1",
|
|
61
78
|
},
|
|
62
79
|
{
|
|
63
|
-
title:
|
|
80
|
+
title: "tab2",
|
|
64
81
|
},
|
|
65
|
-
)
|
|
82
|
+
);
|
|
66
83
|
|
|
67
|
-
expect(wrapper.exists()).toBe(true)
|
|
68
|
-
expect(wrapper.find(
|
|
69
|
-
})
|
|
84
|
+
expect(wrapper.exists()).toBe(true);
|
|
85
|
+
expect(wrapper.find(".tab-container").exists()).toBe(true);
|
|
86
|
+
});
|
|
70
87
|
|
|
71
|
-
it(
|
|
88
|
+
it("should render tab buttons", async () => {
|
|
72
89
|
const wrapper = await createWrapper(
|
|
73
90
|
{
|
|
74
|
-
size:
|
|
91
|
+
size: "sm",
|
|
75
92
|
},
|
|
76
93
|
{
|
|
77
|
-
title:
|
|
94
|
+
title: "tab1",
|
|
78
95
|
},
|
|
79
96
|
{
|
|
80
|
-
title:
|
|
97
|
+
title: "tab2",
|
|
81
98
|
},
|
|
82
|
-
)
|
|
99
|
+
);
|
|
83
100
|
|
|
84
|
-
const buttons = wrapper.findAll(
|
|
85
|
-
expect(buttons.length).toBeGreaterThanOrEqual(2)
|
|
86
|
-
expect(buttons[0].attributes(
|
|
87
|
-
expect(buttons[1].attributes(
|
|
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(
|
|
107
|
+
it("should render tab content for selected tab", async () => {
|
|
91
108
|
const wrapper = await createWrapper(
|
|
92
109
|
{
|
|
93
|
-
size:
|
|
110
|
+
size: "sm",
|
|
94
111
|
},
|
|
95
112
|
{
|
|
96
|
-
title:
|
|
113
|
+
title: "tab1",
|
|
97
114
|
},
|
|
98
115
|
{
|
|
99
|
-
title:
|
|
116
|
+
title: "tab2",
|
|
100
117
|
},
|
|
101
|
-
)
|
|
118
|
+
);
|
|
102
119
|
|
|
103
|
-
expect(wrapper.text()).toContain(
|
|
104
|
-
expect(wrapper.text()).not.toContain(
|
|
105
|
-
})
|
|
120
|
+
expect(wrapper.text()).toContain("Content tab1");
|
|
121
|
+
expect(wrapper.text()).not.toContain("Content tab2");
|
|
122
|
+
});
|
|
106
123
|
|
|
107
|
-
it(
|
|
124
|
+
it("should render tabs-container-end slot", async () => {
|
|
108
125
|
const wrapper = mount(FzTabs, {
|
|
109
126
|
props: {
|
|
110
|
-
size:
|
|
127
|
+
size: "sm",
|
|
111
128
|
},
|
|
112
129
|
slots: {
|
|
113
|
-
default: () => [h(FzTab, { title:
|
|
114
|
-
|
|
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(
|
|
120
|
-
})
|
|
135
|
+
await wrapper.vm.$nextTick();
|
|
136
|
+
expect(wrapper.find(".custom-end").exists()).toBe(true);
|
|
137
|
+
});
|
|
121
138
|
|
|
122
|
-
it(
|
|
139
|
+
it("should render tabs-end slot", async () => {
|
|
123
140
|
const wrapper = mount(FzTabs, {
|
|
124
141
|
props: {
|
|
125
|
-
size:
|
|
142
|
+
size: "sm",
|
|
126
143
|
},
|
|
127
144
|
slots: {
|
|
128
|
-
default: () => [h(FzTab, { title:
|
|
129
|
-
|
|
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(
|
|
135
|
-
})
|
|
150
|
+
await wrapper.vm.$nextTick();
|
|
151
|
+
expect(wrapper.find(".custom-tabs-end").exists()).toBe(true);
|
|
152
|
+
});
|
|
136
153
|
|
|
137
|
-
it(
|
|
154
|
+
it("should render selected tab content slot", async () => {
|
|
138
155
|
const wrapper = await createWrapper(
|
|
139
156
|
{
|
|
140
|
-
size:
|
|
157
|
+
size: "sm",
|
|
141
158
|
},
|
|
142
159
|
{
|
|
143
|
-
title:
|
|
160
|
+
title: "tab1",
|
|
144
161
|
},
|
|
145
162
|
{
|
|
146
|
-
title:
|
|
163
|
+
title: "tab2",
|
|
147
164
|
},
|
|
148
|
-
)
|
|
165
|
+
);
|
|
149
166
|
|
|
150
167
|
const slotWrapper = mount(FzTabs, {
|
|
151
168
|
props: {
|
|
152
|
-
size:
|
|
169
|
+
size: "sm",
|
|
153
170
|
},
|
|
154
171
|
slots: {
|
|
155
|
-
default: () => [h(FzTab, { title:
|
|
172
|
+
default: () => [h(FzTab, { title: "tab1" }, "Content tab1")],
|
|
156
173
|
},
|
|
157
174
|
scopedSlots: {
|
|
158
|
-
selected: (props: { selected: string }) =>
|
|
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(
|
|
172
|
-
describe(
|
|
173
|
-
it(
|
|
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:
|
|
194
|
+
size: "sm",
|
|
177
195
|
},
|
|
178
196
|
{
|
|
179
|
-
title:
|
|
197
|
+
title: "tab1",
|
|
180
198
|
},
|
|
181
199
|
{
|
|
182
|
-
title:
|
|
200
|
+
title: "tab2",
|
|
183
201
|
},
|
|
184
|
-
)
|
|
202
|
+
);
|
|
185
203
|
|
|
186
|
-
const button = wrapper.find(
|
|
187
|
-
|
|
188
|
-
expect(button.classes()).toContain(
|
|
189
|
-
expect(button.classes()).toContain(
|
|
190
|
-
expect(button.classes()).toContain(
|
|
191
|
-
expect(button.classes()).toContain(
|
|
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(
|
|
213
|
+
it("should apply default size classes when md size is passed (deprecated)", async () => {
|
|
195
214
|
const wrapper = await createWrapper(
|
|
196
215
|
{
|
|
197
|
-
size:
|
|
216
|
+
size: "md",
|
|
198
217
|
},
|
|
199
218
|
{
|
|
200
|
-
title:
|
|
219
|
+
title: "tab1",
|
|
201
220
|
},
|
|
202
221
|
{
|
|
203
|
-
title:
|
|
222
|
+
title: "tab2",
|
|
204
223
|
},
|
|
205
|
-
)
|
|
224
|
+
);
|
|
206
225
|
|
|
207
|
-
const button = wrapper.find(
|
|
208
|
-
|
|
209
|
-
expect(button.classes()).toContain(
|
|
210
|
-
expect(button.classes()).toContain(
|
|
211
|
-
expect(button.classes()).toContain(
|
|
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(
|
|
216
|
-
it(
|
|
235
|
+
describe("vertical prop", () => {
|
|
236
|
+
it("should apply horizontal layout by default", async () => {
|
|
217
237
|
const wrapper = await createWrapper(
|
|
218
238
|
{
|
|
219
|
-
size:
|
|
239
|
+
size: "sm",
|
|
220
240
|
vertical: false,
|
|
221
241
|
},
|
|
222
242
|
{
|
|
223
|
-
title:
|
|
243
|
+
title: "tab1",
|
|
224
244
|
},
|
|
225
245
|
{
|
|
226
|
-
title:
|
|
246
|
+
title: "tab2",
|
|
227
247
|
},
|
|
228
|
-
)
|
|
248
|
+
);
|
|
229
249
|
|
|
230
|
-
const container = wrapper.find(
|
|
231
|
-
expect(container.classes()).toContain(
|
|
232
|
-
expect(wrapper.find(
|
|
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(
|
|
255
|
+
it("should apply vertical layout when true", async () => {
|
|
236
256
|
const wrapper = await createWrapper(
|
|
237
257
|
{
|
|
238
|
-
size:
|
|
258
|
+
size: "sm",
|
|
239
259
|
vertical: true,
|
|
240
260
|
},
|
|
241
261
|
{
|
|
242
|
-
title:
|
|
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:
|
|
265
|
+
title: "tab2",
|
|
267
266
|
},
|
|
268
|
-
)
|
|
267
|
+
);
|
|
269
268
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
expect(
|
|
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(
|
|
277
|
-
it(
|
|
275
|
+
describe("FzTab props", () => {
|
|
276
|
+
it("should render tab with icon", async () => {
|
|
278
277
|
const wrapper = await createWrapper(
|
|
279
278
|
{
|
|
280
|
-
size:
|
|
279
|
+
size: "sm",
|
|
281
280
|
},
|
|
282
281
|
{
|
|
283
|
-
title:
|
|
284
|
-
icon:
|
|
282
|
+
title: "tab1",
|
|
283
|
+
icon: "bell",
|
|
285
284
|
},
|
|
286
285
|
{
|
|
287
|
-
title:
|
|
286
|
+
title: "tab2",
|
|
288
287
|
},
|
|
289
|
-
)
|
|
288
|
+
);
|
|
290
289
|
|
|
291
|
-
const icon = wrapper.findComponent({ name:
|
|
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(
|
|
294
|
+
it("should render tab with badgeContent", async () => {
|
|
296
295
|
const wrapper = await createWrapper(
|
|
297
296
|
{
|
|
298
|
-
size:
|
|
297
|
+
size: "sm",
|
|
299
298
|
},
|
|
300
299
|
{
|
|
301
|
-
title:
|
|
302
|
-
badgeContent:
|
|
300
|
+
title: "tab1",
|
|
301
|
+
badgeContent: "5",
|
|
303
302
|
},
|
|
304
303
|
{
|
|
305
|
-
title:
|
|
304
|
+
title: "tab2",
|
|
306
305
|
},
|
|
307
|
-
)
|
|
306
|
+
);
|
|
308
307
|
|
|
309
|
-
const badge = wrapper.findComponent({ name:
|
|
310
|
-
expect(badge.exists()).toBe(true)
|
|
311
|
-
expect(badge.text()).toBe(
|
|
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(
|
|
313
|
+
it("should render disabled tab", async () => {
|
|
315
314
|
const wrapper = await createWrapper(
|
|
316
315
|
{
|
|
317
|
-
size:
|
|
316
|
+
size: "sm",
|
|
318
317
|
},
|
|
319
318
|
{
|
|
320
|
-
title:
|
|
319
|
+
title: "tab1",
|
|
321
320
|
disabled: true,
|
|
322
321
|
},
|
|
323
322
|
{
|
|
324
|
-
title:
|
|
323
|
+
title: "tab2",
|
|
325
324
|
},
|
|
326
|
-
)
|
|
325
|
+
);
|
|
327
326
|
|
|
328
|
-
const button = wrapper.find('button[title="tab1"]')
|
|
329
|
-
expect(button.classes()).toContain(
|
|
330
|
-
})
|
|
327
|
+
const button = wrapper.find('button[title="tab1"]');
|
|
328
|
+
expect(button.classes()).toContain("cursor-not-allowed");
|
|
329
|
+
});
|
|
331
330
|
|
|
332
|
-
it(
|
|
331
|
+
it("should select tab with initialSelected prop", async () => {
|
|
333
332
|
const wrapper = await createWrapper(
|
|
334
333
|
{
|
|
335
|
-
size:
|
|
334
|
+
size: "sm",
|
|
336
335
|
},
|
|
337
336
|
{
|
|
338
|
-
title:
|
|
337
|
+
title: "tab1",
|
|
339
338
|
},
|
|
340
339
|
{
|
|
341
|
-
title:
|
|
340
|
+
title: "tab2",
|
|
342
341
|
initialSelected: true,
|
|
343
342
|
},
|
|
344
|
-
)
|
|
343
|
+
);
|
|
345
344
|
|
|
346
|
-
expect(wrapper.text()).toContain(
|
|
347
|
-
expect(wrapper.text()).not.toContain(
|
|
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(
|
|
356
|
-
it(
|
|
354
|
+
describe("Events", () => {
|
|
355
|
+
it("should emit change event when tab is clicked", async () => {
|
|
357
356
|
const wrapper = await createWrapper(
|
|
358
357
|
{
|
|
359
|
-
size:
|
|
358
|
+
size: "sm",
|
|
360
359
|
},
|
|
361
360
|
{
|
|
362
|
-
title:
|
|
361
|
+
title: "tab1",
|
|
363
362
|
},
|
|
364
363
|
{
|
|
365
|
-
title:
|
|
364
|
+
title: "tab2",
|
|
366
365
|
},
|
|
367
|
-
)
|
|
366
|
+
);
|
|
368
367
|
|
|
369
|
-
const tab2Button = wrapper.find('button[title="tab2"]')
|
|
370
|
-
await tab2Button.trigger(
|
|
368
|
+
const tab2Button = wrapper.find('button[title="tab2"]');
|
|
369
|
+
await tab2Button.trigger("click");
|
|
371
370
|
|
|
372
|
-
expect(wrapper.emitted(
|
|
371
|
+
expect(wrapper.emitted("change")).toBeTruthy();
|
|
373
372
|
// Change event is emitted on mount (tab1) and on click (tab2)
|
|
374
|
-
|
|
375
|
-
|
|
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(
|
|
378
|
+
it("should emit change event with correct tab title", async () => {
|
|
379
379
|
const wrapper = await createWrapper(
|
|
380
380
|
{
|
|
381
|
-
size:
|
|
381
|
+
size: "sm",
|
|
382
382
|
},
|
|
383
383
|
{
|
|
384
|
-
title:
|
|
384
|
+
title: "tab1",
|
|
385
385
|
},
|
|
386
386
|
{
|
|
387
|
-
title:
|
|
387
|
+
title: "tab2",
|
|
388
388
|
},
|
|
389
389
|
{
|
|
390
|
-
title:
|
|
390
|
+
title: "tab3",
|
|
391
391
|
},
|
|
392
|
-
)
|
|
392
|
+
);
|
|
393
393
|
|
|
394
|
-
const tab3Button = wrapper.find('button[title="tab3"]')
|
|
395
|
-
await tab3Button.trigger(
|
|
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
|
-
|
|
399
|
-
|
|
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(
|
|
403
|
+
it("should not emit change event when disabled tab is clicked", async () => {
|
|
403
404
|
const wrapper = await createWrapper(
|
|
404
405
|
{
|
|
405
|
-
size:
|
|
406
|
+
size: "sm",
|
|
406
407
|
},
|
|
407
408
|
{
|
|
408
|
-
title:
|
|
409
|
+
title: "tab1",
|
|
409
410
|
disabled: true,
|
|
410
411
|
},
|
|
411
412
|
{
|
|
412
|
-
title:
|
|
413
|
+
title: "tab2",
|
|
413
414
|
},
|
|
414
|
-
)
|
|
415
|
+
);
|
|
415
416
|
|
|
416
|
-
const tab1Button = wrapper.find('button[title="tab1"]')
|
|
417
|
-
const initialEmitted = wrapper.emitted(
|
|
418
|
-
await tab1Button.trigger(
|
|
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(
|
|
422
|
-
expect(finalEmitted).toBe(initialEmitted)
|
|
423
|
-
})
|
|
422
|
+
const finalEmitted = wrapper.emitted("change")?.length || 0;
|
|
423
|
+
expect(finalEmitted).toBe(initialEmitted);
|
|
424
|
+
});
|
|
424
425
|
|
|
425
|
-
it(
|
|
426
|
+
it("should update selected tab content when change event is emitted", async () => {
|
|
426
427
|
const wrapper = await createWrapper(
|
|
427
428
|
{
|
|
428
|
-
size:
|
|
429
|
+
size: "sm",
|
|
429
430
|
},
|
|
430
431
|
{
|
|
431
|
-
title:
|
|
432
|
+
title: "tab1",
|
|
432
433
|
},
|
|
433
434
|
{
|
|
434
|
-
title:
|
|
435
|
+
title: "tab2",
|
|
435
436
|
},
|
|
436
|
-
)
|
|
437
|
+
);
|
|
437
438
|
|
|
438
|
-
expect(wrapper.text()).toContain(
|
|
439
|
-
expect(wrapper.text()).not.toContain(
|
|
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(
|
|
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(
|
|
446
|
-
expect(wrapper.text()).not.toContain(
|
|
447
|
-
})
|
|
446
|
+
expect(wrapper.text()).toContain("Content tab2");
|
|
447
|
+
expect(wrapper.text()).not.toContain("Content tab1");
|
|
448
|
+
});
|
|
448
449
|
|
|
449
|
-
it(
|
|
450
|
+
it("should emit change event on mount with initial selected tab", async () => {
|
|
450
451
|
const wrapper = await createWrapper(
|
|
451
452
|
{
|
|
452
|
-
size:
|
|
453
|
+
size: "sm",
|
|
453
454
|
},
|
|
454
455
|
{
|
|
455
|
-
title:
|
|
456
|
+
title: "tab1",
|
|
456
457
|
},
|
|
457
458
|
{
|
|
458
|
-
title:
|
|
459
|
+
title: "tab2",
|
|
459
460
|
},
|
|
460
|
-
)
|
|
461
|
+
);
|
|
461
462
|
|
|
462
463
|
// Change event should be emitted on mount with the initially selected tab
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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(
|
|
470
|
+
it("should emit change event with initialSelected tab on mount", async () => {
|
|
469
471
|
const wrapper = await createWrapper(
|
|
470
472
|
{
|
|
471
|
-
size:
|
|
473
|
+
size: "sm",
|
|
472
474
|
},
|
|
473
475
|
{
|
|
474
|
-
title:
|
|
476
|
+
title: "tab1",
|
|
475
477
|
},
|
|
476
478
|
{
|
|
477
|
-
title:
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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(
|
|
491
|
+
it("should emit change event payload with title and button element", async () => {
|
|
489
492
|
const wrapper = await createWrapper(
|
|
490
493
|
{
|
|
491
|
-
size:
|
|
494
|
+
size: "sm",
|
|
492
495
|
},
|
|
493
496
|
{
|
|
494
|
-
title:
|
|
497
|
+
title: "tab1",
|
|
495
498
|
},
|
|
496
499
|
{
|
|
497
|
-
title:
|
|
500
|
+
title: "tab2",
|
|
498
501
|
},
|
|
499
|
-
)
|
|
502
|
+
);
|
|
500
503
|
|
|
501
|
-
const tab2Button = wrapper.find('button[title="tab2"]')
|
|
502
|
-
await tab2Button.trigger(
|
|
504
|
+
const tab2Button = wrapper.find('button[title="tab2"]');
|
|
505
|
+
await tab2Button.trigger("click");
|
|
503
506
|
|
|
504
|
-
const changeEvents = wrapper.emitted(
|
|
505
|
-
const lastEvent = changeEvents[changeEvents.length - 1]
|
|
506
|
-
|
|
507
|
-
expect(
|
|
508
|
-
expect(lastEvent[0]).toBe(
|
|
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(
|
|
515
|
+
it("should emit change event multiple times when switching between tabs", async () => {
|
|
512
516
|
const wrapper = await createWrapper(
|
|
513
517
|
{
|
|
514
|
-
size:
|
|
518
|
+
size: "sm",
|
|
515
519
|
},
|
|
516
520
|
{
|
|
517
|
-
title:
|
|
521
|
+
title: "tab1",
|
|
518
522
|
},
|
|
519
523
|
{
|
|
520
|
-
title:
|
|
524
|
+
title: "tab2",
|
|
521
525
|
},
|
|
522
526
|
{
|
|
523
|
-
title:
|
|
527
|
+
title: "tab3",
|
|
524
528
|
},
|
|
525
|
-
)
|
|
529
|
+
);
|
|
526
530
|
|
|
527
|
-
const initialCount = wrapper.emitted(
|
|
531
|
+
const initialCount = wrapper.emitted("change")?.length || 0;
|
|
528
532
|
|
|
529
|
-
const tab2Button = wrapper.find('button[title="tab2"]')
|
|
530
|
-
await tab2Button.trigger(
|
|
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(
|
|
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(
|
|
539
|
+
const tab1Button = wrapper.find('button[title="tab1"]');
|
|
540
|
+
await tab1Button.trigger("click");
|
|
537
541
|
|
|
538
|
-
const finalCount = wrapper.emitted(
|
|
539
|
-
expect(finalCount).toBe(initialCount + 3)
|
|
542
|
+
const finalCount = wrapper.emitted("change")?.length || 0;
|
|
543
|
+
expect(finalCount).toBe(initialCount + 3);
|
|
540
544
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
expect(changeEvents[changeEvents.length -
|
|
544
|
-
expect(changeEvents[changeEvents.length -
|
|
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(
|
|
552
|
-
describe(
|
|
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:
|
|
561
|
+
size: "sm",
|
|
557
562
|
},
|
|
558
563
|
{
|
|
559
|
-
title:
|
|
564
|
+
title: "tab1",
|
|
560
565
|
},
|
|
561
566
|
{
|
|
562
|
-
title:
|
|
567
|
+
title: "tab2",
|
|
563
568
|
},
|
|
564
|
-
)
|
|
569
|
+
);
|
|
565
570
|
|
|
566
|
-
const container = wrapper.find(
|
|
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:
|
|
580
|
+
size: "sm",
|
|
576
581
|
},
|
|
577
582
|
{
|
|
578
|
-
title:
|
|
583
|
+
title: "tab1",
|
|
579
584
|
},
|
|
580
585
|
{
|
|
581
|
-
title:
|
|
586
|
+
title: "tab2",
|
|
582
587
|
},
|
|
583
|
-
)
|
|
588
|
+
);
|
|
584
589
|
|
|
585
|
-
const buttons = wrapper.findAll(
|
|
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:
|
|
598
|
+
size: "sm",
|
|
594
599
|
},
|
|
595
600
|
{
|
|
596
|
-
title:
|
|
601
|
+
title: "tab1",
|
|
597
602
|
},
|
|
598
603
|
{
|
|
599
|
-
title:
|
|
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:
|
|
616
|
+
size: "sm",
|
|
612
617
|
},
|
|
613
618
|
{
|
|
614
|
-
title:
|
|
619
|
+
title: "tab1",
|
|
615
620
|
},
|
|
616
621
|
{
|
|
617
|
-
title:
|
|
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(
|
|
631
|
+
it("should have aria-controls linking to tabpanel", async () => {
|
|
627
632
|
const wrapper = await createWrapper(
|
|
628
633
|
{
|
|
629
|
-
size:
|
|
634
|
+
size: "sm",
|
|
630
635
|
},
|
|
631
636
|
{
|
|
632
|
-
title:
|
|
637
|
+
title: "tab1",
|
|
633
638
|
},
|
|
634
639
|
{
|
|
635
|
-
title:
|
|
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:
|
|
652
|
+
size: "sm",
|
|
648
653
|
},
|
|
649
654
|
{
|
|
650
|
-
title:
|
|
655
|
+
title: "tab1",
|
|
651
656
|
},
|
|
652
657
|
{
|
|
653
|
-
title:
|
|
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(
|
|
659
|
-
})
|
|
663
|
+
expect(wrapper.text()).toContain("Content tab1");
|
|
664
|
+
});
|
|
660
665
|
|
|
661
|
-
it(
|
|
666
|
+
it("should have aria-disabled on disabled tabs", async () => {
|
|
662
667
|
const wrapper = await createWrapper(
|
|
663
668
|
{
|
|
664
|
-
size:
|
|
669
|
+
size: "sm",
|
|
665
670
|
},
|
|
666
671
|
{
|
|
667
|
-
title:
|
|
672
|
+
title: "tab1",
|
|
668
673
|
disabled: true,
|
|
669
674
|
},
|
|
670
675
|
{
|
|
671
|
-
title:
|
|
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(
|
|
679
|
-
})
|
|
682
|
+
expect(disabledButton.exists()).toBe(true);
|
|
683
|
+
expect(disabledButton.classes()).toContain("cursor-not-allowed");
|
|
684
|
+
});
|
|
680
685
|
|
|
681
|
-
it(
|
|
686
|
+
it("should hide decorative icons from screen readers", async () => {
|
|
682
687
|
const wrapper = await createWrapper(
|
|
683
688
|
{
|
|
684
|
-
size:
|
|
689
|
+
size: "sm",
|
|
685
690
|
},
|
|
686
691
|
{
|
|
687
|
-
title:
|
|
688
|
-
icon:
|
|
692
|
+
title: "tab1",
|
|
693
|
+
icon: "bell",
|
|
689
694
|
},
|
|
690
695
|
{
|
|
691
|
-
title:
|
|
696
|
+
title: "tab2",
|
|
692
697
|
},
|
|
693
|
-
)
|
|
698
|
+
);
|
|
694
699
|
|
|
695
|
-
const icon = wrapper.findComponent({ name:
|
|
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(
|
|
704
|
-
it(
|
|
708
|
+
describe("Keyboard navigation", () => {
|
|
709
|
+
it("should be focusable when not disabled", async () => {
|
|
705
710
|
const wrapper = await createWrapper(
|
|
706
711
|
{
|
|
707
|
-
size:
|
|
712
|
+
size: "sm",
|
|
708
713
|
},
|
|
709
714
|
{
|
|
710
|
-
title:
|
|
715
|
+
title: "tab1",
|
|
711
716
|
},
|
|
712
717
|
{
|
|
713
|
-
title:
|
|
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(
|
|
720
|
-
})
|
|
724
|
+
expect(button.element.tagName).toBe("BUTTON");
|
|
725
|
+
});
|
|
721
726
|
|
|
722
|
-
it(
|
|
727
|
+
it("should support arrow key navigation between tabs", async () => {
|
|
723
728
|
const wrapper = await createWrapper(
|
|
724
729
|
{
|
|
725
|
-
size:
|
|
730
|
+
size: "sm",
|
|
726
731
|
},
|
|
727
732
|
{
|
|
728
|
-
title:
|
|
733
|
+
title: "tab1",
|
|
729
734
|
},
|
|
730
735
|
{
|
|
731
|
-
title:
|
|
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(
|
|
745
|
+
it("should support Enter key to activate tab", async () => {
|
|
741
746
|
const wrapper = await createWrapper(
|
|
742
747
|
{
|
|
743
|
-
size:
|
|
748
|
+
size: "sm",
|
|
744
749
|
},
|
|
745
750
|
{
|
|
746
|
-
title:
|
|
751
|
+
title: "tab1",
|
|
747
752
|
},
|
|
748
753
|
{
|
|
749
|
-
title:
|
|
754
|
+
title: "tab2",
|
|
750
755
|
},
|
|
751
|
-
)
|
|
756
|
+
);
|
|
752
757
|
|
|
753
|
-
const tab2Button = wrapper.find('button[title="tab2"]')
|
|
754
|
-
await tab2Button.trigger(
|
|
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(
|
|
764
|
+
it("should support Space key to activate tab", async () => {
|
|
760
765
|
const wrapper = await createWrapper(
|
|
761
766
|
{
|
|
762
|
-
size:
|
|
767
|
+
size: "sm",
|
|
763
768
|
},
|
|
764
769
|
{
|
|
765
|
-
title:
|
|
770
|
+
title: "tab1",
|
|
766
771
|
},
|
|
767
772
|
{
|
|
768
|
-
title:
|
|
773
|
+
title: "tab2",
|
|
769
774
|
},
|
|
770
|
-
)
|
|
775
|
+
);
|
|
771
776
|
|
|
772
|
-
const tab2Button = wrapper.find('button[title="tab2"]')
|
|
773
|
-
await tab2Button.trigger(
|
|
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(
|
|
780
|
-
it(
|
|
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:
|
|
788
|
+
size: "sm",
|
|
784
789
|
},
|
|
785
790
|
{
|
|
786
|
-
title:
|
|
791
|
+
title: "tab1",
|
|
787
792
|
},
|
|
788
793
|
{
|
|
789
|
-
title:
|
|
794
|
+
title: "tab2",
|
|
790
795
|
},
|
|
791
|
-
)
|
|
796
|
+
);
|
|
792
797
|
|
|
793
|
-
const buttons = wrapper.findAll(
|
|
794
|
-
expect(buttons.length).toBeGreaterThanOrEqual(2)
|
|
795
|
-
})
|
|
798
|
+
const buttons = wrapper.findAll("button");
|
|
799
|
+
expect(buttons.length).toBeGreaterThanOrEqual(2);
|
|
800
|
+
});
|
|
796
801
|
|
|
797
|
-
it(
|
|
802
|
+
it("should have accessible labels via title attribute", async () => {
|
|
798
803
|
const wrapper = await createWrapper(
|
|
799
804
|
{
|
|
800
|
-
size:
|
|
805
|
+
size: "sm",
|
|
801
806
|
},
|
|
802
807
|
{
|
|
803
|
-
title:
|
|
808
|
+
title: "tab1",
|
|
804
809
|
},
|
|
805
810
|
{
|
|
806
|
-
title:
|
|
811
|
+
title: "tab2",
|
|
807
812
|
},
|
|
808
|
-
)
|
|
813
|
+
);
|
|
809
814
|
|
|
810
|
-
const button = wrapper.find('button[title="tab1"]')
|
|
811
|
-
expect(button.attributes(
|
|
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(
|
|
820
|
-
it(
|
|
824
|
+
describe("CSS Classes", () => {
|
|
825
|
+
it("should apply static base classes to container", async () => {
|
|
821
826
|
const wrapper = await createWrapper(
|
|
822
827
|
{
|
|
823
|
-
size:
|
|
828
|
+
size: "sm",
|
|
824
829
|
},
|
|
825
830
|
{
|
|
826
|
-
title:
|
|
831
|
+
title: "tab1",
|
|
827
832
|
},
|
|
828
833
|
{
|
|
829
|
-
title:
|
|
834
|
+
title: "tab2",
|
|
830
835
|
},
|
|
831
|
-
)
|
|
836
|
+
);
|
|
832
837
|
|
|
833
|
-
const container = wrapper.find(
|
|
834
|
-
expect(container.classes()).toContain(
|
|
835
|
-
expect(container.classes()).toContain(
|
|
836
|
-
expect(container.classes()).toContain(
|
|
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(
|
|
844
|
+
it("should apply selected tab classes", async () => {
|
|
840
845
|
const wrapper = await createWrapper(
|
|
841
846
|
{
|
|
842
|
-
size:
|
|
847
|
+
size: "sm",
|
|
843
848
|
},
|
|
844
849
|
{
|
|
845
|
-
title:
|
|
850
|
+
title: "tab1",
|
|
846
851
|
},
|
|
847
852
|
{
|
|
848
|
-
title:
|
|
853
|
+
title: "tab2",
|
|
849
854
|
},
|
|
850
|
-
)
|
|
855
|
+
);
|
|
851
856
|
|
|
852
|
-
const selectedButton = wrapper.find('button[title="tab1"]')
|
|
853
|
-
expect(selectedButton.classes()).toContain(
|
|
854
|
-
expect(selectedButton.classes()).toContain(
|
|
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(
|
|
862
|
+
it("should apply unselected tab classes", async () => {
|
|
858
863
|
const wrapper = await createWrapper(
|
|
859
864
|
{
|
|
860
|
-
size:
|
|
865
|
+
size: "sm",
|
|
861
866
|
},
|
|
862
867
|
{
|
|
863
|
-
title:
|
|
868
|
+
title: "tab1",
|
|
864
869
|
},
|
|
865
870
|
{
|
|
866
|
-
title:
|
|
871
|
+
title: "tab2",
|
|
867
872
|
},
|
|
868
|
-
)
|
|
873
|
+
);
|
|
869
874
|
|
|
870
|
-
const unselectedButton = wrapper.find('button[title="tab2"]')
|
|
871
|
-
expect(unselectedButton.classes()).toContain(
|
|
872
|
-
expect(unselectedButton.classes()).toContain(
|
|
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(
|
|
880
|
+
it("should apply disabled tab classes", async () => {
|
|
876
881
|
const wrapper = await createWrapper(
|
|
877
882
|
{
|
|
878
|
-
size:
|
|
883
|
+
size: "sm",
|
|
879
884
|
},
|
|
880
885
|
{
|
|
881
|
-
title:
|
|
886
|
+
title: "tab1",
|
|
882
887
|
disabled: true,
|
|
883
888
|
},
|
|
884
889
|
{
|
|
885
|
-
title:
|
|
890
|
+
title: "tab2",
|
|
886
891
|
},
|
|
887
|
-
)
|
|
892
|
+
);
|
|
888
893
|
|
|
889
|
-
const disabledButton = wrapper.find('button[title="tab1"]')
|
|
890
|
-
expect(disabledButton.classes()).toContain(
|
|
891
|
-
})
|
|
894
|
+
const disabledButton = wrapper.find('button[title="tab1"]');
|
|
895
|
+
expect(disabledButton.classes()).toContain("cursor-not-allowed");
|
|
896
|
+
});
|
|
892
897
|
|
|
893
|
-
it(
|
|
898
|
+
it("should apply vertical layout classes when vertical prop is true", async () => {
|
|
894
899
|
const wrapper = await createWrapper(
|
|
895
900
|
{
|
|
896
|
-
size:
|
|
901
|
+
size: "sm",
|
|
897
902
|
vertical: true,
|
|
898
903
|
},
|
|
899
904
|
{
|
|
900
|
-
title:
|
|
905
|
+
title: "tab1",
|
|
901
906
|
},
|
|
902
907
|
{
|
|
903
|
-
title:
|
|
908
|
+
title: "tab2",
|
|
904
909
|
},
|
|
905
|
-
)
|
|
910
|
+
);
|
|
906
911
|
|
|
907
|
-
const container = wrapper.find(
|
|
908
|
-
expect(container.classes()).toContain(
|
|
909
|
-
})
|
|
912
|
+
const container = wrapper.find(".tab-container");
|
|
913
|
+
expect(container.classes()).toContain("flex-col");
|
|
914
|
+
});
|
|
910
915
|
|
|
911
|
-
it(
|
|
916
|
+
it("should apply horizontal layout classes when vertical prop is false", async () => {
|
|
912
917
|
const wrapper = await createWrapper(
|
|
913
918
|
{
|
|
914
|
-
size:
|
|
919
|
+
size: "sm",
|
|
915
920
|
vertical: false,
|
|
916
921
|
},
|
|
917
922
|
{
|
|
918
|
-
title:
|
|
923
|
+
title: "tab1",
|
|
919
924
|
},
|
|
920
925
|
{
|
|
921
|
-
title:
|
|
926
|
+
title: "tab2",
|
|
922
927
|
},
|
|
923
|
-
)
|
|
928
|
+
);
|
|
924
929
|
|
|
925
|
-
const container = wrapper.find(
|
|
926
|
-
expect(container.classes()).toContain(
|
|
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(
|
|
934
|
-
it(
|
|
938
|
+
describe("Edge Cases", () => {
|
|
939
|
+
it("should handle empty tabs array gracefully", async () => {
|
|
935
940
|
const wrapper = mount(FzTabs, {
|
|
936
941
|
props: {
|
|
937
|
-
size:
|
|
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(
|
|
948
|
-
)
|
|
949
|
-
})
|
|
953
|
+
expect.stringContaining("FzTabs must have at least one FzTab child"),
|
|
954
|
+
);
|
|
955
|
+
});
|
|
950
956
|
|
|
951
|
-
it(
|
|
957
|
+
it("should handle duplicate tab titles with warning", async () => {
|
|
952
958
|
const wrapper = await createWrapper(
|
|
953
959
|
{
|
|
954
|
-
size:
|
|
960
|
+
size: "sm",
|
|
961
|
+
isDebug: true,
|
|
955
962
|
},
|
|
956
963
|
{
|
|
957
|
-
title:
|
|
964
|
+
title: "tab1",
|
|
958
965
|
},
|
|
959
966
|
{
|
|
960
|
-
title:
|
|
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(
|
|
967
|
-
)
|
|
968
|
-
})
|
|
973
|
+
expect.stringContaining("duplicate titles"),
|
|
974
|
+
);
|
|
975
|
+
});
|
|
969
976
|
|
|
970
|
-
it(
|
|
977
|
+
it("should select first tab when no initialSelected is provided", async () => {
|
|
971
978
|
const wrapper = await createWrapper(
|
|
972
979
|
{
|
|
973
|
-
size:
|
|
980
|
+
size: "sm",
|
|
974
981
|
},
|
|
975
982
|
{
|
|
976
|
-
title:
|
|
983
|
+
title: "tab1",
|
|
977
984
|
},
|
|
978
985
|
{
|
|
979
|
-
title:
|
|
986
|
+
title: "tab2",
|
|
980
987
|
},
|
|
981
|
-
)
|
|
988
|
+
);
|
|
982
989
|
|
|
983
|
-
expect(wrapper.text()).toContain(
|
|
984
|
-
expect(wrapper.text()).not.toContain(
|
|
985
|
-
})
|
|
990
|
+
expect(wrapper.text()).toContain("Content tab1");
|
|
991
|
+
expect(wrapper.text()).not.toContain("Content tab2");
|
|
992
|
+
});
|
|
986
993
|
|
|
987
|
-
it(
|
|
994
|
+
it("should handle tab with very long title", async () => {
|
|
988
995
|
const wrapper = await createWrapper(
|
|
989
996
|
{
|
|
990
|
-
size:
|
|
997
|
+
size: "sm",
|
|
991
998
|
},
|
|
992
999
|
{
|
|
993
|
-
title:
|
|
1000
|
+
title: "This is a very long tab title that might overflow",
|
|
994
1001
|
},
|
|
995
1002
|
{
|
|
996
|
-
title:
|
|
1003
|
+
title: "tab2",
|
|
997
1004
|
},
|
|
998
|
-
)
|
|
1005
|
+
);
|
|
999
1006
|
|
|
1000
|
-
const button = wrapper.find(
|
|
1001
|
-
|
|
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(
|
|
1013
|
+
it("should handle rapid tab switching", async () => {
|
|
1005
1014
|
const wrapper = await createWrapper(
|
|
1006
1015
|
{
|
|
1007
|
-
size:
|
|
1016
|
+
size: "sm",
|
|
1008
1017
|
},
|
|
1009
1018
|
{
|
|
1010
|
-
title:
|
|
1019
|
+
title: "tab1",
|
|
1011
1020
|
},
|
|
1012
1021
|
{
|
|
1013
|
-
title:
|
|
1022
|
+
title: "tab2",
|
|
1014
1023
|
},
|
|
1015
1024
|
{
|
|
1016
|
-
title:
|
|
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(
|
|
1024
|
-
await wrapper.vm.$nextTick()
|
|
1025
|
-
await tab3Button.trigger(
|
|
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(
|
|
1029
|
-
expect(wrapper.emitted(
|
|
1030
|
-
})
|
|
1037
|
+
expect(wrapper.emitted("change")).toBeTruthy();
|
|
1038
|
+
expect(wrapper.emitted("change")!.length).toBeGreaterThanOrEqual(2);
|
|
1039
|
+
});
|
|
1031
1040
|
|
|
1032
|
-
it(
|
|
1041
|
+
it("should handle undefined props gracefully", async () => {
|
|
1033
1042
|
const wrapper = await createWrapper(
|
|
1034
1043
|
{
|
|
1035
|
-
size:
|
|
1044
|
+
size: "sm",
|
|
1036
1045
|
},
|
|
1037
1046
|
{
|
|
1038
|
-
title:
|
|
1047
|
+
title: "tab1",
|
|
1039
1048
|
},
|
|
1040
1049
|
{
|
|
1041
|
-
title:
|
|
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(
|
|
1057
|
+
it("should handle tab removal gracefully", async () => {
|
|
1049
1058
|
const wrapper = await createWrapper(
|
|
1050
1059
|
{
|
|
1051
|
-
size:
|
|
1060
|
+
size: "sm",
|
|
1052
1061
|
},
|
|
1053
1062
|
{
|
|
1054
|
-
title:
|
|
1063
|
+
title: "tab1",
|
|
1055
1064
|
},
|
|
1056
1065
|
{
|
|
1057
|
-
title:
|
|
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(
|
|
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(
|
|
1075
|
-
it(
|
|
1083
|
+
describe("Snapshots", () => {
|
|
1084
|
+
it("should match snapshot - default state", async () => {
|
|
1076
1085
|
const wrapper = await createWrapper(
|
|
1077
1086
|
{
|
|
1078
|
-
size:
|
|
1087
|
+
size: "sm",
|
|
1079
1088
|
},
|
|
1080
1089
|
{
|
|
1081
|
-
title:
|
|
1090
|
+
title: "tab1",
|
|
1082
1091
|
},
|
|
1083
1092
|
{
|
|
1084
|
-
title:
|
|
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(
|
|
1100
|
+
it("should match snapshot - md size", async () => {
|
|
1092
1101
|
const wrapper = await createWrapper(
|
|
1093
1102
|
{
|
|
1094
|
-
size:
|
|
1103
|
+
size: "md",
|
|
1095
1104
|
},
|
|
1096
1105
|
{
|
|
1097
|
-
title:
|
|
1106
|
+
title: "tab1",
|
|
1098
1107
|
},
|
|
1099
1108
|
{
|
|
1100
|
-
title:
|
|
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(
|
|
1116
|
+
it("should match snapshot - with badgeContent", async () => {
|
|
1108
1117
|
const wrapper = await createWrapper(
|
|
1109
1118
|
{
|
|
1110
|
-
size:
|
|
1119
|
+
size: "sm",
|
|
1111
1120
|
},
|
|
1112
1121
|
{
|
|
1113
|
-
title:
|
|
1114
|
-
badgeContent:
|
|
1122
|
+
title: "tab1",
|
|
1123
|
+
badgeContent: "1",
|
|
1115
1124
|
},
|
|
1116
1125
|
{
|
|
1117
|
-
title:
|
|
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(
|
|
1133
|
+
it("should match snapshot - with icon", async () => {
|
|
1125
1134
|
const wrapper = await createWrapper(
|
|
1126
1135
|
{
|
|
1127
|
-
size:
|
|
1136
|
+
size: "sm",
|
|
1128
1137
|
},
|
|
1129
1138
|
{
|
|
1130
|
-
title:
|
|
1131
|
-
icon:
|
|
1139
|
+
title: "tab1",
|
|
1140
|
+
icon: "bell",
|
|
1132
1141
|
},
|
|
1133
1142
|
{
|
|
1134
|
-
title:
|
|
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(
|
|
1150
|
+
it("should match snapshot - tab change", async () => {
|
|
1142
1151
|
const wrapper = await createWrapper(
|
|
1143
1152
|
{
|
|
1144
|
-
size:
|
|
1153
|
+
size: "sm",
|
|
1145
1154
|
},
|
|
1146
1155
|
{
|
|
1147
|
-
title:
|
|
1156
|
+
title: "tab1",
|
|
1148
1157
|
},
|
|
1149
1158
|
{
|
|
1150
|
-
title:
|
|
1159
|
+
title: "tab2",
|
|
1151
1160
|
},
|
|
1152
|
-
)
|
|
1161
|
+
);
|
|
1153
1162
|
|
|
1154
|
-
await wrapper.findAll('button[title="tab2"]')[0].trigger(
|
|
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(
|
|
1167
|
+
it("should match snapshot - vertical direction", async () => {
|
|
1159
1168
|
const wrapper = await createWrapper(
|
|
1160
1169
|
{
|
|
1161
|
-
size:
|
|
1170
|
+
size: "sm",
|
|
1162
1171
|
vertical: true,
|
|
1163
1172
|
},
|
|
1164
1173
|
{
|
|
1165
|
-
title:
|
|
1174
|
+
title: "tab1",
|
|
1166
1175
|
},
|
|
1167
1176
|
{
|
|
1168
|
-
title:
|
|
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(
|
|
1184
|
+
it("should match snapshot - disabled tab", async () => {
|
|
1176
1185
|
const wrapper = await createWrapper(
|
|
1177
1186
|
{
|
|
1178
|
-
size:
|
|
1187
|
+
size: "sm",
|
|
1179
1188
|
},
|
|
1180
1189
|
{
|
|
1181
|
-
title:
|
|
1190
|
+
title: "tab1",
|
|
1182
1191
|
disabled: true,
|
|
1183
1192
|
},
|
|
1184
1193
|
{
|
|
1185
|
-
title:
|
|
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
|
-
|
|
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
|
+
});
|