Skip to content

Commit

Permalink
feat(engine): getRootNode in patched nodes
Browse files Browse the repository at this point in the history
implementation.

Adds the getRootNode for patches nodes.
  • Loading branch information
jodarove committed Oct 30, 2018
1 parent ef51617 commit 2176e59
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 4 deletions.
144 changes: 144 additions & 0 deletions packages/lwc-engine/src/faux-shadow/__tests__/traverse.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1107,3 +1107,147 @@ describe('assignedSlot', () => {
expect(text.assignedSlot).toBe(slot);
});
});


describe('Node.getRootNode on patched elements', () => {
afterEach(() => {
while (document.body.childNodes.length > 0) {
document.body.removeChild(document.body.childNodes[0]);
}
});

const childHtml = compileTemplate(`<template><span class="child-cmp-span">txt</span><slot></slot></template>`);
class ChildComponent extends LightningElement {
render() {
return childHtml;
}
}

const containerHtml = compileTemplate(`
<template>
<div id="container">
<div class="child-div">some text node</div>
<x-child><span class="child-cmp-slotted-span">txt</span></x-child>
</div>
</template>
`, {
modules: { 'x-child': ChildComponent }
});
class ContainerComponent extends LightningElement {
render() {
return containerHtml;
}
}

const selectors: any = {};
selectors.xChild = (elm) => getShadowRoot(elm)!.querySelector('x-child');
selectors.xChildSpanInShadow = (elm) => getShadowRoot(selectors.xChild(elm))!.querySelector('.child-cmp-span');
selectors.containerDiv = (elm) => getShadowRoot(elm)!.querySelector('.child-div');
selectors.containerSlottedSpan = (elm) => getShadowRoot(elm)!.querySelector('.child-cmp-slotted-span');

describe('when options.composed=true', () => {
it('should return itself when node is disconnected', () => {
const elm = createElement('x-container', { is: ContainerComponent });
expect(elm.getRootNode({ composed: true })).toBe(elm);
});

it('should return document when request inside an lwc cmp', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
expect(elm.getRootNode({ composed: true })).toBe(document);
});
});
it('should return document when request in a hierarchy of lwc cmp', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
const child = selectors.xChildSpanInShadow(elm);

expect(child.getRootNode({ composed: true })).toBe(document);
});
});
it('should return documentFragment the cmp is inside it', () => {
class ContainerComponent extends LightningElement {
render() {
return containerHtml;
}
}
const elm = createElement('x-container', { is: ContainerComponent });

const fragment = document.createDocumentFragment();
fragment.appendChild(elm);

return Promise.resolve().then(() => {
expect(elm.getRootNode({ composed: true })).toBe(fragment);
});
});
});

describe('when options.composed=false', () => {
it('should return itself when node is disconnected', () => {
const elm = createElement('x-container', { is: ContainerComponent });
expect(elm.getRootNode({ composed: false })).toBe(elm);
});

it('should return document as root when node is the root custom element', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
expect(elm.getRootNode()).toBe(document);
});
});

it('should return itself as root when node is a shadow', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
expect(getShadowRoot(elm).getRootNode()).toBe(getShadowRoot(elm));
});
});

it('should containing shadow of an element in the template of a component', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
const childDiv = selectors.containerDiv(elm);
expect(childDiv!.getRootNode()).toBe(getShadowRoot(elm));
});
});

it('should containing shadow when node is a component custom element', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
const childCmp = selectors.xChild(elm);
expect(childCmp.getRootNode()).toBe(getShadowRoot(elm));
});
});

it('should return containing shadow when the node is in a component hierachy', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
const spanInChildCmp = selectors.xChildSpanInShadow(elm);
expect(spanInChildCmp!.getRootNode()).toBe(getShadowRoot(selectors.xChild(elm)));
});
});

it('should return containing shadow when node is inside a slot', () => {
const elm = createElement('x-container', { is: ContainerComponent });
document.body.appendChild(elm);

return Promise.resolve().then(() => {
const spanInChildCmp = selectors.containerSlottedSpan(elm);
expect(spanInChildCmp!.getRootNode()).toBe(getShadowRoot(elm));
});
});
});
});
11 changes: 9 additions & 2 deletions packages/lwc-engine/src/faux-shadow/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const {
compareDocumentPosition,
} = Node.prototype;

// TODO: remove after TS 3.x upgrade.
interface GetRootNodeOptions {
composed?: boolean;
}

/**
* Returns the context shadow included root.
*/
Expand All @@ -46,7 +51,7 @@ function findShadowRoot(node: Node): Node {
return node;
}

