@fiscozen/input 3.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/input.css +2 -0
- package/dist/input.js +8359 -8345
- package/dist/input.umd.cjs +5 -9
- package/dist/src/FzCurrencyInput.vue.d.ts +345 -197
- package/dist/src/FzInput.vue.d.ts +52 -243
- package/dist/src/types.d.ts +17 -2
- package/dist/src/useInputStyle.d.ts +1 -2
- package/dist/src/utils.d.ts +0 -1
- package/package.json +9 -9
- package/src/FzCurrencyInput.vue +13 -13
- package/src/FzInput.vue +67 -1
- package/src/__tests__/FzCurrencyInput.spec.ts +104 -0
- package/src/__tests__/FzInput.spec.ts +1332 -614
- package/src/types.ts +42 -27
- package/src/useInputStyle.ts +42 -23
- package/tsconfig.tsbuildinfo +1 -1
- package/coverage/FzCurrencyInput.vue.html +0 -640
- package/coverage/FzInput.vue.html +0 -709
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -494
- package/coverage/coverage-final.json +0 -4
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -146
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/useInputStyle.ts.html +0 -343
- package/dist/style.css +0 -1
|
@@ -1,1109 +1,1828 @@
|
|
|
1
|
-
import { describe, it, expect } from
|
|
2
|
-
import { mount } from
|
|
3
|
-
import { FzInput } from
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { mount } from "@vue/test-utils";
|
|
3
|
+
import { FzInput } from "..";
|
|
4
4
|
|
|
5
|
-
const NUMBER_OF_INPUTS = 1000
|
|
5
|
+
const NUMBER_OF_INPUTS = 1000;
|
|
6
6
|
|
|
7
|
-
describe(
|
|
8
|
-
describe(
|
|
9
|
-
it(
|
|
7
|
+
describe("FzInput", () => {
|
|
8
|
+
describe("Rendering", () => {
|
|
9
|
+
it("renders label", async () => {
|
|
10
10
|
const wrapper = mount(FzInput, {
|
|
11
11
|
props: {
|
|
12
|
-
label:
|
|
12
|
+
label: "Label",
|
|
13
13
|
},
|
|
14
14
|
slots: {},
|
|
15
|
-
})
|
|
15
|
+
});
|
|
16
16
|
|
|
17
|
-
expect(wrapper.text()).toContain(
|
|
18
|
-
})
|
|
17
|
+
expect(wrapper.text()).toContain("Label");
|
|
18
|
+
});
|
|
19
19
|
|
|
20
|
-
it(
|
|
20
|
+
it("renders leftIcon", async () => {
|
|
21
21
|
const wrapper = mount(FzInput, {
|
|
22
22
|
props: {
|
|
23
|
-
label:
|
|
24
|
-
leftIcon:
|
|
23
|
+
label: "Label",
|
|
24
|
+
leftIcon: "calendar-lines",
|
|
25
25
|
},
|
|
26
26
|
slots: {},
|
|
27
|
-
})
|
|
27
|
+
});
|
|
28
28
|
|
|
29
|
-
expect(wrapper.find(
|
|
30
|
-
})
|
|
29
|
+
expect(wrapper.find(".fa-calendar-lines")).toBeTruthy();
|
|
30
|
+
});
|
|
31
31
|
|
|
32
|
-
it(
|
|
32
|
+
it("renders rightIcon", async () => {
|
|
33
33
|
const wrapper = mount(FzInput, {
|
|
34
34
|
props: {
|
|
35
|
-
label:
|
|
36
|
-
rightIcon:
|
|
35
|
+
label: "Label",
|
|
36
|
+
rightIcon: "credit-card",
|
|
37
37
|
},
|
|
38
38
|
slots: {},
|
|
39
|
-
})
|
|
39
|
+
});
|
|
40
40
|
|
|
41
|
-
expect(wrapper.find(
|
|
42
|
-
})
|
|
41
|
+
expect(wrapper.find(".fa-credit-card")).toBeTruthy();
|
|
42
|
+
});
|
|
43
43
|
|
|
44
|
-
it(
|
|
44
|
+
it("renders helpText", async () => {
|
|
45
45
|
const wrapper = mount(FzInput, {
|
|
46
46
|
props: {
|
|
47
|
-
label:
|
|
47
|
+
label: "Label",
|
|
48
48
|
},
|
|
49
49
|
slots: {
|
|
50
|
-
helpText:
|
|
50
|
+
helpText: "This is a helper text",
|
|
51
51
|
},
|
|
52
|
-
})
|
|
52
|
+
});
|
|
53
53
|
|
|
54
|
-
await wrapper.vm.$nextTick()
|
|
55
|
-
expect(wrapper.text()).toContain(
|
|
56
|
-
})
|
|
54
|
+
await wrapper.vm.$nextTick();
|
|
55
|
+
expect(wrapper.text()).toContain("This is a helper text");
|
|
56
|
+
});
|
|
57
57
|
|
|
58
|
-
it(
|
|
58
|
+
it("renders errorMessage", async () => {
|
|
59
59
|
const wrapper = mount(FzInput, {
|
|
60
60
|
props: {
|
|
61
|
-
label:
|
|
61
|
+
label: "Label",
|
|
62
62
|
error: true,
|
|
63
63
|
},
|
|
64
64
|
slots: {
|
|
65
|
-
errorMessage:
|
|
65
|
+
errorMessage: "This is an error message",
|
|
66
66
|
},
|
|
67
|
-
})
|
|
67
|
+
});
|
|
68
68
|
|
|
69
|
-
await wrapper.vm.$nextTick()
|
|
70
|
-
expect(wrapper.text()).toContain(
|
|
71
|
-
})
|
|
72
|
-
})
|
|
69
|
+
await wrapper.vm.$nextTick();
|
|
70
|
+
expect(wrapper.text()).toContain("This is an error message");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
73
|
|
|
74
|
-
describe(
|
|
75
|
-
it(
|
|
74
|
+
describe("Input types", () => {
|
|
75
|
+
it("renders email type", async () => {
|
|
76
76
|
const wrapper = mount(FzInput, {
|
|
77
77
|
props: {
|
|
78
|
-
label:
|
|
79
|
-
type:
|
|
78
|
+
label: "Label",
|
|
79
|
+
type: "email",
|
|
80
80
|
},
|
|
81
81
|
slots: {},
|
|
82
|
-
})
|
|
82
|
+
});
|
|
83
83
|
|
|
84
|
-
expect(wrapper.find(
|
|
85
|
-
})
|
|
84
|
+
expect(wrapper.find("input").attributes("type")).toBe("email");
|
|
85
|
+
});
|
|
86
86
|
|
|
87
|
-
it(
|
|
87
|
+
it("renders tel type", async () => {
|
|
88
88
|
const wrapper = mount(FzInput, {
|
|
89
89
|
props: {
|
|
90
|
-
label:
|
|
91
|
-
type:
|
|
90
|
+
label: "Label",
|
|
91
|
+
type: "tel",
|
|
92
92
|
},
|
|
93
93
|
slots: {},
|
|
94
|
-
})
|
|
94
|
+
});
|
|
95
95
|
|
|
96
|
-
expect(wrapper.find(
|
|
97
|
-
})
|
|
96
|
+
expect(wrapper.find("input").attributes("type")).toBe("tel");
|
|
97
|
+
});
|
|
98
98
|
|
|
99
|
-
it(
|
|
99
|
+
it("renders password type", async () => {
|
|
100
100
|
const wrapper = mount(FzInput, {
|
|
101
101
|
props: {
|
|
102
|
-
label:
|
|
103
|
-
type:
|
|
102
|
+
label: "Label",
|
|
103
|
+
type: "password",
|
|
104
104
|
},
|
|
105
105
|
slots: {},
|
|
106
|
-
})
|
|
106
|
+
});
|
|
107
107
|
|
|
108
|
-
expect(wrapper.find(
|
|
109
|
-
})
|
|
110
|
-
})
|
|
108
|
+
expect(wrapper.find("input").attributes("type")).toBe("password");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
111
|
|
|
112
|
-
describe(
|
|
113
|
-
it(
|
|
112
|
+
describe("Input states", () => {
|
|
113
|
+
it("renders disabled", async () => {
|
|
114
114
|
const wrapper = mount(FzInput, {
|
|
115
115
|
props: {
|
|
116
|
-
label:
|
|
116
|
+
label: "Label",
|
|
117
117
|
disabled: true,
|
|
118
118
|
},
|
|
119
119
|
slots: {},
|
|
120
|
-
})
|
|
120
|
+
});
|
|
121
121
|
|
|
122
|
-
expect(wrapper.find(
|
|
123
|
-
})
|
|
122
|
+
expect(wrapper.find("input").attributes("disabled")).toBe("");
|
|
123
|
+
});
|
|
124
124
|
|
|
125
|
-
it(
|
|
125
|
+
it("renders required", async () => {
|
|
126
126
|
const wrapper = mount(FzInput, {
|
|
127
127
|
props: {
|
|
128
|
-
label:
|
|
128
|
+
label: "Label",
|
|
129
129
|
required: true,
|
|
130
130
|
},
|
|
131
131
|
slots: {},
|
|
132
|
-
})
|
|
132
|
+
});
|
|
133
133
|
|
|
134
|
-
await wrapper.vm.$nextTick()
|
|
134
|
+
await wrapper.vm.$nextTick();
|
|
135
135
|
|
|
136
|
-
expect(wrapper.find(
|
|
137
|
-
expect(wrapper.text()).toContain(
|
|
138
|
-
})
|
|
136
|
+
expect(wrapper.find("input").attributes("required")).toBe("");
|
|
137
|
+
expect(wrapper.text()).toContain("*");
|
|
138
|
+
});
|
|
139
139
|
|
|
140
140
|
it('applies autocomplete="off" by default', async () => {
|
|
141
141
|
const wrapper = mount(FzInput, {
|
|
142
142
|
props: {
|
|
143
|
-
label:
|
|
143
|
+
label: "Label",
|
|
144
144
|
},
|
|
145
145
|
slots: {},
|
|
146
|
-
})
|
|
146
|
+
});
|
|
147
147
|
|
|
148
|
-
await wrapper.vm.$nextTick()
|
|
148
|
+
await wrapper.vm.$nextTick();
|
|
149
149
|
|
|
150
|
-
expect(wrapper.find(
|
|
151
|
-
})
|
|
150
|
+
expect(wrapper.find("input").attributes("autocomplete")).toBe("off");
|
|
151
|
+
});
|
|
152
152
|
|
|
153
153
|
it('applies autocomplete="off" when autocomplete is false', async () => {
|
|
154
154
|
const wrapper = mount(FzInput, {
|
|
155
155
|
props: {
|
|
156
|
-
label:
|
|
156
|
+
label: "Label",
|
|
157
157
|
autocomplete: false,
|
|
158
158
|
},
|
|
159
159
|
slots: {},
|
|
160
|
-
})
|
|
160
|
+
});
|
|
161
161
|
|
|
162
|
-
await wrapper.vm.$nextTick()
|
|
162
|
+
await wrapper.vm.$nextTick();
|
|
163
163
|
|
|
164
|
-
expect(wrapper.find(
|
|
165
|
-
})
|
|
164
|
+
expect(wrapper.find("input").attributes("autocomplete")).toBe("off");
|
|
165
|
+
});
|
|
166
166
|
|
|
167
167
|
it('applies autocomplete="on" when autocomplete is true', async () => {
|
|
168
168
|
const wrapper = mount(FzInput, {
|
|
169
169
|
props: {
|
|
170
|
-
label:
|
|
170
|
+
label: "Label",
|
|
171
171
|
autocomplete: true,
|
|
172
172
|
},
|
|
173
173
|
slots: {},
|
|
174
|
-
})
|
|
174
|
+
});
|
|
175
175
|
|
|
176
|
-
await wrapper.vm.$nextTick()
|
|
176
|
+
await wrapper.vm.$nextTick();
|
|
177
177
|
|
|
178
|
-
expect(wrapper.find(
|
|
179
|
-
})
|
|
180
|
-
})
|
|
178
|
+
expect(wrapper.find("input").attributes("autocomplete")).toBe("on");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
181
|
|
|
182
|
-
describe(
|
|
183
|
-
it(
|
|
182
|
+
describe("Events", () => {
|
|
183
|
+
it("emits fzinput:right-icon-click event", async () => {
|
|
184
184
|
const wrapper = mount(FzInput, {
|
|
185
185
|
props: {
|
|
186
|
-
label:
|
|
187
|
-
rightIcon:
|
|
186
|
+
label: "Label",
|
|
187
|
+
rightIcon: "eye",
|
|
188
188
|
},
|
|
189
189
|
slots: {},
|
|
190
|
-
})
|
|
190
|
+
});
|
|
191
191
|
|
|
192
|
-
await wrapper.find(
|
|
192
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
193
193
|
|
|
194
|
-
expect(wrapper.emitted(
|
|
195
|
-
})
|
|
194
|
+
expect(wrapper.emitted("fzinput:right-icon-click")).toBeTruthy();
|
|
195
|
+
});
|
|
196
196
|
|
|
197
|
-
it(
|
|
197
|
+
it("emits fzinput:left-icon-click event", async () => {
|
|
198
198
|
const wrapper = mount(FzInput, {
|
|
199
199
|
props: {
|
|
200
|
-
label:
|
|
201
|
-
leftIcon:
|
|
200
|
+
label: "Label",
|
|
201
|
+
leftIcon: "eye",
|
|
202
202
|
},
|
|
203
203
|
slots: {},
|
|
204
|
-
})
|
|
204
|
+
});
|
|
205
205
|
|
|
206
|
-
await wrapper.find(
|
|
206
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
207
207
|
|
|
208
|
-
expect(wrapper.emitted(
|
|
209
|
-
})
|
|
208
|
+
expect(wrapper.emitted("fzinput:left-icon-click")).toBeTruthy();
|
|
209
|
+
});
|
|
210
210
|
|
|
211
|
-
it(
|
|
211
|
+
it("does not emit fzinput:right-icon-click event when disabled", async () => {
|
|
212
212
|
const wrapper = mount(FzInput, {
|
|
213
213
|
props: {
|
|
214
|
-
label:
|
|
215
|
-
rightIcon:
|
|
214
|
+
label: "Label",
|
|
215
|
+
rightIcon: "eye",
|
|
216
216
|
disabled: true,
|
|
217
217
|
},
|
|
218
218
|
slots: {},
|
|
219
|
-
})
|
|
219
|
+
});
|
|
220
220
|
|
|
221
|
-
await wrapper.find(
|
|
221
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
222
222
|
|
|
223
|
-
expect(wrapper.emitted(
|
|
224
|
-
})
|
|
223
|
+
expect(wrapper.emitted("fzinput:right-icon-click")).toBeFalsy();
|
|
224
|
+
});
|
|
225
225
|
|
|
226
|
-
it(
|
|
226
|
+
it("does not emit fzinput:right-icon-click event when readonly", async () => {
|
|
227
227
|
const wrapper = mount(FzInput, {
|
|
228
228
|
props: {
|
|
229
|
-
label:
|
|
230
|
-
rightIcon:
|
|
229
|
+
label: "Label",
|
|
230
|
+
rightIcon: "eye",
|
|
231
231
|
readonly: true,
|
|
232
232
|
},
|
|
233
233
|
slots: {},
|
|
234
|
-
})
|
|
234
|
+
});
|
|
235
235
|
|
|
236
|
-
await wrapper.find(
|
|
236
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
237
237
|
|
|
238
|
-
expect(wrapper.emitted(
|
|
239
|
-
})
|
|
238
|
+
expect(wrapper.emitted("fzinput:right-icon-click")).toBeFalsy();
|
|
239
|
+
});
|
|
240
240
|
|
|
241
|
-
it(
|
|
241
|
+
it("does not emit fzinput:left-icon-click event when disabled", async () => {
|
|
242
242
|
const wrapper = mount(FzInput, {
|
|
243
243
|
props: {
|
|
244
|
-
label:
|
|
245
|
-
leftIcon:
|
|
244
|
+
label: "Label",
|
|
245
|
+
leftIcon: "eye",
|
|
246
246
|
disabled: true,
|
|
247
247
|
},
|
|
248
248
|
slots: {},
|
|
249
|
-
})
|
|
249
|
+
});
|
|
250
250
|
|
|
251
|
-
await wrapper.find(
|
|
251
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
252
252
|
|
|
253
|
-
expect(wrapper.emitted(
|
|
254
|
-
})
|
|
253
|
+
expect(wrapper.emitted("fzinput:left-icon-click")).toBeFalsy();
|
|
254
|
+
});
|
|
255
255
|
|
|
256
|
-
it(
|
|
256
|
+
it("does not emit fzinput:left-icon-click event when readonly", async () => {
|
|
257
257
|
const wrapper = mount(FzInput, {
|
|
258
258
|
props: {
|
|
259
|
-
label:
|
|
260
|
-
leftIcon:
|
|
259
|
+
label: "Label",
|
|
260
|
+
leftIcon: "eye",
|
|
261
261
|
readonly: true,
|
|
262
262
|
},
|
|
263
263
|
slots: {},
|
|
264
|
-
})
|
|
264
|
+
});
|
|
265
265
|
|
|
266
|
-
await wrapper.find(
|
|
266
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
267
267
|
|
|
268
|
-
expect(wrapper.emitted(
|
|
269
|
-
})
|
|
268
|
+
expect(wrapper.emitted("fzinput:left-icon-click")).toBeFalsy();
|
|
269
|
+
});
|
|
270
270
|
|
|
271
|
-
it(
|
|
271
|
+
it("does not emit fzinput:second-right-icon-click event when disabled", async () => {
|
|
272
272
|
const wrapper = mount(FzInput, {
|
|
273
273
|
props: {
|
|
274
|
-
label:
|
|
275
|
-
secondRightIcon:
|
|
274
|
+
label: "Label",
|
|
275
|
+
secondRightIcon: "eye",
|
|
276
276
|
disabled: true,
|
|
277
277
|
},
|
|
278
278
|
slots: {},
|
|
279
|
-
})
|
|
279
|
+
});
|
|
280
280
|
|
|
281
|
-
await wrapper.find(
|
|
281
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
282
282
|
|
|
283
|
-
expect(wrapper.emitted(
|
|
284
|
-
})
|
|
283
|
+
expect(wrapper.emitted("fzinput:second-right-icon-click")).toBeFalsy();
|
|
284
|
+
});
|
|
285
285
|
|
|
286
|
-
it(
|
|
286
|
+
it("does not emit fzinput:second-right-icon-click event when readonly", async () => {
|
|
287
287
|
const wrapper = mount(FzInput, {
|
|
288
288
|
props: {
|
|
289
|
-
label:
|
|
290
|
-
secondRightIcon:
|
|
289
|
+
label: "Label",
|
|
290
|
+
secondRightIcon: "eye",
|
|
291
291
|
readonly: true,
|
|
292
292
|
},
|
|
293
293
|
slots: {},
|
|
294
|
-
})
|
|
294
|
+
});
|
|
295
295
|
|
|
296
|
-
await wrapper.find(
|
|
296
|
+
await wrapper.find(".fa-eye").trigger("click");
|
|
297
297
|
|
|
298
|
-
expect(wrapper.emitted(
|
|
299
|
-
})
|
|
298
|
+
expect(wrapper.emitted("fzinput:second-right-icon-click")).toBeFalsy();
|
|
299
|
+
});
|
|
300
300
|
|
|
301
|
-
it(
|
|
301
|
+
it("does not emit fzinput:right-icon-click event when disabled and rightIconButton is true", async () => {
|
|
302
302
|
const wrapper = mount(FzInput, {
|
|
303
303
|
props: {
|
|
304
|
-
label:
|
|
305
|
-
rightIcon:
|
|
304
|
+
label: "Label",
|
|
305
|
+
rightIcon: "eye",
|
|
306
306
|
rightIconButton: true,
|
|
307
307
|
disabled: true,
|
|
308
308
|
},
|
|
309
309
|
slots: {},
|
|
310
|
-
})
|
|
310
|
+
});
|
|
311
311
|
|
|
312
|
-
const button = wrapper.findComponent({ name:
|
|
313
|
-
await button.trigger(
|
|
312
|
+
const button = wrapper.findComponent({ name: "FzIconButton" });
|
|
313
|
+
await button.trigger("click");
|
|
314
314
|
|
|
315
|
-
expect(wrapper.emitted(
|
|
316
|
-
})
|
|
315
|
+
expect(wrapper.emitted("fzinput:right-icon-click")).toBeFalsy();
|
|
316
|
+
});
|
|
317
317
|
|
|
318
|
-
it(
|
|
318
|
+
it("does not emit fzinput:second-right-icon-click event when disabled and secondRightIconButton is true", async () => {
|
|
319
319
|
const wrapper = mount(FzInput, {
|
|
320
320
|
props: {
|
|
321
|
-
label:
|
|
322
|
-
secondRightIcon:
|
|
321
|
+
label: "Label",
|
|
322
|
+
secondRightIcon: "eye",
|
|
323
323
|
secondRightIconButton: true,
|
|
324
324
|
disabled: true,
|
|
325
325
|
},
|
|
326
326
|
slots: {},
|
|
327
|
-
})
|
|
327
|
+
});
|
|
328
328
|
|
|
329
|
-
const buttons = wrapper.findAllComponents({ name:
|
|
330
|
-
await buttons[0].trigger(
|
|
329
|
+
const buttons = wrapper.findAllComponents({ name: "FzIconButton" });
|
|
330
|
+
await buttons[0].trigger("click");
|
|
331
331
|
|
|
332
|
-
expect(wrapper.emitted(
|
|
333
|
-
})
|
|
334
|
-
})
|
|
332
|
+
expect(wrapper.emitted("fzinput:second-right-icon-click")).toBeFalsy();
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
335
|
|
|
336
|
-
describe(
|
|
337
|
-
describe(
|
|
338
|
-
it(
|
|
336
|
+
describe("Accessibility", () => {
|
|
337
|
+
describe("Input ARIA attributes", () => {
|
|
338
|
+
it("applies aria-required when required is true", async () => {
|
|
339
339
|
const wrapper = mount(FzInput, {
|
|
340
340
|
props: {
|
|
341
|
-
label:
|
|
341
|
+
label: "Label",
|
|
342
342
|
required: true,
|
|
343
343
|
},
|
|
344
344
|
slots: {},
|
|
345
|
-
})
|
|
345
|
+
});
|
|
346
346
|
|
|
347
|
-
await wrapper.vm.$nextTick()
|
|
347
|
+
await wrapper.vm.$nextTick();
|
|
348
348
|
|
|
349
|
-
const input = wrapper.find(
|
|
350
|
-
expect(input.getAttribute(
|
|
351
|
-
})
|
|
349
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
350
|
+
expect(input.getAttribute("aria-required")).toBe("true");
|
|
351
|
+
});
|
|
352
352
|
|
|
353
353
|
it('applies aria-required="false" when required is false', async () => {
|
|
354
354
|
const wrapper = mount(FzInput, {
|
|
355
355
|
props: {
|
|
356
|
-
label:
|
|
356
|
+
label: "Label",
|
|
357
357
|
required: false,
|
|
358
358
|
},
|
|
359
359
|
slots: {},
|
|
360
|
-
})
|
|
360
|
+
});
|
|
361
361
|
|
|
362
|
-
await wrapper.vm.$nextTick()
|
|
362
|
+
await wrapper.vm.$nextTick();
|
|
363
363
|
|
|
364
|
-
const input = wrapper.find(
|
|
365
|
-
expect(input.getAttribute(
|
|
366
|
-
})
|
|
364
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
365
|
+
expect(input.getAttribute("aria-required")).toBe("false");
|
|
366
|
+
});
|
|
367
367
|
|
|
368
|
-
it(
|
|
368
|
+
it("applies aria-invalid when error is true", async () => {
|
|
369
369
|
const wrapper = mount(FzInput, {
|
|
370
370
|
props: {
|
|
371
|
-
label:
|
|
371
|
+
label: "Label",
|
|
372
372
|
error: true,
|
|
373
373
|
},
|
|
374
374
|
slots: {
|
|
375
|
-
errorMessage:
|
|
375
|
+
errorMessage: "Error message",
|
|
376
376
|
},
|
|
377
|
-
})
|
|
377
|
+
});
|
|
378
378
|
|
|
379
|
-
await wrapper.vm.$nextTick()
|
|
379
|
+
await wrapper.vm.$nextTick();
|
|
380
380
|
|
|
381
|
-
const input = wrapper.find(
|
|
382
|
-
expect(input.getAttribute(
|
|
383
|
-
})
|
|
381
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
382
|
+
expect(input.getAttribute("aria-invalid")).toBe("true");
|
|
383
|
+
});
|
|
384
384
|
|
|
385
385
|
it('applies aria-invalid="false" when error is false', async () => {
|
|
386
386
|
const wrapper = mount(FzInput, {
|
|
387
387
|
props: {
|
|
388
|
-
label:
|
|
388
|
+
label: "Label",
|
|
389
389
|
error: false,
|
|
390
390
|
},
|
|
391
391
|
slots: {},
|
|
392
|
-
})
|
|
392
|
+
});
|
|
393
393
|
|
|
394
|
-
await wrapper.vm.$nextTick()
|
|
394
|
+
await wrapper.vm.$nextTick();
|
|
395
395
|
|
|
396
|
-
const input = wrapper.find(
|
|
397
|
-
expect(input.getAttribute(
|
|
398
|
-
})
|
|
396
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
397
|
+
expect(input.getAttribute("aria-invalid")).toBe("false");
|
|
398
|
+
});
|
|
399
399
|
|
|
400
|
-
it(
|
|
400
|
+
it("applies aria-disabled when disabled is true", async () => {
|
|
401
401
|
const wrapper = mount(FzInput, {
|
|
402
402
|
props: {
|
|
403
|
-
label:
|
|
403
|
+
label: "Label",
|
|
404
404
|
disabled: true,
|
|
405
405
|
},
|
|
406
406
|
slots: {},
|
|
407
|
-
})
|
|
407
|
+
});
|
|
408
408
|
|
|
409
|
-
await wrapper.vm.$nextTick()
|
|
409
|
+
await wrapper.vm.$nextTick();
|
|
410
410
|
|
|
411
|
-
const input = wrapper.find(
|
|
412
|
-
expect(input.getAttribute(
|
|
413
|
-
})
|
|
411
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
412
|
+
expect(input.getAttribute("aria-disabled")).toBe("true");
|
|
413
|
+
});
|
|
414
414
|
|
|
415
415
|
it('applies aria-disabled="false" when disabled is false', async () => {
|
|
416
416
|
const wrapper = mount(FzInput, {
|
|
417
417
|
props: {
|
|
418
|
-
label:
|
|
418
|
+
label: "Label",
|
|
419
419
|
disabled: false,
|
|
420
420
|
},
|
|
421
421
|
slots: {},
|
|
422
|
-
})
|
|
422
|
+
});
|
|
423
423
|
|
|
424
|
-
await wrapper.vm.$nextTick()
|
|
424
|
+
await wrapper.vm.$nextTick();
|
|
425
425
|
|
|
426
|
-
const input = wrapper.find(
|
|
427
|
-
expect(input.getAttribute(
|
|
428
|
-
})
|
|
426
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
427
|
+
expect(input.getAttribute("aria-disabled")).toBe("false");
|
|
428
|
+
});
|
|
429
429
|
|
|
430
|
-
it(
|
|
430
|
+
it("applies aria-labelledby when label is provided", async () => {
|
|
431
431
|
const wrapper = mount(FzInput, {
|
|
432
432
|
props: {
|
|
433
|
-
label:
|
|
433
|
+
label: "Test Label",
|
|
434
434
|
},
|
|
435
435
|
slots: {},
|
|
436
|
-
})
|
|
436
|
+
});
|
|
437
437
|
|
|
438
|
-
await wrapper.vm.$nextTick()
|
|
438
|
+
await wrapper.vm.$nextTick();
|
|
439
|
+
|
|
440
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
441
|
+
const labelId = input.getAttribute("aria-labelledby");
|
|
442
|
+
expect(labelId).toBeTruthy();
|
|
439
443
|
|
|
440
|
-
const input = wrapper.find('input').element as HTMLInputElement
|
|
441
|
-
const labelId = input.getAttribute('aria-labelledby')
|
|
442
|
-
expect(labelId).toBeTruthy()
|
|
443
|
-
|
|
444
444
|
// Verify label element exists with matching id
|
|
445
|
-
const label = wrapper.find(
|
|
446
|
-
expect(label.getAttribute(
|
|
447
|
-
})
|
|
445
|
+
const label = wrapper.find("label").element as HTMLLabelElement;
|
|
446
|
+
expect(label.getAttribute("id")).toBe(labelId);
|
|
447
|
+
});
|
|
448
448
|
|
|
449
|
-
it(
|
|
449
|
+
it("does not apply aria-labelledby when label is not provided", async () => {
|
|
450
450
|
const wrapper = mount(FzInput, {
|
|
451
451
|
props: {},
|
|
452
452
|
slots: {},
|
|
453
|
-
})
|
|
453
|
+
});
|
|
454
454
|
|
|
455
|
-
await wrapper.vm.$nextTick()
|
|
455
|
+
await wrapper.vm.$nextTick();
|
|
456
456
|
|
|
457
|
-
const input = wrapper.find(
|
|
458
|
-
expect(input.getAttribute(
|
|
459
|
-
})
|
|
457
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
458
|
+
expect(input.getAttribute("aria-labelledby")).toBeNull();
|
|
459
|
+
});
|
|
460
460
|
|
|
461
|
-
it(
|
|
461
|
+
it("does not apply aria-labelledby when custom label slot is provided", async () => {
|
|
462
462
|
const wrapper = mount(FzInput, {
|
|
463
463
|
props: {
|
|
464
|
-
label:
|
|
464
|
+
label: "Test Label",
|
|
465
465
|
},
|
|
466
466
|
slots: {
|
|
467
|
-
label: () =>
|
|
467
|
+
label: () => "Custom Label Slot",
|
|
468
468
|
},
|
|
469
|
-
})
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
await wrapper.vm.$nextTick();
|
|
470
472
|
|
|
471
|
-
|
|
473
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
474
|
+
const ariaLabelledBy = input.getAttribute("aria-labelledby");
|
|
472
475
|
|
|
473
|
-
const input = wrapper.find('input').element as HTMLInputElement
|
|
474
|
-
const ariaLabelledBy = input.getAttribute('aria-labelledby')
|
|
475
|
-
|
|
476
476
|
// aria-labelledby should not be set because the default label element
|
|
477
477
|
// with id="${uniqueId}-label" doesn't exist when custom slot is used
|
|
478
|
-
expect(ariaLabelledBy).toBeNull()
|
|
479
|
-
|
|
478
|
+
expect(ariaLabelledBy).toBeNull();
|
|
479
|
+
|
|
480
480
|
// Verify default label element is not rendered
|
|
481
|
-
const defaultLabel = wrapper.find(
|
|
482
|
-
expect(defaultLabel.exists()).toBe(false)
|
|
483
|
-
})
|
|
481
|
+
const defaultLabel = wrapper.find("label");
|
|
482
|
+
expect(defaultLabel.exists()).toBe(false);
|
|
483
|
+
});
|
|
484
484
|
|
|
485
|
-
it(
|
|
485
|
+
it("applies aria-describedby when helpText slot is provided", async () => {
|
|
486
486
|
const wrapper = mount(FzInput, {
|
|
487
487
|
props: {
|
|
488
|
-
label:
|
|
488
|
+
label: "Label",
|
|
489
489
|
},
|
|
490
490
|
slots: {
|
|
491
|
-
helpText:
|
|
491
|
+
helpText: "Help text",
|
|
492
492
|
},
|
|
493
|
-
})
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
await wrapper.vm.$nextTick();
|
|
494
496
|
|
|
495
|
-
|
|
497
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
498
|
+
const describedBy = input.getAttribute("aria-describedby");
|
|
499
|
+
expect(describedBy).toBeTruthy();
|
|
496
500
|
|
|
497
|
-
const input = wrapper.find('input').element as HTMLInputElement
|
|
498
|
-
const describedBy = input.getAttribute('aria-describedby')
|
|
499
|
-
expect(describedBy).toBeTruthy()
|
|
500
|
-
|
|
501
501
|
// Verify help text element exists with matching id
|
|
502
|
-
const helpText = wrapper.find(`#${describedBy}`)
|
|
503
|
-
expect(helpText.exists()).toBe(true)
|
|
504
|
-
expect(helpText.text()).toContain(
|
|
505
|
-
})
|
|
502
|
+
const helpText = wrapper.find(`#${describedBy}`);
|
|
503
|
+
expect(helpText.exists()).toBe(true);
|
|
504
|
+
expect(helpText.text()).toContain("Help text");
|
|
505
|
+
});
|
|
506
506
|
|
|
507
|
-
it(
|
|
507
|
+
it("applies aria-describedby when errorMessage slot is provided", async () => {
|
|
508
508
|
const wrapper = mount(FzInput, {
|
|
509
509
|
props: {
|
|
510
|
-
label:
|
|
510
|
+
label: "Label",
|
|
511
511
|
error: true,
|
|
512
512
|
},
|
|
513
513
|
slots: {
|
|
514
|
-
errorMessage:
|
|
514
|
+
errorMessage: "Error message",
|
|
515
515
|
},
|
|
516
|
-
})
|
|
516
|
+
});
|
|
517
517
|
|
|
518
|
-
await wrapper.vm.$nextTick()
|
|
518
|
+
await wrapper.vm.$nextTick();
|
|
519
|
+
|
|
520
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
521
|
+
const describedBy = input.getAttribute("aria-describedby");
|
|
522
|
+
expect(describedBy).toBeTruthy();
|
|
519
523
|
|
|
520
|
-
const input = wrapper.find('input').element as HTMLInputElement
|
|
521
|
-
const describedBy = input.getAttribute('aria-describedby')
|
|
522
|
-
expect(describedBy).toBeTruthy()
|
|
523
|
-
|
|
524
524
|
// Verify error message element exists with matching id
|
|
525
|
-
const errorMessage = wrapper.find(`#${describedBy}`)
|
|
526
|
-
expect(errorMessage.exists()).toBe(true)
|
|
527
|
-
expect(errorMessage.text()).toContain(
|
|
528
|
-
})
|
|
525
|
+
const errorMessage = wrapper.find(`#${describedBy}`);
|
|
526
|
+
expect(errorMessage.exists()).toBe(true);
|
|
527
|
+
expect(errorMessage.text()).toContain("Error message");
|
|
528
|
+
});
|
|
529
529
|
|
|
530
|
-
it(
|
|
530
|
+
it("does not apply aria-describedby when neither helpText nor errorMessage are provided", async () => {
|
|
531
531
|
const wrapper = mount(FzInput, {
|
|
532
532
|
props: {
|
|
533
|
-
label:
|
|
533
|
+
label: "Label",
|
|
534
534
|
},
|
|
535
535
|
slots: {},
|
|
536
|
-
})
|
|
536
|
+
});
|
|
537
537
|
|
|
538
|
-
await wrapper.vm.$nextTick()
|
|
538
|
+
await wrapper.vm.$nextTick();
|
|
539
539
|
|
|
540
|
-
const input = wrapper.find(
|
|
541
|
-
expect(input.getAttribute(
|
|
542
|
-
})
|
|
543
|
-
})
|
|
540
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
541
|
+
expect(input.getAttribute("aria-describedby")).toBeNull();
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
544
|
|
|
545
|
-
describe(
|
|
545
|
+
describe("Error message accessibility", () => {
|
|
546
546
|
it('applies role="alert" to error message container', async () => {
|
|
547
547
|
const wrapper = mount(FzInput, {
|
|
548
548
|
props: {
|
|
549
|
-
label:
|
|
549
|
+
label: "Label",
|
|
550
550
|
error: true,
|
|
551
551
|
},
|
|
552
552
|
slots: {
|
|
553
|
-
errorMessage:
|
|
553
|
+
errorMessage: "Error message",
|
|
554
554
|
},
|
|
555
|
-
})
|
|
555
|
+
});
|
|
556
556
|
|
|
557
|
-
await wrapper.vm.$nextTick()
|
|
557
|
+
await wrapper.vm.$nextTick();
|
|
558
558
|
|
|
559
|
-
const errorContainer = wrapper.find('[role="alert"]')
|
|
560
|
-
expect(errorContainer.exists()).toBe(true)
|
|
561
|
-
expect(errorContainer.text()).toContain(
|
|
562
|
-
})
|
|
559
|
+
const errorContainer = wrapper.find('[role="alert"]');
|
|
560
|
+
expect(errorContainer.exists()).toBe(true);
|
|
561
|
+
expect(errorContainer.text()).toContain("Error message");
|
|
562
|
+
});
|
|
563
563
|
|
|
564
|
-
it(
|
|
564
|
+
it("does not render error container when error is false", async () => {
|
|
565
565
|
const wrapper = mount(FzInput, {
|
|
566
566
|
props: {
|
|
567
|
-
label:
|
|
567
|
+
label: "Label",
|
|
568
568
|
error: false,
|
|
569
569
|
},
|
|
570
570
|
slots: {
|
|
571
|
-
errorMessage:
|
|
571
|
+
errorMessage: "Error message",
|
|
572
572
|
},
|
|
573
|
-
})
|
|
573
|
+
});
|
|
574
574
|
|
|
575
|
-
await wrapper.vm.$nextTick()
|
|
575
|
+
await wrapper.vm.$nextTick();
|
|
576
576
|
|
|
577
|
-
const errorContainer = wrapper.find('[role="alert"]')
|
|
578
|
-
expect(errorContainer.exists()).toBe(false)
|
|
579
|
-
})
|
|
580
|
-
})
|
|
577
|
+
const errorContainer = wrapper.find('[role="alert"]');
|
|
578
|
+
expect(errorContainer.exists()).toBe(false);
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
581
|
|
|
582
|
-
describe(
|
|
582
|
+
describe("Decorative icons accessibility", () => {
|
|
583
583
|
it('applies aria-hidden="true" to valid checkmark icon', async () => {
|
|
584
584
|
const wrapper = mount(FzInput, {
|
|
585
585
|
props: {
|
|
586
|
-
label:
|
|
586
|
+
label: "Label",
|
|
587
587
|
valid: true,
|
|
588
588
|
},
|
|
589
589
|
slots: {},
|
|
590
|
-
})
|
|
590
|
+
});
|
|
591
591
|
|
|
592
|
-
await wrapper.vm.$nextTick()
|
|
592
|
+
await wrapper.vm.$nextTick();
|
|
593
593
|
|
|
594
594
|
// Find the check icon (FzIcon with name="check")
|
|
595
|
-
const checkIcons = wrapper.findAllComponents({ name:
|
|
596
|
-
const checkIcon = checkIcons.find(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
expect(
|
|
601
|
-
|
|
595
|
+
const checkIcons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
596
|
+
const checkIcon = checkIcons.find(
|
|
597
|
+
(icon) => icon.props("name") === "check",
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
expect(checkIcon?.exists()).toBe(true);
|
|
601
|
+
const rootElement = checkIcon?.element as HTMLElement;
|
|
602
|
+
expect(rootElement.getAttribute("aria-hidden")).toBe("true");
|
|
603
|
+
});
|
|
602
604
|
|
|
603
605
|
it('applies aria-hidden="true" to error icon', async () => {
|
|
604
606
|
const wrapper = mount(FzInput, {
|
|
605
607
|
props: {
|
|
606
|
-
label:
|
|
608
|
+
label: "Label",
|
|
607
609
|
error: true,
|
|
608
610
|
},
|
|
609
611
|
slots: {
|
|
610
|
-
errorMessage:
|
|
612
|
+
errorMessage: "Error message",
|
|
611
613
|
},
|
|
612
|
-
})
|
|
614
|
+
});
|
|
613
615
|
|
|
614
|
-
await wrapper.vm.$nextTick()
|
|
616
|
+
await wrapper.vm.$nextTick();
|
|
615
617
|
|
|
616
618
|
// Find the error icon (FzIcon with name="circle-xmark")
|
|
617
|
-
const errorIcons = wrapper.findAllComponents({ name:
|
|
618
|
-
const errorIcon = errorIcons.find(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
expect(
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
619
|
+
const errorIcons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
620
|
+
const errorIcon = errorIcons.find(
|
|
621
|
+
(icon) => icon.props("name") === "circle-xmark",
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
expect(errorIcon?.exists()).toBe(true);
|
|
625
|
+
const rootElement = errorIcon?.element as HTMLElement;
|
|
626
|
+
expect(rootElement.getAttribute("aria-hidden")).toBe("true");
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
describe("Container accessibility", () => {
|
|
627
631
|
it('applies tabindex="0" to container when not disabled', async () => {
|
|
628
632
|
const wrapper = mount(FzInput, {
|
|
629
633
|
props: {
|
|
630
|
-
label:
|
|
634
|
+
label: "Label",
|
|
631
635
|
},
|
|
632
636
|
slots: {},
|
|
633
|
-
})
|
|
637
|
+
});
|
|
634
638
|
|
|
635
|
-
await wrapper.vm.$nextTick()
|
|
639
|
+
await wrapper.vm.$nextTick();
|
|
636
640
|
|
|
637
|
-
const container = wrapper.find(
|
|
638
|
-
|
|
639
|
-
|
|
641
|
+
const container = wrapper.find(".fz-input > div")
|
|
642
|
+
.element as HTMLElement;
|
|
643
|
+
expect(container.getAttribute("tabindex")).toBe("0");
|
|
644
|
+
});
|
|
640
645
|
|
|
641
|
-
it(
|
|
646
|
+
it("removes tabindex from container when disabled", async () => {
|
|
642
647
|
const wrapper = mount(FzInput, {
|
|
643
648
|
props: {
|
|
644
|
-
label:
|
|
649
|
+
label: "Label",
|
|
645
650
|
disabled: true,
|
|
646
651
|
},
|
|
647
652
|
slots: {},
|
|
648
|
-
})
|
|
653
|
+
});
|
|
649
654
|
|
|
650
|
-
await wrapper.vm.$nextTick()
|
|
655
|
+
await wrapper.vm.$nextTick();
|
|
651
656
|
|
|
652
|
-
const container = wrapper.find(
|
|
653
|
-
|
|
654
|
-
|
|
657
|
+
const container = wrapper.find(".fz-input > div")
|
|
658
|
+
.element as HTMLElement;
|
|
659
|
+
expect(container.getAttribute("tabindex")).toBeNull();
|
|
660
|
+
});
|
|
655
661
|
|
|
656
|
-
it(
|
|
662
|
+
it("removes tabindex from container when readonly", async () => {
|
|
657
663
|
const wrapper = mount(FzInput, {
|
|
658
664
|
props: {
|
|
659
|
-
label:
|
|
665
|
+
label: "Label",
|
|
660
666
|
readonly: true,
|
|
661
667
|
},
|
|
662
668
|
slots: {},
|
|
663
|
-
})
|
|
669
|
+
});
|
|
664
670
|
|
|
665
|
-
await wrapper.vm.$nextTick()
|
|
671
|
+
await wrapper.vm.$nextTick();
|
|
666
672
|
|
|
667
|
-
const container = wrapper.find(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
673
|
+
const container = wrapper.find(".fz-input > div")
|
|
674
|
+
.element as HTMLElement;
|
|
675
|
+
expect(container.getAttribute("tabindex")).toBeNull();
|
|
676
|
+
});
|
|
677
|
+
});
|
|
671
678
|
|
|
672
|
-
describe(
|
|
673
|
-
it(
|
|
679
|
+
describe("Left icon accessibility", () => {
|
|
680
|
+
it("applies accessibility attributes when leftIconAriaLabel is provided", async () => {
|
|
674
681
|
const wrapper = mount(FzInput, {
|
|
675
682
|
props: {
|
|
676
|
-
label:
|
|
677
|
-
leftIcon:
|
|
678
|
-
leftIconAriaLabel:
|
|
683
|
+
label: "Label",
|
|
684
|
+
leftIcon: "calendar-lines",
|
|
685
|
+
leftIconAriaLabel: "Open calendar",
|
|
679
686
|
},
|
|
680
687
|
slots: {},
|
|
681
|
-
})
|
|
688
|
+
});
|
|
682
689
|
|
|
683
|
-
await wrapper.vm.$nextTick()
|
|
690
|
+
await wrapper.vm.$nextTick();
|
|
684
691
|
|
|
685
692
|
// Find the FzIcon component wrapper div (root element)
|
|
686
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
687
|
-
expect(iconComponent.exists()).toBe(true)
|
|
688
|
-
|
|
693
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
694
|
+
expect(iconComponent.exists()).toBe(true);
|
|
695
|
+
|
|
689
696
|
// Get the root element (div wrapper)
|
|
690
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
691
|
-
|
|
697
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
698
|
+
|
|
692
699
|
// Verify attributes are on the root element
|
|
693
|
-
expect(rootElement.getAttribute(
|
|
694
|
-
expect(rootElement.getAttribute(
|
|
695
|
-
expect(rootElement.getAttribute(
|
|
696
|
-
})
|
|
700
|
+
expect(rootElement.getAttribute("role")).toBe("button");
|
|
701
|
+
expect(rootElement.getAttribute("aria-label")).toBe("Open calendar");
|
|
702
|
+
expect(rootElement.getAttribute("tabindex")).toBe("0");
|
|
703
|
+
});
|
|
697
704
|
|
|
698
|
-
it(
|
|
705
|
+
it("does not apply accessibility attributes when leftIconAriaLabel is not provided", async () => {
|
|
699
706
|
const wrapper = mount(FzInput, {
|
|
700
707
|
props: {
|
|
701
|
-
label:
|
|
702
|
-
leftIcon:
|
|
708
|
+
label: "Label",
|
|
709
|
+
leftIcon: "calendar-lines",
|
|
703
710
|
},
|
|
704
711
|
slots: {},
|
|
705
|
-
})
|
|
712
|
+
});
|
|
706
713
|
|
|
707
|
-
await wrapper.vm.$nextTick()
|
|
714
|
+
await wrapper.vm.$nextTick();
|
|
708
715
|
|
|
709
716
|
// Find the FzIcon component wrapper
|
|
710
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
711
|
-
expect(iconComponent.exists()).toBe(true)
|
|
712
|
-
|
|
717
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
718
|
+
expect(iconComponent.exists()).toBe(true);
|
|
719
|
+
|
|
713
720
|
// Get the root element (div wrapper)
|
|
714
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
715
|
-
|
|
721
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
722
|
+
|
|
716
723
|
// Verify attributes are not on the root element
|
|
717
|
-
expect(rootElement.getAttribute(
|
|
718
|
-
expect(rootElement.getAttribute(
|
|
719
|
-
expect(rootElement.getAttribute(
|
|
720
|
-
})
|
|
724
|
+
expect(rootElement.getAttribute("role")).toBeNull();
|
|
725
|
+
expect(rootElement.getAttribute("aria-label")).toBeNull();
|
|
726
|
+
expect(rootElement.getAttribute("tabindex")).toBeNull();
|
|
727
|
+
});
|
|
721
728
|
|
|
722
|
-
it(
|
|
729
|
+
it("removes tabindex when disabled and leftIconAriaLabel is provided", async () => {
|
|
723
730
|
const wrapper = mount(FzInput, {
|
|
724
731
|
props: {
|
|
725
|
-
label:
|
|
726
|
-
leftIcon:
|
|
727
|
-
leftIconAriaLabel:
|
|
732
|
+
label: "Label",
|
|
733
|
+
leftIcon: "calendar-lines",
|
|
734
|
+
leftIconAriaLabel: "Open calendar",
|
|
728
735
|
disabled: true,
|
|
729
736
|
},
|
|
730
737
|
slots: {},
|
|
731
|
-
})
|
|
738
|
+
});
|
|
732
739
|
|
|
733
|
-
await wrapper.vm.$nextTick()
|
|
740
|
+
await wrapper.vm.$nextTick();
|
|
734
741
|
|
|
735
742
|
// Find the FzIcon component wrapper
|
|
736
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
737
|
-
expect(iconComponent.exists()).toBe(true)
|
|
738
|
-
|
|
743
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
744
|
+
expect(iconComponent.exists()).toBe(true);
|
|
745
|
+
|
|
739
746
|
// Get the root element (div wrapper)
|
|
740
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
741
|
-
|
|
747
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
748
|
+
|
|
742
749
|
// Verify attributes are on the root element
|
|
743
|
-
expect(rootElement.getAttribute(
|
|
744
|
-
expect(rootElement.getAttribute(
|
|
745
|
-
expect(rootElement.getAttribute(
|
|
746
|
-
expect(rootElement.getAttribute(
|
|
747
|
-
})
|
|
750
|
+
expect(rootElement.getAttribute("role")).toBe("button");
|
|
751
|
+
expect(rootElement.getAttribute("aria-label")).toBe("Open calendar");
|
|
752
|
+
expect(rootElement.getAttribute("tabindex")).toBeNull(); // Removed when disabled
|
|
753
|
+
expect(rootElement.getAttribute("aria-disabled")).toBe("true");
|
|
754
|
+
});
|
|
748
755
|
|
|
749
|
-
it(
|
|
756
|
+
it("has keyboard handler when leftIconAriaLabel is provided", async () => {
|
|
750
757
|
const wrapper = mount(FzInput, {
|
|
751
758
|
props: {
|
|
752
|
-
label:
|
|
753
|
-
leftIcon:
|
|
754
|
-
leftIconAriaLabel:
|
|
759
|
+
label: "Label",
|
|
760
|
+
leftIcon: "calendar-lines",
|
|
761
|
+
leftIconAriaLabel: "Open calendar",
|
|
755
762
|
},
|
|
756
763
|
slots: {},
|
|
757
|
-
})
|
|
764
|
+
});
|
|
758
765
|
|
|
759
|
-
await wrapper.vm.$nextTick()
|
|
766
|
+
await wrapper.vm.$nextTick();
|
|
760
767
|
|
|
761
768
|
// Find the FzIcon component wrapper
|
|
762
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
763
|
-
expect(iconComponent.exists()).toBe(true)
|
|
764
|
-
|
|
769
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
770
|
+
expect(iconComponent.exists()).toBe(true);
|
|
771
|
+
|
|
765
772
|
// Get the root element (div wrapper)
|
|
766
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
767
|
-
|
|
773
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
774
|
+
|
|
768
775
|
// Verify icon is keyboard accessible (has tabindex)
|
|
769
|
-
expect(rootElement.getAttribute(
|
|
776
|
+
expect(rootElement.getAttribute("tabindex")).toBe("0");
|
|
770
777
|
// Keyboard interaction is tested in Storybook play functions
|
|
771
|
-
})
|
|
772
|
-
})
|
|
778
|
+
});
|
|
779
|
+
});
|
|
773
780
|
|
|
774
|
-
describe(
|
|
775
|
-
it(
|
|
781
|
+
describe("Right icon accessibility", () => {
|
|
782
|
+
it("applies accessibility attributes when rightIconAriaLabel is provided", async () => {
|
|
776
783
|
const wrapper = mount(FzInput, {
|
|
777
784
|
props: {
|
|
778
|
-
label:
|
|
779
|
-
rightIcon:
|
|
780
|
-
rightIconAriaLabel:
|
|
785
|
+
label: "Label",
|
|
786
|
+
rightIcon: "eye",
|
|
787
|
+
rightIconAriaLabel: "Toggle visibility",
|
|
781
788
|
},
|
|
782
789
|
slots: {},
|
|
783
|
-
})
|
|
790
|
+
});
|
|
784
791
|
|
|
785
|
-
await wrapper.vm.$nextTick()
|
|
792
|
+
await wrapper.vm.$nextTick();
|
|
786
793
|
|
|
787
794
|
// Find the FzIcon component wrapper (not the inner img)
|
|
788
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
789
|
-
expect(iconComponent.exists()).toBe(true)
|
|
790
|
-
|
|
795
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
796
|
+
expect(iconComponent.exists()).toBe(true);
|
|
797
|
+
|
|
791
798
|
// Get the root element (div wrapper)
|
|
792
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
793
|
-
|
|
794
|
-
// Verify attributes are on the root element
|
|
795
|
-
expect(rootElement.getAttribute('role')).toBe('button')
|
|
796
|
-
expect(rootElement.getAttribute('aria-label')).toBe('Toggle visibility')
|
|
797
|
-
expect(rootElement.getAttribute('tabindex')).toBe('0')
|
|
798
|
-
})
|
|
799
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
799
800
|
|
|
800
|
-
|
|
801
|
+
// Verify attributes are on the root element
|
|
802
|
+
expect(rootElement.getAttribute("role")).toBe("button");
|
|
803
|
+
expect(rootElement.getAttribute("aria-label")).toBe(
|
|
804
|
+
"Toggle visibility",
|
|
805
|
+
);
|
|
806
|
+
expect(rootElement.getAttribute("tabindex")).toBe("0");
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it("does not apply accessibility attributes when rightIconAriaLabel is not provided", async () => {
|
|
801
810
|
const wrapper = mount(FzInput, {
|
|
802
811
|
props: {
|
|
803
|
-
label:
|
|
804
|
-
rightIcon:
|
|
812
|
+
label: "Label",
|
|
813
|
+
rightIcon: "eye",
|
|
805
814
|
},
|
|
806
815
|
slots: {},
|
|
807
|
-
})
|
|
816
|
+
});
|
|
808
817
|
|
|
809
|
-
await wrapper.vm.$nextTick()
|
|
818
|
+
await wrapper.vm.$nextTick();
|
|
810
819
|
|
|
811
820
|
// Find the FzIcon component wrapper
|
|
812
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
813
|
-
expect(iconComponent.exists()).toBe(true)
|
|
814
|
-
|
|
821
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
822
|
+
expect(iconComponent.exists()).toBe(true);
|
|
823
|
+
|
|
815
824
|
// Get the root element (div wrapper)
|
|
816
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
817
|
-
|
|
825
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
826
|
+
|
|
818
827
|
// Verify attributes are not on the root element
|
|
819
|
-
expect(rootElement.getAttribute(
|
|
820
|
-
expect(rootElement.getAttribute(
|
|
821
|
-
expect(rootElement.getAttribute(
|
|
822
|
-
})
|
|
828
|
+
expect(rootElement.getAttribute("role")).toBeNull();
|
|
829
|
+
expect(rootElement.getAttribute("aria-label")).toBeNull();
|
|
830
|
+
expect(rootElement.getAttribute("tabindex")).toBeNull();
|
|
831
|
+
});
|
|
823
832
|
|
|
824
|
-
it(
|
|
833
|
+
it("removes tabindex when disabled and rightIconAriaLabel is provided", async () => {
|
|
825
834
|
const wrapper = mount(FzInput, {
|
|
826
835
|
props: {
|
|
827
|
-
label:
|
|
828
|
-
rightIcon:
|
|
829
|
-
rightIconAriaLabel:
|
|
836
|
+
label: "Label",
|
|
837
|
+
rightIcon: "eye",
|
|
838
|
+
rightIconAriaLabel: "Toggle visibility",
|
|
830
839
|
disabled: true,
|
|
831
840
|
},
|
|
832
841
|
slots: {},
|
|
833
|
-
})
|
|
842
|
+
});
|
|
834
843
|
|
|
835
|
-
await wrapper.vm.$nextTick()
|
|
844
|
+
await wrapper.vm.$nextTick();
|
|
836
845
|
|
|
837
846
|
// Find the FzIcon component wrapper
|
|
838
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
839
|
-
expect(iconComponent.exists()).toBe(true)
|
|
840
|
-
|
|
847
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
848
|
+
expect(iconComponent.exists()).toBe(true);
|
|
849
|
+
|
|
841
850
|
// Get the root element (div wrapper)
|
|
842
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
843
|
-
|
|
844
|
-
// Verify attributes are on the root element
|
|
845
|
-
expect(rootElement.getAttribute('role')).toBe('button')
|
|
846
|
-
expect(rootElement.getAttribute('aria-label')).toBe('Toggle visibility')
|
|
847
|
-
expect(rootElement.getAttribute('tabindex')).toBeNull() // Removed when disabled
|
|
848
|
-
expect(rootElement.getAttribute('aria-disabled')).toBe('true')
|
|
849
|
-
})
|
|
851
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
850
852
|
|
|
851
|
-
|
|
853
|
+
// Verify attributes are on the root element
|
|
854
|
+
expect(rootElement.getAttribute("role")).toBe("button");
|
|
855
|
+
expect(rootElement.getAttribute("aria-label")).toBe(
|
|
856
|
+
"Toggle visibility",
|
|
857
|
+
);
|
|
858
|
+
expect(rootElement.getAttribute("tabindex")).toBeNull(); // Removed when disabled
|
|
859
|
+
expect(rootElement.getAttribute("aria-disabled")).toBe("true");
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it("removes tabindex when readonly and rightIconAriaLabel is provided", async () => {
|
|
852
863
|
const wrapper = mount(FzInput, {
|
|
853
864
|
props: {
|
|
854
|
-
label:
|
|
855
|
-
rightIcon:
|
|
856
|
-
rightIconAriaLabel:
|
|
865
|
+
label: "Label",
|
|
866
|
+
rightIcon: "eye",
|
|
867
|
+
rightIconAriaLabel: "Toggle visibility",
|
|
857
868
|
readonly: true,
|
|
858
869
|
},
|
|
859
870
|
slots: {},
|
|
860
|
-
})
|
|
871
|
+
});
|
|
861
872
|
|
|
862
|
-
await wrapper.vm.$nextTick()
|
|
873
|
+
await wrapper.vm.$nextTick();
|
|
863
874
|
|
|
864
875
|
// Find the FzIcon component wrapper
|
|
865
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
866
|
-
expect(iconComponent.exists()).toBe(true)
|
|
867
|
-
|
|
876
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
877
|
+
expect(iconComponent.exists()).toBe(true);
|
|
878
|
+
|
|
868
879
|
// Get the root element (div wrapper)
|
|
869
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
870
|
-
|
|
871
|
-
// Verify attributes are on the root element
|
|
872
|
-
expect(rootElement.getAttribute('role')).toBe('button')
|
|
873
|
-
expect(rootElement.getAttribute('aria-label')).toBe('Toggle visibility')
|
|
874
|
-
expect(rootElement.getAttribute('tabindex')).toBeNull() // Removed when readonly
|
|
875
|
-
expect(rootElement.getAttribute('aria-disabled')).toBe('true')
|
|
876
|
-
})
|
|
880
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
877
881
|
|
|
878
|
-
|
|
882
|
+
// Verify attributes are on the root element
|
|
883
|
+
expect(rootElement.getAttribute("role")).toBe("button");
|
|
884
|
+
expect(rootElement.getAttribute("aria-label")).toBe(
|
|
885
|
+
"Toggle visibility",
|
|
886
|
+
);
|
|
887
|
+
expect(rootElement.getAttribute("tabindex")).toBeNull(); // Removed when readonly
|
|
888
|
+
expect(rootElement.getAttribute("aria-disabled")).toBe("true");
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
it("does not apply accessibility attributes when rightIconButton is true", async () => {
|
|
879
892
|
const wrapper = mount(FzInput, {
|
|
880
893
|
props: {
|
|
881
|
-
label:
|
|
882
|
-
rightIcon:
|
|
894
|
+
label: "Label",
|
|
895
|
+
rightIcon: "eye",
|
|
883
896
|
rightIconButton: true,
|
|
884
|
-
rightIconAriaLabel:
|
|
897
|
+
rightIconAriaLabel: "Toggle visibility",
|
|
885
898
|
},
|
|
886
899
|
slots: {},
|
|
887
|
-
})
|
|
900
|
+
});
|
|
888
901
|
|
|
889
|
-
await wrapper.vm.$nextTick()
|
|
902
|
+
await wrapper.vm.$nextTick();
|
|
890
903
|
|
|
891
904
|
// When rightIconButton is true, FzIconButton is used instead of FzIcon
|
|
892
|
-
const iconButton = wrapper.findComponent({ name:
|
|
893
|
-
expect(iconButton.exists()).toBe(true)
|
|
894
|
-
})
|
|
905
|
+
const iconButton = wrapper.findComponent({ name: "FzIconButton" });
|
|
906
|
+
expect(iconButton.exists()).toBe(true);
|
|
907
|
+
});
|
|
895
908
|
|
|
896
|
-
it(
|
|
909
|
+
it("has keyboard handler when rightIconAriaLabel is provided", async () => {
|
|
897
910
|
const wrapper = mount(FzInput, {
|
|
898
911
|
props: {
|
|
899
|
-
label:
|
|
900
|
-
rightIcon:
|
|
901
|
-
rightIconAriaLabel:
|
|
912
|
+
label: "Label",
|
|
913
|
+
rightIcon: "eye",
|
|
914
|
+
rightIconAriaLabel: "Toggle visibility",
|
|
902
915
|
},
|
|
903
916
|
slots: {},
|
|
904
|
-
})
|
|
917
|
+
});
|
|
905
918
|
|
|
906
|
-
await wrapper.vm.$nextTick()
|
|
919
|
+
await wrapper.vm.$nextTick();
|
|
907
920
|
|
|
908
921
|
// Find the FzIcon component wrapper
|
|
909
|
-
const iconComponent = wrapper.findComponent({ name:
|
|
910
|
-
expect(iconComponent.exists()).toBe(true)
|
|
911
|
-
|
|
922
|
+
const iconComponent = wrapper.findComponent({ name: "FzIcon" });
|
|
923
|
+
expect(iconComponent.exists()).toBe(true);
|
|
924
|
+
|
|
912
925
|
// Get the root element (div wrapper)
|
|
913
|
-
const rootElement = iconComponent.element as HTMLElement
|
|
914
|
-
|
|
926
|
+
const rootElement = iconComponent.element as HTMLElement;
|
|
927
|
+
|
|
915
928
|
// Verify icon is keyboard accessible (has tabindex)
|
|
916
|
-
expect(rootElement.getAttribute(
|
|
929
|
+
expect(rootElement.getAttribute("tabindex")).toBe("0");
|
|
917
930
|
// Keyboard interaction is tested in Storybook play functions
|
|
918
|
-
})
|
|
919
|
-
})
|
|
931
|
+
});
|
|
932
|
+
});
|
|
920
933
|
|
|
921
|
-
describe(
|
|
922
|
-
it(
|
|
934
|
+
describe("Second right icon accessibility", () => {
|
|
935
|
+
it("applies accessibility attributes when secondRightIconAriaLabel is provided", async () => {
|
|
923
936
|
const wrapper = mount(FzInput, {
|
|
924
937
|
props: {
|
|
925
|
-
label:
|
|
926
|
-
secondRightIcon:
|
|
927
|
-
secondRightIconAriaLabel:
|
|
938
|
+
label: "Label",
|
|
939
|
+
secondRightIcon: "info-circle",
|
|
940
|
+
secondRightIconAriaLabel: "Show information",
|
|
928
941
|
},
|
|
929
942
|
slots: {},
|
|
930
|
-
})
|
|
943
|
+
});
|
|
931
944
|
|
|
932
|
-
await wrapper.vm.$nextTick()
|
|
945
|
+
await wrapper.vm.$nextTick();
|
|
933
946
|
|
|
934
947
|
// Find all FzIcon components and get the one with secondRightIcon
|
|
935
|
-
const iconComponents = wrapper.findAllComponents({ name:
|
|
936
|
-
const secondIconComponent = iconComponents.find(
|
|
937
|
-
icon.props(
|
|
938
|
-
)
|
|
939
|
-
|
|
940
|
-
expect(secondIconComponent?.exists()).toBe(true)
|
|
941
|
-
|
|
948
|
+
const iconComponents = wrapper.findAllComponents({ name: "FzIcon" });
|
|
949
|
+
const secondIconComponent = iconComponents.find(
|
|
950
|
+
(icon) => icon.props("name") === "info-circle",
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
expect(secondIconComponent?.exists()).toBe(true);
|
|
954
|
+
|
|
942
955
|
// Get the root element (div wrapper)
|
|
943
|
-
const rootElement = secondIconComponent?.element as HTMLElement
|
|
944
|
-
|
|
956
|
+
const rootElement = secondIconComponent?.element as HTMLElement;
|
|
957
|
+
|
|
945
958
|
// Verify attributes are on the root element
|
|
946
|
-
expect(rootElement.getAttribute(
|
|
947
|
-
expect(rootElement.getAttribute(
|
|
948
|
-
expect(rootElement.getAttribute(
|
|
949
|
-
})
|
|
959
|
+
expect(rootElement.getAttribute("role")).toBe("button");
|
|
960
|
+
expect(rootElement.getAttribute("aria-label")).toBe("Show information");
|
|
961
|
+
expect(rootElement.getAttribute("tabindex")).toBe("0");
|
|
962
|
+
});
|
|
950
963
|
|
|
951
|
-
it(
|
|
964
|
+
it("removes tabindex when readonly and secondRightIconAriaLabel is provided", async () => {
|
|
952
965
|
const wrapper = mount(FzInput, {
|
|
953
966
|
props: {
|
|
954
|
-
label:
|
|
955
|
-
secondRightIcon:
|
|
956
|
-
secondRightIconAriaLabel:
|
|
967
|
+
label: "Label",
|
|
968
|
+
secondRightIcon: "info-circle",
|
|
969
|
+
secondRightIconAriaLabel: "Show information",
|
|
957
970
|
readonly: true,
|
|
958
971
|
},
|
|
959
972
|
slots: {},
|
|
960
|
-
})
|
|
973
|
+
});
|
|
961
974
|
|
|
962
|
-
await wrapper.vm.$nextTick()
|
|
975
|
+
await wrapper.vm.$nextTick();
|
|
963
976
|
|
|
964
977
|
// Find all FzIcon components and get the one with secondRightIcon
|
|
965
|
-
const iconComponents = wrapper.findAllComponents({ name:
|
|
966
|
-
const secondIconComponent = iconComponents.find(
|
|
967
|
-
icon.props(
|
|
968
|
-
)
|
|
969
|
-
|
|
970
|
-
expect(secondIconComponent?.exists()).toBe(true)
|
|
971
|
-
|
|
978
|
+
const iconComponents = wrapper.findAllComponents({ name: "FzIcon" });
|
|
979
|
+
const secondIconComponent = iconComponents.find(
|
|
980
|
+
(icon) => icon.props("name") === "info-circle",
|
|
981
|
+
);
|
|
982
|
+
|
|
983
|
+
expect(secondIconComponent?.exists()).toBe(true);
|
|
984
|
+
|
|
972
985
|
// Get the root element (div wrapper)
|
|
973
|
-
const rootElement = secondIconComponent?.element as HTMLElement
|
|
974
|
-
|
|
975
|
-
// Verify attributes are on the root element
|
|
976
|
-
expect(rootElement.getAttribute('role')).toBe('button')
|
|
977
|
-
expect(rootElement.getAttribute('aria-label')).toBe('Show information')
|
|
978
|
-
expect(rootElement.getAttribute('tabindex')).toBeNull() // Removed when readonly
|
|
979
|
-
expect(rootElement.getAttribute('aria-disabled')).toBe('true')
|
|
980
|
-
})
|
|
981
|
-
})
|
|
986
|
+
const rootElement = secondIconComponent?.element as HTMLElement;
|
|
982
987
|
|
|
983
|
-
|
|
984
|
-
|
|
988
|
+
// Verify attributes are on the root element
|
|
989
|
+
expect(rootElement.getAttribute("role")).toBe("button");
|
|
990
|
+
expect(rootElement.getAttribute("aria-label")).toBe("Show information");
|
|
991
|
+
expect(rootElement.getAttribute("tabindex")).toBeNull(); // Removed when readonly
|
|
992
|
+
expect(rootElement.getAttribute("aria-disabled")).toBe("true");
|
|
993
|
+
});
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
describe("Right icons order", () => {
|
|
997
|
+
it("renders valid checkmark as last icon when all icons are present", async () => {
|
|
985
998
|
const wrapper = mount(FzInput, {
|
|
986
999
|
props: {
|
|
987
|
-
label:
|
|
1000
|
+
label: "Label",
|
|
988
1001
|
valid: true,
|
|
989
|
-
secondRightIcon:
|
|
990
|
-
rightIcon:
|
|
1002
|
+
secondRightIcon: "info-circle",
|
|
1003
|
+
rightIcon: "envelope",
|
|
991
1004
|
},
|
|
992
1005
|
slots: {},
|
|
993
|
-
})
|
|
1006
|
+
});
|
|
994
1007
|
|
|
995
|
-
await wrapper.vm.$nextTick()
|
|
1008
|
+
await wrapper.vm.$nextTick();
|
|
996
1009
|
|
|
997
1010
|
// Find all FzIcon components in the right-icon slot
|
|
998
|
-
const iconComponents = wrapper.findAllComponents({ name:
|
|
999
|
-
const validIcon = iconComponents.find(
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1011
|
+
const iconComponents = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1012
|
+
const validIcon = iconComponents.find(
|
|
1013
|
+
(icon) => icon.props("name") === "check",
|
|
1014
|
+
);
|
|
1015
|
+
const secondIcon = iconComponents.find(
|
|
1016
|
+
(icon) => icon.props("name") === "info-circle",
|
|
1017
|
+
);
|
|
1018
|
+
const rightIcon = iconComponents.find(
|
|
1019
|
+
(icon) => icon.props("name") === "envelope",
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
expect(validIcon?.exists()).toBe(true);
|
|
1023
|
+
expect(secondIcon?.exists()).toBe(true);
|
|
1024
|
+
expect(rightIcon?.exists()).toBe(true);
|
|
1006
1025
|
|
|
1007
1026
|
// Get the container div that wraps all right icons
|
|
1008
|
-
const rightIconContainer = wrapper.find(
|
|
1009
|
-
|
|
1027
|
+
const rightIconContainer = wrapper.find(
|
|
1028
|
+
".fz-input > div > div.flex.items-center.gap-4",
|
|
1029
|
+
);
|
|
1030
|
+
expect(rightIconContainer.exists()).toBe(true);
|
|
1010
1031
|
|
|
1011
1032
|
// Get all icon elements in order
|
|
1012
|
-
const icons = rightIconContainer.findAllComponents({ name:
|
|
1013
|
-
expect(icons.length).toBeGreaterThanOrEqual(3)
|
|
1033
|
+
const icons = rightIconContainer.findAllComponents({ name: "FzIcon" });
|
|
1034
|
+
expect(icons.length).toBeGreaterThanOrEqual(3);
|
|
1014
1035
|
|
|
1015
1036
|
// Verify order: secondRightIcon, rightIcon, valid (check)
|
|
1016
|
-
const iconNames = icons.map((icon) => icon.props(
|
|
1017
|
-
const secondIndex = iconNames.indexOf(
|
|
1018
|
-
const rightIndex = iconNames.indexOf(
|
|
1019
|
-
const validIndex = iconNames.indexOf(
|
|
1020
|
-
|
|
1021
|
-
expect(secondIndex).toBeLessThan(rightIndex)
|
|
1022
|
-
expect(rightIndex).toBeLessThan(validIndex)
|
|
1023
|
-
})
|
|
1024
|
-
})
|
|
1025
|
-
})
|
|
1026
|
-
|
|
1027
|
-
describe(
|
|
1028
|
-
it(
|
|
1037
|
+
const iconNames = icons.map((icon) => icon.props("name"));
|
|
1038
|
+
const secondIndex = iconNames.indexOf("info-circle");
|
|
1039
|
+
const rightIndex = iconNames.indexOf("envelope");
|
|
1040
|
+
const validIndex = iconNames.indexOf("check");
|
|
1041
|
+
|
|
1042
|
+
expect(secondIndex).toBeLessThan(rightIndex);
|
|
1043
|
+
expect(rightIndex).toBeLessThan(validIndex);
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
describe("Attribute forwarding (inheritAttrs: false)", () => {
|
|
1049
|
+
it("applies consumer class to root wrapper div", async () => {
|
|
1029
1050
|
const wrapper = mount(FzInput, {
|
|
1030
|
-
props: { label:
|
|
1031
|
-
attrs: { class:
|
|
1032
|
-
})
|
|
1051
|
+
props: { label: "Label" },
|
|
1052
|
+
attrs: { class: "max-w-xs custom-class" },
|
|
1053
|
+
});
|
|
1033
1054
|
|
|
1034
|
-
await wrapper.vm.$nextTick()
|
|
1055
|
+
await wrapper.vm.$nextTick();
|
|
1035
1056
|
|
|
1036
|
-
const rootDiv = wrapper.element as HTMLElement
|
|
1037
|
-
expect(rootDiv.classList.contains(
|
|
1038
|
-
expect(rootDiv.classList.contains(
|
|
1039
|
-
expect(rootDiv.classList.contains(
|
|
1040
|
-
})
|
|
1057
|
+
const rootDiv = wrapper.element as HTMLElement;
|
|
1058
|
+
expect(rootDiv.classList.contains("max-w-xs")).toBe(true);
|
|
1059
|
+
expect(rootDiv.classList.contains("custom-class")).toBe(true);
|
|
1060
|
+
expect(rootDiv.classList.contains("fz-input")).toBe(true);
|
|
1061
|
+
});
|
|
1041
1062
|
|
|
1042
|
-
it(
|
|
1063
|
+
it("does not apply consumer class to native input element", async () => {
|
|
1043
1064
|
const wrapper = mount(FzInput, {
|
|
1044
|
-
props: { label:
|
|
1045
|
-
attrs: { class:
|
|
1046
|
-
})
|
|
1065
|
+
props: { label: "Label" },
|
|
1066
|
+
attrs: { class: "max-w-xs" },
|
|
1067
|
+
});
|
|
1047
1068
|
|
|
1048
|
-
await wrapper.vm.$nextTick()
|
|
1069
|
+
await wrapper.vm.$nextTick();
|
|
1049
1070
|
|
|
1050
|
-
const input = wrapper.find(
|
|
1051
|
-
expect(input.classList.contains(
|
|
1052
|
-
})
|
|
1071
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
1072
|
+
expect(input.classList.contains("max-w-xs")).toBe(false);
|
|
1073
|
+
});
|
|
1053
1074
|
|
|
1054
|
-
it(
|
|
1075
|
+
it("forwards data-* attributes to native input element", async () => {
|
|
1055
1076
|
const wrapper = mount(FzInput, {
|
|
1056
|
-
props: { label:
|
|
1057
|
-
attrs: {
|
|
1058
|
-
})
|
|
1077
|
+
props: { label: "Label" },
|
|
1078
|
+
attrs: { "data-cy": "my-input", "data-testid": "test-input" },
|
|
1079
|
+
});
|
|
1059
1080
|
|
|
1060
|
-
await wrapper.vm.$nextTick()
|
|
1081
|
+
await wrapper.vm.$nextTick();
|
|
1061
1082
|
|
|
1062
|
-
const input = wrapper.find(
|
|
1063
|
-
expect(input.getAttribute(
|
|
1064
|
-
expect(input.getAttribute(
|
|
1083
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
1084
|
+
expect(input.getAttribute("data-cy")).toBe("my-input");
|
|
1085
|
+
expect(input.getAttribute("data-testid")).toBe("test-input");
|
|
1065
1086
|
|
|
1066
|
-
const rootDiv = wrapper.element as HTMLElement
|
|
1067
|
-
expect(rootDiv.getAttribute(
|
|
1068
|
-
})
|
|
1087
|
+
const rootDiv = wrapper.element as HTMLElement;
|
|
1088
|
+
expect(rootDiv.getAttribute("data-cy")).toBeNull();
|
|
1089
|
+
});
|
|
1069
1090
|
|
|
1070
|
-
it(
|
|
1091
|
+
it("forwards consumer id to native input element (overriding internal id)", async () => {
|
|
1071
1092
|
const wrapper = mount(FzInput, {
|
|
1072
|
-
props: { label:
|
|
1073
|
-
attrs: { id:
|
|
1074
|
-
})
|
|
1093
|
+
props: { label: "Label" },
|
|
1094
|
+
attrs: { id: "custom-id" },
|
|
1095
|
+
});
|
|
1075
1096
|
|
|
1076
|
-
await wrapper.vm.$nextTick()
|
|
1097
|
+
await wrapper.vm.$nextTick();
|
|
1077
1098
|
|
|
1078
|
-
const input = wrapper.find(
|
|
1079
|
-
expect(input.getAttribute(
|
|
1099
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
1100
|
+
expect(input.getAttribute("id")).toBe("custom-id");
|
|
1080
1101
|
|
|
1081
|
-
const rootDiv = wrapper.element as HTMLElement
|
|
1082
|
-
expect(rootDiv.getAttribute(
|
|
1083
|
-
})
|
|
1102
|
+
const rootDiv = wrapper.element as HTMLElement;
|
|
1103
|
+
expect(rootDiv.getAttribute("id")).toBeNull();
|
|
1104
|
+
});
|
|
1084
1105
|
|
|
1085
|
-
it(
|
|
1106
|
+
it("forwards consumer aria-* attributes to native input element", async () => {
|
|
1086
1107
|
const wrapper = mount(FzInput, {
|
|
1087
|
-
props: { label:
|
|
1108
|
+
props: { label: "Label" },
|
|
1088
1109
|
attrs: {
|
|
1089
|
-
|
|
1090
|
-
|
|
1110
|
+
"aria-expanded": "true",
|
|
1111
|
+
"aria-haspopup": "listbox",
|
|
1091
1112
|
},
|
|
1092
|
-
})
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
await wrapper.vm.$nextTick();
|
|
1116
|
+
|
|
1117
|
+
const input = wrapper.find("input").element as HTMLInputElement;
|
|
1118
|
+
expect(input.getAttribute("aria-expanded")).toBe("true");
|
|
1119
|
+
expect(input.getAttribute("aria-haspopup")).toBe("listbox");
|
|
1120
|
+
|
|
1121
|
+
const rootDiv = wrapper.element as HTMLElement;
|
|
1122
|
+
expect(rootDiv.getAttribute("aria-expanded")).toBeNull();
|
|
1123
|
+
expect(rootDiv.getAttribute("aria-haspopup")).toBeNull();
|
|
1124
|
+
});
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
describe("Visual emphasis states", () => {
|
|
1128
|
+
describe("Highlighted state", () => {
|
|
1129
|
+
it("applies highlighted container classes when highlighted is true", async () => {
|
|
1130
|
+
const wrapper = mount(FzInput, {
|
|
1131
|
+
props: {
|
|
1132
|
+
label: "Label",
|
|
1133
|
+
highlighted: true,
|
|
1134
|
+
},
|
|
1135
|
+
slots: {},
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
await wrapper.vm.$nextTick();
|
|
1139
|
+
|
|
1140
|
+
const container = wrapper.find(".fz-input > div")
|
|
1141
|
+
.element as HTMLElement;
|
|
1142
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1143
|
+
true,
|
|
1144
|
+
);
|
|
1145
|
+
expect(
|
|
1146
|
+
container.classList.contains("border-semantic-warning-200"),
|
|
1147
|
+
).toBe(true);
|
|
1148
|
+
expect(container.classList.contains("ring-2")).toBe(true);
|
|
1149
|
+
expect(container.classList.contains("ring-semantic-warning-100")).toBe(
|
|
1150
|
+
true,
|
|
1151
|
+
);
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
it("works with backoffice environment", async () => {
|
|
1155
|
+
const wrapper = mount(FzInput, {
|
|
1156
|
+
props: {
|
|
1157
|
+
label: "Label",
|
|
1158
|
+
highlighted: true,
|
|
1159
|
+
environment: "backoffice",
|
|
1160
|
+
},
|
|
1161
|
+
slots: {},
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
await wrapper.vm.$nextTick();
|
|
1165
|
+
|
|
1166
|
+
const container = wrapper.find(".fz-input > div")
|
|
1167
|
+
.element as HTMLElement;
|
|
1168
|
+
expect(container.classList.contains("h-32")).toBe(true);
|
|
1169
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1170
|
+
true,
|
|
1171
|
+
);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
it("works with floating-label variant", async () => {
|
|
1175
|
+
const wrapper = mount(FzInput, {
|
|
1176
|
+
props: {
|
|
1177
|
+
label: "Label",
|
|
1178
|
+
highlighted: true,
|
|
1179
|
+
variant: "floating-label",
|
|
1180
|
+
placeholder: "Placeholder",
|
|
1181
|
+
},
|
|
1182
|
+
slots: {},
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
await wrapper.vm.$nextTick();
|
|
1186
|
+
|
|
1187
|
+
const container = wrapper.find(".fz-input > div")
|
|
1188
|
+
.element as HTMLElement;
|
|
1189
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1190
|
+
true,
|
|
1191
|
+
);
|
|
1192
|
+
expect(container.classList.contains("h-44")).toBe(true);
|
|
1193
|
+
});
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
describe("AIreasoning state", () => {
|
|
1197
|
+
it("applies aiReasoning container classes when aiReasoning is true", async () => {
|
|
1198
|
+
const wrapper = mount(FzInput, {
|
|
1199
|
+
props: {
|
|
1200
|
+
label: "Label",
|
|
1201
|
+
aiReasoning: true,
|
|
1202
|
+
},
|
|
1203
|
+
slots: {},
|
|
1204
|
+
});
|
|
1093
1205
|
|
|
1094
|
-
|
|
1206
|
+
await wrapper.vm.$nextTick();
|
|
1095
1207
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1208
|
+
const container = wrapper.find(".fz-input > div")
|
|
1209
|
+
.element as HTMLElement;
|
|
1210
|
+
expect(container.classList.contains("bg-purple-50")).toBe(true);
|
|
1211
|
+
expect(container.classList.contains("border-purple-600")).toBe(true);
|
|
1212
|
+
expect(container.classList.contains("ring-2")).toBe(true);
|
|
1213
|
+
expect(container.classList.contains("ring-purple-200")).toBe(true);
|
|
1214
|
+
});
|
|
1099
1215
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1216
|
+
it("auto-renders sparkles icon when aiReasoning is true", async () => {
|
|
1217
|
+
const wrapper = mount(FzInput, {
|
|
1218
|
+
props: {
|
|
1219
|
+
label: "Label",
|
|
1220
|
+
aiReasoning: true,
|
|
1221
|
+
},
|
|
1222
|
+
slots: {},
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
await wrapper.vm.$nextTick();
|
|
1226
|
+
|
|
1227
|
+
const icons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1228
|
+
const sparklesIcon = icons.find(
|
|
1229
|
+
(icon: any) => icon.props("name") === "sparkles",
|
|
1230
|
+
);
|
|
1231
|
+
expect(sparklesIcon?.exists()).toBe(true);
|
|
1232
|
+
expect(sparklesIcon?.props("variant")).toBe("fas");
|
|
1233
|
+
|
|
1234
|
+
const rootElement = sparklesIcon?.element as HTMLElement;
|
|
1235
|
+
expect(rootElement.getAttribute("aria-hidden")).toBe("true");
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
it("does not render sparkles when leftIcon is provided", async () => {
|
|
1239
|
+
const wrapper = mount(FzInput, {
|
|
1240
|
+
props: {
|
|
1241
|
+
label: "Label",
|
|
1242
|
+
aiReasoning: true,
|
|
1243
|
+
leftIcon: "search",
|
|
1244
|
+
},
|
|
1245
|
+
slots: {},
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
await wrapper.vm.$nextTick();
|
|
1249
|
+
|
|
1250
|
+
const icons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1251
|
+
const sparklesIcon = icons.find(
|
|
1252
|
+
(icon) => icon.props("name") === "sparkles",
|
|
1253
|
+
);
|
|
1254
|
+
expect(sparklesIcon).toBeUndefined();
|
|
1255
|
+
|
|
1256
|
+
const searchIcon = icons.find(
|
|
1257
|
+
(icon) => icon.props("name") === "search",
|
|
1258
|
+
);
|
|
1259
|
+
expect(searchIcon?.exists()).toBe(true);
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
it("does not render sparkles when left-icon slot is provided", async () => {
|
|
1263
|
+
const wrapper = mount(FzInput, {
|
|
1264
|
+
props: {
|
|
1265
|
+
label: "Label",
|
|
1266
|
+
aiReasoning: true,
|
|
1267
|
+
},
|
|
1268
|
+
slots: {
|
|
1269
|
+
"left-icon": '<span data-testid="custom-icon">Custom</span>',
|
|
1270
|
+
},
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
await wrapper.vm.$nextTick();
|
|
1274
|
+
|
|
1275
|
+
const icons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1276
|
+
const sparklesIcon = icons.find(
|
|
1277
|
+
(icon) => icon.props("name") === "sparkles",
|
|
1278
|
+
);
|
|
1279
|
+
expect(sparklesIcon).toBeUndefined();
|
|
1280
|
+
|
|
1281
|
+
const customIcon = wrapper.find('[data-testid="custom-icon"]');
|
|
1282
|
+
expect(customIcon.exists()).toBe(true);
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
it("sparkles icon has purple color by default", async () => {
|
|
1286
|
+
const wrapper = mount(FzInput, {
|
|
1287
|
+
props: {
|
|
1288
|
+
label: "Label",
|
|
1289
|
+
aiReasoning: true,
|
|
1290
|
+
},
|
|
1291
|
+
slots: {},
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
await wrapper.vm.$nextTick();
|
|
1295
|
+
|
|
1296
|
+
const icons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1297
|
+
const sparklesIcon = icons.find(
|
|
1298
|
+
(icon) => icon.props("name") === "sparkles",
|
|
1299
|
+
);
|
|
1300
|
+
const rootElement = sparklesIcon?.element as HTMLElement;
|
|
1301
|
+
expect(rootElement.classList.contains("text-purple-600")).toBe(true);
|
|
1302
|
+
});
|
|
1105
1303
|
|
|
1106
|
-
|
|
1304
|
+
it("sparkles icon is muted when error is true", async () => {
|
|
1305
|
+
const wrapper = mount(FzInput, {
|
|
1306
|
+
props: {
|
|
1307
|
+
label: "Label",
|
|
1308
|
+
aiReasoning: true,
|
|
1309
|
+
error: true,
|
|
1310
|
+
},
|
|
1311
|
+
slots: {
|
|
1312
|
+
errorMessage: "Error message",
|
|
1313
|
+
},
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
await wrapper.vm.$nextTick();
|
|
1317
|
+
|
|
1318
|
+
const icons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1319
|
+
const sparklesIcon = icons.find(
|
|
1320
|
+
(icon) => icon.props("name") === "sparkles",
|
|
1321
|
+
);
|
|
1322
|
+
const rootElement = sparklesIcon?.element as HTMLElement;
|
|
1323
|
+
expect(rootElement.classList.contains("text-grey-300")).toBe(true);
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
it("sparkles icon is muted when disabled", async () => {
|
|
1327
|
+
const wrapper = mount(FzInput, {
|
|
1328
|
+
props: {
|
|
1329
|
+
label: "Label",
|
|
1330
|
+
aiReasoning: true,
|
|
1331
|
+
disabled: true,
|
|
1332
|
+
},
|
|
1333
|
+
slots: {},
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
await wrapper.vm.$nextTick();
|
|
1337
|
+
|
|
1338
|
+
const icons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1339
|
+
const sparklesIcon = icons.find(
|
|
1340
|
+
(icon) => icon.props("name") === "sparkles",
|
|
1341
|
+
);
|
|
1342
|
+
const rootElement = sparklesIcon?.element as HTMLElement;
|
|
1343
|
+
expect(rootElement.classList.contains("text-grey-300")).toBe(true);
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
it("works with floating-label variant", async () => {
|
|
1347
|
+
const wrapper = mount(FzInput, {
|
|
1348
|
+
props: {
|
|
1349
|
+
label: "Label",
|
|
1350
|
+
aiReasoning: true,
|
|
1351
|
+
variant: "floating-label",
|
|
1352
|
+
placeholder: "Placeholder",
|
|
1353
|
+
},
|
|
1354
|
+
slots: {},
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
await wrapper.vm.$nextTick();
|
|
1358
|
+
|
|
1359
|
+
const container = wrapper.find(".fz-input > div")
|
|
1360
|
+
.element as HTMLElement;
|
|
1361
|
+
expect(container.classList.contains("bg-purple-50")).toBe(true);
|
|
1362
|
+
expect(container.classList.contains("h-44")).toBe(true);
|
|
1363
|
+
});
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
describe("State priority", () => {
|
|
1367
|
+
it("error overrides highlighted", async () => {
|
|
1368
|
+
const wrapper = mount(FzInput, {
|
|
1369
|
+
props: {
|
|
1370
|
+
label: "Label",
|
|
1371
|
+
highlighted: true,
|
|
1372
|
+
error: true,
|
|
1373
|
+
},
|
|
1374
|
+
slots: {
|
|
1375
|
+
errorMessage: "Error message",
|
|
1376
|
+
},
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
await wrapper.vm.$nextTick();
|
|
1380
|
+
|
|
1381
|
+
const container = wrapper.find(".fz-input > div")
|
|
1382
|
+
.element as HTMLElement;
|
|
1383
|
+
expect(container.classList.contains("border-semantic-error-200")).toBe(
|
|
1384
|
+
true,
|
|
1385
|
+
);
|
|
1386
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1387
|
+
false,
|
|
1388
|
+
);
|
|
1389
|
+
expect(container.classList.contains("ring-2")).toBe(false);
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
it("error overrides aiReasoning", async () => {
|
|
1393
|
+
const wrapper = mount(FzInput, {
|
|
1394
|
+
props: {
|
|
1395
|
+
label: "Label",
|
|
1396
|
+
aiReasoning: true,
|
|
1397
|
+
error: true,
|
|
1398
|
+
},
|
|
1399
|
+
slots: {
|
|
1400
|
+
errorMessage: "Error message",
|
|
1401
|
+
},
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
await wrapper.vm.$nextTick();
|
|
1405
|
+
|
|
1406
|
+
const container = wrapper.find(".fz-input > div")
|
|
1407
|
+
.element as HTMLElement;
|
|
1408
|
+
expect(container.classList.contains("border-semantic-error-200")).toBe(
|
|
1409
|
+
true,
|
|
1410
|
+
);
|
|
1411
|
+
expect(container.classList.contains("bg-purple-50")).toBe(false);
|
|
1412
|
+
expect(container.classList.contains("ring-2")).toBe(false);
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
it("disabled overrides highlighted", async () => {
|
|
1416
|
+
const wrapper = mount(FzInput, {
|
|
1417
|
+
props: {
|
|
1418
|
+
label: "Label",
|
|
1419
|
+
highlighted: true,
|
|
1420
|
+
disabled: true,
|
|
1421
|
+
},
|
|
1422
|
+
slots: {},
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
await wrapper.vm.$nextTick();
|
|
1426
|
+
|
|
1427
|
+
const container = wrapper.find(".fz-input > div")
|
|
1428
|
+
.element as HTMLElement;
|
|
1429
|
+
expect(container.classList.contains("bg-grey-100")).toBe(true);
|
|
1430
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1431
|
+
false,
|
|
1432
|
+
);
|
|
1433
|
+
expect(container.classList.contains("ring-2")).toBe(false);
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1436
|
+
it("disabled overrides aiReasoning", async () => {
|
|
1437
|
+
const wrapper = mount(FzInput, {
|
|
1438
|
+
props: {
|
|
1439
|
+
label: "Label",
|
|
1440
|
+
aiReasoning: true,
|
|
1441
|
+
disabled: true,
|
|
1442
|
+
},
|
|
1443
|
+
slots: {},
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
await wrapper.vm.$nextTick();
|
|
1447
|
+
|
|
1448
|
+
const container = wrapper.find(".fz-input > div")
|
|
1449
|
+
.element as HTMLElement;
|
|
1450
|
+
expect(container.classList.contains("bg-grey-100")).toBe(true);
|
|
1451
|
+
expect(container.classList.contains("bg-purple-50")).toBe(false);
|
|
1452
|
+
expect(container.classList.contains("ring-2")).toBe(false);
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
it("readonly overrides highlighted", async () => {
|
|
1456
|
+
const wrapper = mount(FzInput, {
|
|
1457
|
+
props: {
|
|
1458
|
+
label: "Label",
|
|
1459
|
+
highlighted: true,
|
|
1460
|
+
readonly: true,
|
|
1461
|
+
},
|
|
1462
|
+
slots: {},
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
await wrapper.vm.$nextTick();
|
|
1466
|
+
|
|
1467
|
+
const container = wrapper.find(".fz-input > div")
|
|
1468
|
+
.element as HTMLElement;
|
|
1469
|
+
expect(container.classList.contains("bg-grey-100")).toBe(true);
|
|
1470
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1471
|
+
false,
|
|
1472
|
+
);
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
it("highlighted takes priority over aiReasoning when both are set", async () => {
|
|
1476
|
+
const wrapper = mount(FzInput, {
|
|
1477
|
+
props: {
|
|
1478
|
+
label: "Label",
|
|
1479
|
+
highlighted: true,
|
|
1480
|
+
aiReasoning: true,
|
|
1481
|
+
},
|
|
1482
|
+
slots: {},
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
await wrapper.vm.$nextTick();
|
|
1486
|
+
|
|
1487
|
+
const container = wrapper.find(".fz-input > div")
|
|
1488
|
+
.element as HTMLElement;
|
|
1489
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1490
|
+
true,
|
|
1491
|
+
);
|
|
1492
|
+
expect(container.classList.contains("bg-purple-50")).toBe(false);
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
describe("User input resets emphasis", () => {
|
|
1498
|
+
const findSparkles = (wrapper: ReturnType<typeof mount>) => {
|
|
1499
|
+
const icons = wrapper.findAllComponents({ name: "FzIcon" });
|
|
1500
|
+
return icons.find((icon: any) => icon.props("name") === "sparkles");
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
describe("programmatic modelValue change preserves emphasis", () => {
|
|
1504
|
+
it("highlighted styling persists after programmatic value change", async () => {
|
|
1505
|
+
const wrapper = mount(FzInput, {
|
|
1506
|
+
props: {
|
|
1507
|
+
label: "Label",
|
|
1508
|
+
highlighted: true,
|
|
1509
|
+
modelValue: "",
|
|
1510
|
+
},
|
|
1511
|
+
slots: {},
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
await wrapper.vm.$nextTick();
|
|
1515
|
+
const container = wrapper.find(".fz-input > div")
|
|
1516
|
+
.element as HTMLElement;
|
|
1517
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1518
|
+
true,
|
|
1519
|
+
);
|
|
1520
|
+
|
|
1521
|
+
await wrapper.setProps({ modelValue: "programmatic value" });
|
|
1522
|
+
|
|
1523
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1524
|
+
true,
|
|
1525
|
+
);
|
|
1526
|
+
expect(container.classList.contains("ring-semantic-warning-100")).toBe(
|
|
1527
|
+
true,
|
|
1528
|
+
);
|
|
1529
|
+
expect(wrapper.emitted("update:highlighted")).toBeUndefined();
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
it("aiReasoning styling and sparkles persist after programmatic value change", async () => {
|
|
1533
|
+
const wrapper = mount(FzInput, {
|
|
1534
|
+
props: {
|
|
1535
|
+
label: "Label",
|
|
1536
|
+
aiReasoning: true,
|
|
1537
|
+
modelValue: "",
|
|
1538
|
+
},
|
|
1539
|
+
slots: {},
|
|
1540
|
+
});
|
|
1541
|
+
|
|
1542
|
+
await wrapper.vm.$nextTick();
|
|
1543
|
+
const container = wrapper.find(".fz-input > div")
|
|
1544
|
+
.element as HTMLElement;
|
|
1545
|
+
expect(container.classList.contains("bg-purple-50")).toBe(true);
|
|
1546
|
+
expect(findSparkles(wrapper)?.exists()).toBe(true);
|
|
1547
|
+
|
|
1548
|
+
await wrapper.setProps({ modelValue: "programmatic value" });
|
|
1549
|
+
|
|
1550
|
+
expect(container.classList.contains("bg-purple-50")).toBe(true);
|
|
1551
|
+
expect(container.classList.contains("ring-purple-200")).toBe(true);
|
|
1552
|
+
expect(findSparkles(wrapper)?.exists()).toBe(true);
|
|
1553
|
+
expect(wrapper.emitted("update:aiReasoning")).toBeUndefined();
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
it("emphasis persists through multiple programmatic value changes", async () => {
|
|
1557
|
+
const wrapper = mount(FzInput, {
|
|
1558
|
+
props: {
|
|
1559
|
+
label: "Label",
|
|
1560
|
+
highlighted: true,
|
|
1561
|
+
modelValue: "",
|
|
1562
|
+
},
|
|
1563
|
+
slots: {},
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
await wrapper.vm.$nextTick();
|
|
1567
|
+
const container = wrapper.find(".fz-input > div")
|
|
1568
|
+
.element as HTMLElement;
|
|
1569
|
+
|
|
1570
|
+
await wrapper.setProps({ modelValue: "first" });
|
|
1571
|
+
await wrapper.setProps({ modelValue: "second" });
|
|
1572
|
+
await wrapper.setProps({ modelValue: "third" });
|
|
1573
|
+
|
|
1574
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1575
|
+
true,
|
|
1576
|
+
);
|
|
1577
|
+
expect(wrapper.emitted("update:highlighted")).toBeUndefined();
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
it("emphasis persists when programmatic value is cleared to empty string", async () => {
|
|
1581
|
+
const wrapper = mount(FzInput, {
|
|
1582
|
+
props: {
|
|
1583
|
+
label: "Label",
|
|
1584
|
+
aiReasoning: true,
|
|
1585
|
+
modelValue: "initial",
|
|
1586
|
+
},
|
|
1587
|
+
slots: {},
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
await wrapper.vm.$nextTick();
|
|
1591
|
+
const container = wrapper.find(".fz-input > div")
|
|
1592
|
+
.element as HTMLElement;
|
|
1593
|
+
|
|
1594
|
+
await wrapper.setProps({ modelValue: "" });
|
|
1595
|
+
|
|
1596
|
+
expect(container.classList.contains("bg-purple-50")).toBe(true);
|
|
1597
|
+
expect(findSparkles(wrapper)?.exists()).toBe(true);
|
|
1598
|
+
expect(wrapper.emitted("update:aiReasoning")).toBeUndefined();
|
|
1599
|
+
});
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
describe("user input reverts to default state", () => {
|
|
1603
|
+
it("highlighted reverts to default styling on user input", async () => {
|
|
1604
|
+
const wrapper = mount(FzInput, {
|
|
1605
|
+
props: {
|
|
1606
|
+
label: "Label",
|
|
1607
|
+
highlighted: true,
|
|
1608
|
+
},
|
|
1609
|
+
slots: {},
|
|
1610
|
+
});
|
|
1611
|
+
|
|
1612
|
+
await wrapper.vm.$nextTick();
|
|
1613
|
+
const container = wrapper.find(".fz-input > div")
|
|
1614
|
+
.element as HTMLElement;
|
|
1615
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1616
|
+
true,
|
|
1617
|
+
);
|
|
1618
|
+
|
|
1619
|
+
await wrapper.find("input").setValue("user typed");
|
|
1620
|
+
|
|
1621
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1622
|
+
false,
|
|
1623
|
+
);
|
|
1624
|
+
expect(
|
|
1625
|
+
container.classList.contains("border-semantic-warning-200"),
|
|
1626
|
+
).toBe(false);
|
|
1627
|
+
expect(container.classList.contains("ring-semantic-warning-100")).toBe(
|
|
1628
|
+
false,
|
|
1629
|
+
);
|
|
1630
|
+
expect(container.classList.contains("border-grey-300")).toBe(true);
|
|
1631
|
+
});
|
|
1632
|
+
|
|
1633
|
+
it("aiReasoning reverts to default styling and hides sparkles on user input", async () => {
|
|
1634
|
+
const wrapper = mount(FzInput, {
|
|
1635
|
+
props: {
|
|
1636
|
+
label: "Label",
|
|
1637
|
+
aiReasoning: true,
|
|
1638
|
+
},
|
|
1639
|
+
slots: {},
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
await wrapper.vm.$nextTick();
|
|
1643
|
+
const container = wrapper.find(".fz-input > div")
|
|
1644
|
+
.element as HTMLElement;
|
|
1645
|
+
expect(container.classList.contains("bg-purple-50")).toBe(true);
|
|
1646
|
+
expect(findSparkles(wrapper)?.exists()).toBe(true);
|
|
1647
|
+
|
|
1648
|
+
await wrapper.find("input").setValue("user typed");
|
|
1649
|
+
|
|
1650
|
+
expect(container.classList.contains("bg-purple-50")).toBe(false);
|
|
1651
|
+
expect(container.classList.contains("border-purple-600")).toBe(false);
|
|
1652
|
+
expect(container.classList.contains("ring-purple-200")).toBe(false);
|
|
1653
|
+
expect(container.classList.contains("border-grey-300")).toBe(true);
|
|
1654
|
+
expect(findSparkles(wrapper)).toBeUndefined();
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
it("both highlighted and aiReasoning revert on user input", async () => {
|
|
1658
|
+
const wrapper = mount(FzInput, {
|
|
1659
|
+
props: {
|
|
1660
|
+
label: "Label",
|
|
1661
|
+
highlighted: true,
|
|
1662
|
+
aiReasoning: true,
|
|
1663
|
+
},
|
|
1664
|
+
slots: {},
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
await wrapper.vm.$nextTick();
|
|
1668
|
+
const container = wrapper.find(".fz-input > div")
|
|
1669
|
+
.element as HTMLElement;
|
|
1670
|
+
// highlighted takes priority
|
|
1671
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1672
|
+
true,
|
|
1673
|
+
);
|
|
1674
|
+
|
|
1675
|
+
await wrapper.find("input").setValue("user typed");
|
|
1676
|
+
|
|
1677
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1678
|
+
false,
|
|
1679
|
+
);
|
|
1680
|
+
expect(container.classList.contains("bg-purple-50")).toBe(false);
|
|
1681
|
+
expect(container.classList.contains("border-grey-300")).toBe(true);
|
|
1682
|
+
expect(findSparkles(wrapper)).toBeUndefined();
|
|
1683
|
+
});
|
|
1684
|
+
|
|
1685
|
+
it("emits update:highlighted false on user input", async () => {
|
|
1686
|
+
const wrapper = mount(FzInput, {
|
|
1687
|
+
props: { label: "Label", highlighted: true },
|
|
1688
|
+
slots: {},
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
await wrapper.find("input").setValue("typed");
|
|
1692
|
+
|
|
1693
|
+
expect(wrapper.emitted("update:highlighted")).toEqual([[false]]);
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
it("emits update:aiReasoning false on user input", async () => {
|
|
1697
|
+
const wrapper = mount(FzInput, {
|
|
1698
|
+
props: { label: "Label", aiReasoning: true },
|
|
1699
|
+
slots: {},
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
await wrapper.find("input").setValue("typed");
|
|
1703
|
+
|
|
1704
|
+
expect(wrapper.emitted("update:aiReasoning")).toEqual([[false]]);
|
|
1705
|
+
});
|
|
1706
|
+
|
|
1707
|
+
it("emits both update events when both are active", async () => {
|
|
1708
|
+
const wrapper = mount(FzInput, {
|
|
1709
|
+
props: { label: "Label", highlighted: true, aiReasoning: true },
|
|
1710
|
+
slots: {},
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
await wrapper.find("input").setValue("typed");
|
|
1714
|
+
|
|
1715
|
+
expect(wrapper.emitted("update:highlighted")).toEqual([[false]]);
|
|
1716
|
+
expect(wrapper.emitted("update:aiReasoning")).toEqual([[false]]);
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
it("does not emit update events when no emphasis is active", async () => {
|
|
1720
|
+
const wrapper = mount(FzInput, {
|
|
1721
|
+
props: { label: "Label" },
|
|
1722
|
+
slots: {},
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
await wrapper.find("input").setValue("typed");
|
|
1726
|
+
|
|
1727
|
+
expect(wrapper.emitted("update:highlighted")).toBeUndefined();
|
|
1728
|
+
expect(wrapper.emitted("update:aiReasoning")).toBeUndefined();
|
|
1729
|
+
});
|
|
1730
|
+
|
|
1731
|
+
it("only emits once even with multiple user keystrokes", async () => {
|
|
1732
|
+
const wrapper = mount(FzInput, {
|
|
1733
|
+
props: { label: "Label", highlighted: true },
|
|
1734
|
+
slots: {},
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
const input = wrapper.find("input");
|
|
1738
|
+
await input.setValue("a");
|
|
1739
|
+
await input.setValue("ab");
|
|
1740
|
+
await input.setValue("abc");
|
|
1741
|
+
|
|
1742
|
+
// First keystroke resets emphasis; subsequent ones have nothing to reset
|
|
1743
|
+
expect(wrapper.emitted("update:highlighted")).toEqual([[false]]);
|
|
1744
|
+
});
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1747
|
+
describe("re-enabling emphasis after user reset", () => {
|
|
1748
|
+
it("re-applies highlighted when prop cycles false → true after user reset", async () => {
|
|
1749
|
+
const wrapper = mount(FzInput, {
|
|
1750
|
+
props: { label: "Label", highlighted: true },
|
|
1751
|
+
slots: {},
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
const container = wrapper.find(".fz-input > div")
|
|
1755
|
+
.element as HTMLElement;
|
|
1756
|
+
await wrapper.find("input").setValue("typed");
|
|
1757
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1758
|
+
false,
|
|
1759
|
+
);
|
|
1760
|
+
|
|
1761
|
+
// Parent syncs to false then re-enables
|
|
1762
|
+
await wrapper.setProps({ highlighted: false });
|
|
1763
|
+
await wrapper.setProps({ highlighted: true });
|
|
1764
|
+
|
|
1765
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1766
|
+
true,
|
|
1767
|
+
);
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
it("re-applies aiReasoning and sparkles when prop cycles false → true after user reset", async () => {
|
|
1771
|
+
const wrapper = mount(FzInput, {
|
|
1772
|
+
props: { label: "Label", aiReasoning: true },
|
|
1773
|
+
slots: {},
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
const container = wrapper.find(".fz-input > div")
|
|
1777
|
+
.element as HTMLElement;
|
|
1778
|
+
await wrapper.find("input").setValue("typed");
|
|
1779
|
+
expect(container.classList.contains("bg-purple-50")).toBe(false);
|
|
1780
|
+
expect(findSparkles(wrapper)).toBeUndefined();
|
|
1781
|
+
|
|
1782
|
+
await wrapper.setProps({ aiReasoning: false });
|
|
1783
|
+
await wrapper.setProps({ aiReasoning: true });
|
|
1784
|
+
|
|
1785
|
+
expect(container.classList.contains("bg-purple-50")).toBe(true);
|
|
1786
|
+
expect(findSparkles(wrapper)?.exists()).toBe(true);
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
it("user can type again after re-enabled emphasis to reset it a second time", async () => {
|
|
1790
|
+
const wrapper = mount(FzInput, {
|
|
1791
|
+
props: { label: "Label", highlighted: true },
|
|
1792
|
+
slots: {},
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
const container = wrapper.find(".fz-input > div")
|
|
1796
|
+
.element as HTMLElement;
|
|
1797
|
+
const input = wrapper.find("input");
|
|
1798
|
+
|
|
1799
|
+
// First cycle: user types → reset
|
|
1800
|
+
await input.setValue("first");
|
|
1801
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1802
|
+
false,
|
|
1803
|
+
);
|
|
1804
|
+
|
|
1805
|
+
// Parent re-enables
|
|
1806
|
+
await wrapper.setProps({ highlighted: false });
|
|
1807
|
+
await wrapper.setProps({ highlighted: true });
|
|
1808
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1809
|
+
true,
|
|
1810
|
+
);
|
|
1811
|
+
|
|
1812
|
+
// Second cycle: user types again → reset again
|
|
1813
|
+
await input.setValue("second");
|
|
1814
|
+
expect(container.classList.contains("bg-semantic-warning-50")).toBe(
|
|
1815
|
+
false,
|
|
1816
|
+
);
|
|
1817
|
+
expect(wrapper.emitted("update:highlighted")).toEqual([
|
|
1818
|
+
[false],
|
|
1819
|
+
[false],
|
|
1820
|
+
]);
|
|
1821
|
+
});
|
|
1822
|
+
});
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
describe("Edge cases", () => {
|
|
1107
1826
|
it(`renders ${NUMBER_OF_INPUTS} input with different ids`, async () => {
|
|
1108
1827
|
const wrapperList = Array.from({ length: NUMBER_OF_INPUTS }).map((_, i) =>
|
|
1109
1828
|
mount(FzInput, {
|
|
@@ -1112,14 +1831,13 @@ describe('FzInput', () => {
|
|
|
1112
1831
|
},
|
|
1113
1832
|
slots: {},
|
|
1114
1833
|
}),
|
|
1115
|
-
)
|
|
1116
|
-
|
|
1117
|
-
await Promise.all(wrapperList.map((w) => w.vm.$nextTick()))
|
|
1834
|
+
);
|
|
1118
1835
|
|
|
1119
|
-
|
|
1836
|
+
await Promise.all(wrapperList.map((w) => w.vm.$nextTick()));
|
|
1120
1837
|
|
|
1121
|
-
|
|
1122
|
-
})
|
|
1123
|
-
})
|
|
1124
|
-
})
|
|
1838
|
+
const ids = wrapperList.map((w) => w.find("input").attributes("id"));
|
|
1125
1839
|
|
|
1840
|
+
expect(new Set(ids).size).toBe(NUMBER_OF_INPUTS);
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
1843
|
+
});
|