Build tools improvement: vite for bundling & migrate tests to TS (#128)

* rename tests to ts

* setup vite & karma-vite; remove webpack; migrate test files to ts

* remove redundant comment
This commit is contained in:
宋铄运 (Alan Song) 2023-05-19 21:54:35 -07:00 committed by GitHub
parent c4d59d9f1f
commit 63efc455ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1637 additions and 3601 deletions

View File

@ -1,46 +1,15 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const webpackConfig = require('./webpack.conf');
module.exports = (config) => {
config.set({
basePath: '',
frameworks: ['jasmine'],
plugins: ['karma-jasmine', 'karma-vite', 'karma-chrome-launcher', 'karma-sauce-launcher'],
frameworks: ['jasmine', 'vite'],
files: [
'test/parchment.ts',
'test/setup.js',
'test/registry/*.js',
'test/unit/linked-list.js', // Control test order
'test/unit/registry.js',
'test/unit/attributor.js',
'test/unit/blot.js',
'test/unit/parent.js',
'test/unit/scroll.js',
'test/unit/container.js',
'test/unit/block.js',
'test/unit/inline.js',
'test/unit/embed.js',
'test/unit/text.js',
'test/unit/lifecycle.js',
],
preprocessors: {
'test/registry/*.js': ['babel'],
'test/parchment.ts': ['webpack'],
},
mime: {
'text/x-typescript': ['ts'],
},
webpack: webpackConfig,
webpackMiddleware: {
stats: {
assets: false,
chunks: false,
colors: true,
errorDetails: true,
hash: false,
timings: false,
version: false,
{
pattern: 'test/unit/*.ts',
type: 'module',
watched: false,
served: false,
},
},
],
exclude: [],
reporters: ['progress'],
browsers: ['Chrome'],

4170
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,29 +12,24 @@
"src"
],
"devDependencies": {
"@babel/core": "^7.17.5",
"@microsoft/api-extractor": "^7.34.4",
"@microsoft/api-extractor": "^7.34.6",
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"babel-loader": "^8.2.3",
"del-cli": "^4.0.1",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"jasmine-core": "^4.0.1",
"karma": "^6.3.17",
"jasmine-core": "^4.6.0",
"karma": "^6.4.2",
"karma-babel-preprocessor": "^8.0.2",
"karma-chrome-launcher": "^3.1.1",
"karma-jasmine": "^4.0.1",
"karma-chrome-launcher": "^3.2.0",
"karma-jasmine": "^5.1.0",
"karma-sauce-launcher": "^4.3.6",
"karma-webpack": "^5.0.0",
"karma-vite": "^1.0.4",
"prettier": "^2.5.1",
"ts-loader": "^9.2.8",
"tsd": "^0.28.1",
"typescript": "^4.9.5",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2"
"vite": "^4.2.1"
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
@ -68,9 +63,8 @@
"url": "https://github.com/quilljs/parchment"
},
"scripts": {
"build": "webpack --config webpack.conf.js",
"build": "vite build",
"build:types": "tsc --emitDeclarationOnly && api-extractor run && rm -rf dist/typings",
"prebuild": "del-cli dist",
"lint": "eslint 'src/**/*.ts'",
"prepare": "npm run build && npm run build:types",
"test": "karma start",

View File

@ -9,7 +9,7 @@ import InlineBlot from './inline';
class BlockBlot extends ParentBlot implements Formattable {
public static blotName = 'block';
public static scope = Scope.BLOCK_BLOT;
public static tagName = 'P';
public static tagName: string | string[] = 'P';
public static allowedChildren: BlotConstructor[] = [
InlineBlot,
BlockBlot,

View File

@ -29,7 +29,7 @@ class InlineBlot extends ParentBlot implements Formattable {
public static allowedChildren: BlotConstructor[] = [InlineBlot, LeafBlot];
public static blotName = 'inline';
public static scope = Scope.INLINE_BLOT;
public static tagName = 'SPAN';
public static tagName: string | string[] = 'SPAN';
public static formats(domNode: HTMLElement, scroll: Root): any {
const match = scroll.query(InlineBlot.blotName);

View File

@ -1,61 +0,0 @@
import Attributor from '../src/attributor/attributor';
import ClassAttributor from '../src/attributor/class';
import StyleAttributor from '../src/attributor/style';
import ShadowBlot from '../src/blot/abstract/shadow';
import ParentBlot from '../src/blot/abstract/parent';
import ContainerBlot from '../src/blot/abstract/container';
import LeafBlot from '../src/blot/abstract/leaf';
import ScrollBlot from '../src/blot/scroll';
import BlockBlot from '../src/blot/block';
import InlineBlot from '../src/blot/inline';
import EmbedBlot from '../src/blot/embed';
import TextBlot from '../src/blot/text';
import LinkedList from '../src/collection/linked-list';
import Registry from '../src/registry';
import Scope from '../src/scope';
const TestRegistry = new Registry();
// @ts-expect-error
window['Attributor'] = Attributor;
// @ts-expect-error
window['ClassAttributor'] = ClassAttributor;
// @ts-expect-error
window['StyleAttributor'] = StyleAttributor;
// @ts-expect-error
window['ShadowBlot'] = ShadowBlot;
// @ts-expect-error
window['ParentBlot'] = ParentBlot;
// @ts-expect-error
window['LeafBlot'] = LeafBlot;
// @ts-expect-error
window['EmbedBlot'] = EmbedBlot;
// @ts-expect-error
window['ScrollBlot'] = ScrollBlot;
// @ts-expect-error
window['ContainerBlot'] = ContainerBlot;
// @ts-expect-error
window['BlockBlot'] = BlockBlot;
// @ts-expect-error
window['InlineBlot'] = InlineBlot;
// @ts-expect-error
window['TextBlot'] = TextBlot;
// @ts-expect-error
window['LinkedList'] = LinkedList;
// @ts-expect-error
window['Scope'] = Scope;
// @ts-expect-error
window['Registry'] = Registry;
// @ts-expect-error
window['TestRegistry'] = TestRegistry;
TestRegistry.register(ScrollBlot);
TestRegistry.register(BlockBlot);
TestRegistry.register(InlineBlot);
TestRegistry.register(TextBlot);

View File

@ -1,27 +0,0 @@
'use strict';
let Color = new StyleAttributor('color', 'color', {
scope: Scope.INLINE_ATTRIBUTE,
});
let Size = new StyleAttributor('size', 'font-size', {
scope: Scope.INLINE_ATTRIBUTE,
});
let Family = new StyleAttributor('family', 'font-family', {
scope: Scope.INLINE_ATTRIBUTE,
whitelist: ['Arial', 'Times New Roman'],
});
let Id = new Attributor('id', 'id');
let Align = new StyleAttributor('align', 'text-align', {
scope: Scope.BLOCK_ATTRIBUTE,
whitelist: ['right', 'center'], // exclude justify to test valid but missing from whitelist
});
let Indent = new ClassAttributor('indent', 'indent', {
scope: Scope.BLOCK_ATTRIBUTE,
});
TestRegistry.register(Color, Size, Family, Id, Align, Indent);

View File

@ -0,0 +1,28 @@
import Attributor from '../../src/attributor/attributor';
import ClassAttributor from '../../src/attributor/class';
import StyleAttributor from '../../src/attributor/style';
import Scope from '../../src/scope';
export const Color = new StyleAttributor('color', 'color', {
scope: Scope.INLINE_ATTRIBUTE,
});
export const Size = new StyleAttributor('size', 'font-size', {
scope: Scope.INLINE_ATTRIBUTE,
});
export const Family = new StyleAttributor('family', 'font-family', {
scope: Scope.INLINE_ATTRIBUTE,
whitelist: ['Arial', 'Times New Roman'],
});
export const Id = new Attributor('id', 'id');
export const Align = new StyleAttributor('align', 'text-align', {
scope: Scope.BLOCK_ATTRIBUTE,
whitelist: ['right', 'center'], // exclude justify to test valid but missing from whitelist
});
export const Indent = new ClassAttributor('indent', 'indent', {
scope: Scope.BLOCK_ATTRIBUTE,
});

View File

@ -1,7 +0,0 @@
'use strict';
class HeaderBlot extends BlockBlot {}
HeaderBlot.blotName = 'header';
HeaderBlot.tagName = ['h1', 'h2'];
TestRegistry.register(HeaderBlot);

5
test/registry/block.ts Normal file
View File

@ -0,0 +1,5 @@
import BlockBlot from '../../src/blot/block';
export class HeaderBlot extends BlockBlot {}
HeaderBlot.blotName = 'header';
HeaderBlot.tagName = ['h1', 'h2'];

View File

@ -1,7 +0,0 @@
'use strict';
class BreakBlot extends EmbedBlot {}
BreakBlot.blotName = 'break';
BreakBlot.tagName = 'br';
TestRegistry.register(BreakBlot);

5
test/registry/break.ts Normal file
View File

@ -0,0 +1,5 @@
import EmbedBlot from '../../src/blot/embed';
export class BreakBlot extends EmbedBlot {}
BreakBlot.blotName = 'break';
BreakBlot.tagName = 'br';

View File

@ -1,6 +1,7 @@
'use strict';
import EmbedBlot from '../../src/blot/embed';
import Scope from '../../src/scope';
class ImageBlot extends EmbedBlot {
export class ImageBlot extends EmbedBlot {
static create(value) {
let node = super.create(value);
if (typeof value === 'string') {
@ -31,7 +32,7 @@ class ImageBlot extends EmbedBlot {
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'IMG';
class VideoBlot extends EmbedBlot {
export class VideoBlot extends EmbedBlot {
static create(value) {
let node = super.create(value);
if (typeof value === 'string') {
@ -68,6 +69,3 @@ class VideoBlot extends EmbedBlot {
VideoBlot.blotName = 'video';
VideoBlot.scope = Scope.BLOCK_BLOT;
VideoBlot.tagName = 'VIDEO';
TestRegistry.register(ImageBlot);
TestRegistry.register(VideoBlot);

View File

@ -1,19 +1,17 @@
'use strict';
import InlineBlot from '../../src/blot/inline';
class AuthorBlot extends InlineBlot {}
export class AuthorBlot extends InlineBlot {}
AuthorBlot.blotName = 'author';
AuthorBlot.className = 'author-blot';
class BoldBlot extends InlineBlot {}
export class BoldBlot extends InlineBlot {}
BoldBlot.blotName = 'bold';
BoldBlot.tagName = 'STRONG';
class ItalicBlot extends InlineBlot {}
export class ItalicBlot extends InlineBlot {}
ItalicBlot.blotName = 'italic';
ItalicBlot.tagName = 'em';
class ScriptBlot extends InlineBlot {}
export class ScriptBlot extends InlineBlot {}
ScriptBlot.blotName = 'script';
ScriptBlot.tagName = ['sup', 'sub'];
TestRegistry.register(AuthorBlot, BoldBlot, ItalicBlot, ScriptBlot);

View File

@ -1,15 +1,13 @@
'use strict';
import ContainerBlot from '../../src/blot/abstract/container';
import BlockBlot from '../../src/blot/block';
class ListItem extends BlockBlot {}
export class ListItem extends BlockBlot {}
ListItem.blotName = 'list';
ListItem.tagName = 'LI';
class ListContainer extends ContainerBlot {}
export class ListContainer extends ContainerBlot {}
ListContainer.blotName = 'list-container';
ListContainer.tagName = 'OL';
ListContainer.allowedChildren = [ListItem];
ListItem.requiredContainer = ListContainer;
TestRegistry.register(ListItem);
TestRegistry.register(ListContainer);

View File

@ -1,9 +0,0 @@
beforeEach(function () {
this.container = document.createElement('div');
this.scroll = new ScrollBlot(TestRegistry, this.container);
});
afterEach(function () {
this.container = null;
this.scroll = null;
});

54
test/setup.ts Normal file
View File

@ -0,0 +1,54 @@
import Registry from '../src/registry';
import ScrollBlot from '../src/blot/scroll';
import BlockBlot from '../src/blot/block';
import InlineBlot from '../src/blot/inline';
import TextBlot from '../src/blot/text';
import {
AuthorBlot,
BoldBlot,
ItalicBlot,
ScriptBlot,
} from './registry/inline';
import { Align, Color, Family, Id, Indent, Size } from './registry/attributor';
import { HeaderBlot } from './registry/block';
import { ImageBlot, VideoBlot } from './registry/embed';
import { ListContainer, ListItem } from './registry/list';
import { BreakBlot } from './registry/break';
const getTestRegistry = () => {
const reg = new Registry();
reg.register(ScrollBlot);
reg.register(BlockBlot);
reg.register(InlineBlot);
reg.register(TextBlot);
reg.register(AuthorBlot, BoldBlot, ItalicBlot, ScriptBlot);
reg.register(Color, Size, Family, Id, Align, Indent);
reg.register(HeaderBlot);
reg.register(ImageBlot, VideoBlot);
reg.register(ListItem, ListContainer);
reg.register(BreakBlot);
return reg;
};
type TestContext = {
container: HTMLElement;
scroll: ScrollBlot;
registry: Registry;
};
export const setupContextBeforeEach = () => {
const ctx: TestContext = {} as TestContext;
beforeEach(() => {
const container = document.createElement('div');
const registry = getTestRegistry();
const scroll = new ScrollBlot(registry, container);
ctx.container = container;
ctx.scroll = scroll;
ctx.registry = registry;
});
return ctx;
};

View File

@ -1,8 +1,10 @@
'use strict';
import { setupContextBeforeEach } from '../setup';
describe('Attributor', function () {
const ctx = setupContextBeforeEach();
it('build', function () {
let blot = this.scroll.create('inline');
let blot = ctx.scroll.create('inline');
blot.domNode.style.color = 'red';
blot.domNode.style.fontSize = '24px';
blot.domNode.id = 'blot-test';
@ -14,8 +16,8 @@ describe('Attributor', function () {
});
it('add to inline', function () {
let container = this.scroll.create('block');
let boldBlot = this.scroll.create('bold');
let container = ctx.scroll.create('block');
let boldBlot = ctx.scroll.create('bold');
container.appendChild(boldBlot);
boldBlot.format('id', 'test-add');
expect(boldBlot.domNode.id).toEqual('test-add');
@ -24,7 +26,7 @@ describe('Attributor', function () {
it('add multiple', function () {
let node = document.createElement('p');
node.innerHTML = '<em><strong>0</strong></em>';
let container = this.scroll.create(node);
let container = ctx.scroll.create(node);
container.formatAt(0, 1, 'color', 'red');
container.formatAt(0, 1, 'size', '18px');
expect(node.innerHTML).toEqual(
@ -33,15 +35,15 @@ describe('Attributor', function () {
});
it('add to text', function () {
let container = this.scroll.create('block');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('block');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
textBlot.formatAt(0, 4, 'color', 'red');
expect(textBlot.domNode.parentNode.style.color).toEqual('red');
});
it('add existing style', function () {
let boldBlot = this.scroll.create('bold');
let boldBlot = ctx.scroll.create('bold');
boldBlot.format('color', 'red');
expect(boldBlot.domNode.style.color).toEqual('red');
let original = boldBlot.domNode.outerHTML;
@ -52,7 +54,7 @@ describe('Attributor', function () {
});
it('replace existing class', function () {
let blockBlot = this.scroll.create('block');
let blockBlot = ctx.scroll.create('block');
blockBlot.format('indent', 2);
expect(blockBlot.domNode.classList.contains('indent-2')).toBe(true);
blockBlot.format('indent', 3);
@ -61,23 +63,23 @@ describe('Attributor', function () {
});
it('add whitelist style', function () {
let blockBlot = this.scroll.create('block');
let blockBlot = ctx.scroll.create('block');
blockBlot.format('align', 'right');
expect(blockBlot.domNode.style.textAlign).toBe('right');
});
it('add non-whitelisted style', function () {
let blockBlot = this.scroll.create('block');
let blockBlot = ctx.scroll.create('block');
blockBlot.format('align', 'justify');
expect(blockBlot.domNode.style.textAlign).toBeFalsy();
});
it('unwrap', function () {
let container = this.scroll.create('block');
let container = ctx.scroll.create('block');
let node = document.createElement('strong');
node.style.color = 'red';
node.innerHTML = '<em>01</em>23';
let blot = this.scroll.create(node);
let blot = ctx.scroll.create(node);
container.appendChild(blot);
container.formatAt(0, 4, 'bold', false);
expect(container.domNode.innerHTML).toEqual(
@ -86,14 +88,14 @@ describe('Attributor', function () {
});
it('remove', function () {
let container = this.scroll.create('block');
let container = ctx.scroll.create('block');
let node = document.createElement('strong');
node.innerHTML = 'Bold';
node.style.color = 'red';
node.style.fontSize = '24px';
container.domNode.classList.add('indent-5');
container.domNode.id = 'test-remove';
let boldBlot = this.scroll.create(node);
let boldBlot = ctx.scroll.create(node);
container.appendChild(boldBlot);
container.formatAt(1, 2, 'color', false);
expect(container.children.length).toEqual(3);
@ -109,17 +111,17 @@ describe('Attributor', function () {
});
it('remove nonexistent', function () {
let container = this.scroll.create('block');
let container = ctx.scroll.create('block');
let node = document.createElement('strong');
node.innerHTML = 'Bold';
let boldBlot = this.scroll.create(node);
let boldBlot = ctx.scroll.create(node);
container.appendChild(boldBlot);
boldBlot.format('color', false);
expect(container.domNode.innerHTML).toEqual('<strong>Bold</strong>');
});
it('keep class attribute after removal', function () {
let boldBlot = this.scroll.create('bold');
let boldBlot = ctx.scroll.create('bold');
boldBlot.domNode.classList.add('blot');
boldBlot.format('indent', 2);
boldBlot.format('indent', false);
@ -127,11 +129,11 @@ describe('Attributor', function () {
});
it('move attribute', function () {
let container = this.scroll.create('block');
let container = ctx.scroll.create('block');
let node = document.createElement('strong');
node.innerHTML = 'Bold';
node.style.color = 'red';
let boldBlot = this.scroll.create(node);
let boldBlot = ctx.scroll.create(node);
container.appendChild(boldBlot);
container.formatAt(1, 2, 'bold', false);
expect(container.children.length).toEqual(3);
@ -140,10 +142,10 @@ describe('Attributor', function () {
});
it('wrap with inline', function () {
let container = this.scroll.create('block');
let container = ctx.scroll.create('block');
let node = document.createElement('strong');
node.style.color = 'red';
let boldBlot = this.scroll.create(node);
let boldBlot = ctx.scroll.create(node);
container.appendChild(boldBlot);
boldBlot.wrap('italic');
expect(node.style.color).toBeFalsy();
@ -151,10 +153,10 @@ describe('Attributor', function () {
});
it('wrap with block', function () {
let container = this.scroll.create('block');
let container = ctx.scroll.create('block');
let node = document.createElement('strong');
node.style.color = 'red';
let boldBlot = this.scroll.create(node);
let boldBlot = ctx.scroll.create(node);
container.appendChild(boldBlot);
boldBlot.wrap('block');
expect(node.style.color).toBe('red');
@ -162,8 +164,8 @@ describe('Attributor', function () {
});
it('add to block', function () {
let container = this.scroll.create('block');
let block = this.scroll.create('header', 'h1');
let container = ctx.scroll.create('block');
let block = ctx.scroll.create('header', 'h1');
container.appendChild(block);
block.format('align', 'right');
expect(container.domNode.innerHTML).toBe(
@ -176,14 +178,14 @@ describe('Attributor', function () {
});
it('missing class value', function () {
let block = this.scroll.create('block');
let indentAttributor = this.scroll.query('indent');
let block = ctx.scroll.create('block');
let indentAttributor = ctx.scroll.query('indent');
expect(indentAttributor.value(block.domNode)).toBeFalsy();
});
it('removes quotes from attribute value when checking if canAdd', function () {
let bold = this.scroll.create('bold');
let familyAttributor = this.scroll.query('family');
let bold = ctx.scroll.create('bold');
let familyAttributor = ctx.scroll.query('family');
expect(familyAttributor.canAdd(bold.domNode, 'Arial')).toBeTruthy();
expect(
familyAttributor.canAdd(bold.domNode, '"Times New Roman"'),

View File

@ -1,59 +0,0 @@
'use strict';
describe('Block', function () {
describe('format', function () {
it('add', function () {
let block = this.scroll.create('block');
this.scroll.appendChild(block);
block.format('header', 'h1');
expect(this.scroll.domNode.innerHTML).toBe('<h1></h1>');
expect(this.scroll.children.head.statics.blotName).toBe('header');
expect(this.scroll.children.head.formats()).toEqual({ header: 'h1' });
});
it('remove', function () {
let block = this.scroll.create('header', 'h1');
this.scroll.appendChild(block);
block.format('header', false);
expect(this.scroll.domNode.innerHTML).toBe('<p></p>');
expect(this.scroll.children.head.statics.blotName).toBe('block');
expect(this.scroll.children.head.formats()).toEqual({});
});
it('change', function () {
let block = this.scroll.create('block');
let text = this.scroll.create('text', 'Test');
block.appendChild(text);
this.scroll.appendChild(block);
block.format('header', 'h2');
expect(this.scroll.domNode.innerHTML).toBe('<h2>Test</h2>');
expect(this.scroll.children.head.statics.blotName).toBe('header');
expect(this.scroll.children.head.formats()).toEqual({ header: 'h2' });
expect(this.scroll.children.head.children.length).toBe(1);
expect(this.scroll.children.head.children.head).toBe(text);
});
it('split', function () {
let block = this.scroll.create('block');
let text = this.scroll.create('text', 'Test');
block.appendChild(text);
this.scroll.appendChild(block);
let src = 'http://www.w3schools.com/html/mov_bbb.mp4';
block.insertAt(2, 'video', src);
expect(this.scroll.domNode.innerHTML).toBe(
`<p>Te</p><video src="${src}"></video><p>st</p>`,
);
expect(this.scroll.children.length).toBe(3);
expect(this.scroll.children.head.next.statics.blotName).toBe('video');
});
it('ignore inline', function () {
let block = this.scroll.create('header', 1);
this.scroll.appendChild(block);
block.format('bold', true);
expect(this.scroll.domNode.innerHTML).toBe('<h1></h1>');
expect(this.scroll.children.head.statics.blotName).toBe('header');
expect(this.scroll.children.head.formats()).toEqual({ header: 'h1' });
});
});
});

61
test/unit/block.ts Normal file
View File

@ -0,0 +1,61 @@
import { setupContextBeforeEach } from '../setup';
describe('Block', function () {
const ctx = setupContextBeforeEach();
describe('format', function () {
it('add', function () {
let block = ctx.scroll.create('block');
ctx.scroll.appendChild(block);
block.format('header', 'h1');
expect(ctx.scroll.domNode.innerHTML).toBe('<h1></h1>');
expect(ctx.scroll.children.head.statics.blotName).toBe('header');
expect(ctx.scroll.children.head.formats()).toEqual({ header: 'h1' });
});
it('remove', function () {
let block = ctx.scroll.create('header', 'h1');
ctx.scroll.appendChild(block);
block.format('header', false);
expect(ctx.scroll.domNode.innerHTML).toBe('<p></p>');
expect(ctx.scroll.children.head.statics.blotName).toBe('block');
expect(ctx.scroll.children.head.formats()).toEqual({});
});
it('change', function () {
let block = ctx.scroll.create('block');
let text = ctx.scroll.create('text', 'Test');
block.appendChild(text);
ctx.scroll.appendChild(block);
block.format('header', 'h2');
expect(ctx.scroll.domNode.innerHTML).toBe('<h2>Test</h2>');
expect(ctx.scroll.children.head.statics.blotName).toBe('header');
expect(ctx.scroll.children.head.formats()).toEqual({ header: 'h2' });
expect(ctx.scroll.children.head.children.length).toBe(1);
expect(ctx.scroll.children.head.children.head).toBe(text);
});
it('split', function () {
let block = ctx.scroll.create('block');
let text = ctx.scroll.create('text', 'Test');
block.appendChild(text);
ctx.scroll.appendChild(block);
let src = 'http://www.w3schools.com/html/mov_bbb.mp4';
block.insertAt(2, 'video', src);
expect(ctx.scroll.domNode.innerHTML).toBe(
`<p>Te</p><video src="${src}"></video><p>st</p>`,
);
expect(ctx.scroll.children.length).toBe(3);
expect(ctx.scroll.children.head.next.statics.blotName).toBe('video');
});
it('ignore inline', function () {
let block = ctx.scroll.create('header', 1);
ctx.scroll.appendChild(block);
block.format('bold', true);
expect(ctx.scroll.domNode.innerHTML).toBe('<h1></h1>');
expect(ctx.scroll.children.head.statics.blotName).toBe('header');
expect(ctx.scroll.children.head.formats()).toEqual({ header: 'h1' });
});
});
});

View File

@ -1,25 +1,28 @@
'use strict';
import Registry from '../../src/registry';
import { setupContextBeforeEach } from '../setup';
describe('Blot', function () {
const ctx = setupContextBeforeEach();
it('offset()', function () {
let blockNode = document.createElement('p');
blockNode.innerHTML = '<span>01</span><em>23<strong>45</strong></em>';
let blockBlot = this.scroll.create(blockNode);
let blockBlot = ctx.scroll.create(blockNode);
let boldBlot = blockBlot.children.tail.children.tail;
expect(boldBlot.offset()).toEqual(2);
expect(boldBlot.offset(blockBlot)).toEqual(4);
});
it('detach()', function () {
let blot = this.scroll.create('block');
let blot = ctx.scroll.create('block');
expect(Registry.blots.get(blot.domNode)).toEqual(blot);
blot.detach();
expect(Registry.blots.get(blot.domNode)).toEqual(undefined);
});
it('remove()', function () {
let blot = this.scroll.create('block');
let text = this.scroll.create('text', 'Test');
let blot = ctx.scroll.create('block');
let text = ctx.scroll.create('text', 'Test');
blot.appendChild(text);
expect(blot.children.head).toBe(text);
expect(blot.domNode.innerHTML).toBe('Test');
@ -29,10 +32,10 @@ describe('Blot', function () {
});
it('wrap()', function () {
let parent = this.scroll.create('block');
let head = this.scroll.create('bold');
let text = this.scroll.create('text', 'Test');
let tail = this.scroll.create('bold');
let parent = ctx.scroll.create('block');
let head = ctx.scroll.create('bold');
let text = ctx.scroll.create('text', 'Test');
let tail = ctx.scroll.create('bold');
parent.appendChild(head);
parent.appendChild(text);
parent.appendChild(tail);
@ -49,9 +52,9 @@ describe('Blot', function () {
});
it('wrap() with blot', function () {
let parent = this.scroll.create('block');
let text = this.scroll.create('text', 'Test');
let italic = this.scroll.create('italic');
let parent = ctx.scroll.create('block');
let text = ctx.scroll.create('text', 'Test');
let italic = ctx.scroll.create('italic');
parent.appendChild(text);
text.wrap(italic);
expect(parent.domNode.innerHTML).toEqual('<em>Test</em>');

View File

@ -1,17 +1,19 @@
'use strict';
import { setupContextBeforeEach } from '../setup';
describe('Container', function () {
const ctx = setupContextBeforeEach();
beforeEach(function () {
this.container.innerHTML = '<ol><li>1</li></ol>';
ctx.container.innerHTML = '<ol><li>1</li></ol>';
});
describe('enforceAllowedChildren()', function () {
it('keep allowed', function () {
const li = document.createElement('li');
li.innerHTML = 2;
this.scroll.domNode.firstChild.appendChild(li);
this.scroll.update();
expect(this.scroll.domNode.innerHTML).toEqual(
ctx.scroll.domNode.firstChild.appendChild(li);
ctx.scroll.update();
expect(ctx.scroll.domNode.innerHTML).toEqual(
'<ol><li>1</li><li>2</li></ol>',
);
});
@ -19,17 +21,17 @@ describe('Container', function () {
it('remove unallowed child', function () {
const strong = document.createElement('strong');
strong.innerHTML = 2;
this.scroll.domNode.firstChild.appendChild(strong);
this.scroll.update();
expect(this.scroll.domNode.innerHTML).toEqual('<ol><li>1</li></ol>');
ctx.scroll.domNode.firstChild.appendChild(strong);
ctx.scroll.update();
expect(ctx.scroll.domNode.innerHTML).toEqual('<ol><li>1</li></ol>');
});
it('isolate block', function () {
const header = document.createElement('h1');
header.innerHTML = 2;
this.scroll.domNode.firstChild.appendChild(header);
this.scroll.update();
expect(this.scroll.domNode.innerHTML).toEqual(
ctx.scroll.domNode.firstChild.appendChild(header);
ctx.scroll.update();
expect(ctx.scroll.domNode.innerHTML).toEqual(
'<ol><li>1</li></ol><h1>2</h1>',
);
});

View File

@ -1,16 +1,18 @@
'use strict';
import { setupContextBeforeEach } from '../setup';
describe('EmbedBlot', function () {
const ctx = setupContextBeforeEach();
it('value()', function () {
let imageBlot = this.scroll.create('image', 'favicon.ico');
let imageBlot = ctx.scroll.create('image', 'favicon.ico');
expect(imageBlot.value()).toEqual({
image: 'favicon.ico',
});
});
it('deleteAt()', function () {
let container = this.scroll.create('block');
let imageBlot = this.scroll.create('image');
let container = ctx.scroll.create('block');
let imageBlot = ctx.scroll.create('image');
container.appendChild(imageBlot);
container.insertAt(1, '!');
container.deleteAt(0, 1);
@ -20,24 +22,24 @@ describe('EmbedBlot', function () {
});
it('format()', function () {
let container = this.scroll.create('block');
let imageBlot = this.scroll.create('image');
let container = ctx.scroll.create('block');
let imageBlot = ctx.scroll.create('image');
container.appendChild(imageBlot);
imageBlot.format('alt', 'Quill Icon');
expect(imageBlot.formats()).toEqual({ alt: 'Quill Icon' });
});
it('formatAt()', function () {
let container = this.scroll.create('block');
let imageBlot = this.scroll.create('image');
let container = ctx.scroll.create('block');
let imageBlot = ctx.scroll.create('image');
container.appendChild(imageBlot);
container.formatAt(0, 1, 'color', 'red');
expect(container.children.head.statics.blotName).toBe('inline');
});
it('insertAt()', function () {
let container = this.scroll.create('inline');
let imageBlot = this.scroll.create('image');
let container = ctx.scroll.create('inline');
let imageBlot = ctx.scroll.create('image');
container.appendChild(imageBlot);
imageBlot.insertAt(0, 'image', true);
imageBlot.insertAt(0, '|');
@ -48,22 +50,22 @@ describe('EmbedBlot', function () {
it('split()', function () {
let blockNode = document.createElement('p');
blockNode.innerHTML = '<em>Te</em><img><strong>st</strong>';
let blockBlot = this.scroll.create(blockNode);
let blockBlot = ctx.scroll.create(blockNode);
let imageBlot = blockBlot.children.head.next;
expect(imageBlot.split(0)).toBe(imageBlot);
expect(imageBlot.split(1)).toBe(blockBlot.children.tail);
});
it('index()', function () {
let imageBlot = this.scroll.create('image');
let imageBlot = ctx.scroll.create('image');
expect(imageBlot.index(imageBlot.domNode, 0)).toEqual(0);
expect(imageBlot.index(imageBlot.domNode, 1)).toEqual(1);
expect(imageBlot.index(document.body, 1)).toEqual(-1);
});
it('position()', function () {
let container = this.scroll.create('block');
let imageBlot = this.scroll.create('image');
let container = ctx.scroll.create('block');
let imageBlot = ctx.scroll.create('image');
container.appendChild(imageBlot);
let [node, offset] = imageBlot.position(1, true);
expect(node).toEqual(container.domNode);

View File

@ -1,9 +1,11 @@
'use strict';
import { setupContextBeforeEach } from '../setup';
describe('InlineBlot', function () {
const ctx = setupContextBeforeEach();
it('format addition', function () {
let italicBlot = this.scroll.create('italic');
italicBlot.appendChild(this.scroll.create('text', 'Test'));
let italicBlot = ctx.scroll.create('italic');
italicBlot.appendChild(ctx.scroll.create('text', 'Test'));
italicBlot.formatAt(1, 2, 'bold', true);
expect(italicBlot.domNode.outerHTML).toEqual(
'<em>T<strong>es</strong>t</em>',
@ -11,8 +13,8 @@ describe('InlineBlot', function () {
});
it('format invalid', function () {
let boldBlot = this.scroll.create('bold');
boldBlot.appendChild(this.scroll.create('text', 'Test'));
let boldBlot = ctx.scroll.create('bold');
boldBlot.appendChild(ctx.scroll.create('text', 'Test'));
let original = boldBlot.domNode.outerHTML;
expect(function () {
boldBlot.format('nonexistent', true);
@ -21,9 +23,9 @@ describe('InlineBlot', function () {
});
it('format existing', function () {
let italicBlot = this.scroll.create('italic');
let boldBlot = this.scroll.create('bold');
boldBlot.appendChild(this.scroll.create('text', 'Test'));
let italicBlot = ctx.scroll.create('italic');
let boldBlot = ctx.scroll.create('bold');
boldBlot.appendChild(ctx.scroll.create('text', 'Test'));
italicBlot.appendChild(boldBlot);
let original = italicBlot.domNode.outerHTML;
expect(function () {
@ -34,9 +36,9 @@ describe('InlineBlot', function () {
});
it('format removal nonexistent', function () {
let container = this.scroll.create('block');
let italicBlot = this.scroll.create('italic');
italicBlot.appendChild(this.scroll.create('text', 'Test'));
let container = ctx.scroll.create('block');
let italicBlot = ctx.scroll.create('italic');
italicBlot.appendChild(ctx.scroll.create('text', 'Test'));
container.appendChild(italicBlot);
let original = italicBlot.domNode.outerHTML;
expect(function () {
@ -48,7 +50,7 @@ describe('InlineBlot', function () {
it('delete + unwrap', function () {
let node = document.createElement('p');
node.innerHTML = '<em><strong>Test</strong></em>!';
let container = this.scroll.create(node);
let container = ctx.scroll.create(node);
container.deleteAt(0, 4);
expect(container.children.head.value()).toEqual('!');
});
@ -57,13 +59,13 @@ describe('InlineBlot', function () {
let italic = document.createElement('em');
italic.style.color = 'red';
italic.innerHTML = '<strong>Test</strong>!';
let blot = this.scroll.create(italic);
let blot = ctx.scroll.create(italic);
expect(blot.formats()).toEqual({ italic: true, color: 'red' });
});
it('change', function () {
let container = this.scroll.create('block');
let script = this.scroll.create('script', 'sup');
let container = ctx.scroll.create('block');
let script = ctx.scroll.create('script', 'sup');
container.appendChild(script);
script.format('script', 'sub');
expect(container.domNode.innerHTML).toEqual('<sub></sub>');

View File

@ -1,6 +1,13 @@
'use strict';
import LeafBlot from '../../src/blot/abstract/leaf';
import ShadowBlot from '../../src/blot/abstract/shadow';
import { HeaderBlot } from '../registry/block';
import { ImageBlot } from '../registry/embed';
import { BoldBlot } from '../registry/inline';
import { setupContextBeforeEach } from '../setup';
describe('Lifecycle', function () {
const ctx = setupContextBeforeEach();
describe('create()', function () {
it('specific tagName', function () {
let node = BoldBlot.create();
@ -11,21 +18,21 @@ describe('Lifecycle', function () {
it('array tagName index', function () {
let node = HeaderBlot.create(2);
expect(node).toBeTruthy();
let blot = this.scroll.create(node);
let blot = ctx.scroll.create(node);
expect(blot.formats()).toEqual({ header: 'h2' });
});
it('array tagName value', function () {
let node = HeaderBlot.create('h2');
expect(node).toBeTruthy();
let blot = this.scroll.create(node);
let blot = ctx.scroll.create(node);
expect(blot.formats()).toEqual({ header: 'h2' });
});
it('array tagName default', function () {
let node = HeaderBlot.create();
expect(node).toBeTruthy();
let blot = this.scroll.create(node);
let blot = ctx.scroll.create(node);
expect(blot.formats()).toEqual({ header: 'h1' });
});
@ -50,12 +57,12 @@ describe('Lifecycle', function () {
let node = document.createElement('p');
node.innerHTML =
'<span style="color: red;"><strong>Te</strong><em>st</em></span>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let span = this.scroll.find(node.querySelector('span'));
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
let span = ctx.scroll.find(node.querySelector('span'));
span.format('color', false);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual(
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual(
'<p><strong>Te</strong><em>st</em></p>',
);
});
@ -63,73 +70,73 @@ describe('Lifecycle', function () {
it('unwrap recursive', function () {
let node = document.createElement('p');
node.innerHTML = '<em><strong>Test</strong></em>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let text = this.scroll.find(node.querySelector('strong').firstChild);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
let text = ctx.scroll.find(node.querySelector('strong').firstChild);
text.deleteAt(0, 4);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual('');
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual('');
});
it('format merge', function () {
let node = document.createElement('p');
node.innerHTML = '<strong>T</strong>es<strong>t</strong>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let text = this.scroll.find(node.childNodes[1]);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
let text = ctx.scroll.find(node.childNodes[1]);
text.formatAt(0, 2, 'bold', true);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
expect(this.container.querySelector('strong').childNodes.length).toBe(1);
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
});
it('format recursive merge', function () {
let node = document.createElement('p');
node.innerHTML =
'<em><strong>T</strong></em><strong>es</strong><em><strong>t</strong></em>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let target = this.scroll.find(node.childNodes[1]);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
let target = ctx.scroll.find(node.childNodes[1]);
target.wrap('italic', true);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual(
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual(
'<p><em><strong>Test</strong></em></p>',
);
expect(this.container.querySelector('strong').childNodes.length).toBe(1);
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
});
it('remove format merge', function () {
let node = document.createElement('p');
node.innerHTML =
'<strong>T</strong><em><strong>es</strong></em><strong>t</strong>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
block.formatAt(1, 2, 'italic', false);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
expect(this.container.querySelector('strong').childNodes.length).toBe(1);
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
});
it('remove attribute merge', function () {
let node = document.createElement('p');
node.innerHTML = '<em>T</em><em style="color: red;">es</em><em>t</em>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
block.formatAt(1, 2, 'color', false);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual('<p><em>Test</em></p>');
expect(this.container.querySelector('em').childNodes.length).toBe(1);
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual('<p><em>Test</em></p>');
expect(ctx.container.querySelector('em').childNodes.length).toBe(1);
});
it('format no merge attribute mismatch', function () {
let node = document.createElement('p');
node.innerHTML =
'<strong>Te</strong><em><strong style="color: red;">st</strong></em>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
block.formatAt(2, 2, 'italic', false);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual(
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual(
'<p><strong>Te</strong><strong style="color: red;">st</strong></p>',
);
});
@ -137,41 +144,41 @@ describe('Lifecycle', function () {
it('delete + merge', function () {
let node = document.createElement('p');
node.innerHTML = '<em>T</em>es<em>t</em>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
block.deleteAt(1, 2);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual('<p><em>Tt</em></p>');
expect(this.container.querySelector('em').childNodes.length).toBe(1);
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual('<p><em>Tt</em></p>');
expect(ctx.container.querySelector('em').childNodes.length).toBe(1);
});
it('unwrap + recursive merge', function () {
let node = document.createElement('p');
node.innerHTML =
'<strong>T</strong><em style="color: red;"><strong>es</strong></em><strong>t</strong>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
block.formatAt(1, 2, 'italic', false);
block.formatAt(1, 2, 'color', false);
this.scroll.optimize();
expect(this.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
expect(this.container.querySelector('strong').childNodes.length).toBe(1);
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
});
it('remove text + recursive merge', function () {
let node = document.createElement('p');
node.innerHTML = '<em>Te</em>|<em>st</em>';
let block = this.scroll.create(node);
this.scroll.appendChild(block);
let block = ctx.scroll.create(node);
ctx.scroll.appendChild(block);
node.childNodes[1].data = '';
this.scroll.optimize();
expect(this.container.innerHTML).toEqual('<p><em>Test</em></p>');
expect(this.container.firstChild.firstChild.childNodes.length).toBe(1);
ctx.scroll.optimize();
expect(ctx.container.innerHTML).toEqual('<p><em>Test</em></p>');
expect(ctx.container.firstChild.firstChild.childNodes.length).toBe(1);
});
it('insert default child', function () {
HeaderBlot.defaultChild = ImageBlot;
let blot = this.scroll.create('header');
let blot = ctx.scroll.create('header');
expect(blot.domNode.innerHTML).toEqual('');
blot.optimize();
HeaderBlot.defaultChild = undefined;
@ -181,11 +188,11 @@ describe('Lifecycle', function () {
describe('update()', function () {
beforeEach(function () {
this.container.innerHTML =
ctx.container.innerHTML =
'<p><em style="color: red;"><strong>Test</strong><img>ing</em></p><p><em>!</em></p>';
this.scroll.update();
ctx.scroll.update();
// [p, em, strong, text, image, text, p, em, text]
this.descendants = this.scroll.descendants(ShadowBlot);
this.descendants = ctx.scroll.descendants(ShadowBlot);
this.descendants.forEach(function (blot) {
spyOn(blot, 'update').and.callThrough();
});
@ -202,7 +209,7 @@ describe('Lifecycle', function () {
});
};
this.checkValues = (expected) => {
let values = this.scroll.descendants(LeafBlot).map(function (leaf) {
let values = ctx.scroll.descendants(LeafBlot).map(function (leaf) {
return leaf.value();
});
expect(values).toEqual(expected);
@ -211,15 +218,15 @@ describe('Lifecycle', function () {
describe('api', function () {
it('insert text', function () {
this.scroll.insertAt(2, '|');
this.scroll.optimize();
ctx.scroll.insertAt(2, '|');
ctx.scroll.optimize();
this.checkValues(['Te|st', { image: true }, 'ing', '!']);
expect(this.scroll.observer.takeRecords()).toEqual([]);
expect(ctx.scroll.observer.takeRecords()).toEqual([]);
});
it('insert embed', function () {
this.scroll.insertAt(2, 'image', true);
this.scroll.optimize();
ctx.scroll.insertAt(2, 'image', true);
ctx.scroll.optimize();
this.checkValues([
'Te',
{ image: true },
@ -228,21 +235,21 @@ describe('Lifecycle', function () {
'ing',
'!',
]);
expect(this.scroll.observer.takeRecords()).toEqual([]);
expect(ctx.scroll.observer.takeRecords()).toEqual([]);
});
it('delete', function () {
this.scroll.deleteAt(2, 5);
this.scroll.optimize();
ctx.scroll.deleteAt(2, 5);
ctx.scroll.optimize();
this.checkValues(['Te', 'g', '!']);
expect(this.scroll.observer.takeRecords()).toEqual([]);
expect(ctx.scroll.observer.takeRecords()).toEqual([]);
});
it('format', function () {
this.scroll.formatAt(2, 5, 'size', '24px');
this.scroll.optimize();
ctx.scroll.formatAt(2, 5, 'size', '24px');
ctx.scroll.optimize();
this.checkValues(['Te', 'st', { image: true }, 'in', 'g', '!']);
expect(this.scroll.observer.takeRecords()).toEqual([]);
expect(ctx.scroll.observer.takeRecords()).toEqual([]);
});
});
@ -250,7 +257,7 @@ describe('Lifecycle', function () {
it('change text', function () {
let textBlot = this.descendants[3];
textBlot.domNode.data = 'Te|st';
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(textBlot);
expect(textBlot.value()).toEqual('Te|st');
});
@ -258,17 +265,17 @@ describe('Lifecycle', function () {
it('add/remove unknown element', function () {
let unknownElement = document.createElement('unknownElement');
let unknownElement2 = document.createElement('unknownElement2');
this.scroll.domNode.appendChild(unknownElement);
ctx.scroll.domNode.appendChild(unknownElement);
unknownElement.appendChild(unknownElement2);
this.scroll.domNode.removeChild(unknownElement);
this.scroll.update();
ctx.scroll.domNode.removeChild(unknownElement);
ctx.scroll.update();
this.checkValues(['Test', { image: true }, 'ing', '!']);
});
it('add attribute', function () {
let attrBlot = this.descendants[1];
attrBlot.domNode.setAttribute('id', 'blot');
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(attrBlot);
expect(attrBlot.formats()).toEqual({
color: 'red',
@ -280,14 +287,14 @@ describe('Lifecycle', function () {
it('add embed attribute', function () {
let imageBlot = this.descendants[4];
imageBlot.domNode.setAttribute('alt', 'image');
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(imageBlot);
});
it('change attributes', function () {
let attrBlot = this.descendants[1];
attrBlot.domNode.style.color = 'blue';
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(attrBlot);
expect(attrBlot.formats()).toEqual({ color: 'blue', italic: true });
});
@ -295,7 +302,7 @@ describe('Lifecycle', function () {
it('remove attribute', function () {
let attrBlot = this.descendants[1];
attrBlot.domNode.removeAttribute('style');
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(attrBlot);
expect(attrBlot.formats()).toEqual({ italic: true });
});
@ -303,7 +310,7 @@ describe('Lifecycle', function () {
it('add child node', function () {
let italicBlot = this.descendants[1];
italicBlot.domNode.appendChild(document.createTextNode('|'));
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(italicBlot);
this.checkValues(['Test', { image: true }, 'ing|', '!']);
});
@ -311,13 +318,13 @@ describe('Lifecycle', function () {
it('add empty family', function () {
let blockBlot = this.descendants[0];
let boldNode = document.createElement('strong');
let html = this.scroll.innerHTML;
let html = ctx.scroll.innerHTML;
boldNode.appendChild(document.createTextNode(''));
blockBlot.domNode.appendChild(boldNode);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(blockBlot);
expect(this.scroll.innerHTML).toBe(html);
expect(this.scroll.descendants(ShadowBlot).length).toEqual(
expect(ctx.scroll.innerHTML).toBe(html);
expect(ctx.scroll.descendants(ShadowBlot).length).toEqual(
this.descendants.length,
);
});
@ -328,7 +335,7 @@ describe('Lifecycle', function () {
imageBlot.domNode,
imageBlot.domNode.previousSibling,
);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(imageBlot.parent);
this.checkValues([{ image: true }, 'Test', 'ing', '!']);
});
@ -339,7 +346,7 @@ describe('Lifecycle', function () {
imageBlot.domNode.nextSibling,
imageBlot.domNode,
);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(imageBlot.parent);
this.checkValues(['Test', 'ing', { image: true }, '!']);
});
@ -349,7 +356,7 @@ describe('Lifecycle', function () {
let lastItalicBlot = this.descendants[7];
firstBlockBlot.domNode.appendChild(lastItalicBlot.domNode);
lastItalicBlot.domNode.innerHTML = '?';
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls([
firstBlockBlot,
this.descendants[6],
@ -366,7 +373,7 @@ describe('Lifecycle', function () {
italicBlot.domNode.insertBefore(textNode, refNode);
italicBlot.domNode.insertBefore(imageNode, textNode);
italicBlot.domNode.removeChild(refNode);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(italicBlot);
this.checkValues(['Test', { image: true }, '|ing', '!']);
});
@ -375,9 +382,9 @@ describe('Lifecycle', function () {
let textNode = this.descendants[5].domNode;
let spanNode = document.createElement('span');
textNode.parentNode.removeChild(textNode);
this.scroll.domNode.lastChild.appendChild(spanNode);
ctx.scroll.domNode.lastChild.appendChild(spanNode);
spanNode.appendChild(textNode);
this.scroll.update();
ctx.scroll.update();
this.checkValues(['Test', { image: true }, '!', 'ing']);
});
@ -386,7 +393,7 @@ describe('Lifecycle', function () {
let textNode = document.createTextNode('|');
italicBlot.domNode.appendChild(textNode);
italicBlot.domNode.removeChild(textNode);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(italicBlot);
this.checkValues(['Test', { image: true }, 'ing', '!']);
});
@ -394,7 +401,7 @@ describe('Lifecycle', function () {
it('remove child node', function () {
let imageBlot = this.descendants[4];
imageBlot.domNode.parentNode.removeChild(imageBlot.domNode);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(this.descendants[1]);
this.checkValues(['Test', 'ing', '!']);
});
@ -403,7 +410,7 @@ describe('Lifecycle', function () {
let italicBlot = this.descendants[1];
italicBlot.domNode.color = 'blue';
italicBlot.domNode.parentNode.removeChild(italicBlot.domNode);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(italicBlot.parent);
this.checkValues(['!']);
});
@ -412,8 +419,8 @@ describe('Lifecycle', function () {
let blockBlot = this.descendants[0];
let italicBlot = this.descendants[1];
italicBlot.domNode.color = 'blue';
this.scroll.domNode.removeChild(blockBlot.domNode);
this.scroll.update();
ctx.scroll.domNode.removeChild(blockBlot.domNode);
ctx.scroll.update();
this.checkUpdateCalls([]);
this.checkValues(['!']);
});
@ -425,7 +432,7 @@ describe('Lifecycle', function () {
document.createTextNode('|'),
attrBlot.domNode.childNodes[1],
);
this.scroll.update();
ctx.scroll.update();
this.checkUpdateCalls(attrBlot);
expect(attrBlot.formats()).toEqual({ color: 'blue', italic: true });
this.checkValues(['Test', '|', { image: true }, 'ing', '!']);

View File

@ -1,4 +1,4 @@
'use strict';
import LinkedList from '../../src/collection/linked-list';
describe('LinkedList', function () {
beforeEach(function () {
@ -12,9 +12,7 @@ describe('LinkedList', function () {
return 0;
},
};
this.a.length = this.b.length = this.c.length = function () {
return 3;
};
this.a.length = this.b.length = this.c.length = () => 3;
});
describe('manipulation', function () {

View File

@ -1,10 +1,22 @@
'use strict';
import LeafBlot from '../../src/blot/abstract/leaf';
import ParentBlot from '../../src/blot/abstract/parent';
import ShadowBlot from '../../src/blot/abstract/shadow';
import EmbedBlot from '../../src/blot/embed';
import { VideoBlot } from '../registry/embed';
import { ItalicBlot } from '../registry/inline';
import Registry from '../../src/registry';
import TextBlot from '../../src/blot/text';
import { setupContextBeforeEach } from '../setup';
describe('Parent', function () {
const ctx = setupContextBeforeEach();
beforeEach(function () {
let node = document.createElement('p');
node.innerHTML = '<span>0</span><em>1<strong>2</strong><img></em>4';
this.blot = TestRegistry.create(this.scroll, node);
this.blot = ctx.registry.create(ctx.scroll, node);
});
describe('descendants()', function () {
@ -77,22 +89,22 @@ describe('Parent', function () {
let node = document.createElement('p');
node.appendChild(document.createElement('input'));
expect(() => {
this.scroll.create(node);
ctx.scroll.create(node);
}).not.toThrowError(/\[Parchment\]/);
});
it('ignore added uiNode', function () {
this.scroll.appendChild(this.blot);
ctx.scroll.appendChild(this.blot);
this.blot.attachUI(document.createElement('div'));
this.scroll.update();
expect(this.scroll.domNode.innerHTML).toEqual(
ctx.scroll.update();
expect(ctx.scroll.domNode.innerHTML).toEqual(
'<p><div contenteditable="false"></div>0<em>1<strong>2</strong><img></em>4</p>',
);
});
it('allowedChildren', function () {
this.scroll.domNode.innerHTML = '<p>A</p>B<span>C</span><p>D</p>';
this.scroll.update();
expect(this.scroll.domNode.innerHTML).toEqual('<p>A</p><p>D</p>');
ctx.scroll.domNode.innerHTML = '<p>A</p>B<span>C</span><p>D</p>';
ctx.scroll.update();
expect(ctx.scroll.domNode.innerHTML).toEqual('<p>A</p><p>D</p>');
});
});

View File

@ -1,41 +1,50 @@
'use strict';
import Scope from '../../src/scope';
import { HeaderBlot } from '../registry/block';
import { AuthorBlot, BoldBlot, ItalicBlot } from '../registry/inline';
import ShadowBlot from '../../src/blot/abstract/shadow';
import InlineBlot from '../../src/blot/inline';
import BlockBlot from '../../src/blot/block';
import { setupContextBeforeEach } from '../setup';
describe('ctx.registry', function () {
const ctx = setupContextBeforeEach();
describe('TestRegistry', function () {
describe('create()', function () {
it('name', function () {
let blot = TestRegistry.create(this.scroll, 'bold');
let blot = ctx.registry.create(ctx.scroll, 'bold');
expect(blot instanceof BoldBlot).toBe(true);
expect(blot.statics.blotName).toBe('bold');
});
it('node', function () {
let node = document.createElement('strong');
let blot = TestRegistry.create(this.scroll, node);
let blot = ctx.registry.create(ctx.scroll, node);
expect(blot instanceof BoldBlot).toBe(true);
expect(blot.statics.blotName).toBe('bold');
});
it('block', function () {
let blot = TestRegistry.create(this.scroll, Scope.BLOCK_BLOT);
let blot = ctx.registry.create(ctx.scroll, Scope.BLOCK_BLOT);
expect(blot instanceof BlockBlot).toBe(true);
expect(blot.statics.blotName).toBe('block');
});
it('inline', function () {
let blot = TestRegistry.create(this.scroll, Scope.INLINE_BLOT);
let blot = ctx.registry.create(ctx.scroll, Scope.INLINE_BLOT);
expect(blot instanceof InlineBlot).toBe(true);
expect(blot.statics.blotName).toBe('inline');
});
it('string index', function () {
let blot = TestRegistry.create(this.scroll, 'header', '2');
let blot = ctx.registry.create(ctx.scroll, 'header', '2');
expect(blot instanceof HeaderBlot).toBe(true);
expect(blot.formats()).toEqual({ header: 'h2' });
});
it('invalid', function () {
expect(() => {
TestRegistry.create(this.scroll, BoldBlot);
ctx.registry.create(ctx.scroll, BoldBlot);
}).toThrowError(/\[Parchment\]/);
});
});
@ -43,13 +52,13 @@ describe('TestRegistry', function () {
describe('register()', function () {
it('invalid', function () {
expect(function () {
TestRegistry.register({});
ctx.registry.register({});
}).toThrowError(/\[Parchment\]/);
});
it('abstract', function () {
expect(function () {
TestRegistry.register(ShadowBlot);
ctx.registry.register(ShadowBlot);
}).toThrowError(/\[Parchment\]/);
});
});
@ -58,43 +67,43 @@ describe('TestRegistry', function () {
it('exact', function () {
let blockNode = document.createElement('p');
blockNode.innerHTML = '<span>01</span><em>23<strong>45</strong></em>';
let blockBlot = TestRegistry.create(this.scroll, blockNode);
expect(TestRegistry.find(document.body)).toBeFalsy();
expect(TestRegistry.find(blockNode)).toBe(blockBlot);
expect(TestRegistry.find(blockNode.querySelector('span'))).toBe(
let blockBlot = ctx.registry.create(ctx.scroll, blockNode);
expect(ctx.registry.find(document.body)).toBeFalsy();
expect(ctx.registry.find(blockNode)).toBe(blockBlot);
expect(ctx.registry.find(blockNode.querySelector('span'))).toBe(
blockBlot.children.head,
);
expect(TestRegistry.find(blockNode.querySelector('em'))).toBe(
expect(ctx.registry.find(blockNode.querySelector('em'))).toBe(
blockBlot.children.tail,
);
expect(TestRegistry.find(blockNode.querySelector('strong'))).toBe(
expect(ctx.registry.find(blockNode.querySelector('strong'))).toBe(
blockBlot.children.tail.children.tail,
);
let text01 = blockBlot.children.head.children.head;
let text23 = blockBlot.children.tail.children.head;
let text45 = blockBlot.children.tail.children.tail.children.head;
expect(TestRegistry.find(text01.domNode)).toBe(text01);
expect(TestRegistry.find(text23.domNode)).toBe(text23);
expect(TestRegistry.find(text45.domNode)).toBe(text45);
expect(ctx.registry.find(text01.domNode)).toBe(text01);
expect(ctx.registry.find(text23.domNode)).toBe(text23);
expect(ctx.registry.find(text45.domNode)).toBe(text45);
});
it('bubble', function () {
let blockBlot = TestRegistry.create(this.scroll, 'block');
let blockBlot = ctx.registry.create(ctx.scroll, 'block');
let textNode = document.createTextNode('Test');
blockBlot.domNode.appendChild(textNode);
expect(TestRegistry.find(textNode)).toBeFalsy();
expect(TestRegistry.find(textNode, true)).toEqual(blockBlot);
expect(ctx.registry.find(textNode)).toBeFalsy();
expect(ctx.registry.find(textNode, true)).toEqual(blockBlot);
});
it('detached parent', function () {
let blockNode = document.createElement('p');
blockNode.appendChild(document.createTextNode('Test'));
expect(TestRegistry.find(blockNode.firstChild)).toBeFalsy();
expect(TestRegistry.find(blockNode.firstChild, true)).toBeFalsy();
expect(ctx.registry.find(blockNode.firstChild)).toBeFalsy();
expect(ctx.registry.find(blockNode.firstChild, true)).toBeFalsy();
});
it('restricted parent', function () {
let blockBlot = TestRegistry.create(this.scroll, 'block');
let blockBlot = ctx.registry.create(ctx.scroll, 'block');
let textNode = document.createTextNode('Test');
blockBlot.domNode.appendChild(textNode);
Object.defineProperty(textNode, 'parentNode', {
@ -102,8 +111,8 @@ describe('TestRegistry', function () {
throw new Error('Permission denied to access property "parentNode"');
},
});
expect(TestRegistry.find(textNode)).toEqual(null);
expect(TestRegistry.find(textNode, true)).toEqual(null);
expect(ctx.registry.find(textNode)).toEqual(null);
expect(ctx.registry.find(textNode, true)).toEqual(null);
});
});
@ -111,41 +120,41 @@ describe('TestRegistry', function () {
it('class', function () {
let node = document.createElement('em');
node.setAttribute('class', 'author-blot');
expect(TestRegistry.query(node)).toBe(AuthorBlot);
expect(ctx.registry.query(node)).toBe(AuthorBlot);
});
it('type mismatch', function () {
let match = TestRegistry.query('italic', Scope.ATTRIBUTE);
let match = ctx.registry.query('italic', Scope.ATTRIBUTE);
expect(match).toBeFalsy();
});
it('level mismatch for blot', function () {
let match = TestRegistry.query('italic', Scope.BLOCK);
let match = ctx.registry.query('italic', Scope.BLOCK);
expect(match).toBeFalsy();
});
it('level mismatch for attribute', function () {
let match = TestRegistry.query('color', Scope.BLOCK);
let match = ctx.registry.query('color', Scope.BLOCK);
expect(match).toBeFalsy();
});
it('either level', function () {
let match = TestRegistry.query('italic', Scope.BLOCK | Scope.INLINE);
let match = ctx.registry.query('italic', Scope.BLOCK | Scope.INLINE);
expect(match).toBe(ItalicBlot);
});
it('level and type match', function () {
let match = TestRegistry.query('italic', Scope.INLINE & Scope.BLOT);
let match = ctx.registry.query('italic', Scope.INLINE & Scope.BLOT);
expect(match).toBe(ItalicBlot);
});
it('level match and type mismatch', function () {
let match = TestRegistry.query('italic', Scope.INLINE & Scope.ATTRIBUTE);
let match = ctx.registry.query('italic', Scope.INLINE & Scope.ATTRIBUTE);
expect(match).toBeFalsy();
});
it('type match and level mismatch', function () {
let match = TestRegistry.query('italic', Scope.BLOCK & Scope.BLOT);
let match = ctx.registry.query('italic', Scope.BLOCK & Scope.BLOT);
expect(match).toBeFalsy();
});
});

View File

@ -1,15 +1,17 @@
'use strict';
import { setupContextBeforeEach } from '../setup';
describe('scroll', function () {
const ctx = setupContextBeforeEach();
describe('Scroll', function () {
beforeEach(function () {
this.container.innerHTML =
ctx.container.innerHTML =
'<p><strong>012</strong><span>34</span><em><strong>5678</strong></em></p>';
this.scroll.update();
ctx.scroll.update();
});
describe('path()', function () {
it('middle', function () {
let path = this.scroll.path(7);
let path = ctx.scroll.path(7);
let expected = [
['scroll', 7],
['block', 7],
@ -25,7 +27,7 @@ describe('Scroll', function () {
});
it('between blots', function () {
let path = this.scroll.path(5);
let path = ctx.scroll.path(5);
let expected = [
['scroll', 5],
['block', 5],
@ -41,7 +43,7 @@ describe('Scroll', function () {
});
it('inclusive', function () {
let path = this.scroll.path(3, true);
let path = ctx.scroll.path(3, true);
let expected = [
['scroll', 3],
['block', 3],
@ -56,7 +58,7 @@ describe('Scroll', function () {
});
it('last', function () {
let path = this.scroll.path(9);
let path = ctx.scroll.path(9);
let expected = [['scroll', 9]];
expect(path.length).toEqual(expected.length);
path.forEach(function (position, i) {
@ -68,20 +70,20 @@ describe('Scroll', function () {
it('delete all', function () {
let wrapper = document.createElement('div');
wrapper.appendChild(this.scroll.domNode);
this.scroll.deleteAt(0, 9);
expect(wrapper.firstChild).toEqual(this.scroll.domNode);
wrapper.appendChild(ctx.scroll.domNode);
ctx.scroll.deleteAt(0, 9);
expect(wrapper.firstChild).toEqual(ctx.scroll.domNode);
});
it('detach', function (done) {
spyOn(this.scroll, 'optimize').and.callThrough();
this.scroll.domNode.innerHTML = 'Test';
spyOn(ctx.scroll, 'optimize').and.callThrough();
ctx.scroll.domNode.innerHTML = 'Test';
setTimeout(() => {
expect(this.scroll.optimize).toHaveBeenCalledTimes(1);
this.scroll.detach();
this.scroll.domNode.innerHTML = '!';
expect(ctx.scroll.optimize).toHaveBeenCalledTimes(1);
ctx.scroll.detach();
ctx.scroll.domNode.innerHTML = '!';
setTimeout(() => {
expect(this.scroll.optimize).toHaveBeenCalledTimes(1);
expect(ctx.scroll.optimize).toHaveBeenCalledTimes(1);
done();
}, 1);
}, 1);
@ -89,23 +91,23 @@ describe('Scroll', function () {
describe('scroll reference', function () {
it('initialization', function () {
expect(this.scroll.scroll).toEqual(this.scroll);
this.scroll.descendants((blot) => {
expect(blot.scroll).toEqual(this.scroll);
expect(ctx.scroll).toEqual(ctx.scroll);
ctx.scroll.descendants((blot) => {
expect(blot.scroll).toEqual(ctx.scroll);
});
});
it('api change', function () {
const blot = this.scroll.create('text', 'Test');
this.scroll.appendChild(blot);
expect(blot.scroll).toEqual(this.scroll);
const blot = ctx.scroll.create('text', 'Test');
ctx.scroll.appendChild(blot);
expect(blot.scroll).toEqual(ctx.scroll);
});
it('user change', function () {
this.scroll.domNode.innerHTML = '<p><em>01</em>23</p>';
this.scroll.update();
this.scroll.descendants((blot) => {
expect(blot.scroll).toEqual(this.scroll);
ctx.scroll.domNode.innerHTML = '<p><em>01</em>23</p>';
ctx.scroll.update();
ctx.scroll.descendants((blot) => {
expect(blot.scroll).toEqual(ctx.scroll);
});
});
});

View File

@ -1,23 +1,26 @@
'use strict';
import TextBlot from '../../src/blot/text';
import { setupContextBeforeEach } from '../setup';
describe('TextBlot', function () {
const ctx = setupContextBeforeEach();
it('constructor(node)', function () {
let node = document.createTextNode('Test');
let blot = new TextBlot(this.scroll, node);
let blot = new TextBlot(ctx.scroll, node);
expect(blot.text).toEqual('Test');
expect(blot.domNode.data).toEqual('Test');
});
it('deleteAt() partial', function () {
let blot = this.scroll.create('text', 'Test');
let blot = ctx.scroll.create('text', 'Test');
blot.deleteAt(1, 2);
expect(blot.value()).toEqual('Tt');
expect(blot.length()).toEqual(2);
});
it('deleteAt() all', function () {
let container = this.scroll.create('inline');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('inline');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
expect(container.domNode.firstChild).toEqual(textBlot.domNode);
textBlot.deleteAt(0, 4);
@ -25,14 +28,14 @@ describe('TextBlot', function () {
});
it('insertAt() text', function () {
let textBlot = this.scroll.create('text', 'Test');
let textBlot = ctx.scroll.create('text', 'Test');
textBlot.insertAt(1, 'ough');
expect(textBlot.value()).toEqual('Toughest');
});
it('insertAt() other', function () {
let container = this.scroll.create('inline');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('inline');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
textBlot.insertAt(2, 'image', {});
expect(textBlot.value()).toEqual('Te');
@ -41,8 +44,8 @@ describe('TextBlot', function () {
});
it('split() middle', function () {
let container = this.scroll.create('inline');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('inline');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
let after = textBlot.split(2);
expect(textBlot.value()).toEqual('Te');
@ -52,8 +55,8 @@ describe('TextBlot', function () {
});
it('split() noop', function () {
let container = this.scroll.create('inline');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('inline');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
let before = textBlot.split(0);
let after = textBlot.split(4);
@ -62,8 +65,8 @@ describe('TextBlot', function () {
});
it('split() force', function () {
let container = this.scroll.create('inline');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('inline');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
let after = textBlot.split(4, true);
expect(after).not.toEqual(textBlot);
@ -73,8 +76,8 @@ describe('TextBlot', function () {
});
it('format wrap', function () {
let container = this.scroll.create('inline');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('inline');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
textBlot.formatAt(0, 4, 'bold', true);
expect(textBlot.domNode.parentNode.tagName).toEqual('STRONG');
@ -82,8 +85,8 @@ describe('TextBlot', function () {
});
it('format null', function () {
let container = this.scroll.create('inline');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('inline');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
textBlot.formatAt(0, 4, 'bold', null);
expect(textBlot.domNode.parentNode.tagName).toEqual('SPAN');
@ -91,8 +94,8 @@ describe('TextBlot', function () {
});
it('format split', function () {
let container = this.scroll.create('block');
let textBlot = this.scroll.create('text', 'Test');
let container = ctx.scroll.create('block');
let textBlot = ctx.scroll.create('text', 'Test');
container.appendChild(textBlot);
textBlot.formatAt(1, 2, 'bold', true);
expect(container.domNode.innerHTML).toEqual('T<strong>es</strong>t');
@ -101,13 +104,13 @@ describe('TextBlot', function () {
});
it('index()', function () {
let textBlot = this.scroll.create('text', 'Test');
let textBlot = ctx.scroll.create('text', 'Test');
expect(textBlot.index(textBlot.domNode, 2)).toEqual(2);
expect(textBlot.index(document.body, 2)).toEqual(-1);
});
it('position()', function () {
let textBlot = this.scroll.create('text', 'Test');
let textBlot = ctx.scroll.create('text', 'Test');
let [node, offset] = textBlot.position(2);
expect(node).toEqual(textBlot.domNode);
expect(offset).toEqual(2);

13
vite.config.ts Normal file
View File

@ -0,0 +1,13 @@
import { defineConfig } from 'vite';
export default defineConfig({
build: {
outDir: 'dist',
lib: {
name: 'Parchment',
entry: './src/parchment.ts',
formats: ['es', 'umd'],
},
sourcemap: true,
},
});

View File

@ -1,33 +0,0 @@
module.exports = {
entry: {
parchment: './src/parchment.ts',
},
output: {
filename: '[name].js',
library: {
name: 'Parchment',
type: 'umd',
},
path: __dirname + '/dist',
// https://github.com/webpack/webpack/issues/6525
globalObject: `(() => {
if (typeof self !== 'undefined') {
return self;
} else if (typeof window !== 'undefined') {
return window;
} else if (typeof global !== 'undefined') {
return global;
} else {
return Function('return this')();
}
})()`,
},
resolve: {
extensions: ['.js', '.ts'],
},
module: {
rules: [{ test: /\.ts$/, use: 'ts-loader' }],
},
devtool: 'source-map',
mode: 'production',
};