UNPKG

tldraw

Version:

A tiny little drawing editor.

293 lines (248 loc) • 9.12 kB
import { TLShapeId, Vec, createShapeId } from '@tldraw/editor' import { TestEditor } from '../TestEditor' let editor: TestEditor const ids = { boxA: createShapeId('boxA'), boxB: createShapeId('boxB'), } beforeEach(() => { editor = new TestEditor() editor.createShapes([ { id: ids.boxA, type: 'geo', x: 10, y: 10, props: { w: 100, h: 100, }, }, { id: ids.boxB, type: 'geo', x: 27, y: 13, props: { w: 120, h: 167, }, }, ]) }) function nudgeAndGet(ids: TLShapeId[], key: string, shiftKey: boolean) { const step = editor.getInstanceState().isGridMode ? (shiftKey ? 50 : 10) : shiftKey ? 10 : 1 switch (key) { case 'ArrowLeft': { editor.markHistoryStoppingPoint('nudge') editor.nudgeShapes(editor.getSelectedShapeIds(), new Vec(-step, 0)) break } case 'ArrowRight': { editor.markHistoryStoppingPoint('nudge') editor.nudgeShapes(editor.getSelectedShapeIds(), new Vec(step, 0)) break } case 'ArrowUp': { editor.markHistoryStoppingPoint('nudge') editor.nudgeShapes(editor.getSelectedShapeIds(), new Vec(0, -step)) break } case 'ArrowDown': { editor.markHistoryStoppingPoint('nudge') editor.nudgeShapes(editor.getSelectedShapeIds(), new Vec(0, step)) break } } const shapes = ids.map((id) => editor.getShape(id)!) return shapes.map((shape) => ({ x: shape.x, y: shape.y })) } function getShape(ids: TLShapeId[]) { const shapes = ids.map((id) => editor.getShape(id)!) return shapes.map((shape) => ({ x: shape.x, y: shape.y })) } describe('When a shape is selected...', () => { it('nudges and undoes', () => { editor.setSelectedShapes([ids.boxA]) editor.keyDown('ArrowUp') expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 9 }) editor.keyUp('ArrowUp') editor.undo() expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 10 }) editor.redo() expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 9 }) }) it('nudges and holds', () => { editor.setSelectedShapes([ids.boxA]) editor.keyDown('ArrowUp') editor.keyRepeat('ArrowUp') editor.keyRepeat('ArrowUp') editor.keyRepeat('ArrowUp') editor.keyRepeat('ArrowUp') editor.keyUp('ArrowUp') expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 5 }) // Undoing should go back to the keydown state, all those // repeats should be ephemeral and squashed down editor.undo() expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 10 }) editor.redo() expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 5 }) }) it('nudges a shape correctly', () => { editor.setSelectedShapes([ids.boxA]) editor.keyDown('ArrowUp') expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 9 }) editor.keyUp('ArrowUp') editor.keyDown('ArrowRight') expect(editor.getSelectionPageBounds()).toMatchObject({ x: 11, y: 9 }) editor.keyUp('ArrowRight') editor.keyDown('ArrowDown') expect(editor.getSelectionPageBounds()).toMatchObject({ x: 11, y: 10 }) editor.keyUp('ArrowDown') editor.keyDown('ArrowLeft') expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 10 }) editor.keyUp('ArrowLeft') }) }) // The tests below were written before the tests above; they all still work // but there may be some redundancy. They may cover a few cases that aren't // covered above though. describe('When a shape is rotated...', () => { it('Translates correctly in page space', () => { // Rotate boxB by 90 degrees and move it to 0,0 for simplicity's sake editor.select(ids.boxB) editor.rotateShapesBy([ids.boxB], Math.PI / 2) editor.updateShapes([{ id: ids.boxB, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } }]) // Make box A a child of box B editor.reparentShapes([ids.boxA], ids.boxB) // editor.updateShapes([{ id: ids.boxB, type: 'geo', x: 10, y: 10 }]) // Here's the selection page bounds and shape before we nudge it editor.setSelectedShapes([ids.boxA]) expect(editor.getSelectionPageBounds()).toCloselyMatchObject({ x: 10, y: 10, w: 100, h: 100 }) expect(editor.getShape(ids.boxA)).toCloselyMatchObject({ x: 10, y: -10 }) // Select box A and move it up. The page bounds should move up, but the // shape should move left (since its parent is rotated 90 degrees) editor.keyDown('ArrowUp') expect(editor.getSelectionPageBounds()).toMatchObject({ x: 10, y: 9, w: 100, h: 100 }) expect(editor.getShape(ids.boxA)).toMatchObject({ x: 9, y: -10 }) editor.keyUp('ArrowUp') }) }) describe('When a shape is selected...', () => { it('nudges a shape correctly', () => { editor.setSelectedShapes([ids.boxA]) expect(nudgeAndGet([ids.boxA], 'ArrowUp', false)).toMatchObject([{ x: 10, y: 9 }]) expect(nudgeAndGet([ids.boxA], 'ArrowRight', false)).toMatchObject([{ x: 11, y: 9 }]) expect(nudgeAndGet([ids.boxA], 'ArrowDown', false)).toMatchObject([{ x: 11, y: 10 }]) expect(nudgeAndGet([ids.boxA], 'ArrowLeft', false)).toMatchObject([{ x: 10, y: 10 }]) }) it('nudges a shape with shift key pressed', () => { editor.setSelectedShapes([ids.boxA]) expect(nudgeAndGet([ids.boxA], 'ArrowUp', true)).toMatchObject([{ x: 10, y: 0 }]) expect(nudgeAndGet([ids.boxA], 'ArrowRight', true)).toMatchObject([{ x: 20, y: 0 }]) expect(nudgeAndGet([ids.boxA], 'ArrowDown', true)).toMatchObject([{ x: 20, y: 10 }]) expect(nudgeAndGet([ids.boxA], 'ArrowLeft', true)).toMatchObject([{ x: 10, y: 10 }]) }) it.todo('updates bound shapes') }) describe('When grid is enabled...', () => { it('nudges a shape correctly', () => { editor.updateInstanceState({ isGridMode: true }) editor.setSelectedShapes([ids.boxA]) expect(nudgeAndGet([ids.boxA], 'ArrowUp', false)).toMatchObject([{ x: 10, y: 0 }]) expect(nudgeAndGet([ids.boxA], 'ArrowRight', false)).toMatchObject([{ x: 20, y: 0 }]) expect(nudgeAndGet([ids.boxA], 'ArrowDown', false)).toMatchObject([{ x: 20, y: 10 }]) expect(nudgeAndGet([ids.boxA], 'ArrowLeft', false)).toMatchObject([{ x: 10, y: 10 }]) }) it('nudges a shape with shift key pressed', () => { editor.updateInstanceState({ isGridMode: true }) editor.setSelectedShapes([ids.boxA]) expect(nudgeAndGet([ids.boxA], 'ArrowUp', true)).toMatchObject([{ x: 10, y: -40 }]) expect(nudgeAndGet([ids.boxA], 'ArrowRight', true)).toMatchObject([{ x: 60, y: -40 }]) expect(nudgeAndGet([ids.boxA], 'ArrowDown', true)).toMatchObject([{ x: 60, y: 10 }]) expect(nudgeAndGet([ids.boxA], 'ArrowLeft', true)).toMatchObject([{ x: 10, y: 10 }]) }) }) describe('When multiple shapes are selected...', () => { it('Nudges all shapes correctly', () => { editor.setSelectedShapes([ids.boxA, ids.boxB]) expect(nudgeAndGet([ids.boxA, ids.boxB], 'ArrowUp', false)).toMatchObject([ { x: 10, y: 9 }, { x: 27, y: 12 }, ]) expect(nudgeAndGet([ids.boxA, ids.boxB], 'ArrowRight', false)).toMatchObject([ { x: 11, y: 9 }, { x: 28, y: 12 }, ]) expect(nudgeAndGet([ids.boxA, ids.boxB], 'ArrowDown', false)).toMatchObject([ { x: 11, y: 10 }, { x: 28, y: 13 }, ]) expect(nudgeAndGet([ids.boxA, ids.boxB], 'ArrowLeft', false)).toMatchObject([ { x: 10, y: 10 }, { x: 27, y: 13 }, ]) }) }) describe('When undo redo is on...', () => { it('Does not nudge any shapes', () => { editor.setSelectedShapes([ids.boxA]) expect(nudgeAndGet([ids.boxA], 'ArrowUp', false)).toMatchObject([{ x: 10, y: 9 }]) editor.undo() expect(getShape([ids.boxA])).toMatchObject([{ x: 10, y: 10 }]) editor.redo() expect(getShape([ids.boxA])).toMatchObject([{ x: 10, y: 9 }]) expect(nudgeAndGet([ids.boxA], 'ArrowRight', false)).toMatchObject([{ x: 11, y: 9 }]) editor.undo() expect(getShape([ids.boxA])).toMatchObject([{ x: 10, y: 9 }]) editor.redo() expect(getShape([ids.boxA])).toMatchObject([{ x: 11, y: 9 }]) }) }) describe('When nudging a rotated shape...', () => { it('Moves the page point correctly', () => { editor.setSelectedShapes([ids.boxA]) const shapeA = editor.getShape(ids.boxA)! editor.updateShapes([{ id: ids.boxA, type: shapeA.type, rotation: 90 }]) expect(nudgeAndGet([ids.boxA], 'ArrowRight', false)).toMatchObject([{ x: 11, y: 10 }]) editor.updateShapes([{ id: ids.boxA, type: shapeA.type, rotation: -90 }]) expect(nudgeAndGet([ids.boxA], 'ArrowDown', false)).toMatchObject([{ x: 11, y: 11 }]) }) }) describe('When nudging multiple rotated shapes...', () => { it('Moves the page point correctly', () => { editor.setSelectedShapes([ids.boxA, ids.boxB]) const shapeA = editor.getShape(ids.boxA)! const shapeB = editor.getShape(ids.boxB)! editor.updateShapes([ { ...shapeA, rotation: 90, }, { ...shapeB, rotation: -90, }, ]) expect(nudgeAndGet([ids.boxA, ids.boxB], 'ArrowRight', false)).toMatchObject([ { x: 11, y: 10 }, { x: 28, y: 13 }, ]) editor.updateShapes([ { id: shapeA.id, type: shapeA.type, rotation: -90, }, { id: shapeB.id, type: shapeB.type, rotation: 90, }, ]) expect(nudgeAndGet([ids.boxA, ids.boxB], 'ArrowDown', false)).toMatchObject([ { x: 11, y: 11 }, { x: 28, y: 14 }, ]) }) })