Tldraw/packages/editor/src/lib/test/tools/cropping.test.ts

1010 wiersze
28 KiB
TypeScript

import { TLImageShape, createShapeId } from '@tldraw/tlschema'
import { MIN_CROP_SIZE } from '../../constants'
import { TestEditor } from '../TestEditor'
jest.useFakeTimers()
let editor: TestEditor
afterEach(() => {
editor?.dispose()
})
const ids = {
imageA: createShapeId('imageA'),
imageB: createShapeId('imageB'),
boxA: createShapeId('boxA'),
}
const imageWidth = 1200
const imageHeight = 800
const imageProps = {
assetId: null,
playing: true,
url: '',
w: imageWidth,
h: imageHeight,
}
beforeEach(() => {
editor = new TestEditor()
editor.createShapes([
{
id: ids.imageA,
type: 'image',
x: 100,
y: 100,
props: imageProps,
},
{
id: ids.imageB,
type: 'image',
x: 500,
y: 500,
props: {
...imageProps,
w: imageWidth / 2,
h: imageHeight / 2,
crop: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: 0.5, y: 0.5 },
},
},
},
{
id: ids.boxA,
type: 'geo',
x: 1000,
y: 1000,
props: {
w: 100,
h: 100,
},
},
])
})
describe('When in the select.idle state', () => {
it('double clicking an image should transition to select.crop', () => {
editor.select(ids.boxA)
editor.expectPathToBe('root.select.idle')
expect(editor.selectedIds).toMatchObject([ids.boxA])
expect(editor.croppingId).toBe(null)
editor.doubleClick(550, 550, ids.imageB)
editor.expectPathToBe('root.select.crop.idle')
expect(editor.selectedIds).toMatchObject([ids.imageB])
expect(editor.croppingId).toBe(ids.imageB)
editor.undo()
// first selection should have been a mark
editor.expectPathToBe('root.select.idle')
expect(editor.selectedIds).toMatchObject([ids.imageB])
expect(editor.croppingId).toBe(null)
editor.undo()
// back to start
editor.expectPathToBe('root.select.idle')
expect(editor.selectedIds).toMatchObject([ids.boxA])
expect(editor.croppingId).toBe(null)
editor.redo().redo()
editor.expectPathToBe('root.select.idle')
expect(editor.selectedIds).toMatchObject([ids.imageB])
expect(editor.croppingId).toBe(ids.imageB) // todo: fix this! we shouldn't set this on redo
})
it('when ONLY ONE image is selected double clicking a selection handle should transition to select.crop', () => {
// when two shapes are selected, double click should not transition
// corner (two shapes)
editor
.expectPathToBe('root.select.idle')
.select(ids.imageA, ids.imageB)
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom_right',
})
.expectPathToBe('root.select.idle')
expect(editor.croppingId).toBe(null)
// edge (two shapes)
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom',
})
.expectPathToBe('root.select.idle')
expect(editor.croppingId).toBe(null)
// corner (one shape)
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom_right',
})
.expectPathToBe('root.select.crop.idle')
expect(editor.croppingId).toBe(ids.imageB)
// edge (one shape)
editor
.cancel()
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom',
})
.expectPathToBe('root.select.crop.idle')
expect(editor.croppingId).toBe(ids.imageB)
})
it('when only an image is selected pressing enter should transition to select.crop', () => {
// two shapes
editor
.expectPathToBe('root.select.idle')
.select(ids.imageA, ids.imageB)
.keyDown('Enter')
.keyUp('Enter')
.expectPathToBe('root.select.idle')
// one image
editor
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.keyDown('Enter')
.keyUp('Enter')
.expectPathToBe('root.select.crop.idle')
expect(editor.croppingId).toBe(ids.imageB)
})
it('when only an image is selected control-pointing a selection handle should transition to select.pointing_crop_handle', () => {
// two shapes / edge
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageA, ids.imageB)
.pointerDown(500, 550, { target: 'selection', handle: 'bottom', ctrlKey: true })
.expectPathToBe('root.select.brushing')
// two shapes / corner
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageA, ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.brushing')
// one shape / edge
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.pointerDown(500, 550, { target: 'selection', handle: 'bottom', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
// one shape / corner
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
})
})
describe('When in the crop.idle state', () => {
it('pressing escape should transition to select.idle', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.cancel()
.expectPathToBe('root.select.idle')
})
it('pressing enter should transition to select.idle', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.keyDown('Enter')
.keyUp('Enter')
.expectPathToBe('root.select.idle')
})
it('pointing the canvas should return to select.idle', () => {
editor
.expectPathToBe('root.select.idle')
.click(550, 550, { target: 'canvas' })
.expectPathToBe('root.select.idle')
})
it('pointing some other shape should return to select.idle', () => {
editor
.expectPathToBe('root.select.idle')
.click(550, 550, { target: 'shape', shape: editor.getShapeById(ids.boxA) })
.expectPathToBe('root.select.idle')
})
it('pointing a selection handle should enter the select.pointing_crop_handle state', () => {
// corner
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
//reset
editor.cancel().cancel()
// edge
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
})
it('pointing the cropping image should enter the select.crop.translating_crop state', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(500, 600, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
expect(editor.croppingId).toBe(ids.imageB)
expect(editor.selectedIds).toMatchObject([ids.imageB])
})
it('clicking another image shape should set that shape as the new cropping shape and transition to pointing_crop', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(100, 100, { target: 'shape', shape: editor.getShapeById(ids.imageA) })
.expectPathToBe('root.select.crop.pointing_crop')
expect(editor.croppingId).toBe(ids.imageA)
expect(editor.selectedIds).toMatchObject([ids.imageA])
})
it('rotating will return to select.crop.idle', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'top_left_rotate' })
.expectPathToBe('root.select.pointing_rotate_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.rotating')
.pointerUp()
.expectPathToBe('root.select.crop.idle')
})
it('nudges the cropped image', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
const crop = () => editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
expect(crop()).toCloselyMatchObject({
topLeft: { x: 0, y: 0 },
bottomRight: { x: 0.5, y: 0.5 },
})
editor.keyDown('ArrowDown')
expect(crop()).toCloselyMatchObject({
topLeft: { x: 0, y: 0.00125 },
bottomRight: { x: 0.5, y: 0.50125 },
})
editor.keyRepeat('ArrowDown')
editor.keyRepeat('ArrowDown')
editor.keyRepeat('ArrowDown')
editor.keyRepeat('ArrowDown')
editor.keyUp('ArrowDown')
expect(crop()).toCloselyMatchObject({
topLeft: { x: 0, y: 0.0062 },
bottomRight: { x: 0.5, y: 0.5062 },
})
// Undoing should go back to the keydown state, all those
// repeats should be ephemeral and squashed down
editor.undo()
expect(crop()).toCloselyMatchObject({
topLeft: { x: 0, y: 0 },
bottomRight: { x: 0.5, y: 0.5 },
})
editor.redo()
expect(crop()).toCloselyMatchObject({
topLeft: { x: 0, y: 0.0062 },
bottomRight: { x: 0.5, y: 0.5062 },
})
})
})
describe('When in the select.crop.pointing_crop state', () => {
it('pressing escape / cancel returns to select.crop.idle', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.cancel()
.expectPathToBe('root.select.crop.idle')
})
it('dragging enters select.crop.translating_crop', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.pointerMove(560, 560)
.expectPathToBe('root.select.crop.translating_crop')
})
})
describe('When in the select.crop.translating_crop state', () => {
it('moving the pointer should adjust the crop', () => {
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
expect(before.topLeft.x).toBe(0)
expect(before.topLeft.y).toBe(0)
expect(before.bottomRight.x).toBe(0.5)
expect(before.bottomRight.y).toBe(0.5)
// Move the pointer to the left
editor
.pointerMove(550 - imageWidth / 4, 550 - imageHeight / 4)
.expectPathToBe('root.select.crop.translating_crop')
// Update should have run right away
const afterFirst = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
expect(afterFirst.topLeft.x).toBe(0.25)
expect(afterFirst.topLeft.y).toBe(0.25)
expect(afterFirst.bottomRight.x).toBe(0.75)
expect(afterFirst.bottomRight.y).toBe(0.75)
// and back to the start
editor.pointerMove(550, 550)
// Update should have run right away
const afterSecond = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
expect(afterSecond.topLeft.x).toBe(0)
expect(afterSecond.topLeft.y).toBe(0)
expect(afterSecond.bottomRight.x).toBe(0.5)
expect(afterSecond.bottomRight.y).toBe(0.5)
// and back to the left again (first)
editor.pointerMove(250, 250)
const afterEnd = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor.pointerUp()
editor.undo()
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
editor.redo()
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(afterEnd)
})
it('moving the pointer while holding shift should adjust the crop', () => {
editor
.doubleClick(550, 550, ids.imageB)
.pointerDown(550, 550, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.keyDown('Shift')
.pointerMove(550 - imageWidth / 8, 550 - imageHeight / 8)
.expectPathToBe('root.select.crop.translating_crop')
// Update should have run right away
const afterShiftDown = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
expect(afterShiftDown).toMatchObject({
topLeft: { x: 0.125, y: 0 },
bottomRight: { x: 0.625, y: 0.5 },
})
editor.keyUp('Shift')
jest.advanceTimersByTime(500)
const afterShiftUp = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
expect(afterShiftUp).toMatchObject({
topLeft: { x: 0.125, y: 0.125 },
bottomRight: { x: 0.625, y: 0.625 },
})
editor.keyDown('Shift')
const afterShiftDownAgain = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
expect(afterShiftDownAgain).toMatchObject(afterShiftDown)
})
it('pressing escape / cancel should bail on that change and transition to select.crop.idle', () => {
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.pointerMove(250, 250)
.expectPathToBe('root.select.crop.translating_crop')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.cancel().expectPathToBe('root.select.crop.idle')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
})
it('pressing enter / pointer up / complete should transition to select.crop.idle', () => {
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.pointerMove(250, 250)
.expectPathToBe('root.select.crop.translating_crop')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.keyDown('Enter').keyUp('Enter').expectPathToBe('root.select.crop.idle')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
})
})
describe('When in the select.pointing_crop_handle state', () => {
it('moving the pointer should transition to select.cropping', () => {
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
})
it('when entered from select.idle, pressing escape / cancel should return to idle and clear cropping idle', () => {
// coming from select.idle
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.cancel()
.expectPathToBe('root.select.idle')
expect(editor.croppingId).toBe(null)
})
it('when entered from select.crop.idle, pressing escape / cancel should return to select.crop.idle and preserve croppingId', () => {
// coming from idle
editor
.cancel()
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.cancel()
.expectPathToBe('root.select.crop.idle')
expect(editor.croppingId).toBe(ids.imageB)
})
})
describe('When in the select.cropping state', () => {
it('moving the pointer should adjust the crop', () => {
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
})
it('escape / cancel should revert the change and transition to select.idle when that is the history state', () => {
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.cancel()
.expectPathToBe('root.select.idle')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
})
it('escape / cancel should revert the change and transition to crop.idle when that is the history state', () => {
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor
.cancel()
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.cancel()
.expectPathToBe('root.select.crop.idle')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
})
it('pointer up / complete should commit the change and transition to crop.idle when that is the history state', () => {
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor
.cancel()
.expectPathToBe('root.select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.pointerUp()
.expectPathToBe('root.select.crop.idle')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.undo()
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
editor.redo()
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
})
it('pointer up / complete should commit the change and transition to select.idle when that is the history state', () => {
const before = editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!
editor
.cancel()
.expectPathToBe('root.select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.pointerUp()
.expectPathToBe('root.select.idle')
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.undo()
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
editor.redo()
expect(editor.getShapeById<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
})
})
describe('When cropping...', () => {
it('Correctly stops the crop when the crop is smaller than the minimum crop size', () => {
const imageX = 100
const imageY = 100
// Crop the image to 0x0 which is below mimimum crop size
const moveX = imageWidth
const moveY = imageHeight
const stoppingCropX = (imageWidth - MIN_CROP_SIZE) / imageWidth
const stoppingCropY = (imageHeight - MIN_CROP_SIZE) / imageHeight
editor
.select(ids.imageA)
.pointerDown(
imageX,
imageY,
{
target: 'selection',
handle: 'top_left',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + moveX, imageY + moveY)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX + (imageWidth - MIN_CROP_SIZE),
y: imageY + (imageHeight - MIN_CROP_SIZE),
props: {
...imageProps,
crop: {
topLeft: { x: stoppingCropX, y: stoppingCropY },
bottomRight: { x: 1, y: 1 },
},
w: MIN_CROP_SIZE,
h: MIN_CROP_SIZE,
},
})
})
it('Correctly resets the crop when double clicking a corner', () => {
editor
.doubleClick(550, 550, { target: 'shape', shape: editor.getShapeById(ids.imageB) })
.expectToBeIn('select.crop.idle')
.expectShapeToMatch({
id: ids.imageB,
x: 500,
y: 500,
props: {
w: imageWidth / 2,
h: imageHeight / 2,
crop: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: 0.5, y: 0.5 },
},
},
})
.doubleClick(500, 500, { target: 'selection', handle: 'top_left' })
.expectToBeIn('select.crop.idle')
.expectShapeToMatch({
id: ids.imageB,
x: 500,
y: 500,
props: {
w: imageWidth,
h: imageHeight,
crop: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: 1, y: 1 },
},
},
})
})
it('Crop the image from the top left', () => {
const imageX = 100
const imageY = 100
const cropX = 0.5
const cropY = 0.75
const moveX = imageWidth * cropX
const moveY = imageHeight * cropY
editor
.select(ids.imageA)
.pointerDown(
imageX,
imageY,
{
target: 'selection',
handle: 'top_left',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + moveX, imageY + moveY)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX + moveX,
y: imageY + moveY,
props: {
...imageProps,
crop: {
topLeft: { x: cropX, y: cropY },
bottomRight: { x: 1, y: 1 },
},
w: imageWidth - moveX,
h: imageHeight - moveY,
},
})
})
it('Crop the image from the top right', () => {
const imageX = 100
const imageY = 100
const cropX = 0.5
const cropY = 0.75
const moveX = imageWidth * cropX
const moveY = imageHeight * cropY
editor
.select(ids.imageA)
.pointerDown(
imageX + imageWidth,
imageY,
{
target: 'selection',
handle: 'top_right',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + imageWidth - moveX, imageY + moveY)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX,
y: imageY + moveY,
props: {
...imageProps,
crop: {
topLeft: { x: 0, y: cropY },
bottomRight: { x: cropX, y: 1 },
},
w: imageWidth - moveX,
h: imageHeight - moveY,
},
})
})
it('Crop the image from the bottom left', () => {
const imageX = 100
const imageY = 100
const cropX = 0.5
const cropY = 0.75
const moveX = imageWidth * cropX
const moveY = imageHeight * cropY
editor
.select(ids.imageA)
.pointerDown(
imageX,
imageY + imageHeight,
{
target: 'selection',
handle: 'bottom_left',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + moveX, imageY + imageHeight - moveY)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX + moveX,
y: imageY,
props: {
...imageProps,
crop: {
topLeft: { x: cropX, y: 0 },
bottomRight: { x: 1, y: 1 - cropY },
},
w: imageWidth - moveX,
h: imageHeight - moveY,
},
})
})
it('Crop the image from the bottom right', () => {
const imageX = 100
const imageY = 100
const cropX = 0.5
const cropY = 0.75
const moveX = imageWidth * cropX
const moveY = imageHeight * cropY
editor
.select(ids.imageA)
.pointerDown(
imageX + imageWidth,
imageY + imageHeight,
{
target: 'selection',
handle: 'bottom_right',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + imageWidth - moveX, imageY + imageHeight - moveY)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX,
y: imageY,
props: {
...imageProps,
crop: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: 1 - cropX, y: 1 - cropY },
},
w: imageWidth - moveX,
h: imageHeight - moveY,
},
})
})
it('Crop the image from the left', () => {
const imageX = 100
const imageY = 100
const cropX = 0.5
const moveX = imageWidth * cropX
editor
.select(ids.imageA)
.pointerDown(
imageX,
imageY + imageHeight / 2,
{
target: 'selection',
handle: 'left',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + moveX, imageY + imageHeight / 2)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX + moveX,
y: imageY,
props: {
...imageProps,
crop: {
topLeft: { x: cropX, y: 0 },
bottomRight: { x: 1, y: 1 },
},
w: imageWidth - moveX,
h: imageHeight,
},
})
})
it('Crop the image from the top', () => {
const imageX = 100
const imageY = 100
const cropY = 0.75
const moveY = imageHeight * cropY
editor
.select(ids.imageA)
.pointerDown(
imageX + imageWidth / 2,
imageY,
{
target: 'selection',
handle: 'top',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + imageWidth / 2, imageY + moveY)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX,
y: imageY + moveY,
props: {
...imageProps,
crop: {
topLeft: { x: 0, y: cropY },
bottomRight: { x: 1, y: 1 },
},
w: imageWidth,
h: imageHeight - moveY,
},
})
})
it('Crop the image from the right', () => {
const imageX = 100
const imageY = 100
const cropX = 0.5
const moveX = imageWidth * cropX
editor
.select(ids.imageA)
.pointerDown(
imageX + imageWidth,
imageY + imageHeight / 2,
{
target: 'selection',
handle: 'right',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(150, 150)
.expectToBeIn('select.cropping')
.pointerMove(imageX + imageWidth - moveX, imageY + imageHeight / 2)
.expectShapeToMatch({
id: ids.imageA,
x: imageX,
y: imageY,
props: {
...imageProps,
crop: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: 1 - cropX, y: 1 },
},
w: imageWidth - moveX,
h: imageHeight,
},
})
})
it('Crop the image from the bottom', () => {
const imageX = 100
const imageY = 100
const cropY = 0.75
const moveY = imageHeight * cropY
editor
.select(ids.imageA)
.pointerDown(
imageX + imageWidth / 2,
imageY + imageHeight,
{
target: 'selection',
handle: 'bottom',
},
{ ctrlKey: true }
)
.expectToBeIn('select.pointing_crop_handle')
.expectShapeToMatch({ id: ids.imageA, x: imageX, y: imageY, props: imageProps })
.pointerMove(imageX + imageWidth / 2, imageY + imageHeight - moveY)
.expectToBeIn('select.cropping')
.expectShapeToMatch({
id: ids.imageA,
x: imageX,
y: imageY,
props: {
...imageProps,
crop: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: 1, y: 1 - cropY },
},
w: imageWidth,
h: imageHeight - moveY,
},
})
})
})