p-elements-core 1.2.32-rc1 → 1.2.32-rc11
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 +201 -0
- package/demo/sample.js +1 -1
- package/demo/theme.css +1 -0
- package/dist/p-elements-core-modern.js +1 -1
- package/dist/p-elements-core.js +1 -1
- package/index.html +15 -2
- package/p-elements-core.d.ts +11 -1
- package/package.json +10 -3
- package/src/custom-element-controller.test.ts +226 -0
- package/src/custom-element.test.ts +906 -0
- package/src/custom-element.ts +74 -17
- package/src/custom-style-element.ts +4 -1
- package/src/decorators/bind.test.ts +163 -0
- package/src/decorators/property.test.ts +279 -0
- package/src/decorators/property.ts +176 -10
- package/src/decorators/query.test.ts +146 -0
- package/src/helpers/css.test.ts +150 -0
- package/src/maquette/cache.test.ts +150 -0
- package/src/maquette/dom.test.ts +263 -0
- package/src/maquette/h.test.ts +165 -0
- package/src/maquette/mapping.test.ts +294 -0
- package/src/maquette/maquette.test.ts +493 -0
- package/src/maquette/projection.test.ts +366 -0
- package/src/maquette/projector.test.ts +351 -0
- package/src/maquette/projector.ts +6 -1
- package/src/sample/sample.tsx +167 -8
- package/src/test-setup.ts +85 -0
- package/src/test-utils.ts +223 -0
- package/tsconfig.json +1 -0
- package/vitest.config.ts +41 -0
- package/webpack.config.js +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Maquette cache module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { createCache } from './cache.js';
|
|
7
|
+
|
|
8
|
+
describe('Maquette cache', () => {
|
|
9
|
+
describe('createCache', () => {
|
|
10
|
+
it('should create a cache instance', () => {
|
|
11
|
+
const cache = createCache<number>();
|
|
12
|
+
expect(cache).toBeDefined();
|
|
13
|
+
expect(cache.result).toBeDefined();
|
|
14
|
+
expect(cache.invalidate).toBeDefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should calculate result on first call', () => {
|
|
18
|
+
const cache = createCache<number>();
|
|
19
|
+
let calculationCount = 0;
|
|
20
|
+
|
|
21
|
+
const result = cache.result([1, 2, 3], () => {
|
|
22
|
+
calculationCount++;
|
|
23
|
+
return 10;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(result).toBe(10);
|
|
27
|
+
expect(calculationCount).toBe(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return cached result when inputs unchanged', () => {
|
|
31
|
+
const cache = createCache<number>();
|
|
32
|
+
let calculationCount = 0;
|
|
33
|
+
|
|
34
|
+
const inputs = [1, 2, 3];
|
|
35
|
+
const calculation = () => {
|
|
36
|
+
calculationCount++;
|
|
37
|
+
return 10;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
cache.result(inputs, calculation);
|
|
41
|
+
const result2 = cache.result(inputs, calculation);
|
|
42
|
+
const result3 = cache.result(inputs, calculation);
|
|
43
|
+
|
|
44
|
+
expect(result2).toBe(10);
|
|
45
|
+
expect(result3).toBe(10);
|
|
46
|
+
expect(calculationCount).toBe(1); // Only calculated once
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should recalculate when inputs change', () => {
|
|
50
|
+
const cache = createCache<number>();
|
|
51
|
+
let calculationCount = 0;
|
|
52
|
+
|
|
53
|
+
cache.result([1, 2, 3], () => {
|
|
54
|
+
calculationCount++;
|
|
55
|
+
return 10;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
cache.result([1, 2, 4], () => {
|
|
59
|
+
calculationCount++;
|
|
60
|
+
return 20;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(calculationCount).toBe(2);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should detect changes in any input position', () => {
|
|
67
|
+
const cache = createCache<string>();
|
|
68
|
+
let calculationCount = 0;
|
|
69
|
+
|
|
70
|
+
const calc = () => {
|
|
71
|
+
calculationCount++;
|
|
72
|
+
return 'result';
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
cache.result(['a', 'b', 'c'], calc);
|
|
76
|
+
cache.result(['x', 'b', 'c'], calc); // First element changed
|
|
77
|
+
cache.result(['x', 'y', 'c'], calc); // Second element changed
|
|
78
|
+
cache.result(['x', 'y', 'z'], calc); // Third element changed
|
|
79
|
+
|
|
80
|
+
expect(calculationCount).toBe(4);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should invalidate cache', () => {
|
|
84
|
+
const cache = createCache<number>();
|
|
85
|
+
let calculationCount = 0;
|
|
86
|
+
|
|
87
|
+
const inputs = [1, 2, 3];
|
|
88
|
+
const calculation = () => {
|
|
89
|
+
calculationCount++;
|
|
90
|
+
return 10;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
cache.result(inputs, calculation);
|
|
94
|
+
cache.invalidate();
|
|
95
|
+
cache.result(inputs, calculation);
|
|
96
|
+
|
|
97
|
+
expect(calculationCount).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle empty inputs', () => {
|
|
101
|
+
const cache = createCache<number>();
|
|
102
|
+
let calculationCount = 0;
|
|
103
|
+
|
|
104
|
+
const result1 = cache.result([], () => {
|
|
105
|
+
calculationCount++;
|
|
106
|
+
return 42;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const result2 = cache.result([], () => {
|
|
110
|
+
calculationCount++;
|
|
111
|
+
return 42;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(result1).toBe(42);
|
|
115
|
+
expect(result2).toBe(42);
|
|
116
|
+
expect(calculationCount).toBe(1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should cache complex objects', () => {
|
|
120
|
+
const cache = createCache<{ value: number }>();
|
|
121
|
+
let calculationCount = 0;
|
|
122
|
+
|
|
123
|
+
const obj1 = { id: 1 };
|
|
124
|
+
const obj2 = { id: 2 };
|
|
125
|
+
|
|
126
|
+
const result1 = cache.result([obj1, obj2], () => {
|
|
127
|
+
calculationCount++;
|
|
128
|
+
return { value: 100 };
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const result2 = cache.result([obj1, obj2], () => {
|
|
132
|
+
calculationCount++;
|
|
133
|
+
return { value: 200 };
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(result1).toBe(result2); // Same cached object
|
|
137
|
+
expect(calculationCount).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should invalidate and allow new calculations', () => {
|
|
141
|
+
const cache = createCache<string>();
|
|
142
|
+
|
|
143
|
+
cache.result([1], () => 'first');
|
|
144
|
+
cache.invalidate();
|
|
145
|
+
const result = cache.result([2], () => 'second');
|
|
146
|
+
|
|
147
|
+
expect(result).toBe('second');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Maquette DOM manipulation module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { dom } from './dom.js';
|
|
7
|
+
import { h } from './h.js';
|
|
8
|
+
|
|
9
|
+
describe('Maquette DOM', () => {
|
|
10
|
+
let container: HTMLDivElement;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
container = document.createElement('div');
|
|
14
|
+
document.body.appendChild(container);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
if (container.parentNode) {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('dom.create', () => {
|
|
24
|
+
it('should create DOM from VNode', () => {
|
|
25
|
+
const vnode = h('div.test', ['Hello World']);
|
|
26
|
+
const projection = dom.create(vnode);
|
|
27
|
+
|
|
28
|
+
expect(projection).toBeDefined();
|
|
29
|
+
expect(projection.domNode).toBeDefined();
|
|
30
|
+
expect(projection.update).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should create nested elements', () => {
|
|
34
|
+
const vnode = h('div', [
|
|
35
|
+
h('span', ['Text 1']),
|
|
36
|
+
h('span', ['Text 2']),
|
|
37
|
+
]);
|
|
38
|
+
const projection = dom.create(vnode);
|
|
39
|
+
|
|
40
|
+
expect(projection.domNode.children.length).toBe(2);
|
|
41
|
+
expect(projection.domNode.children[0].textContent).toBe('Text 1');
|
|
42
|
+
expect(projection.domNode.children[1].textContent).toBe('Text 2');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should apply properties', () => {
|
|
46
|
+
const vnode = h('input', {
|
|
47
|
+
type: 'text',
|
|
48
|
+
value: 'test',
|
|
49
|
+
placeholder: 'Enter text'
|
|
50
|
+
});
|
|
51
|
+
const projection = dom.create(vnode);
|
|
52
|
+
const input = projection.domNode as HTMLInputElement;
|
|
53
|
+
|
|
54
|
+
expect(input.type).toBe('text');
|
|
55
|
+
expect(input.value).toBe('test');
|
|
56
|
+
expect(input.placeholder).toBe('Enter text');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should apply classes', () => {
|
|
60
|
+
const vnode = h('div.class1.class2');
|
|
61
|
+
const projection = dom.create(vnode);
|
|
62
|
+
|
|
63
|
+
expect(projection.domNode.classList.contains('class1')).toBe(true);
|
|
64
|
+
expect(projection.domNode.classList.contains('class2')).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('dom.append', () => {
|
|
69
|
+
it('should append VNode to parent element', () => {
|
|
70
|
+
const vnode = h('div.child', ['Child content']);
|
|
71
|
+
const projection = dom.append(container, vnode);
|
|
72
|
+
|
|
73
|
+
expect(container.children.length).toBe(1);
|
|
74
|
+
expect(container.children[0].classList.contains('child')).toBe(true);
|
|
75
|
+
expect(container.children[0].textContent).toBe('Child content');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should append multiple children sequentially', () => {
|
|
79
|
+
dom.append(container, h('div', ['First']));
|
|
80
|
+
dom.append(container, h('div', ['Second']));
|
|
81
|
+
dom.append(container, h('div', ['Third']));
|
|
82
|
+
|
|
83
|
+
expect(container.children.length).toBe(3);
|
|
84
|
+
expect(container.children[0].textContent).toBe('First');
|
|
85
|
+
expect(container.children[1].textContent).toBe('Second');
|
|
86
|
+
expect(container.children[2].textContent).toBe('Third');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('dom.insertBefore', () => {
|
|
91
|
+
it('should insert VNode before specified node', () => {
|
|
92
|
+
const first = document.createElement('div');
|
|
93
|
+
first.textContent = 'First';
|
|
94
|
+
container.appendChild(first);
|
|
95
|
+
|
|
96
|
+
const last = document.createElement('div');
|
|
97
|
+
last.textContent = 'Last';
|
|
98
|
+
container.appendChild(last);
|
|
99
|
+
|
|
100
|
+
const vnode = h('div', ['Middle']);
|
|
101
|
+
dom.insertBefore(last, vnode);
|
|
102
|
+
|
|
103
|
+
expect(container.children.length).toBe(3);
|
|
104
|
+
expect(container.children[0].textContent).toBe('First');
|
|
105
|
+
expect(container.children[1].textContent).toBe('Middle');
|
|
106
|
+
expect(container.children[2].textContent).toBe('Last');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should insert at beginning', () => {
|
|
110
|
+
const existing = document.createElement('div');
|
|
111
|
+
existing.textContent = 'Existing';
|
|
112
|
+
container.appendChild(existing);
|
|
113
|
+
|
|
114
|
+
const vnode = h('div', ['New First']);
|
|
115
|
+
dom.insertBefore(existing, vnode);
|
|
116
|
+
|
|
117
|
+
expect(container.children.length).toBe(2);
|
|
118
|
+
expect(container.children[0].textContent).toBe('New First');
|
|
119
|
+
expect(container.children[1].textContent).toBe('Existing');
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('dom.merge', () => {
|
|
124
|
+
it('should merge VNode with existing element', () => {
|
|
125
|
+
const existingDiv = document.createElement('div');
|
|
126
|
+
existingDiv.setAttribute('id', 'existing');
|
|
127
|
+
existingDiv.textContent = 'Original';
|
|
128
|
+
container.appendChild(existingDiv);
|
|
129
|
+
|
|
130
|
+
const vnode = h('div', { 'data-merged': 'true' }, ['Merged']);
|
|
131
|
+
const projection = dom.merge(existingDiv, vnode);
|
|
132
|
+
|
|
133
|
+
expect(projection.domNode).toBe(existingDiv);
|
|
134
|
+
expect(existingDiv.getAttribute('id')).toBe('existing'); // Preserved
|
|
135
|
+
expect(existingDiv.getAttribute('data-merged')).toBe('true'); // Added
|
|
136
|
+
expect(existingDiv.textContent).toBe('Merged'); // Updated
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should preserve existing children when merging', () => {
|
|
140
|
+
const existingDiv = document.createElement('div');
|
|
141
|
+
const existingChild = document.createElement('span');
|
|
142
|
+
existingChild.textContent = 'Existing child';
|
|
143
|
+
existingDiv.appendChild(existingChild);
|
|
144
|
+
container.appendChild(existingDiv);
|
|
145
|
+
|
|
146
|
+
const vnode = h('div', {}, [h('span', ['New child'])]);
|
|
147
|
+
dom.merge(existingDiv, vnode);
|
|
148
|
+
|
|
149
|
+
// Behavior depends on implementation - typically adds new children
|
|
150
|
+
expect(existingDiv.children.length).toBeGreaterThan(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('projection.update', () => {
|
|
155
|
+
it('should update DOM when VNode changes', () => {
|
|
156
|
+
let text = 'Initial';
|
|
157
|
+
const createVNode = () => h('div', [text]);
|
|
158
|
+
|
|
159
|
+
const projection = dom.append(container, createVNode());
|
|
160
|
+
expect(container.children[0].textContent).toBe('Initial');
|
|
161
|
+
|
|
162
|
+
text = 'Updated';
|
|
163
|
+
projection.update(createVNode());
|
|
164
|
+
expect(container.children[0].textContent).toBe('Updated');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should update properties', () => {
|
|
168
|
+
let value = 'initial';
|
|
169
|
+
const createVNode = () => h('input', { value });
|
|
170
|
+
|
|
171
|
+
const projection = dom.append(container, createVNode());
|
|
172
|
+
const input = container.children[0] as HTMLInputElement;
|
|
173
|
+
expect(input.value).toBe('initial');
|
|
174
|
+
|
|
175
|
+
value = 'updated';
|
|
176
|
+
projection.update(createVNode());
|
|
177
|
+
expect(input.value).toBe('updated');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should update nested elements', () => {
|
|
181
|
+
let count = 1;
|
|
182
|
+
const createVNode = () => h('div', [
|
|
183
|
+
h('span.count', [`Count: ${count}`]),
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
const projection = dom.append(container, createVNode());
|
|
187
|
+
expect(container.querySelector('.count')?.textContent).toBe('Count: 1');
|
|
188
|
+
|
|
189
|
+
count = 5;
|
|
190
|
+
projection.update(createVNode());
|
|
191
|
+
expect(container.querySelector('.count')?.textContent).toBe('Count: 5');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('projection options', () => {
|
|
196
|
+
it('should apply custom styleApplyer', () => {
|
|
197
|
+
let stylesCalled: Array<{ styleName: string; value: string }> = [];
|
|
198
|
+
|
|
199
|
+
const vnode = h('div', {
|
|
200
|
+
styles: {
|
|
201
|
+
color: 'red',
|
|
202
|
+
fontSize: '16px'
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const projection = dom.create(vnode, {
|
|
207
|
+
styleApplyer: (domNode, styleName, value) => {
|
|
208
|
+
stylesCalled.push({ styleName, value });
|
|
209
|
+
(domNode.style as any)[styleName] = value;
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(stylesCalled.length).toBeGreaterThan(0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should handle CSS variables in styles', () => {
|
|
217
|
+
const vnode = h('div', {
|
|
218
|
+
styles: {
|
|
219
|
+
'--custom-color': 'blue',
|
|
220
|
+
'color': 'red'
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const projection = dom.create(vnode);
|
|
225
|
+
const div = projection.domNode as HTMLElement;
|
|
226
|
+
|
|
227
|
+
// CSS variables should be set
|
|
228
|
+
expect(div.style.getPropertyValue('--custom-color')).toBe('blue');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('edge cases', () => {
|
|
233
|
+
it('should handle empty VNode', () => {
|
|
234
|
+
const vnode = h('div');
|
|
235
|
+
const projection = dom.create(vnode);
|
|
236
|
+
|
|
237
|
+
expect(projection.domNode).toBeDefined();
|
|
238
|
+
expect(projection.domNode.children.length).toBe(0);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should handle VNode with only text', () => {
|
|
242
|
+
const vnode = h('div', ['Just text']);
|
|
243
|
+
const projection = dom.create(vnode);
|
|
244
|
+
|
|
245
|
+
expect(projection.domNode.textContent).toBe('Just text');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should handle deeply nested structures', () => {
|
|
249
|
+
const vnode = h('div', [
|
|
250
|
+
h('div', [
|
|
251
|
+
h('div', [
|
|
252
|
+
h('div', [
|
|
253
|
+
h('span', ['Deep'])
|
|
254
|
+
])
|
|
255
|
+
])
|
|
256
|
+
])
|
|
257
|
+
]);
|
|
258
|
+
|
|
259
|
+
const projection = dom.create(vnode);
|
|
260
|
+
expect(projection.domNode.querySelector('span')?.textContent).toBe('Deep');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Maquette h (hyperscript) module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { h } from './h.js';
|
|
7
|
+
|
|
8
|
+
describe('Maquette h (hyperscript)', () => {
|
|
9
|
+
describe('Basic VNode creation', () => {
|
|
10
|
+
it('should create VNode with selector only', () => {
|
|
11
|
+
const vnode = h('div');
|
|
12
|
+
|
|
13
|
+
expect(vnode.vnodeSelector).toBe('div');
|
|
14
|
+
expect(vnode.properties).toBeUndefined();
|
|
15
|
+
expect(vnode.children).toBeUndefined();
|
|
16
|
+
expect(vnode.text).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should create VNode with properties', () => {
|
|
20
|
+
const vnode = h('div', { id: 'test', class: 'my-class' });
|
|
21
|
+
|
|
22
|
+
expect(vnode.vnodeSelector).toBe('div');
|
|
23
|
+
expect(vnode.properties).toEqual({ id: 'test', class: 'my-class' });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should create VNode with children array', () => {
|
|
27
|
+
const vnode = h('div', [h('span', ['text'])]);
|
|
28
|
+
|
|
29
|
+
expect(vnode.vnodeSelector).toBe('div');
|
|
30
|
+
expect(vnode.children).toHaveLength(1);
|
|
31
|
+
expect(vnode.children?.[0].vnodeSelector).toBe('span');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('Nested and complex children', () => {
|
|
36
|
+
it('should flatten nested arrays in children', () => {
|
|
37
|
+
const vnode = h('div', [
|
|
38
|
+
[h('span', ['a']), h('span', ['b'])],
|
|
39
|
+
h('span', ['c'])
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
expect(vnode.children).toHaveLength(3);
|
|
43
|
+
expect(vnode.children?.[0].vnodeSelector).toBe('span');
|
|
44
|
+
expect(vnode.children?.[1].vnodeSelector).toBe('span');
|
|
45
|
+
expect(vnode.children?.[2].vnodeSelector).toBe('span');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle deeply nested arrays', () => {
|
|
49
|
+
const vnode = h('div', [
|
|
50
|
+
[[h('span', ['level1'])]],
|
|
51
|
+
[[[h('span', ['level2'])]]]
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
expect(vnode.children).toHaveLength(2);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should convert string children to text VNodes in nested arrays', () => {
|
|
58
|
+
const vnode = h('div', [['text1', 'text2'], ['text3']]);
|
|
59
|
+
|
|
60
|
+
expect(vnode.children).toHaveLength(3);
|
|
61
|
+
vnode.children?.forEach(child => {
|
|
62
|
+
expect(child.vnodeSelector).toBe('');
|
|
63
|
+
expect(typeof child.text).toBe('string');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should filter out null, undefined, and false from children', () => {
|
|
68
|
+
const vnode = h('div', [
|
|
69
|
+
null,
|
|
70
|
+
undefined,
|
|
71
|
+
false,
|
|
72
|
+
h('span', ['visible'])
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
expect(vnode.children).toHaveLength(1);
|
|
76
|
+
expect(vnode.children?.[0].vnodeSelector).toBe('span');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return undefined for empty children array', () => {
|
|
80
|
+
const vnode = h('div', [null, undefined, false]);
|
|
81
|
+
|
|
82
|
+
expect(vnode.children).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('Text nodes', () => {
|
|
87
|
+
it('should handle single text child as text property', () => {
|
|
88
|
+
const vnode = h('div', ['single text']);
|
|
89
|
+
|
|
90
|
+
expect(vnode.text).toBe('single text');
|
|
91
|
+
expect(vnode.children).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should convert empty string to undefined', () => {
|
|
95
|
+
const vnode = h('div', ['']);
|
|
96
|
+
|
|
97
|
+
expect(vnode.text).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle number strings in nested arrays', () => {
|
|
101
|
+
const vnode = h('div', [['123'], ['456']]);
|
|
102
|
+
|
|
103
|
+
expect(vnode.children).toHaveLength(2);
|
|
104
|
+
expect(vnode.children?.[0].text).toBe('123');
|
|
105
|
+
expect(vnode.children?.[1].text).toBe('456');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('Properties handling', () => {
|
|
110
|
+
it('should treat array as children when used as second argument', () => {
|
|
111
|
+
const vnode = h('div', [h('span')]);
|
|
112
|
+
|
|
113
|
+
expect(vnode.properties).toBeUndefined();
|
|
114
|
+
expect(vnode.children).toHaveLength(1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle properties and children together', () => {
|
|
118
|
+
const vnode = h('div', { id: 'test' }, [h('span')]);
|
|
119
|
+
|
|
120
|
+
expect(vnode.properties?.id).toBe('test');
|
|
121
|
+
expect(vnode.children).toHaveLength(1);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('Error handling', () => {
|
|
126
|
+
it('should throw error when properties is a string', () => {
|
|
127
|
+
expect(() => {
|
|
128
|
+
h('div', 'invalid' as any);
|
|
129
|
+
}).toThrow('h called with invalid arguments');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should throw error when properties is a VNode', () => {
|
|
133
|
+
expect(() => {
|
|
134
|
+
h('div', h('span') as any);
|
|
135
|
+
}).toThrow('h called with invalid arguments');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should throw error when children contains VNode without array', () => {
|
|
139
|
+
expect(() => {
|
|
140
|
+
h('div', {}, h('span') as any);
|
|
141
|
+
}).toThrow('h called with invalid arguments');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should throw error when children is a string outside array', () => {
|
|
145
|
+
expect(() => {
|
|
146
|
+
h('div', {}, 'invalid' as any);
|
|
147
|
+
}).toThrow('h called with invalid arguments');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('Complex selectors', () => {
|
|
152
|
+
it('should handle selector with classes and id', () => {
|
|
153
|
+
const vnode = h('div.class1.class2#myid');
|
|
154
|
+
|
|
155
|
+
expect(vnode.vnodeSelector).toBe('div.class1.class2#myid');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should handle SVG elements', () => {
|
|
159
|
+
const vnode = h('svg', [h('circle', { r: 10 })]);
|
|
160
|
+
|
|
161
|
+
expect(vnode.vnodeSelector).toBe('svg');
|
|
162
|
+
expect(vnode.children?.[0].vnodeSelector).toBe('circle');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|