decue 1.0.0 → 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 +333 -191
- package/dist/decue.min.js +1 -1
- package/eslint.config.js +53 -0
- package/examples.html +82 -12
- package/external.html +1 -0
- package/npm.sh +1 -1
- package/package.json +24 -11
- package/src/decue.js +333 -191
- 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,201 @@
|
|
|
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('debug', () => {
|
|
18
|
+
|
|
19
|
+
['none', 'open', 'closed'].forEach(shadow => {
|
|
20
|
+
describe("shadowmode: " + shadow, function() {
|
|
21
|
+
it('debug mode adds data attributes for observed attributes', function () {
|
|
22
|
+
const name = uniqueName('test-debug-observed-attrs');
|
|
23
|
+
|
|
24
|
+
// Create a template first
|
|
25
|
+
const template = document.createElement('template');
|
|
26
|
+
template.setAttribute('decue', name);
|
|
27
|
+
template.setAttribute('debug', '');
|
|
28
|
+
template.setAttribute('observed-attributes', 'value,data-test');
|
|
29
|
+
if (shadow !== 'none') {
|
|
30
|
+
template.setAttribute('shadow', shadow);
|
|
31
|
+
}
|
|
32
|
+
template.innerHTML = '<div>Test</div>';
|
|
33
|
+
document.head.appendChild(template);
|
|
34
|
+
|
|
35
|
+
// Process template
|
|
36
|
+
decue.processTemplate(template);
|
|
37
|
+
|
|
38
|
+
const el = document.createElement(name);
|
|
39
|
+
el.setAttribute('data-test-element', '');
|
|
40
|
+
if (shadow !== 'none') {
|
|
41
|
+
el.setAttribute('shadow', shadow);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
document.body.appendChild(el);
|
|
45
|
+
|
|
46
|
+
// Check if debug attribute was added
|
|
47
|
+
expect(el).to.exist;
|
|
48
|
+
if (el.hasAttribute('data-decue-observed-attributes')) {
|
|
49
|
+
expect(el.getAttribute('data-decue-observed-attributes')).to.include('value');
|
|
50
|
+
expect(el.getAttribute('data-decue-observed-attributes')).to.include('data-test');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('debug mode adds data attributes for mutation observed attributes', function () {
|
|
55
|
+
const name = uniqueName('test-debug-mutation-attrs');
|
|
56
|
+
|
|
57
|
+
const template = document.createElement('template');
|
|
58
|
+
template.setAttribute('decue', name);
|
|
59
|
+
template.setAttribute('debug', '');
|
|
60
|
+
if (shadow !== 'none') {
|
|
61
|
+
template.setAttribute('shadow', shadow);
|
|
62
|
+
}
|
|
63
|
+
template.innerHTML = '<div class="content">{text}</div>';
|
|
64
|
+
document.head.appendChild(template);
|
|
65
|
+
|
|
66
|
+
decue.processTemplate(template);
|
|
67
|
+
|
|
68
|
+
const el = createElement(name, {
|
|
69
|
+
'data-test-element': '',
|
|
70
|
+
text: 'test value',
|
|
71
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Check if debug attribute was added
|
|
75
|
+
expect(el).to.exist;
|
|
76
|
+
|
|
77
|
+
// Should have mutation observer attributes marked
|
|
78
|
+
if (el.hasAttribute('data-decue-mutation-observed-attributes')) {
|
|
79
|
+
const attrs = el.getAttribute('data-decue-mutation-observed-attributes');
|
|
80
|
+
expect(attrs).to.be.a('string');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('debug mode logs to console when enabled', function () {
|
|
85
|
+
const name = uniqueName('test-debug-console');
|
|
86
|
+
const originalLog = console.log;
|
|
87
|
+
let logCalled = false;
|
|
88
|
+
let logMessages = [];
|
|
89
|
+
|
|
90
|
+
// Mock console.log BEFORE creating the element
|
|
91
|
+
console.log = function(...args) {
|
|
92
|
+
logCalled = true;
|
|
93
|
+
logMessages.push(args);
|
|
94
|
+
// Don't call originalLog to keep test output clean
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Create template
|
|
99
|
+
const template = document.createElement('template');
|
|
100
|
+
template.setAttribute('decue', name);
|
|
101
|
+
if (shadow !== 'none') {
|
|
102
|
+
template.setAttribute('shadow', shadow);
|
|
103
|
+
}
|
|
104
|
+
template.innerHTML = '<div>{testattr}</div>';
|
|
105
|
+
document.head.appendChild(template);
|
|
106
|
+
|
|
107
|
+
// Use defineElement directly with debug=true instead of processTemplate
|
|
108
|
+
// because processTemplate uses the module-level debug variable
|
|
109
|
+
decue.defineElement(
|
|
110
|
+
true, // debug enabled
|
|
111
|
+
name,
|
|
112
|
+
template,
|
|
113
|
+
false,
|
|
114
|
+
['testattr']
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const el = document.createElement(name);
|
|
118
|
+
el.setAttribute('data-test-element', '');
|
|
119
|
+
el.setAttribute('testattr', 'value');
|
|
120
|
+
if (shadow !== 'none') {
|
|
121
|
+
el.setAttribute('shadow', shadow);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
document.body.appendChild(el);
|
|
125
|
+
|
|
126
|
+
// Debug mode should have logged something when element connects
|
|
127
|
+
expect(logCalled).to.be.true;
|
|
128
|
+
expect(logMessages.length).to.be.greaterThan(0);
|
|
129
|
+
|
|
130
|
+
// Verify the first argument of at least one log call is the element name
|
|
131
|
+
const hasElementName = logMessages.some(args => args[0] === name);
|
|
132
|
+
expect(hasElementName).to.be.true;
|
|
133
|
+
|
|
134
|
+
// Verify "Finalizing..." message was logged
|
|
135
|
+
const hasFinalizingMessage = logMessages.some(args =>
|
|
136
|
+
args.length > 1 && args[1] === 'Finalizing...'
|
|
137
|
+
);
|
|
138
|
+
expect(hasFinalizingMessage).to.be.true;
|
|
139
|
+
} finally {
|
|
140
|
+
// Restore console.log
|
|
141
|
+
console.log = originalLog;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('non-debug mode does not add debug attributes', function () {
|
|
146
|
+
const name = uniqueName('test-no-debug');
|
|
147
|
+
|
|
148
|
+
const template = document.createElement('template');
|
|
149
|
+
template.setAttribute('decue', name);
|
|
150
|
+
template.setAttribute('observed-attributes', 'value,data-test');
|
|
151
|
+
if (shadow !== 'none') {
|
|
152
|
+
template.setAttribute('shadow', shadow);
|
|
153
|
+
}
|
|
154
|
+
template.innerHTML = '<div>Test</div>';
|
|
155
|
+
document.head.appendChild(template);
|
|
156
|
+
|
|
157
|
+
decue.processTemplate(template);
|
|
158
|
+
|
|
159
|
+
const el = document.createElement(name);
|
|
160
|
+
el.setAttribute('data-test-element', '');
|
|
161
|
+
if (shadow !== 'none') {
|
|
162
|
+
el.setAttribute('shadow', shadow);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
document.body.appendChild(el);
|
|
166
|
+
|
|
167
|
+
// Should not have debug attributes
|
|
168
|
+
expect(el.hasAttribute('data-decue-observed-attributes')).to.be.false;
|
|
169
|
+
expect(el.hasAttribute('data-decue-mutation-observed-attributes')).to.be.false;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('debug mode works with template elements', function () {
|
|
173
|
+
const name = uniqueName('test-debug-template');
|
|
174
|
+
|
|
175
|
+
const template = document.createElement('template');
|
|
176
|
+
template.setAttribute('decue', name);
|
|
177
|
+
template.setAttribute('debug', '');
|
|
178
|
+
if (shadow !== 'none') {
|
|
179
|
+
template.setAttribute('shadow', shadow);
|
|
180
|
+
}
|
|
181
|
+
template.innerHTML = '<div class="content">{value}</div>';
|
|
182
|
+
document.head.appendChild(template);
|
|
183
|
+
|
|
184
|
+
// Process the template (simulate DeCuE's automatic processing)
|
|
185
|
+
decue.processTemplate(template);
|
|
186
|
+
|
|
187
|
+
const el = document.createElement(name);
|
|
188
|
+
el.setAttribute('data-test-element', '');
|
|
189
|
+
el.setAttribute('value', 'test');
|
|
190
|
+
if (shadow !== 'none') {
|
|
191
|
+
el.setAttribute('shadow', shadow);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
document.body.appendChild(el);
|
|
195
|
+
|
|
196
|
+
expect(el).to.exist;
|
|
197
|
+
expect(el.isConnected).to.be.true;
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
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('decue', () => {
|
|
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('shadowroot is present or not as expected', function () {
|
|
26
|
+
const name = uniqueName('test-shadow-element');
|
|
27
|
+
createTemplate(name, '<div>Shadow test</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
28
|
+
|
|
29
|
+
const el = createElement(name, {
|
|
30
|
+
'data-test-element': '',
|
|
31
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (shadow === 'none') {
|
|
35
|
+
// In 'none' mode, content is placed directly in the element (no shadow root)
|
|
36
|
+
expect(el.shadowRoot).to.be.null;
|
|
37
|
+
expect(el.querySelector('div')).to.exist;
|
|
38
|
+
expect(el.querySelector('div').textContent).to.equal('Shadow test');
|
|
39
|
+
} else if (shadow === 'open') {
|
|
40
|
+
// In 'open' mode, shadowRoot is accessible
|
|
41
|
+
expect(el.shadowRoot).to.exist;
|
|
42
|
+
expect(el.shadowRoot.querySelector('div')).to.exist;
|
|
43
|
+
expect(el.shadowRoot.querySelector('div').textContent).to.equal('Shadow test');
|
|
44
|
+
} else if (shadow === 'closed') {
|
|
45
|
+
// In 'closed' mode, shadowRoot is not accessible from outside
|
|
46
|
+
// But the content should still be rendered
|
|
47
|
+
expect(el.shadowRoot).to.be.null;
|
|
48
|
+
// We can't directly check closed shadow root content from outside
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('element with no attributes works', function () {
|
|
53
|
+
const name = uniqueName('test-no-attr-element');
|
|
54
|
+
createTemplate(name, '<div class="content">Static content</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
55
|
+
|
|
56
|
+
const el = createElement(name, {
|
|
57
|
+
'data-test-element': '',
|
|
58
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Verify element was created successfully
|
|
62
|
+
expect(el).to.exist;
|
|
63
|
+
expect(el.tagName.toLowerCase()).to.equal(name);
|
|
64
|
+
|
|
65
|
+
const root = getContentRoot(el, shadow);
|
|
66
|
+
if (shadow !== 'closed') {
|
|
67
|
+
expect(root.querySelector('.content')).to.exist;
|
|
68
|
+
expect(root.querySelector('.content').textContent).to.equal('Static content');
|
|
69
|
+
} else {
|
|
70
|
+
// For closed shadow DOM, verify shadowRoot is not accessible
|
|
71
|
+
expect(el.shadowRoot).to.be.null;
|
|
72
|
+
// But the element should still be properly initialized
|
|
73
|
+
expect(el.isConnected).to.be.true;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
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('errors', () => {
|
|
18
|
+
|
|
19
|
+
['none', 'open', 'closed'].forEach(shadow => {
|
|
20
|
+
describe("shadowmode: " + shadow, function() {
|
|
21
|
+
it('element placeholder can reference element property', function () {
|
|
22
|
+
const name = uniqueName('test-element-property');
|
|
23
|
+
createTemplate(name, '<div class="content">Tag: {.tagName}</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
24
|
+
|
|
25
|
+
const el = createElement(name, {
|
|
26
|
+
'data-test-element': '',
|
|
27
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(el).to.exist;
|
|
31
|
+
|
|
32
|
+
const root = getContentRoot(el, shadow);
|
|
33
|
+
if (shadow !== 'closed') {
|
|
34
|
+
const div = root.querySelector('.content');
|
|
35
|
+
expect(div).to.exist;
|
|
36
|
+
expect(div.textContent).to.equal(`Tag: ${name.toUpperCase()}`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('formAssociated element has correct class property', function () {
|
|
41
|
+
const name = uniqueName('test-form-class');
|
|
42
|
+
|
|
43
|
+
// Define element as formAssociated
|
|
44
|
+
decue.defineElement(false, name, null, true, []);
|
|
45
|
+
|
|
46
|
+
const elementClass = customElements.get(name);
|
|
47
|
+
expect(elementClass).to.exist;
|
|
48
|
+
expect(elementClass.formAssociated).to.be.true;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('node cleanup works when element is removed and re-added', function () {
|
|
52
|
+
const name = uniqueName('test-node-cleanup');
|
|
53
|
+
createTemplate(name, '<div class="content">{text}</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
54
|
+
|
|
55
|
+
const el = createElement(name, {
|
|
56
|
+
'data-test-element': '',
|
|
57
|
+
text: 'initial',
|
|
58
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const root = getContentRoot(el, shadow);
|
|
62
|
+
|
|
63
|
+
// Initial state
|
|
64
|
+
if (shadow !== 'closed') {
|
|
65
|
+
const div = root.querySelector('.content');
|
|
66
|
+
expect(div.textContent).to.equal('initial');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Remove element from DOM
|
|
70
|
+
el.remove();
|
|
71
|
+
|
|
72
|
+
// Re-add to DOM
|
|
73
|
+
document.body.appendChild(el);
|
|
74
|
+
|
|
75
|
+
// Update attribute - should still work
|
|
76
|
+
el.setAttribute('text', 'updated');
|
|
77
|
+
|
|
78
|
+
if (shadow !== 'closed') {
|
|
79
|
+
const div = root.querySelector('.content');
|
|
80
|
+
expect(div.textContent).to.equal('updated');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('node cleanup works with multiple disconnect/reconnect cycles', function () {
|
|
85
|
+
const name = uniqueName('test-multiple-cleanup');
|
|
86
|
+
createTemplate(name, '<div class="content">{value}</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
87
|
+
|
|
88
|
+
const el = createElement(name, {
|
|
89
|
+
'data-test-element': '',
|
|
90
|
+
value: 'cycle-1',
|
|
91
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const root = getContentRoot(el, shadow);
|
|
95
|
+
|
|
96
|
+
// Cycle 1: disconnect and reconnect
|
|
97
|
+
el.remove();
|
|
98
|
+
document.body.appendChild(el);
|
|
99
|
+
el.setAttribute('value', 'cycle-2');
|
|
100
|
+
|
|
101
|
+
if (shadow !== 'closed') {
|
|
102
|
+
const div = root.querySelector('.content');
|
|
103
|
+
expect(div.textContent).to.equal('cycle-2');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Cycle 2: disconnect and reconnect again
|
|
107
|
+
el.remove();
|
|
108
|
+
document.body.appendChild(el);
|
|
109
|
+
el.setAttribute('value', 'cycle-3');
|
|
110
|
+
|
|
111
|
+
if (shadow !== 'closed') {
|
|
112
|
+
const div = root.querySelector('.content');
|
|
113
|
+
expect(div.textContent).to.equal('cycle-3');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('node cleanup works with multiple placeholders', function () {
|
|
118
|
+
const name = uniqueName('test-cleanup-multiple-placeholders');
|
|
119
|
+
createTemplate(name, '<div class="first">{attr1}</div><div class="second">{attr2}</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
120
|
+
|
|
121
|
+
const el = createElement(name, {
|
|
122
|
+
'data-test-element': '',
|
|
123
|
+
attr1: 'first',
|
|
124
|
+
attr2: 'second',
|
|
125
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const root = getContentRoot(el, shadow);
|
|
129
|
+
|
|
130
|
+
// Remove and re-add
|
|
131
|
+
el.remove();
|
|
132
|
+
document.body.appendChild(el);
|
|
133
|
+
|
|
134
|
+
// Update both attributes
|
|
135
|
+
el.setAttribute('attr1', 'updated-first');
|
|
136
|
+
el.setAttribute('attr2', 'updated-second');
|
|
137
|
+
|
|
138
|
+
if (shadow !== 'closed') {
|
|
139
|
+
const first = root.querySelector('.first');
|
|
140
|
+
const second = root.querySelector('.second');
|
|
141
|
+
expect(first.textContent).to.equal('updated-first');
|
|
142
|
+
expect(second.textContent).to.equal('updated-second');
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('special cases', function() {
|
|
149
|
+
it('processes template that is already defined (skips re-definition)', function () {
|
|
150
|
+
const name = uniqueName('test-already-defined');
|
|
151
|
+
|
|
152
|
+
// Define first
|
|
153
|
+
createTemplate(name, '<div>First</div>');
|
|
154
|
+
expect(customElements.get(name)).to.exist;
|
|
155
|
+
|
|
156
|
+
// Try to process same element again
|
|
157
|
+
const template2 = document.createElement('template');
|
|
158
|
+
template2.setAttribute('decue', name);
|
|
159
|
+
template2.innerHTML = '<div>Second</div>';
|
|
160
|
+
|
|
161
|
+
// Process template should skip since already defined
|
|
162
|
+
decue.processTemplate(template2);
|
|
163
|
+
|
|
164
|
+
// Element should still use first definition
|
|
165
|
+
const el = createElement(name, {
|
|
166
|
+
'data-test-element': ''
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const root = getContentRoot(el, 'none');
|
|
170
|
+
const div = root.querySelector('div');
|
|
171
|
+
expect(div.textContent).to.equal('First');
|
|
172
|
+
|
|
173
|
+
template2.remove();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('fires events even when CustomEvent is unavailable', function (done) {
|
|
177
|
+
const name = uniqueName('test-event-fallback');
|
|
178
|
+
createTemplate(name, '<div>Content</div>');
|
|
179
|
+
|
|
180
|
+
// Save original CustomEvent
|
|
181
|
+
const originalCustomEvent = window.CustomEvent;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Temporarily remove CustomEvent to test fallback
|
|
185
|
+
// @ts-ignore
|
|
186
|
+
delete window.CustomEvent;
|
|
187
|
+
|
|
188
|
+
const el = document.createElement(name);
|
|
189
|
+
el.setAttribute('data-test-element', '');
|
|
190
|
+
|
|
191
|
+
let eventFired = false;
|
|
192
|
+
el.addEventListener('connect', () => {
|
|
193
|
+
eventFired = true;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
document.body.appendChild(el);
|
|
197
|
+
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
expect(eventFired).to.be.true;
|
|
200
|
+
|
|
201
|
+
// Restore CustomEvent
|
|
202
|
+
window.CustomEvent = originalCustomEvent;
|
|
203
|
+
done();
|
|
204
|
+
}, 50);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
// Restore in case of error
|
|
207
|
+
window.CustomEvent = originalCustomEvent;
|
|
208
|
+
throw e;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
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('eventHandlers', () => {
|
|
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('decue-on can catch all of the known events', function (done) {
|
|
26
|
+
const name = uniqueName('test-decue-on-known');
|
|
27
|
+
createTemplate(name, '<div data-value="{testattr}">Content</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
28
|
+
|
|
29
|
+
let eventsCaught = [];
|
|
30
|
+
|
|
31
|
+
// Register global handler
|
|
32
|
+
window.testEventHandler = function(e) {
|
|
33
|
+
eventsCaught.push(e.type);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const el = document.createElement(name);
|
|
37
|
+
el.setAttribute('data-test-element', '');
|
|
38
|
+
el.setAttribute('decue-on:connect', 'testEventHandler');
|
|
39
|
+
el.setAttribute('decue-on:disconnect', 'testEventHandler');
|
|
40
|
+
el.setAttribute('decue-on:attributechange', 'testEventHandler');
|
|
41
|
+
el.setAttribute('testattr', 'initial');
|
|
42
|
+
if (shadow !== 'none') {
|
|
43
|
+
el.setAttribute('shadow', shadow);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
document.body.appendChild(el);
|
|
47
|
+
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
el.setAttribute('testattr', 'value');
|
|
50
|
+
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
el.remove();
|
|
53
|
+
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
expect(eventsCaught).to.include('connect');
|
|
56
|
+
expect(eventsCaught).to.include('disconnect');
|
|
57
|
+
expect(eventsCaught).to.include('attributechange');
|
|
58
|
+
delete window.testEventHandler;
|
|
59
|
+
done();
|
|
60
|
+
}, 50);
|
|
61
|
+
}, 50);
|
|
62
|
+
}, 50);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('decue-on can catch any unknown event', function (done) {
|
|
66
|
+
const name = uniqueName('test-decue-on-custom');
|
|
67
|
+
createTemplate(name, '<div>Content</div>', { shadow: shadow !== 'none' ? shadow : undefined });
|
|
68
|
+
|
|
69
|
+
let customEventCaught = false;
|
|
70
|
+
|
|
71
|
+
// Register global handler
|
|
72
|
+
window.customEventHandler = function(e) {
|
|
73
|
+
if (e.type === 'customevent') {
|
|
74
|
+
customEventCaught = true;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const el = createElement(name, {
|
|
79
|
+
'data-test-element': '',
|
|
80
|
+
'decue-on:customevent': 'customEventHandler',
|
|
81
|
+
...(shadow !== 'none' ? { shadow } : {})
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Dispatch a custom event
|
|
85
|
+
el.dispatchEvent(new CustomEvent('customevent', { detail: { test: true } }));
|
|
86
|
+
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
expect(customEventCaught).to.be.true;
|
|
89
|
+
delete window.customEventHandler;
|
|
90
|
+
done();
|
|
91
|
+
}, 50);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|