function findComposedRootNode(node: Node): Node {
function getShadowIncludingRoot(node: Node): Node {
let nodeParent;
while (!isNull(nodeParent = parentNodeGetter.call(node))) {
node = nodeParent;
Expand All @@ -69,7 +74,7 @@ function getRootNode(
const composed: boolean = isUndefined(options) ? false : !!options.composed;

return isTrue(composed) ?
findComposedRootNode(this) :
getShadowIncludingRoot(this) :
findShadowRoot(this);
}

Expand All @@ -94,6 +99,8 @@ export {
parentElementGetter,
childNodesGetter,
textContextSetter,
getShadowIncludingRoot,
GetRootNodeOptions,

// Node
DOCUMENT_POSITION_CONTAINS,
Expand Down
10 changes: 8 additions & 2 deletions packages/lwc-engine/src/faux-shadow/shadow-root.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import assert from "../shared/assert";
import { isFalse, create, isUndefined, getOwnPropertyDescriptor, ArrayReduce, isNull, defineProperties, setPrototypeOf, defineProperty } from "../shared/language";
import { isFalse, isTrue, create, isUndefined, getOwnPropertyDescriptor, ArrayReduce, isNull, defineProperties, setPrototypeOf, defineProperty } from "../shared/language";
import { addShadowRootEventListener, removeShadowRootEventListener } from "./events";
import { shadowDomElementFromPoint, shadowRootQuerySelector, shadowRootQuerySelectorAll, shadowRootChildNodes, isNodeOwnedBy, isSlotElement } from "./traverse";
import { getInternalField, setInternalField, createFieldName } from "../shared/fields";
import { getInnerHTML } from "../3rdparty/polymer/inner-html";
import { getTextContent } from "../3rdparty/polymer/text-content";
import { compareDocumentPosition, DOCUMENT_POSITION_CONTAINED_BY, parentElementGetter } from "./node";
import { compareDocumentPosition, DOCUMENT_POSITION_CONTAINED_BY, parentElementGetter, GetRootNodeOptions, getShadowIncludingRoot } from "./node";
// it is ok to import from the polyfill since they always go hand-to-hand anyways.
import { ElementPrototypeAriaPropertyNames } from "../polyfills/aria-properties/polyfill";
import { DocumentPrototypeActiveElement } from "./document";
Expand Down Expand Up @@ -240,6 +240,12 @@ export class SyntheticShadowRoot extends DocumentFragment implements ShadowRoot
getSelection(this: SyntheticShadowRootInterface): Selection | null {
throw new Error();
}

getRootNode(options?: GetRootNodeOptions): Node {
const composed: boolean = isUndefined(options) ? false : !!options.composed;
// @ts-ignore: //TODO: remove after TS 3.x upgrade: Attributes property is removed from Node (https://developer.mozilla.org/en-US/docs/Web/API/Node)
return isTrue(composed) ? getShadowIncludingRoot(getHost(this)) : this;
}
}

// Is native ShadowDom is available on window,
Expand Down
20 changes: 20 additions & 0 deletions packages/lwc-engine/src/faux-shadow/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
DOCUMENT_POSITION_CONTAINS,
getNodeKey,
getNodeOwnerKey,
getShadowIncludingRoot,
GetRootNodeOptions
} from "./node";
import {
querySelectorAll as nativeQuerySelectorAll, innerHTMLSetter, getAttribute, tagNameGetter,
Expand Down Expand Up @@ -137,6 +139,19 @@ function getAllMatches(owner: HTMLElement, nodeList: NodeList | Node[]): Synthet
return new SyntheticNodeList(filteredAndPatched);
}

function getRoot(node: Node): Node {
const ownerNode = getNodeOwner(node);

if (isNull(ownerNode)) {
// we hit a wall, is not in lwc boundary.
return getShadowIncludingRoot(node);
}

// @ts-ignore: Attributes property is removed from Node (https://developer.mozilla.org/en-US/docs/Web/API/Node)
return getShadowRoot(ownerNode) as Node;
}


function getFirstMatch(owner: HTMLElement, nodeList: NodeList): Element | null {
for (let i = 0, len = nodeList.length; i < len; i += 1) {
if (isNodeOwnedBy(owner, nodeList[i])) {
Expand Down Expand Up @@ -323,6 +338,11 @@ export function PatchedNode(node: Node): NodeConstructor {
}
return parentNode;
}
getRootNode(options?: GetRootNodeOptions): Node {
const composed: boolean = isUndefined(options) ? false : !!options.composed;

return isTrue(composed) ? getShadowIncludingRoot(this) : getRoot(this);
}
};
}

Expand Down

0 comments on commit 2176e59

Please sign in to comment.