', 'Set contents at selection (collapsed)');
// Insert in middle of paragraph
editor.setContent('
beforeafter
');
rng = editor.dom.createRng();
rng.setStart(editor.getBody().firstChild?.firstChild as Text, 'before'.length);
rng.setEnd(editor.getBody().firstChild?.firstChild as Text, 'before'.length);
editor.selection.setRng(rng);
editor.selection.setContent(' ');
LegacyUnit.equal(editor.getContent(), '
before after
', 'Set contents at selection (inside paragraph)');
// Check the caret is left in the correct position.
rng = editor.selection.getRng();
LegacyUnit.equalDom(rng.startContainer, editor.getBody().firstChild as HTMLParagraphElement, 'Selection start container');
LegacyUnit.equal(rng.startOffset, 2, 'Selection start offset');
LegacyUnit.equalDom(rng.endContainer, editor.getBody().firstChild as HTMLParagraphElement, 'Selection end container');
LegacyUnit.equal(rng.endOffset, 2, 'Selection end offset');
editor.setContent('
');
rng = editor.dom.createRng();
rng.setStart(editor.getBody().firstChild?.firstChild as Text, 0);
rng.setEnd(editor.getBody().firstChild?.firstChild as Text, 0);
editor.selection.setRng(rng);
LegacyUnit.equal(editor.selection.getStart().id, 'a', 'Selected contents (getStart, collapsed)');
LegacyUnit.equal(editor.selection.getEnd().id, 'a', 'Selected contents (getEnd, collapsed)');
});
it('getSelectedBlocks with collapsed selection between elements', () => {
const editor = hook.editor();
editor.setContent('
a
b
c
');
const rng = editor.dom.createRng();
rng.setStart(editor.getBody(), 1);
rng.setEnd(editor.getBody(), 1);
editor.selection.setRng(rng);
LegacyUnit.equal(editor.selection.getSelectedBlocks().length, 0, 'should return empty array');
});
it('getStart/getEnd on comment should return parent element', () => {
const editor = hook.editor();
editor.setContent('');
const rng = editor.dom.createRng();
rng.setStart(editor.getBody().firstChild as HTMLParagraphElement, 0);
rng.setEnd(editor.getBody().firstChild as HTMLParagraphElement, 0);
editor.selection.setRng(rng);
LegacyUnit.equal(editor.selection.getStart().nodeName, 'P', 'Node name should be paragraph');
LegacyUnit.equal(editor.selection.getStart(true).nodeName, 'P', 'Node name should be paragraph');
LegacyUnit.equal(editor.selection.getEnd().nodeName, 'P', 'Node name should be paragraph');
LegacyUnit.equal(editor.selection.getEnd(true).nodeName, 'P', 'Node name should be paragraph');
});
it('getBookmark/setBookmark (persistent)', () => {
const editor = hook.editor();
let bookmark: Bookmark;
let rng = editor.dom.createRng();
// Get persistent bookmark simple text selection
editor.setContent('
text
');
rng.setStart(editor.getBody().firstChild?.firstChild as Text, 1);
rng.setEnd(editor.getBody().firstChild?.firstChild as Text, 3);
editor.selection.setRng(rng);
bookmark = editor.selection.getBookmark();
LegacyUnit.equal(editor.getContent(), '
text
', 'Editor contents (text)');
editor.selection.moveToBookmark(bookmark);
LegacyUnit.equal(editor.selection.getContent(), 'ex', 'Selected contents (text)');
// Get persistent bookmark multiple elements text selection
editor.setContent('
text
\n
text
');
rng = editor.dom.createRng();
rng.setStart(editor.getBody().firstChild?.firstChild as Text, 1);
rng.setEnd(editor.getBody().lastChild?.firstChild as Text, 3);
editor.selection.setRng(rng);
bookmark = editor.selection.getBookmark();
LegacyUnit.equal(editor.getContent(), '
');
editor.selection.select(editor.dom.select('table')[0], true);
LegacyUnit.equal(editor.selection.getStart().id, 'a', 'Expand to text content 1 (start)');
LegacyUnit.equal(editor.selection.getEnd().id, 'b', 'Expand to text content 1 (end)');
});
it('select table text 2', () => {
const editor = hook.editor();
editor.setContent('
');
editor.selection.select(editor.dom.select('table')[0], true);
LegacyUnit.equal(editor.dom.getParent(editor.selection.getStart(), 'td')?.id, 'a', 'Expand to text content 2 (start)');
LegacyUnit.equal(editor.dom.getParent(editor.selection.getEnd(), 'td')?.id, 'b', 'Expand to text content 2 (end)');
});
it('getNode', () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.setContent('
span1 word span2 word span3
');
const p1 = editor.dom.get('p1') as HTMLParagraphElement;
const s1 = editor.dom.get('s1') as HTMLSpanElement;
const s2 = editor.dom.get('s2') as HTMLSpanElement;
const s3 = editor.dom.get('s3') as HTMLSpanElement;
rng.setStart(s1.firstChild as Text, 0);
rng.setEnd(s1.nextSibling as Text, 0);
editor.selection.setRng(rng);
LegacyUnit.equalDom(
editor.selection.getNode(),
s1,
'Detect selection ends immediately after node at start of paragraph.'
);
rng = editor.dom.createRng();
rng.setStart(s2.previousSibling as Text, (s2.previousSibling as Text).length);
rng.setEnd(s2.nextSibling as Text, 0);
editor.selection.setRng(rng);
LegacyUnit.equalDom(
editor.selection.getNode(),
s2,
'Detect selection immediately surrounds node in middle of paragraph.'
);
rng = editor.dom.createRng();
rng.setStart(s3.previousSibling as Text, (s3.previousSibling as Text).length);
rng.setEnd(s3.lastChild as Text, (s3.lastChild as Text).length);
editor.selection.setRng(rng);
LegacyUnit.equalDom(
editor.selection.getNode(),
s3,
'Detect selection starts immediately before node at end of paragraph.'
);
rng = editor.dom.createRng();
rng.setStart(s2.previousSibling as Text, (s2.previousSibling as Text).length);
rng.setEnd(s3.lastChild as Text, (s3.lastChild as Text).length);
editor.selection.setRng(rng);
LegacyUnit.equalDom(
editor.selection.getNode(),
p1,
'Detect selection wrapping multiple nodes does not collapse.'
);
});
it('normalize to text node from document', () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.setContent('
');
rng.setStart(editor.getBody().firstChild?.lastChild as Text, 0);
rng.setEnd(editor.getBody().firstChild?.lastChild as Text, 0);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.collapsed, true);
LegacyUnit.equal(CaretContainer.isCaretContainer(rng.startContainer), true);
});
it('normalize with contentEditable:false parent and contentEditable:true child element', () => {
const editor = hook.editor();
editor.setContent('
ab
');
LegacyUnit.setSelection(editor, 'em', 0);
editor.selection.normalize();
const rng = editor.selection.getRng();
LegacyUnit.equal(rng.collapsed, true);
LegacyUnit.equal(rng.startContainer.nodeType, 3);
LegacyUnit.equal((rng.startContainer as Text).data, 'b');
// WebKit is in some state state here, so lets restore it
rng.setStart(editor.getBody(), 0);
rng.setEnd(editor.getBody(), 0);
editor.selection.setRng(rng);
});
it('normalize to text node from body', () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.setContent('
';
LegacyUnit.setSelection(editor, 'b', 0);
editor.selection.normalize();
const rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeValue, 'b');
LegacyUnit.equal(rng.startOffset, 0);
});
it(`normalize lean left but don't walk out the parent block`, () => {
const editor = hook.editor();
editor.getBody().innerHTML = '
a
b
';
LegacyUnit.setSelection(editor, 'b', 0);
editor.selection.normalize();
const rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeValue, 'b');
LegacyUnit.equal(rng.startOffset, 0);
});
it('normalize lean left into empty inline elements when caret is before br', () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.getBody().innerHTML = '
';
rng.setStartBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
rng.setEndBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeName, 'B');
LegacyUnit.equal(rng.startOffset, 0);
});
it('normalize lean left from br into formatter caret container', () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.getBody().innerHTML = '
' + Zwsp.ZWSP + '
';
rng.setStartBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
rng.setEndBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeType, 3);
LegacyUnit.equal(rng.startOffset, 1);
});
it(`normalize doesn't lean left into empty inline elements if there is a br element after caret`, () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.getBody().innerHTML = '
';
rng.setStartBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
rng.setEndBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeName, 'P');
LegacyUnit.equal(rng.startOffset, 2);
});
it(`normalize doesn't lean left into empty inline elements if there is a br element before caret`, () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.getBody().innerHTML = '
';
rng.setStartBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
rng.setEndBefore(editor.getBody().firstChild?.lastChild as HTMLBRElement);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeName, 'P');
LegacyUnit.equal(rng.startOffset, 1);
});
it(`normalize doesn't move start/end if it's before/after table`, () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.getBody().innerHTML = '
X
';
rng.setStartBefore(editor.getBody().firstChild as HTMLTableElement);
rng.setEndAfter(editor.getBody().lastChild as HTMLTableElement);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeName, 'BODY');
LegacyUnit.equal(rng.startOffset, 0);
LegacyUnit.equal(rng.endContainer.nodeName, 'BODY');
LegacyUnit.equal(rng.endOffset, 1);
});
it('normalize after paragraph', () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.getBody().innerHTML = '
a
';
rng.setStartAfter(editor.getBody().firstChild as HTMLParagraphElement);
rng.setEndAfter(editor.getBody().lastChild as HTMLParagraphElement);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeName, '#text');
LegacyUnit.equal(rng.startOffset, 1);
LegacyUnit.equal(rng.endContainer.nodeName, '#text');
LegacyUnit.equal(rng.endOffset, 1);
});
it('normalize caret after trailing BR', () => {
const editor = hook.editor();
let rng = editor.dom.createRng();
editor.getBody().innerHTML = '
');
rng.setStart(editor.getBody().firstChild as HTMLParagraphElement, 3);
rng.setEnd(editor.getBody().firstChild as HTMLParagraphElement, 3);
editor.selection.setRng(rng);
editor.selection.normalize();
rng = editor.selection.getRng();
LegacyUnit.equal(rng.startContainer.nodeName, 'P', 'startContainer node name');
LegacyUnit.equal(rng.startOffset, 3, 'startContainer offset');
LegacyUnit.equal(rng.endContainer.nodeName, 'P', 'endContainer node name');
LegacyUnit.equal(rng.endOffset, 3, 'endOffset offset');
});
it('custom elements', () => {
const editor = hook.editor();
editor.setContent('testtest');
const rng = editor.dom.createRng();
rng.setStart(editor.getBody(), 0);
rng.setEnd(editor.getBody(), 2);
editor.selection.setRng(rng);
// custom2 has been specified as an inline element in the editor config so expect it be wrapped in a p tag
LegacyUnit.equal(editor.selection.getContent(), 'test\n
');
const textNode = editor.dom.select('em')[0].firstChild as Text;
editor.setContent('');
const rng = editor.dom.createRng();
rng.setStart(textNode, 0);
rng.setEnd(textNode, 1);
editor.selection.setRng(rng);
const curRng = editor.selection.getRng();
LegacyUnit.equal(curRng.startContainer.nodeName, 'P');
LegacyUnit.equal(curRng.startOffset, 0);
LegacyUnit.equal(curRng.endContainer.nodeName, 'P');
LegacyUnit.equal(curRng.endOffset, 0);
});
it('TINY-9001: Expanding a word expands the selection', () => {
const editor = hook.editor();
editor.setContent('A BCDE F');
TinySelections.setCursor(editor, [ 0, 0 ], 4);
editor.selection.expand();
TinyAssertions.assertSelection(editor, [ 0, 0 ], 2, [ 0, 0 ], 6);
});
it('TINY-9001: Expanding a word expands the selection, with a provided option', () => {
const editor = hook.editor();
editor.setContent('A BCDE F');
TinySelections.setCursor(editor, [ 0, 0 ], 4);
editor.selection.expand({ type: 'word' });
TinyAssertions.assertSelection(editor, [ 0, 0 ], 2, [ 0, 0 ], 6);
});
it('TINY-9259: Should be able to get selection range on hidden editors', () => {
const editor = hook.editor();
editor.focus();
editor.setContent('
ab
');
TinySelections.setCursor(editor, [ 0, 0 ], 1);
editor.getContainer().style.display = 'none';
TinyAssertions.assertCursor(editor, [ 0, 0 ], 1);
editor.getContainer().style.display = '';
});
/*
// TODO: Re-implement this test as a separate test if needed by destroying an editor etc
it('getRng should return null if win.document is not defined or null', () => {
const editor = hook.editor();
const win = editor.selection.win;
let rng = editor.dom.createRng();
editor.setContent('
',
editableRoot: false,
path: [ 0, 0 ],
offset: 0,
expected: false
}));
it('TINY-9462: isEditable on expanded range in editable root', testIsEditableSelection({
input: '
ab
cd
',
spath: [ 0, 0 ],
soffset: 1,
fpath: [ 1, 0 ],
foffset: 1,
expected: true
}));
it('TINY-9462: isEditable on expanded range in noneditable root', testIsEditableSelection({
input: '
ab
cd
',
editableRoot: false,
spath: [ 0, 0 ],
soffset: 1,
fpath: [ 1, 0 ],
foffset: 1,
expected: false
}));
it('TINY-9462: isEditable on expanded range where start is noneditable', testIsEditableSelection({
input: '
ab
cd
',
spath: [ 1, 0 ], // Shifted by one due to fake caret before P
soffset: 1,
fpath: [ 2, 0 ], // Shifted by one due to fake caret before P
foffset: 1,
expected: false
}));
it('TINY-9462: isEditable on expanded range where end is noneditable', testIsEditableSelection({
input: '