Skip to content

Commit

Permalink
#450@trivial: Continue on Range implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
capricorn86 committed May 27, 2022
1 parent d065030 commit d3da6e2
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 40 deletions.
129 changes: 89 additions & 40 deletions packages/happy-dom/src/range/Range.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
(<INode>this.endContainer) = this.startContainer;
(<number>this.endOffset) = this.startOffset;
} else {
(<INode>this.startContainer) = this.endContainer;
(<number>this.startOffset) = this.endOffset;
}
return (<typeof Range>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;
}

/**
Expand Down Expand Up @@ -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 {
(<INode>this.endContainer) = endNode;
(<number>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 {
(<INode>this.startContainer) = startNode;
(<number>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);
}

/**
Expand Down
139 changes: 139 additions & 0 deletions 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 <u>world</u>!`;
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 <u>world</u>!`;
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 <u>world</u>!`;
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 <u>world</u>!`;

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 <u>world</u>!`;

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);
});
});
});

0 comments on commit d3da6e2

Please sign in to comment.