From d7268ce04cf5fc0b054c96cfe4bd9d3d473e66ee Mon Sep 17 00:00:00 2001 From: doouding Date: Tue, 8 Apr 2025 16:20:35 +0000 Subject: [PATCH] test: add std gfx test (#11442) ### Changed - Move some intergraion tests to std as they are more like basic tests - Add some basic gfx-related tests --- .../src/__tests__/editor-host.unit.spec.ts | 2 + .../convert-decorator-convert-decorator-1.png | Bin 0 -> 3012 bytes ...ived-decorator-should-work-correctly-1.png | Bin 0 -> 3012 bytes ...-stash-and-pop-should-work-correctly-1.png | Bin 0 -> 3012 bytes ...should-also-trigger-derive-decorator-1.png | Bin 0 -> 3012 bytes ...-basic-created-slot-should-be-called-1.png | Bin 0 -> 3012 bytes ...sic-delete-observer-should-be-called-1.png | Bin 0 -> 3012 bytes ...e-basic-update-slot-should-be-called-1.png | Bin 0 -> 3012 bytes ...local-element-view-should-be-created-1.png | Bin 0 -> 51672 bytes ...sic-query-gfx-block-view-should-work-1.png | Bin 0 -> 3012 bytes ...nt-view-basic-view-should-be-created-1.png | Bin 0 -> 3012 bytes ...nt-view-basic-view-should-be-removed-1.png | Bin 0 -> 3012 bytes .../src/__tests__/gfx/surface.unit.spec.ts | 358 ++++++++++++++++++ .../std/src/__tests__/gfx/view.unit.spec.ts | 134 +++++++ .../framework/std/src/__tests__/test-block.ts | 22 +- .../std/src/__tests__/test-gfx-element.ts | 44 +++ .../std/src/__tests__/test-schema.ts | 66 +++- .../framework/std/src/__tests__/test-spec.ts | 4 + .../__tests__/edgeless/surface-model.spec.ts | 276 +------------- 19 files changed, 641 insertions(+), 265 deletions(-) create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/convert-decorator-convert-decorator-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/derive-decorator-derived-decorator-should-work-correctly-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/stash-pop-stash-and-pop-should-work-correctly-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/stash-pop-stashed-property-should-also-trigger-derive-decorator-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-created-slot-should-be-called-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-delete-observer-should-be-called-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-update-slot-should-be-called-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-local-element-view-should-be-created-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-query-gfx-block-view-should-work-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-view-should-be-created-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-view-should-be-removed-1.png create mode 100644 blocksuite/framework/std/src/__tests__/gfx/surface.unit.spec.ts create mode 100644 blocksuite/framework/std/src/__tests__/gfx/view.unit.spec.ts create mode 100644 blocksuite/framework/std/src/__tests__/test-gfx-element.ts diff --git a/blocksuite/framework/std/src/__tests__/editor-host.unit.spec.ts b/blocksuite/framework/std/src/__tests__/editor-host.unit.spec.ts index d35e5087dd..6e292960b1 100644 --- a/blocksuite/framework/std/src/__tests__/editor-host.unit.spec.ts +++ b/blocksuite/framework/std/src/__tests__/editor-host.unit.spec.ts @@ -11,6 +11,7 @@ import { HeadingBlockSchemaExtension, NoteBlockSchemaExtension, RootBlockSchemaExtension, + SurfaceBlockSchemaExtension, } from './test-schema.js'; import { testSpecs } from './test-spec.js'; @@ -20,6 +21,7 @@ const extensions = [ RootBlockSchemaExtension, NoteBlockSchemaExtension, HeadingBlockSchemaExtension, + SurfaceBlockSchemaExtension, ]; function createTestOptions() { diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/convert-decorator-convert-decorator-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/convert-decorator-convert-decorator-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/derive-decorator-derived-decorator-should-work-correctly-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/derive-decorator-derived-decorator-should-work-correctly-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/stash-pop-stash-and-pop-should-work-correctly-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/stash-pop-stash-and-pop-should-work-correctly-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/stash-pop-stashed-property-should-also-trigger-derive-decorator-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/stash-pop-stashed-property-should-also-trigger-derive-decorator-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-created-slot-should-be-called-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-created-slot-should-be-called-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-delete-observer-should-be-called-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-delete-observer-should-be-called-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-update-slot-should-be-called-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/surface.unit.spec.ts/surface-basic-update-slot-should-be-called-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-local-element-view-should-be-created-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-local-element-view-should-be-created-1.png new file mode 100644 index 0000000000000000000000000000000000000000..06188403babd7593333e61f1871f63b616fd3a6d GIT binary patch literal 51672 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 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-query-gfx-block-view-should-work-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-query-gfx-block-view-should-work-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-view-should-be-created-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-view-should-be-created-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-view-should-be-removed-1.png b/blocksuite/framework/std/src/__tests__/gfx/__screenshots__/view.unit.spec.ts/gfx-element-view-basic-view-should-be-removed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..f88c55c1cad7d3de8229e8a42a99ad5e0bc9e037 GIT binary patch literal 3012 zcmeAS@N?(olHy`uVBq!ia0y~yV4TOmz}&#W1QcnFZJGt77>k44ofy`glX=O&z+LC* z;uumf=k1k)oD2#A2MkL8&pofovQS0%%T-%r?gdZ(@G(@Jzty0S&D^wxmt&Ke!iiDN zXc&y9gVDS&S{{s+iKF$xXmvQ+Bp7WLjW!L*YaZ)u@!|YCPt|%AuvyOF>FVdQ&MBb@ E0IgVhRsaA1 literal 0 HcmV?d00001 diff --git a/blocksuite/framework/std/src/__tests__/gfx/surface.unit.spec.ts b/blocksuite/framework/std/src/__tests__/gfx/surface.unit.spec.ts new file mode 100644 index 0000000000..a056465c7d --- /dev/null +++ b/blocksuite/framework/std/src/__tests__/gfx/surface.unit.spec.ts @@ -0,0 +1,358 @@ +import { + createAutoIncrementIdGenerator, + TestWorkspace, +} from '@blocksuite/store/test'; +import { describe, expect, test, vi } from 'vitest'; + +import { effects } from '../../effects.js'; +import type { TestShapeElement } from '../test-gfx-element.js'; +import { + RootBlockSchemaExtension, + type SurfaceBlockModel, + SurfaceBlockSchemaExtension, +} from '../test-schema.js'; + +effects(); + +const extensions = [RootBlockSchemaExtension, SurfaceBlockSchemaExtension]; + +function createTestOptions() { + const idGenerator = createAutoIncrementIdGenerator(); + return { id: 'test-collection', idGenerator }; +} + +const commonSetup = () => { + const collection = new TestWorkspace(createTestOptions()); + + collection.meta.initialize(); + const doc = collection.createDoc('home'); + const store = doc.getStore({ extensions }); + doc.load(); + + const rootId = store.addBlock('test:page'); + const surfaceId = store.addBlock('test:surface', {}, rootId); + + const surfaceBlock = store.getBlock(surfaceId)!; + + return { + surfaceId, + surfaceModel: surfaceBlock.model as SurfaceBlockModel, + }; +}; + +describe('surface basic', () => { + test('addElement should work correctly', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + + expect(model.elementModels[0].id).toBe(id); + }); + + test('removeElement should work correctly', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + + model.deleteElement(id); + + expect(model.elementModels.length).toBe(0); + }); + + test('updateElement should work correctly', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + + model.updateElement(id, { xywh: '[10,10,200,200]' }); + + expect(model.elementModels[0].xywh).toBe('[10,10,200,200]'); + }); + + test('getElementById should return element', () => { + const { surfaceModel: model } = commonSetup(); + + const id = model.addElement({ + type: 'testShape', + }); + + expect(model.getElementById(id)).not.toBeNull(); + }); + + test('getElementById should return null if not found', () => { + const { surfaceModel: model } = commonSetup(); + + expect(model.getElementById('not-found')).toBeNull(); + }); + + test('created observer should be called', () => { + const { surfaceModel } = commonSetup(); + + let expectPayload; + const elementAddedCallback = vi.fn(payload => (expectPayload = payload)); + + surfaceModel.elementAdded.subscribe(elementAddedCallback); + + const shapeId = surfaceModel.addElement({ + type: 'testShape', + rotate: 0, + xywh: '[0, 0, 10, 10]', + }); + + expect(elementAddedCallback).toHaveBeenCalled(); + expect(expectPayload).toMatchObject({ + id: shapeId, + }); + }); + + test('update and props observer should be called', () => { + const { surfaceModel } = commonSetup(); + + const shapeId = surfaceModel.addElement({ + type: 'testShape', + rotate: 0, + xywh: '[0, 0, 10, 10]', + }); + const shapeModel = surfaceModel.getElementById(shapeId)!; + + let expectPayload; + const elementUpdatedCallback = vi.fn(payload => (expectPayload = payload)); + let propsUpdatedPayload; + const propsUpdatedCallback = vi.fn(payload => { + propsUpdatedPayload = payload; + }); + + surfaceModel.elementUpdated.subscribe(elementUpdatedCallback); + shapeModel.propsUpdated.subscribe(propsUpdatedCallback); + + surfaceModel.updateElement(shapeId, { + rotate: 10, + }); + + expect(elementUpdatedCallback).toHaveBeenCalled(); + expect(propsUpdatedCallback).toHaveBeenCalled(); + expect(expectPayload).toMatchObject({ + id: shapeId, + props: { + rotate: 10, + }, + oldValues: { + rotate: 0, + }, + }); + expect(propsUpdatedPayload).toMatchObject({ + key: 'rotate', + }); + }); + + test('delete observer should be called', () => { + const { surfaceModel } = commonSetup(); + + const shapeId = surfaceModel.addElement({ + type: 'testShape', + rotate: 0, + xywh: '[0, 0, 10, 10]', + }); + + let expectPayload; + const deletedCallback = vi.fn(payload => (expectPayload = payload)); + + surfaceModel.elementRemoved.subscribe(deletedCallback); + surfaceModel.deleteElement(shapeId); + + expect(deletedCallback).toHaveBeenCalled(); + expect(expectPayload).toMatchObject({ + id: shapeId, + type: 'testShape', + }); + }); +}); + +describe('element model', () => { + test('default value should work correctly', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + + const element = model.getElementById(id)! as TestShapeElement; + + expect(element.rotate).toBe(0); + expect(element.xywh).toBe('[0,0,10,10]'); + }); + + test('defined prop should not be overwritten by default value', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + rotate: 20, + }); + + const element = model.getElementById(id)! as TestShapeElement; + + expect(element.rotate).toBe(20); + }); + + test('assign value to model property should update ymap directly', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + + const element = model.getElementById(id)! as TestShapeElement; + + expect(element.yMap.get('rotate')).toBe(0); + element.rotate = 30; + expect(element.yMap.get('rotate')).toBe(30); + }); +}); + +describe('stash/pop', () => { + const { surfaceModel: model } = commonSetup(); + test('stash and pop should work correctly', () => { + const id = model.addElement({ + type: 'testShape', + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + expect(elementModel.rotate).toBe(0); + + elementModel.stash('rotate'); + elementModel.rotate = 10; + expect(elementModel.rotate).toBe(10); + expect(elementModel.yMap.get('rotate')).toBe(0); + + elementModel.pop('rotate'); + expect(elementModel.rotate).toBe(10); + expect(elementModel.yMap.get('rotate')).toBe(10); + + elementModel.rotate = 6; + expect(elementModel.rotate).toBe(6); + expect(elementModel.yMap.get('rotate')).toBe(6); + }); + + test('assign stashed property should emit event', () => { + const id = model.addElement({ + type: 'testShape', + rotate: 4, + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + elementModel.stash('rotate'); + + const onchange = vi.fn(); + const subscription = model.elementUpdated.subscribe(({ id }) => { + subscription.unsubscribe(); + onchange(id); + }); + + elementModel.rotate = 10; + expect(onchange).toHaveBeenCalledWith(id); + }); + + test('stashed property should also trigger derive decorator', () => { + const id = model.addElement({ + type: 'testShape', + rotate: 20, + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + elementModel.stash('shapeType'); + elementModel.shapeType = 'triangle'; + + // rotation should be 0 'cause of derive decorator + expect(elementModel.rotate).toBe(0); + }); + + test('non-field property should not allow stash/pop, and should fail silently ', () => { + const id = model.addElement({ + type: 'testShape', + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + // opacity is a local property, so it should not be stashed + elementModel.stash('opacity'); + expect(elementModel['_stashed'].has('opacity')).toBe(false); + + // pop the `opacity` should not affect yMap + elementModel.opacity = 0.5; + elementModel.pop('opacity'); + expect(elementModel.yMap.has('opacity')).toBe(false); + }); +}); + +describe('derive decorator', () => { + test('derived decorator should work correctly', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + rotate: 20, + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + elementModel.shapeType = 'triangle'; + + expect(elementModel.rotate).toBe(0); + }); +}); + +describe('local decorator', () => { + test('local decorator should work correctly', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + expect(elementModel.display).toBe(true); + + elementModel.display = false; + expect(elementModel.display).toBe(false); + + elementModel.opacity = 0.5; + expect(elementModel.opacity).toBe(0.5); + }); + + test('assign local property should emit event', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + const onchange = vi.fn(); + const subscription = model.elementUpdated.subscribe(({ id }) => { + subscription.unsubscribe(); + onchange(id); + }); + + const onPropChange = vi.fn(); + elementModel.propsUpdated.subscribe(({ key }) => { + onPropChange(key); + }); + + elementModel.display = false; + + expect(elementModel.display).toBe(false); + expect(onchange).toHaveBeenCalledWith(id); + expect(onPropChange).toHaveBeenCalledWith('display'); + }); +}); + +describe('convert decorator', () => { + test('convert decorator', () => { + const { surfaceModel: model } = commonSetup(); + const id = model.addElement({ + type: 'testShape', + }); + const elementModel = model.getElementById(id)! as TestShapeElement; + + // @ts-expect-error test needed + elementModel.shapeType = 'otherImpossibleType'; + + expect(elementModel.shapeType).toBe('rect'); + }); +}); diff --git a/blocksuite/framework/std/src/__tests__/gfx/view.unit.spec.ts b/blocksuite/framework/std/src/__tests__/gfx/view.unit.spec.ts new file mode 100644 index 0000000000..ad645edfd8 --- /dev/null +++ b/blocksuite/framework/std/src/__tests__/gfx/view.unit.spec.ts @@ -0,0 +1,134 @@ +import { + createAutoIncrementIdGenerator, + TestWorkspace, +} from '@blocksuite/store/test'; +import { describe, expect, test } from 'vitest'; + +import { effects } from '../../effects.js'; +import { GfxControllerIdentifier } from '../../gfx/identifiers.js'; +import { TestEditorContainer } from '../test-editor.js'; +import { TestLocalElement } from '../test-gfx-element.js'; +import { + RootBlockSchemaExtension, + type SurfaceBlockModel, + SurfaceBlockSchemaExtension, + TestGfxBlockSchemaExtension, +} from '../test-schema.js'; +import { testSpecs } from '../test-spec.js'; + +effects(); + +const extensions = [ + RootBlockSchemaExtension, + SurfaceBlockSchemaExtension, + TestGfxBlockSchemaExtension, +]; + +function createTestOptions() { + const idGenerator = createAutoIncrementIdGenerator(); + return { id: 'test-collection', idGenerator }; +} + +const commonSetup = async () => { + const collection = new TestWorkspace(createTestOptions()); + + collection.meta.initialize(); + const doc = collection.createDoc('home'); + const store = doc.getStore({ extensions }); + doc.load(); + + const rootId = store.addBlock('test:page'); + const surfaceId = store.addBlock('test:surface', {}, rootId); + + const surfaceBlock = store.getBlock(surfaceId)!; + + const editorContainer = new TestEditorContainer(); + editorContainer.doc = store; + editorContainer.specs = testSpecs; + document.body.append(editorContainer); + + await editorContainer.updateComplete; + + const gfx = editorContainer.std.get(GfxControllerIdentifier); + + return { + gfx, + surfaceId, + rootId, + surfaceModel: surfaceBlock.model as SurfaceBlockModel, + }; +}; + +describe('gfx element view basic', () => { + test('view should be created', async () => { + const { gfx, surfaceModel } = await commonSetup(); + + const id = surfaceModel.addElement({ + type: 'testShape', + }); + const shapeView = gfx.view.get(id); + + expect(shapeView).not.toBeNull(); + expect(shapeView!.model.id).toBe(id); + expect(shapeView!.isConnected).toBe(true); + }); + + test('view should be removed', async () => { + const { gfx, surfaceModel } = await commonSetup(); + + const id = surfaceModel.addElement({ + type: 'testShape', + }); + const shapeView = gfx.view.get(id); + + expect(shapeView).not.toBeNull(); + expect(shapeView!.model.id).toBe(id); + + surfaceModel.deleteElement(id); + expect(gfx.view.get(id)).toBeNull(); + expect(shapeView!.isConnected).toBe(false); + }); + + test('query gfx block view should work', async () => { + const { gfx, surfaceId, rootId } = await commonSetup(); + + const waitGfxViewConnected = (id: string) => { + const { promise, resolve } = Promise.withResolvers(); + const subscription = gfx.std.view.viewUpdated.subscribe(payload => { + if ( + payload.id === id && + payload.type === 'block' && + payload.method === 'add' + ) { + subscription.unsubscribe(); + resolve(); + } + }); + + return promise; + }; + const id = gfx.std.store.addBlock('test:gfx-block', undefined, surfaceId); + await waitGfxViewConnected(id); + const gfxBlockView = gfx.view.get(id); + expect(gfxBlockView).not.toBeNull(); + + const rootView = gfx.view.get(rootId); + // root is not a gfx block, so it should be null + expect(rootView).toBeNull(); + }); + + test('local element view should be created', async () => { + const { gfx, surfaceModel } = await commonSetup(); + const localElement = new TestLocalElement(surfaceModel); + localElement.id = 'test-local-element'; + + surfaceModel.addLocalElement(localElement); + + const localView = gfx.view.get(localElement); + expect(localView).not.toBeNull(); + expect(localView!.isConnected).toBe(true); + + surfaceModel.deleteLocalElement(localElement); + expect(localView!.isConnected).toBe(false); + }); +}); diff --git a/blocksuite/framework/std/src/__tests__/test-block.ts b/blocksuite/framework/std/src/__tests__/test-block.ts index 5d008b0c65..3c5cea5ae9 100644 --- a/blocksuite/framework/std/src/__tests__/test-block.ts +++ b/blocksuite/framework/std/src/__tests__/test-block.ts @@ -1,11 +1,13 @@ import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { BlockComponent } from '../view/index.js'; +import { BlockComponent, GfxBlockComponent } from '../view/index.js'; import type { HeadingBlockModel, NoteBlockModel, RootBlockModel, + SurfaceBlockModel, + TestGfxBlockModel, } from './test-schema.js'; @customElement('test-root-block') @@ -39,3 +41,21 @@ export class HeadingH2BlockComponent extends BlockComponent { return html`
${this.model.text}
`; } } + +@customElement('test-surface-block') +export class SurfaceBlockComponent extends BlockComponent { + override renderBlock() { + return html` +
${this.renderChildren(this.model)}
+ `; + } +} + +@customElement('test-gfx-block') +export class TestGfxBlockComponent extends GfxBlockComponent { + override renderGfxBlock() { + return html` +
${this.renderChildren(this.model)}
+ `; + } +} diff --git a/blocksuite/framework/std/src/__tests__/test-gfx-element.ts b/blocksuite/framework/std/src/__tests__/test-gfx-element.ts new file mode 100644 index 0000000000..83b5716a40 --- /dev/null +++ b/blocksuite/framework/std/src/__tests__/test-gfx-element.ts @@ -0,0 +1,44 @@ +import type { SerializedXYWH } from '@blocksuite/global/gfx'; + +import { + convert, + derive, + field, + GfxLocalElementModel, + GfxPrimitiveElementModel, +} from '../gfx/index.js'; + +export class TestShapeElement extends GfxPrimitiveElementModel { + get type() { + return 'testShape'; + } + + @field() + accessor rotate: number = 0; + + @field() + accessor xywh: SerializedXYWH = '[0,0,10,10]'; + + @convert(val => { + if (['rect', 'triangle'].includes(val)) { + return val; + } + + return 'rect'; + }) + @derive(val => { + if (val === 'triangle') { + return { + rotate: 0, + }; + } + + return {}; + }) + @field() + accessor shapeType: 'rect' | 'triangle' = 'rect'; +} + +export class TestLocalElement extends GfxLocalElementModel { + override type: string = 'testLocal'; +} diff --git a/blocksuite/framework/std/src/__tests__/test-schema.ts b/blocksuite/framework/std/src/__tests__/test-schema.ts index 5bd854c31f..af5198801e 100644 --- a/blocksuite/framework/std/src/__tests__/test-schema.ts +++ b/blocksuite/framework/std/src/__tests__/test-schema.ts @@ -1,8 +1,17 @@ +import type { SerializedXYWH } from '@blocksuite/global/gfx'; import { BlockModel, BlockSchemaExtension, defineBlockSchema, } from '@blocksuite/store'; +import * as Y from 'yjs'; + +import { SurfaceBlockModel as BaseSurfaceModel } from '../gfx/index.js'; +import { + GfxCompatibleBlockModel, + type GfxCompatibleProps, +} from '../gfx/model/gfx-block-model.js'; +import { TestShapeElement } from './test-gfx-element.js'; export const RootBlockSchema = defineBlockSchema({ flavour: 'test:page', @@ -15,7 +24,7 @@ export const RootBlockSchema = defineBlockSchema({ metadata: { version: 2, role: 'root', - children: ['test:note'], + children: ['test:note', 'test:surface'], }, }); @@ -61,3 +70,58 @@ export const HeadingBlockSchemaExtension = export class HeadingBlockModel extends BlockModel< ReturnType<(typeof HeadingBlockSchema)['model']['props']> > {} + +export const SurfaceBlockSchema = defineBlockSchema({ + flavour: 'test:surface', + props: internal => ({ + elements: internal.Boxed>>(new Y.Map()), + }), + metadata: { + version: 1, + role: 'hub', + parent: ['test:page'], + }, + toModel: () => new SurfaceBlockModel(), +}); + +export const SurfaceBlockSchemaExtension = + BlockSchemaExtension(SurfaceBlockSchema); + +export class SurfaceBlockModel extends BaseSurfaceModel { + override _init() { + this._extendElement({ + testShape: TestShapeElement, + }); + super._init(); + } +} + +type GfxTestBlockProps = { + xywh: SerializedXYWH; + rotate: number; + index: string; +} & GfxCompatibleProps; + +export const TestGfxBlockSchema = defineBlockSchema({ + flavour: 'test:gfx-block', + props: () => + ({ + xywh: '[0,0,10,10]' as SerializedXYWH, + rotate: 0, + index: 'a0', + lockedBySelf: false, + }) as GfxTestBlockProps, + metadata: { + version: 1, + role: 'content', + parent: ['test:surface'], + }, + toModel: () => new TestGfxBlockModel(), +}); + +export const TestGfxBlockSchemaExtension = + BlockSchemaExtension(TestGfxBlockSchema); + +export class TestGfxBlockModel extends GfxCompatibleBlockModel( + BlockModel +) {} diff --git a/blocksuite/framework/std/src/__tests__/test-spec.ts b/blocksuite/framework/std/src/__tests__/test-spec.ts index 3f5871481c..8f814cb4d2 100644 --- a/blocksuite/framework/std/src/__tests__/test-spec.ts +++ b/blocksuite/framework/std/src/__tests__/test-spec.ts @@ -20,4 +20,8 @@ export const testSpecs: ExtensionType[] = [ return literal`test-h2-block`; }), + + BlockViewExtension('test:surface', literal`test-surface-block`), + + BlockViewExtension('test:gfx-block', literal`test-gfx-block`), ]; diff --git a/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts b/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts index 596d4af24c..091f28eeb8 100644 --- a/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts +++ b/blocksuite/integration-test/src/__tests__/edgeless/surface-model.spec.ts @@ -2,10 +2,8 @@ import type { SurfaceBlockModel } from '@blocksuite/affine/blocks/surface'; import type { BrushElementModel, GroupElementModel, - ShapeElementModel, } from '@blocksuite/affine/model'; -import { DefaultTheme } from '@blocksuite/affine/model'; -import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { beforeEach, describe, expect, test } from 'vitest'; import { wait } from '../utils/common.js'; import { setupEditor } from '../utils/setup.js'; @@ -23,88 +21,19 @@ beforeEach(async () => { return cleanup; }); -describe('elements management', () => { - test('addElement should work correctly', () => { - const id = model.addElement({ - type: 'shape', - }); - - expect(model.elementModels[0].id).toBe(id); - }); - - test('removeElement should work correctly', () => { - const id = model.addElement({ - type: 'shape', - }); - - model.deleteElement(id); - - expect(model.elementModels.length).toBe(0); - }); - - test('updateElement should work correctly', () => { - const id = model.addElement({ - type: 'shape', - }); - - model.updateElement(id, { xywh: '[10,10,200,200]' }); - - expect(model.elementModels[0].xywh).toBe('[10,10,200,200]'); - }); - - test('getElementById should return element', () => { - const id = model.addElement({ - type: 'shape', - }); - - expect(model.getElementById(id)).not.toBeNull(); - }); - - test('getElementById should return null if not found', () => { - expect(model.getElementById('not-found')).toBeNull(); - }); -}); - -describe('element model', () => { - test('default value should work correctly', () => { - const id = model.addElement({ - type: 'shape', - }); - - const element = model.getElementById(id)! as ShapeElementModel; - - expect(element.index).toBe('a0'); - expect(element.strokeColor).toBe(DefaultTheme.shapeStrokeColor); - expect(element.strokeWidth).toBe(4); - }); - - test('defined prop should not be overwritten by default value', () => { - const id = model.addElement({ - type: 'shape', - strokeColor: '--affine-palette-line-black', - }); - - const element = model.getElementById(id)! as ShapeElementModel; - - expect(element.strokeColor).toBe('--affine-palette-line-black'); - }); - - test('assign value to model property should update ymap directly', () => { - const id = model.addElement({ - type: 'shape', - }); - - const element = model.getElementById(id)! as ShapeElementModel; - - expect(element.yMap.get('strokeColor')).toBe(DefaultTheme.shapeStrokeColor); - - element.strokeColor = '--affine-palette-line-black'; - expect(element.yMap.get('strokeColor')).toBe('--affine-palette-line-black'); - expect(element.strokeColor).toBe('--affine-palette-line-black'); - }); -}); - describe('group', () => { + test('empty group should have all zero xywh', () => { + const id = model.addElement({ + type: 'group', + }); + const group = model.getElementById(id)! as GroupElementModel; + + expect(group.x).toBe(0); + expect(group.y).toBe(0); + expect(group.w).toBe(0); + expect(group.h).toBe(0); + }); + test('should get group', () => { const id = model.addElement({ type: 'shape', @@ -323,185 +252,6 @@ describe('connector', () => { }); }); -describe('stash/pop', () => { - test('stash and pop should work correctly', () => { - const id = model.addElement({ - type: 'shape', - strokeWidth: 4, - }); - const elementModel = model.getElementById(id)! as ShapeElementModel; - - expect(elementModel.strokeWidth).toBe(4); - - elementModel.stash('strokeWidth'); - elementModel.strokeWidth = 10; - expect(elementModel.strokeWidth).toBe(10); - expect(elementModel.yMap.get('strokeWidth')).toBe(4); - - elementModel.pop('strokeWidth'); - expect(elementModel.strokeWidth).toBe(10); - expect(elementModel.yMap.get('strokeWidth')).toBe(10); - - elementModel.strokeWidth = 6; - expect(elementModel.strokeWidth).toBe(6); - expect(elementModel.yMap.get('strokeWidth')).toBe(6); - }); - - test('assign stashed property should emit event', () => { - const id = model.addElement({ - type: 'shape', - strokeWidth: 4, - }); - const elementModel = model.getElementById(id)! as ShapeElementModel; - - elementModel.stash('strokeWidth'); - - const onchange = vi.fn(); - const subscription = model.elementUpdated.subscribe(({ id }) => { - subscription.unsubscribe(); - onchange(id); - }); - - elementModel.strokeWidth = 10; - expect(onchange).toHaveBeenCalledWith(id); - }); - - test('stashed property should also trigger derive decorator', () => { - const id = model.addElement({ - type: 'brush', - points: [ - [0, 0], - [100, 100], - [120, 150], - ], - }); - const elementModel = model.getElementById(id)! as BrushElementModel; - - elementModel.stash('points'); - elementModel.points = [ - [0, 0], - [50, 50], - [135, 145], - [150, 170], - [200, 180], - ]; - const points = elementModel.points; - - expect(elementModel.w).toBe(200 + elementModel.lineWidth); - expect(elementModel.h).toBe(180 + elementModel.lineWidth); - - expect(elementModel.yMap.get('points')).not.toEqual(points); - expect(elementModel.w).toBe(200 + elementModel.lineWidth); - expect(elementModel.h).toBe(180 + elementModel.lineWidth); - }); - - test('non-field property should not allow stash/pop, and should failed silently ', () => { - const id = model.addElement({ - type: 'group', - }); - const elementModel = model.getElementById(id)! as GroupElementModel; - - elementModel.stash('xywh'); - elementModel.xywh = '[10,10,200,200]'; - - expect(elementModel['_stashed'].has('xywh')).toBe(false); - - elementModel.pop('xywh'); - - expect(elementModel['_stashed'].has('xywh')).toBe(false); - expect(elementModel.yMap.has('xywh')).toBe(false); - }); -}); - -describe('derive decorator', () => { - test('derived decorator should work correctly', () => { - const id = model.addElement({ - type: 'brush', - points: [ - [0, 0], - [100, 100], - [120, 150], - ], - }); - const elementModel = model.getElementById(id)! as BrushElementModel; - - expect(elementModel.w).toBe(120 + elementModel.lineWidth); - expect(elementModel.h).toBe(150 + elementModel.lineWidth); - }); -}); - -describe('local decorator', () => { - test('local decorator should work correctly', () => { - const id = model.addElement({ - type: 'shape', - }); - const elementModel = model.getElementById(id)! as BrushElementModel; - - expect(elementModel.display).toBe(true); - - elementModel.display = false; - expect(elementModel.display).toBe(false); - - elementModel.opacity = 0.5; - expect(elementModel.opacity).toBe(0.5); - }); - - test('assign local property should emit event', () => { - const id = model.addElement({ - type: 'shape', - }); - const elementModel = model.getElementById(id)! as BrushElementModel; - - const onchange = vi.fn(); - - const subscription = model.elementUpdated.subscribe(({ id }) => { - subscription.unsubscribe(); - onchange(id); - }); - elementModel.display = false; - - expect(elementModel.display).toBe(false); - expect(onchange).toHaveBeenCalledWith(id); - }); -}); - -describe('convert decorator', () => { - test('convert decorator', () => { - const id = model.addElement({ - type: 'brush', - points: [ - [50, 25], - [200, 200], - [300, 300], - ], - }); - const elementModel = model.getElementById(id)! as BrushElementModel; - const halfLineWidth = elementModel.lineWidth / 2; - const xOffset = 50 - halfLineWidth; - const yOffset = 25 - halfLineWidth; - - expect(elementModel.points).toEqual([ - [50 - xOffset, 25 - yOffset], - [200 - xOffset, 200 - yOffset], - [300 - xOffset, 300 - yOffset], - ]); - }); -}); - -describe('basic property', () => { - test('empty group should have all zero xywh', () => { - const id = model.addElement({ - type: 'group', - }); - const group = model.getElementById(id)! as GroupElementModel; - - expect(group.x).toBe(0); - expect(group.y).toBe(0); - expect(group.w).toBe(0); - expect(group.h).toBe(0); - }); -}); - describe('brush', () => { test('same lineWidth should have same xywh', () => { const id = model.addElement({