UNPKG

tldraw

Version:

A tiny little drawing editor.

309 lines (271 loc) • 8.37 kB
import { createShapeId } from '@tldraw/editor' import { TestEditor } from '../TestEditor' let editor: TestEditor const ids = { lockedShapeA: createShapeId('boxA'), unlockedShapeA: createShapeId('boxB'), unlockedShapeB: createShapeId('boxC'), lockedShapeB: createShapeId('boxD'), lockedGroup: createShapeId('lockedGroup'), groupedBoxA: createShapeId('grouppedBoxA'), groupedBoxB: createShapeId('grouppedBoxB'), lockedFrame: createShapeId('lockedFrame'), } beforeEach(() => { editor = new TestEditor() editor.selectAll() editor.deleteShapes(editor.getSelectedShapeIds()) editor.createShapes([ { id: ids.lockedShapeA, type: 'geo', x: 0, y: 0, isLocked: true, }, { id: ids.lockedShapeB, type: 'geo', x: 100, y: 100, isLocked: true, }, { id: ids.unlockedShapeA, type: 'geo', x: 200, y: 200, isLocked: false, }, { id: ids.unlockedShapeB, type: 'geo', x: 300, y: 300, isLocked: false, }, { id: ids.lockedGroup, type: 'group', x: 800, y: 800, isLocked: true, }, { id: ids.groupedBoxA, type: 'geo', x: 1000, y: 1000, parentId: ids.lockedGroup, isLocked: false, }, { id: ids.groupedBoxB, type: 'geo', x: 1200, y: 1200, parentId: ids.lockedGroup, isLocked: false, }, { id: ids.lockedFrame, type: 'frame', x: 1600, y: 1600, isLocked: true, }, ]) }) describe('Locking', () => { it('Can lock shapes', () => { editor.setSelectedShapes([ids.unlockedShapeA]) editor.toggleLock(editor.getSelectedShapeIds()) expect(editor.getShape(ids.unlockedShapeA)!.isLocked).toBe(true) // Locking deselects the shape expect(editor.getSelectedShapeIds()).toEqual([]) }) }) describe('Locked shapes', () => { it('Cannot be deleted', () => { const numberOfShapesBefore = editor.getCurrentPageShapes().length editor.deleteShapes([ids.lockedShapeA]) expect(editor.getCurrentPageShapes().length).toBe(numberOfShapesBefore) }) it('Cannot be changed', () => { const xBefore = editor.getShape(ids.lockedShapeA)!.x editor.updateShapes([{ id: ids.lockedShapeA, type: 'geo', x: 100 }]) expect(editor.getShape(ids.lockedShapeA)!.x).toBe(xBefore) }) it('Cannot be moved', () => { const shape = editor.getShape(ids.lockedShapeA) editor.pointerDown(150, 150, { target: 'shape', shape }) editor.expectToBeIn('select.pointing_canvas') editor.pointerMove(10, 10) editor.expectToBeIn('select.brushing') editor.pointerUp() editor.expectToBeIn('select.idle') }) it('Cannot be selected with select all', () => { editor.selectAll() expect(editor.getSelectedShapeIds()).toEqual([ids.unlockedShapeA, ids.unlockedShapeB]) }) it('Cannot be selected by clicking', () => { const shape = editor.getShape(ids.lockedShapeA)! editor .pointerDown(10, 10, { target: 'shape', shape }) .expectToBeIn('select.pointing_canvas') .pointerUp() .expectToBeIn('select.idle') expect(editor.getSelectedShapeIds()).not.toContain(shape.id) }) it('Cannot be edited', () => { const shape = editor.getShape(ids.lockedShapeA)! const shapeCount = editor.getCurrentPageShapes().length // We create a new shape and we edit that one editor.doubleClick(10, 10, { target: 'shape', shape }).expectToBeIn('select.editing_shape') expect(editor.getCurrentPageShapes().length).toBe(shapeCount + 1) expect(editor.getSelectedShapeIds()).not.toContain(shape.id) }) it('Cannot be grouped', () => { const shapeCount = editor.getCurrentPageShapes().length const parentBefore = editor.getShape(ids.lockedShapeA)!.parentId editor.groupShapes([ids.lockedShapeA, ids.unlockedShapeA, ids.unlockedShapeB]) expect(editor.getCurrentPageShapes().length).toBe(shapeCount + 1) const parentAfter = editor.getShape(ids.lockedShapeA)!.parentId expect(parentAfter).toBe(parentBefore) }) it('Locked frames do not accept new shapes', () => { const frame = editor.getShape(ids.lockedFrame)! const frameUtil = editor.getShapeUtil(frame) expect(frameUtil.canReceiveNewChildrenOfType(frame, 'box')).toBe(false) const shape = editor.getShape(ids.lockedShapeA)! expect(frameUtil.canDropShapes(frame, [shape])).toBe(false) }) }) describe('Unlocking', () => { it('Can unlock shapes', () => { editor.setSelectedShapes([ids.lockedShapeA, ids.lockedShapeB]) const getLockedStatus = () => [ids.lockedShapeA, ids.lockedShapeB].map((id) => editor.getShape(id)!.isLocked) expect(getLockedStatus()).toStrictEqual([true, true]) editor.toggleLock(editor.getSelectedShapeIds()) expect(getLockedStatus()).toStrictEqual([false, false]) }) }) describe('When forced', () => { it('Can be deleted', () => { editor.run( () => { const numberOfShapesBefore = editor.getCurrentPageShapes().length editor.deleteShapes([ids.lockedShapeA]) expect(editor.getCurrentPageShapes().length).toBe(numberOfShapesBefore - 1) }, { ignoreShapeLock: true } ) }) it('Can be changed', () => { editor.run( () => { editor.updateShapes([{ id: ids.lockedShapeA, type: 'geo', x: 100 }]) expect(editor.getShape(ids.lockedShapeA)!.x).toBe(100) }, { ignoreShapeLock: true } ) }) it('Can be grouped / ungrouped', () => { editor.run( () => { const shapeCount = editor.getCurrentPageShapes().length editor.groupShapes([ids.lockedShapeA, ids.unlockedShapeA, ids.unlockedShapeB]) expect(editor.getCurrentPageShapes().length).toBe(shapeCount + 1) expect(editor.getShape(ids.lockedShapeA)!.parentId).not.toBe(editor.getCurrentPageId()) }, { ignoreShapeLock: true } ) }) it('Cannot be moved', () => { editor.run( () => { const shape = editor.getShape(ids.lockedShapeA) editor.pointerDown(150, 150, { target: 'shape', shape }) editor.expectToBeIn('select.pointing_canvas') editor.pointerMove(10, 10) editor.expectToBeIn('select.brushing') editor.pointerUp() editor.expectToBeIn('select.idle') }, { ignoreShapeLock: true } ) }) it('Can be selected with select all', () => { editor.run( () => { editor.selectAll() expect(editor.getSelectedShapeIds()).toEqual([ids.unlockedShapeA, ids.unlockedShapeB]) }, { ignoreShapeLock: true } ) }) it('Cannot be selected by clicking', () => { editor.run( () => { const shape = editor.getShape(ids.lockedShapeA)! editor .pointerDown(10, 10, { target: 'shape', shape }) .expectToBeIn('select.pointing_canvas') .pointerUp() .expectToBeIn('select.idle') expect(editor.getSelectedShapeIds()).not.toContain(shape.id) }, { ignoreShapeLock: true } ) }) it('Cannot be edited', () => { editor.run( () => { const shape = editor.getShape(ids.lockedShapeA)! const shapeCount = editor.getCurrentPageShapes().length // We create a new shape and we edit that one editor.doubleClick(10, 10, { target: 'shape', shape }).expectToBeIn('select.editing_shape') expect(editor.getCurrentPageShapes().length).toBe(shapeCount + 1) expect(editor.getSelectedShapeIds()).not.toContain(shape.id) }, { ignoreShapeLock: true } ) }) }) it('does not update a locked shape, even if spreading in a full shape', () => { const myShapeId = createShapeId() editor.createShape({ id: myShapeId, type: 'geo', isLocked: true }) const myLockedShape = editor.getShape(myShapeId)! // include the `isLocked` property, but don't change it editor.updateShape({ ...myLockedShape, x: 100 }) expect(editor.getShape(myShapeId)).toMatchObject(myLockedShape) }) it('works when forced', () => { const myShapeId = createShapeId() editor.createShape({ id: myShapeId, type: 'geo', isLocked: true }) const myLockedShape = editor.getShape(myShapeId)! // no change from update editor.updateShape({ ...myLockedShape, x: 100 }) expect(editor.getShape(myShapeId)).toMatchObject(myLockedShape) // no change from delete editor.deleteShapes([myLockedShape]) expect(editor.getShape(myShapeId)).toMatchObject(myLockedShape) // update works editor.run( () => { editor.updateShape({ ...myLockedShape, x: 100 }) }, { ignoreShapeLock: true } ) expect(editor.getShape(myShapeId)).toMatchObject({ ...myLockedShape, x: 100 }) // delete works editor.run( () => { editor.deleteShapes([myLockedShape]) }, { ignoreShapeLock: true } ) expect(editor.getShape(myShapeId)).toBeUndefined() })