Improved typing and migrated to Vitest (#132)
This commit is contained in:
parent
26053f7d2e
commit
4fe064304e
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
@ -8,16 +8,16 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
# https://playwright.dev/docs/ci#via-containers
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.36.0-jammy
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
- run: npm run test:types
|
||||
- run: npm run test:ci
|
||||
- run: npm test
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
SAUCE_USERNAME: quill
|
||||
SAUCE_ACCESS_KEY: ${{ secrets.SAUCELABS_ACCESS_KEY }}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"mainEntryPointFilePath": "dist/typings/parchment.d.ts",
|
||||
"mainEntryPointFilePath": "dist/typings/src/parchment.d.ts",
|
||||
"dtsRollup": {
|
||||
"enabled": true,
|
||||
"untrimmedFilePath": "<projectFolder>/dist/parchment.d.ts"
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
module.exports = (config) => {
|
||||
config.set({
|
||||
plugins: ['karma-jasmine', 'karma-vite', 'karma-chrome-launcher', 'karma-sauce-launcher'],
|
||||
frameworks: ['jasmine', 'vite'],
|
||||
files: [
|
||||
{
|
||||
pattern: 'test/unit/*.ts',
|
||||
type: 'module',
|
||||
watched: false,
|
||||
served: false,
|
||||
},
|
||||
],
|
||||
exclude: [],
|
||||
reporters: ['progress'],
|
||||
browsers: ['Chrome'],
|
||||
customLaunchers: {
|
||||
'saucelabs-chrome': {
|
||||
base: 'SauceLabs',
|
||||
browserName: 'Chrome',
|
||||
platform: 'OS X 10.15',
|
||||
version: '75',
|
||||
},
|
||||
},
|
||||
sauceLabs: {
|
||||
testName: 'Parchment Unit Tests',
|
||||
build: process.env.GITHUB_RUN_ID
|
||||
? `${process.env.GITHUB_REPOSITORY} run #${process.env.GITHUB_RUN_ID}`
|
||||
: null,
|
||||
},
|
||||
port: process.env.GITHUB_ACTION ? 9876 : 10876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
singleRun: true,
|
||||
});
|
||||
};
|
||||
10259
package-lock.json
generated
10259
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@ -13,25 +13,21 @@
|
||||
"src"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.34.6",
|
||||
"@microsoft/api-extractor": "^7.36.3",
|
||||
"@types/node": "^18.15.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.14.0",
|
||||
"@typescript-eslint/parser": "^5.14.0",
|
||||
"del-cli": "^4.0.1",
|
||||
"eslint": "^8.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||
"@typescript-eslint/parser": "^6.2.0",
|
||||
"@vitest/browser": "^0.33.0",
|
||||
"del-cli": "^5.0.0",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jasmine-core": "^4.6.0",
|
||||
"karma": "^6.4.2",
|
||||
"karma-babel-preprocessor": "^8.0.2",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-jasmine": "^5.1.0",
|
||||
"karma-sauce-launcher": "^4.3.6",
|
||||
"karma-vite": "^1.0.4",
|
||||
"playwright": "^1.36.2",
|
||||
"prettier": "^2.5.1",
|
||||
"tsd": "^0.28.1",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^4.2.1"
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.7",
|
||||
"vitest": "^0.33.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
@ -66,13 +62,11 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"build:types": "tsc --emitDeclarationOnly && api-extractor run && rm -rf dist/typings",
|
||||
"build:types": "tsc --emitDeclarationOnly && api-extractor run && del-cli dist/typings",
|
||||
"lint": "eslint 'src/**/*.ts'",
|
||||
"prepare": "npm run build && npm run build:types",
|
||||
"test": "karma start",
|
||||
"test:types": "npm run build && npm run build:types && tsd",
|
||||
"test:server": "karma start --no-single-run",
|
||||
"test:ci": "karma start --browsers saucelabs-chrome --reporters dots,saucelabs"
|
||||
"test": "vitest",
|
||||
"test:types": "npm run build && npm run build:types && tsd"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/quilljs/parchment/issues"
|
||||
|
||||
@ -10,18 +10,14 @@ export default class Attributor {
|
||||
return Array.from(node.attributes).map((item: Attr) => item.name);
|
||||
}
|
||||
|
||||
public attrName: string;
|
||||
public keyName: string;
|
||||
public scope: Scope;
|
||||
public whitelist: string[] | undefined;
|
||||
|
||||
constructor(
|
||||
attrName: string,
|
||||
keyName: string,
|
||||
public readonly attrName: string,
|
||||
public readonly keyName: string,
|
||||
options: AttributorOptions = {},
|
||||
) {
|
||||
this.attrName = attrName;
|
||||
this.keyName = keyName;
|
||||
const attributeBit = Scope.TYPE & Scope.ATTRIBUTE;
|
||||
this.scope =
|
||||
options.scope != null
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Formattable } from '../blot/abstract/blot';
|
||||
import type { Formattable } from '../blot/abstract/blot';
|
||||
import Registry from '../registry';
|
||||
import Scope from '../scope';
|
||||
import Attributor from './attributor';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Attributor from '../../attributor/attributor';
|
||||
import LinkedList from '../../collection/linked-list';
|
||||
import LinkedNode from '../../collection/linked-node';
|
||||
import type LinkedList from '../../collection/linked-list';
|
||||
import type LinkedNode from '../../collection/linked-node';
|
||||
import type { RegistryDefinition } from '../../registry';
|
||||
import Scope from '../../scope';
|
||||
|
||||
export interface BlotConstructor {
|
||||
@ -38,7 +38,7 @@ export interface Blot extends LinkedNode {
|
||||
replaceWith(name: string, value: any): Blot;
|
||||
replaceWith(replacement: Blot): Blot;
|
||||
split(index: number, force?: boolean): Blot | null;
|
||||
wrap(name: string, value: any): Parent;
|
||||
wrap(name: string, value?: any): Parent;
|
||||
wrap(wrapper: Parent): Parent;
|
||||
|
||||
deleteAt(index: number, length: number): void;
|
||||
@ -72,10 +72,7 @@ export interface Parent extends Blot {
|
||||
export interface Root extends Parent {
|
||||
create(input: Node | string | Scope, value?: any): Blot;
|
||||
find(node: Node | null, bubble?: boolean): Blot | null;
|
||||
query(
|
||||
query: string | Node | Scope,
|
||||
scope?: Scope,
|
||||
): Attributor | BlotConstructor | null;
|
||||
query(query: string | Node | Scope, scope?: Scope): RegistryDefinition | null;
|
||||
}
|
||||
|
||||
export interface Formattable extends Blot {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import Scope from '../../scope';
|
||||
import { Leaf } from './blot';
|
||||
import type { Leaf } from './blot';
|
||||
import ShadowBlot from './shadow';
|
||||
|
||||
class LeafBlot extends ShadowBlot implements Leaf {
|
||||
|
||||
@ -1,32 +1,30 @@
|
||||
import LinkedList from '../../collection/linked-list';
|
||||
import ParchmentError from '../../error';
|
||||
import Scope from '../../scope';
|
||||
import { Blot, BlotConstructor, Parent, Root } from './blot';
|
||||
import type { Blot, BlotConstructor, Parent, Root } from './blot';
|
||||
import ShadowBlot from './shadow';
|
||||
|
||||
function makeAttachedBlot(node: Node, scroll: Root): Blot {
|
||||
let blot = scroll.find(node);
|
||||
if (blot == null) {
|
||||
try {
|
||||
blot = scroll.create(node);
|
||||
} catch (e) {
|
||||
blot = scroll.create(Scope.INLINE) as Blot;
|
||||
Array.from(node.childNodes).forEach((child: Node) => {
|
||||
// @ts-expect-error
|
||||
blot.domNode.appendChild(child);
|
||||
});
|
||||
if (node.parentNode) {
|
||||
node.parentNode.replaceChild(blot.domNode, node);
|
||||
}
|
||||
blot.attach();
|
||||
const found = scroll.find(node);
|
||||
if (found) return found;
|
||||
try {
|
||||
return scroll.create(node);
|
||||
} catch (e) {
|
||||
const blot = scroll.create(Scope.INLINE);
|
||||
Array.from(node.childNodes).forEach((child: Node) => {
|
||||
blot.domNode.appendChild(child);
|
||||
});
|
||||
if (node.parentNode) {
|
||||
node.parentNode.replaceChild(blot.domNode, node);
|
||||
}
|
||||
blot.attach();
|
||||
return blot;
|
||||
}
|
||||
return blot as Blot;
|
||||
}
|
||||
|
||||
class ParentBlot extends ShadowBlot implements Parent {
|
||||
public static allowedChildren: BlotConstructor[] | null;
|
||||
public static defaultChild: BlotConstructor | null;
|
||||
public static defaultChild?: BlotConstructor;
|
||||
public static uiClass = '';
|
||||
|
||||
public children!: LinkedList<Blot>;
|
||||
@ -241,7 +239,7 @@ class ParentBlot extends ShadowBlot implements Parent {
|
||||
});
|
||||
}
|
||||
|
||||
public optimize(context: { [key: string]: any }): void {
|
||||
public optimize(context?: { [key: string]: any }): void {
|
||||
super.optimize(context);
|
||||
this.enforceAllowedChildren();
|
||||
if (this.uiNode != null && this.uiNode !== this.domNode.firstChild) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import ParchmentError from '../../error';
|
||||
import Registry from '../../registry';
|
||||
import Scope from '../../scope';
|
||||
import { Blot, BlotConstructor, Formattable, Parent, Root } from './blot';
|
||||
import type { Blot, BlotConstructor, Formattable, Parent, Root } from './blot';
|
||||
|
||||
class ShadowBlot implements Blot {
|
||||
public static blotName = 'abstract';
|
||||
@ -10,21 +10,24 @@ class ShadowBlot implements Blot {
|
||||
public static scope: Scope;
|
||||
public static tagName: string | string[];
|
||||
|
||||
public static create(value: any): Node {
|
||||
public static create(rawValue?: unknown): Node {
|
||||
if (this.tagName == null) {
|
||||
throw new ParchmentError('Blot definition missing tagName');
|
||||
}
|
||||
let node;
|
||||
let node: HTMLElement;
|
||||
let value: string | number | undefined;
|
||||
if (Array.isArray(this.tagName)) {
|
||||
if (typeof value === 'string') {
|
||||
value = value.toUpperCase();
|
||||
if (typeof rawValue === 'string') {
|
||||
value = rawValue.toUpperCase();
|
||||
if (parseInt(value, 10).toString() === value) {
|
||||
value = parseInt(value, 10);
|
||||
}
|
||||
} else if (typeof rawValue === 'number') {
|
||||
value = rawValue;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
node = document.createElement(this.tagName[value - 1]);
|
||||
} else if (this.tagName.indexOf(value) > -1) {
|
||||
} else if (value && this.tagName.indexOf(value) > -1) {
|
||||
node = document.createElement(value);
|
||||
} else {
|
||||
node = document.createElement(this.tagName[0]);
|
||||
@ -120,7 +123,7 @@ class ShadowBlot implements Blot {
|
||||
return this.parent.children.offset(this) + this.parent.offset(root);
|
||||
}
|
||||
|
||||
public optimize(_context: { [key: string]: any }): void {
|
||||
public optimize(_context?: { [key: string]: any }): void {
|
||||
if (
|
||||
this.statics.requiredContainer &&
|
||||
!(this.parent instanceof this.statics.requiredContainer)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Attributor from '../attributor/attributor';
|
||||
import AttributorStore from '../attributor/store';
|
||||
import Scope from '../scope';
|
||||
import { Blot, BlotConstructor, Formattable, Root } from './abstract/blot';
|
||||
import type { Blot, BlotConstructor, Formattable, Root } from './abstract/blot';
|
||||
import LeafBlot from './abstract/leaf';
|
||||
import ParentBlot from './abstract/parent';
|
||||
import InlineBlot from './inline';
|
||||
@ -16,6 +16,10 @@ class BlockBlot extends ParentBlot implements Formattable {
|
||||
LeafBlot,
|
||||
];
|
||||
|
||||
static create(value?: unknown) {
|
||||
return super.create(value) as HTMLElement;
|
||||
}
|
||||
|
||||
public static formats(domNode: HTMLElement, scroll: Root): any {
|
||||
const match = scroll.query(BlockBlot.blotName);
|
||||
if (
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Formattable, Root } from './abstract/blot';
|
||||
import type { Formattable, Root } from './abstract/blot';
|
||||
import LeafBlot from './abstract/leaf';
|
||||
|
||||
class EmbedBlot extends LeafBlot implements Formattable {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Attributor from '../attributor/attributor';
|
||||
import AttributorStore from '../attributor/store';
|
||||
import Scope from '../scope';
|
||||
import {
|
||||
import type {
|
||||
Blot,
|
||||
BlotConstructor,
|
||||
Formattable,
|
||||
@ -12,12 +12,14 @@ import LeafBlot from './abstract/leaf';
|
||||
import ParentBlot from './abstract/parent';
|
||||
|
||||
// Shallow object comparison
|
||||
function isEqual(obj1: object, obj2: object): boolean {
|
||||
function isEqual(
|
||||
obj1: Record<string, unknown>,
|
||||
obj2: Record<string, unknown>,
|
||||
): boolean {
|
||||
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
|
||||
return false;
|
||||
}
|
||||
for (const prop in obj1) {
|
||||
// @ts-expect-error
|
||||
if (obj1[prop] !== obj2[prop]) {
|
||||
return false;
|
||||
}
|
||||
@ -31,6 +33,10 @@ class InlineBlot extends ParentBlot implements Formattable {
|
||||
public static scope = Scope.INLINE_BLOT;
|
||||
public static tagName: string | string[] = 'SPAN';
|
||||
|
||||
static create(value?: unknown) {
|
||||
return super.create(value) as HTMLElement;
|
||||
}
|
||||
|
||||
public static formats(domNode: HTMLElement, scroll: Root): any {
|
||||
const match = scroll.query(InlineBlot.blotName);
|
||||
if (
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import Attributor from '../attributor/attributor';
|
||||
import Registry from '../registry';
|
||||
import Registry, { type RegistryDefinition } from '../registry';
|
||||
import Scope from '../scope';
|
||||
import { Blot, BlotConstructor, Root } from './abstract/blot';
|
||||
import type { Blot, BlotConstructor, Root } from './abstract/blot';
|
||||
import ContainerBlot from './abstract/container';
|
||||
import ParentBlot from './abstract/parent';
|
||||
import BlockBlot from './block';
|
||||
@ -23,13 +22,11 @@ class ScrollBlot extends ParentBlot implements Root {
|
||||
public static scope = Scope.BLOCK_BLOT;
|
||||
public static tagName = 'DIV';
|
||||
|
||||
public registry: Registry;
|
||||
public observer: MutationObserver;
|
||||
|
||||
constructor(registry: Registry, node: HTMLDivElement) {
|
||||
// @ts-expect-error
|
||||
constructor(public registry: Registry, node: HTMLDivElement) {
|
||||
// @ts-expect-error scroll is the root with no parent
|
||||
super(null, node);
|
||||
this.registry = registry;
|
||||
this.scroll = this;
|
||||
this.build();
|
||||
this.observer = new MutationObserver((mutations: MutationRecord[]) => {
|
||||
@ -57,11 +54,11 @@ class ScrollBlot extends ParentBlot implements Root {
|
||||
public query(
|
||||
query: string | Node | Scope,
|
||||
scope: Scope = Scope.ANY,
|
||||
): Attributor | BlotConstructor | null {
|
||||
): RegistryDefinition | null {
|
||||
return this.registry.query(query, scope);
|
||||
}
|
||||
|
||||
public register(...definitions: any[]): any {
|
||||
public register(...definitions: RegistryDefinition[]) {
|
||||
return this.registry.register(...definitions);
|
||||
}
|
||||
|
||||
@ -103,7 +100,7 @@ class ScrollBlot extends ParentBlot implements Root {
|
||||
super.insertAt(index, value, def);
|
||||
}
|
||||
|
||||
public optimize(context: { [key: string]: any }): void;
|
||||
public optimize(context?: { [key: string]: any }): void;
|
||||
public optimize(
|
||||
mutations: MutationRecord[],
|
||||
context: { [key: string]: any },
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import Scope from '../scope';
|
||||
import { Blot, Leaf, Root } from './abstract/blot';
|
||||
import type { Blot, Leaf, Root } from './abstract/blot';
|
||||
import LeafBlot from './abstract/leaf';
|
||||
|
||||
class TextBlot extends LeafBlot implements Leaf {
|
||||
public static blotName = 'text';
|
||||
public static readonly blotName = 'text';
|
||||
public static scope = Scope.INLINE_BLOT;
|
||||
|
||||
public static create(value: string): Text {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import LinkedNode from './linked-node';
|
||||
import type LinkedNode from './linked-node';
|
||||
|
||||
class LinkedList<T extends LinkedNode> {
|
||||
public head: T | null;
|
||||
|
||||
@ -5,4 +5,4 @@ interface LinkedNode {
|
||||
length(): number;
|
||||
}
|
||||
|
||||
export default LinkedNode;
|
||||
export type { LinkedNode as default };
|
||||
|
||||
@ -33,7 +33,7 @@ export {
|
||||
Scope,
|
||||
};
|
||||
|
||||
export type { RegistryInterface } from './registry';
|
||||
export type { RegistryInterface, RegistryDefinition } from './registry';
|
||||
export type { default as ShadowBlot } from './blot/abstract/shadow';
|
||||
export type { default as LinkedList } from './collection/linked-list';
|
||||
export type { default as LinkedNode } from './collection/linked-node';
|
||||
|
||||
103
src/registry.ts
103
src/registry.ts
@ -1,21 +1,24 @@
|
||||
import Attributor from './attributor/attributor';
|
||||
import { Blot, BlotConstructor, Root } from './blot/abstract/blot';
|
||||
import {
|
||||
type Blot,
|
||||
type BlotConstructor,
|
||||
type Root,
|
||||
} from './blot/abstract/blot';
|
||||
import ParchmentError from './error';
|
||||
import Scope from './scope';
|
||||
|
||||
export type RegistryDefinition = Attributor | BlotConstructor;
|
||||
|
||||
export interface RegistryInterface {
|
||||
create(sroll: Root, input: Node | string | Scope, value?: any): Blot;
|
||||
query(
|
||||
query: string | Node | Scope,
|
||||
scope: Scope,
|
||||
): Attributor | BlotConstructor | null;
|
||||
query(query: string | Node | Scope, scope: Scope): RegistryDefinition | null;
|
||||
register(...definitions: any[]): any;
|
||||
}
|
||||
|
||||
export default class Registry implements RegistryInterface {
|
||||
public static blots = new WeakMap<Node, Blot>();
|
||||
|
||||
public static find(node: Node | null, bubble = false): Blot | null {
|
||||
public static find(node?: Node | null, bubble = false): Blot | null {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
@ -41,7 +44,7 @@ export default class Registry implements RegistryInterface {
|
||||
private attributes: { [key: string]: Attributor } = {};
|
||||
private classes: { [key: string]: BlotConstructor } = {};
|
||||
private tags: { [key: string]: BlotConstructor } = {};
|
||||
private types: { [key: string]: Attributor | BlotConstructor } = {};
|
||||
private types: { [key: string]: RegistryDefinition } = {};
|
||||
|
||||
public create(scroll: Root, input: Node | string | Scope, value?: any): Blot {
|
||||
const match = this.query(input);
|
||||
@ -67,7 +70,7 @@ export default class Registry implements RegistryInterface {
|
||||
public query(
|
||||
query: string | Node | Scope,
|
||||
scope: Scope = Scope.ANY,
|
||||
): Attributor | BlotConstructor | null {
|
||||
): RegistryDefinition | null {
|
||||
let match;
|
||||
if (typeof query === 'string') {
|
||||
match = this.types[query] || this.attributes[query];
|
||||
@ -94,53 +97,59 @@ export default class Registry implements RegistryInterface {
|
||||
if (match == null) {
|
||||
return null;
|
||||
}
|
||||
// @ts-expect-error
|
||||
if (scope & Scope.LEVEL & match.scope && scope & Scope.TYPE & match.scope) {
|
||||
if (
|
||||
'scope' in match &&
|
||||
scope & Scope.LEVEL & match.scope &&
|
||||
scope & Scope.TYPE & match.scope
|
||||
) {
|
||||
return match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public register(...definitions: any[]): any {
|
||||
if (definitions.length > 1) {
|
||||
return definitions.map((d) => {
|
||||
return this.register(d);
|
||||
});
|
||||
}
|
||||
const definition = definitions[0];
|
||||
if (
|
||||
typeof definition.blotName !== 'string' &&
|
||||
typeof definition.attrName !== 'string'
|
||||
) {
|
||||
throw new ParchmentError('Invalid definition');
|
||||
} else if (definition.blotName === 'abstract') {
|
||||
throw new ParchmentError('Cannot register abstract class');
|
||||
}
|
||||
this.types[definition.blotName || definition.attrName] = definition;
|
||||
if (typeof definition.keyName === 'string') {
|
||||
this.attributes[definition.keyName] = definition;
|
||||
} else {
|
||||
if (definition.className != null) {
|
||||
this.classes[definition.className] = definition;
|
||||
public register(...definitions: RegistryDefinition[]): RegistryDefinition[] {
|
||||
return definitions.map((definition) => {
|
||||
const isBlot = 'blotName' in definition;
|
||||
const isAttr = 'attrName' in definition;
|
||||
if (!isBlot && !isAttr) {
|
||||
throw new ParchmentError('Invalid definition');
|
||||
} else if (isBlot && definition.blotName === 'abstract') {
|
||||
throw new ParchmentError('Cannot register abstract class');
|
||||
}
|
||||
if (definition.tagName != null) {
|
||||
if (Array.isArray(definition.tagName)) {
|
||||
definition.tagName = definition.tagName.map((tagName: string) => {
|
||||
return tagName.toUpperCase();
|
||||
});
|
||||
} else {
|
||||
definition.tagName = definition.tagName.toUpperCase();
|
||||
const key = isBlot
|
||||
? definition.blotName
|
||||
: isAttr
|
||||
? definition.attrName
|
||||
: (undefined as never); // already handled by above checks
|
||||
this.types[key] = definition;
|
||||
|
||||
if (isAttr) {
|
||||
if (typeof definition.keyName === 'string') {
|
||||
this.attributes[definition.keyName] = definition;
|
||||
}
|
||||
const tagNames = Array.isArray(definition.tagName)
|
||||
? definition.tagName
|
||||
: [definition.tagName];
|
||||
tagNames.forEach((tag: string) => {
|
||||
if (this.tags[tag] == null || definition.className == null) {
|
||||
this.tags[tag] = definition;
|
||||
} else if (isBlot) {
|
||||
if (definition.className) {
|
||||
this.classes[definition.className] = definition;
|
||||
}
|
||||
if (definition.tagName) {
|
||||
if (Array.isArray(definition.tagName)) {
|
||||
definition.tagName = definition.tagName.map((tagName: string) => {
|
||||
return tagName.toUpperCase();
|
||||
});
|
||||
} else {
|
||||
definition.tagName = definition.tagName.toUpperCase();
|
||||
}
|
||||
});
|
||||
const tagNames = Array.isArray(definition.tagName)
|
||||
? definition.tagName
|
||||
: [definition.tagName];
|
||||
tagNames.forEach((tag: string) => {
|
||||
if (this.tags[tag] == null || definition.className == null) {
|
||||
this.tags[tag] = definition;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return definition;
|
||||
return definition;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { expectType } from 'tsd';
|
||||
import { Blot, EmbedBlot, Registry, ScrollBlot, ParentBlot } from '..';
|
||||
import { type Blot, EmbedBlot, Registry, ScrollBlot, ParentBlot } from '..';
|
||||
|
||||
const registry = new Registry();
|
||||
const root = document.createElement('div');
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
import BlockBlot from '../../src/blot/block';
|
||||
|
||||
export class HeaderBlot extends BlockBlot {}
|
||||
HeaderBlot.blotName = 'header';
|
||||
HeaderBlot.tagName = ['h1', 'h2'];
|
||||
export class HeaderBlot extends BlockBlot {
|
||||
static readonly blotName = 'header';
|
||||
static tagName = ['h1', 'h2'];
|
||||
static create(value?: number | string) {
|
||||
return super.create(value) as HTMLHeadingElement;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import EmbedBlot from '../../src/blot/embed';
|
||||
|
||||
export class BreakBlot extends EmbedBlot {}
|
||||
BreakBlot.blotName = 'break';
|
||||
BreakBlot.tagName = 'br';
|
||||
export class BreakBlot extends EmbedBlot {
|
||||
static readonly blotName = 'break';
|
||||
static tagName = 'br';
|
||||
}
|
||||
|
||||
@ -2,26 +2,29 @@ import EmbedBlot from '../../src/blot/embed';
|
||||
import Scope from '../../src/scope';
|
||||
|
||||
export class ImageBlot extends EmbedBlot {
|
||||
static create(value) {
|
||||
let node = super.create(value);
|
||||
declare domNode: HTMLImageElement;
|
||||
static readonly blotName = 'image';
|
||||
static tagName = 'IMG';
|
||||
static create(value: string) {
|
||||
let node = super.create(value) as HTMLElement;
|
||||
if (typeof value === 'string') {
|
||||
node.setAttribute('src', value);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static value(domNode) {
|
||||
static value(domNode: HTMLImageElement) {
|
||||
return domNode.getAttribute('src');
|
||||
}
|
||||
|
||||
static formats(domNode) {
|
||||
static formats(domNode: HTMLImageElement) {
|
||||
if (domNode.hasAttribute('alt')) {
|
||||
return { alt: domNode.getAttribute('alt') };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
format(name, value) {
|
||||
format(name: string, value: string) {
|
||||
if (name === 'alt') {
|
||||
this.domNode.setAttribute(name, value);
|
||||
} else {
|
||||
@ -29,35 +32,37 @@ export class ImageBlot extends EmbedBlot {
|
||||
}
|
||||
}
|
||||
}
|
||||
ImageBlot.blotName = 'image';
|
||||
ImageBlot.tagName = 'IMG';
|
||||
|
||||
export class VideoBlot extends EmbedBlot {
|
||||
static create(value) {
|
||||
let node = super.create(value);
|
||||
declare domNode: HTMLVideoElement;
|
||||
static scope = Scope.BLOCK_BLOT;
|
||||
static readonly blotName = 'video';
|
||||
static tagName = 'VIDEO';
|
||||
static create(value: string) {
|
||||
let node = super.create(value) as HTMLVideoElement;
|
||||
if (typeof value === 'string') {
|
||||
node.setAttribute('src', value);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static formats(domNode) {
|
||||
let formats = {};
|
||||
if (domNode.hasAttribute('height'))
|
||||
formats['height'] = domNode.getAttribute('height');
|
||||
if (domNode.hasAttribute('width'))
|
||||
formats['width'] = domNode.getAttribute('width');
|
||||
static formats(domNode: HTMLVideoElement) {
|
||||
let formats: Partial<{ height: string; width: string }> = {};
|
||||
const height = domNode.getAttribute('height');
|
||||
const width = domNode.getAttribute('width');
|
||||
height && (formats.height = height);
|
||||
width && (formats.width = width);
|
||||
return formats;
|
||||
}
|
||||
|
||||
static value(domNode) {
|
||||
static value(domNode: HTMLVideoElement) {
|
||||
return domNode.getAttribute('src');
|
||||
}
|
||||
|
||||
format(name, value) {
|
||||
format(name: string, value: unknown) {
|
||||
if (name === 'height' || name === 'width') {
|
||||
if (value) {
|
||||
this.domNode.setAttribute(name, value);
|
||||
this.domNode.setAttribute(name, value.toString());
|
||||
} else {
|
||||
this.domNode.removeAttribute(name);
|
||||
}
|
||||
@ -66,6 +71,3 @@ export class VideoBlot extends EmbedBlot {
|
||||
}
|
||||
}
|
||||
}
|
||||
VideoBlot.blotName = 'video';
|
||||
VideoBlot.scope = Scope.BLOCK_BLOT;
|
||||
VideoBlot.tagName = 'VIDEO';
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import InlineBlot from '../../src/blot/inline';
|
||||
|
||||
export class AuthorBlot extends InlineBlot {}
|
||||
AuthorBlot.blotName = 'author';
|
||||
AuthorBlot.className = 'author-blot';
|
||||
export class AuthorBlot extends InlineBlot {
|
||||
static readonly blotName = 'author';
|
||||
static className = 'author-blot';
|
||||
}
|
||||
|
||||
export class BoldBlot extends InlineBlot {}
|
||||
BoldBlot.blotName = 'bold';
|
||||
BoldBlot.tagName = 'STRONG';
|
||||
export class BoldBlot extends InlineBlot {
|
||||
static readonly blotName = 'bold';
|
||||
static tagName = 'strong';
|
||||
}
|
||||
|
||||
export class ItalicBlot extends InlineBlot {}
|
||||
ItalicBlot.blotName = 'italic';
|
||||
ItalicBlot.tagName = 'em';
|
||||
export class ItalicBlot extends InlineBlot {
|
||||
static readonly blotName = 'italic';
|
||||
static tagName = 'em';
|
||||
}
|
||||
|
||||
export class ScriptBlot extends InlineBlot {}
|
||||
ScriptBlot.blotName = 'script';
|
||||
ScriptBlot.tagName = ['sup', 'sub'];
|
||||
export class ScriptBlot extends InlineBlot {
|
||||
static readonly blotName = 'script';
|
||||
static tagName = ['sup', 'sub'];
|
||||
}
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import ContainerBlot from '../../src/blot/abstract/container';
|
||||
import BlockBlot from '../../src/blot/block';
|
||||
|
||||
export class ListItem extends BlockBlot {}
|
||||
ListItem.blotName = 'list';
|
||||
ListItem.tagName = 'LI';
|
||||
export class ListItem extends BlockBlot {
|
||||
static readonly blotName = 'list';
|
||||
static tagName = 'LI';
|
||||
}
|
||||
|
||||
export class ListContainer extends ContainerBlot {}
|
||||
ListContainer.blotName = 'list-container';
|
||||
ListContainer.tagName = 'OL';
|
||||
export class ListContainer extends ContainerBlot {
|
||||
static readonly blotName = 'list-container';
|
||||
static tagName = 'OL';
|
||||
static allowedChildren = [ListItem];
|
||||
}
|
||||
|
||||
ListContainer.allowedChildren = [ListItem];
|
||||
// Can only define outside of ListItem class due to used-before-declaration error
|
||||
ListItem.requiredContainer = ListContainer;
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import Registry from '../src/registry';
|
||||
import { beforeEach } from 'vitest';
|
||||
|
||||
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 {
|
||||
Registry,
|
||||
ScrollBlot,
|
||||
BlockBlot,
|
||||
InlineBlot,
|
||||
TextBlot,
|
||||
type BlotConstructor,
|
||||
} from '../src/parchment';
|
||||
import {
|
||||
AuthorBlot,
|
||||
BoldBlot,
|
||||
@ -19,7 +23,7 @@ import { BreakBlot } from './registry/break';
|
||||
const getTestRegistry = () => {
|
||||
const reg = new Registry();
|
||||
|
||||
reg.register(ScrollBlot);
|
||||
reg.register(ScrollBlot as unknown as BlotConstructor);
|
||||
reg.register(BlockBlot);
|
||||
reg.register(InlineBlot);
|
||||
reg.register(TextBlot);
|
||||
@ -41,7 +45,7 @@ type TestContext = {
|
||||
};
|
||||
|
||||
export const setupContextBeforeEach = () => {
|
||||
const ctx: TestContext = {} as TestContext;
|
||||
const ctx = {} as TestContext;
|
||||
beforeEach(() => {
|
||||
const container = document.createElement('div');
|
||||
const registry = getTestRegistry();
|
||||
|
||||
@ -1,23 +1,34 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type {
|
||||
Attributor,
|
||||
BlockBlot,
|
||||
Formattable,
|
||||
InlineBlot,
|
||||
} from '../../src/parchment';
|
||||
import type { HeaderBlot } from '../registry/block';
|
||||
import type { BoldBlot } from '../registry/inline';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('Attributor', function () {
|
||||
const ctx = setupContextBeforeEach();
|
||||
|
||||
it('build', function () {
|
||||
let blot = ctx.scroll.create('inline');
|
||||
let blot = ctx.scroll.create('inline') as InlineBlot;
|
||||
blot.domNode.style.color = 'red';
|
||||
blot.domNode.style.fontSize = '24px';
|
||||
blot.domNode.id = 'blot-test';
|
||||
blot.domNode.classList.add('indent-2');
|
||||
blot.attributes.build();
|
||||
expect(Object.keys(blot.attributes.attributes).sort()).toEqual(
|
||||
// Use bracket notation to access private fields as escape hatch
|
||||
// https://github.com/microsoft/TypeScript/issues/19335
|
||||
blot['attributes'].build();
|
||||
expect(Object.keys(blot['attributes']['attributes']).sort()).toEqual(
|
||||
['color', 'size', 'id', 'indent'].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
it('add to inline', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let boldBlot = ctx.scroll.create('bold');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let boldBlot = ctx.scroll.create('bold') as BoldBlot;
|
||||
container.appendChild(boldBlot);
|
||||
boldBlot.format('id', 'test-add');
|
||||
expect(boldBlot.domNode.id).toEqual('test-add');
|
||||
@ -35,15 +46,15 @@ describe('Attributor', function () {
|
||||
});
|
||||
|
||||
it('add to text', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
container.appendChild(textBlot);
|
||||
textBlot.formatAt(0, 4, 'color', 'red');
|
||||
expect(textBlot.domNode.parentNode.style.color).toEqual('red');
|
||||
expect(textBlot.domNode.parentElement?.style.color).toEqual('red');
|
||||
});
|
||||
|
||||
it('add existing style', function () {
|
||||
let boldBlot = ctx.scroll.create('bold');
|
||||
let boldBlot = ctx.scroll.create('bold') as BoldBlot;
|
||||
boldBlot.format('color', 'red');
|
||||
expect(boldBlot.domNode.style.color).toEqual('red');
|
||||
let original = boldBlot.domNode.outerHTML;
|
||||
@ -54,7 +65,7 @@ describe('Attributor', function () {
|
||||
});
|
||||
|
||||
it('replace existing class', function () {
|
||||
let blockBlot = ctx.scroll.create('block');
|
||||
let blockBlot = ctx.scroll.create('block') as BlockBlot;
|
||||
blockBlot.format('indent', 2);
|
||||
expect(blockBlot.domNode.classList.contains('indent-2')).toBe(true);
|
||||
blockBlot.format('indent', 3);
|
||||
@ -63,19 +74,19 @@ describe('Attributor', function () {
|
||||
});
|
||||
|
||||
it('add whitelist style', function () {
|
||||
let blockBlot = ctx.scroll.create('block');
|
||||
let blockBlot = ctx.scroll.create('block') as BlockBlot;
|
||||
blockBlot.format('align', 'right');
|
||||
expect(blockBlot.domNode.style.textAlign).toBe('right');
|
||||
});
|
||||
|
||||
it('add non-whitelisted style', function () {
|
||||
let blockBlot = ctx.scroll.create('block');
|
||||
let blockBlot = ctx.scroll.create('block') as BlockBlot;
|
||||
blockBlot.format('align', 'justify');
|
||||
expect(blockBlot.domNode.style.textAlign).toBeFalsy();
|
||||
});
|
||||
|
||||
it('unwrap', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let node = document.createElement('strong');
|
||||
node.style.color = 'red';
|
||||
node.innerHTML = '<em>01</em>23';
|
||||
@ -88,7 +99,7 @@ describe('Attributor', function () {
|
||||
});
|
||||
|
||||
it('remove', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let node = document.createElement('strong');
|
||||
node.innerHTML = 'Bold';
|
||||
node.style.color = 'red';
|
||||
@ -99,7 +110,7 @@ describe('Attributor', function () {
|
||||
container.appendChild(boldBlot);
|
||||
container.formatAt(1, 2, 'color', false);
|
||||
expect(container.children.length).toEqual(3);
|
||||
let targetNode = boldBlot.next.domNode;
|
||||
let targetNode = boldBlot.next?.domNode as HTMLElement;
|
||||
expect(targetNode.style.color).toEqual('');
|
||||
container.formatAt(1, 2, 'size', false);
|
||||
expect(targetNode.style.fontSize).toEqual('');
|
||||
@ -111,17 +122,17 @@ describe('Attributor', function () {
|
||||
});
|
||||
|
||||
it('remove nonexistent', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let node = document.createElement('strong');
|
||||
node.innerHTML = 'Bold';
|
||||
let boldBlot = ctx.scroll.create(node);
|
||||
let boldBlot = ctx.scroll.create(node) as BoldBlot;
|
||||
container.appendChild(boldBlot);
|
||||
boldBlot.format('color', false);
|
||||
expect(container.domNode.innerHTML).toEqual('<strong>Bold</strong>');
|
||||
});
|
||||
|
||||
it('keep class attribute after removal', function () {
|
||||
let boldBlot = ctx.scroll.create('bold');
|
||||
let boldBlot = ctx.scroll.create('bold') as BoldBlot;
|
||||
boldBlot.domNode.classList.add('blot');
|
||||
boldBlot.format('indent', 2);
|
||||
boldBlot.format('indent', false);
|
||||
@ -129,7 +140,7 @@ describe('Attributor', function () {
|
||||
});
|
||||
|
||||
it('move attribute', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let node = document.createElement('strong');
|
||||
node.innerHTML = 'Bold';
|
||||
node.style.color = 'red';
|
||||
@ -137,41 +148,41 @@ describe('Attributor', function () {
|
||||
container.appendChild(boldBlot);
|
||||
container.formatAt(1, 2, 'bold', false);
|
||||
expect(container.children.length).toEqual(3);
|
||||
expect(boldBlot.next.statics.blotName).toEqual('inline');
|
||||
expect(boldBlot.next.formats().color).toEqual('red');
|
||||
expect(boldBlot.next?.statics.blotName).toEqual('inline');
|
||||
expect((boldBlot.next as Formattable)?.formats().color).toEqual('red');
|
||||
});
|
||||
|
||||
it('wrap with inline', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let node = document.createElement('strong');
|
||||
node.style.color = 'red';
|
||||
let boldBlot = ctx.scroll.create(node);
|
||||
container.appendChild(boldBlot);
|
||||
boldBlot.wrap('italic');
|
||||
expect(node.style.color).toBeFalsy();
|
||||
expect(node.parentNode.style.color).toBe('red');
|
||||
expect(node.parentElement?.style.color).toBe('red');
|
||||
});
|
||||
|
||||
it('wrap with block', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let node = document.createElement('strong');
|
||||
node.style.color = 'red';
|
||||
let boldBlot = ctx.scroll.create(node);
|
||||
let boldBlot = ctx.scroll.create(node) as BoldBlot;
|
||||
container.appendChild(boldBlot);
|
||||
boldBlot.wrap('block');
|
||||
expect(node.style.color).toBe('red');
|
||||
expect(node.parentNode.style.color).toBeFalsy();
|
||||
expect(node.parentElement?.style.color).toBeFalsy();
|
||||
});
|
||||
|
||||
it('add to block', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let block = ctx.scroll.create('header', 'h1');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let block = ctx.scroll.create('header', 'h1') as HeaderBlot;
|
||||
container.appendChild(block);
|
||||
block.format('align', 'right');
|
||||
expect(container.domNode.innerHTML).toBe(
|
||||
'<h1 style="text-align: right;"></h1>',
|
||||
);
|
||||
expect(container.children.head.formats()).toEqual({
|
||||
expect((container.children.head as Formattable)?.formats()).toEqual({
|
||||
header: 'h1',
|
||||
align: 'right',
|
||||
});
|
||||
@ -179,20 +190,17 @@ describe('Attributor', function () {
|
||||
|
||||
it('missing class value', function () {
|
||||
let block = ctx.scroll.create('block');
|
||||
let indentAttributor = ctx.scroll.query('indent');
|
||||
expect(indentAttributor.value(block.domNode)).toBeFalsy();
|
||||
let indentAttributor = ctx.scroll.query('indent') as Attributor;
|
||||
expect(indentAttributor.value(block.domNode as HTMLElement)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('removes quotes from attribute value when checking if canAdd', function () {
|
||||
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"'),
|
||||
).toBeTruthy();
|
||||
expect(familyAttributor.canAdd(bold.domNode, 'monotype')).toBeFalsy();
|
||||
expect(
|
||||
familyAttributor.canAdd(bold.domNode, '"Lucida Grande"'),
|
||||
).toBeFalsy();
|
||||
let familyAttributor = ctx.scroll.query('family') as Attributor;
|
||||
const domNode = bold.domNode as HTMLElement;
|
||||
expect(familyAttributor.canAdd(domNode, 'Arial')).toBeTruthy();
|
||||
expect(familyAttributor.canAdd(domNode, '"Times New Roman"')).toBeTruthy();
|
||||
expect(familyAttributor.canAdd(domNode, 'monotype')).toBeFalsy();
|
||||
expect(familyAttributor.canAdd(domNode, '"Lucida Grande"')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { BlockBlot } from '../../src/parchment';
|
||||
import type { HeaderBlot } from '../registry/block';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('Block', function () {
|
||||
@ -5,38 +8,41 @@ describe('Block', function () {
|
||||
|
||||
describe('format', function () {
|
||||
it('add', function () {
|
||||
let block = ctx.scroll.create('block');
|
||||
let block = ctx.scroll.create('block') as BlockBlot;
|
||||
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' });
|
||||
const childrenHead = ctx.scroll.children.head as HeaderBlot;
|
||||
expect(childrenHead.statics.blotName).toBe('header');
|
||||
expect(childrenHead.formats()).toEqual({ header: 'h1' });
|
||||
});
|
||||
|
||||
it('remove', function () {
|
||||
let block = ctx.scroll.create('header', 'h1');
|
||||
let block = ctx.scroll.create('header', 'h1') as HeaderBlot;
|
||||
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({});
|
||||
const childrenHead = ctx.scroll.children.head as BlockBlot;
|
||||
expect(childrenHead.statics.blotName).toBe('block');
|
||||
expect(childrenHead.formats()).toEqual({});
|
||||
});
|
||||
|
||||
it('change', function () {
|
||||
let block = ctx.scroll.create('block');
|
||||
let block = ctx.scroll.create('block') as BlockBlot;
|
||||
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);
|
||||
const childrenHead = ctx.scroll.children.head as HeaderBlot;
|
||||
expect(childrenHead.statics.blotName).toBe('header');
|
||||
expect(childrenHead.formats()).toEqual({ header: 'h2' });
|
||||
expect(childrenHead.children.length).toBe(1);
|
||||
expect(childrenHead.children.head).toBe(text);
|
||||
});
|
||||
|
||||
it('split', function () {
|
||||
let block = ctx.scroll.create('block');
|
||||
let block = ctx.scroll.create('block') as BlockBlot;
|
||||
let text = ctx.scroll.create('text', 'Test');
|
||||
block.appendChild(text);
|
||||
ctx.scroll.appendChild(block);
|
||||
@ -46,16 +52,17 @@ describe('Block', function () {
|
||||
`<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');
|
||||
expect(ctx.scroll.children.head?.next?.statics.blotName).toBe('video');
|
||||
});
|
||||
|
||||
it('ignore inline', function () {
|
||||
let block = ctx.scroll.create('header', 1);
|
||||
let block = ctx.scroll.create('header', 1) as HeaderBlot;
|
||||
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' });
|
||||
const childrenHead = ctx.scroll.children.head as HeaderBlot;
|
||||
expect(childrenHead.statics.blotName).toBe('header');
|
||||
expect(childrenHead.formats()).toEqual({ header: 'h1' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { BlockBlot, Parent } from '../../src/parchment';
|
||||
import Registry from '../../src/registry';
|
||||
import type { ItalicBlot } from '../registry/inline';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('Blot', function () {
|
||||
@ -7,10 +10,10 @@ describe('Blot', function () {
|
||||
it('offset()', function () {
|
||||
let blockNode = document.createElement('p');
|
||||
blockNode.innerHTML = '<span>01</span><em>23<strong>45</strong></em>';
|
||||
let blockBlot = ctx.scroll.create(blockNode);
|
||||
let boldBlot = blockBlot.children.tail.children.tail;
|
||||
expect(boldBlot.offset()).toEqual(2);
|
||||
expect(boldBlot.offset(blockBlot)).toEqual(4);
|
||||
let blockBlot = ctx.scroll.create(blockNode) as BlockBlot;
|
||||
let boldBlot = (blockBlot.children.tail as Parent)?.children.tail;
|
||||
expect(boldBlot?.offset()).toEqual(2);
|
||||
expect(boldBlot?.offset(blockBlot)).toEqual(4);
|
||||
});
|
||||
|
||||
it('detach()', function () {
|
||||
@ -21,7 +24,7 @@ describe('Blot', function () {
|
||||
});
|
||||
|
||||
it('remove()', function () {
|
||||
let blot = ctx.scroll.create('block');
|
||||
let blot = ctx.scroll.create('block') as BlockBlot;
|
||||
let text = ctx.scroll.create('text', 'Test');
|
||||
blot.appendChild(text);
|
||||
expect(blot.children.head).toBe(text);
|
||||
@ -32,7 +35,7 @@ describe('Blot', function () {
|
||||
});
|
||||
|
||||
it('wrap()', function () {
|
||||
let parent = ctx.scroll.create('block');
|
||||
let parent = ctx.scroll.create('block') as BlockBlot;
|
||||
let head = ctx.scroll.create('bold');
|
||||
let text = ctx.scroll.create('text', 'Test');
|
||||
let tail = ctx.scroll.create('bold');
|
||||
@ -47,14 +50,14 @@ describe('Blot', function () {
|
||||
'<strong></strong><em>Test</em><strong></strong>',
|
||||
);
|
||||
expect(parent.children.head).toEqual(head);
|
||||
expect(parent.children.head.next).toEqual(wrapper);
|
||||
expect(parent.children.head?.next).toEqual(wrapper);
|
||||
expect(parent.children.tail).toEqual(tail);
|
||||
});
|
||||
|
||||
it('wrap() with blot', function () {
|
||||
let parent = ctx.scroll.create('block');
|
||||
let parent = ctx.scroll.create('block') as BlockBlot;
|
||||
let text = ctx.scroll.create('text', 'Test');
|
||||
let italic = ctx.scroll.create('italic');
|
||||
let italic = ctx.scroll.create('italic') as ItalicBlot;
|
||||
parent.appendChild(text);
|
||||
text.wrap(italic);
|
||||
expect(parent.domNode.innerHTML).toEqual('<em>Test</em>');
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('Container', function () {
|
||||
@ -10,8 +11,8 @@ describe('Container', function () {
|
||||
describe('enforceAllowedChildren()', function () {
|
||||
it('keep allowed', function () {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = 2;
|
||||
ctx.scroll.domNode.firstChild.appendChild(li);
|
||||
li.innerHTML = '2';
|
||||
ctx.scroll.domNode.firstChild?.appendChild(li);
|
||||
ctx.scroll.update();
|
||||
expect(ctx.scroll.domNode.innerHTML).toEqual(
|
||||
'<ol><li>1</li><li>2</li></ol>',
|
||||
@ -20,16 +21,16 @@ describe('Container', function () {
|
||||
|
||||
it('remove unallowed child', function () {
|
||||
const strong = document.createElement('strong');
|
||||
strong.innerHTML = 2;
|
||||
ctx.scroll.domNode.firstChild.appendChild(strong);
|
||||
strong.innerHTML = '2';
|
||||
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;
|
||||
ctx.scroll.domNode.firstChild.appendChild(header);
|
||||
header.innerHTML = '2';
|
||||
ctx.scroll.domNode.firstChild?.appendChild(header);
|
||||
ctx.scroll.update();
|
||||
expect(ctx.scroll.domNode.innerHTML).toEqual(
|
||||
'<ol><li>1</li></ol><h1>2</h1>',
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { BlockBlot, InlineBlot } from '../../src/parchment';
|
||||
import type { ImageBlot } from '../registry/embed';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('EmbedBlot', function () {
|
||||
const ctx = setupContextBeforeEach();
|
||||
|
||||
it('value()', function () {
|
||||
let imageBlot = ctx.scroll.create('image', 'favicon.ico');
|
||||
let imageBlot = ctx.scroll.create('image', 'favicon.ico') as ImageBlot;
|
||||
expect(imageBlot.value()).toEqual({
|
||||
image: 'favicon.ico',
|
||||
});
|
||||
});
|
||||
|
||||
it('deleteAt()', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let imageBlot = ctx.scroll.create('image');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let imageBlot = ctx.scroll.create('image') as ImageBlot;
|
||||
container.appendChild(imageBlot);
|
||||
container.insertAt(1, '!');
|
||||
container.deleteAt(0, 1);
|
||||
@ -22,23 +25,23 @@ describe('EmbedBlot', function () {
|
||||
});
|
||||
|
||||
it('format()', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let imageBlot = ctx.scroll.create('image');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let imageBlot = ctx.scroll.create('image') as ImageBlot;
|
||||
container.appendChild(imageBlot);
|
||||
imageBlot.format('alt', 'Quill Icon');
|
||||
expect(imageBlot.formats()).toEqual({ alt: 'Quill Icon' });
|
||||
});
|
||||
|
||||
it('formatAt()', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let imageBlot = ctx.scroll.create('image');
|
||||
container.appendChild(imageBlot);
|
||||
container.formatAt(0, 1, 'color', 'red');
|
||||
expect(container.children.head.statics.blotName).toBe('inline');
|
||||
expect(container.children.head?.statics.blotName).toBe('inline');
|
||||
});
|
||||
|
||||
it('insertAt()', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let imageBlot = ctx.scroll.create('image');
|
||||
container.appendChild(imageBlot);
|
||||
imageBlot.insertAt(0, 'image', true);
|
||||
@ -50,22 +53,22 @@ describe('EmbedBlot', function () {
|
||||
it('split()', function () {
|
||||
let blockNode = document.createElement('p');
|
||||
blockNode.innerHTML = '<em>Te</em><img><strong>st</strong>';
|
||||
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);
|
||||
let blockBlot = ctx.scroll.create(blockNode) as BlockBlot;
|
||||
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 = ctx.scroll.create('image');
|
||||
let imageBlot = ctx.scroll.create('image') as ImageBlot;
|
||||
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 = ctx.scroll.create('block');
|
||||
let imageBlot = ctx.scroll.create('image');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let imageBlot = ctx.scroll.create('image') as ImageBlot;
|
||||
container.appendChild(imageBlot);
|
||||
let [node, offset] = imageBlot.position(1, true);
|
||||
expect(node).toEqual(container.domNode);
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { BlockBlot, Leaf } from '../../src/parchment';
|
||||
import type { BoldBlot, ItalicBlot, ScriptBlot } from '../registry/inline';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('InlineBlot', function () {
|
||||
const ctx = setupContextBeforeEach();
|
||||
|
||||
it('format addition', function () {
|
||||
let italicBlot = ctx.scroll.create('italic');
|
||||
let italicBlot = ctx.scroll.create('italic') as ItalicBlot;
|
||||
italicBlot.appendChild(ctx.scroll.create('text', 'Test'));
|
||||
italicBlot.formatAt(1, 2, 'bold', true);
|
||||
expect(italicBlot.domNode.outerHTML).toEqual(
|
||||
@ -13,7 +16,7 @@ describe('InlineBlot', function () {
|
||||
});
|
||||
|
||||
it('format invalid', function () {
|
||||
let boldBlot = ctx.scroll.create('bold');
|
||||
let boldBlot = ctx.scroll.create('bold') as BoldBlot;
|
||||
boldBlot.appendChild(ctx.scroll.create('text', 'Test'));
|
||||
let original = boldBlot.domNode.outerHTML;
|
||||
expect(function () {
|
||||
@ -23,8 +26,8 @@ describe('InlineBlot', function () {
|
||||
});
|
||||
|
||||
it('format existing', function () {
|
||||
let italicBlot = ctx.scroll.create('italic');
|
||||
let boldBlot = ctx.scroll.create('bold');
|
||||
let italicBlot = ctx.scroll.create('italic') as ItalicBlot;
|
||||
let boldBlot = ctx.scroll.create('bold') as BoldBlot;
|
||||
boldBlot.appendChild(ctx.scroll.create('text', 'Test'));
|
||||
italicBlot.appendChild(boldBlot);
|
||||
let original = italicBlot.domNode.outerHTML;
|
||||
@ -36,8 +39,8 @@ describe('InlineBlot', function () {
|
||||
});
|
||||
|
||||
it('format removal nonexistent', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let italicBlot = ctx.scroll.create('italic');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let italicBlot = ctx.scroll.create('italic') as ItalicBlot;
|
||||
italicBlot.appendChild(ctx.scroll.create('text', 'Test'));
|
||||
container.appendChild(italicBlot);
|
||||
let original = italicBlot.domNode.outerHTML;
|
||||
@ -50,25 +53,27 @@ describe('InlineBlot', function () {
|
||||
it('delete + unwrap', function () {
|
||||
let node = document.createElement('p');
|
||||
node.innerHTML = '<em><strong>Test</strong></em>!';
|
||||
let container = ctx.scroll.create(node);
|
||||
let container = ctx.scroll.create(node) as BlockBlot;
|
||||
container.deleteAt(0, 4);
|
||||
expect(container.children.head.value()).toEqual('!');
|
||||
expect((container.children.head as Leaf).value()).toEqual('!');
|
||||
});
|
||||
|
||||
it('formats()', function () {
|
||||
let italic = document.createElement('em');
|
||||
italic.style.color = 'red';
|
||||
italic.innerHTML = '<strong>Test</strong>!';
|
||||
let blot = ctx.scroll.create(italic);
|
||||
let blot = ctx.scroll.create(italic) as ItalicBlot;
|
||||
expect(blot.formats()).toEqual({ italic: true, color: 'red' });
|
||||
});
|
||||
|
||||
it('change', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let script = ctx.scroll.create('script', 'sup');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let script = ctx.scroll.create('script', 'sup') as ScriptBlot;
|
||||
container.appendChild(script);
|
||||
script.format('script', 'sub');
|
||||
expect(container.domNode.innerHTML).toEqual('<sub></sub>');
|
||||
expect(container.children.head.formats()).toEqual({ script: 'sub' });
|
||||
expect((container.children.head as ScriptBlot).formats()).toEqual({
|
||||
script: 'sub',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,15 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import LeafBlot from '../../src/blot/abstract/leaf';
|
||||
import ShadowBlot from '../../src/blot/abstract/shadow';
|
||||
import type {
|
||||
BlockBlot,
|
||||
Blot,
|
||||
InlineBlot,
|
||||
TextBlot,
|
||||
} from '../../src/parchment';
|
||||
import { HeaderBlot } from '../registry/block';
|
||||
import { ImageBlot } from '../registry/embed';
|
||||
import type { ItalicBlot } from '../registry/inline';
|
||||
import { BoldBlot } from '../registry/inline';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
@ -18,21 +26,21 @@ describe('Lifecycle', function () {
|
||||
it('array tagName index', function () {
|
||||
let node = HeaderBlot.create(2);
|
||||
expect(node).toBeTruthy();
|
||||
let blot = ctx.scroll.create(node);
|
||||
let blot = ctx.scroll.create(node) as HeaderBlot;
|
||||
expect(blot.formats()).toEqual({ header: 'h2' });
|
||||
});
|
||||
|
||||
it('array tagName value', function () {
|
||||
let node = HeaderBlot.create('h2');
|
||||
expect(node).toBeTruthy();
|
||||
let blot = ctx.scroll.create(node);
|
||||
let blot = ctx.scroll.create(node) as HeaderBlot;
|
||||
expect(blot.formats()).toEqual({ header: 'h2' });
|
||||
});
|
||||
|
||||
it('array tagName default', function () {
|
||||
let node = HeaderBlot.create();
|
||||
expect(node).toBeTruthy();
|
||||
let blot = ctx.scroll.create(node);
|
||||
let blot = ctx.scroll.create(node) as HeaderBlot;
|
||||
expect(blot.formats()).toEqual({ header: 'h1' });
|
||||
});
|
||||
|
||||
@ -42,9 +50,13 @@ describe('Lifecycle', function () {
|
||||
});
|
||||
|
||||
it('className', function () {
|
||||
class ClassBlot extends ShadowBlot {}
|
||||
ClassBlot.className = 'test';
|
||||
ClassBlot.tagName = 'span';
|
||||
class ClassBlot extends ShadowBlot {
|
||||
static className = 'test';
|
||||
static tagName = 'span';
|
||||
static create() {
|
||||
return super.create() as HTMLElement;
|
||||
}
|
||||
}
|
||||
let node = ClassBlot.create();
|
||||
expect(node).toBeTruthy();
|
||||
expect(node.classList.contains('test')).toBe(true);
|
||||
@ -59,7 +71,7 @@ describe('Lifecycle', function () {
|
||||
'<span style="color: red;"><strong>Te</strong><em>st</em></span>';
|
||||
let block = ctx.scroll.create(node);
|
||||
ctx.scroll.appendChild(block);
|
||||
let span = ctx.scroll.find(node.querySelector('span'));
|
||||
let span = ctx.scroll.find(node.querySelector('span')) as InlineBlot;
|
||||
span.format('color', false);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual(
|
||||
@ -72,8 +84,10 @@ describe('Lifecycle', function () {
|
||||
node.innerHTML = '<em><strong>Test</strong></em>';
|
||||
let block = ctx.scroll.create(node);
|
||||
ctx.scroll.appendChild(block);
|
||||
let text = ctx.scroll.find(node.querySelector('strong').firstChild);
|
||||
text.deleteAt(0, 4);
|
||||
let text = ctx.scroll.find(
|
||||
node.querySelector('strong')?.firstChild as HTMLElement,
|
||||
);
|
||||
text?.deleteAt(0, 4);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual('');
|
||||
});
|
||||
@ -84,10 +98,10 @@ describe('Lifecycle', function () {
|
||||
let block = ctx.scroll.create(node);
|
||||
ctx.scroll.appendChild(block);
|
||||
let text = ctx.scroll.find(node.childNodes[1]);
|
||||
text.formatAt(0, 2, 'bold', true);
|
||||
text?.formatAt(0, 2, 'bold', true);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
|
||||
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
|
||||
expect(ctx.container.querySelector('strong')?.childNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('format recursive merge', function () {
|
||||
@ -97,12 +111,12 @@ describe('Lifecycle', function () {
|
||||
let block = ctx.scroll.create(node);
|
||||
ctx.scroll.appendChild(block);
|
||||
let target = ctx.scroll.find(node.childNodes[1]);
|
||||
target.wrap('italic', true);
|
||||
target?.wrap('italic', true);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual(
|
||||
'<p><em><strong>Test</strong></em></p>',
|
||||
);
|
||||
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
|
||||
expect(ctx.container.querySelector('strong')?.childNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('remove format merge', function () {
|
||||
@ -114,7 +128,7 @@ describe('Lifecycle', function () {
|
||||
block.formatAt(1, 2, 'italic', false);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
|
||||
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
|
||||
expect(ctx.container.querySelector('strong')?.childNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('remove attribute merge', function () {
|
||||
@ -125,7 +139,7 @@ describe('Lifecycle', function () {
|
||||
block.formatAt(1, 2, 'color', false);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual('<p><em>Test</em></p>');
|
||||
expect(ctx.container.querySelector('em').childNodes.length).toBe(1);
|
||||
expect(ctx.container.querySelector('em')?.childNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('format no merge attribute mismatch', function () {
|
||||
@ -149,7 +163,7 @@ describe('Lifecycle', function () {
|
||||
block.deleteAt(1, 2);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual('<p><em>Tt</em></p>');
|
||||
expect(ctx.container.querySelector('em').childNodes.length).toBe(1);
|
||||
expect(ctx.container.querySelector('em')?.childNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('unwrap + recursive merge', function () {
|
||||
@ -162,7 +176,7 @@ describe('Lifecycle', function () {
|
||||
block.formatAt(1, 2, 'color', false);
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual('<p><strong>Test</strong></p>');
|
||||
expect(ctx.container.querySelector('strong').childNodes.length).toBe(1);
|
||||
expect(ctx.container.querySelector('strong')?.childNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('remove text + recursive merge', function () {
|
||||
@ -170,15 +184,15 @@ describe('Lifecycle', function () {
|
||||
node.innerHTML = '<em>Te</em>|<em>st</em>';
|
||||
let block = ctx.scroll.create(node);
|
||||
ctx.scroll.appendChild(block);
|
||||
node.childNodes[1].data = '';
|
||||
(node.childNodes[1] as Text).data = '';
|
||||
ctx.scroll.optimize();
|
||||
expect(ctx.container.innerHTML).toEqual('<p><em>Test</em></p>');
|
||||
expect(ctx.container.firstChild.firstChild.childNodes.length).toBe(1);
|
||||
expect(ctx.container.firstChild?.firstChild?.childNodes.length).toBe(1);
|
||||
});
|
||||
|
||||
it('insert default child', function () {
|
||||
HeaderBlot.defaultChild = ImageBlot;
|
||||
let blot = ctx.scroll.create('header');
|
||||
let blot = ctx.scroll.create('header') as HeaderBlot;
|
||||
expect(blot.domNode.innerHTML).toEqual('');
|
||||
blot.optimize();
|
||||
HeaderBlot.defaultChild = undefined;
|
||||
@ -187,17 +201,35 @@ describe('Lifecycle', function () {
|
||||
});
|
||||
|
||||
describe('update()', function () {
|
||||
// [p, em, strong, text, image, text, p, em, text]
|
||||
const ContentFixture =
|
||||
'<p><em style="color: red;"><strong>Test</strong><img>ing</em></p><p><em>!</em></p>';
|
||||
type Blots /* corresponds to ContentFixture */ = [
|
||||
BlockBlot,
|
||||
ItalicBlot,
|
||||
BoldBlot,
|
||||
TextBlot,
|
||||
ImageBlot,
|
||||
TextBlot,
|
||||
BlockBlot,
|
||||
ItalicBlot,
|
||||
TextBlot,
|
||||
];
|
||||
type UpdateTestContext = {
|
||||
checkUpdateCalls: (called: Blot | Blot[]) => void;
|
||||
checkValues: (expected: any[]) => void;
|
||||
descendants: Blots;
|
||||
};
|
||||
let updateCtx = {} as UpdateTestContext;
|
||||
beforeEach(function () {
|
||||
ctx.container.innerHTML =
|
||||
'<p><em style="color: red;"><strong>Test</strong><img>ing</em></p><p><em>!</em></p>';
|
||||
ctx.container.innerHTML = ContentFixture;
|
||||
ctx.scroll.update();
|
||||
// [p, em, strong, text, image, text, p, em, text]
|
||||
this.descendants = ctx.scroll.descendants(ShadowBlot);
|
||||
this.descendants.forEach(function (blot) {
|
||||
spyOn(blot, 'update').and.callThrough();
|
||||
updateCtx.descendants = ctx.scroll.descendants(ShadowBlot) as Blots;
|
||||
updateCtx.descendants.forEach(function (blot: ShadowBlot) {
|
||||
vi.spyOn(blot, 'update');
|
||||
});
|
||||
this.checkUpdateCalls = (called) => {
|
||||
this.descendants.forEach(function (blot) {
|
||||
updateCtx.checkUpdateCalls = (called) => {
|
||||
updateCtx.descendants.forEach(function (blot) {
|
||||
if (
|
||||
called === blot ||
|
||||
(Array.isArray(called) && called.indexOf(blot) > -1)
|
||||
@ -208,7 +240,7 @@ describe('Lifecycle', function () {
|
||||
}
|
||||
});
|
||||
};
|
||||
this.checkValues = (expected) => {
|
||||
updateCtx.checkValues = (expected) => {
|
||||
let values = ctx.scroll.descendants(LeafBlot).map(function (leaf) {
|
||||
return leaf.value();
|
||||
});
|
||||
@ -220,14 +252,14 @@ describe('Lifecycle', function () {
|
||||
it('insert text', function () {
|
||||
ctx.scroll.insertAt(2, '|');
|
||||
ctx.scroll.optimize();
|
||||
this.checkValues(['Te|st', { image: true }, 'ing', '!']);
|
||||
updateCtx.checkValues(['Te|st', { image: true }, 'ing', '!']);
|
||||
expect(ctx.scroll.observer.takeRecords()).toEqual([]);
|
||||
});
|
||||
|
||||
it('insert embed', function () {
|
||||
ctx.scroll.insertAt(2, 'image', true);
|
||||
ctx.scroll.optimize();
|
||||
this.checkValues([
|
||||
updateCtx.checkValues([
|
||||
'Te',
|
||||
{ image: true },
|
||||
'st',
|
||||
@ -241,24 +273,24 @@ describe('Lifecycle', function () {
|
||||
it('delete', function () {
|
||||
ctx.scroll.deleteAt(2, 5);
|
||||
ctx.scroll.optimize();
|
||||
this.checkValues(['Te', 'g', '!']);
|
||||
updateCtx.checkValues(['Te', 'g', '!']);
|
||||
expect(ctx.scroll.observer.takeRecords()).toEqual([]);
|
||||
});
|
||||
|
||||
it('format', function () {
|
||||
ctx.scroll.formatAt(2, 5, 'size', '24px');
|
||||
ctx.scroll.optimize();
|
||||
this.checkValues(['Te', 'st', { image: true }, 'in', 'g', '!']);
|
||||
updateCtx.checkValues(['Te', 'st', { image: true }, 'in', 'g', '!']);
|
||||
expect(ctx.scroll.observer.takeRecords()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dom', function () {
|
||||
it('change text', function () {
|
||||
let textBlot = this.descendants[3];
|
||||
let textBlot = updateCtx.descendants[3];
|
||||
textBlot.domNode.data = 'Te|st';
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(textBlot);
|
||||
updateCtx.checkUpdateCalls(textBlot);
|
||||
expect(textBlot.value()).toEqual('Te|st');
|
||||
});
|
||||
|
||||
@ -269,14 +301,14 @@ describe('Lifecycle', function () {
|
||||
unknownElement.appendChild(unknownElement2);
|
||||
ctx.scroll.domNode.removeChild(unknownElement);
|
||||
ctx.scroll.update();
|
||||
this.checkValues(['Test', { image: true }, 'ing', '!']);
|
||||
updateCtx.checkValues(['Test', { image: true }, 'ing', '!']);
|
||||
});
|
||||
|
||||
it('add attribute', function () {
|
||||
let attrBlot = this.descendants[1];
|
||||
let attrBlot = updateCtx.descendants[1];
|
||||
attrBlot.domNode.setAttribute('id', 'blot');
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(attrBlot);
|
||||
updateCtx.checkUpdateCalls(attrBlot);
|
||||
expect(attrBlot.formats()).toEqual({
|
||||
color: 'red',
|
||||
italic: true,
|
||||
@ -285,88 +317,88 @@ describe('Lifecycle', function () {
|
||||
});
|
||||
|
||||
it('add embed attribute', function () {
|
||||
let imageBlot = this.descendants[4];
|
||||
let imageBlot = updateCtx.descendants[4];
|
||||
imageBlot.domNode.setAttribute('alt', 'image');
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(imageBlot);
|
||||
updateCtx.checkUpdateCalls(imageBlot);
|
||||
});
|
||||
|
||||
it('change attributes', function () {
|
||||
let attrBlot = this.descendants[1];
|
||||
let attrBlot = updateCtx.descendants[1];
|
||||
attrBlot.domNode.style.color = 'blue';
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(attrBlot);
|
||||
updateCtx.checkUpdateCalls(attrBlot);
|
||||
expect(attrBlot.formats()).toEqual({ color: 'blue', italic: true });
|
||||
});
|
||||
|
||||
it('remove attribute', function () {
|
||||
let attrBlot = this.descendants[1];
|
||||
let attrBlot = updateCtx.descendants[1];
|
||||
attrBlot.domNode.removeAttribute('style');
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(attrBlot);
|
||||
updateCtx.checkUpdateCalls(attrBlot);
|
||||
expect(attrBlot.formats()).toEqual({ italic: true });
|
||||
});
|
||||
|
||||
it('add child node', function () {
|
||||
let italicBlot = this.descendants[1];
|
||||
let italicBlot = updateCtx.descendants[1];
|
||||
italicBlot.domNode.appendChild(document.createTextNode('|'));
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(italicBlot);
|
||||
this.checkValues(['Test', { image: true }, 'ing|', '!']);
|
||||
updateCtx.checkUpdateCalls(italicBlot);
|
||||
updateCtx.checkValues(['Test', { image: true }, 'ing|', '!']);
|
||||
});
|
||||
|
||||
it('add empty family', function () {
|
||||
let blockBlot = this.descendants[0];
|
||||
let blockBlot = updateCtx.descendants[0];
|
||||
let boldNode = document.createElement('strong');
|
||||
let html = ctx.scroll.innerHTML;
|
||||
let html = ctx.scroll.domNode.innerHTML;
|
||||
boldNode.appendChild(document.createTextNode(''));
|
||||
blockBlot.domNode.appendChild(boldNode);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(blockBlot);
|
||||
expect(ctx.scroll.innerHTML).toBe(html);
|
||||
updateCtx.checkUpdateCalls(blockBlot);
|
||||
expect(ctx.scroll.domNode.innerHTML).toBe(html);
|
||||
expect(ctx.scroll.descendants(ShadowBlot).length).toEqual(
|
||||
this.descendants.length,
|
||||
updateCtx.descendants.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('move node up', function () {
|
||||
let imageBlot = this.descendants[4];
|
||||
imageBlot.domNode.parentNode.insertBefore(
|
||||
let imageBlot = updateCtx.descendants[4];
|
||||
imageBlot.domNode.parentNode?.insertBefore(
|
||||
imageBlot.domNode,
|
||||
imageBlot.domNode.previousSibling,
|
||||
);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(imageBlot.parent);
|
||||
this.checkValues([{ image: true }, 'Test', 'ing', '!']);
|
||||
updateCtx.checkUpdateCalls(imageBlot.parent);
|
||||
updateCtx.checkValues([{ image: true }, 'Test', 'ing', '!']);
|
||||
});
|
||||
|
||||
it('move node down', function () {
|
||||
let imageBlot = this.descendants[4];
|
||||
imageBlot.domNode.parentNode.insertBefore(
|
||||
imageBlot.domNode.nextSibling,
|
||||
let imageBlot = updateCtx.descendants[4];
|
||||
imageBlot.domNode.parentNode?.insertBefore(
|
||||
imageBlot.domNode.nextSibling!,
|
||||
imageBlot.domNode,
|
||||
);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(imageBlot.parent);
|
||||
this.checkValues(['Test', 'ing', { image: true }, '!']);
|
||||
updateCtx.checkUpdateCalls(imageBlot.parent);
|
||||
updateCtx.checkValues(['Test', 'ing', { image: true }, '!']);
|
||||
});
|
||||
|
||||
it('move node and change', function () {
|
||||
let firstBlockBlot = this.descendants[0];
|
||||
let lastItalicBlot = this.descendants[7];
|
||||
let firstBlockBlot = updateCtx.descendants[0];
|
||||
let lastItalicBlot = updateCtx.descendants[7];
|
||||
firstBlockBlot.domNode.appendChild(lastItalicBlot.domNode);
|
||||
lastItalicBlot.domNode.innerHTML = '?';
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls([
|
||||
updateCtx.checkUpdateCalls([
|
||||
firstBlockBlot,
|
||||
this.descendants[6],
|
||||
this.descendants[7],
|
||||
updateCtx.descendants[6],
|
||||
updateCtx.descendants[7],
|
||||
]);
|
||||
this.checkValues(['Test', { image: true }, 'ing', '?']);
|
||||
updateCtx.checkValues(['Test', { image: true }, 'ing', '?']);
|
||||
});
|
||||
|
||||
it('add and remove consecutive nodes', function () {
|
||||
let italicBlot = this.descendants[1];
|
||||
let italicBlot = updateCtx.descendants[1];
|
||||
let imageNode = document.createElement('img');
|
||||
let textNode = document.createTextNode('|');
|
||||
let refNode = italicBlot.domNode.childNodes[1]; // Old img
|
||||
@ -374,68 +406,70 @@ describe('Lifecycle', function () {
|
||||
italicBlot.domNode.insertBefore(imageNode, textNode);
|
||||
italicBlot.domNode.removeChild(refNode);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(italicBlot);
|
||||
this.checkValues(['Test', { image: true }, '|ing', '!']);
|
||||
updateCtx.checkUpdateCalls(italicBlot);
|
||||
updateCtx.checkValues(['Test', { image: true }, '|ing', '!']);
|
||||
});
|
||||
|
||||
it('wrap text', function () {
|
||||
let textNode = this.descendants[5].domNode;
|
||||
let textNode = updateCtx.descendants[5].domNode;
|
||||
let spanNode = document.createElement('span');
|
||||
textNode.parentNode.removeChild(textNode);
|
||||
ctx.scroll.domNode.lastChild.appendChild(spanNode);
|
||||
textNode.parentNode?.removeChild(textNode);
|
||||
ctx.scroll.domNode.lastChild?.appendChild(spanNode);
|
||||
spanNode.appendChild(textNode);
|
||||
ctx.scroll.update();
|
||||
this.checkValues(['Test', { image: true }, '!', 'ing']);
|
||||
updateCtx.checkValues(['Test', { image: true }, '!', 'ing']);
|
||||
});
|
||||
|
||||
it('add then remove same node', function () {
|
||||
let italicBlot = this.descendants[1];
|
||||
let italicBlot = updateCtx.descendants[1];
|
||||
let textNode = document.createTextNode('|');
|
||||
italicBlot.domNode.appendChild(textNode);
|
||||
italicBlot.domNode.removeChild(textNode);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(italicBlot);
|
||||
this.checkValues(['Test', { image: true }, 'ing', '!']);
|
||||
updateCtx.checkUpdateCalls(italicBlot);
|
||||
updateCtx.checkValues(['Test', { image: true }, 'ing', '!']);
|
||||
});
|
||||
|
||||
it('remove child node', function () {
|
||||
let imageBlot = this.descendants[4];
|
||||
imageBlot.domNode.parentNode.removeChild(imageBlot.domNode);
|
||||
let imageBlot = updateCtx.descendants[4];
|
||||
imageBlot.domNode.parentNode?.removeChild(imageBlot.domNode);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(this.descendants[1]);
|
||||
this.checkValues(['Test', 'ing', '!']);
|
||||
updateCtx.checkUpdateCalls(updateCtx.descendants[1]);
|
||||
updateCtx.checkValues(['Test', 'ing', '!']);
|
||||
});
|
||||
|
||||
it('change and remove node', function () {
|
||||
let italicBlot = this.descendants[1];
|
||||
let italicBlot = updateCtx.descendants[1];
|
||||
// @ts-expect-error This simulates ignored dom mutation
|
||||
italicBlot.domNode.color = 'blue';
|
||||
italicBlot.domNode.parentNode.removeChild(italicBlot.domNode);
|
||||
italicBlot.domNode.parentNode?.removeChild(italicBlot.domNode);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(italicBlot.parent);
|
||||
this.checkValues(['!']);
|
||||
updateCtx.checkUpdateCalls(italicBlot.parent);
|
||||
updateCtx.checkValues(['!']);
|
||||
});
|
||||
|
||||
it('change and remove parent', function () {
|
||||
let blockBlot = this.descendants[0];
|
||||
let italicBlot = this.descendants[1];
|
||||
let blockBlot = updateCtx.descendants[0];
|
||||
let italicBlot = updateCtx.descendants[1];
|
||||
// @ts-expect-error This simulates ignored dom mutation
|
||||
italicBlot.domNode.color = 'blue';
|
||||
ctx.scroll.domNode.removeChild(blockBlot.domNode);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls([]);
|
||||
this.checkValues(['!']);
|
||||
updateCtx.checkUpdateCalls([]);
|
||||
updateCtx.checkValues(['!']);
|
||||
});
|
||||
|
||||
it('different changes to same blot', function () {
|
||||
let attrBlot = this.descendants[1];
|
||||
let attrBlot = updateCtx.descendants[1];
|
||||
attrBlot.domNode.style.color = 'blue';
|
||||
attrBlot.domNode.insertBefore(
|
||||
document.createTextNode('|'),
|
||||
attrBlot.domNode.childNodes[1],
|
||||
);
|
||||
ctx.scroll.update();
|
||||
this.checkUpdateCalls(attrBlot);
|
||||
updateCtx.checkUpdateCalls(attrBlot);
|
||||
expect(attrBlot.formats()).toEqual({ color: 'blue', italic: true });
|
||||
this.checkValues(['Test', '|', { image: true }, 'ing', '!']);
|
||||
updateCtx.checkValues(['Test', '|', { image: true }, 'ing', '!']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,249 +1,259 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import LinkedList from '../../src/collection/linked-list';
|
||||
import type { LinkedNode } from '../../src/parchment';
|
||||
|
||||
interface StrNode extends LinkedNode {
|
||||
str: string;
|
||||
}
|
||||
|
||||
const setupContextBeforeEach = () => {
|
||||
const getContext = () => {
|
||||
const length = () => 3;
|
||||
return {
|
||||
list: new LinkedList<StrNode>(),
|
||||
a: { str: 'a', length } as StrNode,
|
||||
b: { str: 'b', length } as StrNode,
|
||||
c: { str: 'c', length } as StrNode,
|
||||
zero: { str: '!', length: () => 0 } as StrNode,
|
||||
};
|
||||
};
|
||||
let ctx = getContext();
|
||||
beforeEach(function () {
|
||||
Object.assign(ctx, getContext());
|
||||
});
|
||||
return ctx;
|
||||
};
|
||||
|
||||
describe('LinkedList', function () {
|
||||
beforeEach(function () {
|
||||
this.list = new LinkedList();
|
||||
this.a = { str: 'a' };
|
||||
this.b = { str: 'b' };
|
||||
this.c = { str: 'c' };
|
||||
this.zero = {
|
||||
str: '!',
|
||||
length: function () {
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
this.a.length = this.b.length = this.c.length = () => 3;
|
||||
});
|
||||
const ctx = setupContextBeforeEach();
|
||||
|
||||
describe('manipulation', function () {
|
||||
it('append to empty list', function () {
|
||||
this.list.append(this.a);
|
||||
expect(this.list.length).toBe(1);
|
||||
expect(this.list.head).toBe(this.a);
|
||||
expect(this.list.tail).toBe(this.a);
|
||||
expect(this.a.prev).toBeNull();
|
||||
expect(this.a.next).toBeNull();
|
||||
ctx.list.append(ctx.a);
|
||||
expect(ctx.list.length).toBe(1);
|
||||
expect(ctx.list.head).toBe(ctx.a);
|
||||
expect(ctx.list.tail).toBe(ctx.a);
|
||||
expect(ctx.a.prev).toBeNull();
|
||||
expect(ctx.a.next).toBeNull();
|
||||
});
|
||||
|
||||
it('insert to become head', function () {
|
||||
this.list.append(this.b);
|
||||
this.list.insertBefore(this.a, this.b);
|
||||
expect(this.list.length).toBe(2);
|
||||
expect(this.list.head).toBe(this.a);
|
||||
expect(this.list.tail).toBe(this.b);
|
||||
expect(this.a.prev).toBeNull();
|
||||
expect(this.a.next).toBe(this.b);
|
||||
expect(this.b.prev).toBe(this.a);
|
||||
expect(this.b.next).toBeNull();
|
||||
ctx.list.append(ctx.b);
|
||||
ctx.list.insertBefore(ctx.a, ctx.b);
|
||||
expect(ctx.list.length).toBe(2);
|
||||
expect(ctx.list.head).toBe(ctx.a);
|
||||
expect(ctx.list.tail).toBe(ctx.b);
|
||||
expect(ctx.a.prev).toBeNull();
|
||||
expect(ctx.a.next).toBe(ctx.b);
|
||||
expect(ctx.b.prev).toBe(ctx.a);
|
||||
expect(ctx.b.next).toBeNull();
|
||||
});
|
||||
|
||||
it('insert to become tail', function () {
|
||||
this.list.append(this.a);
|
||||
this.list.insertBefore(this.b, null);
|
||||
expect(this.list.length).toBe(2);
|
||||
expect(this.list.head).toBe(this.a);
|
||||
expect(this.list.tail).toBe(this.b);
|
||||
expect(this.a.prev).toBeNull();
|
||||
expect(this.a.next).toBe(this.b);
|
||||
expect(this.b.prev).toBe(this.a);
|
||||
expect(this.b.next).toBeNull();
|
||||
ctx.list.append(ctx.a);
|
||||
ctx.list.insertBefore(ctx.b, null);
|
||||
expect(ctx.list.length).toBe(2);
|
||||
expect(ctx.list.head).toBe(ctx.a);
|
||||
expect(ctx.list.tail).toBe(ctx.b);
|
||||
expect(ctx.a.prev).toBeNull();
|
||||
expect(ctx.a.next).toBe(ctx.b);
|
||||
expect(ctx.b.prev).toBe(ctx.a);
|
||||
expect(ctx.b.next).toBeNull();
|
||||
});
|
||||
|
||||
it('insert in middle', function () {
|
||||
this.list.append(this.a, this.c);
|
||||
this.list.insertBefore(this.b, this.c);
|
||||
expect(this.list.length).toBe(3);
|
||||
expect(this.list.head).toBe(this.a);
|
||||
expect(this.a.next).toBe(this.b);
|
||||
expect(this.b.next).toBe(this.c);
|
||||
expect(this.list.tail).toBe(this.c);
|
||||
ctx.list.append(ctx.a, ctx.c);
|
||||
ctx.list.insertBefore(ctx.b, ctx.c);
|
||||
expect(ctx.list.length).toBe(3);
|
||||
expect(ctx.list.head).toBe(ctx.a);
|
||||
expect(ctx.a.next).toBe(ctx.b);
|
||||
expect(ctx.b.next).toBe(ctx.c);
|
||||
expect(ctx.list.tail).toBe(ctx.c);
|
||||
});
|
||||
|
||||
it('remove head', function () {
|
||||
this.list.append(this.a, this.b);
|
||||
this.list.remove(this.a);
|
||||
expect(this.list.length).toBe(1);
|
||||
expect(this.list.head).toBe(this.b);
|
||||
expect(this.list.tail).toBe(this.b);
|
||||
expect(this.list.head.prev).toBeNull();
|
||||
expect(this.list.tail.next).toBeNull();
|
||||
ctx.list.append(ctx.a, ctx.b);
|
||||
ctx.list.remove(ctx.a);
|
||||
expect(ctx.list.length).toBe(1);
|
||||
expect(ctx.list.head).toBe(ctx.b);
|
||||
expect(ctx.list.tail).toBe(ctx.b);
|
||||
expect(ctx.list.head?.prev).toBeNull();
|
||||
expect(ctx.list.tail?.next).toBeNull();
|
||||
});
|
||||
|
||||
it('remove tail', function () {
|
||||
this.list.append(this.a, this.b);
|
||||
this.list.remove(this.b);
|
||||
expect(this.list.length).toBe(1);
|
||||
expect(this.list.head).toBe(this.a);
|
||||
expect(this.list.tail).toBe(this.a);
|
||||
expect(this.list.head.prev).toBeNull();
|
||||
expect(this.list.tail.next).toBeNull();
|
||||
ctx.list.append(ctx.a, ctx.b);
|
||||
ctx.list.remove(ctx.b);
|
||||
expect(ctx.list.length).toBe(1);
|
||||
expect(ctx.list.head).toBe(ctx.a);
|
||||
expect(ctx.list.tail).toBe(ctx.a);
|
||||
expect(ctx.list.head?.prev).toBeNull();
|
||||
expect(ctx.list.tail?.next).toBeNull();
|
||||
});
|
||||
|
||||
it('remove inner', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
this.list.remove(this.b);
|
||||
expect(this.list.length).toBe(2);
|
||||
expect(this.list.head).toBe(this.a);
|
||||
expect(this.list.tail).toBe(this.c);
|
||||
expect(this.list.head.prev).toBeNull();
|
||||
expect(this.list.tail.next).toBeNull();
|
||||
expect(this.a.next).toBe(this.c);
|
||||
expect(this.c.prev).toBe(this.a);
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
ctx.list.remove(ctx.b);
|
||||
expect(ctx.list.length).toBe(2);
|
||||
expect(ctx.list.head).toBe(ctx.a);
|
||||
expect(ctx.list.tail).toBe(ctx.c);
|
||||
expect(ctx.list.head?.prev).toBeNull();
|
||||
expect(ctx.list.tail?.next).toBeNull();
|
||||
expect(ctx.a.next).toBe(ctx.c);
|
||||
expect(ctx.c.prev).toBe(ctx.a);
|
||||
// Maintain references
|
||||
expect(this.b.prev).toBe(this.a);
|
||||
expect(this.b.next).toBe(this.c);
|
||||
expect(ctx.b.prev).toBe(ctx.a);
|
||||
expect(ctx.b.next).toBe(ctx.c);
|
||||
});
|
||||
|
||||
it('remove only node', function () {
|
||||
this.list.append(this.a);
|
||||
this.list.remove(this.a);
|
||||
expect(this.list.length).toBe(0);
|
||||
expect(this.list.head).toBeNull();
|
||||
expect(this.list.tail).toBeNull();
|
||||
ctx.list.append(ctx.a);
|
||||
ctx.list.remove(ctx.a);
|
||||
expect(ctx.list.length).toBe(0);
|
||||
expect(ctx.list.head).toBeNull();
|
||||
expect(ctx.list.tail).toBeNull();
|
||||
});
|
||||
|
||||
it('contains', function () {
|
||||
this.list.append(this.a, this.b);
|
||||
expect(this.list.contains(this.a)).toBe(true);
|
||||
expect(this.list.contains(this.b)).toBe(true);
|
||||
expect(this.list.contains(this.c)).toBe(false);
|
||||
ctx.list.append(ctx.a, ctx.b);
|
||||
expect(ctx.list.contains(ctx.a)).toBe(true);
|
||||
expect(ctx.list.contains(ctx.b)).toBe(true);
|
||||
expect(ctx.list.contains(ctx.c)).toBe(false);
|
||||
});
|
||||
|
||||
it('move', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
this.list.remove(this.b);
|
||||
this.list.remove(this.a);
|
||||
this.list.remove(this.c);
|
||||
this.list.append(this.b);
|
||||
expect(this.b.prev).toBeNull();
|
||||
expect(this.b.next).toBeNull();
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
ctx.list.remove(ctx.b);
|
||||
ctx.list.remove(ctx.a);
|
||||
ctx.list.remove(ctx.c);
|
||||
ctx.list.append(ctx.b);
|
||||
expect(ctx.b.prev).toBeNull();
|
||||
expect(ctx.b.next).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('iteration', function () {
|
||||
const spy = vi.fn();
|
||||
beforeEach(function () {
|
||||
this.spy = {
|
||||
callback: function () {
|
||||
return arguments;
|
||||
},
|
||||
};
|
||||
spyOn(this.spy, 'callback');
|
||||
spy.mockReset();
|
||||
});
|
||||
|
||||
it('iterate over empty list', function () {
|
||||
this.list.forEach(this.spy.callback);
|
||||
expect(this.spy.callback.calls.count()).toBe(0);
|
||||
ctx.list.forEach(spy);
|
||||
expect(spy.mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('iterate non-head start', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
let next = this.list.iterator(this.b);
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
let next = ctx.list.iterator(ctx.b);
|
||||
let b = next();
|
||||
let c = next();
|
||||
let d = next();
|
||||
expect(b).toBe(this.b);
|
||||
expect(c).toBe(this.c);
|
||||
expect(b).toBe(ctx.b);
|
||||
expect(c).toBe(ctx.c);
|
||||
expect(d).toBeNull();
|
||||
});
|
||||
|
||||
it('find', function () {
|
||||
this.list.append(this.a, this.b, this.zero, this.c);
|
||||
expect(this.list.find(0)).toEqual([this.a, 0]);
|
||||
expect(this.list.find(2)).toEqual([this.a, 2]);
|
||||
expect(this.list.find(6)).toEqual([this.c, 0]);
|
||||
expect(this.list.find(3, true)).toEqual([this.a, 3]);
|
||||
expect(this.list.find(6, true)).toEqual([this.zero, 0]);
|
||||
expect(this.list.find(3)).toEqual([this.b, 0]);
|
||||
expect(this.list.find(4)).toEqual([this.b, 1]);
|
||||
expect(this.list.find(10)).toEqual([null, 0]);
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.zero, ctx.c);
|
||||
expect(ctx.list.find(0)).toEqual([ctx.a, 0]);
|
||||
expect(ctx.list.find(2)).toEqual([ctx.a, 2]);
|
||||
expect(ctx.list.find(6)).toEqual([ctx.c, 0]);
|
||||
expect(ctx.list.find(3, true)).toEqual([ctx.a, 3]);
|
||||
expect(ctx.list.find(6, true)).toEqual([ctx.zero, 0]);
|
||||
expect(ctx.list.find(3)).toEqual([ctx.b, 0]);
|
||||
expect(ctx.list.find(4)).toEqual([ctx.b, 1]);
|
||||
expect(ctx.list.find(10)).toEqual([null, 0]);
|
||||
});
|
||||
|
||||
it('offset', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
expect(this.list.offset(this.a)).toBe(0);
|
||||
expect(this.list.offset(this.b)).toBe(3);
|
||||
expect(this.list.offset(this.c)).toBe(6);
|
||||
expect(this.list.offset({})).toBe(-1);
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
expect(ctx.list.offset(ctx.a)).toBe(0);
|
||||
expect(ctx.list.offset(ctx.b)).toBe(3);
|
||||
expect(ctx.list.offset(ctx.c)).toBe(6);
|
||||
// @ts-ignore This tests invalid usage
|
||||
expect(ctx.list.offset({})).toBe(-1);
|
||||
});
|
||||
|
||||
it('forEach', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
this.list.forEach(this.spy.callback);
|
||||
expect(this.spy.callback.calls.count()).toBe(3);
|
||||
let result = this.spy.callback.calls.all().reduce(function (memo, call) {
|
||||
return memo + call.args[0].str;
|
||||
}, '');
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
ctx.list.forEach(spy);
|
||||
expect(spy.mock.calls.length).toBe(3);
|
||||
const result = spy.mock.calls.reduce(
|
||||
(memo: string, call: StrNode[]) => memo + call[0].str,
|
||||
'',
|
||||
);
|
||||
expect(result).toBe('abc');
|
||||
});
|
||||
|
||||
it('destructive modification', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
let arr = [];
|
||||
this.list.forEach((node) => {
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
let arr: string[] = [];
|
||||
ctx.list.forEach((node) => {
|
||||
arr.push(node.str);
|
||||
if (node === this.a) {
|
||||
this.list.remove(this.a);
|
||||
this.list.remove(this.b);
|
||||
this.list.append(this.a);
|
||||
if (node === ctx.a) {
|
||||
ctx.list.remove(ctx.a);
|
||||
ctx.list.remove(ctx.b);
|
||||
ctx.list.append(ctx.a);
|
||||
}
|
||||
});
|
||||
expect(arr).toEqual(['a', 'b', 'c', 'a']);
|
||||
});
|
||||
|
||||
it('map', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
let arr = this.list.map(function (node) {
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
let arr = ctx.list.map(function (node) {
|
||||
return node.str;
|
||||
});
|
||||
expect(arr).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('reduce', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
let memo = this.list.reduce(function (memo, node) {
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
let memo = ctx.list.reduce(function (memo, node) {
|
||||
return memo + node.str;
|
||||
}, '');
|
||||
expect(memo).toBe('abc');
|
||||
});
|
||||
|
||||
it('forEachAt', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
this.list.forEachAt(3, 3, this.spy.callback);
|
||||
expect(this.spy.callback.calls.count()).toBe(1);
|
||||
expect(this.spy.callback.calls.first().args).toEqual([this.b, 0, 3]);
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
ctx.list.forEachAt(3, 3, spy);
|
||||
expect(spy.mock.calls.length).toBe(1);
|
||||
expect(spy.mock.calls[0]).toEqual([ctx.b, 0, 3]);
|
||||
});
|
||||
|
||||
it('forEachAt zero length nodes', function () {
|
||||
this.list.append(this.a, this.zero, this.c);
|
||||
this.list.forEachAt(2, 2, this.spy.callback);
|
||||
expect(this.spy.callback.calls.count()).toBe(3);
|
||||
let calls = this.spy.callback.calls.all();
|
||||
expect(calls[0].args).toEqual([this.a, 2, 1]);
|
||||
expect(calls[1].args).toEqual([this.zero, 0, 0]);
|
||||
expect(calls[2].args).toEqual([this.c, 0, 1]);
|
||||
ctx.list.append(ctx.a, ctx.zero, ctx.c);
|
||||
ctx.list.forEachAt(2, 2, spy);
|
||||
expect(spy.mock.calls.length).toBe(3);
|
||||
let calls = spy.mock.calls;
|
||||
expect(calls[0]).toEqual([ctx.a, 2, 1]);
|
||||
expect(calls[1]).toEqual([ctx.zero, 0, 0]);
|
||||
expect(calls[2]).toEqual([ctx.c, 0, 1]);
|
||||
});
|
||||
|
||||
it('forEachAt none', function () {
|
||||
this.list.append(this.a, this.b);
|
||||
this.list.forEachAt(1, 0, this.spy.callback);
|
||||
expect(this.spy.callback.calls.count()).toBe(0);
|
||||
ctx.list.append(ctx.a, ctx.b);
|
||||
ctx.list.forEachAt(1, 0, spy);
|
||||
expect(spy.mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('forEachAt partial nodes', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
this.list.forEachAt(1, 7, this.spy.callback);
|
||||
expect(this.spy.callback.calls.count()).toBe(3);
|
||||
let calls = this.spy.callback.calls.all();
|
||||
expect(calls[0].args).toEqual([this.a, 1, 2]);
|
||||
expect(calls[1].args).toEqual([this.b, 0, 3]);
|
||||
expect(calls[2].args).toEqual([this.c, 0, 2]);
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
ctx.list.forEachAt(1, 7, spy);
|
||||
expect(spy.mock.calls.length).toBe(3);
|
||||
let calls = spy.mock.calls;
|
||||
expect(calls[0]).toEqual([ctx.a, 1, 2]);
|
||||
expect(calls[1]).toEqual([ctx.b, 0, 3]);
|
||||
expect(calls[2]).toEqual([ctx.c, 0, 2]);
|
||||
});
|
||||
|
||||
it('forEachAt at part of single node', function () {
|
||||
this.list.append(this.a, this.b, this.c);
|
||||
this.list.forEachAt(4, 1, this.spy.callback);
|
||||
expect(this.spy.callback.calls.count()).toBe(1);
|
||||
expect(this.spy.callback.calls.first().args).toEqual([this.b, 1, 1]);
|
||||
ctx.list.append(ctx.a, ctx.b, ctx.c);
|
||||
ctx.list.forEachAt(4, 1, spy);
|
||||
expect(spy.mock.calls.length).toBe(1);
|
||||
expect(spy.mock.calls[0]).toEqual([ctx.b, 1, 1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import LeafBlot from '../../src/blot/abstract/leaf';
|
||||
import ParentBlot from '../../src/blot/abstract/parent';
|
||||
import ShadowBlot from '../../src/blot/abstract/shadow';
|
||||
@ -9,41 +10,44 @@ import { ItalicBlot } from '../registry/inline';
|
||||
import Registry from '../../src/registry';
|
||||
import TextBlot from '../../src/blot/text';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
import type { BlockBlot, Blot } from '../../src/parchment';
|
||||
|
||||
describe('Parent', function () {
|
||||
const ctx = setupContextBeforeEach();
|
||||
|
||||
let testBlot!: BlockBlot;
|
||||
|
||||
beforeEach(function () {
|
||||
let node = document.createElement('p');
|
||||
node.innerHTML = '<span>0</span><em>1<strong>2</strong><img></em>4';
|
||||
this.blot = ctx.registry.create(ctx.scroll, node);
|
||||
testBlot = ctx.registry.create(ctx.scroll, node) as BlockBlot;
|
||||
});
|
||||
|
||||
describe('descendants()', function () {
|
||||
it('all', function () {
|
||||
expect(this.blot.descendants(ShadowBlot).length).toEqual(8);
|
||||
expect(testBlot.descendants(ShadowBlot).length).toEqual(8);
|
||||
});
|
||||
|
||||
it('container', function () {
|
||||
expect(this.blot.descendants(ParentBlot).length).toEqual(3);
|
||||
expect(testBlot.descendants(ParentBlot).length).toEqual(3);
|
||||
});
|
||||
|
||||
it('leaf', function () {
|
||||
expect(this.blot.descendants(LeafBlot).length).toEqual(5);
|
||||
expect(testBlot.descendants(LeafBlot).length).toEqual(5);
|
||||
});
|
||||
|
||||
it('embed', function () {
|
||||
expect(this.blot.descendants(EmbedBlot).length).toEqual(1);
|
||||
expect(testBlot.descendants(EmbedBlot).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('range', function () {
|
||||
expect(this.blot.descendants(TextBlot, 1, 3).length).toEqual(2);
|
||||
expect(testBlot.descendants(TextBlot, 1, 3).length).toEqual(2);
|
||||
});
|
||||
|
||||
it('function match', function () {
|
||||
expect(
|
||||
this.blot.descendants(
|
||||
function (blot) {
|
||||
testBlot.descendants(
|
||||
function (blot: Blot) {
|
||||
return blot instanceof TextBlot;
|
||||
},
|
||||
1,
|
||||
@ -55,13 +59,13 @@ describe('Parent', function () {
|
||||
|
||||
describe('descendant', function () {
|
||||
it('index', function () {
|
||||
let [blot, offset] = this.blot.descendant(ItalicBlot, 3);
|
||||
let [blot, offset] = testBlot.descendant(ItalicBlot, 3);
|
||||
expect(blot instanceof ItalicBlot).toBe(true);
|
||||
expect(offset).toEqual(2);
|
||||
});
|
||||
|
||||
it('function match', function () {
|
||||
let [blot, offset] = this.blot.descendant(function (blot) {
|
||||
let [blot, offset] = testBlot.descendant(function (blot: Blot) {
|
||||
return blot instanceof ItalicBlot;
|
||||
}, 3);
|
||||
expect(blot instanceof ItalicBlot).toBe(true);
|
||||
@ -69,18 +73,18 @@ describe('Parent', function () {
|
||||
});
|
||||
|
||||
it('no match', function () {
|
||||
let [blot, offset] = this.blot.descendant(VideoBlot, 1);
|
||||
let [blot, offset] = testBlot.descendant(VideoBlot, 1);
|
||||
expect(blot).toEqual(null);
|
||||
expect(offset).toEqual(-1);
|
||||
});
|
||||
});
|
||||
|
||||
it('detach()', function () {
|
||||
expect(Registry.blots.get(this.blot.domNode)).toEqual(this.blot);
|
||||
expect(this.blot.descendants(ShadowBlot).length).toEqual(8);
|
||||
this.blot.detach();
|
||||
expect(Registry.blots.has(this.blot.domNode)).toBe(false);
|
||||
this.blot.descendants(ShadowBlot).forEach((blot) => {
|
||||
expect(Registry.blots.get(testBlot.domNode)).toEqual(testBlot);
|
||||
expect(testBlot.descendants(ShadowBlot).length).toEqual(8);
|
||||
testBlot.detach();
|
||||
expect(Registry.blots.has(testBlot.domNode)).toBe(false);
|
||||
testBlot.descendants(ShadowBlot).forEach((blot) => {
|
||||
expect(Registry.blots.has(blot.domNode)).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -94,8 +98,8 @@ describe('Parent', function () {
|
||||
});
|
||||
|
||||
it('ignore added uiNode', function () {
|
||||
ctx.scroll.appendChild(this.blot);
|
||||
this.blot.attachUI(document.createElement('div'));
|
||||
ctx.scroll.appendChild(testBlot);
|
||||
testBlot.attachUI(document.createElement('div'));
|
||||
ctx.scroll.update();
|
||||
expect(ctx.scroll.domNode.innerHTML).toEqual(
|
||||
'<p><div contenteditable="false"></div>0<em>1<strong>2</strong><img></em>4</p>',
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import Scope from '../../src/scope';
|
||||
import { HeaderBlot } from '../registry/block';
|
||||
import { AuthorBlot, BoldBlot, ItalicBlot } from '../registry/inline';
|
||||
@ -5,6 +6,8 @@ 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 type { Parent } from '../../src/parchment';
|
||||
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('ctx.registry', function () {
|
||||
@ -38,12 +41,14 @@ describe('ctx.registry', function () {
|
||||
|
||||
it('string index', function () {
|
||||
let blot = ctx.registry.create(ctx.scroll, 'header', '2');
|
||||
expect(blot instanceof HeaderBlot).toBe(true);
|
||||
expect(blot.formats()).toEqual({ header: 'h2' });
|
||||
expect(blot instanceof HeaderBlot && blot.formats()).toEqual({
|
||||
header: 'h2',
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid', function () {
|
||||
expect(() => {
|
||||
// @ts-expect-error This tests invalid usage
|
||||
ctx.registry.create(ctx.scroll, BoldBlot);
|
||||
}).toThrowError(/\[Parchment\]/);
|
||||
});
|
||||
@ -52,6 +57,7 @@ describe('ctx.registry', function () {
|
||||
describe('register()', function () {
|
||||
it('invalid', function () {
|
||||
expect(function () {
|
||||
// @ts-expect-error This tests invalid usage
|
||||
ctx.registry.register({});
|
||||
}).toThrowError(/\[Parchment\]/);
|
||||
});
|
||||
@ -67,7 +73,7 @@ describe('ctx.registry', function () {
|
||||
it('exact', function () {
|
||||
let blockNode = document.createElement('p');
|
||||
blockNode.innerHTML = '<span>01</span><em>23<strong>45</strong></em>';
|
||||
let blockBlot = ctx.registry.create(ctx.scroll, blockNode);
|
||||
let blockBlot = ctx.registry.create(ctx.scroll, blockNode) as BlockBlot;
|
||||
expect(ctx.registry.find(document.body)).toBeFalsy();
|
||||
expect(ctx.registry.find(blockNode)).toBe(blockBlot);
|
||||
expect(ctx.registry.find(blockNode.querySelector('span'))).toBe(
|
||||
@ -77,11 +83,12 @@ describe('ctx.registry', function () {
|
||||
blockBlot.children.tail,
|
||||
);
|
||||
expect(ctx.registry.find(blockNode.querySelector('strong'))).toBe(
|
||||
blockBlot.children.tail.children.tail,
|
||||
(blockBlot.children.tail as Parent)?.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;
|
||||
let text01 = (blockBlot.children.head as Parent).children.head!;
|
||||
let text23 = (blockBlot.children.tail as Parent).children.head!;
|
||||
let text45 = ((blockBlot.children.tail as Parent).children.tail as Parent)
|
||||
.children.head!;
|
||||
expect(ctx.registry.find(text01.domNode)).toBe(text01);
|
||||
expect(ctx.registry.find(text23.domNode)).toBe(text23);
|
||||
expect(ctx.registry.find(text45.domNode)).toBe(text45);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('scroll', function () {
|
||||
@ -11,14 +12,14 @@ describe('scroll', function () {
|
||||
|
||||
describe('path()', function () {
|
||||
it('middle', function () {
|
||||
let path = ctx.scroll.path(7);
|
||||
let expected = [
|
||||
const path = ctx.scroll.path(7);
|
||||
const expected = [
|
||||
['scroll', 7],
|
||||
['block', 7],
|
||||
['italic', 2],
|
||||
['bold', 2],
|
||||
['text', 2],
|
||||
];
|
||||
] as const;
|
||||
expect(path.length).toEqual(expected.length);
|
||||
path.forEach(function (position, i) {
|
||||
expect(position[0].statics.blotName).toEqual(expected[i][0]);
|
||||
@ -27,14 +28,14 @@ describe('scroll', function () {
|
||||
});
|
||||
|
||||
it('between blots', function () {
|
||||
let path = ctx.scroll.path(5);
|
||||
let expected = [
|
||||
const path = ctx.scroll.path(5);
|
||||
const expected = [
|
||||
['scroll', 5],
|
||||
['block', 5],
|
||||
['italic', 0],
|
||||
['bold', 0],
|
||||
['text', 0],
|
||||
];
|
||||
] as const;
|
||||
expect(path.length).toEqual(expected.length);
|
||||
path.forEach(function (position, i) {
|
||||
expect(position[0].statics.blotName).toEqual(expected[i][0]);
|
||||
@ -43,13 +44,13 @@ describe('scroll', function () {
|
||||
});
|
||||
|
||||
it('inclusive', function () {
|
||||
let path = ctx.scroll.path(3, true);
|
||||
let expected = [
|
||||
const path = ctx.scroll.path(3, true);
|
||||
const expected = [
|
||||
['scroll', 3],
|
||||
['block', 3],
|
||||
['bold', 3],
|
||||
['text', 3],
|
||||
];
|
||||
] as const;
|
||||
expect(path.length).toEqual(expected.length);
|
||||
path.forEach(function (position, i) {
|
||||
expect(position[0].statics.blotName).toEqual(expected[i][0]);
|
||||
@ -58,8 +59,8 @@ describe('scroll', function () {
|
||||
});
|
||||
|
||||
it('last', function () {
|
||||
let path = ctx.scroll.path(9);
|
||||
let expected = [['scroll', 9]];
|
||||
const path = ctx.scroll.path(9);
|
||||
const expected = [['scroll', 9]] as const;
|
||||
expect(path.length).toEqual(expected.length);
|
||||
path.forEach(function (position, i) {
|
||||
expect(position[0].statics.blotName).toEqual(expected[i][0]);
|
||||
@ -75,26 +76,28 @@ describe('scroll', function () {
|
||||
expect(wrapper.firstChild).toEqual(ctx.scroll.domNode);
|
||||
});
|
||||
|
||||
it('detach', function (done) {
|
||||
spyOn(ctx.scroll, 'optimize').and.callThrough();
|
||||
it('detach', async function () {
|
||||
vi.spyOn(ctx.scroll, 'optimize');
|
||||
ctx.scroll.domNode.innerHTML = 'Test';
|
||||
setTimeout(() => {
|
||||
expect(ctx.scroll.optimize).toHaveBeenCalledTimes(1);
|
||||
ctx.scroll.detach();
|
||||
ctx.scroll.domNode.innerHTML = '!';
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
expect(ctx.scroll.optimize).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
ctx.scroll.detach();
|
||||
ctx.scroll.domNode.innerHTML = '!';
|
||||
setTimeout(() => {
|
||||
expect(ctx.scroll.optimize).toHaveBeenCalledTimes(1);
|
||||
resolve();
|
||||
}, 1);
|
||||
}, 1);
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scroll reference', function () {
|
||||
it('initialization', function () {
|
||||
expect(ctx.scroll).toEqual(ctx.scroll);
|
||||
ctx.scroll.descendants((blot) => {
|
||||
expect(blot.scroll).toEqual(ctx.scroll);
|
||||
});
|
||||
ctx.scroll
|
||||
.descendants(() => true)
|
||||
.forEach((blot) => expect(blot.scroll).toEqual(ctx.scroll));
|
||||
});
|
||||
|
||||
it('api change', function () {
|
||||
@ -106,9 +109,9 @@ describe('scroll', function () {
|
||||
it('user change', function () {
|
||||
ctx.scroll.domNode.innerHTML = '<p><em>01</em>23</p>';
|
||||
ctx.scroll.update();
|
||||
ctx.scroll.descendants((blot) => {
|
||||
expect(blot.scroll).toEqual(ctx.scroll);
|
||||
});
|
||||
ctx.scroll
|
||||
.descendants(() => true)
|
||||
.forEach((blot) => expect(blot.scroll).toEqual(ctx.scroll));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import TextBlot from '../../src/blot/text';
|
||||
import type { BlockBlot, InlineBlot } from '../../src/parchment';
|
||||
import { setupContextBeforeEach } from '../setup';
|
||||
|
||||
describe('TextBlot', function () {
|
||||
@ -7,20 +9,20 @@ describe('TextBlot', function () {
|
||||
it('constructor(node)', function () {
|
||||
let node = document.createTextNode('Test');
|
||||
let blot = new TextBlot(ctx.scroll, node);
|
||||
expect(blot.text).toEqual('Test');
|
||||
expect(blot['text']).toEqual('Test');
|
||||
expect(blot.domNode.data).toEqual('Test');
|
||||
});
|
||||
|
||||
it('deleteAt() partial', function () {
|
||||
let blot = ctx.scroll.create('text', 'Test');
|
||||
let blot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
blot.deleteAt(1, 2);
|
||||
expect(blot.value()).toEqual('Tt');
|
||||
expect(blot.length()).toEqual(2);
|
||||
});
|
||||
|
||||
it('deleteAt() all', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
expect(container.domNode.firstChild).toEqual(textBlot.domNode);
|
||||
textBlot.deleteAt(0, 4);
|
||||
@ -28,35 +30,36 @@ describe('TextBlot', function () {
|
||||
});
|
||||
|
||||
it('insertAt() text', function () {
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
textBlot.insertAt(1, 'ough');
|
||||
expect(textBlot.value()).toEqual('Toughest');
|
||||
});
|
||||
|
||||
it('insertAt() other', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
textBlot.insertAt(2, 'image', {});
|
||||
expect(textBlot.value()).toEqual('Te');
|
||||
expect(textBlot.next.statics.blotName).toEqual('image');
|
||||
expect(textBlot.next.next.value()).toEqual('st');
|
||||
expect(textBlot.next?.statics.blotName).toEqual('image');
|
||||
const nextNext = textBlot.next?.next;
|
||||
expect(nextNext instanceof TextBlot && nextNext.value()).toEqual('st');
|
||||
});
|
||||
|
||||
it('split() middle', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
let after = textBlot.split(2);
|
||||
expect(textBlot.value()).toEqual('Te');
|
||||
expect(after.value()).toEqual('st');
|
||||
expect(after instanceof TextBlot && after.value()).toEqual('st');
|
||||
expect(textBlot.next).toEqual(after);
|
||||
expect(after.prev).toEqual(textBlot);
|
||||
expect(after instanceof TextBlot && after.prev).toEqual(textBlot);
|
||||
});
|
||||
|
||||
it('split() noop', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
let before = textBlot.split(0);
|
||||
let after = textBlot.split(4);
|
||||
@ -65,52 +68,52 @@ describe('TextBlot', function () {
|
||||
});
|
||||
|
||||
it('split() force', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
let after = textBlot.split(4, true);
|
||||
expect(after).not.toEqual(textBlot);
|
||||
expect(after.value()).toEqual('');
|
||||
expect(after instanceof TextBlot && after.value()).toEqual('');
|
||||
expect(textBlot.next).toEqual(after);
|
||||
expect(after.prev).toEqual(textBlot);
|
||||
expect(after?.prev).toEqual(textBlot);
|
||||
});
|
||||
|
||||
it('format wrap', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
textBlot.formatAt(0, 4, 'bold', true);
|
||||
expect(textBlot.domNode.parentNode.tagName).toEqual('STRONG');
|
||||
expect(textBlot.domNode.parentElement?.tagName).toEqual('STRONG');
|
||||
expect(textBlot.value()).toEqual('Test');
|
||||
});
|
||||
|
||||
it('format null', function () {
|
||||
let container = ctx.scroll.create('inline');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('inline') as InlineBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
textBlot.formatAt(0, 4, 'bold', null);
|
||||
expect(textBlot.domNode.parentNode.tagName).toEqual('SPAN');
|
||||
expect(textBlot.domNode.parentElement?.tagName).toEqual('SPAN');
|
||||
expect(textBlot.value()).toEqual('Test');
|
||||
});
|
||||
|
||||
it('format split', function () {
|
||||
let container = ctx.scroll.create('block');
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let container = ctx.scroll.create('block') as BlockBlot;
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
container.appendChild(textBlot);
|
||||
textBlot.formatAt(1, 2, 'bold', true);
|
||||
expect(container.domNode.innerHTML).toEqual('T<strong>es</strong>t');
|
||||
expect(textBlot.next.statics.blotName).toEqual('bold');
|
||||
expect(textBlot.next?.statics.blotName).toEqual('bold');
|
||||
expect(textBlot.value()).toEqual('T');
|
||||
});
|
||||
|
||||
it('index()', function () {
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
expect(textBlot.index(textBlot.domNode, 2)).toEqual(2);
|
||||
expect(textBlot.index(document.body, 2)).toEqual(-1);
|
||||
});
|
||||
|
||||
it('position()', function () {
|
||||
let textBlot = ctx.scroll.create('text', 'Test');
|
||||
let textBlot = ctx.scroll.create('text', 'Test') as TextBlot;
|
||||
let [node, offset] = textBlot.position(2);
|
||||
expect(node).toEqual(textBlot.domNode);
|
||||
expect(offset).toEqual(2);
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
"noUnusedParameters": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "test"],
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
@ -10,4 +11,12 @@ export default defineConfig({
|
||||
},
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
include: ['./test/unit/*'],
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: 'playwright',
|
||||
name: 'chromium',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user