Skip to content

Commit e0eaa16

Browse files
committed
#7076 first unit tests
1 parent 9a435d6 commit e0eaa16

4 files changed

Lines changed: 177 additions & 3 deletions

File tree

src/util/VDom.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,13 @@ class VDom extends Base {
261261
*/
262262
static getVdom(vdom) {
263263
if (vdom.componentId) {
264-
vdom = ComponentManager.get(vdom.componentId).vdom
264+
const component = ComponentManager.get(vdom.componentId);
265+
266+
if (!component) {
267+
throw new Error(`util.VDom.getVdom: Component not found for id: ${vdom.componentId}`)
268+
}
269+
270+
vdom = component.vdom
265271
}
266272

267273
return vdom

src/util/VNode.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,13 @@ class VNode extends Base {
168168
*/
169169
static getVnode(vnode) {
170170
if (vnode.componentId) {
171-
vnode = ComponentManager.get(vnode.componentId).vnode
171+
const component = ComponentManager.get(vnode.componentId);
172+
173+
if (!component) {
174+
throw new Error(`util.VNode.getVnode: Component not found for id: ${vnode.componentId}`)
175+
}
176+
177+
vnode = component.vnode
172178
}
173179

174180
return vnode

test/siesta/siesta.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ project.plan(
6969
'tests/vdom/table/Container.mjs'
7070
]
7171
},
72-
'tests/vdom/Advanced.mjs']
72+
'tests/vdom/Advanced.mjs',
73+
'tests/vdom/VdomAsymmetricUpdates.mjs']
7374
}
7475
);
7576

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import Neo from '../../../../src/Neo.mjs';
2+
import * as core from '../../../../src/core/_export.mjs';
3+
import ComponentManager from '../../../../src/manager/Component.mjs';
4+
import TreeBuilder from '../../../../src/util/vdom/TreeBuilder.mjs';
5+
import VDomUpdate from '../../../../src/manager/VDomUpdate.mjs';
6+
import VdomHelper from '../../../../src/vdom/Helper.mjs';
7+
import VDomUtil from '../../../../src/util/VDom.mjs';
8+
9+
// IMPORTANT: Test with the new standard renderer
10+
Neo.config.useDomApiRenderer = true;
11+
VdomHelper.onNeoConfigChange({useDomApiRenderer: true});
12+
13+
/**
14+
* Creates a mock component object for testing.
15+
* @param {string} id
16+
* @param {string} parentId
17+
* @param {Object} vdom
18+
* @returns {Object} A mock component
19+
*/
20+
const createMockComponent = (id, parentId, vdom) => {
21+
const component = {
22+
id,
23+
parentId,
24+
vdom
25+
};
26+
// Create the initial vnode from the vdom definition.
27+
const { vnode } = VdomHelper.create({ vdom });
28+
component.vnode = vnode;
29+
30+
// Register the component BEFORE syncing IDs. This is critical so that
31+
// a parent's syncVdomIds call can find this component if it's a child.
32+
ComponentManager.register(component);
33+
VDomUtil.syncVdomIds(component.vnode, component.vdom);
34+
35+
return component;
36+
};
37+
38+
StartTest(t => {
39+
40+
t.beforeEach(() => {
41+
// Reset managers to ensure test isolation
42+
VDomUpdate.mergedCallbackMap.clear();
43+
VDomUpdate.postUpdateQueueMap.clear();
44+
ComponentManager.wrapperNodes.clear();
45+
ComponentManager.clear();
46+
});
47+
48+
t.it('Should handle asymmetric update with depth 2 using DomApiRenderer', t => {
49+
// 1. SETUP
50+
// Create a parent and a child. The parent's vdom references the child via componentId.
51+
const childVdomInitial = { id: 'child-1', cn: [{ tag: 'span', text: 'Initial' }] };
52+
const parentVdom = {
53+
id: 'parent-1',
54+
cn: [{ componentId: 'child-1' }]
55+
};
56+
57+
// Create components dependency-first (child before parent) to ensure
58+
// component references can be resolved during VDOM/VNode processing.
59+
// The `createMockComponent` factory now handles registration.
60+
let child = createMockComponent('child-1', 'parent-1', childVdomInitial);
61+
let parent = createMockComponent('parent-1', 'root', parentVdom);
62+
63+
// 2. SIMULATE A CHILD-INITIATED UPDATE
64+
// The child's internal state changes, and it requests to be part of the parent's next update.
65+
VDomUpdate.registerMerged(
66+
parent.id,
67+
child.id,
68+
[], // callbacks
69+
1, // childUpdateDepth
70+
1 // distance
71+
);
72+
73+
// The child's vdom has now changed. We update our mock to reflect this.
74+
const childVdomUpdated = { id: 'child-1', cn: [{ tag: 'span', text: 'Updated' }] };
75+
child.vdom = childVdomUpdated;
76+
77+
// 3. SIMULATE THE PARENT'S UPDATE LIFECYCLE
78+
// The parent calculates the required depth for the update.
79+
const adjustedDepth = VDomUpdate.getAdjustedUpdateDepth(parent.id);
80+
t.is(adjustedDepth, 2, 'Adjusted update depth should be 2 to include direct children');
81+
82+
// The parent builds an asymmetric VDOM tree. TreeBuilder will find the updated
83+
// child.vdom via the ComponentManager.
84+
const newAsymmetricVdom = TreeBuilder.getVdomTree(parent.vdom, adjustedDepth);
85+
86+
// Verify the created tree has the child's *new* vdom
87+
t.is(newAsymmetricVdom.cn[0].id, 'child-1', 'The child component VDOM is expanded in the asymmetric tree');
88+
t.is(newAsymmetricVdom.cn[0].cn[0].text, 'Updated', 'The expanded VDOM reflects the childs updated state');
89+
90+
// 4. GENERATE DELTAS
91+
// VdomHelper diffs the new, expanded tree against the parent's OLD vnode.
92+
const { deltas } = VdomHelper.update({
93+
vdom : newAsymmetricVdom,
94+
vnode: parent.vnode
95+
});
96+
97+
// 5. ASSERTIONS
98+
t.is(deltas.length, 1, 'Should generate exactly one delta for the text change');
99+
const delta = deltas[0];
100+
t.is(delta.action, 'updateVtext', 'The delta action should be to update the text node');
101+
t.is(delta.value, 'Updated', 'The new text content should be correct');
102+
t.ok(delta.id.startsWith('neo-vtext'), 'The delta correctly targets the text vnode');
103+
});
104+
105+
t.it('Should handle nested asymmetric update (grandchild update)', t => {
106+
// 1. SETUP
107+
const grandchildVdomInitial = { id: 'grandchild-1', cn: [{ tag: 'span', text: 'Initial' }] };
108+
const childVdom = {
109+
id: 'child-1',
110+
cn: [{ componentId: 'grandchild-1' }]
111+
};
112+
const parentVdom = {
113+
id: 'parent-1',
114+
cn: [{ componentId: 'child-1' }]
115+
};
116+
117+
// Create components dependency-first (grandchild -> child -> parent) to ensure
118+
// component references can be resolved during VDOM/VNode processing.
119+
let grandchild = createMockComponent('grandchild-1', 'child-1', grandchildVdomInitial);
120+
let child = createMockComponent('child-1', 'parent-1', childVdom);
121+
let parent = createMockComponent('parent-1', 'root', parentVdom);
122+
123+
// 2. SIMULATE A GRANDCHILD-INITIATED UPDATE
124+
// The grandchild's state changes. It is at a distance of 2 from the updating parent.
125+
VDomUpdate.registerMerged(
126+
parent.id,
127+
grandchild.id,
128+
[], // callbacks
129+
1, // grandchild's own updateDepth
130+
2 // distance from parent
131+
);
132+
133+
// The grandchild's vdom has now changed.
134+
const grandchildVdomUpdated = { id: 'grandchild-1', cn: [{ tag: 'span', text: 'Updated' }] };
135+
grandchild.vdom = grandchildVdomUpdated;
136+
137+
// 3. SIMULATE THE PARENT'S UPDATE LIFECYCLE
138+
// The required depth for the parent should be 3 to expand down to the grandchild.
139+
const adjustedDepth = VDomUpdate.getAdjustedUpdateDepth(parent.id);
140+
t.is(adjustedDepth, 3, 'Adjusted update depth should be 3 to include grandchild');
141+
142+
// The parent builds an asymmetric VDOM tree.
143+
const newAsymmetricVdom = TreeBuilder.getVdomTree(parent.vdom, adjustedDepth);
144+
145+
// Verify the created tree has the grandchild's *new* vdom
146+
const expandedChild = newAsymmetricVdom.cn[0];
147+
const expandedGrandchild = expandedChild.cn[0];
148+
t.is(expandedGrandchild.id, 'grandchild-1', 'The grandchild component VDOM is expanded in the asymmetric tree');
149+
t.is(expandedGrandchild.cn[0].text, 'Updated', 'The expanded VDOM reflects the grandchilds updated state');
150+
151+
// 4. GENERATE DELTAS
152+
const { deltas } = VdomHelper.update({
153+
vdom : newAsymmetricVdom,
154+
vnode: parent.vnode
155+
});
156+
157+
// 5. ASSERTIONS
158+
t.is(deltas.length, 1, 'Should generate exactly one delta for the text change');
159+
t.is(deltas[0].action, 'updateVtext', 'The delta action should be to update the text node');
160+
});
161+
});

0 commit comments

Comments
 (0)