Skip to content

Commit

Permalink
fix: getRootNode when composed is false and the element is the root (#…
Browse files Browse the repository at this point in the history
…337)

## Details

Fix error throw in getRootNode when the component is the root of the component tree.

## Does this PR introduce a breaking change?

* [ ] Yes
* [X] No
  • Loading branch information
pmdartus authored May 22, 2018
1 parent 51adcb6 commit 8dd8392
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 71 deletions.
58 changes: 7 additions & 51 deletions packages/lwc-engine/src/framework/__tests__/dom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,34 +50,12 @@ describe('dom', () => {
});

it('should return correct value from self', () => {
class Parent extends Element {
handleFoo(evt) {
expect(evt.target).toBe(this.template.querySelector('x-foo'));
}

render() {
return ($api, $cmp) => {
return [
$api.h(
'div',
{
on: {
foo: $api.b($cmp.handleFoo)
},
key: 0,
},
[]
)
]
}
}
}

class Parent extends Element {}
const elm = createElement('x-parent', { is: Parent });
document.body.appendChild(elm);

const match = getRootNode.call(elm, { composed: true });
// We can't assert against document directly, because
// for some reasons, jest is locking up with document here

expect(match.nodeName).toBe('#document');
});
});
Expand Down Expand Up @@ -129,34 +107,12 @@ describe('dom', () => {
});

it('should return correct value from self', () => {
class Parent extends Element {
handleFoo(evt) {
expect(evt.target).toBe(this.template.querySelector('x-foo'));
}

render() {
return ($api, $cmp) => {
return [
$api.h(
'div',
{
on: {
foo: $api.b($cmp.handleFoo)
},
key: 0,
},
[]
)
]
}
}
}

class Parent extends Element {}
const elm = createElement('x-parent', { is: Parent });
document.body.appendChild(elm);
const match = getRootNode.call(elm, { composed: true });
// We can't assert against document directly, because
// for some reasons, jest is locking up with document here

const match = getRootNode.call(elm, { composed: false });

expect(match.nodeName).toBe('#document');
});
});
Expand Down
66 changes: 46 additions & 20 deletions packages/lwc-engine/src/framework/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getOwnPropertyDescriptor,
isUndefined,
isNull,
isTrue,
defineProperties,
} from './language';
import { ViewModelReflection } from "./utils";
Expand All @@ -35,34 +36,59 @@ const {
compareDocumentPosition,
} = Node.prototype;

function findShadowRoot(node) {
let root = node;
while (isUndefined(root[ViewModelReflection])) {
root = root.parentNode;
/**
* Returns the context shadow included root.
*/
function findShadowRoot(node: Node): Node {
// We need to ensure that the parent element is present before accessing it.
if (isNull(node.parentNode)) {
return node;
}
return root;

// In the case of LWC, the root and the host element are the same things. Therefor,
// when calling findShadowRoot on the a host element we want to return the parent host
// element and not the current host element.
node = node.parentNode;
while (
!isNull(node.parentNode) &&
isUndefined(node[ViewModelReflection])
) {
node = node.parentNode;
}

return node;
}

function findComposedRootNode(node: Node) {
while (node !== document) {
const parent = node.parentNode;
if (isNull(parent)) {
return node;
}
node = parent;
/**
* Returns the context root beyond the shadow root.
*
* It doesn't returns actually the root but the host. This approximation is sufficiant
* in our case.
*/
function findComposedRootNode(node: Node): Node {
while (!isNull(node.parentNode)) {
node = node.parentNode;
}

return node;
}

// TODO: once we start using the real shadowDOM, we can rely on:
// const { getRootNode } = Node.prototype;
// for now, we need to provide a dummy implementation to provide retargeting
function getRootNode(this: Node, options: Record<string, any> | undefined): Node {
/**
* Dummy implementation of the Node.prototype.getRootNode.
* Spec: https://dom.spec.whatwg.org/#dom-node-getrootnode
*
* TODO: Once we start using the real shadowDOM, this method should be replaced by:
* const { getRootNode } = Node.prototype;
*/
function getRootNode(
this: Node,
options?: { composed?: boolean }
): Node {
const composed: boolean = isUndefined(options) ? false : !!options.composed;
if (!composed) {
return findShadowRoot(this.parentNode); // this is not quite the root (it is the host), but for us is sufficient
}
return findComposedRootNode(this);

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

export function isChildNode(root: Element, node: Node): boolean {
Expand Down

0 comments on commit 8dd8392

Please sign in to comment.