From d212611021ffdb3143f9ec1b22ef856752036feb Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 8 Jun 2022 23:50:44 +0200 Subject: [PATCH] #450@trivial: Continue on Range implementation. --- packages/happy-dom/src/nodes/node/INode.ts | 14 ++-- packages/happy-dom/src/nodes/node/Node.ts | 27 ++++--- .../happy-dom/src/nodes/node/NodeTypeEnum.ts | 11 +++ .../happy-dom/src/nodes/node/NodeUtility.ts | 39 ++++++++- packages/happy-dom/src/range/Range.ts | 79 +++++++++++++++++-- packages/happy-dom/test/range/Range.test.ts | 30 ++++++- 6 files changed, 172 insertions(+), 28 deletions(-) create mode 100644 packages/happy-dom/src/nodes/node/NodeTypeEnum.ts diff --git a/packages/happy-dom/src/nodes/node/INode.ts b/packages/happy-dom/src/nodes/node/INode.ts index bb0a0dc50..535ee71d4 100644 --- a/packages/happy-dom/src/nodes/node/INode.ts +++ b/packages/happy-dom/src/nodes/node/INode.ts @@ -2,14 +2,16 @@ import IEventTarget from '../../event/IEventTarget'; import IDocument from '../document/IDocument'; import IElement from '../element/IElement'; import INodeList from './INodeList'; +import NodeTypeEnum from './NodeTypeEnum'; export default interface INode extends IEventTarget { - readonly ELEMENT_NODE: number; - readonly TEXT_NODE: number; - readonly COMMENT_NODE: number; - readonly DOCUMENT_NODE: number; - readonly DOCUMENT_TYPE_NODE: number; - readonly DOCUMENT_FRAGMENT_NODE: number; + readonly ELEMENT_NODE: NodeTypeEnum; + readonly TEXT_NODE: NodeTypeEnum; + readonly COMMENT_NODE: NodeTypeEnum; + readonly DOCUMENT_NODE: NodeTypeEnum; + readonly DOCUMENT_TYPE_NODE: NodeTypeEnum; + readonly DOCUMENT_FRAGMENT_NODE: NodeTypeEnum; + readonly PROCESSING_INSTRUCTION_NODE: NodeTypeEnum; readonly ownerDocument: IDocument; readonly parentNode: INode; readonly parentElement: IElement; diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts index 54b36b8e7..02dae8c60 100644 --- a/packages/happy-dom/src/nodes/node/Node.ts +++ b/packages/happy-dom/src/nodes/node/Node.ts @@ -10,25 +10,28 @@ import IElement from '../element/IElement'; import IHTMLBaseElement from '../html-base-element/IHTMLBaseElement'; import INodeList from './INodeList'; import NodeListFactory from './NodeListFactory'; +import NodeTypeEnum from './NodeTypeEnum'; /** * Node. */ export default class Node extends EventTarget implements INode { // Public properties - public static readonly ELEMENT_NODE = 1; - public static readonly TEXT_NODE = 3; - public static readonly COMMENT_NODE = 8; - public static readonly DOCUMENT_NODE = 9; - public static readonly DOCUMENT_TYPE_NODE = 10; - public static readonly DOCUMENT_FRAGMENT_NODE = 11; + public static readonly ELEMENT_NODE = NodeTypeEnum.elementNode; + public static readonly TEXT_NODE = NodeTypeEnum.textNode; + public static readonly COMMENT_NODE = NodeTypeEnum.commentNode; + public static readonly DOCUMENT_NODE = NodeTypeEnum.documentNode; + public static readonly DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode; + public static readonly DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode; + public static readonly PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode; public static ownerDocument: IDocument = null; - public readonly ELEMENT_NODE = 1; - public readonly TEXT_NODE = 3; - public readonly COMMENT_NODE = 8; - public readonly DOCUMENT_NODE = 9; - public readonly DOCUMENT_TYPE_NODE = 10; - public readonly DOCUMENT_FRAGMENT_NODE = 11; + public readonly ELEMENT_NODE = NodeTypeEnum.elementNode; + public readonly TEXT_NODE = NodeTypeEnum.textNode; + public readonly COMMENT_NODE = NodeTypeEnum.commentNode; + public readonly DOCUMENT_NODE = NodeTypeEnum.documentNode; + public readonly DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode; + public readonly DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode; + public readonly PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode; public readonly ownerDocument: IDocument = null; public readonly parentNode: INode = null; public readonly nodeType: number; diff --git a/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts b/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts new file mode 100644 index 000000000..40e856e5c --- /dev/null +++ b/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts @@ -0,0 +1,11 @@ +enum NodeTypeEnum { + elementNode = 1, + textNode = 3, + commentNode = 8, + documentNode = 9, + documentTypeNode = 10, + documentFragmentNode = 11, + processingInstructionNode = 7 +} + +export default NodeTypeEnum; diff --git a/packages/happy-dom/src/nodes/node/NodeUtility.ts b/packages/happy-dom/src/nodes/node/NodeUtility.ts index f82ec3052..958712bbd 100644 --- a/packages/happy-dom/src/nodes/node/NodeUtility.ts +++ b/packages/happy-dom/src/nodes/node/NodeUtility.ts @@ -1,4 +1,7 @@ +import IText from '../text/IText'; +import IComment from '../comment/IComment'; import INode from './INode'; +import NodeTypeEnum from './NodeTypeEnum'; /** * Node utility. @@ -32,14 +35,44 @@ export default class NodeUtility { * @returns "true" if following. */ public static isFollowing(nodeA: INode, nodeB: INode): boolean { - let current: INode = nodeA.nextSibling; + if (nodeA === nodeB) { + return false; + } + + let current: INode = nodeB; + while (current) { - if (current === nodeB) { + const nextSibling = current.nextSibling; + + if (nextSibling === nodeA) { return true; } - const nextSibling = current.nextSibling; + current = nextSibling ? nextSibling : current.parentNode; } + return false; } + + /** + * Node length. + * + * @see https://dom.spec.whatwg.org/#concept-node-length + * @param node Node. + * @returns Node length. + */ + public static getNodeLength(node: INode): number { + switch (node.nodeType) { + case NodeTypeEnum.documentTypeNode: + return 0; + + case NodeTypeEnum.textNode: + case NodeTypeEnum.processingInstructionNode: + case NodeTypeEnum.commentNode: + return (node).data.length; + + default: + return node.childNodes.length; + } + } } diff --git a/packages/happy-dom/src/range/Range.ts b/packages/happy-dom/src/range/Range.ts index 4dd4d571d..6031d4edd 100644 --- a/packages/happy-dom/src/range/Range.ts +++ b/packages/happy-dom/src/range/Range.ts @@ -6,6 +6,8 @@ import RangeHowEnum from './RangeHowEnum'; import DOMException from '../exception/DOMException'; import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum'; import RangeUtility from './RangeUtility'; +import NodeTypeEnum from '../nodes/node/NodeTypeEnum'; +import NodeUtility from '../nodes/node/NodeUtility'; /** * Range. @@ -161,12 +163,47 @@ export default class Range { /** * Returns -1, 0, or 1 depending on whether the referenceNode is before, the same as, or after the Range. * - * @param _referenceNode Reference node. - * @param [_offset=0] Offset. + * @param referenceNode Reference node. + * @param offset Offset. * @returns -1,0, or 1. */ - public comparePoint(_referenceNode: INode, _offset = 0): number { - // TODO: Implement + public comparePoint(referenceNode: INode, offset): number { + if (referenceNode.ownerDocument !== this._ownerDocument) { + throw new DOMException( + `The two Ranges are not in the same tree.`, + DOMExceptionNameEnum.wrongDocumentError + ); + } + + if (referenceNode.nodeType === NodeTypeEnum.documentTypeNode) { + throw new DOMException( + `DocumentType Node can't be used as boundary point.`, + DOMExceptionNameEnum.invalidNodeTypeError + ); + } + + if (offset > NodeUtility.getNodeLength(referenceNode)) { + throw new DOMException(`'Offset out of bound.`, DOMExceptionNameEnum.indexSizeError); + } + + const boundaryPoint = { node: referenceNode, offset }; + + if ( + RangeUtility.compareBoundaryPointsPosition(boundaryPoint, { + node: this.startContainer, + offset: this.startOffset + }) === -1 + ) { + return -1; + } else if ( + RangeUtility.compareBoundaryPointsPosition(boundaryPoint, { + node: this.endContainer, + offset: this.endOffset + }) === 1 + ) { + return 1; + } + return 0; } @@ -324,7 +361,7 @@ export default class Range { ); } if ( - endNode.nodeType !== endNode.TEXT_NODE && + endNode.nodeType !== NodeTypeEnum.textNode && endOffset > 0 && endNode.childNodes.length < endOffset ) { @@ -332,6 +369,21 @@ export default class Range { `Failed to execute 'setEnd' on 'Range': There is no child at offset ${endOffset}.` ); } + if ( + endNode.ownerDocument !== this._ownerDocument || + RangeUtility.compareBoundaryPointsPosition( + { + node: endNode, + offset: endOffset + }, + { + node: this.startContainer, + offset: this.startOffset + } + ) === -1 + ) { + this.setStart(endNode, endOffset); + } (this.endContainer) = endNode; (this.endOffset) = endOffset; } @@ -349,7 +401,7 @@ export default class Range { ); } if ( - startNode.nodeType !== startNode.TEXT_NODE && + startNode.nodeType !== NodeTypeEnum.textNode && startOffset > 0 && startNode.childNodes.length < startOffset ) { @@ -357,6 +409,21 @@ export default class Range { `Failed to execute 'setStart' on 'Range': There is no child at offset ${startOffset}.` ); } + if ( + startNode.ownerDocument !== this._ownerDocument || + RangeUtility.compareBoundaryPointsPosition( + { + node: startNode, + offset: startOffset + }, + { + node: this.endContainer, + offset: this.endOffset + } + ) === 1 + ) { + this.setEnd(startNode, startOffset); + } (this.startContainer) = startNode; (this.startOffset) = startOffset; } diff --git a/packages/happy-dom/test/range/Range.test.ts b/packages/happy-dom/test/range/Range.test.ts index 7b899b8c2..3b0418475 100644 --- a/packages/happy-dom/test/range/Range.test.ts +++ b/packages/happy-dom/test/range/Range.test.ts @@ -153,7 +153,7 @@ describe('Range', () => { range.collapse(true); expect(range.startContainer === span.childNodes[0]).toBe(true); - expect(range.endContainer === span.childNodes[0]).toBe(true); + expect(range.endContainer === span2.childNodes[0]).toBe(true); expect(range.startOffset).toBe(1); expect(range.endOffset).toBe(1); expect(range.collapsed).toBe(true); @@ -223,4 +223,32 @@ describe('Range', () => { expect(range.compareBoundaryPoints(Range.START_TO_END, sourceRange)).toBe(0); }); }); + + describe('comparePoint()', () => { + it('Returns 1 when referenceNode is after range.', () => { + const container = document.createElement('div'); + + container.innerHTML = ` +
This is the Range 1 Content
+
This is the Range 2 Content
+ `; + + range.selectNode(container.children[0]); + + expect(range.comparePoint(container.children[1], 0)).toBe(1); + }); + + it('Returns -1 when referenceNode is before range.', () => { + const container = document.createElement('div'); + + container.innerHTML = ` +
This is the Range 1 Content
+
This is the Range 2 Content
+ `; + + range.selectNode(container.children[1]); + + expect(range.comparePoint(container.children[0], 0)).toBe(-1); + }); + }); });