UNPKG

tldraw

Version:

A tiny little drawing editor.

291 lines (234 loc) • 8.99 kB
import { TLDrawShape, TLHighlightShape, last } from '@tldraw/editor' import { vi } from 'vitest' import { TEST_DRAW_SHAPE_SCREEN_POINTS } from './drawing.data' import { base64ToPoints } from './test-jsx' import { TestEditor } from './TestEditor' vi.useFakeTimers() let editor: TestEditor afterEach(() => { editor?.dispose() }) beforeEach(() => { editor = new TestEditor() editor.createShapes([]) }) type DrawableShape = TLDrawShape | TLHighlightShape for (const toolType of ['draw', 'highlight'] as const) { describe(`When ${toolType}ing...`, () => { it('Creates a dot', () => { editor .setCurrentTool(toolType) .pointerDown(60, 60) .expectToBeIn(`${toolType}.drawing`) .pointerUp() .expectToBeIn(`${toolType}.idle`) expect(editor.getCurrentPageShapes()).toHaveLength(1) const shape = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape.type).toBe(toolType) expect(shape.props.segments.length).toBe(1) const segment = shape.props.segments[0] expect(segment.type).toBe('free') }) it('Creates a dot when shift is held down', () => { editor .setCurrentTool(toolType) .keyDown('Shift') .pointerDown(60, 60) .expectToBeIn(`${toolType}.drawing`) .pointerUp() .expectToBeIn(`${toolType}.idle`) expect(editor.getCurrentPageShapes()).toHaveLength(1) const shape = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape.type).toBe(toolType) expect(shape.props.segments.length).toBe(1) const segment = shape.props.segments[0] expect(segment.type).toBe('straight') }) it('Creates a free draw line when shift is not held', () => { editor.setCurrentTool(toolType).pointerDown(10, 10).pointerMove(20, 20) const shape = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape.props.segments.length).toBe(1) const segment = shape.props.segments[0] expect(segment.type).toBe('free') }) it('Creates a straight line when shift is held', () => { editor.setCurrentTool(toolType).keyDown('Shift').pointerDown(10, 10).pointerMove(20, 20) const shape = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape.props.segments.length).toBe(1) const segment = shape.props.segments[0] expect(segment.type).toBe('straight') const points = base64ToPoints(segment.path) expect(points.length).toBe(2) }) it('Switches between segment types when shift is pressed / released (starting with shift up)', () => { editor .setCurrentTool(toolType) .pointerDown(10, 10) .pointerMove(20, 20) .keyDown('Shift') .pointerMove(30, 30) .keyUp('Shift') .pointerMove(40, 40) .pointerUp() const shape = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape.props.segments.length).toBe(3) expect(shape.props.segments[0].type).toBe('free') expect(shape.props.segments[1].type).toBe('straight') expect(shape.props.segments[2].type).toBe('free') }) it('Switches between segment types when shift is pressed / released (starting with shift down)', () => { editor .setCurrentTool(toolType) .keyDown('Shift') .pointerDown(10, 10) .pointerMove(20, 20) .keyUp('Shift') .pointerMove(30, 30) .keyDown('Shift') .pointerMove(40, 40) .pointerUp() const shape = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape.props.segments.length).toBe(3) expect(shape.props.segments[0].type).toBe('straight') expect(shape.props.segments[1].type).toBe('free') expect(shape.props.segments[2].type).toBe('straight') }) it('Extends previously drawn line when shift is held', () => { editor .setCurrentTool(toolType) .keyDown('Shift') .pointerDown(10, 10) .pointerUp() .pointerDown(20, 20) const shape1 = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape1.props.segments.length).toBe(2) expect(shape1.props.segments[0].type).toBe('straight') expect(shape1.props.segments[1].type).toBe('straight') editor.pointerUp().pointerDown(30, 30).pointerUp() const shape2 = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape2.props.segments.length).toBe(3) expect(shape2.props.segments[2].type).toBe('straight') }) it('Does not extends previously drawn line after switching to another tool', () => { editor .setCurrentTool(toolType) .pointerDown(10, 10) .pointerUp() .setCurrentTool('select') .setCurrentTool(toolType) .keyDown('Shift') .pointerDown(20, 20) .pointerMove(30, 30) expect(editor.getCurrentPageShapes()).toHaveLength(2) const shape1 = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape1.props.segments.length).toBe(1) expect(shape1.props.segments[0].type).toBe('free') const shape2 = editor.getCurrentPageShapes()[1] as DrawableShape expect(shape2.props.segments.length).toBe(1) expect(shape2.props.segments[0].type).toBe('straight') }) it('Snaps to 15 degree angle when shift is held', () => { const magnitude = 10 const angle = (17 * Math.PI) / 180 const x = magnitude * Math.cos(angle) const y = magnitude * Math.sin(angle) const snappedAngle = (15 * Math.PI) / 180 const snappedX = magnitude * Math.cos(snappedAngle) const snappedY = magnitude * Math.sin(snappedAngle) editor.setCurrentTool(toolType).keyDown('Shift').pointerDown(0, 0).pointerMove(x, y) const shape = editor.getCurrentPageShapes()[0] as DrawableShape const segment = shape.props.segments[0] const points = base64ToPoints(segment.path) expect(points[1].x).toBeCloseTo(snappedX) expect(points[1].y).toBeCloseTo(snappedY) }) it('Doesnt snap to 15 degree angle when cmd is held', () => { const magnitude = 10 const angle = (17 * Math.PI) / 180 const x = magnitude * Math.cos(angle) const y = magnitude * Math.sin(angle) editor.setCurrentTool(toolType).keyDown('Meta').pointerDown(0, 0).pointerMove(x, y) const shape = editor.getCurrentPageShapes()[0] as DrawableShape const segment = shape.props.segments[0] const points = base64ToPoints(segment.path) expect(points[1].x).toBeCloseTo(x) expect(points[1].y).toBeCloseTo(y) }) it('Snaps to start or end of straight segments in self when shift + cmd is held', () => { editor .setCurrentTool(toolType) .keyDown('Shift') .pointerDown(0, 0) .pointerUp() .pointerDown(0, 10) .pointerUp() .pointerDown(10, 0) .pointerUp() .pointerDown(10, 0) .pointerMove(1, 0) // very close to first point const shape1 = editor.getCurrentPageShapes()[0] as DrawableShape const segment1 = last(shape1.props.segments)! const points1 = base64ToPoints(segment1.path) const point1 = last(points1)! expect(point1.x).toBe(1) editor.keyDown('Meta') const shape2 = editor.getCurrentPageShapes()[0] as DrawableShape const segment2 = last(shape2.props.segments)! const points2 = base64ToPoints(segment2.path) const point2 = last(points2)! expect(point2.x).toBe(0) }) it('Snaps to position along straight segments in self when shift + cmd is held', () => { editor .setCurrentTool(toolType) .keyDown('Shift') .pointerDown(0, 0) .pointerUp() .pointerDown(0, 10) .pointerUp() .pointerDown(10, 5) .pointerUp() .pointerDown(10, 5) .pointerMove(1, 5) const shape1 = editor.getCurrentPageShapes()[0] as DrawableShape const segment1 = last(shape1.props.segments)! const points1 = base64ToPoints(segment1.path) const point1 = last(points1)! expect(point1.x).toBe(1) editor.keyDown('Meta') const shape2 = editor.getCurrentPageShapes()[0] as DrawableShape const segment2 = last(shape2.props.segments)! const points2 = base64ToPoints(segment2.path) const point2 = last(points2)! expect(point2.x).toBe(0) }) it('Deletes very short lines on interrupt', () => { editor.setCurrentTool(toolType).pointerDown(0, 0).pointerMove(0.1, 0.1).interrupt() expect(editor.getCurrentPageShapes()).toHaveLength(0) }) it('Does not delete longer lines on interrupt', () => { editor.setCurrentTool(toolType).pointerDown(0, 0).pointerMove(5, 5).interrupt() expect(editor.getCurrentPageShapes()).toHaveLength(1) }) it('Completes on cancel', () => { editor.setCurrentTool(toolType).pointerDown(0, 0).pointerMove(5, 5).cancel() expect(editor.getCurrentPageShapes()).toHaveLength(1) const shape = editor.getCurrentPageShapes()[0] as DrawableShape expect(shape.props.segments.length).toBe(1) }) }) } it('Draws a bunch', () => { editor.setCurrentTool('draw').setCamera({ x: 0, y: 0, z: 1 }) const [first, ...rest] = TEST_DRAW_SHAPE_SCREEN_POINTS editor.pointerMove(first.x, first.y).pointerDown() for (const point of rest) { editor.pointerMove(point.x, point.y) } editor.pointerUp() editor.selectAll() const shape = { ...editor.getLastCreatedShape() } // @ts-expect-error delete shape.id expect(shape).toMatchSnapshot('draw shape') })