Skip to content

Commit

Permalink
Adjust tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante committed Feb 19, 2024
1 parent 32072b8 commit c1b9715
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 17 deletions.
46 changes: 46 additions & 0 deletions index.test-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* `index` means the character-index of a node relative to its main element,
* regardless of other HTML elements in between.
* Example: "Hello <i>World<b>!</b></i>"
* The tag `b` and the exclamation mark are at index 11
*/
export function getIndex(container: Node & ParentNode, target: Node): number {
let index = 0;
do {
while (target.previousSibling) {
index += target.previousSibling.textContent!.length;
target = target.previousSibling;
}

target = target.parentElement!;
} while (target && target !== container);

return index;
}

// Get text node at an character index of an Element.
// Only needed because .setStart only accepts a character index relative to a single TextNode
export function getNodeAtIndex(container: Node, index: number): [Node, number] {
let relativeIndex = index;
let cursor = container;
while (cursor?.firstChild) {
cursor = cursor.firstChild;
while (cursor && cursor.textContent!.length < relativeIndex) {
relativeIndex -= cursor.textContent!.length;
if (cursor.nextSibling) {
cursor = cursor.nextSibling;
}
}
}

return [cursor, relativeIndex];
}

// Get Range that starts/ends across multiple/nested TextNodes of an Element.
// Only needed because .setStart only accepts a character index relative to a single TextNode
export function getSmartIndexRange(node: Node & ParentNode, start: number, end: number): Range {
const range = document.createRange();
range.setStart(...getNodeAtIndex(node, start));
range.setEnd(...getNodeAtIndex(node, end));
return range;
}
49 changes: 33 additions & 16 deletions index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import test from 'tape';
import {getSmartIndexRange} from './index.test-utils.js';
import {
insertTextIntoField,
setFieldText,
replaceFieldText,
wrapFieldSelection,
getFieldSelection,
_TEST_ONLY_withFocus,
} from './index.js';

type NativeField = HTMLTextAreaElement | HTMLInputElement;
Expand All @@ -13,10 +15,12 @@ function isNativeField(field: HTMLElement): field is NativeField {
return field instanceof HTMLTextAreaElement || field instanceof HTMLInputElement;
}

function getField(type: 'contenteditable', state?: string): HTMLElement;
function getField(type: 'textarea' | 'input', state?: string): NativeField;
function getField(type: string, state?: string): HTMLElement;
function getField(type: string, state = '|') {
const field = document.createElement(type);
const field = document.createElement(type === 'contenteditable' ? 'div' : type);
document.body.append(field);

if (type === 'contenteditable') {
field.setAttribute('contenteditable', 'true');
}
Expand All @@ -30,25 +34,34 @@ function getField(type: string, state = '|') {
field.selectionStart = selectionStart;
field.selectionEnd = selectionEnd;
} else {
field.textContent = value;
const range = document.createRange();
range.setStart(field.firstChild!, selectionStart);
range.setEnd(field.firstChild!, selectionEnd);
const selection = window.getSelection()!;
selection.removeAllRanges();
selection.addRange(range);
field.append(value);

// This changes the focus of the whole document, so make sure to reset it afterwards to avoid side effects while testing
_TEST_ONLY_withFocus(field, () => {
const selection = window.getSelection()!;
selection.removeAllRanges();
selection.addRange(getSmartIndexRange(field, selectionStart, selectionEnd));
});
}

document.body.append(field);
return field;
}

function getState(field: HTMLElement) {
const {value, selectionStart, selectionEnd} = isNativeField(field) ? field : {
function getSimplifiedFieldState(field: HTMLElement) {
if (isNativeField(field)) {
return field;
}

const selection = getSelection()!;
return {
value: field.textContent!,
selectionStart: 0,
selectionEnd: 0,
selectionStart: selection.anchorOffset,
selectionEnd: selection.anchorOffset + selection.toString().length,
};
}

function getState(field: HTMLElement) {
const {value, selectionStart, selectionEnd} = getSimplifiedFieldState(field);
if (selectionStart === selectionEnd) {
return value.slice(0, selectionStart!) + '|' + value.slice(selectionStart!);
}
Expand All @@ -62,8 +75,8 @@ function getState(field: HTMLElement) {
);
}

for (const type of ['textarea', 'input'] as const) {
test(`${type}: test harness test`, t => {
for (const type of ['textarea', 'input', 'contenteditable'] as const) {
test(`${type}: harness test`, t => {
t.equal(getState(getField(type)), '|');
t.equal(getState(getField(type, '|')), '|');
t.equal(getState(getField(type, 'A|')), 'A|');
Expand Down Expand Up @@ -173,6 +186,10 @@ for (const type of ['textarea', 'input'] as const) {
t.end();
});

if (type === 'contenteditable') {
continue;
}

test(`${type}: replace() supports strings`, t => {
const field = getField(type, 'ABACUS');
replaceFieldText(field, 'A', 'THE ');
Expand Down
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,5 @@ const textFieldEdit = {
getSelection: getFieldSelection,
} as const;
export default textFieldEdit;

export {withFocus as _TEST_ONLY_withFocus};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"test:blink": "browserify -p esmify index.test.js | tape-run --browser chrome",
"test:gecko": "browserify -p esmify index.test.js | tape-run --browser firefox",
"test:lint": "xo",
"watch": "tsc --watch"
"watch": "tsc --watch --noEmitOnError false"
},
"xo": {
"envs": [
Expand Down

0 comments on commit c1b9715

Please sign in to comment.