bard-tag_field 0.5.1 → 0.5.2
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.
- checksums.yaml +4 -4
- data/CLAUDE.md +9 -8
- data/Rakefile +1 -1
- data/app/assets/javascripts/input-tag.js +1 -0
- data/input-tag/.gitignore +6 -2
- data/input-tag/CLAUDE.md +87 -0
- data/input-tag/LICENSE +21 -0
- data/input-tag/README.md +135 -0
- data/input-tag/TESTING.md +99 -0
- data/input-tag/bun.lock +821 -0
- data/input-tag/index.html +331 -0
- data/input-tag/package.json +52 -8
- data/input-tag/rollup.config.js +4 -4
- data/input-tag/src/input-tag.js +849 -0
- data/input-tag/src/taggle.js +546 -0
- data/input-tag/test/api-methods.test.js +684 -0
- data/input-tag/test/autocomplete.test.js +615 -0
- data/input-tag/test/basic-functionality.test.js +567 -0
- data/input-tag/test/dom-mutation.test.js +466 -0
- data/input-tag/test/edge-cases.test.js +524 -0
- data/input-tag/test/events.test.js +425 -0
- data/input-tag/test/form-integration.test.js +447 -0
- data/input-tag/test/input-tag.test.js +90 -0
- data/input-tag/test/lib/fail-only.mjs +24 -0
- data/input-tag/test/lib/test-utils.js +187 -0
- data/input-tag/test/nested-datalist.test.js +328 -0
- data/input-tag/test/value-label-separation.test.js +357 -0
- data/input-tag/web-test-runner.config.mjs +20 -0
- data/lib/bard/tag_field/version.rb +1 -1
- metadata +23 -4
- data/app/assets/javascripts/input-tag.js +0 -1859
- data/input-tag/bun.lockb +0 -0
- data/input-tag/index.js +0 -1
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { expect } from '@esm-bundle/chai'
|
|
2
|
+
import '../src/input-tag.js'
|
|
3
|
+
import {
|
|
4
|
+
setupGlobalTestHooks,
|
|
5
|
+
waitForElement,
|
|
6
|
+
waitForUpdate,
|
|
7
|
+
getTagElements,
|
|
8
|
+
getTagValues
|
|
9
|
+
} from './lib/test-utils.js'
|
|
10
|
+
|
|
11
|
+
describe('Form Integration', () => {
|
|
12
|
+
setupGlobalTestHooks()
|
|
13
|
+
|
|
14
|
+
describe('Form Association', () => {
|
|
15
|
+
it('should be associated with parent form', async () => {
|
|
16
|
+
document.body.innerHTML = `
|
|
17
|
+
<form id="test-form">
|
|
18
|
+
<input-tag name="tags" multiple>
|
|
19
|
+
<tag-option value="test">Test</tag-option>
|
|
20
|
+
</input-tag>
|
|
21
|
+
</form>
|
|
22
|
+
`
|
|
23
|
+
const form = document.querySelector('#test-form')
|
|
24
|
+
const inputTag = document.querySelector('input-tag')
|
|
25
|
+
|
|
26
|
+
await waitForElement(inputTag, '_taggle')
|
|
27
|
+
|
|
28
|
+
expect(inputTag.form).to.equal(form)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should be associated with form via form attribute', async () => {
|
|
32
|
+
document.body.innerHTML = `
|
|
33
|
+
<form id="external-form"></form>
|
|
34
|
+
<input-tag name="tags" form="external-form" multiple>
|
|
35
|
+
<tag-option value="test">Test</tag-option>
|
|
36
|
+
</input-tag>
|
|
37
|
+
`
|
|
38
|
+
const form = document.querySelector('#external-form')
|
|
39
|
+
const inputTag = document.querySelector('input-tag')
|
|
40
|
+
|
|
41
|
+
await waitForElement(inputTag, '_taggle')
|
|
42
|
+
|
|
43
|
+
expect(inputTag.form).to.equal(form)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should return null when not associated with any form', async () => {
|
|
47
|
+
document.body.innerHTML = `
|
|
48
|
+
<input-tag name="tags" multiple>
|
|
49
|
+
<tag-option value="test">Test</tag-option>
|
|
50
|
+
</input-tag>
|
|
51
|
+
`
|
|
52
|
+
const inputTag = document.querySelector('input-tag')
|
|
53
|
+
|
|
54
|
+
await waitForElement(inputTag, '_taggle')
|
|
55
|
+
|
|
56
|
+
expect(inputTag.form).to.be.null
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('Form Data Submission', () => {
|
|
61
|
+
it('should include tag values in FormData', async () => {
|
|
62
|
+
document.body.innerHTML = `
|
|
63
|
+
<form>
|
|
64
|
+
<input-tag name="technologies" multiple>
|
|
65
|
+
<tag-option value="javascript">JavaScript</tag-option>
|
|
66
|
+
<tag-option value="python">Python</tag-option>
|
|
67
|
+
<tag-option value="go">Go</tag-option>
|
|
68
|
+
</input-tag>
|
|
69
|
+
</form>
|
|
70
|
+
`
|
|
71
|
+
const form = document.querySelector('form')
|
|
72
|
+
const inputTag = document.querySelector('input-tag')
|
|
73
|
+
|
|
74
|
+
await waitForElement(inputTag, '_taggle')
|
|
75
|
+
|
|
76
|
+
const formData = new FormData(form)
|
|
77
|
+
const values = formData.getAll('technologies')
|
|
78
|
+
|
|
79
|
+
expect(values).to.deep.equal(['javascript', 'python', 'go'])
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should handle empty tag list in FormData', async () => {
|
|
83
|
+
document.body.innerHTML = `
|
|
84
|
+
<form>
|
|
85
|
+
<input-tag name="empty-tags" multiple></input-tag>
|
|
86
|
+
</form>
|
|
87
|
+
`
|
|
88
|
+
const form = document.querySelector('form')
|
|
89
|
+
const inputTag = document.querySelector('input-tag')
|
|
90
|
+
|
|
91
|
+
await waitForElement(inputTag, '_taggle')
|
|
92
|
+
|
|
93
|
+
const formData = new FormData(form)
|
|
94
|
+
const values = formData.getAll('empty-tags')
|
|
95
|
+
|
|
96
|
+
// Should include empty string so server knows to clear values (like Rails multiple checkboxes)
|
|
97
|
+
expect(values).to.deep.equal([''])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should send empty string when all tags are removed from multiple input-tag', async () => {
|
|
101
|
+
document.body.innerHTML = `
|
|
102
|
+
<form>
|
|
103
|
+
<input-tag name="course_ids" multiple>
|
|
104
|
+
<tag-option value="1">Course 1</tag-option>
|
|
105
|
+
</input-tag>
|
|
106
|
+
</form>
|
|
107
|
+
`
|
|
108
|
+
const form = document.querySelector('form')
|
|
109
|
+
const inputTag = document.querySelector('input-tag')
|
|
110
|
+
|
|
111
|
+
await waitForElement(inputTag, '_taggle')
|
|
112
|
+
|
|
113
|
+
// Verify initial state has the value
|
|
114
|
+
let formData = new FormData(form)
|
|
115
|
+
expect(formData.getAll('course_ids')).to.deep.equal(['1'])
|
|
116
|
+
|
|
117
|
+
// Remove all tags (simulates clicking the X button)
|
|
118
|
+
inputTag.removeAll()
|
|
119
|
+
await waitForUpdate()
|
|
120
|
+
|
|
121
|
+
// After removing all tags, FormData should still include an empty string
|
|
122
|
+
// so the server knows to clear the values (like Rails multiple checkboxes)
|
|
123
|
+
formData = new FormData(form)
|
|
124
|
+
expect(formData.getAll('course_ids')).to.deep.equal([''])
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should update FormData when tags change', async () => {
|
|
128
|
+
document.body.innerHTML = `
|
|
129
|
+
<form>
|
|
130
|
+
<input-tag name="dynamic-tags" multiple>
|
|
131
|
+
<tag-option value="initial">Initial</tag-option>
|
|
132
|
+
</input-tag>
|
|
133
|
+
</form>
|
|
134
|
+
`
|
|
135
|
+
const form = document.querySelector('form')
|
|
136
|
+
const inputTag = document.querySelector('input-tag')
|
|
137
|
+
|
|
138
|
+
await waitForElement(inputTag, '_taggle')
|
|
139
|
+
|
|
140
|
+
// Add a tag
|
|
141
|
+
inputTag.add('added')
|
|
142
|
+
await waitForUpdate()
|
|
143
|
+
|
|
144
|
+
const formData = new FormData(form)
|
|
145
|
+
const values = formData.getAll('dynamic-tags')
|
|
146
|
+
|
|
147
|
+
expect(values).to.deep.equal(['initial', 'added'])
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('should handle single tag mode in FormData', async () => {
|
|
151
|
+
document.body.innerHTML = `
|
|
152
|
+
<form>
|
|
153
|
+
<input-tag name="single-tag">
|
|
154
|
+
<tag-option value="only-one">Only One</tag-option>
|
|
155
|
+
</input-tag>
|
|
156
|
+
</form>
|
|
157
|
+
`
|
|
158
|
+
const form = document.querySelector('form')
|
|
159
|
+
const inputTag = document.querySelector('input-tag')
|
|
160
|
+
|
|
161
|
+
await waitForElement(inputTag, '_taggle')
|
|
162
|
+
|
|
163
|
+
const formData = new FormData(form)
|
|
164
|
+
const values = formData.getAll('single-tag')
|
|
165
|
+
|
|
166
|
+
expect(values).to.deep.equal(['only-one'])
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('Form Validation', () => {
|
|
171
|
+
it('should be valid when not required and empty', async () => {
|
|
172
|
+
document.body.innerHTML = `
|
|
173
|
+
<form>
|
|
174
|
+
<input-tag name="optional-tags" multiple></input-tag>
|
|
175
|
+
</form>
|
|
176
|
+
`
|
|
177
|
+
const inputTag = document.querySelector('input-tag')
|
|
178
|
+
|
|
179
|
+
await waitForElement(inputTag, '_taggle')
|
|
180
|
+
|
|
181
|
+
expect(inputTag.checkValidity()).to.be.true
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it('should be invalid when required and empty', async () => {
|
|
185
|
+
document.body.innerHTML = `
|
|
186
|
+
<form>
|
|
187
|
+
<input-tag name="required-tags" required multiple></input-tag>
|
|
188
|
+
</form>
|
|
189
|
+
`
|
|
190
|
+
const inputTag = document.querySelector('input-tag')
|
|
191
|
+
|
|
192
|
+
await waitForElement(inputTag, '_taggle')
|
|
193
|
+
|
|
194
|
+
expect(inputTag.checkValidity()).to.be.false
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('should be valid when required and has tags', async () => {
|
|
198
|
+
document.body.innerHTML = `
|
|
199
|
+
<form>
|
|
200
|
+
<input-tag name="required-tags" required multiple>
|
|
201
|
+
<tag-option value="present">Present</tag-option>
|
|
202
|
+
</input-tag>
|
|
203
|
+
</form>
|
|
204
|
+
`
|
|
205
|
+
const inputTag = document.querySelector('input-tag')
|
|
206
|
+
|
|
207
|
+
await waitForElement(inputTag, '_taggle')
|
|
208
|
+
|
|
209
|
+
expect(inputTag.checkValidity()).to.be.true
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should update validity when tags are added to required field', async () => {
|
|
213
|
+
document.body.innerHTML = `
|
|
214
|
+
<form>
|
|
215
|
+
<input-tag name="required-tags" required multiple></input-tag>
|
|
216
|
+
</form>
|
|
217
|
+
`
|
|
218
|
+
const inputTag = document.querySelector('input-tag')
|
|
219
|
+
|
|
220
|
+
await waitForElement(inputTag, '_taggle')
|
|
221
|
+
|
|
222
|
+
expect(inputTag.checkValidity()).to.be.false
|
|
223
|
+
|
|
224
|
+
inputTag.add('now-valid')
|
|
225
|
+
await waitForUpdate()
|
|
226
|
+
|
|
227
|
+
expect(inputTag.checkValidity()).to.be.true
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should update validity when tags are removed from required field', async () => {
|
|
231
|
+
document.body.innerHTML = `
|
|
232
|
+
<form>
|
|
233
|
+
<input-tag name="required-tags" required multiple>
|
|
234
|
+
<tag-option value="will-remove">Will Remove</tag-option>
|
|
235
|
+
</input-tag>
|
|
236
|
+
</form>
|
|
237
|
+
`
|
|
238
|
+
const inputTag = document.querySelector('input-tag')
|
|
239
|
+
|
|
240
|
+
await waitForElement(inputTag, '_taggle')
|
|
241
|
+
|
|
242
|
+
expect(inputTag.checkValidity()).to.be.true
|
|
243
|
+
|
|
244
|
+
inputTag.removeAll()
|
|
245
|
+
await waitForUpdate()
|
|
246
|
+
|
|
247
|
+
expect(inputTag.checkValidity()).to.be.false
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should support reportValidity() method', async () => {
|
|
251
|
+
document.body.innerHTML = `
|
|
252
|
+
<form>
|
|
253
|
+
<input-tag name="required-tags" required multiple></input-tag>
|
|
254
|
+
</form>
|
|
255
|
+
`
|
|
256
|
+
const inputTag = document.querySelector('input-tag')
|
|
257
|
+
|
|
258
|
+
await waitForElement(inputTag, '_taggle')
|
|
259
|
+
|
|
260
|
+
// Should return false for invalid required field
|
|
261
|
+
expect(inputTag.reportValidity()).to.be.false
|
|
262
|
+
|
|
263
|
+
inputTag.add('valid-now')
|
|
264
|
+
await waitForUpdate()
|
|
265
|
+
|
|
266
|
+
// Should return true after adding a tag
|
|
267
|
+
expect(inputTag.reportValidity()).to.be.true
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('Form Reset Behavior', () => {
|
|
272
|
+
it('should clear all tags when form is reset', async () => {
|
|
273
|
+
document.body.innerHTML = `
|
|
274
|
+
<form>
|
|
275
|
+
<input-tag name="reset-tags" multiple>
|
|
276
|
+
<tag-option value="will-be-cleared">Will Be Cleared</tag-option>
|
|
277
|
+
</input-tag>
|
|
278
|
+
</form>
|
|
279
|
+
`
|
|
280
|
+
const form = document.querySelector('form')
|
|
281
|
+
const inputTag = document.querySelector('input-tag')
|
|
282
|
+
|
|
283
|
+
await waitForElement(inputTag, '_taggle')
|
|
284
|
+
|
|
285
|
+
expect(getTagElements(inputTag)).to.have.length(1)
|
|
286
|
+
|
|
287
|
+
form.reset()
|
|
288
|
+
await waitForUpdate()
|
|
289
|
+
|
|
290
|
+
expect(getTagElements(inputTag)).to.have.length(0)
|
|
291
|
+
expect(inputTag.tags).to.deep.equal([])
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should clear input field when form is reset', async () => {
|
|
295
|
+
document.body.innerHTML = `
|
|
296
|
+
<form>
|
|
297
|
+
<input-tag name="reset-tags" multiple></input-tag>
|
|
298
|
+
</form>
|
|
299
|
+
`
|
|
300
|
+
const form = document.querySelector('form')
|
|
301
|
+
const inputTag = document.querySelector('input-tag')
|
|
302
|
+
|
|
303
|
+
await waitForElement(inputTag, '_taggle')
|
|
304
|
+
|
|
305
|
+
// Set some value in the input
|
|
306
|
+
inputTag._taggleInputTarget.value = 'pending-input'
|
|
307
|
+
|
|
308
|
+
form.reset()
|
|
309
|
+
await waitForUpdate()
|
|
310
|
+
|
|
311
|
+
expect(inputTag._taggleInputTarget.value).to.equal('')
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('should handle multiple input-tags in form reset', async () => {
|
|
315
|
+
document.body.innerHTML = `
|
|
316
|
+
<form>
|
|
317
|
+
<input-tag name="tags1" multiple>
|
|
318
|
+
<tag-option value="tag1">Tag 1</tag-option>
|
|
319
|
+
</input-tag>
|
|
320
|
+
<input-tag name="tags2" multiple>
|
|
321
|
+
<tag-option value="tag2">Tag 2</tag-option>
|
|
322
|
+
</input-tag>
|
|
323
|
+
</form>
|
|
324
|
+
`
|
|
325
|
+
const form = document.querySelector('form')
|
|
326
|
+
const inputTag1 = document.querySelector('input-tag[name="tags1"]')
|
|
327
|
+
const inputTag2 = document.querySelector('input-tag[name="tags2"]')
|
|
328
|
+
|
|
329
|
+
await waitForElement(inputTag1, '_taggle')
|
|
330
|
+
await waitForElement(inputTag2, '_taggle')
|
|
331
|
+
|
|
332
|
+
expect(getTagElements(inputTag1)).to.have.length(1)
|
|
333
|
+
expect(getTagElements(inputTag2)).to.have.length(1)
|
|
334
|
+
|
|
335
|
+
form.reset()
|
|
336
|
+
await waitForUpdate()
|
|
337
|
+
|
|
338
|
+
expect(getTagElements(inputTag1)).to.have.length(0)
|
|
339
|
+
expect(getTagElements(inputTag2)).to.have.length(0)
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('Hidden Input Synchronization', () => {
|
|
344
|
+
it('should have correct name attribute on hidden input', async () => {
|
|
345
|
+
document.body.innerHTML = `
|
|
346
|
+
<input-tag name="sync-test" multiple>
|
|
347
|
+
<tag-option value="test">Test</tag-option>
|
|
348
|
+
</input-tag>
|
|
349
|
+
`
|
|
350
|
+
const inputTag = document.querySelector('input-tag')
|
|
351
|
+
|
|
352
|
+
await waitForElement(inputTag, '_taggle')
|
|
353
|
+
|
|
354
|
+
const hiddenInput = inputTag.shadowRoot.querySelector('input[type="hidden"]')
|
|
355
|
+
expect(hiddenInput.name).to.equal('sync-test')
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('should sync hidden input value with tag values', async () => {
|
|
359
|
+
document.body.innerHTML = `
|
|
360
|
+
<form>
|
|
361
|
+
<input-tag name="sync-test" multiple>
|
|
362
|
+
<tag-option value="initial">Initial</tag-option>
|
|
363
|
+
</input-tag>
|
|
364
|
+
</form>
|
|
365
|
+
`
|
|
366
|
+
const form = document.querySelector('form')
|
|
367
|
+
const inputTag = document.querySelector('input-tag')
|
|
368
|
+
|
|
369
|
+
await waitForElement(inputTag, '_taggle')
|
|
370
|
+
|
|
371
|
+
// Check initial FormData
|
|
372
|
+
let formData = new FormData(form)
|
|
373
|
+
expect(formData.getAll('sync-test')).to.deep.equal(['initial'])
|
|
374
|
+
|
|
375
|
+
// Add a tag and check FormData updates
|
|
376
|
+
inputTag.add('added')
|
|
377
|
+
await waitForUpdate()
|
|
378
|
+
|
|
379
|
+
formData = new FormData(form)
|
|
380
|
+
expect(formData.getAll('sync-test')).to.deep.equal(['initial', 'added'])
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('Custom Validity States', () => {
|
|
385
|
+
it('should support custom validation messages', async () => {
|
|
386
|
+
document.body.innerHTML = `
|
|
387
|
+
<form>
|
|
388
|
+
<input-tag name="custom-valid" required multiple></input-tag>
|
|
389
|
+
</form>
|
|
390
|
+
`
|
|
391
|
+
const inputTag = document.querySelector('input-tag')
|
|
392
|
+
|
|
393
|
+
await waitForElement(inputTag, '_taggle')
|
|
394
|
+
|
|
395
|
+
// Set custom validity message
|
|
396
|
+
inputTag._taggleInputTarget.setCustomValidity('Please select at least one technology')
|
|
397
|
+
|
|
398
|
+
expect(inputTag.checkValidity()).to.be.false
|
|
399
|
+
expect(inputTag._taggleInputTarget.validationMessage).to.equal('Please select at least one technology')
|
|
400
|
+
|
|
401
|
+
// Clear custom validity
|
|
402
|
+
inputTag._taggleInputTarget.setCustomValidity('')
|
|
403
|
+
inputTag.add('valid')
|
|
404
|
+
await waitForUpdate()
|
|
405
|
+
|
|
406
|
+
expect(inputTag.checkValidity()).to.be.true
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
describe('Name Property', () => {
|
|
411
|
+
it('should return correct name from name attribute', async () => {
|
|
412
|
+
document.body.innerHTML = `
|
|
413
|
+
<input-tag name="test-name" multiple></input-tag>
|
|
414
|
+
`
|
|
415
|
+
const inputTag = document.querySelector('input-tag')
|
|
416
|
+
|
|
417
|
+
await waitForElement(inputTag, '_taggle')
|
|
418
|
+
|
|
419
|
+
expect(inputTag.name).to.equal('test-name')
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
it('should return null when name attribute is not set', async () => {
|
|
423
|
+
document.body.innerHTML = `
|
|
424
|
+
<input-tag multiple></input-tag>
|
|
425
|
+
`
|
|
426
|
+
const inputTag = document.querySelector('input-tag')
|
|
427
|
+
|
|
428
|
+
await waitForElement(inputTag, '_taggle')
|
|
429
|
+
|
|
430
|
+
expect(inputTag.name).to.be.null
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it('should update when name attribute changes', async () => {
|
|
434
|
+
document.body.innerHTML = `
|
|
435
|
+
<input-tag name="original-name" multiple></input-tag>
|
|
436
|
+
`
|
|
437
|
+
const inputTag = document.querySelector('input-tag')
|
|
438
|
+
|
|
439
|
+
await waitForElement(inputTag, '_taggle')
|
|
440
|
+
|
|
441
|
+
expect(inputTag.name).to.equal('original-name')
|
|
442
|
+
|
|
443
|
+
inputTag.setAttribute('name', 'new-name')
|
|
444
|
+
expect(inputTag.name).to.equal('new-name')
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { expect } from '@esm-bundle/chai'
|
|
2
|
+
import '../src/input-tag.js'
|
|
3
|
+
import {
|
|
4
|
+
setupGlobalTestHooks,
|
|
5
|
+
setupInputTag,
|
|
6
|
+
waitForElement,
|
|
7
|
+
waitForBasicInitialization,
|
|
8
|
+
waitForUpdate,
|
|
9
|
+
getTagElements
|
|
10
|
+
} from './lib/test-utils.js'
|
|
11
|
+
|
|
12
|
+
describe('Input Tag - Smoke Tests', () => {
|
|
13
|
+
setupGlobalTestHooks()
|
|
14
|
+
|
|
15
|
+
it('should define custom elements', () => {
|
|
16
|
+
expect(customElements.get('input-tag')).to.not.be.undefined
|
|
17
|
+
expect(customElements.get('tag-option')).to.not.be.undefined
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should initialize with empty state', async () => {
|
|
21
|
+
const inputTag = await setupInputTag('<input-tag name="tags" multiple></input-tag>')
|
|
22
|
+
|
|
23
|
+
expect(getTagElements(inputTag)).to.have.length(0)
|
|
24
|
+
expect(inputTag.tags).to.deep.equal([])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should initialize with pre-existing tags', async () => {
|
|
28
|
+
const inputTag = await setupInputTag(`
|
|
29
|
+
<input-tag name="tags" multiple>
|
|
30
|
+
<tag-option value="javascript">JavaScript</tag-option>
|
|
31
|
+
<tag-option value="typescript">TypeScript</tag-option>
|
|
32
|
+
</input-tag>
|
|
33
|
+
`)
|
|
34
|
+
|
|
35
|
+
expect(getTagElements(inputTag)).to.have.length(2)
|
|
36
|
+
expect(inputTag.tags).to.deep.equal(['javascript', 'typescript'])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should add tags programmatically', async () => {
|
|
40
|
+
const inputTag = await setupInputTag('<input-tag name="tags" multiple></input-tag>')
|
|
41
|
+
|
|
42
|
+
inputTag.add('react')
|
|
43
|
+
await waitForUpdate()
|
|
44
|
+
|
|
45
|
+
expect(getTagElements(inputTag)).to.have.length(1)
|
|
46
|
+
expect(inputTag.children[0].value).to.equal('react')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should remove tags programmatically', async () => {
|
|
50
|
+
const inputTag = await setupInputTag(`
|
|
51
|
+
<input-tag name="tags" multiple>
|
|
52
|
+
<tag-option value="javascript">JavaScript</tag-option>
|
|
53
|
+
</input-tag>
|
|
54
|
+
`)
|
|
55
|
+
|
|
56
|
+
inputTag.remove('javascript')
|
|
57
|
+
await waitForUpdate()
|
|
58
|
+
|
|
59
|
+
expect(getTagElements(inputTag)).to.have.length(0)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should handle single tag mode', async () => {
|
|
63
|
+
const inputTag = await setupInputTag('<input-tag name="tag"></input-tag>')
|
|
64
|
+
|
|
65
|
+
inputTag.add('first')
|
|
66
|
+
await waitForUpdate()
|
|
67
|
+
expect(getTagElements(inputTag)).to.have.length(1)
|
|
68
|
+
|
|
69
|
+
// Try to add second tag - should not be added due to maxTags: 1
|
|
70
|
+
inputTag.add('second')
|
|
71
|
+
await waitForUpdate()
|
|
72
|
+
expect(getTagElements(inputTag)).to.have.length(1)
|
|
73
|
+
expect(inputTag.children[0].value).to.equal('first')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should use datalist for autocomplete', async () => {
|
|
77
|
+
document.body.innerHTML = `
|
|
78
|
+
<input-tag name="frameworks" list="suggestions" multiple></input-tag>
|
|
79
|
+
<datalist id="suggestions">
|
|
80
|
+
<option value="react">React</option>
|
|
81
|
+
<option value="vue">Vue</option>
|
|
82
|
+
</datalist>
|
|
83
|
+
`
|
|
84
|
+
const inputTag = document.querySelector('input-tag')
|
|
85
|
+
|
|
86
|
+
await waitForBasicInitialization(inputTag)
|
|
87
|
+
|
|
88
|
+
expect(inputTag.options).to.deep.equal(['react', 'vue'])
|
|
89
|
+
})
|
|
90
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { promises as fs } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: 'fail-only',
|
|
6
|
+
async transform(context) {
|
|
7
|
+
const filePath = context.path
|
|
8
|
+
const failOnlyEnabled = process.argv.includes('--fail-only')
|
|
9
|
+
if (failOnlyEnabled && filePath.match(/^\/test\/[^/]+\.js$/)) {
|
|
10
|
+
const fileContent = await fs.readFile(`.${filePath}`, 'utf-8')
|
|
11
|
+
if (/\bit\.only\b/.test(fileContent)) {
|
|
12
|
+
abort(`--fail-only:\nFound 'it.only' in ${filePath}. Remove it to proceed.`)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function abort(message) {
|
|
19
|
+
const RED = '\x1b[31m' // Red color code
|
|
20
|
+
const RESET = '\x1b[0m' // Reset color code
|
|
21
|
+
process.stderr.write(`${RED}${message}\n${RESET}`, () => {
|
|
22
|
+
process.exit(1)
|
|
23
|
+
})
|
|
24
|
+
}
|