Skip to content

Commit

Permalink
fix(synthetic-shadow): synthetic shadow doesn't preserve assignedSlot…
Browse files Browse the repository at this point in the history
… behavior (#2547)

* fix(synthetic-shadow): synthetic shadow doesn't preserve assignedSlot behavio

Fixes #2377

W-9492998

* fix: add bug repro as a new test

* fix: minor change and disable test for ie11

* fix: use existing method to check for native shadow root

* fix: use shadow root getter instead of dot-notation

* fix: add a new test for assignedSlot returns null for default content in light dom
  • Loading branch information
rui-rayqiu authored Dec 18, 2021
1 parent 656663a commit caa3c72
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 315 deletions.
8 changes: 8 additions & 0 deletions packages/@lwc/synthetic-shadow/src/env/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ const shadowRootGetter: (this: Element) => ShadowRoot | null = hasOwnProperty.ca
? getOwnPropertyDescriptor(Element.prototype, 'shadowRoot')!.get!
: () => null;

const assignedSlotGetter: (this: Element) => HTMLSlotElement | null = hasOwnProperty.call(
Element.prototype,
'assignedSlot'
)
? getOwnPropertyDescriptor(Element.prototype, 'assignedSlot')!.get!
: () => null;

export {
attachShadow,
childrenGetter,
Expand Down Expand Up @@ -139,4 +146,5 @@ export {
tagNameGetter,
tabIndexGetter,
tabIndexSetter,
assignedSlotGetter,
};
15 changes: 15 additions & 0 deletions packages/@lwc/synthetic-shadow/src/env/text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

import { hasOwnProperty, getOwnPropertyDescriptor } from '@lwc/shared';

export const assignedSlotGetter: (this: Text) => HTMLSlotElement | null = hasOwnProperty.call(
Text.prototype,
'assignedSlot'
)
? getOwnPropertyDescriptor(Text.prototype, 'assignedSlot')!.get!
: () => null;
4 changes: 3 additions & 1 deletion packages/@lwc/synthetic-shadow/src/faux-shadow/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ function parentElementGetterPatched(this: Node): Element | null {
}

function compareDocumentPositionPatched(this: Node, otherNode: Node) {
if (this.getRootNode() === otherNode) {
if (this === otherNode) {
return 0;
} else if (this.getRootNode() === otherNode) {
// "this" is in a shadow tree where the shadow root is the "otherNode".
return 10; // Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING
} else if (getNodeOwnerKey(this) !== getNodeOwnerKey(otherNode)) {
Expand Down
24 changes: 21 additions & 3 deletions packages/@lwc/synthetic-shadow/src/faux-shadow/slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@ import {
isTrue,
isUndefined,
} from '@lwc/shared';
import { getAttribute, setAttribute } from '../env/element';
import {
getAttribute,
setAttribute,
assignedSlotGetter as originalElementAssignedSlotGetter,
shadowRootGetter,
} from '../env/element';
import { assignedSlotGetter as originalTextAssignedSlotGetter } from '../env/text';
import { dispatchEvent } from '../env/event-target';
import { MutationObserverObserve, MutationObserver } from '../env/mutation-observer';
import { childNodesGetter, parentNodeGetter } from '../env/node';
import {
assignedNodes as originalAssignedNodes,
assignedElements as originalAssignedElements,
} from '../env/slot';
import { isInstanceOfNativeShadowRoot } from '../env/shadow-root';
import {
isSlotElement,
getNodeOwner,
Expand Down Expand Up @@ -83,12 +90,23 @@ function getFilteredSlotFlattenNodes(slot: HTMLElement): Node[] {
);
}

export function assignedSlotGetterPatched(this: Element): HTMLSlotElement | null {
export function assignedSlotGetterPatched(this: Element | Text): HTMLSlotElement | null {
const parentNode = parentNodeGetter.call(this);

// use original assignedSlot if parent has a native shdow root
if (parentNode instanceof Element) {
const sr = shadowRootGetter.call(parentNode);
if (isInstanceOfNativeShadowRoot(sr)) {
if (this instanceof Text) {
return originalTextAssignedSlotGetter.call(this);
}
return originalElementAssignedSlotGetter.call(this);
}
}

/**
* The node is assigned to a slot if:
* - it has a parent and it parent its parent is a slot element
* - it has a parent and its parent is a slot element
* - and if the slot owner key is different than the node owner key.
*
* When the slot and the slotted node are 2 different shadow trees, the owner keys will be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ describe('Slotting', () => {
]);
});

it('should return null for assignedSlot of default slotted content', () => {
const nodes = createTestElement('x-dynamic-children', DynamicChildren);

expect(nodes['container-upper-slot-default'].assignedSlot).toBeNull();
expect(nodes['container-lower-slot-default'].assignedSlot).toBeNull();
});

it('shadow container, light consumer', () => {
const nodes = createTestElement('x-light-consumer', LightConsumer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ describe('Light DOM + Synthetic Shadow DOM', () => {
expect(nodes.slot.assignedElements()).toEqual([nodes.p]);
});

if (!process.env.MIXED_SHADOW) {
it('assignedSlot', () => {
expect(nodes.p.assignedSlot).toEqual(nodes.slot);
});
}
it('assignedSlot', () => {
expect(nodes.p.assignedSlot).toEqual(nodes.slot);
});

it('childNodes', () => {
expect(Array.from(nodes.slot.childNodes)).toEqual([]);
Expand Down Expand Up @@ -97,13 +95,9 @@ describe('Light DOM + Synthetic Shadow DOM', () => {
it('assignedElements', () => {
expect(nodes.slot.assignedElements()).toEqual([nodes.p]);
});

if (!process.env.MIXED_SHADOW) {
it('assignedSlot', () => {
expect(nodes.p.assignedSlot).toEqual(nodes.slot);
});
}

it('assignedSlot', () => {
expect(nodes.p.assignedSlot).toEqual(nodes.slot);
});
it('childNodes', () => {
expect(Array.from(nodes.slot.childNodes)).toEqual([]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,33 @@ import Container from 'x/container';
// In the synthetic shadow however, slot content will be not be present in the DOM when not rendered.

describe('custom elements', () => {
if (!process.env.MIXED_SHADOW) {
it('should not be reused when slotted', function () {
const elm = createElement('x-container', { is: Container });
elm.isCustomElement = true;
document.body.appendChild(elm);
it('should not be reused when slotted', function () {
const elm = createElement('x-container', { is: Container });
elm.isCustomElement = true;
document.body.appendChild(elm);

const child = elm.shadowRoot.querySelector('x-child');
let firstRenderCustomElement;
const child = elm.shadowRoot.querySelector('x-child');
let firstRenderCustomElement;

return Promise.resolve()
.then(() => (child.open = true))
.then(() => {
firstRenderCustomElement = elm.shadowRoot.querySelector('x-simple');
child.open = false;
})
.then(() => (child.open = true))
.then(() => {
const xSimple = elm.shadowRoot.querySelector('x-simple');
return Promise.resolve()
.then(() => (child.open = true))
.then(() => {
firstRenderCustomElement = elm.shadowRoot.querySelector('x-simple');
child.open = false;
})
.then(() => (child.open = true))
.then(() => {
const xSimple = elm.shadowRoot.querySelector('x-simple');

expect(xSimple).not.toBeNull();
expect(xSimple.assignedSlot).not.toBeNull();
expect(elm.shadowRoot.querySelector('.mark')).not.toBeNull();
expect(xSimple).not.toBeNull();
expect(xSimple.assignedSlot).not.toBeNull();
expect(elm.shadowRoot.querySelector('.mark')).not.toBeNull();

if (!process.env.NATIVE_SHADOW) {
expect(xSimple).not.toBe(firstRenderCustomElement);
}
});
});
}
if (!process.env.NATIVE_SHADOW) {
expect(xSimple).not.toBe(firstRenderCustomElement);
}
});
});
});

describe('elements', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,51 +23,45 @@ describe('assignedSlot', () => {
expect(child.assignedSlot).toBe(null);
});

if (!process.env.MIXED_SHADOW) {
it('should return the correct slot when native element is slotted', () => {
const elm = createElement('x-native-slotted-component', { is: SlottedParent });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-slot').shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('div');
expect(child.assignedSlot).toBe(slot);
});
it('should return the correct slot when native element is slotted', () => {
const elm = createElement('x-native-slotted-component', { is: SlottedParent });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-slot').shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('div');
expect(child.assignedSlot).toBe(slot);
});

it('should return the correct slot when custom element is slotted', () => {
const elm = createElement('x-custom-slotted-component', { is: SlottedCustomElement });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-slot').shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('x-child');
expect(child.assignedSlot).toBe(slot);
});
it('should return the correct slot when custom element is slotted', () => {
const elm = createElement('x-custom-slotted-component', { is: SlottedCustomElement });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-slot').shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('x-child');
expect(child.assignedSlot).toBe(slot);
});

it('should return the correct named slot when native element is slotted', () => {
const elm = createElement('x-native-slotted-component', { is: SlottedParent });
document.body.appendChild(elm);
const slot = elm.shadowRoot
.querySelector('x-named-slot')
.shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('div.named');
expect(child.assignedSlot).toBe(slot);
});
it('should return the correct named slot when native element is slotted', () => {
const elm = createElement('x-native-slotted-component', { is: SlottedParent });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-named-slot').shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('div.named');
expect(child.assignedSlot).toBe(slot);
});

it('should return the correct named slot when custom element is slotted', () => {
const elm = createElement('x-custom-slotted-component', { is: SlottedCustomElement });
document.body.appendChild(elm);
const slot = elm.shadowRoot
.querySelector('x-named-slot')
.shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('x-child.named');
expect(child.assignedSlot).toBe(slot);
});
it('should return the correct named slot when custom element is slotted', () => {
const elm = createElement('x-custom-slotted-component', { is: SlottedCustomElement });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-named-slot').shadowRoot.querySelector('slot');
const child = elm.shadowRoot.querySelector('x-child.named');
expect(child.assignedSlot).toBe(slot);
});

it('should return the correct slot when text is slotted', () => {
const elm = createElement('x-native-slotted-component', { is: TextSlotted });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-slot').shadowRoot.querySelector('slot');
const text = getHostChildNodes(elm.shadowRoot.querySelector('x-slot'))[0];
expect(text.assignedSlot).toBe(slot);
});
}
it('should return the correct slot when text is slotted', () => {
const elm = createElement('x-native-slotted-component', { is: TextSlotted });
document.body.appendChild(elm);
const slot = elm.shadowRoot.querySelector('x-slot').shadowRoot.querySelector('slot');
const text = getHostChildNodes(elm.shadowRoot.querySelector('x-slot'))[0];
expect(text.assignedSlot).toBe(slot);
});

it('should return null when native element default slot content', () => {
const elm = createElement('x-assigned-slot', { is: SlotReceiver });
Expand Down
Loading

0 comments on commit caa3c72

Please sign in to comment.