From 08225212b887bd4b1eb81cd4bb1198175d57856a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Caridy=20Pati=C3=B1o?= Date: Fri, 21 Sep 2018 13:24:21 -0400 Subject: [PATCH] fix(engine): fixes #663 - unique keys for sloted elements --- .../src/framework/__tests__/vm.spec.ts | 70 +++++++++++++++++++ packages/lwc-engine/src/framework/vm.ts | 7 +- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/packages/lwc-engine/src/framework/__tests__/vm.spec.ts b/packages/lwc-engine/src/framework/__tests__/vm.spec.ts index cb3673099d..ecdc90908a 100644 --- a/packages/lwc-engine/src/framework/__tests__/vm.spec.ts +++ b/packages/lwc-engine/src/framework/__tests__/vm.spec.ts @@ -122,5 +122,75 @@ describe('vm', () => { expect(getErrorComponentStack(vm.elm)).toBe('\n\t'); }); }); + describe('slotting for slowpath', () => { + it('should re-keyed slotted content to avoid reusing elements from default content', () => { + const childHTML = compileTemplate(``); + class ChildComponent extends LightningElement { + render() { + return childHTML; + } + renderedCallback() { + const h1 = this.template.querySelector('h1'); + const h2 = this.template.querySelector('h2'); + if (h1) { + h1.setAttribute('def-1', 'internal'); + } + if (h2) { + h2.setAttribute('def-2', 'internal'); + } + } + } + const parentHTML = compileTemplate(``, { + modules: { + 'c-child': ChildComponent + } + }); + let parentTemplate; + class Parent extends LightningElement { + constructor() { + super(); + this.h1 = false; + this.h2 = false; + parentTemplate = this.template; + } + render() { + return parentHTML; + } + enable() { + this.h1 = this.h2 = true; + } + disable() { + this.h1 = this.h2 = true; + } + } + Parent.track = { h1: 1, h2: 1 }; + Parent.publicMethods = ['enable', 'disable']; + const elm = createElement('x-parent', { is: Parent }); + document.body.appendChild(elm); + elm.enable(); + return Promise.resolve().then(() => { + // at this point, if we are reusing the h1 and h2 from the default content + // of the slots in c-child, they will have an extraneous attribute on them, + // which will be a problem. + expect(parentTemplate.querySelector('c-child').outerHTML).toBe(`

slotted

`); + }); + }); + }); }); diff --git a/packages/lwc-engine/src/framework/vm.ts b/packages/lwc-engine/src/framework/vm.ts index bb06543563..f770023fc1 100644 --- a/packages/lwc-engine/src/framework/vm.ts +++ b/packages/lwc-engine/src/framework/vm.ts @@ -573,7 +573,12 @@ export function allocateInSlot(vm: VM, children: VNodes) { const data = (vnode.data as VNodeData); const slotName = ((data.attrs && data.attrs.slot) || '') as string; const vnodes: VNodes = cmpSlots[slotName] = cmpSlots[slotName] || []; - vnodes.push(vnode); + // re-keying the vnodes is necessary to avoid conflicts with default content for the slot + // which might have similar keys. Each vnode will always have a key that + // starts with a numeric character from compiler. In this case, we add a unique + // notation for slotted vnodes keys, e.g.: `@foo:1:1` + vnode.key = `@${slotName}:${vnode.key}`; + ArrayPush.call(vnodes, vnode); } if (!vm.isDirty) { // We need to determine if the old allocation is really different from the new one