Skip to content

Commit

Permalink
Add support for nextElementSibling and previousElementSibling (amppro…
Browse files Browse the repository at this point in the history
…ject#1132)

* Add support for nextElementSibling and previousElementSibling

* update compat table

* fix tests

* also add to CharacterData

* bump filesize

* Flesh out one more aspect of test case.

* bumped wrong filesize

* babel loose mode compat: dont spread Set (ampproject#1133)

* add comment node to test
  • Loading branch information
samouri authored Feb 11, 2022
1 parent d418048 commit c906bfc
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"brotli": "4.5 kB"
},
"./dist/amp-production/worker/worker.mjs": {
"brotli": "13 kB"
"brotli": "14 kB"
},
"./dist/amp-production/worker/worker.nodom.mjs": {
"brotli": "2 kB"
Expand Down
27 changes: 27 additions & 0 deletions src/test/node/nextSibling.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import anyTest, { TestInterface } from 'ava';
import { Comment } from '../../worker-thread/dom/Comment';
import { Element } from '../../worker-thread/dom/Element';
import { Text } from '../../worker-thread/dom/Text';
import { createTestingDocument } from '../DocumentCreation';

const test = anyTest as TestInterface<{
node: Element;
child: Element;
childTwo: Element;
textNode: Text;
textNodeTwo: Text;
commentNode: Comment;
}>;

test.beforeEach((t) => {
Expand All @@ -15,6 +20,9 @@ test.beforeEach((t) => {
node: document.createElement('div'),
child: document.createElement('div'),
childTwo: document.createElement('div'),
textNode: document.createTextNode('Hello'),
textNodeTwo: document.createTextNode('World'),
commentNode: document.createComment('comment'),
};
});

Expand All @@ -24,12 +32,14 @@ test('when a parent contains two children, the next sibling of the first is the
node.appendChild(child);
node.appendChild(childTwo);
t.deepEqual(child.nextSibling, childTwo);
t.deepEqual(child.nextElementSibling, childTwo);
});

test('when a node does not have a parent, its next sibling is null', (t) => {
const { node } = t.context;

t.is(node.nextSibling, null);
t.is(node.nextElementSibling, null);
});

test('when a node is the last child of a parent, the next sibling is null', (t) => {
Expand All @@ -38,4 +48,21 @@ test('when a node is the last child of a parent, the next sibling is null', (t)
node.appendChild(child);
node.appendChild(childTwo);
t.is(childTwo.nextSibling, null);
t.is(childTwo.nextElementSibling, null);
});

test('nextElementSibling skips over non-element nodes', (t) => {
const { node, child, childTwo, textNode, textNodeTwo, commentNode } = t.context;

node.appendChild(child);
node.appendChild(commentNode);
node.appendChild(textNode);
node.appendChild(childTwo);
node.appendChild(textNodeTwo);

t.is(child.nextElementSibling, childTwo);
t.is(commentNode.nextElementSibling, childTwo);
t.is(textNode.nextElementSibling, childTwo);
t.is(childTwo.nextElementSibling, null);
t.is(textNodeTwo.nextElementSibling, null);
});
30 changes: 30 additions & 0 deletions src/test/node/previousSibling.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import anyTest, { TestInterface } from 'ava';
import { Comment } from '../../worker-thread/dom/Comment';
import { Element } from '../../worker-thread/dom/Element';
import { Text } from '../../worker-thread/dom/Text';
import { createTestingDocument } from '../DocumentCreation';

const test = anyTest as TestInterface<{
node: Element;
child: Element;
childTwo: Element;
textNode: Text;
textNodeTwo: Text;
commentNode: Comment;
}>;

test.beforeEach((t) => {
Expand All @@ -15,6 +20,9 @@ test.beforeEach((t) => {
node: document.createElement('div'),
child: document.createElement('div'),
childTwo: document.createElement('div'),
textNode: document.createTextNode('Hello world'),
textNodeTwo: document.createTextNode('World'),
commentNode: document.createComment('comment'),
};
});

Expand All @@ -24,12 +32,14 @@ test('when a parent contains two children, the previous sibling of the second is
node.appendChild(child);
node.appendChild(childTwo);
t.deepEqual(childTwo.previousSibling, child);
t.deepEqual(childTwo.previousElementSibling, child);
});

test('when a node does not have a parent, its previous sibling is null', (t) => {
const { node } = t.context;

t.is(node.previousSibling, null);
t.is(node.previousElementSibling, null);
});

test('when a node is the first child of a parent, the previous sibling is null', (t) => {
Expand All @@ -38,3 +48,23 @@ test('when a node is the first child of a parent, the previous sibling is null',
node.appendChild(child);
t.is(child.previousSibling, null);
});

test('previousElementSibling skips over text nodes', (t) => {
const { node, child, childTwo, textNode, textNodeTwo, commentNode } = t.context;

node.appendChild(child);
node.appendChild(commentNode);
node.appendChild(textNode);
node.appendChild(textNodeTwo);
node.appendChild(childTwo);

t.is(commentNode.previousElementSibling, child);
t.is(textNode.previousElementSibling, child);
t.is(textNodeTwo.previousElementSibling, child);
t.is(childTwo.previousElementSibling, child);

node.innerHTML = '';
node.appendChild(textNode);
node.appendChild(textNodeTwo);
t.is(textNodeTwo.previousElementSibling, null);
});
19 changes: 19 additions & 0 deletions src/worker-thread/dom/CharacterData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Document } from './Document';
import { NodeType } from '../../transfer/TransferrableNodes';
import { TransferrableKeys } from '../../transfer/TransferrableKeys';
import { TransferrableMutationType } from '../../transfer/TransferrableMutation';
import { getNextElementSibling, getPreviousElementSibling } from './elementSibling';

// @see https://developer.mozilla.org/en-US/docs/Web/API/CharacterData
export abstract class CharacterData extends Node {
Expand Down Expand Up @@ -73,4 +74,22 @@ export abstract class CharacterData extends Node {
set nodeValue(value: string) {
this.data = value;
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/previousElementSibling
* Returns the Element immediately prior to the specified one in its parent's children list,
* or null if the specified element is the first one in the list.
*/
get previousElementSibling(): Node | null {
return getPreviousElementSibling(this);
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/nextElementSibling
* Returns the Element immediately following the specified one in its parent's children list,
* or null if the specified element is the last one in the list.
*/
get nextElementSibling(): Node | null {
return getNextElementSibling(this);
}
}
21 changes: 19 additions & 2 deletions src/worker-thread/dom/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { MessageToWorker, MessageType, BoundingClientRectToWorker } from '../../
import { parse } from '../../third_party/html-parser/html-parser';
import { propagate } from './Node';
import { Event } from '../Event';
import { getNextElementSibling, getPreviousElementSibling } from './elementSibling';

export const NS_NAME_TO_CLASS: { [key: string]: typeof Element } = {};
export const registerSubclass = (localName: string, subclass: typeof Element, namespace: string = HTML_NAMESPACE): any =>
Expand Down Expand Up @@ -115,9 +116,7 @@ export class Element extends ParentNode {
// Element.clientTop – https://developer.mozilla.org/en-US/docs/Web/API/Element/clientTop
// Element.clientWidth – https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth
// set Element.innerHTML – https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
// NonDocumentTypeChildNode.nextElementSibling – https://developer.mozilla.org/en-US/docs/Web/API/NonDocumentTypeChildNode/nextElementSibling
// Element.prefix – https://developer.mozilla.org/en-US/docs/Web/API/Element/prefix
// NonDocummentTypeChildNode.previousElementSibling – https://developer.mozilla.org/en-US/docs/Web/API/NonDocumentTypeChildNode/previousElementSibling
// Element.scrollHeight – https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
// Element.scrollLeft – https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
// Element.scrollLeftMax – https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeftMax
Expand Down Expand Up @@ -161,6 +160,24 @@ export class Element extends ParentNode {
// Mixins not implemented
// Slotable.assignedSlot – https://developer.mozilla.org/en-US/docs/Web/API/Slotable/assignedSlot

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/previousElementSibling
* Returns the Element immediately prior to the specified one in its parent's children list,
* or null if the specified element is the first one in the list.
*/
get previousElementSibling(): Node | null {
return getPreviousElementSibling(this);
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/nextElementSibling
* Returns the Element immediately following the specified one in its parent's children list,
* or null if the specified element is the last one in the list.
*/
get nextElementSibling(): Node | null {
return getNextElementSibling(this);
}

/**
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML
* @return string representation of serialized HTML describing the Element and its descendants.
Expand Down
33 changes: 33 additions & 0 deletions src/worker-thread/dom/elementSibling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NodeType } from '../../transfer/TransferrableNodes';
import { Element } from './Element';
import { Node } from './Node';

export function getPreviousElementSibling(node: Node): Element | null {
let parentNodes = node.parentNode && node.parentNode.childNodes;
if (!parentNodes) {
return null;
}

for (let i = parentNodes.indexOf(node) - 1; i >= 0; i--) {
let node = parentNodes[i];
if (node.nodeType === NodeType.ELEMENT_NODE) {
return node as Element;
}
}
return null;
}

export function getNextElementSibling(node: Node): Element | null {
let parentNodes = node.parentNode && node.parentNode.childNodes;
if (!parentNodes) {
return null;
}

for (let i = parentNodes.indexOf(node) + 1; i < parentNodes.length; i++) {
let node = parentNodes[i];
if (node.nodeType === NodeType.ELEMENT_NODE) {
return node as Element;
}
}
return null;
}
4 changes: 2 additions & 2 deletions web_compat_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,8 @@ This section highlights the DOM APIs that are implemented in WorkerDOM currently
| NodeList.keys() | ✖️ | |
| NodeList.length | ✖️ | |
| NodeList.values() | ✖️ | |
| NonDocumentTypeChildNode.nextElementSibling | | |
| NonDocumentTypeChildNode.previousElementSibling | | |
| NonDocumentTypeChildNode.nextElementSibling | | |
| NonDocumentTypeChildNode.previousElementSibling | ️✔| |
| ParentNode.append() | ✖️ | |
| ParentNode.childElementCount | ✔️ | |
| ParentNode.children | ✔️ | |
Expand Down

0 comments on commit c906bfc

Please sign in to comment.