From d3da6e26b97dec6a54320b39d83e2010aef1629f Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 27 May 2022 17:07:51 +0200 Subject: [PATCH] #450@trivial: Continue on Range implementation. --- packages/happy-dom/src/range/Range.ts | 129 ++++++++++++------ packages/happy-dom/test/range/Range.test.ts | 139 ++++++++++++++++++++ 2 files changed, 228 insertions(+), 40 deletions(-) create mode 100644 packages/happy-dom/test/range/Range.test.ts diff --git a/packages/happy-dom/src/range/Range.ts b/packages/happy-dom/src/range/Range.ts index adcfa2281..992563833 100644 --- a/packages/happy-dom/src/range/Range.ts +++ b/packages/happy-dom/src/range/Range.ts @@ -12,14 +12,14 @@ import RangeHowEnum from './RangeHowEnum'; */ export default class Range { public static _ownerDocument: IDocument = null; - public static readonly END_TO_END = RangeHowEnum.endToEnd; - public static readonly END_TO_START = RangeHowEnum.endToStart; - public static readonly START_TO_END = RangeHowEnum.startToEnd; - public static readonly START_TO_START = RangeHowEnum.startToStart; - public readonly END_TO_END = RangeHowEnum.endToEnd; - public readonly END_TO_START = RangeHowEnum.endToStart; - public readonly START_TO_END = RangeHowEnum.startToEnd; - public readonly START_TO_START = RangeHowEnum.startToStart; + public static readonly END_TO_END: number = RangeHowEnum.endToEnd; + public static readonly END_TO_START: number = RangeHowEnum.endToStart; + public static readonly START_TO_END: number = RangeHowEnum.startToEnd; + public static readonly START_TO_START: number = RangeHowEnum.startToStart; + public readonly END_TO_END: number = RangeHowEnum.endToEnd; + public readonly END_TO_START: number = RangeHowEnum.endToStart; + public readonly START_TO_END: number = RangeHowEnum.startToEnd; + public readonly START_TO_START: number = RangeHowEnum.startToStart; public readonly startOffset: number = 0; public readonly endOffset: number = 0; public readonly startContainer: INode = null; @@ -52,28 +52,51 @@ export default class Range { return this.startContainer; } - const startAncestors = []; const endAncestors = []; - let parent = this.startContainer; + let parent = this.endContainer; - while (parent !== null) { - startAncestors.push(parent); + while (parent) { + endAncestors.push(parent); parent = parent.parentNode; } - parent = this.endContainer; + parent = this.startContainer; - while (parent !== null) { - endAncestors.push(parent); + while (parent) { + if (endAncestors.includes(parent)) { + return parent; + } parent = parent.parentNode; } - for (const ancestor of startAncestors) { - if (endAncestors.includes(ancestor)) { - return ancestor; - } + return this.endContainer || this.startContainer; + } + + /** + * Returns -1, 0, or 1 depending on whether the referenceNode is before, the same as, or after the Range. + * + * @param toStart A boolean value: true collapses the Range to its start, false to its end. If omitted, it defaults to false. + */ + public collapse(toStart = false): void { + if (toStart) { + (this.endContainer) = this.startContainer; + (this.endOffset) = this.startOffset; + } else { + (this.startContainer) = this.endContainer; + (this.startOffset) = this.endOffset; } - return (this.constructor)._ownerDocument; + } + + /** + * Compares the boundary points of the Range with those of another range. + * + * @param _how How. + * @param _sourceRange Range. + * @returns A number, -1, 0, or 1, indicating whether the corresponding boundary-point of the Range is respectively before, equal to, or after the corresponding boundary-point of sourceRange. + */ + public compareBoundaryPoints(_how: RangeHowEnum, _sourceRange: Range): number { + // TODO: Implement + return 0; } /** @@ -220,57 +243,83 @@ export default class Range { /** * Sets the end position of a Range to be located at the given offset into the specified node x. * - * @param _endNode End node. - * @param _endOffset End offset. + * @param endNode End node. + * @param endOffset End offset. */ - public setEnd(_endNode: INode, _endOffset = 0): void { - // TODO: Implement + public setEnd(endNode: INode, endOffset = 0): void { + (this.endContainer) = endNode; + (this.endOffset) = endOffset; } /** * Sets the start position of a Range. * - * @param _startNode Start node. - * @param _startOffset Start offset. + * @param startNode Start node. + * @param startOffset Start offset. */ - public setStart(_startNode: INode, _startOffset = 0): void { - // TODO: Implement + public setStart(startNode: INode, startOffset = 0): void { + (this.startContainer) = startNode; + (this.startOffset) = startOffset; } /** * Sets the end position of a Range relative to another Node. * - * @param _referenceNode Reference node. + * @param referenceNode Reference node. */ - public setEndAfter(_referenceNode: INode): void { - // TODO: Implement + public setEndAfter(referenceNode: INode): void { + const sibling = referenceNode.nextSibling; + if (!sibling) { + throw new Error( + 'Failed to set range end. "referenceNode" does not have any nodes after itself.' + ); + } + this.setEnd(sibling); } /** * Sets the end position of a Range relative to another Node. * - * @param _referenceNode Reference node. + * @param referenceNode Reference node. */ - public setEndBefore(_referenceNode: INode): void { - // TODO: Implement + public setEndBefore(referenceNode: INode): void { + const sibling = referenceNode.previousSibling; + if (!sibling) { + throw new Error( + 'Failed to set range end. "referenceNode" does not have any nodes before itself.' + ); + } + this.setEnd(sibling); } /** * Sets the start position of a Range relative to a Node. * - * @param _referenceNode Reference node. + * @param referenceNode Reference node. */ - public setStartAfter(_referenceNode: INode): void { - // TODO: Implement + public setStartAfter(referenceNode: INode): void { + const sibling = referenceNode.nextSibling; + if (!sibling) { + throw new Error( + 'Failed to set range start. "referenceNode" does not have any nodes after itself.' + ); + } + this.setStart(sibling); } /** * Sets the start position of a Range relative to another Node. * - * @param _referenceNode Reference node. + * @param referenceNode Reference node. */ - public setStartBefore(_referenceNode: INode): void { - // TODO: Implement + public setStartBefore(referenceNode: INode): void { + const sibling = referenceNode.previousSibling; + if (!sibling) { + throw new Error( + 'Failed to set range start. "referenceNode" does not have any nodes before itself.' + ); + } + this.setStart(sibling); } /** diff --git a/packages/happy-dom/test/range/Range.test.ts b/packages/happy-dom/test/range/Range.test.ts new file mode 100644 index 000000000..c5a69f7b0 --- /dev/null +++ b/packages/happy-dom/test/range/Range.test.ts @@ -0,0 +1,139 @@ +import Window from '../../src/window/Window'; +import IWindow from '../../src/window/IWindow'; +import IDocument from '../../src/nodes/document/IDocument'; +import Range from '../../src/range/Range'; + +describe('Range', () => { + let window: IWindow; + let document: IDocument; + let range: Range; + + beforeEach(() => { + window = new Window(); + document = window.document; + Range._ownerDocument = document; + range = new Range(); + }); + + describe('get collapsed()', () => { + it('Returns true when start and end container are the same and has the same offset.', () => { + const container = document.createElement('div'); + container.innerHTML = `Hello world!`; + range.setStart(container, 0); + range.setEnd(container, 0); + + expect(range.collapsed).toBe(true); + }); + + it('Returns false when start and end container are the same, but does not have the same offset.', () => { + const container = document.createElement('div'); + container.innerHTML = `Hello world!`; + range.setStart(container, 0); + range.setEnd(container, 1); + + expect(range.collapsed).toBe(false); + }); + + it('Returns false when start and end container are not the same, but have the same offset.', () => { + const container = document.createElement('div'); + container.innerHTML = `Hello world!`; + range.setStart(container, 0); + range.setEnd(container.children[0], 0); + + expect(range.collapsed).toBe(false); + }); + }); + + describe('get commonAncestorContainer()', () => { + it('Returns ancestor parent container when end container is set to a child of start container.', () => { + const container = document.createElement('div'); + + range.setStart(container, 0); + range.setEnd(container.children[0], 0); + + expect(range.commonAncestorContainer === container).toBe(true); + }); + + it('Returns ancestor parent container when start and end container are the same.', () => { + const container = document.createElement('div'); + + range.setStart(container, 0); + range.setEnd(container, 0); + + expect(range.commonAncestorContainer === container).toBe(true); + }); + + it('Returns end container when start and end container does not have a common ancestor.', () => { + const container = document.createElement('div'); + const container2 = document.createElement('div'); + + range.setStart(container, 0); + range.setEnd(container2, 0); + + expect(range.commonAncestorContainer === container2).toBe(true); + }); + + it('Returns common parent container.', () => { + const container = document.createElement('div'); + const span = document.createElement('span'); + const span2 = document.createElement('span'); + + container.appendChild(span); + container.appendChild(span2); + + range.setStart(span, 0); + range.setEnd(span2, 0); + + expect(range.commonAncestorContainer === container).toBe(true); + }); + + it('Returns body when it is the common parent container.', () => { + const span = document.createElement('span'); + const span2 = document.createElement('span'); + + document.body.appendChild(span); + document.body.appendChild(span2); + + range.setStart(span, 0); + range.setEnd(span2, 0); + + expect(range.commonAncestorContainer === document.body).toBe(true); + }); + }); + + describe('collapse()', () => { + it('Collapses the Range to the end container by default.', () => { + const container = document.createElement('div'); + + container.innerHTML = `Hello world!`; + + range.setStart(container, 2); + range.setEnd(container.children[0], 1); + + range.collapse(); + + expect(range.startContainer === container.children[0]).toBe(true); + expect(range.endContainer === container.children[0]).toBe(true); + expect(range.startOffset).toBe(1); + expect(range.endOffset).toBe(1); + expect(range.collapsed).toBe(true); + }); + + it('Collapses the Range to the end container, even though the toStart parameter is set to true, when the end container is a child of the start container.', () => { + const container = document.createElement('div'); + + container.innerHTML = `Hello world!`; + + range.setStart(container, 2); + range.setEnd(container.children[0], 1); + + range.collapse(true); + + expect(range.startContainer === container.children[0]).toBe(true); + expect(range.endContainer === container.children[0]).toBe(true); + expect(range.startOffset).toBe(1); + expect(range.endOffset).toBe(1); + expect(range.collapsed).toBe(true); + }); + }); +});