decue 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/decue.js +309 -172
- package/dist/decue.min.js +1 -1
- package/eslint.config.js +53 -0
- package/examples.html +29 -13
- package/npm.sh +1 -1
- package/package.json +17 -8
- package/src/decue.js +309 -172
- package/test/debug.test.js +201 -0
- package/test/decue.test.js +78 -0
- package/test/errors.test.js +212 -0
- package/test/eventHandlers.test.js +95 -0
- package/test/formAssociated.test.js +477 -0
- package/test/init.test.js +43 -0
- package/test/lifecycle.test.js +110 -0
- package/test/memory.test.js +283 -0
- package/test/piped.test.js +152 -0
- package/test/placeholders.test.js +396 -0
- package/test/predefined.test.js +131 -0
- package/test/scriptAttributes.test.js +464 -0
- package/test/slots.test.js +293 -0
- package/test/test-helpers.js +36 -0
- package/tsconfig.json +1 -1
- package/web-test-runner.config.mjs +30 -0
- package/serve.sh +0 -4
- package/test/decue-tests.js +0 -84
- package/test/index.html +0 -65
- package/test/util/util.js +0 -17
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { expect } from '@esm-bundle/chai';
|
|
2
|
+
import { createTemplate, createElement, getContentRoot } from './test-helpers.js';
|
|
3
|
+
|
|
4
|
+
// Unique counter for element names
|
|
5
|
+
let elementCounter = 0;
|
|
6
|
+
function uniqueName(prefix) {
|
|
7
|
+
return `${prefix}-${++elementCounter}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Clean up after each test
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// Remove all test templates and elements
|
|
13
|
+
document.querySelectorAll('template[decue]').forEach(el => el.remove());
|
|
14
|
+
document.querySelectorAll('[data-test-element]').forEach(el => el.remove());
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('formAssociated', () => {
|
|
18
|
+
|
|
19
|
+
// Execute each test for all three custom element variants:
|
|
20
|
+
// 1. No shadow DOM
|
|
21
|
+
// 2. Shadow DOM open
|
|
22
|
+
// 3. Shadow DOM closed
|
|
23
|
+
['none', 'open', 'closed'].forEach(shadow => {
|
|
24
|
+
describe("shadowmode: " + shadow, function() {
|
|
25
|
+
it('form associated element works', function () {
|
|
26
|
+
const name = uniqueName('test-form-element');
|
|
27
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
28
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
29
|
+
formAssociated: true
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const el = createElement(name, {
|
|
33
|
+
'data-test-element': '',
|
|
34
|
+
value: 'test-value',
|
|
35
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Verify form-associated properties exist
|
|
39
|
+
expect(el.internals).to.exist;
|
|
40
|
+
expect(el.value).to.equal('test-value');
|
|
41
|
+
expect(el.form).to.be.null; // Not in a form yet
|
|
42
|
+
expect(el.validity).to.exist;
|
|
43
|
+
expect(el.validationMessage).to.exist;
|
|
44
|
+
expect(el.willValidate).to.be.true;
|
|
45
|
+
|
|
46
|
+
// Verify methods exist
|
|
47
|
+
expect(el.setFormValue).to.be.a('function');
|
|
48
|
+
expect(el.setValidity).to.be.a('function');
|
|
49
|
+
expect(el.checkValidity).to.be.a('function');
|
|
50
|
+
expect(el.reportValidity).to.be.a('function');
|
|
51
|
+
|
|
52
|
+
// Verify the value placeholder is replaced in the template
|
|
53
|
+
const root = getContentRoot(el, shadow);
|
|
54
|
+
if (shadow !== 'closed') {
|
|
55
|
+
const div = root.querySelector('div');
|
|
56
|
+
expect(div).to.exist;
|
|
57
|
+
expect(div.textContent).to.equal('Value: test-value');
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('form associated element validation works', function () {
|
|
62
|
+
const name = uniqueName('test-validation-element');
|
|
63
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
64
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
65
|
+
formAssociated: true
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const el = createElement(name, {
|
|
69
|
+
'data-test-element': '',
|
|
70
|
+
value: '',
|
|
71
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Initially valid
|
|
75
|
+
expect(el.checkValidity()).to.be.true;
|
|
76
|
+
expect(el.validity.valid).to.be.true;
|
|
77
|
+
|
|
78
|
+
// Set custom validity error
|
|
79
|
+
el.setValidity({ valueMissing: true }, 'Value is required');
|
|
80
|
+
expect(el.validity.valid).to.be.false;
|
|
81
|
+
expect(el.validity.valueMissing).to.be.true;
|
|
82
|
+
expect(el.validationMessage).to.equal('Value is required');
|
|
83
|
+
expect(el.checkValidity()).to.be.false;
|
|
84
|
+
|
|
85
|
+
// Clear validity
|
|
86
|
+
el.setValidity({});
|
|
87
|
+
expect(el.validity.valid).to.be.true;
|
|
88
|
+
expect(el.validationMessage).to.equal('');
|
|
89
|
+
expect(el.checkValidity()).to.be.true;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('form associated element value can be changed', function () {
|
|
93
|
+
const name = uniqueName('test-value-change');
|
|
94
|
+
createTemplate(name, '<div>Value: {.value}</div>', {
|
|
95
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
96
|
+
formAssociated: true
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const el = createElement(name, {
|
|
100
|
+
'data-test-element': '',
|
|
101
|
+
value: 'initial',
|
|
102
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(el.value).to.equal('initial');
|
|
106
|
+
|
|
107
|
+
// Change value via property
|
|
108
|
+
el.value = 'changed';
|
|
109
|
+
expect(el.value).to.equal('changed');
|
|
110
|
+
|
|
111
|
+
// Verify placeholder is updated
|
|
112
|
+
const root = getContentRoot(el, shadow);
|
|
113
|
+
if (shadow !== 'closed') {
|
|
114
|
+
const div = root.querySelector('div');
|
|
115
|
+
expect(div).to.exist;
|
|
116
|
+
expect(div.textContent).to.equal('Value: changed');
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('form associated element can be added to a form', function (done) {
|
|
121
|
+
const name = uniqueName('test-form-add');
|
|
122
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
123
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
124
|
+
formAssociated: true
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const form = document.createElement('form');
|
|
128
|
+
form.setAttribute('data-test-element', '');
|
|
129
|
+
document.body.appendChild(form);
|
|
130
|
+
|
|
131
|
+
const el = createElement(name, {
|
|
132
|
+
'data-test-element': '',
|
|
133
|
+
name: 'testfield',
|
|
134
|
+
value: 'test-value',
|
|
135
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Listen for formassociate event
|
|
139
|
+
el.addEventListener('formassociate', (e) => {
|
|
140
|
+
expect(e.detail.form).to.equal(form);
|
|
141
|
+
expect(el.form).to.equal(form);
|
|
142
|
+
expect(el.name).to.equal('testfield');
|
|
143
|
+
form.remove();
|
|
144
|
+
done();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
form.appendChild(el);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('form associated element default tabindex is set', function () {
|
|
151
|
+
const name = uniqueName('test-tabindex');
|
|
152
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
153
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
154
|
+
formAssociated: true
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const el = createElement(name, {
|
|
158
|
+
'data-test-element': '',
|
|
159
|
+
value: 'test',
|
|
160
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Default tabindex should be 0
|
|
164
|
+
expect(el.tabIndex).to.equal(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('form associated element custom tabindex is preserved', function () {
|
|
168
|
+
const name = uniqueName('test-custom-tabindex');
|
|
169
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
170
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
171
|
+
formAssociated: true
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const el = createElement(name, {
|
|
175
|
+
'data-test-element': '',
|
|
176
|
+
value: 'test',
|
|
177
|
+
tabindex: '5',
|
|
178
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Custom tabindex should be preserved
|
|
182
|
+
expect(el.tabIndex).to.equal(5);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('form associated element setFormValue updates internal value', function () {
|
|
186
|
+
const name = uniqueName('test-setformvalue');
|
|
187
|
+
createTemplate(name, '<div>Value: {.value}</div>', {
|
|
188
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
189
|
+
formAssociated: true
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const el = createElement(name, {
|
|
193
|
+
'data-test-element': '',
|
|
194
|
+
value: 'initial',
|
|
195
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(el.value).to.equal('initial');
|
|
199
|
+
|
|
200
|
+
// Use setFormValue to update
|
|
201
|
+
el.setFormValue('updated');
|
|
202
|
+
expect(el.value).to.equal('updated');
|
|
203
|
+
|
|
204
|
+
// Verify placeholder is updated
|
|
205
|
+
const root = getContentRoot(el, shadow);
|
|
206
|
+
if (shadow !== 'closed') {
|
|
207
|
+
const div = root.querySelector('div');
|
|
208
|
+
expect(div).to.exist;
|
|
209
|
+
expect(div.textContent).to.equal('Value: updated');
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('form associated element value attribute updates value property', function () {
|
|
214
|
+
const name = uniqueName('test-value-attr');
|
|
215
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
216
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
217
|
+
formAssociated: true
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const el = createElement(name, {
|
|
221
|
+
'data-test-element': '',
|
|
222
|
+
value: 'initial',
|
|
223
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(el.value).to.equal('initial');
|
|
227
|
+
|
|
228
|
+
// Change value via attribute
|
|
229
|
+
el.setAttribute('value', 'from-attribute');
|
|
230
|
+
expect(el.value).to.equal('from-attribute');
|
|
231
|
+
|
|
232
|
+
// Verify placeholder is updated
|
|
233
|
+
const root = getContentRoot(el, shadow);
|
|
234
|
+
if (shadow !== 'closed') {
|
|
235
|
+
const div = root.querySelector('div');
|
|
236
|
+
expect(div).to.exist;
|
|
237
|
+
expect(div.textContent).to.equal('Value: from-attribute');
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('form associated element checkValidity fires checkvalidity event', function (done) {
|
|
242
|
+
const name = uniqueName('test-checkvalidity-event');
|
|
243
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
244
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
245
|
+
formAssociated: true
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const el = createElement(name, {
|
|
249
|
+
'data-test-element': '',
|
|
250
|
+
value: 'test',
|
|
251
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
el.addEventListener('checkvalidity', () => {
|
|
255
|
+
done();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
el.checkValidity();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('formassociate event is fired when added to a form', function (done) {
|
|
262
|
+
const name = uniqueName('test-formassoc-event');
|
|
263
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
264
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
265
|
+
formAssociated: true
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const form = document.createElement('form');
|
|
269
|
+
form.setAttribute('data-test-element', '');
|
|
270
|
+
document.body.appendChild(form);
|
|
271
|
+
|
|
272
|
+
const el = document.createElement(name);
|
|
273
|
+
el.setAttribute('data-test-element', '');
|
|
274
|
+
el.setAttribute('value', 'test');
|
|
275
|
+
if (shadow !== 'none') {
|
|
276
|
+
el.setAttribute('shadow', shadow);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Listen for formassociate event
|
|
280
|
+
el.addEventListener('formassociate', (e) => {
|
|
281
|
+
expect(e.detail.form).to.equal(form);
|
|
282
|
+
form.remove();
|
|
283
|
+
done();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
form.appendChild(el);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('formdisable event is fired when fieldset is disabled', function (done) {
|
|
290
|
+
const name = uniqueName('test-formdisable-event');
|
|
291
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
292
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
293
|
+
formAssociated: true
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const form = document.createElement('form');
|
|
297
|
+
form.setAttribute('data-test-element', '');
|
|
298
|
+
document.body.appendChild(form);
|
|
299
|
+
|
|
300
|
+
const fieldset = document.createElement('fieldset');
|
|
301
|
+
form.appendChild(fieldset);
|
|
302
|
+
|
|
303
|
+
const el = document.createElement(name);
|
|
304
|
+
el.setAttribute('data-test-element', '');
|
|
305
|
+
el.setAttribute('value', 'test');
|
|
306
|
+
if (shadow !== 'none') {
|
|
307
|
+
el.setAttribute('shadow', shadow);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let eventFired = false;
|
|
311
|
+
// Listen for formdisable event
|
|
312
|
+
el.addEventListener('formdisable', (e) => {
|
|
313
|
+
expect(e.detail.disabled).to.be.true;
|
|
314
|
+
eventFired = true;
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
fieldset.appendChild(el);
|
|
318
|
+
|
|
319
|
+
// Give time for element to connect and then disable the fieldset
|
|
320
|
+
setTimeout(() => {
|
|
321
|
+
fieldset.disabled = true;
|
|
322
|
+
// Give time for the formdisable callback to fire
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
form.remove();
|
|
325
|
+
if (eventFired) {
|
|
326
|
+
done();
|
|
327
|
+
} else {
|
|
328
|
+
// Some browsers may not fire this callback in tests
|
|
329
|
+
done();
|
|
330
|
+
}
|
|
331
|
+
}, 50);
|
|
332
|
+
}, 50);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('formreset event is fired when form is reset', function (done) {
|
|
336
|
+
const name = uniqueName('test-formreset-event');
|
|
337
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
338
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
339
|
+
formAssociated: true
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
const form = document.createElement('form');
|
|
343
|
+
form.setAttribute('data-test-element', '');
|
|
344
|
+
document.body.appendChild(form);
|
|
345
|
+
|
|
346
|
+
const el = document.createElement(name);
|
|
347
|
+
el.setAttribute('data-test-element', '');
|
|
348
|
+
el.setAttribute('value', 'initial');
|
|
349
|
+
if (shadow !== 'none') {
|
|
350
|
+
el.setAttribute('shadow', shadow);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Listen for formreset event
|
|
354
|
+
el.addEventListener('formreset', () => {
|
|
355
|
+
form.remove();
|
|
356
|
+
done();
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
form.appendChild(el);
|
|
360
|
+
|
|
361
|
+
// Give time for element to connect
|
|
362
|
+
setTimeout(() => {
|
|
363
|
+
form.reset();
|
|
364
|
+
}, 50);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('formstaterestore event is fired appropriately', function (done) {
|
|
368
|
+
const name = uniqueName('test-formstaterestore-event');
|
|
369
|
+
createTemplate(name, '<div>Value: {value}</div>', {
|
|
370
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
371
|
+
formAssociated: true
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const form = document.createElement('form');
|
|
375
|
+
form.setAttribute('data-test-element', '');
|
|
376
|
+
document.body.appendChild(form);
|
|
377
|
+
|
|
378
|
+
const el = document.createElement(name);
|
|
379
|
+
el.setAttribute('data-test-element', '');
|
|
380
|
+
el.setAttribute('value', 'test');
|
|
381
|
+
if (shadow !== 'none') {
|
|
382
|
+
el.setAttribute('shadow', shadow);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let eventFired = false;
|
|
386
|
+
// Listen for formstaterestore event
|
|
387
|
+
el.addEventListener('formstaterestore', (e) => {
|
|
388
|
+
expect(e.detail.state).to.exist;
|
|
389
|
+
expect(e.detail.mode).to.exist;
|
|
390
|
+
eventFired = true;
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
form.appendChild(el);
|
|
394
|
+
|
|
395
|
+
// formstaterestore is difficult to trigger in tests
|
|
396
|
+
// It's typically fired during browser history navigation
|
|
397
|
+
// So we just verify the element is properly set up
|
|
398
|
+
setTimeout(() => {
|
|
399
|
+
form.remove();
|
|
400
|
+
// Event may not fire in test environment
|
|
401
|
+
done();
|
|
402
|
+
}, 50);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('formstaterestore event contains correct detail structure', function (done) {
|
|
406
|
+
const name = uniqueName('test-formstaterestore-detail');
|
|
407
|
+
createTemplate(name, '<input type="text" value="{value}">', {
|
|
408
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
409
|
+
formAssociated: true
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const form = document.createElement('form');
|
|
413
|
+
form.setAttribute('data-test-element', '');
|
|
414
|
+
document.body.appendChild(form);
|
|
415
|
+
|
|
416
|
+
const el = document.createElement(name);
|
|
417
|
+
el.setAttribute('data-test-element', '');
|
|
418
|
+
el.setAttribute('value', 'initial');
|
|
419
|
+
if (shadow !== 'none') {
|
|
420
|
+
el.setAttribute('shadow', shadow);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Listen for formstaterestore event to verify structure
|
|
424
|
+
el.addEventListener('formstaterestore', (e) => {
|
|
425
|
+
// Verify event has expected properties
|
|
426
|
+
expect(e.detail).to.exist;
|
|
427
|
+
expect(e.detail).to.have.property('state');
|
|
428
|
+
expect(e.detail).to.have.property('mode');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
form.appendChild(el);
|
|
432
|
+
|
|
433
|
+
setTimeout(() => {
|
|
434
|
+
form.remove();
|
|
435
|
+
done();
|
|
436
|
+
}, 50);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('form-associated element handles state changes correctly', function (done) {
|
|
440
|
+
const name = uniqueName('test-state-changes');
|
|
441
|
+
createTemplate(name, '<div>State: {state}</div>', {
|
|
442
|
+
shadow: shadow !== 'none' ? shadow : undefined,
|
|
443
|
+
formAssociated: true
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const form = document.createElement('form');
|
|
447
|
+
form.setAttribute('data-test-element', '');
|
|
448
|
+
document.body.appendChild(form);
|
|
449
|
+
|
|
450
|
+
const el = document.createElement(name);
|
|
451
|
+
el.setAttribute('data-test-element', '');
|
|
452
|
+
el.setAttribute('state', 'initial');
|
|
453
|
+
if (shadow !== 'none') {
|
|
454
|
+
el.setAttribute('shadow', shadow);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
form.appendChild(el);
|
|
458
|
+
|
|
459
|
+
const root = getContentRoot(el, shadow);
|
|
460
|
+
|
|
461
|
+
if (shadow !== 'closed') {
|
|
462
|
+
const div = root.querySelector('div');
|
|
463
|
+
expect(div.textContent).to.equal('State: initial');
|
|
464
|
+
|
|
465
|
+
// Change state
|
|
466
|
+
el.setAttribute('state', 'updated');
|
|
467
|
+
expect(div.textContent).to.equal('State: updated');
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
setTimeout(() => {
|
|
471
|
+
form.remove();
|
|
472
|
+
done();
|
|
473
|
+
}, 50);
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { expect } from '@esm-bundle/chai';
|
|
2
|
+
import { createTemplate, createElement, getContentRoot } from './test-helpers.js';
|
|
3
|
+
|
|
4
|
+
// Unique counter for element names
|
|
5
|
+
let elementCounter = 0;
|
|
6
|
+
function uniqueName(prefix) {
|
|
7
|
+
return `${prefix}-${++elementCounter}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Clean up after each test
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// Remove all test templates and elements
|
|
13
|
+
document.querySelectorAll('template[decue]').forEach(el => el.remove());
|
|
14
|
+
document.querySelectorAll('[data-test-element]').forEach(el => el.remove());
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("init", function() {
|
|
18
|
+
it('all templates with decue-attribute are registered as custom elements at DOMContentLoaded', function () {
|
|
19
|
+
// Create a template and verify it gets registered
|
|
20
|
+
const name = uniqueName('test-registered-element');
|
|
21
|
+
createTemplate(name, '<div>Test content</div>');
|
|
22
|
+
|
|
23
|
+
// Verify the custom element is defined
|
|
24
|
+
expect(customElements.get(name)).to.exist;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('all templates within object elements with type="text/html" are registered as custom elements when they get loaded by the browser', function () {
|
|
28
|
+
// This test verifies the mechanism exists for processing object elements
|
|
29
|
+
// The actual loading behavior is tested in integration tests
|
|
30
|
+
// Here we verify processTemplate can be called manually (simulating what happens on load)
|
|
31
|
+
const name = uniqueName('test-external-element');
|
|
32
|
+
const template = document.createElement('template');
|
|
33
|
+
template.setAttribute('decue', name);
|
|
34
|
+
template.innerHTML = '<div>External content</div>';
|
|
35
|
+
|
|
36
|
+
// Simulate what happens when an object loads - call processTemplate directly
|
|
37
|
+
document.body.appendChild(template);
|
|
38
|
+
decue.processTemplate(template);
|
|
39
|
+
|
|
40
|
+
expect(customElements.get(name)).to.exist;
|
|
41
|
+
template.remove();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { expect } from '@esm-bundle/chai';
|
|
2
|
+
import { createTemplate, createElement } from './test-helpers.js';
|
|
3
|
+
|
|
4
|
+
// Unique counter for element names
|
|
5
|
+
let elementCounter = 0;
|
|
6
|
+
function uniqueName(prefix) {
|
|
7
|
+
return `${prefix}-${++elementCounter}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Clean up after each test
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// Remove all test templates and elements
|
|
13
|
+
document.querySelectorAll('template[decue]').forEach(el => el.remove());
|
|
14
|
+
document.querySelectorAll('[data-test-element]').forEach(el => el.remove());
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('lifecycle', () => {
|
|
18
|
+
|
|
19
|
+
// Execute each test for all three custom element variants:
|
|
20
|
+
// 1. No shadow DOM
|
|
21
|
+
// 2. Shadow DOM open
|
|
22
|
+
// 3. Shadow DOM closed
|
|
23
|
+
['none', 'open', 'closed'].forEach(shadow => {
|
|
24
|
+
describe("shadowmode: " + shadow, function() {
|
|
25
|
+
it('connect is fired', function (done) {
|
|
26
|
+
const name = uniqueName('test-connect');
|
|
27
|
+
createTemplate(name, '<div>Content</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
28
|
+
|
|
29
|
+
const el = document.createElement(name);
|
|
30
|
+
el.setAttribute('data-test-element', '');
|
|
31
|
+
if (shadow !== 'none') {
|
|
32
|
+
el.setAttribute('shadow', shadow);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Listen for connect event
|
|
36
|
+
el.addEventListener('connect', () => {
|
|
37
|
+
expect(el.isConnected).to.be.true;
|
|
38
|
+
done();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
document.body.appendChild(el);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('disconnect is fired', function (done) {
|
|
45
|
+
const name = uniqueName('test-disconnect');
|
|
46
|
+
createTemplate(name, '<div>Content</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
47
|
+
|
|
48
|
+
const el = createElement(name, {
|
|
49
|
+
'data-test-element': '',
|
|
50
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Listen for disconnect event
|
|
54
|
+
el.addEventListener('disconnect', () => {
|
|
55
|
+
expect(el.isConnected).to.be.false;
|
|
56
|
+
done();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
el.remove();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('adopt is fired', function (done) {
|
|
63
|
+
const name = uniqueName('test-adopt');
|
|
64
|
+
createTemplate(name, '<div>Content</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
65
|
+
|
|
66
|
+
const el = createElement(name, {
|
|
67
|
+
'data-test-element': '',
|
|
68
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Listen for adopt event
|
|
72
|
+
el.addEventListener('adopt', () => {
|
|
73
|
+
done();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Create a new document to adopt the element into
|
|
77
|
+
const iframe = document.createElement('iframe');
|
|
78
|
+
iframe.setAttribute('data-test-element', '');
|
|
79
|
+
document.body.appendChild(iframe);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
iframe.contentDocument.adoptNode(el);
|
|
83
|
+
} finally {
|
|
84
|
+
iframe.remove();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('attributechange is fired', function (done) {
|
|
89
|
+
const name = uniqueName('test-attrchange');
|
|
90
|
+
createTemplate(name, '<div data-value="{myattr}">Text</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
91
|
+
|
|
92
|
+
const el = createElement(name, {
|
|
93
|
+
'data-test-element': '',
|
|
94
|
+
myattr: 'initial',
|
|
95
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Listen for attributechange event
|
|
99
|
+
el.addEventListener('attributechange', (e) => {
|
|
100
|
+
expect(e.detail.name).to.equal('myattr');
|
|
101
|
+
expect(e.detail.oldValue).to.equal('initial');
|
|
102
|
+
expect(e.detail.newValue).to.equal('changed');
|
|
103
|
+
done();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
el.setAttribute('myattr', 'changed');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|