refactor(editor): rename presets to integration test (#10340)

This commit is contained in:
Saul-Mirone
2025-02-21 06:26:03 +00:00
parent f79324b6a1
commit f3218ab3bc
116 changed files with 156 additions and 210 deletions
@@ -0,0 +1,202 @@
import type { Point } from '@blocksuite/global/utils';
export function wait(time: number = 0) {
return new Promise(resolve => {
requestAnimationFrame(() => {
setTimeout(resolve, time);
});
});
}
/**
* simulate click event
* @param target
* @param position position relative to the target
*/
export function click(target: HTMLElement, position: { x: number; y: number }) {
const element = target.getBoundingClientRect();
const clientX = element.x + position.x;
const clientY = element.y + position.y;
target.dispatchEvent(
new PointerEvent('pointerdown', {
clientX,
clientY,
bubbles: true,
pointerId: 1,
isPrimary: true,
})
);
target.dispatchEvent(
new PointerEvent('pointerup', {
clientX,
clientY,
bubbles: true,
pointerId: 1,
isPrimary: true,
})
);
target.dispatchEvent(
new MouseEvent('click', {
clientX,
clientY,
bubbles: true,
})
);
}
type PointerOptions = {
isPrimary?: boolean;
pointerId?: number;
pointerType?: string;
};
const defaultPointerOptions: PointerOptions = {
isPrimary: true,
pointerId: 1,
pointerType: 'mouse',
};
/**
* simulate pointerdown event
* @param target
* @param position position relative to the target
*/
export function pointerdown(
target: HTMLElement,
position: { x: number; y: number },
options: PointerOptions = defaultPointerOptions
) {
const element = target.getBoundingClientRect();
const clientX = element.x + position.x;
const clientY = element.y + position.y;
target.dispatchEvent(
new PointerEvent('pointerdown', {
clientX,
clientY,
bubbles: true,
...options,
})
);
}
/**
* simulate pointerup event
* @param target
* @param position position relative to the target
*/
export function pointerup(
target: HTMLElement,
position: { x: number; y: number },
options: PointerOptions = defaultPointerOptions
) {
const element = target.getBoundingClientRect();
const clientX = element.x + position.x;
const clientY = element.y + position.y;
target.dispatchEvent(
new PointerEvent('pointerup', {
clientX,
clientY,
bubbles: true,
...options,
})
);
}
/**
* simulate pointermove event
* @param target
* @param position position relative to the target
*/
export function pointermove(
target: HTMLElement,
position: { x: number; y: number },
options: PointerOptions = defaultPointerOptions
) {
const element = target.getBoundingClientRect();
const clientX = element.x + position.x;
const clientY = element.y + position.y;
target.dispatchEvent(
new PointerEvent('pointermove', {
clientX,
clientY,
bubbles: true,
...options,
})
);
}
export function drag(
target: HTMLElement,
start: { x: number; y: number },
end: { x: number; y: number },
step: number = 5
) {
pointerdown(target, start);
pointermove(target, start);
if (step !== 0) {
const xStep = (end.x - start.x) / step;
const yStep = (end.y - start.y) / step;
for (const [i] of Array.from({ length: step }).entries()) {
pointermove(target, {
x: start.x + xStep * (i + 1),
y: start.y + yStep * (i + 1),
});
}
}
pointermove(target, end);
pointerup(target, end);
}
export function multiTouchDown(target: Element, points: Point[]) {
points.forEach((point, index) => {
pointerdown(target as HTMLElement, point, {
isPrimary: index === 0,
pointerId: index,
pointerType: 'touch',
});
});
}
export function multiTouchMove(
target: Element,
from: Point[],
to: Point[],
step = 5
) {
if (from.length !== to.length) {
throw new Error('from and to should have the same length');
}
if (step !== 0) {
for (const [i] of Array.from({ length: step }).entries()) {
const stepPoints = from.map((point, index) => ({
x: point.x + ((to[index].x - point.x) / step) * (i + 1),
y: point.y + ((to[index].y - point.y) / step) * (i + 1),
}));
from.forEach((_, index) => {
pointermove(target as HTMLElement, stepPoints[index], {
isPrimary: index === 0,
pointerId: index,
pointerType: 'touch',
});
});
}
}
}
export function multiTouchUp(target: Element, points: Point[]) {
points.forEach((point, index) => {
pointerup(target as HTMLElement, point, {
isPrimary: index === 0,
pointerId: index,
pointerType: 'touch',
});
});
}
@@ -0,0 +1,41 @@
import { type Store, Text } from '@blocksuite/store';
function addParagraph(doc: Store, noteId: string, content: string) {
const props = { text: new Text(content) };
doc.addBlock('affine:paragraph', props, noteId);
}
function addSampleNote(doc: Store, noteId: string, i: number) {
addParagraph(doc, noteId, `Note ${i + 1}`);
addParagraph(doc, noteId, 'Hello World!');
addParagraph(
doc,
noteId,
'Hello World! Lorem ipsum dolor sit amet. Consectetur adipiscing elit. Sed do eiusmod tempor incididunt.'
);
addParagraph(
doc,
noteId,
'你好这是测试,这是一个为了换行而写的中文段落。这个段落会自动换行。'
);
}
export function addSampleNotes(doc: Store, n: number) {
const cols = Math.ceil(Math.sqrt(n));
const NOTE_WIDTH = 500;
const NOTE_HEIGHT = 250;
const SPACING = 50;
const rootId = doc.getBlocksByFlavour('affine:page')[0]?.id;
for (let i = 0; i < n; i++) {
const row = Math.floor(i / cols);
const col = i % cols;
const x = col * (NOTE_WIDTH + SPACING);
const y = row * (NOTE_HEIGHT + SPACING);
const xywh = `[${x},${y},${NOTE_WIDTH},${NOTE_HEIGHT}]`;
const noteId = doc.addBlock('affine:note', { xywh }, rootId);
addSampleNote(doc, noteId, i);
}
}
@@ -0,0 +1,51 @@
import type {
EdgelessRootBlockComponent,
PageRootBlockComponent,
SurfaceBlockComponent,
} from '@blocksuite/blocks';
import type { Store } from '@blocksuite/store';
import type { TestAffineEditorContainer } from '../../index.js';
export function getSurface(doc: Store, editor: TestAffineEditorContainer) {
const surfaceModel = doc.getBlockByFlavour('affine:surface');
return editor.host!.view.getBlock(
surfaceModel[0]!.id
) as SurfaceBlockComponent;
}
export function getDocRootBlock(
doc: Store,
editor: TestAffineEditorContainer,
mode: 'page'
): PageRootBlockComponent;
export function getDocRootBlock(
doc: Store,
editor: TestAffineEditorContainer,
mode: 'edgeless'
): EdgelessRootBlockComponent;
export function getDocRootBlock(
doc: Store,
editor: TestAffineEditorContainer,
_?: 'edgeless' | 'page'
) {
return editor.host!.view.getBlock(doc.root!.id) as
| EdgelessRootBlockComponent
| PageRootBlockComponent;
}
export function addNote(doc: Store, props: Record<string, any> = {}) {
const noteId = doc.addBlock(
'affine:note',
{
xywh: '[0, 0, 800, 100]',
...props,
},
doc.root
);
doc.addBlock('affine:paragraph', {}, noteId);
return noteId;
}
@@ -0,0 +1,24 @@
import { replaceIdMiddleware } from '@blocksuite/blocks';
import {
type DocSnapshot,
Transformer,
type Workspace,
} from '@blocksuite/store';
export async function importFromSnapshot(
collection: Workspace,
snapshot: DocSnapshot
) {
const job = new Transformer({
schema: collection.schema,
blobCRUD: collection.blobSync,
docCRUD: {
create: (id: string) => collection.createDoc({ id }),
get: (id: string) => collection.getDoc(id),
delete: (id: string) => collection.removeDoc(id),
},
middlewares: [replaceIdMiddleware(collection.idGenerator)],
});
return job.snapshotToDoc(snapshot);
}
@@ -0,0 +1,12 @@
import { ViewportTurboRendererExtension } from '@blocksuite/affine-shared/viewport-renderer';
import { addSampleNotes } from './doc-generator.js';
import { setupEditor } from './setup.js';
async function init() {
setupEditor('edgeless', [ViewportTurboRendererExtension]);
addSampleNotes(doc, 100);
doc.load();
}
init();
@@ -0,0 +1,140 @@
import '@toeverything/theme/style.css';
import '@toeverything/theme/fonts.css';
import { effects as blocksEffects } from '@blocksuite/blocks/effects';
import type { ExtensionType, Store, Transformer } from '@blocksuite/store';
import { effects } from '../../effects.js';
blocksEffects();
effects();
import {
CommunityCanvasTextFonts,
type DocMode,
EdgelessEditorBlockSpecs,
FontConfigExtension,
PageEditorBlockSpecs,
StoreExtensions,
} from '@blocksuite/blocks';
import { AffineSchemas } from '@blocksuite/blocks/schemas';
import { assertExists } from '@blocksuite/global/utils';
import { Schema, Text } from '@blocksuite/store';
import {
createAutoIncrementIdGenerator,
TestWorkspace,
} from '@blocksuite/store/test';
import { TestAffineEditorContainer } from '../../index.js';
function createCollectionOptions() {
const schema = new Schema();
const room = Math.random().toString(16).slice(2, 8);
schema.register(AffineSchemas);
const idGenerator = createAutoIncrementIdGenerator();
return {
id: room,
schema,
idGenerator,
defaultFlags: {
enable_synced_doc_block: true,
enable_pie_menu: true,
readonly: {
'doc:home': false,
},
},
};
}
function initCollection(collection: TestWorkspace) {
const doc = collection.createDoc({ id: 'doc:home' });
doc.load(() => {
const rootId = doc.addBlock('affine:page', {
title: new Text(),
});
doc.addBlock('affine:surface', {}, rootId);
});
doc.resetHistory();
}
async function createEditor(
collection: TestWorkspace,
mode: DocMode = 'page',
extensions: ExtensionType[] = []
) {
const app = document.createElement('div');
const blockCollection = collection.docs.values().next().value;
assertExists(blockCollection, 'Need to create a doc first');
const doc = blockCollection.getStore();
const editor = new TestAffineEditorContainer();
editor.doc = doc;
editor.mode = mode;
editor.pageSpecs = [
...PageEditorBlockSpecs,
FontConfigExtension(CommunityCanvasTextFonts),
...extensions,
];
editor.edgelessSpecs = [
...EdgelessEditorBlockSpecs,
FontConfigExtension(CommunityCanvasTextFonts),
...extensions,
];
app.append(editor);
window.editor = editor;
window.doc = doc;
app.style.width = '100%';
app.style.height = '1280px';
app.style.overflowY = 'auto';
document.body.append(app);
await editor.updateComplete;
return app;
}
export async function setupEditor(
mode: DocMode = 'page',
extensions: ExtensionType[] = []
) {
const collection = new TestWorkspace(createCollectionOptions());
collection.storeExtensions = StoreExtensions;
collection.meta.initialize();
window.collection = collection;
initCollection(collection);
const appElement = await createEditor(collection, mode, extensions);
return () => {
appElement.remove();
cleanup();
};
}
export function cleanup() {
window.editor.remove();
delete (window as any).collection;
delete (window as any).editor;
delete (window as any).doc;
}
declare global {
const editor: TestAffineEditorContainer;
const doc: Store;
const collection: TestWorkspace;
const job: Transformer;
interface Window {
editor: TestAffineEditorContainer;
doc: Store;
job: Transformer;
collection: TestWorkspace;
}
}