From e289a97fd5b95a814febf49ae4ecd131f0e59113 Mon Sep 17 00:00:00 2001 From: chyizheng Date: Wed, 27 Sep 2023 17:23:11 +0800 Subject: [PATCH] fix(core): fix composing issues for virtual host components --- glass-easel/src/element.ts | 49 ++++----- glass-easel/tests/core/placeholder.test.ts | 41 ++++++- glass-easel/tests/legacy/virtual.test.js | 119 ++++++++++++++++++++- 3 files changed, 180 insertions(+), 29 deletions(-) diff --git a/glass-easel/src/element.ts b/glass-easel/src/element.ts index 156b504..76fb010 100644 --- a/glass-easel/src/element.ts +++ b/glass-easel/src/element.ts @@ -787,22 +787,23 @@ export class Element implements NodeCast { if (ret) return ret } } - if (!parent._$virtual) return null - const containingSlot = parent.containingSlot - if (containingSlot === null) return null - if (containingSlot !== undefined) { - return Element._$findFirstNonVirtualChild(containingSlot, parent.slotIndex! + 1) - } - let cur: Element = parent - if (parent instanceof ShadowRoot) { - cur = parent.getHostNode() - if (!cur._$virtual) return null - } - const p = cur.parentNode - if (p) { - return Element._$findFirstNonVirtualChild(p, cur.parentIndex + 1) + const recvParent = (parent: Element): Node | null => { + if (!parent._$virtual) return null + const containingSlot = parent.containingSlot + if (containingSlot === null) return null + if (containingSlot !== undefined) { + return Element._$findFirstNonVirtualChild(containingSlot, parent.slotIndex! + 1) + } + if (parent instanceof ShadowRoot) { + return recvParent(parent.getHostNode()) + } + const p = parent.parentNode + if (p) { + return Element._$findFirstNonVirtualChild(p, parent.parentIndex + 1) + } + return null } - return null + return recvParent(parent) } /** @@ -825,6 +826,7 @@ export class Element implements NodeCast { if (element instanceof ShadowRoot) { cur = element.getHostNode() if (!cur._$virtual) return null + return Element._$findFirstNonVirtualChild(cur, cur.parentIndex + 1) } const p = cur.parentNode if (p) { @@ -1830,6 +1832,9 @@ export class Element implements NodeCast { } } + // handling child nodes list for placeholder + placeholder.childNodes = [] + // change the parent placeholder.parentNode = null placeholder.parentIndex = -1 @@ -1876,12 +1881,7 @@ export class Element implements NodeCast { } ;(frag as backend.Element).release() } else { - if (placeholder.isVirtual()) { - // virtual placeholder does not need to remove - Element.insertChildComposed(parent, replacer, undefined, false, posIndex) - } else { - Element.insertChildComposed(parent, replacer, placeholder, true, posIndex) - } + Element.insertChildComposed(parent, replacer, placeholder, true, posIndex) for (let i = 0; i < replacedChildren.length; i += 1) { const child = replacedChildren[i]! Element.insertChildComposed(replacer, child, undefined, false, i) @@ -1906,18 +1906,15 @@ export class Element implements NodeCast { parent._$mutationObserverTarget?.attachChild(replacer) // handling child nodes list for replacer - replacer.childNodes.push(...placeholder.childNodes) + replacer.childNodes.push(...replacedChildren) for ( - let i = replacer.childNodes.length - placeholder.childNodes.length; + let i = replacer.childNodes.length - replacedChildren.length; i < replacer.childNodes.length; i += 1 ) { replacer.childNodes[i]!.parentIndex = i } - // handling child nodes list for placeholder - placeholder.childNodes = [] - // update id and slot cache if needed parent.ownerShadowRoot?._$markIdCacheDirty() diff --git a/glass-easel/tests/core/placeholder.test.ts b/glass-easel/tests/core/placeholder.test.ts index c24dd0a..afd80a5 100644 --- a/glass-easel/tests/core/placeholder.test.ts +++ b/glass-easel/tests/core/placeholder.test.ts @@ -405,7 +405,46 @@ const testCases = (testBackend: glassEasel.GeneralBackendContext) => { expect(domHtml(elem)).toBe('
21
') matchElementWithDom(elem) }) + + test('replacing virtual host component', () => { + const placeholder = componentSpace.defineComponent({ + options: { + virtualHost: true, + }, + template: tmpl('
placeholder
'), + }) + const card = componentSpace.defineComponent({ + template: tmpl(`
`), + }) + const def = componentSpace.defineComponent({ + is: 'placeholder/ttt/parent', + using: { + child: 'child', + placeholder: placeholder.general(), + card, + }, + placeholders: { + child: 'placeholder', + }, + template: tmpl(` + + content + + `), + }) + const elem = glassEasel.Component.createWithContext('root', def.general(), testBackend) + matchElementWithDom(elem) + + componentSpace.defineComponent({ + is: 'placeholder/ttt/child', + options: { + virtualHost: true, + }, + template: tmpl('
actual
'), + }) + matchElementWithDom(elem) + }) } describe('placeholder (DOM backend)', () => testCases(domBackend)) -describe('placeholder (composed backend)', () => testCases(composedBackend)) \ No newline at end of file +describe('placeholder (composed backend)', () => testCases(composedBackend)) diff --git a/glass-easel/tests/legacy/virtual.test.js b/glass-easel/tests/legacy/virtual.test.js index 3261ff2..7df8566 100644 --- a/glass-easel/tests/legacy/virtual.test.js +++ b/glass-easel/tests/legacy/virtual.test.js @@ -342,6 +342,7 @@ const testCases = function (testBackend) { var e3 = glassEasel.NativeNode.create('e3', root.shadowRoot) var v1 = glassEasel.VirtualNode.create('v1', root.shadowRoot) var v2 = glassEasel.VirtualNode.create('v2', root.shadowRoot) + var v3 = glassEasel.VirtualNode.create('v3', root.shadowRoot) var t1 = glassEasel.TextNode.create('t1', root.shadowRoot) var t2 = glassEasel.TextNode.create('t2', root.shadowRoot) e1.appendChild(e2) @@ -356,11 +357,17 @@ const testCases = function (testBackend) { expect(e2.parentNode).toBe(null) expect(e2.childNodes.length).toBe(0) - v2.selfReplaceWith(e2) + v2.selfReplaceWith(v3) matchElementWithDom(e1) matchElementWithDom(v2) expect(v2.parentNode).toBe(null) expect(v2.childNodes.length).toBe(0) + + v3.selfReplaceWith(e2) + matchElementWithDom(e1) + matchElementWithDom(v3) + expect(v3.parentNode).toBe(null) + expect(v3.childNodes.length).toBe(0) }) it('should replace component with children', function () { @@ -405,6 +412,59 @@ const testCases = function (testBackend) { }) it('should replace virtualHost component with children', function () { + regElem({ + is: 'virtual-comp1', + options: { virtualHost: true }, + template: '
virtual
', + }) + regElem({ + is: 'comp1', + template: '
actual
', + }) + regElem({ + is: 'virtual-comp2', + options: { multipleSlots: true, virtualHost: true }, + template: '
virtual
', + }) + regElem({ + is: 'comp2', + options: { multipleSlots: true }, + template: '
actual
', + }) + var e1 = glassEasel.NativeNode.create('e1', root.shadowRoot) + var e2 = glassEasel.NativeNode.create('e2', root.shadowRoot) + var e3 = glassEasel.NativeNode.create('e3', root.shadowRoot) + var e4 = glassEasel.NativeNode.create('e4', root.shadowRoot) + var v1 = glassEasel.VirtualNode.create('v1', root.shadowRoot) + var t1 = glassEasel.TextNode.create('t1', root.shadowRoot) + var c1 = root.shadowRoot.createComponent('comp1') + var vc1 = root.shadowRoot.createComponent('virtual-comp1') + var c2 = root.shadowRoot.createComponent('comp2') + var vc2 = root.shadowRoot.createComponent('virtual-comp2') + e1.appendChild(e2) + e2.appendChild(e3) + e2.appendChild(v1) + v1.appendChild(t1) + e2.appendChild(e4) + e4.slot = 'b' + + e2.selfReplaceWith(vc1) + matchElementWithDom(e1) + expect(e2.parentNode).toBe(null) + expect(e2.childNodes.length).toBe(0) + + vc1.selfReplaceWith(vc2) + matchElementWithDom(e1) + expect(vc1.parentNode).toBe(null) + expect(vc1.childNodes.length).toBe(0) + + vc2.selfReplaceWith(c2) + matchElementWithDom(e1) + expect(vc2.parentNode).toBe(null) + expect(vc2.childNodes.length).toBe(0) + }) + + it('should replace nested virtualHost component with children', function () { regElem({ is: 'virtual-comp1', options: { virtualHost: true }, @@ -460,6 +520,21 @@ const testCases = function (testBackend) { matchElementWithDom(e1) expect(vc2.parentNode).toBe(null) expect(vc2.childNodes.length).toBe(0) + + c2.selfReplaceWith(vc2) + matchElementWithDom(e1) + expect(c2.parentNode).toBe(null) + expect(c2.childNodes.length).toBe(0) + + vc2.selfReplaceWith(vc1) + matchElementWithDom(e1) + expect(vc2.parentNode).toBe(null) + expect(vc2.childNodes.length).toBe(0) + + vc1.selfReplaceWith(vc2) + matchElementWithDom(e1) + expect(vc1.parentNode).toBe(null) + expect(vc1.childNodes.length).toBe(0) }) it('should replace slot contents with children', function () { @@ -531,7 +606,7 @@ const testCases = function (testBackend) { matchElementWithDom(parent) }) - if (testBackend === domBackend) + if (testBackend !== shadowBackend) { it('should handles tree manipulations', function () { regElem({ is: 'virtual-host-c', @@ -567,6 +642,46 @@ const testCases = function (testBackend) { expect(elem.$$.childNodes.length).toBe(0) matchElementWithDom(elem) }) + } + + it('should handles wx-if update', function () { + regElem({ + is: 'list-view', + options: { + virtualHost: true, + }, + template: '', + }) + regElem({ + is: 'virtual-host-b', + options: { + virtualHost: true, + }, + data: { + a: false, + b: false, + }, + template: '', + }) + regElem({ + is: 'c', + options: {}, + template: '', + }) + var parent = glassEasel.NativeNode.create('span', root.shadowRoot) + var elem = root.shadowRoot.createComponent('c') + parent.appendChild(elem) + matchElementWithDom(parent) + + elem.$.b.setData({ a: true }) + matchElementWithDom(parent) + + elem.$.b.setData({ a: false, b: true }) + matchElementWithDom(parent) + + elem.$.b.setData({ b: false }) + matchElementWithDom(parent) + }) }) }