mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-26 02:35:58 +08:00
898 lines
25 KiB
TypeScript
898 lines
25 KiB
TypeScript
import type { EdgelessRootBlockComponent } from '@blocksuite/affine/blocks/root';
|
|
import type {
|
|
CanvasRenderer,
|
|
SurfaceElementModel,
|
|
} from '@blocksuite/affine/blocks/surface';
|
|
import { ungroupCommand } from '@blocksuite/affine/gfx/group';
|
|
import type {
|
|
GroupElementModel,
|
|
NoteBlockModel,
|
|
} from '@blocksuite/affine/model';
|
|
import { generateKeyBetween } from '@blocksuite/affine/std/gfx';
|
|
import type { BlockComponent } from '@blocksuite/std';
|
|
import type { BlockModel, Store } from '@blocksuite/store';
|
|
import { beforeEach, describe, expect, test } from 'vitest';
|
|
import * as Y from 'yjs';
|
|
|
|
import { wait } from '../utils/common.js';
|
|
import {
|
|
addNote as _addNote,
|
|
getDocRootBlock,
|
|
getSurface,
|
|
} from '../utils/edgeless.js';
|
|
import { setupEditor } from '../utils/setup.js';
|
|
|
|
let service!: EdgelessRootBlockComponent['service'];
|
|
|
|
const addNote = (doc: Store, props: Record<string, unknown> = {}) => {
|
|
return _addNote(doc, {
|
|
index: service.layer.generateIndex(),
|
|
...props,
|
|
});
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
const cleanup = await setupEditor('edgeless');
|
|
service = getDocRootBlock(window.doc, window.editor, 'edgeless').service;
|
|
|
|
return async () => {
|
|
await wait(100);
|
|
cleanup();
|
|
};
|
|
});
|
|
|
|
test('layer manager initial state', () => {
|
|
expect(service.layer).toBeDefined();
|
|
expect(service.layer.layers.length).toBe(0);
|
|
expect(service.layer.canvasLayers.length).toBe(1);
|
|
});
|
|
|
|
describe('add new edgeless blocks or canvas elements should update layer automatically', () => {
|
|
test('add note, note, shape sequentially', async () => {
|
|
addNote(doc);
|
|
addNote(doc);
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
|
|
await wait();
|
|
|
|
expect(service.layer.layers.length).toBe(2);
|
|
});
|
|
|
|
test('add note, shape, note sequentially', async () => {
|
|
addNote(doc);
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
addNote(doc);
|
|
await wait();
|
|
|
|
expect(service.layer.layers.length).toBe(3);
|
|
});
|
|
});
|
|
|
|
test('delete element should update layer automatically', () => {
|
|
const id = addNote(doc);
|
|
const canvasElId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
|
|
service.removeElement(id);
|
|
|
|
expect(service.layer.layers.length).toBe(1);
|
|
|
|
service.removeElement(canvasElId!);
|
|
|
|
expect(service.layer.layers.length).toBe(0);
|
|
});
|
|
|
|
test('change element should update layer automatically', async () => {
|
|
const id = addNote(doc);
|
|
const canvasElId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
|
|
await wait();
|
|
|
|
service.crud.updateElement(id, {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(id)!,
|
|
'forward'
|
|
),
|
|
});
|
|
expect(service.layer.layers[service.layer.layers.length - 1].type).toBe(
|
|
'block'
|
|
);
|
|
|
|
service.crud.updateElement(canvasElId!, {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(canvasElId!)!,
|
|
'forward'
|
|
),
|
|
});
|
|
expect(service.layer.layers[service.layer.layers.length - 1].type).toBe(
|
|
'canvas'
|
|
);
|
|
});
|
|
|
|
test('new added canvas elements should be placed in the topmost canvas layer', async () => {
|
|
addNote(doc);
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
|
|
await wait();
|
|
|
|
expect(service.layer.layers.length).toBe(2);
|
|
expect(service.layer.layers[1].type).toBe('canvas');
|
|
});
|
|
|
|
test("there should be at lease one layer in canvasLayers property even there's no canvas element", () => {
|
|
addNote(doc);
|
|
|
|
expect(service.layer.canvasLayers.length).toBe(1);
|
|
});
|
|
|
|
test('if the topmost layer is canvas layer, the length of canvasLayers array should equal to the counts of canvas layers', () => {
|
|
addNote(doc);
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
addNote(doc);
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
|
|
expect(service.layer.layers.length).toBe(4);
|
|
expect(service.layer.canvasLayers.length).toBe(
|
|
service.layer.layers.filter(layer => layer.type === 'canvas').length
|
|
);
|
|
});
|
|
|
|
test('a new layer should be created in canvasLayers prop when the topmost layer is not canvas layer', () => {
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
addNote(doc);
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
addNote(doc);
|
|
|
|
expect(service.layer.canvasLayers.length).toBe(3);
|
|
});
|
|
|
|
test('layer zindex should update correctly when elements changed', async () => {
|
|
addNote(doc);
|
|
const noteId = addNote(doc);
|
|
const note = service.crud.getElementById(noteId);
|
|
addNote(doc);
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
const topShapeId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
});
|
|
const topShape = service.crud.getElementById(topShapeId!)!;
|
|
|
|
await wait();
|
|
|
|
const assertInitialState = () => {
|
|
expect(service.layer.layers[0].type).toBe('block');
|
|
expect(service.layer.layers[0].zIndex).toBe(1);
|
|
|
|
expect(service.layer.layers[1].type).toBe('canvas');
|
|
expect(service.layer.layers[1].zIndex).toBe(4);
|
|
};
|
|
assertInitialState();
|
|
|
|
service.doc.captureSync();
|
|
|
|
service.crud.updateElement(noteId, {
|
|
index: service.layer.getReorderedIndex(note!, 'front'),
|
|
});
|
|
await wait();
|
|
service.doc.captureSync();
|
|
|
|
const assert2StepState = () => {
|
|
expect(service.layer.layers[1].type).toBe('canvas');
|
|
expect(service.layer.layers[1].zIndex).toBe(3);
|
|
|
|
expect(service.layer.layers[2].type).toBe('block');
|
|
expect(service.layer.layers[2].zIndex).toBe(4);
|
|
};
|
|
assert2StepState();
|
|
|
|
service.crud.updateElement(topShapeId!, {
|
|
index: service.layer.getReorderedIndex(topShape!, 'front'),
|
|
});
|
|
await wait();
|
|
service.doc.captureSync();
|
|
|
|
expect(service.layer.layers[3].type).toBe('canvas');
|
|
expect(service.layer.layers[3].zIndex).toBe(5);
|
|
|
|
service.doc.undo();
|
|
await wait();
|
|
assert2StepState();
|
|
|
|
service.doc.undo();
|
|
await wait();
|
|
assertInitialState();
|
|
});
|
|
|
|
test('blocks should rerender when their z-index changed', async () => {
|
|
const blocks = [addNote(doc), addNote(doc), addNote(doc), addNote(doc)];
|
|
const assertBlocksContent = () => {
|
|
const blocks = Array.from(
|
|
document.querySelectorAll(
|
|
'affine-edgeless-root gfx-viewport > [data-block-id]'
|
|
)
|
|
);
|
|
|
|
expect(blocks.length).toBe(5);
|
|
|
|
blocks.forEach(element => {
|
|
expect(element.children.length).toBeGreaterThan(0);
|
|
});
|
|
};
|
|
|
|
await wait();
|
|
assertBlocksContent();
|
|
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
index: generateKeyBetween(
|
|
service.crud.getElementById(blocks[1])!.index,
|
|
service.crud.getElementById(blocks[2])!.index
|
|
),
|
|
});
|
|
|
|
await wait();
|
|
assertBlocksContent();
|
|
});
|
|
|
|
describe('layer reorder functionality', () => {
|
|
let ids: string[] = [];
|
|
|
|
beforeEach(() => {
|
|
ids = [
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
];
|
|
});
|
|
|
|
test('forward', async () => {
|
|
service.crud.updateElement(ids[0], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[0])!,
|
|
'forward'
|
|
),
|
|
});
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[0]) as any)
|
|
)
|
|
).toBe(1);
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[1]) as any)
|
|
)
|
|
).toBe(0);
|
|
|
|
await wait();
|
|
|
|
service.crud.updateElement(ids[1], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[1])!,
|
|
'forward'
|
|
),
|
|
});
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[0]) as any)
|
|
)
|
|
).toBe(0);
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[1]) as any)
|
|
)
|
|
).toBe(1);
|
|
});
|
|
|
|
test('front', async () => {
|
|
service.crud.updateElement(ids[0], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[0])!,
|
|
'front'
|
|
),
|
|
});
|
|
|
|
await wait();
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[0]) as any)
|
|
)
|
|
).toBe(3);
|
|
|
|
service.crud.updateElement(ids[1], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[1])!,
|
|
'front'
|
|
),
|
|
});
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[1]) as any)
|
|
)
|
|
).toBe(3);
|
|
});
|
|
|
|
test('backward', async () => {
|
|
service.crud.updateElement(ids[3], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[3])!,
|
|
'backward'
|
|
),
|
|
});
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[3]) as any)
|
|
)
|
|
).toBe(1);
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[2]) as any)
|
|
)
|
|
).toBe(2);
|
|
|
|
await wait();
|
|
|
|
service.crud.updateElement(ids[2], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[2])!,
|
|
'backward'
|
|
),
|
|
});
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[3]) as any)
|
|
)
|
|
).toBe(3);
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[2]) as any)
|
|
)
|
|
).toBe(2);
|
|
});
|
|
|
|
test('back', async () => {
|
|
service.crud.updateElement(ids[3], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[3])!,
|
|
'back'
|
|
),
|
|
});
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[3]) as any)
|
|
)
|
|
).toBe(0);
|
|
|
|
await wait();
|
|
|
|
service.crud.updateElement(ids[2], {
|
|
index: service.layer.getReorderedIndex(
|
|
service.crud.getElementById(ids[2])!,
|
|
'back'
|
|
),
|
|
});
|
|
|
|
expect(
|
|
service.layer.layers.findIndex(layer =>
|
|
layer.set.has(service.crud.getElementById(ids[2]) as any)
|
|
)
|
|
).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('group related functionality', () => {
|
|
const createGroup = (
|
|
service: EdgelessRootBlockComponent['service'],
|
|
childIds: string[]
|
|
) => {
|
|
const children = new Y.Map<boolean>();
|
|
childIds.forEach(id => children.set(id, true));
|
|
|
|
return service.crud.addElement('group', {
|
|
children,
|
|
});
|
|
};
|
|
|
|
test("new added group should effect it children's layer", async () => {
|
|
const edgeless = getDocRootBlock(doc, editor, 'edgeless');
|
|
const elements = [
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
];
|
|
|
|
await wait(0);
|
|
expect(
|
|
edgeless.querySelectorAll<HTMLCanvasElement>('.indexable-canvas').length
|
|
).toBe(2);
|
|
|
|
Array.from(
|
|
edgeless.querySelectorAll<HTMLCanvasElement>('.indexable-canvas')
|
|
).forEach(canvas => {
|
|
const rect = canvas.getBoundingClientRect();
|
|
|
|
expect(rect.width).toBeGreaterThan(0);
|
|
expect(rect.height).toBeGreaterThan(0);
|
|
});
|
|
|
|
createGroup(
|
|
service,
|
|
elements.filter((_, idx) => idx !== 1 && idx !== 3)
|
|
);
|
|
|
|
expect(service.layer.layers.length).toBe(2);
|
|
|
|
expect(service.layer.layers[0].type).toBe('block');
|
|
expect(service.layer.layers[0].set.size).toBe(2);
|
|
|
|
expect(service.layer.layers[1].type).toBe('canvas');
|
|
expect(service.layer.layers[1].set.size).toBe(4);
|
|
|
|
expect(
|
|
edgeless.querySelectorAll<HTMLCanvasElement>('.indexable-canvas').length
|
|
).toBe(0);
|
|
|
|
const topCanvas = edgeless.querySelector(
|
|
'affine-surface canvas'
|
|
) as HTMLCanvasElement;
|
|
|
|
expect(
|
|
Number(
|
|
(
|
|
edgeless.querySelector(
|
|
`[data-block-id="${elements[1]}"]`
|
|
) as HTMLElement
|
|
).style.zIndex
|
|
)
|
|
).toBeLessThan(Number(topCanvas.style.zIndex));
|
|
expect(
|
|
Number(
|
|
(
|
|
edgeless.querySelector(
|
|
`[data-block-id="${elements[3]}"]`
|
|
) as HTMLElement
|
|
).style.zIndex
|
|
)
|
|
).toBeLessThan(Number(topCanvas.style.zIndex));
|
|
});
|
|
|
|
test("change group index should update its children's layer", () => {
|
|
const elements = [
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
];
|
|
|
|
const groupId = createGroup(
|
|
service,
|
|
elements.filter((_, idx) => idx !== 1 && idx !== 3)
|
|
)!;
|
|
const group = service.crud.getElementById(groupId)!;
|
|
|
|
expect(service.layer.layers.length).toBe(2);
|
|
|
|
group.index = service.layer.getReorderedIndex(group, 'back');
|
|
expect(service.layer.layers[0].type).toBe('canvas');
|
|
expect(service.layer.layers[0].set.size).toBe(4);
|
|
expect(service.layer.layers[0].elements[0]).toBe(group);
|
|
|
|
group.index = service.layer.getReorderedIndex(group, 'front');
|
|
expect(service.layer.layers[1].type).toBe('canvas');
|
|
expect(service.layer.layers[1].set.size).toBe(4);
|
|
expect(service.layer.layers[1].elements[0]).toBe(group);
|
|
});
|
|
|
|
test('should keep relative index order of elements after group, ungroup, undo, redo', () => {
|
|
const edgeless = getDocRootBlock(doc, editor, 'edgeless');
|
|
const elementIds = [
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
addNote(doc),
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!,
|
|
];
|
|
service.doc.captureSync();
|
|
const elements = elementIds.map(id => service.crud.getElementById(id)!);
|
|
|
|
const isKeptRelativeOrder = () => {
|
|
return elements.every((element, idx) => {
|
|
if (idx === 0) return true;
|
|
return elements[idx - 1].index < element.index;
|
|
});
|
|
};
|
|
|
|
expect(isKeptRelativeOrder()).toBeTruthy();
|
|
|
|
const groupId = createGroup(edgeless.service, elementIds)!;
|
|
expect(isKeptRelativeOrder()).toBeTruthy();
|
|
|
|
service.std.command.exec(ungroupCommand, {
|
|
group: service.crud.getElementById(groupId) as GroupElementModel,
|
|
});
|
|
expect(isKeptRelativeOrder()).toBeTruthy();
|
|
|
|
service.doc.undo();
|
|
expect(isKeptRelativeOrder()).toBeTruthy();
|
|
|
|
service.doc.redo();
|
|
expect(isKeptRelativeOrder()).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('compare function', () => {
|
|
const SORT_ORDER = {
|
|
AFTER: 1,
|
|
BEFORE: -1,
|
|
SAME: 0,
|
|
};
|
|
const createGroup = (
|
|
service: EdgelessRootBlockComponent['service'],
|
|
childIds: string[]
|
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
|
) => {
|
|
const children = new Y.Map<boolean>();
|
|
childIds.forEach(id => children.set(id, true));
|
|
|
|
return service.crud.addElement('group', {
|
|
children,
|
|
});
|
|
};
|
|
|
|
test('compare same element', () => {
|
|
const shapeId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const shapeEl = service.crud.getElementById(shapeId)!;
|
|
expect(service.layer.compare(shapeEl, shapeEl)).toBe(SORT_ORDER.SAME);
|
|
|
|
const groupId = createGroup(service, [shapeId])!;
|
|
const groupEl = service.crud.getElementById(groupId)!;
|
|
expect(service.layer.compare(groupEl, groupEl)).toBe(SORT_ORDER.SAME);
|
|
|
|
const noteId = addNote(doc);
|
|
const note = service.crud.getElementById(noteId)! as NoteBlockModel;
|
|
expect(service.layer.compare(note, note)).toBe(SORT_ORDER.SAME);
|
|
});
|
|
|
|
test('compare a group and its child', () => {
|
|
const shapeId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const shapeEl = service.crud.getElementById(shapeId)!;
|
|
const noteId = addNote(doc);
|
|
const note = service.crud.getElementById(noteId)! as NoteBlockModel;
|
|
const groupId = createGroup(service, [shapeId, noteId])!;
|
|
const groupEl = service.crud.getElementById(groupId)!;
|
|
|
|
expect(service.layer.compare(groupEl, shapeEl)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(shapeEl, groupEl)).toBe(SORT_ORDER.AFTER);
|
|
expect(service.layer.compare(groupEl, note)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(note, groupEl)).toBe(SORT_ORDER.AFTER);
|
|
});
|
|
|
|
test('compare two different elements', () => {
|
|
const shape1Id = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const shape1 = service.crud.getElementById(shape1Id)!;
|
|
const shape2Id = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const shape2 = service.crud.getElementById(shape2Id)!;
|
|
const note1Id = addNote(doc);
|
|
|
|
const note1 = service.crud.getElementById(note1Id)! as NoteBlockModel;
|
|
const note2Id = addNote(doc);
|
|
const note2 = service.crud.getElementById(note2Id)! as NoteBlockModel;
|
|
|
|
expect(service.layer.compare(shape1, shape2)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(shape2, shape1)).toBe(SORT_ORDER.AFTER);
|
|
|
|
expect(service.layer.compare(note1, note2)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(note2, note1)).toBe(SORT_ORDER.AFTER);
|
|
|
|
expect(service.layer.compare(shape1, note1)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(note1, shape1)).toBe(SORT_ORDER.AFTER);
|
|
|
|
expect(service.layer.compare(shape2, note2)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(note2, shape2)).toBe(SORT_ORDER.AFTER);
|
|
});
|
|
|
|
test('compare nested elements', () => {
|
|
const shape1Id = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const shape2Id = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const note1Id = addNote(doc);
|
|
const note2Id = addNote(doc);
|
|
const group1Id = createGroup(service, [
|
|
shape1Id,
|
|
shape2Id,
|
|
note1Id,
|
|
note2Id,
|
|
])!;
|
|
const group2Id = createGroup(service, [group1Id])!;
|
|
|
|
const shape1 = service.crud.getElementById(shape1Id)!;
|
|
const shape2 = service.crud.getElementById(shape2Id)!;
|
|
const note1 = service.crud.getElementById(note1Id)! as NoteBlockModel;
|
|
const note2 = service.crud.getElementById(note2Id)! as NoteBlockModel;
|
|
const group1 = service.crud.getElementById(group1Id)!;
|
|
const group2 = service.crud.getElementById(group2Id)!;
|
|
|
|
// assert nested group to group
|
|
expect(service.layer.compare(group2, group1)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(group1, group2)).toBe(SORT_ORDER.AFTER);
|
|
|
|
// assert element in the same group
|
|
expect(service.layer.compare(shape1, shape2)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(shape2, shape1)).toBe(SORT_ORDER.AFTER);
|
|
expect(service.layer.compare(note1, note2)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(note2, note1)).toBe(SORT_ORDER.AFTER);
|
|
|
|
// assert group and its nested element
|
|
expect(service.layer.compare(group2, shape1)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(shape1, group2)).toBe(SORT_ORDER.AFTER);
|
|
expect(service.layer.compare(group1, shape2)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(shape2, group1)).toBe(SORT_ORDER.AFTER);
|
|
expect(service.layer.compare(group2, note1)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(note1, group2)).toBe(SORT_ORDER.AFTER);
|
|
expect(service.layer.compare(group1, note2)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(note2, group1)).toBe(SORT_ORDER.AFTER);
|
|
});
|
|
|
|
test('compare two nested elements', () => {
|
|
const groupAShapeId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const groupANoteId = addNote(doc);
|
|
const groupAId = createGroup(service, [
|
|
createGroup(service, [groupAShapeId, groupANoteId])!,
|
|
])!;
|
|
const groupAShape = service.crud.getElementById(groupAShapeId)!;
|
|
const groupANote = service.crud.getElementById(groupANoteId)!;
|
|
const groupA = service.crud.getElementById(groupAId)!;
|
|
|
|
const groupBShapeId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const groupBNoteId = addNote(doc);
|
|
const groupBId = createGroup(service, [
|
|
createGroup(service, [groupBShapeId, groupBNoteId])!,
|
|
])!;
|
|
const groupBShape = service.crud.getElementById(groupBShapeId)!;
|
|
const groupBNote = service.crud.getElementById(groupBNoteId)!;
|
|
const groupB = service.crud.getElementById(groupBId)!;
|
|
|
|
expect(service.layer.compare(groupAShape, groupBShape)).toBe(
|
|
SORT_ORDER.BEFORE
|
|
);
|
|
expect(service.layer.compare(groupBShape, groupAShape)).toBe(
|
|
SORT_ORDER.AFTER
|
|
);
|
|
expect(service.layer.compare(groupANote, groupBNote)).toBe(
|
|
SORT_ORDER.BEFORE
|
|
);
|
|
expect(service.layer.compare(groupBNote, groupANote)).toBe(
|
|
SORT_ORDER.AFTER
|
|
);
|
|
expect(service.layer.compare(groupB, groupA)).toBe(SORT_ORDER.AFTER);
|
|
|
|
groupB.index = service.layer.getReorderedIndex(groupB, 'back');
|
|
expect(service.layer.compare(groupB, groupA)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(groupAShape, groupBShape)).toBe(
|
|
SORT_ORDER.AFTER
|
|
);
|
|
expect(service.layer.compare(groupBShape, groupAShape)).toBe(
|
|
SORT_ORDER.BEFORE
|
|
);
|
|
expect(service.layer.compare(groupANote, groupBNote)).toBe(
|
|
SORT_ORDER.AFTER
|
|
);
|
|
expect(service.layer.compare(groupBNote, groupANote)).toBe(
|
|
SORT_ORDER.BEFORE
|
|
);
|
|
|
|
groupA.index = service.layer.getReorderedIndex(groupA, 'back');
|
|
expect(service.layer.compare(groupA, groupB)).toBe(SORT_ORDER.BEFORE);
|
|
expect(service.layer.compare(groupAShape, groupBShape)).toBe(
|
|
SORT_ORDER.BEFORE
|
|
);
|
|
expect(service.layer.compare(groupBShape, groupAShape)).toBe(
|
|
SORT_ORDER.AFTER
|
|
);
|
|
expect(service.layer.compare(groupANote, groupBNote)).toBe(
|
|
SORT_ORDER.BEFORE
|
|
);
|
|
expect(service.layer.compare(groupBNote, groupANote)).toBe(
|
|
SORT_ORDER.AFTER
|
|
);
|
|
});
|
|
});
|
|
|
|
test('indexed canvas should be inserted into edgeless portal when switch to edgeless mode', async () => {
|
|
let surface = getSurface(doc, editor);
|
|
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
|
|
addNote(doc);
|
|
|
|
await wait();
|
|
|
|
service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
|
|
editor.mode = 'page';
|
|
await wait();
|
|
editor.mode = 'edgeless';
|
|
await wait();
|
|
|
|
surface = getSurface(doc, editor);
|
|
const edgeless = getDocRootBlock(doc, editor, 'edgeless');
|
|
expect(edgeless.querySelectorAll('.indexable-canvas').length).toBe(1);
|
|
|
|
const indexedCanvas = edgeless.querySelectorAll(
|
|
'.indexable-canvas'
|
|
)[0] as HTMLCanvasElement;
|
|
|
|
expect(indexedCanvas.width).toBe(
|
|
(surface.renderer as CanvasRenderer).canvas.width
|
|
);
|
|
expect(indexedCanvas.height).toBe(
|
|
(surface.renderer as CanvasRenderer).canvas.height
|
|
);
|
|
expect(indexedCanvas.width).not.toBe(0);
|
|
expect(indexedCanvas.height).not.toBe(0);
|
|
});
|
|
|
|
test('the actual rendering z-index should satisfy the logic order of their indexes', async () => {
|
|
editor.mode = 'page';
|
|
|
|
await wait();
|
|
|
|
const indexes = [
|
|
'ao',
|
|
'b0D',
|
|
'ar',
|
|
'as',
|
|
'at',
|
|
'au',
|
|
'av',
|
|
'b0Y',
|
|
'b0V',
|
|
'b0H',
|
|
'b0M',
|
|
'b0T',
|
|
'b0f',
|
|
'b0fV',
|
|
'b0g',
|
|
'b0i',
|
|
'b0fl',
|
|
];
|
|
|
|
indexes.forEach(index => {
|
|
addNote(doc, {
|
|
index,
|
|
});
|
|
});
|
|
|
|
await wait();
|
|
|
|
editor.mode = 'edgeless';
|
|
await wait(500);
|
|
|
|
const edgeless = getDocRootBlock(doc, editor, 'edgeless');
|
|
const blocks = Array.from(
|
|
edgeless.querySelectorAll('gfx-viewport > [data-block-id]')
|
|
) as BlockComponent[];
|
|
|
|
expect(blocks.length).toBe(indexes.length + 1);
|
|
|
|
blocks
|
|
.filter(block => block.flavour !== 'affine:surface')
|
|
.forEach((block, index) => {
|
|
if (index === blocks.length - 1) return;
|
|
|
|
const model = block.model as BlockModel<{ index: string }>;
|
|
const nextModel = blocks[index + 1].model as BlockModel<{
|
|
index: string;
|
|
}>;
|
|
|
|
const zIndex = Number(block.style.zIndex);
|
|
const nextZIndex = Number(blocks[index + 1].style.zIndex);
|
|
|
|
expect(model.props.index <= nextModel.props.index).equals(
|
|
zIndex <= nextZIndex
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('index generator', () => {
|
|
let preinsertedShape: SurfaceElementModel;
|
|
let preinsertedNote: NoteBlockModel;
|
|
|
|
beforeEach(() => {
|
|
const shapeId = service.crud.addElement('shape', {
|
|
shapeType: 'rect',
|
|
})!;
|
|
const noteId = addNote(doc);
|
|
|
|
preinsertedShape = service.crud.getElementById(
|
|
shapeId
|
|
)! as SurfaceElementModel;
|
|
preinsertedNote = service.crud.getElementById(noteId)! as NoteBlockModel;
|
|
});
|
|
|
|
test('generator should remember the index it generated', () => {
|
|
const generator = service.layer.createIndexGenerator();
|
|
|
|
const shape1 = generator();
|
|
const block1 = generator();
|
|
const shape2 = generator();
|
|
const block2 = generator();
|
|
|
|
expect(block2 > shape2).toBeTruthy();
|
|
expect(shape2 > block1).toBeTruthy();
|
|
expect(block1 > shape1).toBeTruthy();
|
|
expect(shape1 > preinsertedNote.index).toBeTruthy();
|
|
expect(shape1 > preinsertedShape.index).toBeTruthy();
|
|
});
|
|
});
|