import { assert } from '@tldraw/editor' import { TestEditor } from '../../../test/TestEditor' let editor: TestEditor beforeEach(() => { editor = new TestEditor() }) it('enters the line state', () => { editor.setCurrentTool('line') expect(editor.getCurrentToolId()).toBe('line') editor.expectToBeIn('line.idle') }) describe('When in the idle state', () => { it('enters the pointing state and creates a shape on pointer down', () => { const shapesBefore = editor.getCurrentPageShapes().length editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }) const shapesAfter = editor.getCurrentPageShapes().length expect(shapesAfter).toBe(shapesBefore + 1) editor.expectToBeIn('line.pointing') }) it('returns to select on cancel', () => { editor.setCurrentTool('line') editor.cancel() editor.expectToBeIn('select.idle') }) }) describe('When in the pointing state', () => { it('createes on pointer up', () => { const shapesBefore = editor.getCurrentPageShapes().length editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerUp(0, 0) const shapesAfter = editor.getCurrentPageShapes().length expect(shapesAfter).toBe(shapesBefore + 1) expect(editor.getHintingShapeIds().length).toBe(0) editor.expectToBeIn('line.idle') }) it('bails on cancel', () => { const shapesBefore = editor.getCurrentPageShapes().length editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).cancel() const shapesAfter = editor.getCurrentPageShapes().length expect(shapesAfter).toBe(shapesBefore) expect(editor.getHintingShapeIds().length).toBe(0) editor.expectToBeIn('line.idle') }) it('enters the dragging state on pointer move', () => { editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) editor.expectToBeIn('select.dragging_handle') }) }) // This could be moved to dragging_handle describe('When dragging the line', () => { it('updates the line on pointer move', () => { editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] editor.expectShapeToMatch(line, { id: line.id, type: 'line', x: 0, y: 0, props: { points: { a1: { id: 'a1', index: 'a1', x: 0, y: 0 }, a2: { id: 'a2', index: 'a2', x: 10, y: 10 }, }, }, }) editor.expectToBeIn('select.dragging_handle') }) it('returns to select.idle, keeping shape, on pointer up', () => { const shapesBefore = editor.getCurrentPageShapes().length editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) const shapesAfter = editor.getCurrentPageShapes().length expect(shapesAfter).toBe(shapesBefore + 1) expect(editor.getHintingShapeIds().length).toBe(0) editor.expectToBeIn('select.idle') }) it('returns to line.idle, keeping shape, on pointer up if tool lock is enabled', () => { editor.updateInstanceState({ isToolLocked: true }) const shapesBefore = editor.getCurrentPageShapes().length editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) const shapesAfter = editor.getCurrentPageShapes().length expect(shapesAfter).toBe(shapesBefore + 1) expect(editor.getHintingShapeIds().length).toBe(0) editor.expectToBeIn('line.idle') }) it('bails on cancel', () => { const shapesBefore = editor.getCurrentPageShapes().length editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .cancel() const shapesAfter = editor.getCurrentPageShapes().length expect(shapesAfter).toBe(shapesBefore) editor.expectToBeIn('line.idle') }) }) describe('When extending the line with the shift-key in tool-lock mode', () => { it('extends a line by joining-the-dots', () => { editor.updateInstanceState({ isToolLocked: true }) editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) .keyDown('Shift') .pointerDown(20, 10, { target: 'canvas' }) .pointerUp(20, 10) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] assert(editor.isShapeOfType(line, 'line')) expect(Object.keys(line.props.points).length).toBe(3) }) it('extends a line after a click by shift-click dragging', () => { editor.updateInstanceState({ isToolLocked: true }) editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerUp(0, 0) .keyDown('Shift') .pointerDown(20, 10, { target: 'canvas' }) .pointerMove(30, 10) .pointerUp(30, 10) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] assert(editor.isShapeOfType(line, 'line')) expect(Object.keys(line.props.points).length).toBe(2) }) it('extends a line by shift-click dragging', () => { editor.updateInstanceState({ isToolLocked: true }) editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) .keyDown('Shift') .pointerDown(20, 10, { target: 'canvas' }) .pointerMove(30, 10) .pointerUp(30, 10) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] assert(editor.isShapeOfType(line, 'line')) expect(Object.keys(line.props.points).length).toBe(3) }) it('extends a line by shift-clicking even after canceling a pointerdown', () => { editor.updateInstanceState({ isToolLocked: true }) editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) .keyDown('Shift') .pointerDown(20, 10, { target: 'canvas' }) .cancel() .pointerDown(20, 10, { target: 'canvas' }) .pointerMove(30, 10) .pointerUp(30, 10) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] assert(editor.isShapeOfType(line, 'line')) expect(Object.keys(line.props.points).length).toBe(3) }) it('extends a line by shift-clicking even after canceling a pointermove', () => { editor.updateInstanceState({ isToolLocked: true }) editor .setCurrentTool('line') .pointerDown(0, 0, { target: 'canvas' }) .pointerMove(10, 10) .pointerUp(10, 10) .keyDown('Shift') .pointerDown(20, 10, { target: 'canvas' }) .pointerUp(20, 10) .pointerMove(30, 10) .cancel() .pointerDown(30, 10, { target: 'canvas' }) .pointerMove(40, 10) .pointerUp(40, 10) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] assert(editor.isShapeOfType(line, 'line')) expect(Object.keys(line.props.points).length).toBe(3) }) }) describe('minimum bend distance for shift-clicked handles is measured in screen space', () => { // regression for #7661: minimum bend distance for shift-clicked handles is measured in screen space // Creates a two-point line whose end handle sits at screen point (200, 200), the // camera being static so the handle's screen position matches the pointer-up location. function createTwoPointLine() { editor .setCurrentTool('line') .pointerDown(100, 100, { target: 'canvas' }) .pointerMove(200, 200) .pointerUp(200, 200) } beforeEach(() => { editor.updateInstanceState({ isToolLocked: true }) }) it('adds a point when the click is more than 2 screen pixels away while zoomed in', () => { editor.setCamera({ x: 0, y: 0, z: 4 }) createTwoPointLine() // 4 screen pixels from the end handle: past the 2px screen threshold, but only 1 // page unit — under the old page-space threshold this would have merged. editor.keyDown('Shift').pointerDown(204, 200, { target: 'canvas' }).pointerUp(204, 200) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] assert(editor.isShapeOfType(line, 'line')) expect(Object.keys(line.props.points).length).toBe(3) }) it('merges the click when within 2 screen pixels while zoomed out', () => { editor.setCamera({ x: 0, y: 0, z: 0.25 }) createTwoPointLine() // 1 screen pixel from the end handle: within the 2px screen threshold, but 4 page // units — under the old page-space threshold this would have added a point. editor.keyDown('Shift').pointerDown(201, 200, { target: 'canvas' }).pointerUp(201, 200) const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1] assert(editor.isShapeOfType(line, 'line')) expect(Object.keys(line.props.points).length).toBe(2) }) }) describe('tool lock bug', () => { it('works as expected when tool lock is on but shift is off', () => { expect(editor.getCurrentPageShapes().length).toBe(0) editor .updateInstanceState({ isToolLocked: true }) .setCurrentTool('line') .pointerDown(0, 0) .pointerMove(10, 10) .pointerUp(10, 10) .pointerDown(100, 100) .pointerMove(110, 110) .pointerUp(100, 100) .pointerUp(120, 110) expect(editor.getCurrentPageShapes().length).toBe(2) }) })