/**
* @jest-environment jsdom
*/
import { afterEach, beforeEach, describe, expect, it } from '@jest/globals';
import { Squire } from '../source/Editor';
document.body.innerHTML = `
`;
let editor;
describe('Squire RTE', () => {
beforeEach(() => {
const squireContainer = document.getElementById('squire')!;
editor = new Squire(squireContainer, {
sanitizeToDOMFragment(html) {
const doc = new DOMParser().parseFromString(html, 'text/html');
const frag = doc.createDocumentFragment();
const body = doc.body;
while (body.firstChild) {
frag.appendChild(body.firstChild);
}
return document.importNode(frag, true);
},
});
});
function selectAll(editor) {
document.getSelection().removeAllRanges();
const range = document.createRange();
range.setStart(editor._root.childNodes.item(0), 0);
range.setEnd(
editor._root.childNodes.item(0),
editor._root.childNodes.item(0).childNodes.length,
);
editor.setSelection(range);
}
describe('hasFormat', () => {
let startHTML;
beforeEach(() => {
startHTML = '
one two three four five
';
editor.setHTML(startHTML);
});
it('returns false when range not touching format', () => {
const range = document.createRange();
range.setStart(editor._root.childNodes.item(0), 0);
range.setEnd(editor._root.childNodes.item(0), 1);
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(false);
});
it('returns false when range inside other format', () => {
const range = document.createRange();
range.setStart(document.querySelector('i').childNodes[0], 1);
range.setEnd(document.querySelector('i').childNodes[0], 2);
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(false);
});
it('returns false when range covers anything outside format', () => {
const range = document.createRange();
range.setStart(document.querySelector('b').previousSibling, 2);
range.setEnd(document.querySelector('b').childNodes[0], 8);
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(false);
});
it('returns true when range inside format', () => {
const range = document.createRange();
range.setStart(document.querySelector('b').childNodes[0], 2);
range.setEnd(document.querySelector('b').childNodes[0], 8);
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(true);
});
it('returns true when range covers start of format', () => {
const range = document.createRange();
range.setStartBefore(document.querySelector('b'));
range.setEnd(document.querySelector('b').childNodes[0], 8);
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(true);
});
it('returns true when range covers start of format, even in weird cases', () => {
const range = document.createRange();
const prev = document.querySelector('b').previousSibling as Text;
range.setStart(prev, prev.length);
range.setEnd(document.querySelector('b').childNodes[0], 8);
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(true);
});
it('returns true when range covers end of format', () => {
const range = document.createRange();
range.setStart(document.querySelector('b').childNodes[0], 2);
range.setEndAfter(document.querySelector('b'));
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(true);
});
it('returns true when range covers end of format, even in weird cases', () => {
const range = document.createRange();
range.setStart(document.querySelector('b').childNodes[0], 2);
const next = document.querySelector('b').nextSibling;
range.setEnd(next, 0);
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(true);
});
it('returns true when range covers all of format', () => {
const range = document.createRange();
range.setStartBefore(document.querySelector('b'));
range.setEndAfter(document.querySelector('b'));
editor.setSelection(range);
expect(editor.hasFormat('b')).toBe(true);
});
});
describe('removeAllFormatting', () => {
// Trivial cases
it('removes inline styles', () => {
const startHTML =
'
one two three four five
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
selectAll(editor);
editor.removeAllFormatting();
expect(editor._root.innerHTML).toBe(
'
one two three four five
',
);
});
it('removes block styles', () => {
const startHTML =
'
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
selectAll(editor);
editor.removeAllFormatting();
const expectedHTML =
'
one
two
three
four
five
';
expect(editor._root.innerHTML).toBe(expectedHTML);
});
// Potential bugs
// TODO: more analysis of this; this could just be an off-by-one in the test
it('removes styles that begin inside the range', () => {
const startHTML = '
one two three four five
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
range.setStart(
editor._root
.getElementsByTagName('i')
.item(0)
.childNodes.item(0),
3,
);
range.setEnd(
editor._root
.getElementsByTagName('i')
.item(0)
.childNodes.item(0),
8,
);
editor.removeAllFormatting(range);
expect(editor._root.innerHTML).toBe(
'
one two three four five
',
);
});
it('removes styles that end inside the range', () => {
const startHTML = '
one two three four five
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
range.setStart(
document.getElementsByTagName('i').item(0).childNodes.item(0),
13,
);
range.setEnd(
editor._root.childNodes.item(0),
editor._root.childNodes.item(0).childNodes.length,
);
editor.removeAllFormatting(range);
expect(editor._root.innerHTML).toBe(
'
one two three four five
',
);
});
it('removes styles enclosed by the range', () => {
const startHTML = '
one two three four five
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
range.setStart(editor._root.childNodes.item(0), 0);
range.setEnd(
editor._root.childNodes.item(0),
editor._root.childNodes.item(0).childNodes.length,
);
editor.removeAllFormatting(range);
expect(editor._root.innerHTML).toBe(
'
one two three four five
',
);
});
it('removes styles enclosing the range', () => {
const startHTML = '
one two three four five
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
range.setStart(
document.getElementsByTagName('i').item(0).childNodes.item(0),
4,
);
range.setEnd(
document.getElementsByTagName('i').item(0).childNodes.item(0),
18,
);
editor.removeAllFormatting(range);
expect(editor._root.innerHTML).toBe(
'
one two three four five
',
);
});
it('removes nested styles and closes tags correctly', () => {
const startHTML =
'
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
range.setStart(document.getElementsByTagName('td').item(1), 0);
range.setEnd(
document.getElementsByTagName('td').item(2),
document.getElementsByTagName('td').item(2).childNodes.length,
);
editor.removeAllFormatting(range);
expect(editor._root.innerHTML).toBe(
'
two
three
',
);
});
});
describe('getPath', () => {
let startHTML;
beforeEach(() => {
startHTML = '
one two three four five
';
editor.setHTML(startHTML);
const range = document.createRange();
range.setStart(editor._root.childNodes.item(0), 0);
range.setEnd(editor._root.childNodes.item(0), 1);
editor.setSelection(range);
});
it('returns the path to the selection', () => {
const range = document.createRange();
range.setStart(
editor._root.childNodes.item(0).childNodes.item(1),
0,
);
range.setEnd(editor._root.childNodes.item(0).childNodes.item(1), 0);
editor.setSelection(range);
//Manually tell it to update the path
editor._updatePath(range);
expect(editor.getPath()).toBe('DIV>B');
});
it('includes id in the path', () => {
editor.setHTML('
Text
');
expect(editor.getPath()).toBe('DIV#spanId');
});
it('includes class name in the path', () => {
editor.setHTML('
Text
');
expect(editor.getPath()).toBe('DIV.myClass');
});
it('includes all class names in the path', () => {
editor.setHTML('
Text
');
expect(editor.getPath()).toBe('DIV.myClass.myClass2.myClass3');
});
it('includes direction in the path', () => {
editor.setHTML('
Text
');
expect(editor.getPath()).toBe('DIV[dir=rtl]');
});
it('includes highlight value in the path', () => {
editor.setHTML(
'
Text
',
);
expect(editor.getPath()).toBe(
'DIV.highlight[backgroundColor=rgb(255,0,0)]',
);
});
it('includes color value in the path', () => {
editor.setHTML(
'
Text
',
);
expect(editor.getPath()).toBe('DIV.color[color=rgb(255,0,0)]');
});
it('includes font family value in the path', () => {
editor.setHTML(
'
Text
',
);
expect(editor.getPath()).toBe(
'DIV.font[fontFamily=Arial,sans-serif]',
);
});
it('includes font size value in the path', () => {
editor.setHTML(
'
Text
',
);
expect(editor.getPath()).toBe('DIV.size[fontSize=12pt]');
});
it('is (selection) when the selection is a range', () => {
const range = document.createRange();
range.setStart(
editor._root.childNodes.item(0).childNodes.item(0) as Node,
0,
);
range.setEnd(
editor._root.childNodes.item(0).childNodes.item(3) as Node,
0,
);
editor.setSelection(range);
//Manually tell it to update the path
editor._updatePath(range);
expect(editor.getPath()).toBe('(selection)');
});
});
describe('multi-level lists', () => {
it('increases list indentation', () => {
const startHTML =
'
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
const textNode = document.getElementsByTagName('li').item(1)
.childNodes[0].childNodes[0];
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
editor.setSelection(range);
editor.increaseListLevel();
expect(editor._root.innerHTML).toBe(
'
',
);
});
it('increases list indentation 2', () => {
const startHTML =
'
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
const textNode = document.getElementsByTagName('li').item(1)
.childNodes[0].childNodes[0];
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
editor.setSelection(range);
editor.increaseListLevel();
editor.increaseListLevel();
expect(editor._root.innerHTML).toBe(
'
',
);
});
it('decreases list indentation', () => {
const startHTML =
'
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
const textNode = document.getElementsByTagName('li').item(1)
.childNodes[0].childNodes[0];
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
editor.setSelection(range);
editor.decreaseListLevel();
expect(editor._root.innerHTML).toBe(
'
',
);
});
it('decreases list indentation 2', () => {
const startHTML =
'
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
const textNode = document.getElementsByTagName('li').item(1)
.childNodes[0].childNodes[0];
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
editor.setSelection(range);
editor.decreaseListLevel();
editor.decreaseListLevel();
expect(editor._root.innerHTML).toBe(
'
',
);
});
it('removes lists', () => {
const startHTML =
'
';
editor.setHTML(startHTML);
expect(editor._root.innerHTML).toBe(startHTML);
const range = document.createRange();
const textNode = document.getElementsByTagName('li').item(1)
.childNodes[0].childNodes[0];
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
editor.setSelection(range);
editor.removeList();
expect(editor._root.innerHTML).toBe(
'
bar
',
);
});
});
describe('insertHTML', () => {
it('fix CF_HTML incomplete table', () => {
editor.insertHTML(
'
',
);
expect(editor.getHTML()).toEqual(
expect.stringMatching(
'
',
),
);
editor.setHTML('');
editor.insertHTML(
'
',
);
expect(editor.getHTML()).toEqual(
expect.stringMatching(
'
',
),
);
});
const LINK_MAP = {
'dewdw@fre.fr': 'mailto:dewdw@fre.fr',
'dew@free.fr?dew=dew': 'mailto:dew@free.fr?dew=dew',
'dew@free.fr?subject=dew': 'mailto:dew@free.fr?subject=dew',
'test@example.com?subject=foo&body=bar':
'mailto:test@example.com?subject=foo&body=bar',
'dew@fre.fr dewdwe @dew': 'mailto:dew@fre.fr',
'http://free.fr': 'http://free.fr/',
'http://google.com': 'http://google.com/',
'https://google.com': 'https://google.com/',
'https://www.google.com': 'https://www.google.com/',
'https://www.google.com/': 'https://www.google.com/',
'https://google.com/?': 'https://google.com/',
'https://google.com?': 'https://google.com/',
'https://google.com?a': 'https://google.com/?a',
'https://google.com?a=': 'https://google.com/?a=',
'https://google.com?a=b': 'https://google.com/?a=b',
'https://google.com?a=b?': 'https://google.com/?a=b',
'https://google.com?a=b&': 'https://google.com/?a=b',
'https://google.com?a=b&c': 'https://google.com/?a=b&c',
'https://google.com?a=b&c=': 'https://google.com/?a=b&c=',
'https://google.com?a=b&c=d': 'https://google.com/?a=b&c=d',
'https://google.com?a=b&c=d?': 'https://google.com/?a=b&c=d',
'https://google.com?a=b&c=d&': 'https://google.com/?a=b&c=d',
'https://google.com?a=b&c=d&e=': 'https://google.com/?a=b&c=d&e=',
'https://google.com?a=b&c=d&e=f': 'https://google.com/?a=b&c=d&e=f',
};
Object.keys(LINK_MAP).forEach((input) => {
it('should auto convert links to anchor: ' + input, () => {
editor.insertHTML(input);
const link = document.querySelector('a');
expect(link.href).toBe(LINK_MAP[input]);
editor.setHTML('');
});
});
it('should auto convert a part of the link to an anchor', () => {
editor.insertHTML(`
dew@fre.fr dewdwe @dew
`);
const link = document.querySelector('a');
expect(link.textContent).toBe('dew@fre.fr');
expect(link.href).toBe('mailto:dew@fre.fr');
editor.setHTML('');
});
it('should not auto convert non links to anchor', () => {
editor.insertHTML(`
dewdwe @dew
deww.de
monique.fre
google.com
`);
const link = document.querySelector('a');
expect(link).toBe(null);
editor.setHTML('');
});
});
afterEach(() => {
editor = null;
document.body.innerHTML = `
`;
});
});