Your IP : 3.145.202.104


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/js/ui/bbcode/model/test/nodes/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/js/ui/bbcode/model/test/nodes/nodes.test.js

import { BBCodeNode } from '../../src/nodes/node';
import { BBCodeScheme } from '../../src/scheme/bbcode-scheme';
import { BBCodeTagScheme } from '../../src/scheme/node-schemes/tag-scheme';
import { DefaultBBCodeScheme } from '../../src/scheme/default-bbcode-scheme';

describe('ui.bbcode.model/nodes', () => {
	let scheme;

	beforeEach(() => {
		scheme = new DefaultBBCodeScheme();
	});

	describe('TextNode', () => {
		it('Create TextNode with options object', () => {
			const parent = scheme.createNode({ name: 'p' });
			const node = scheme.createText({
				content: 'test text',
				name: '1111',
				parent,
			});

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === 'test text');
			assert.ok(node.toString() === 'test text');
			assert.ok(node.getParent() === parent);
			assert.ok(node.getName() === '#text');
		});

		it('Create TextNode with options string', () => {
			const node = scheme.createText('test text');

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === 'test text');
			assert.ok(node.toString() === 'test text');
		});

		it('Should return decoded content', () => {
			const node = scheme.createText('[text]');

			assert.ok(node.getContent() === '[text]');
			assert.ok(node.toString() === '[text]');
		});

		it('TextNode.setParent()', () => {
			const node = scheme.createText();
			const parent1 = scheme.createNode({ name: 'p' });
			const parent2 = scheme.createNode({ name: 'p' });

			assert.ok(node.getParent() === null);

			node.setParent(parent1);
			assert.ok(node.getParent() === parent1);

			node.setParent(parent2);
			assert.ok(node.getParent() === parent2);

			node.setParent(null);
			assert.ok(node.getParent() === null);
		});

		it('TextNode.toJSON()', () => {
			const node = scheme.createText('test text');

			assert.deepEqual(node.toJSON(), {content: 'test text', name: '#text'});
		});

		it('TextNode.setName()', () => {
			const node = scheme.createText();

			assert.ok(node.getName() === '#text');

			node.setName('11111');

			assert.ok(node.getName() === '#text');
		});

		it('TextNode.clone()', () => {
			const sourceNode = scheme.createText('test text');
			const clonedNode = sourceNode.clone();

			assert.deepEqual(sourceNode.toString(), clonedNode.toString());
			assert.ok(sourceNode.getScheme() === clonedNode.getScheme());
		});

		describe('TextNode.split()', () => {
			it('should throws if passed offset less than 0', () => {
				const textNode = scheme.createText('test text');

				assert.throws(() => {
					textNode.split({ offset: -1});
				});

				assert.throws(() => {
					textNode.split({ offset: -20 });
				});
			});

			it('should throws if passed offset more than text length', () => {
				const textNode = scheme.createText('test text');

				assert.throws(() => {
					textNode.split({ offset: 10 });
				});

				assert.throws(() => {
					textNode.split({ offset: 20 });
				});
			});

			it('should returns null for left node if passed 0 offset', () => {
				const textNode = scheme.createText('test text');
				const [leftNode, rightNode] = textNode.split({ offset: 0 });

				assert.ok(leftNode === null);
				assert.ok(rightNode.getName() === '#text');
			});

			it('should returns null for right node if offset equal text length', () => {
				const textNode = scheme.createText('test text');
				const [leftNode, rightNode] = textNode.split({ offset: 9 });

				assert.ok(leftNode.getName() === '#text');
				assert.ok(rightNode === null);
			});

			it('should split node if passed offset in text range', () => {
				const textNode = scheme.createText('test text');

				const [leftNode1, rightNode1] = textNode.split({ offset: 1 });
				assert.ok(leftNode1.getContent() === 't');
				assert.ok(rightNode1.getContent() === 'est text');

				const [leftNode2, rightNode2] = textNode.split({ offset: 2 });
				assert.ok(leftNode2.getContent() === 'te');
				assert.ok(rightNode2.getContent() === 'st text');

				const [leftNode3, rightNode3] = textNode.split({ offset: 3 });
				assert.ok(leftNode3.getContent() === 'tes');
				assert.ok(rightNode3.getContent() === 't text');

				const [leftNode4, rightNode4] = textNode.split({ offset: 4 });
				assert.ok(leftNode4.getContent() === 'test');
				assert.ok(rightNode4.getContent() === ' text');

				const [leftNode5, rightNode5] = textNode.split({ offset: 5 });
				assert.ok(leftNode5.getContent() === 'test ');
				assert.ok(rightNode5.getContent() === 'text');

				const [leftNode6, rightNode6] = textNode.split({ offset: 6 });
				assert.ok(leftNode6.getContent() === 'test t');
				assert.ok(rightNode6.getContent() === 'ext');

				const [leftNode7, rightNode7] = textNode.split({ offset: 7 });
				assert.ok(leftNode7.getContent() === 'test te');
				assert.ok(rightNode7.getContent() === 'xt');

				const [leftNode8, rightNode8] = textNode.split({ offset: 8 });
				assert.ok(leftNode8.getContent() === 'test tex');
				assert.ok(rightNode8.getContent() === 't');
			});

			it('should returns this node as left node if offset is equal content length', () => {
				const textNode = scheme.createText('test text');
				const [leftNode, rightNode] = textNode.split({ offset: 9 });

				assert.ok(leftNode === textNode);
				assert.ok(rightNode === null);
			});

			it('should returns this node as right node if offset is equal 0', () => {
				const textNode = scheme.createText('test text');
				const [leftNode, rightNode] = textNode.split({ offset: 0 });

				assert.ok(leftNode === null);
				assert.ok(rightNode === textNode);
			});
		});
	});

	describe('NewLineNode', () => {
		it('Create NewLineNode without options', () => {
			const node = scheme.createNewLine();

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\n');
			assert.ok(node.toString() === '\n');
		});

		it('Create NewLineNode with options object', () => {
			const parent = scheme.createNode({ name: 'p' });
			const node = scheme.createNewLine({
				content: '1111',
				name: '11111',
				parent,
			});

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\n');
			assert.ok(node.toString() === '\n');
			assert.ok(node.getParent() === parent);
			assert.ok(node.getName() === '#linebreak');
		});

		it('Create NewLineNode with options string', () => {
			const node = scheme.createNewLine('1111');

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\n');
			assert.ok(node.toString() === '\n');
		});

		it('NewLineNode.setContent() do not affected content property', () => {
			const node = scheme.createNewLine();

			node.setContent('1111111');

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\n');
			assert.ok(node.toString() === '\n');
		});

		it('NewLineNode.setParent()', () => {
			const node = scheme.createNewLine();
			const parent1 = scheme.createNode({ name: 'p' });
			const parent2 = scheme.createNode({ name: 'p' });

			assert.ok(node.getParent() === null);

			node.setParent(parent1);
			assert.ok(node.getParent() === parent1);

			node.setParent(parent2);
			assert.ok(node.getParent() === parent2);

			node.setParent(null);
			assert.ok(node.getParent() === null);
		});

		it('NewLineNode.toJSON()', () => {
			const node = scheme.createNewLine();

			assert.deepEqual(node.toJSON(), {content: '\n', name: '#linebreak'});
		});

		it('NewLineNode.setName()', () => {
			const node = scheme.createNewLine();

			assert.ok(node.getName() === '#linebreak');

			node.setName('111111');

			assert.ok(node.getName() === '#linebreak');
		});

		it('NewLineNode.setContent()', () => {
			const node = scheme.createNewLine();

			assert.ok(node.getContent() === '\n');

			node.setContent('111111');

			assert.ok(node.getContent() === '\n');
		});

		it('NewLineNode.clone()', () => {
		    const sourceNode = scheme.createNewLine();
			const clonedNode = sourceNode.clone();

			assert.deepEqual(sourceNode.toString(), clonedNode.toString());
			assert.ok(sourceNode.getScheme() === clonedNode.getScheme());
		});
	});

	describe('TabNode', () => {
		it('Create TabNode without options', () => {
			const node = scheme.createTab();

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\t');
			assert.ok(node.toString() === '\t');
		});

		it('Create TabNode with options object', () => {
			const parent = scheme.createNode({ name: 'p' });
			const node = scheme.createTab({ content: '1111', parent });

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\t');
			assert.ok(node.toString() === '\t');
			assert.ok(node.getParent() === parent);
		});

		it('Create TabNode with options string', () => {
			const node = scheme.createTab('1111');

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\t');
			assert.ok(node.toString() === '\t');
		});

		it('TabNode.setContent() do not affected content property', () => {
			const node = scheme.createTab();

			node.setContent('1111111');

			assert.ok(node.getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node.getContent() === '\t');
			assert.ok(node.toString() === '\t');
		});

		it('TabNode.setParent()', () => {
			const node = scheme.createTab();
			const parent1 = scheme.createNode({ name: 'p' });
			const parent2 = scheme.createNode({ name: 'p' });

			assert.ok(node.getParent() === null);

			node.setParent(parent1);
			assert.ok(node.getParent() === parent1);

			node.setParent(parent2);
			assert.ok(node.getParent() === parent2);

			node.setParent(null);
			assert.ok(node.getParent() === null);
		});

		it('TabNode.toJSON()', () => {
			const node = scheme.createTab();

			assert.deepEqual(node.toJSON(), {content: '\t', name: '#tab'});
		});

		it('TabNode.setName()', () => {
			const node = scheme.createTab();

			assert.ok(node.getName() === '#tab');

			node.setName('111111');

			assert.ok(node.getName() === '#tab');
		});

		it('TabNode.setContent()', () => {
			const node = scheme.createTab();

			assert.ok(node.getContent() === '\t');

			node.setContent('111111');

			assert.ok(node.getContent() === '\t');
		});

		it('TabNode.clone()', () => {
			const sourceNode = scheme.createTab();
			const clonedNode = sourceNode.clone();

			assert.deepEqual(sourceNode.toString(), clonedNode.toString());
			assert.ok(sourceNode.getScheme() === clonedNode.getScheme());
		});
	});

	describe('FragmentNode', () => {
		it('Create FragmentNode with options', () => {
			const node = scheme.createFragment({
				children: [
					scheme.createText('text'),
					scheme.createFragment({
						children: [
							scheme.createText('text2'),
						],
					})
				],
			});

			assert.ok(node.getType() === BBCodeNode.FRAGMENT_NODE);
			assert.ok(node.getChildrenCount() === 2);
			assert.ok(node.getChildren().at(0).getContent() === 'text');
			assert.ok(node.getChildren().at(1).getContent() === 'text2');
		});

		it('FragmentNode.clone()', () => {
			const sourceNode = scheme.createFragment();
			const clonedNode = sourceNode.clone();

			assert.deepEqual(sourceNode.toString(), clonedNode.toString());
		});

		it('FragmentNode.clone({ deep: true })', () => {
			const sourceNode = scheme.createFragment({
				children: [
					scheme.createElement({
						name: 'b',
						children: [
							scheme.createText('test text'),
						],
					}),
				],
			});
			const clonedNode = sourceNode.clone({ deep: true });

			assert.deepEqual(sourceNode.toString(), clonedNode.toString());
			assert.ok(sourceNode.getScheme() === clonedNode.getScheme());
		});

		it('Must be able to include any elements', () => {
		    const fragment = scheme.createFragment();

			const p = scheme.createElement({ name: 'p' });
			const b = scheme.createElement({ name: 'b' });
			const table = scheme.createElement({ name: 'table' });
			const td = scheme.createElement({ name: 'td' });
			const spoiler = scheme.createElement({ name: 'spoiler' });

			const newline = scheme.createNewLine();
			const tab = scheme.createTab();

			fragment.appendChild(p, b, table, td, spoiler, newline, tab);

			assert.ok(fragment.getChildren().includes(p));
			assert.ok(fragment.getChildren().includes(b));
			assert.ok(fragment.getChildren().includes(table));
			assert.ok(fragment.getChildren().includes(td));
			assert.ok(fragment.getChildren().includes(spoiler));
			assert.ok(fragment.getChildren().includes(newline));
			assert.ok(fragment.getChildren().includes(tab));
		});
	});

	describe('ElementNode', () => {
		it('Create ElementNode with options object', () => {
			const node = scheme.createElement({
				name: 'p',
			});

			assert.ok(node.getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(node.getName() === 'p');
			assert.ok(node.getValue() === '');
			assert.ok(node.isVoid() === false);
			assert.deepEqual(node.getAttributes(), {});
			assert.ok(node.getChildrenCount() === 0);
			assert.deepEqual(node.getChildren(), []);
			assert.ok(node.hasChildren() === false);

			const node2 = scheme.createElement({
				name: 'disk',
				value: 'test',
				attributes: {
					key1: 'value1',
					key2: true,
					key3: 33,
				},
			});

			assert.ok(node2.getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(node2.getName() === 'disk');
			assert.ok(node2.getValue() === 'test');
			assert.ok(node2.isVoid() === true);
			assert.deepEqual(node2.getAttributes(), { key1: 'value1', key2: true, key3: 33 });

			const node3 = scheme.createElement({
				name: 'p',
				children: [
					scheme.createText('test'),
					scheme.createElement({
						name: 'b',
						children: [
							scheme.createText('bold'),
						],
					}),
					scheme.createNewLine(),
					scheme.createNewLine(),
					scheme.createElement({
						name: 'i',
					}),
					scheme.createFragment({
						children: [
							scheme.createElement({
								name: 'b',
								value: 'fragment1',
							}),
							scheme.createText('fragment2'),
							scheme.createNewLine(),
							scheme.createTab(),
						],
					}),
				],
			});

			assert.ok(node3.getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(node3.getName() === 'p');
			assert.ok(node3.getChildrenCount() === 8);
			assert.ok(node3.hasChildren() === true);
			assert.ok(node3.getChildren().at(0).getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node3.getChildren().at(0).getContent() === 'test');
			assert.ok(node3.getChildren().at(1).getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(node3.getChildren().at(1).getName() === 'b');
			assert.ok(node3.getChildren().at(1).getChildrenCount() === 1);
			assert.ok(node3.getChildren().at(1).getChildren().at(0).getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node3.getChildren().at(1).getChildren().at(0).getContent() === 'bold');
			assert.ok(node3.getChildren().at(2).getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node3.getChildren().at(2).getContent() === '\n');
			assert.ok(node3.getChildren().at(3).getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node3.getChildren().at(3).getContent() === '\n');
			assert.ok(node3.getChildren().at(4).getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(node3.getChildren().at(4).getName() === 'i');
			assert.ok(node3.getChildren().at(5).getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(node3.getChildren().at(5).getName() === 'b');
			assert.ok(node3.getChildren().at(5).getValue() === 'fragment1');
			assert.ok(node3.getChildren().at(6).getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node3.getChildren().at(6).getContent() === 'fragment2');
			assert.ok(node3.getChildren().at(7).getType() === BBCodeNode.TEXT_NODE);
			assert.ok(node3.getChildren().at(7).getContent() === '\n');
		});

		it('ElementNode.appendChild()', () => {
			const node = scheme.createElement({
				name: 'p',
			});

			const bold = scheme.createElement({
				name: 'b',
			});

			const italic = scheme.createElement({
				name: 'i',
			});

			assert.ok(node.hasChildren() === false);

			node.appendChild(bold);
			assert.ok(node.hasChildren() === true);
			assert.ok(node.getChildrenCount() === 1);
			assert.ok(node.getChildren().at(0) === bold);
			assert.ok(bold.getParent() === node);

			node.appendChild(italic);
			assert.ok(node.hasChildren() === true);
			assert.ok(node.getChildrenCount() === 2);
			assert.ok(node.getChildren().at(0) === bold);
			assert.ok(node.getChildren().at(1) === italic);
			assert.ok(italic.getParent() === node);

			node.appendChild(bold);
			assert.ok(node.getChildrenCount() === 2);
			assert.ok(node.getChildren().at(0) === italic);
			assert.ok(node.getChildren().at(1) === bold);
			assert.ok(bold.getParent() === node);
		});

		it('ElementNode.replaceChild()', () => {
			const node = scheme.createElement({
				name: 'p',
			});

			const bold = scheme.createElement({
				name: 'b',
			});

			const italic = scheme.createElement({
				name: 'i',
			});

			node.appendChild(bold);
			assert.ok(node.getChildrenCount() === 1);
			assert.ok(node.getChildren().at(0) === bold);
			assert.ok(node.getChildren().at(0).getParent() === node);

			node.replaceChild(bold, italic);
			assert.ok(node.getChildrenCount() === 1);
			assert.ok(node.getChildren().at(0) === italic);
			assert.ok(node.getChildren().at(0).getParent() === node);
			assert.ok(bold.getParent() === null);

			const strike = scheme.createElement({ name: 's' });
			const text = scheme.createText('test');
			const fragment = scheme.createFragment({
				children: [
					strike,
					text,
				],
			});

			node.replaceChild(italic, fragment);
			assert.ok(node.getChildrenCount() === 2);
			assert.ok(node.getChildren().at(0) === strike);
			assert.ok(node.getChildren().at(1) === text);
		});

		it('ElementNode.removeChild()', () => {
			const node = scheme.createElement({
				name: 'p',
			});

			const bold = scheme.createElement({
				name: 'b',
			});

			const text = scheme.createText('test');
			const newLine = scheme.createNewLine();

			node.appendChild(...[bold, text, newLine]);
			assert.ok(node.getChildrenCount() === 3);
			assert.ok(node.getChildren().at(0) === bold);
			assert.ok(node.getChildren().at(1) === text);
			assert.ok(node.getChildren().at(2) === newLine);

			node.removeChild(text);
			assert.ok(text.getParent() === null);
			assert.ok(node.getChildrenCount() === 2);
			assert.ok(node.getChildren().at(0) === bold);
			assert.ok(node.getChildren().at(1) === newLine);

			node.removeChild(bold);
			assert.ok(node.getChildrenCount() === 1);
			assert.ok(bold.getParent() === null);
			assert.ok(node.getChildren().at(0) === newLine);
		});

		it('Table child filter', () => {
			const table = scheme.createElement({
				name: 'table',
				children: [
					scheme.createElement({ name: 'p' }),
					scheme.createElement({ name: 'td' }),
					scheme.createElement({ name: 'th' }),
					scheme.createElement({ name: 'tr' }),
					scheme.createText('test'),
					scheme.createNewLine(),
					scheme.createTab(),
				],
			});

			assert.ok(table.getChildrenCount() === 1);
			assert.ok(table.getChildren().at(0).getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(table.getChildren().at(0).getName() === 'tr');

			table.appendChild(scheme.createElement({ name: 'p' }));
			table.appendChild(scheme.createText('test'));
			table.appendChild(scheme.createNewLine());

			assert.ok(table.getChildrenCount() === 1);
			assert.ok(table.getChildren().at(0).getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(table.getChildren().at(0).getName() === 'tr');

			table.appendChild(scheme.createElement({ name: 'tr' }));

			assert.ok(table.getChildrenCount() === 2);
			assert.ok(table.getChildren().at(0).getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(table.getChildren().at(0).getName() === 'tr');
			assert.ok(table.getChildren().at(1).getType() === BBCodeNode.ELEMENT_NODE);
			assert.ok(table.getChildren().at(1).getName() === 'tr');
		});

		it('Table row child filter', () => {
			const row = scheme.createElement({
				name: 'tr',
			});
			const tr = scheme.createElement({ name: 'tr' });
			const td = scheme.createElement({ name: 'td' });
			const th = scheme.createElement({ name: 'th' });
			const p = scheme.createElement({ name: 'p' });
			const text = scheme.createText('test');

			row.appendChild(...[tr, td, th, p, text]);

			assert.ok(row.getChildrenCount() === 2);
			assert.ok(row.getChildren().at(0) === td);
			assert.ok(row.getChildren().at(1) === th);
		});

		xit('Table cell child filter', () => {
			const td = scheme.createElement({
				name: 'td'
			});

			const table = scheme.createElement({ name: 'table' });
			const tr = scheme.createElement({ name: 'tr' });
			const p = scheme.createElement({ name: 'p' });
			const bold = scheme.createElement({ name: 'b' });
			const strike = scheme.createElement({ name: 's' });
			const text = scheme.createText('test');
			const newLine = scheme.createNewLine();
			const tab = scheme.createTab();

			td.appendChild(...[table, tr, p, bold, strike, text, newLine, tab]);

			assert.ok(td.getChildrenCount() === 4);
			assert.ok(td.getChildren().at(0) === bold);
			assert.ok(td.getChildren().at(1) === strike);
			assert.ok(td.getChildren().at(2) === text);
			assert.ok(td.getChildren().at(3) === newLine);
		});

		it('Propagate unresolved nodes from constructor options', () => {
		    const rootNode = scheme.createRoot({
				children: [
					scheme.createElement({
						name: 'p',
						value: 'p1',
						children: [
							scheme.createElement({
								name: 'p',
								value: 'p2',
							}),
						],
					}),
				],
			});

			assert.ok(rootNode.getChildrenCount() === 2);
			assert.ok(rootNode.getFirstChild().getValue() === 'p2');
			assert.ok(rootNode.getLastChild().getValue() === 'p1');
		});

		it('Node name and attribute names must always be in lowercase', () => {
		    const p = scheme.createElement({
				name: 'P',
				attributes: {
					ATTR1: 'UPPER',
					aTTR2: 'LOWER',
				},
				children: [
					scheme.createElement({
						name: 'B',
					}),
				],
			});

			assert.ok(p.getName() === 'p');
			assert.ok(p.getAttribute('attr1') === 'UPPER');
			assert.ok(p.getAttribute('attr2') === 'LOWER');
			assert.deepEqual(p.getAttributes(), {attr1: 'UPPER', attr2: 'LOWER'});
			assert.ok(p.getFirstChild().getName() === 'b');
		});

		it('Must return the tag name and attribute names with lowerCase', () => {
			const localFactory =new BBCodeScheme({
				tagSchemes: [
					new BBCodeTagScheme({
						name: 'p',
					}),
				],
				tagCase: BBCodeScheme.Case.LOWER,
			});

			const p = localFactory.createElement({
				name: 'P',
				attributes: {
					attr1: 'value1',
					attr2: 'value2',
				},
			});

			assert.ok(p.toString() === '[p attr1=value1 attr2=value2][/p]');
		});

		it('Must return the tag name and attribute names with upperCase', () => {
			const localScheme = new BBCodeScheme({
				tagSchemes: [
					new BBCodeTagScheme({
						name: 'p',
					}),
				],
				outputTagCase: BBCodeScheme.Case.UPPER,
			});

			const p = localScheme.createElement({
				name: 'P',
				attributes: {
					attr1: 'value1',
					attr2: 'value2',
				},
			});

			assert.ok(p.toString() === '[P ATTR1=value1 ATTR2=value2][/P]');
		});

		it('getName should return tag name in lowerCase', () => {
			const tagSchemes = [
				new BBCodeTagScheme({
					name: 'p',
				}),
			];

			const localScheme1 = new BBCodeScheme({
				tagSchemes,
			});

			const localScheme2 = new BBCodeScheme({
				tagSchemes,
				tagCase: BBCodeScheme.Case.LOWER,
			});

			const localScheme3 = new BBCodeScheme({
				tagSchemes,
				tagCase: BBCodeScheme.Case.UPPER,
			});

			const p1 = localScheme1.createElement({ name: 'P' });
			const p2 = localScheme2.createElement({ name: 'P' });
			const p3 = localScheme3.createElement({ name: 'P' });

			assert.ok(p1.getName() === 'p');
			assert.ok(p2.getName() === 'p');
			assert.ok(p3.getName() === 'p');
		});

		it('getAttribute should be case-insensitive', () => {
			const tagSchemes = [
				new BBCodeTagScheme({
					name: 'p',
				}),
			];

			const localScheme = new BBCodeScheme({
				tagSchemes,
			});

			const localScheme2 = new BBCodeScheme({
				tagSchemes,
				tagCase: BBCodeScheme.Case.LOWER,
			});

			const localScheme3 = new BBCodeScheme({
				tagSchemes,
				tagCase: BBCodeScheme.Case.UPPER,
			});

			const p1 = localScheme.createElement({
				name: 'p',
				attributes: {
					Attr1: 'value1',
					ATTR2: 'value2',
					attr3: 'value3',
				},
			});

			const p2 = localScheme2.createElement({
				name: 'p',
				attributes: {
					Attr1: 'value1',
					ATTR2: 'value2',
					attr3: 'value3',
				},
			});

			const p3 = localScheme3.createElement({
				name: 'p',
				attributes: {
					Attr1: 'value1',
					ATTR2: 'value2',
					attr3: 'value3',
				},
			});

			assert.ok(p1.getAttribute('attr1') === 'value1');
			assert.ok(p1.getAttribute('Attr1') === 'value1');
			assert.ok(p1.getAttribute('ATTR1') === 'value1');
			assert.ok(p1.getAttribute('attr2') === 'value2');
			assert.ok(p1.getAttribute('Attr2') === 'value2');
			assert.ok(p1.getAttribute('ATTR2') === 'value2');
			assert.ok(p1.getAttribute('attr3') === 'value3');
			assert.ok(p1.getAttribute('Attr3') === 'value3');
			assert.ok(p1.getAttribute('ATTR3') === 'value3');

			assert.ok(p2.getAttribute('attr1') === 'value1');
			assert.ok(p2.getAttribute('Attr1') === 'value1');
			assert.ok(p2.getAttribute('ATTR1') === 'value1');
			assert.ok(p2.getAttribute('attr2') === 'value2');
			assert.ok(p2.getAttribute('Attr2') === 'value2');
			assert.ok(p2.getAttribute('ATTR2') === 'value2');
			assert.ok(p2.getAttribute('attr3') === 'value3');
			assert.ok(p2.getAttribute('Attr3') === 'value3');
			assert.ok(p2.getAttribute('ATTR3') === 'value3');

			assert.ok(p3.getAttribute('attr1') === 'value1');
			assert.ok(p3.getAttribute('Attr1') === 'value1');
			assert.ok(p3.getAttribute('ATTR1') === 'value1');
			assert.ok(p3.getAttribute('attr2') === 'value2');
			assert.ok(p3.getAttribute('Attr2') === 'value2');
			assert.ok(p3.getAttribute('ATTR2') === 'value2');
			assert.ok(p3.getAttribute('attr3') === 'value3');
			assert.ok(p3.getAttribute('Attr3') === 'value3');
			assert.ok(p3.getAttribute('ATTR3') === 'value3');
		});

		describe('ElementNode.clone()', () => {
			it('Clone without options', () => {
				const sourceNode = scheme.createElement({
					name: 'p',
					value: 'test',
					attributes: {
						x: 1,
						y: '2',
					},
					children: [
						scheme.createText('test text'),
						scheme.createElement({
							name: 'b',
							children: [
								scheme.createText('bold'),
							],
						}),
					],
				});

				const clonedNode = sourceNode.clone();

				assert.deepEqual(clonedNode.toString(), '[p=test x=1 y=2][/p]');
			});

			it('Clone with { deep: true }', () => {
				const sourceNode = scheme.createElement({
					name: 'p',
					value: 'test',
					attributes: {
						x: 1,
						y: '2',
					},
					children: [
						scheme.createText('test text'),
						scheme.createElement({
							name: 'b',
							children: [
								scheme.createText('bold'),
							],
						}),
					],
				});

				const clonedNode = sourceNode.clone();

				assert.deepEqual(clonedNode.toString(), clonedNode.toString());
			});
		});

		describe('Formatting', () => {
		    it('Should add linebreak before opening tag if previews sibling is plain text', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createText({ content: 'text' }),
						scheme.createElement({ name: 'p' })
					],
				});

				assert.deepEqual(root.toString(), 'text\n[p][/p]');
		    });

			it('Should not add linebreak before opening tag if previews sibling is linebreak', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createText({ content: 'text' }),
						scheme.createNewLine(),
						scheme.createElement({ name: 'p' })
					],
				});

				assert.deepEqual(root.toString(), 'text\n[p][/p]');
			});

			it('Should not add linebreak before opening tag if node is first child', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createElement({ name: 'p' }),
						scheme.createText({ content: 'text' }),
						scheme.createNewLine(),
					],
				});

				assert.deepEqual(root.toString(), '[p][/p]\ntext\n');
			});

			it('Should add linebreak before opening tag if previews sibling is inline', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createElement({ name: 'b' }),
						scheme.createElement({ name: 'p' }),
					],
				});

				assert.deepEqual(root.toString(), '[b][/b]\n[p][/p]');
			});

			it('Should add linebreak after closing tag if new sibling is plain text', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createElement({ name: 'p' }),
						scheme.createText({ content: 'text' }),
					],
				});

				assert.deepEqual(root.toString(), '[p][/p]\ntext');
			});

			it('Should add linebreak after closing tag if new sibling is inline', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createElement({ name: 'p' }),
						scheme.createElement({ name: 'b' }),
					],
				});

				assert.deepEqual(root.toString(), '[p][/p]\n[b][/b]');
			});

			it('Should not add linebreak after closing tag if new sibling is linebreak', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createElement({ name: 'p' }),
						scheme.createNewLine()
					],
				});

				assert.deepEqual(root.toString(), '[p][/p]\n');
			});

			it('Should not add linebreak after closing tag if node is last child', () => {
				const root = scheme.createRoot({
					children: [
						scheme.createText({ content: 'text' }),
						scheme.createElement({ name: 'p' }),
					],
				});

				assert.deepEqual(root.toString(), 'text\n[p][/p]');
			});
		});

		describe('ElementNode.splitByChildIndex()', () => {
		    it('should return left and right node if passed index', () => {
		        const node = scheme.createElement({
					name: 'p',
					children: [
						scheme.createElement({
							name: 'b',
							children: [
								scheme.createText('bold'),
							],
						}),
						scheme.createElement({
							name: 'i',
							children: [
								scheme.createText('italic'),
							],
						}),
						scheme.createElement({
							name: 's',
							children: [
								scheme.createText('strike'),
							],
						}),
					],
				});

				const [leftNode, rightNode] = node.splitByChildIndex(1);

				assert.deepEqual(leftNode.toString(), '[p]\n[b]bold[/b]\n[/p]');
				assert.deepEqual(rightNode.toString(), '[p]\n[i]italic[/i][s]strike[/s]\n[/p]');
		    });

			it('should replaces this node with left and right nodes', () => {
				const node = scheme.createElement({
					name: 'p',
					children: [
						scheme.createElement({
							name: 'b',
							children: [
								scheme.createText('bold'),
							],
						}),
						scheme.createElement({
							name: 'i',
							children: [
								scheme.createText('italic'),
							],
						}),
						scheme.createElement({
							name: 's',
							children: [
								scheme.createText('strike'),
							],
						}),
					],
				});

				const rootNode = scheme.createRoot({
					children: [
						node,
					],
				});

				const [leftNode, rightNode] = node.splitByChildIndex(1);

				assert.ok(rootNode.getChildrenCount() === 2);
				assert.ok(rootNode.getFirstChild() === leftNode);
				assert.ok(rootNode.getLastChild() === rightNode);
			});

			it('should throws if passed index more than children count', () => {
			    const node = scheme.createElement({
					name: 'p',
					children: [
						scheme.createText('test text'),
					],
				});

				assert.throws(() => {
					node.splitByChildIndex(2);
				});

				assert.throws(() => {
					node.splitByChildIndex(99);
				});
			});

			it('should throws if passed index less than 0', () => {
				const node = scheme.createElement({
					name: 'p',
					children: [
						scheme.createText('test text'),
					],
				});

				assert.throws(() => {
					node.splitByChildIndex(-1);
				});

				assert.throws(() => {
					node.splitByChildIndex(-22);
				});
			});
		});

		describe('ElementNode.toPlainText()', () => {
			it('should returns plain text', () => {
				scheme.setTagScheme(
					new BBCodeTagScheme({
						name: 'div',
					}),
				);

				const node = scheme.createElement({
					name: 'div',
					children: [
						scheme.createElement({
							name: 'b',
							children: [
								scheme.createText('bold'),
							],
						}),
						scheme.createElement({
							name: 'i',
							children: [
								scheme.createText(' italic'),
							],
						}),
						scheme.createElement({
							name: 's',
							children: [
								scheme.createText(' strike'),
							],
						}),
						scheme.createElement({
							name: 'div',
							children: [
								scheme.createText(' p1'),
								scheme.createText(' p2'),
								scheme.createElement({
									name: 'div',
									children: [
										scheme.createText(' pp1'),
										scheme.createText(' pp2'),
									],
								}),
							],
						}),
					],
				});

				assert.equal(node.toPlainText(), 'bold italic strike p1 p2 pp1 pp2');
			});
		});

		describe('Node.split()', () => {
		    it('Should split tree into left and right parts by index', () => {
				const localScheme = new BBCodeScheme({
					tagSchemes: [
						new BBCodeTagScheme({
							name: ['b', 'i', 's'],
						}),
						new BBCodeTagScheme({
							name: ['div1', 'div2', 'div3'],
							stringify: BBCodeTagScheme.defaultBlockStringifier,
						}),
						new BBCodeTagScheme({
							name: '#text',
						}),
					],
				});

				const node = localScheme.createElement({
					name: 'div1',
					children: [
						localScheme.createElement({
							name: 'b',
							children: [
								localScheme.createText('bold'),
							],
						}),
						localScheme.createElement({
							name: 'i',
							children: [
								localScheme.createText(' italic'),
							],
						}),
						localScheme.createElement({
							name: 's',
							children: [
								localScheme.createText(' strike'),
							],
						}),
						localScheme.createElement({
							name: 'div2',
							children: [
								localScheme.createText(' p1'),
								localScheme.createText(' p2'),
								localScheme.createElement({
									name: 'div3',
									children: [
										localScheme.createText(' pp1'),
										localScheme.createText(' pp2'),
									],
								}),
							],
						}),
					],
				});

				const [left0, right0] = node.split({ offset: 0 });
				assert.ok(left0 === null);
				assert.deepEqual(right0, node);

				const [left1, right1] = node.split({ offset: 1 });
				assert.deepEqual(left1.toString(), '[div1]\n[b]b[/b]\n[/div1]');
				assert.deepEqual(right1.toString(), '[div1]\n[b]old[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left1.toPlainText(), 'b');
				assert.deepEqual(right1.toPlainText(), 'old italic strike p1 p2 pp1 pp2');

				const [left2, right2] = node.split({ offset: 2 });
				assert.deepEqual(left2.toString(), '[div1]\n[b]bo[/b]\n[/div1]');
				assert.deepEqual(right2.toString(), '[div1]\n[b]ld[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left2.toPlainText(), 'bo');
				assert.deepEqual(right2.toPlainText(), 'ld italic strike p1 p2 pp1 pp2');

				const [left3, right3] = node.split({ offset: 3 });
				assert.deepEqual(left3.toString(), '[div1]\n[b]bol[/b]\n[/div1]');
				assert.deepEqual(right3.toString(), '[div1]\n[b]d[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left3.toPlainText(), 'bol');
				assert.deepEqual(right3.toPlainText(), 'd italic strike p1 p2 pp1 pp2');

				const [left4, right4] = node.split({ offset: 4 });
				assert.deepEqual(left4.toString(), '[div1]\n[b]bold[/b]\n[/div1]');
				assert.deepEqual(right4.toString(), '[div1]\n[i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left4.toPlainText(), 'bold');
				assert.deepEqual(right4.toPlainText(), ' italic strike p1 p2 pp1 pp2');

				const [left5, right5] = node.split({ offset: 5 });
				assert.deepEqual(left5.toString(), '[div1]\n[b]bold[/b][i] [/i]\n[/div1]');
				assert.deepEqual(right5.toString(), '[div1]\n[i]italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left5.toPlainText(), 'bold ');
				assert.deepEqual(right5.toPlainText(), 'italic strike p1 p2 pp1 pp2');

				const [left6, right6] = node.split({ offset: 6 });
				assert.deepEqual(left6.toString(), '[div1]\n[b]bold[/b][i] i[/i]\n[/div1]');
				assert.deepEqual(right6.toString(), '[div1]\n[i]talic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left6.toPlainText(), 'bold i');
				assert.deepEqual(right6.toPlainText(), 'talic strike p1 p2 pp1 pp2');

				const [left7, right7] = node.split({ offset: 7 });
				assert.deepEqual(left7.toString(), '[div1]\n[b]bold[/b][i] it[/i]\n[/div1]');
				assert.deepEqual(right7.toString(), '[div1]\n[i]alic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left7.toPlainText(), 'bold it');
				assert.deepEqual(right7.toPlainText(), 'alic strike p1 p2 pp1 pp2');

				const [left8, right8] = node.split({ offset: 8 });
				assert.deepEqual(left8.toString(), '[div1]\n[b]bold[/b][i] ita[/i]\n[/div1]');
				assert.deepEqual(right8.toString(), '[div1]\n[i]lic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left8.toPlainText(), 'bold ita');
				assert.deepEqual(right8.toPlainText(), 'lic strike p1 p2 pp1 pp2');

				const [left9, right9] = node.split({ offset: 9 });
				assert.deepEqual(left9.toString(), '[div1]\n[b]bold[/b][i] ital[/i]\n[/div1]');
				assert.deepEqual(right9.toString(), '[div1]\n[i]ic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left9.toPlainText(), 'bold ital');
				assert.deepEqual(right9.toPlainText(), 'ic strike p1 p2 pp1 pp2');

				const [left10, right10] = node.split({ offset: 10 });
				assert.deepEqual(left10.toString(), '[div1]\n[b]bold[/b][i] itali[/i]\n[/div1]');
				assert.deepEqual(right10.toString(), '[div1]\n[i]c[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left10.toPlainText(), 'bold itali');
				assert.deepEqual(right10.toPlainText(), 'c strike p1 p2 pp1 pp2');

				const [left11, right11] = node.split({ offset: 11 });
				assert.deepEqual(left11.toString(), '[div1]\n[b]bold[/b][i] italic[/i]\n[/div1]');
				assert.deepEqual(right11.toString(), '[div1]\n[s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left11.toPlainText(), 'bold italic');
				assert.deepEqual(right11.toPlainText(), ' strike p1 p2 pp1 pp2');

				const [left12, right12] = node.split({ offset: 12 });
				assert.deepEqual(left12.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] [/s]\n[/div1]');
				assert.deepEqual(right12.toString(), '[div1]\n[s]strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left12.toPlainText(), 'bold italic ');
				assert.deepEqual(right12.toPlainText(), 'strike p1 p2 pp1 pp2');

				const [left13, right13] = node.split({ offset: 13 });
				assert.deepEqual(left13.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] s[/s]\n[/div1]');
				assert.deepEqual(right13.toString(), '[div1]\n[s]trike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left13.toPlainText(), 'bold italic s');
				assert.deepEqual(right13.toPlainText(), 'trike p1 p2 pp1 pp2');

				const [left14, right14] = node.split({ offset: 14 });
				assert.deepEqual(left14.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] st[/s]\n[/div1]');
				assert.deepEqual(right14.toString(), '[div1]\n[s]rike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left14.toPlainText(), 'bold italic st');
				assert.deepEqual(right14.toPlainText(), 'rike p1 p2 pp1 pp2');

				const [left15, right15] = node.split({ offset: 15 });
				assert.deepEqual(left15.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] str[/s]\n[/div1]');
				assert.deepEqual(right15.toString(), '[div1]\n[s]ike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left15.toPlainText(), 'bold italic str');
				assert.deepEqual(right15.toPlainText(), 'ike p1 p2 pp1 pp2');

				const [left16, right16] = node.split({ offset: 16 });
				assert.deepEqual(left16.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] stri[/s]\n[/div1]');
				assert.deepEqual(right16.toString(), '[div1]\n[s]ke[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left16.toPlainText(), 'bold italic stri');
				assert.deepEqual(right16.toPlainText(), 'ke p1 p2 pp1 pp2');

				const [left17, right17] = node.split({ offset: 17 });
				assert.deepEqual(left17.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strik[/s]\n[/div1]');
				assert.deepEqual(right17.toString(), '[div1]\n[s]e[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left17.toPlainText(), 'bold italic strik');
				assert.deepEqual(right17.toPlainText(), 'e p1 p2 pp1 pp2');

				const [left18, right18] = node.split({ offset: 18 });
				assert.deepEqual(left18.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[/div1]');
				assert.deepEqual(right18.toString(), '[div1]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left18.toPlainText(), 'bold italic strike');
				assert.deepEqual(right18.toPlainText(), ' p1 p2 pp1 pp2');

				const [left19, right19] = node.split({ offset: 19 });
				assert.deepEqual(left19.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n \n[/div2]\n[/div1]');
				assert.deepEqual(right19.toString(), '[div1]\n[div2]\np1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left19.toPlainText(), 'bold italic strike ');
				assert.deepEqual(right19.toPlainText(), 'p1 p2 pp1 pp2');

				const [left20, right20] = node.split({ offset: 20 });
				assert.deepEqual(left20.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p\n[/div2]\n[/div1]');
				assert.deepEqual(right20.toString(), '[div1]\n[div2]\n1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left20.toPlainText(), 'bold italic strike p');
				assert.deepEqual(right20.toPlainText(), '1 p2 pp1 pp2');

				const [left21, right21] = node.split({ offset: 21 });
				assert.deepEqual(left21.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1\n[/div2]\n[/div1]');
				assert.deepEqual(right21.toString(), '[div1]\n[div2]\n p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left21.toPlainText(), 'bold italic strike p1');
				assert.deepEqual(right21.toPlainText(), ' p2 pp1 pp2');

				const [left22, right22] = node.split({ offset: 22 });
				assert.deepEqual(left22.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 \n[/div2]\n[/div1]');
				assert.deepEqual(right22.toString(), '[div1]\n[div2]\np2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left22.toPlainText(), 'bold italic strike p1 ');
				assert.deepEqual(right22.toPlainText(), 'p2 pp1 pp2');

				const [left23, right23] = node.split({ offset: 23 });
				assert.deepEqual(left23.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p\n[/div2]\n[/div1]');
				assert.deepEqual(right23.toString(), '[div1]\n[div2]\n2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left23.toPlainText(), 'bold italic strike p1 p');
				assert.deepEqual(right23.toPlainText(), '2 pp1 pp2');

				const [left24, right24] = node.split({ offset: 24 });
				assert.deepEqual(left24.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[/div2]\n[/div1]');
				assert.deepEqual(right24.toString(), '[div1]\n[div2]\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left24.toPlainText(), 'bold italic strike p1 p2');
				assert.deepEqual(right24.toPlainText(), ' pp1 pp2');

				const [left25, right25] = node.split({ offset: 25 });
				assert.deepEqual(left25.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n \n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(right25.toString(), '[div1]\n[div2]\n[div3]\npp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left25.toPlainText(), 'bold italic strike p1 p2 ');
				assert.deepEqual(right25.toPlainText(), 'pp1 pp2');

				const [left26, right26] = node.split({ offset: 26 });
				assert.deepEqual(left26.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n p\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(right26.toString(), '[div1]\n[div2]\n[div3]\np1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left26.toPlainText(), 'bold italic strike p1 p2 p');
				assert.deepEqual(right26.toPlainText(), 'p1 pp2');

				const [left27, right27] = node.split({ offset: 27 });
				assert.deepEqual(left27.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(right27.toString(), '[div1]\n[div2]\n[div3]\n1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left27.toPlainText(), 'bold italic strike p1 p2 pp');
				assert.deepEqual(right27.toPlainText(), '1 pp2');

				const [left28, right28] = node.split({ offset: 28 });
				assert.deepEqual(left28.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(right28.toString(), '[div1]\n[div2]\n[div3]\n pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left28.toPlainText(), 'bold italic strike p1 p2 pp1');
				assert.deepEqual(right28.toPlainText(), ' pp2');

				const [left29, right29] = node.split({ offset: 29 });
				assert.deepEqual(left29.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 \n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(right29.toString(), '[div1]\n[div2]\n[div3]\npp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left29.toPlainText(), 'bold italic strike p1 p2 pp1 ');
				assert.deepEqual(right29.toPlainText(), 'pp2');

				const [left30, right30] = node.split({ offset: 30 });
				assert.deepEqual(left30.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 p\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(right30.toString(), '[div1]\n[div2]\n[div3]\np2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left30.toPlainText(), 'bold italic strike p1 p2 pp1 p');
				assert.deepEqual(right30.toPlainText(), 'p2');

				const [left31, right31] = node.split({ offset: 31 });
				assert.deepEqual(left31.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(right31.toString(), '[div1]\n[div2]\n[div3]\n2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left31.toPlainText(), 'bold italic strike p1 p2 pp1 pp');
				assert.deepEqual(right31.toPlainText(), '2');

				const [left32, right32] = node.split({ offset: 32 });
				assert.deepEqual(left32.toString(), '[div1]\n[b]bold[/b][i] italic[/i][s] strike[/s]\n[div2]\n p1 p2\n[div3]\n pp1 pp2\n[/div3]\n[/div2]\n[/div1]');
				assert.deepEqual(left32.toPlainText(), 'bold italic strike p1 p2 pp1 pp2');
				assert.deepEqual(right32, null);
			});

			it('Should split node with text nodes only', () => {
			    const node = scheme.createElement({
					name: 'p',
					children: [
						scheme.createText('text1'),
						scheme.createText('text2'),
						scheme.createText('text3'),
					],
				});

				const [left1, right1] = node.split({ offset: 0 });
				assert.deepEqual(left1, null);
				assert.deepEqual(right1.toString(), '[p]\ntext1text2text3\n[/p]');
				assert.deepEqual(right1.toPlainText(), 'text1text2text3');

				const [left5, right5] = node.split({ offset: 5 });
				assert.deepEqual(left5.toString(), '[p]\ntext1\n[/p]');
				assert.deepEqual(right5.toString(), '[p]\ntext2text3\n[/p]');
				assert.deepEqual(left5.toPlainText(), 'text1');
				assert.deepEqual(right5.toPlainText(), 'text2text3');

				const [left15, right15] = node.split({ offset: 15 });
				assert.deepEqual(left15.toString(), '[p]\ntext1text2text3\n[/p]');
				assert.deepEqual(right15, null);
				assert.deepEqual(left15.toPlainText(), 'text1text2text3');
			});

			it('Should split tree by words', () => {
				const node = scheme.createElement({
					name: 'p',
					children: [
						scheme.createText('text1 text2 text3'),
						scheme.createText('text4'),
						scheme.createText('text5'),
					],
				});

				const [left0, right0] = node.split({ offset: 0, byWord: true });
				assert.deepEqual(left0, null);
				assert.deepEqual(right0.toPlainText(), 'text1 text2 text3text4text5');

				const [left1, right1] = node.split({ offset: 1, byWord: true });
				assert.deepEqual(left1.toPlainText(), '');
				assert.deepEqual(right1.toPlainText(), 'text1 text2 text3text4text5');

				const [left2, right2] = node.split({ offset: 2, byWord: true });
				assert.deepEqual(left2.toPlainText(), '');
				assert.deepEqual(right2.toPlainText(), 'text1 text2 text3text4text5');

				const [left3, right3] = node.split({ offset: 3, byWord: true });
				assert.deepEqual(left3.toPlainText(), '');
				assert.deepEqual(right3.toPlainText(), 'text1 text2 text3text4text5');

				const [left4, right4] = node.split({ offset: 4, byWord: true });
				assert.deepEqual(left4.toPlainText(), '');
				assert.deepEqual(right4.toPlainText(), 'text1 text2 text3text4text5');

				const [left5, right5] = node.split({ offset: 5, byWord: true });
				assert.deepEqual(left5.toPlainText(), 'text1');
				assert.deepEqual(right5.toPlainText(), ' text2 text3text4text5');

				const [left6, right6] = node.split({ offset: 6, byWord: true });
				assert.deepEqual(left6.toPlainText(), 'text1 ');
				assert.deepEqual(right6.toPlainText(), 'text2 text3text4text5');

				const [left7, right7] = node.split({ offset: 7, byWord: true });
				assert.deepEqual(left7.toPlainText(), 'text1 ');
				assert.deepEqual(right7.toPlainText(), 'text2 text3text4text5');

				const [left8, right8] = node.split({ offset: 8, byWord: true });
				assert.deepEqual(left8.toPlainText(), 'text1 ');
				assert.deepEqual(right8.toPlainText(), 'text2 text3text4text5');

				const [left9, right9] = node.split({ offset: 9, byWord: true });
				assert.deepEqual(left9.toPlainText(), 'text1 ');
				assert.deepEqual(right9.toPlainText(), 'text2 text3text4text5');

				const [left10, right10] = node.split({ offset: 10, byWord: true });
				assert.deepEqual(left10.toPlainText(), 'text1 ');
				assert.deepEqual(right10.toPlainText(), 'text2 text3text4text5');

				const [left11, right11] = node.split({ offset: 11, byWord: true });
				assert.deepEqual(left11.toPlainText(), 'text1 text2');
				assert.deepEqual(right11.toPlainText(), ' text3text4text5');

				const [left12, right12] = node.split({ offset: 12, byWord: true });
				assert.deepEqual(left12.toPlainText(), 'text1 text2 ');
				assert.deepEqual(right12.toPlainText(), 'text3text4text5');

				const [left13, right13] = node.split({ offset: 13, byWord: true });
				assert.deepEqual(left13.toPlainText(), 'text1 text2 ');
				assert.deepEqual(right13.toPlainText(), 'text3text4text5');

				const [left14, right14] = node.split({ offset: 14, byWord: true });
				assert.deepEqual(left14.toPlainText(), 'text1 text2 ');
				assert.deepEqual(right14.toPlainText(), 'text3text4text5');

				const [left15, right15] = node.split({ offset: 15, byWord: true });
				assert.deepEqual(left15.toPlainText(), 'text1 text2 ');
				assert.deepEqual(right15.toPlainText(), 'text3text4text5');

				const [left16, right16] = node.split({ offset: 16, byWord: true });
				assert.deepEqual(left16.toPlainText(), 'text1 text2 ');
				assert.deepEqual(right16.toPlainText(), 'text3text4text5');

				const [left17, right17] = node.split({ offset: 17, byWord: true });
				assert.deepEqual(left17.toPlainText(), 'text1 text2 text3');
				assert.deepEqual(right17.toPlainText(), 'text4text5');

				const [left18, right18] = node.split({ offset: 18, byWord: true });
				assert.deepEqual(left18.toPlainText(), 'text1 text2 text3');
				assert.deepEqual(right18.toPlainText(), 'text4text5');

				const [left19, right19] = node.split({ offset: 19, byWord: true });
				assert.deepEqual(left19.toPlainText(), 'text1 text2 text3');
				assert.deepEqual(right19.toPlainText(), 'text4text5');

				const [left20, right20] = node.split({ offset: 20, byWord: true });
				assert.deepEqual(left20.toPlainText(), 'text1 text2 text3');
				assert.deepEqual(right20.toPlainText(), 'text4text5');

				const [left21, right21] = node.split({ offset: 21, byWord: true });
				assert.deepEqual(left21.toPlainText(), 'text1 text2 text3');
				assert.deepEqual(right21.toPlainText(), 'text4text5');

				const [left22, right22] = node.split({ offset: 22, byWord: true });
				assert.deepEqual(left22.toPlainText(), 'text1 text2 text3text4');
				assert.deepEqual(right22.toPlainText(), 'text5');

				const [left23, right23] = node.split({ offset: 23, byWord: true });
				assert.deepEqual(left23.toPlainText(), 'text1 text2 text3text4');
				assert.deepEqual(right23.toPlainText(), 'text5');

				const [left24, right24] = node.split({ offset: 24, byWord: true });
				assert.deepEqual(left24.toPlainText(), 'text1 text2 text3text4');
				assert.deepEqual(right24.toPlainText(), 'text5');

				const [left25, right25] = node.split({ offset: 25, byWord: true });
				assert.deepEqual(left25.toPlainText(), 'text1 text2 text3text4');
				assert.deepEqual(right25.toPlainText(), 'text5');

				const [left26, right26] = node.split({ offset: 26, byWord: true });
				assert.deepEqual(left26.toPlainText(), 'text1 text2 text3text4');
				assert.deepEqual(right26.toPlainText(), 'text5');

				const [left27, right27] = node.split({ offset: 27, byWord: true });
				assert.deepEqual(left27.toPlainText(), 'text1 text2 text3text4text5');
				assert.deepEqual(right27, null);
			});

			it('should split node with linebreaks', () => {
			    const node = scheme.createElement({
					name: 'b',
					children: [
						scheme.createText('text'),
						scheme.createNewLine(),
						scheme.createText('text2'),
						scheme.createNewLine(),
						scheme.createNewLine(),
						scheme.createText('text3'),
					],
				});

				const [leftTree, rightTree] = node.split({ offset: 5 });

				assert.deepEqual(leftTree.toString(), '[b]text\n[/b]');
				assert.deepEqual(rightTree.toString(), '[b]text2\n\ntext3[/b]');
			});

			it('should split node with tabs', () => {
				const node = scheme.createRoot({
					children: [
						scheme.createText('text'),
						scheme.createTab(),
						scheme.createText('text2'),
						scheme.createTab(),
						scheme.createTab(),
						scheme.createText('text3'),
					],
				});

				const [leftTree, rightTree] = node.split({ offset: 5 });

				assert.deepEqual(leftTree.toString(), 'text\t');
				assert.deepEqual(rightTree.toString(), 'text2\t\ttext3');
			});
		});

		describe('ElementNode.insertBefore()', () => {
		    it('should insert nodes before current node', () => {
				const p1 = scheme.createElement({ name: 'p', value: 1 });
				const p2 = scheme.createElement({ name: 'p', value: 2 });
				const p3 = scheme.createElement({ name: 'p', value: 3 });
				const p4 = scheme.createElement({ name: 'p', value: 4 });

				const root = scheme.createRoot({
					children: [
						p1,
						p2,
						p3,
						p4,
					],
				});

				const p11 = scheme.createElement({ name: 'p', value: 11 });
				const p22 = scheme.createElement({ name: 'p', value: 22 });

				p2.insertBefore(p11, p22);

				assert.ok(root.getChildrenCount() === 6);

				const children = root.getChildren();
				assert.ok(children.at(0) === p1);
				assert.ok(children.at(1) === p11);
				assert.ok(children.at(2) === p22);
				assert.ok(children.at(3) === p2);
				assert.ok(children.at(4) === p3);
				assert.ok(children.at(5) === p4);
		    });

			it('should insert nodes before current nodes if current node is first child of parent', () => {
				const p1 = scheme.createElement({ name: 'p', value: 1 });
				const p2 = scheme.createElement({ name: 'p', value: 2 });

				const root = scheme.createRoot({
					children: [
						p1,
						p2,
					],
				});

				const p3 = scheme.createElement({ name: 'p', value: 3 });
				const p4 = scheme.createElement({ name: 'p', value: 4 });

				p1.insertBefore(p3, p4);

				assert.ok(root.getChildrenCount() === 4);

				const children = root.getChildren();
				assert.ok(children.at(0) === p3);
				assert.ok(children.at(1) === p4);
				assert.ok(children.at(2) === p1);
				assert.ok(children.at(3) === p2);
			});
		});

		describe('ElementNode.insertAfter()', () => {
			it('should insert nodes before current node', () => {
				const p1 = scheme.createElement({ name: 'p', value: 1 });
				const p2 = scheme.createElement({ name: 'p', value: 2 });
				const p3 = scheme.createElement({ name: 'p', value: 3 });
				const p4 = scheme.createElement({ name: 'p', value: 4 });

				const root = scheme.createRoot({
					children: [
						p1,
						p2,
						p3,
						p4,
					],
				});

				const p11 = scheme.createElement({ name: 'p', value: 11 });
				const p22 = scheme.createElement({ name: 'p', value: 22 });

				p2.insertAfter(p11, p22);

				assert.ok(root.getChildrenCount() === 6);

				const children = root.getChildren();
				assert.ok(children.at(0) === p1);
				assert.ok(children.at(1) === p2);
				assert.ok(children.at(2) === p11);
				assert.ok(children.at(3) === p22);
				assert.ok(children.at(4) === p3);
				assert.ok(children.at(5) === p4);
			});

			it('should insert nodes before current nodes if current node is last child of parent', () => {
				const p1 = scheme.createElement({ name: 'p', value: 1 });
				const p2 = scheme.createElement({ name: 'p', value: 2 });

				const root = scheme.createRoot({
					children: [
						p1,
						p2,
					],
				});

				const p3 = scheme.createElement({ name: 'p', value: 3 });
				const p4 = scheme.createElement({ name: 'p', value: 4 });

				p2.insertAfter(p3, p4);

				assert.ok(root.getChildrenCount() === 4);

				const children = root.getChildren();
				assert.ok(children.at(0) === p1);
				assert.ok(children.at(1) === p2);
				assert.ok(children.at(2) === p3);
				assert.ok(children.at(3) === p4);
			});
		});
	});

	describe('Default scheme rules', () => {
		describe('allowedChildren', () => {
			it('b, i, u, s, span should include allowed tags only', () => {
				const b = scheme.createElement({
					name: 'b',
					children: [
						scheme.createElement({ name: 'i' }),
						scheme.createElement({ name: 'u' }),
						scheme.createElement({ name: 's' }),
						scheme.createElement({ name: 'span' }),
						scheme.createText('text'),
						scheme.createNewLine(),
						scheme.createElement({ name: 'p' }),
					],
				});

				assert.ok(b.getChildrenCount() === 6, '#1');
				assert.ok(b.getChildren().at(0).getName() === 'i', '#2');
				assert.ok(b.getChildren().at(1).getName() === 'u', '#3');
				assert.ok(b.getChildren().at(2).getName() === 's', '#4');
				assert.ok(b.getChildren().at(3).getName() === 'span', '#5');
				assert.ok(b.getChildren().at(4).getName() === '#text', '#6');
				assert.ok(b.getChildren().at(5).getName() === '#linebreak', '#7');
			});

			it('img, url should include only allowed child', () => {
				const img = scheme.createElement({
					name: 'img',
					children: [
						scheme.createElement({ name: 'b' }),
						scheme.createNewLine(),
						scheme.createTab(),
						scheme.createText('test'),
					],
				});

				assert.ok(img.getChildrenCount() === 1);
				assert.ok(img.getLastChild().getName() === '#text');

				const url = scheme.createElement({
					name: 'url',
					children: [
						scheme.createElement({ name: 'b' }),
						scheme.createNewLine(),
						scheme.createTab(),
						scheme.createText('test'),
					],
				});

				assert.ok(url.getChildrenCount() === 2);
				assert.ok(url.getFirstChild().getName() === 'b');
				assert.ok(url.getLastChild().getName() === '#text');
			});

			it('p should include only allowed child', () => {
				const p = scheme.createElement({
					name: 'p',
					children: [
						scheme.createElement({ name: 'b' }),
						scheme.createElement({ name: 'i' }),
						scheme.createText('test'),
						scheme.createNewLine(),
						scheme.createTab(),
						scheme.createElement({ name: 'p' }),
						scheme.createElement({ name: 'disk' }),
					],
				});

				assert.ok(p.getChildrenCount() === 5);
				assert.ok(p.getChildren().at(0).getName() === 'b');
				assert.ok(p.getChildren().at(1).getName() === 'i');
				assert.ok(p.getChildren().at(2).getName() === '#text');
				assert.ok(p.getChildren().at(3).getName() === '#linebreak');
				assert.ok(p.getChildren().at(4).getName() === 'disk');
			});
		});

		describe('void', () => {
		    it('disk should be void', () => {
				const disk = scheme.createElement({ name: 'disk' });
				assert.ok(disk.isVoid());
		    });

			it('table should not be void', () => {
			    const table = scheme.createElement({ name: 'table' });
				assert.ok(table.isVoid() === false);
			});
		});

		describe('stringify', () => {
			it('* content should be without final line break', () => {
				const item = scheme.createElement({
					name: '*',
					children: [
						scheme.createText('test'),
						scheme.createNewLine(),
					],
				});

				assert.deepEqual(item.toString(), '[*]test');

				const item2 = scheme.createElement({
					name: '*',
					children: [
						scheme.createText('test\n'),
					],
				});

				assert.deepEqual(item2.toString(), '[*]test');
			});

			it('p should includes line breaks after opening tag and before closing tag', () => {
			    const p = scheme.createElement({
					name: 'p',
					children: [
						scheme.createText('test'),
					],
				});

				assert.deepEqual(p.toString(), '[p]\ntest\n[/p]');
			});

			it('p should includes line breaks before opening tag and after closing tag', () => {
				const p = scheme.createElement({
					name: 'p',
					children: [
						scheme.createText('test'),
					],
				});

				void scheme.createRoot({
					children: [
						scheme.createElement({ name: 'b' }),
						p,
						scheme.createElement({ name: 'i' }),
					],
				});

				assert.deepEqual(p.toString(), '\n[p]\ntest\n[/p]\n');
			});
		});

		describe('convertChild', () => {
		    it('code should include strings, line breaks and tabs', () => {
		        const code = scheme.createElement({
					name: 'code',
					children: [
						scheme.createElement({
							name: 'p',
							children: [
								scheme.createText('test'),
							],
						}),
						scheme.createNewLine(),
						scheme.createTab(),
						scheme.createText('test1'),
					],
				});
		    });

			it('should return correct canBeEmpty value', () => {
			    const scheme = new BBCodeScheme({
					tagSchemes: [
						new BBCodeTagScheme({
							name: 'div',
							canBeEmpty: true,
						}),
						new BBCodeTagScheme({
							name: 'div2',
							canBeEmpty: false,
						}),
						new BBCodeTagScheme({
							name: 'div3',
						}),
					],
				});

				assert.ok(scheme.createElement({ name: 'div'}).canBeEmpty());
				assert.ok(scheme.createElement({ name: 'div2'}).canBeEmpty() === false);
				assert.ok(scheme.createElement({ name: 'div3'}).canBeEmpty());
			});
		});
	});
});