kopia lustrzana https://github.com/Tldraw/Tldraw
258 wiersze
6.8 KiB
TypeScript
258 wiersze
6.8 KiB
TypeScript
import { createShapeId, TLArrowShape, TLShapePartial } from '@tldraw/editor'
|
|
import { TestEditor } from './TestEditor'
|
|
|
|
let editor: TestEditor
|
|
|
|
const ids = {
|
|
box1: createShapeId('box1'),
|
|
box2: createShapeId('box2'),
|
|
box3: createShapeId('box3'),
|
|
box4: createShapeId('box4'),
|
|
arrow1: createShapeId('arrow1'),
|
|
}
|
|
|
|
beforeEach(() => {
|
|
editor = new TestEditor()
|
|
|
|
editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
|
|
})
|
|
it('creates new bindings for arrows when pasting', async () => {
|
|
editor
|
|
.selectAll()
|
|
.deleteShapes(editor.getSelectedShapeIds())
|
|
.createShapes([
|
|
{ id: ids.box1, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } },
|
|
{ id: ids.box2, type: 'geo', x: 300, y: 300, props: { w: 100, h: 100 } },
|
|
{
|
|
id: ids.arrow1,
|
|
type: 'arrow',
|
|
x: 150,
|
|
y: 150,
|
|
props: {
|
|
start: {
|
|
type: 'binding',
|
|
boundShapeId: ids.box1,
|
|
isExact: false,
|
|
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
isPrecise: false,
|
|
},
|
|
end: {
|
|
type: 'binding',
|
|
boundShapeId: ids.box2,
|
|
isExact: false,
|
|
normalizedAnchor: { x: 0.5, y: 0.5 },
|
|
isPrecise: false,
|
|
},
|
|
},
|
|
},
|
|
])
|
|
|
|
const shapesBefore = editor.getCurrentPageShapes()
|
|
|
|
editor.selectAll().duplicateShapes(editor.getSelectedShapeIds())
|
|
|
|
const shapesAfter = editor.getCurrentPageShapes()
|
|
|
|
// We should not have changed the original shapes
|
|
expect(shapesBefore[0]).toMatchObject(shapesAfter[0])
|
|
expect(shapesBefore[1]).toMatchObject(shapesAfter[1])
|
|
expect(shapesBefore[2]).toMatchObject(shapesAfter[2])
|
|
|
|
const box1a = shapesAfter[0]
|
|
const box2a = shapesAfter[1]
|
|
const arrow1a = shapesAfter[2] as TLArrowShape
|
|
|
|
const box1b = shapesAfter[3]
|
|
const box2b = shapesAfter[4]
|
|
const arrow1b = shapesAfter[5]
|
|
|
|
// The new shapes should match the old shapes, except for their id and the arrow's bindings!
|
|
expect(shapesAfter.length).toBe(shapesBefore.length * 2)
|
|
expect(box1b).toMatchObject({ ...box1a, id: box1b.id, index: 'a1V' })
|
|
expect(box2b).toMatchObject({ ...box2a, id: box2b.id, index: 'a2V' })
|
|
expect(arrow1b).toMatchObject({
|
|
id: arrow1b.id,
|
|
index: 'a4',
|
|
props: {
|
|
...arrow1a.props,
|
|
start: { ...arrow1a.props.start, boundShapeId: box1b.id },
|
|
end: { ...arrow1a.props.end, boundShapeId: box2b.id },
|
|
},
|
|
})
|
|
})
|
|
|
|
// blood moat incoming
|
|
describe('When duplicating shapes that include arrows', () => {
|
|
let shapes: TLShapePartial[]
|
|
|
|
beforeEach(() => {
|
|
const box1 = createShapeId()
|
|
const box2 = createShapeId()
|
|
const box3 = createShapeId()
|
|
|
|
shapes = [
|
|
{
|
|
id: box1,
|
|
type: 'geo',
|
|
x: 0,
|
|
y: 0,
|
|
},
|
|
{
|
|
id: box2,
|
|
type: 'geo',
|
|
x: 300,
|
|
y: 300,
|
|
},
|
|
{
|
|
id: box3,
|
|
type: 'geo',
|
|
x: 300,
|
|
y: 0,
|
|
},
|
|
{
|
|
id: createShapeId(),
|
|
type: 'arrow',
|
|
x: 50,
|
|
y: 50,
|
|
props: {
|
|
bend: 200,
|
|
start: {
|
|
type: 'binding',
|
|
normalizedAnchor: { x: 0.75, y: 0.75 },
|
|
boundShapeId: box1,
|
|
isExact: false,
|
|
isPrecise: true,
|
|
},
|
|
end: {
|
|
type: 'binding',
|
|
normalizedAnchor: { x: 0.25, y: 0.25 },
|
|
boundShapeId: box1,
|
|
isExact: false,
|
|
isPrecise: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: createShapeId(),
|
|
type: 'arrow',
|
|
x: 50,
|
|
y: 50,
|
|
props: {
|
|
bend: -200,
|
|
start: {
|
|
type: 'binding',
|
|
normalizedAnchor: { x: 0.75, y: 0.75 },
|
|
boundShapeId: box1,
|
|
isExact: false,
|
|
isPrecise: true,
|
|
},
|
|
end: {
|
|
type: 'binding',
|
|
normalizedAnchor: { x: 0.25, y: 0.25 },
|
|
boundShapeId: box1,
|
|
isExact: false,
|
|
isPrecise: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
id: createShapeId(),
|
|
type: 'arrow',
|
|
x: 50,
|
|
y: 50,
|
|
props: {
|
|
bend: -200,
|
|
start: {
|
|
type: 'binding',
|
|
normalizedAnchor: { x: 0.75, y: 0.75 },
|
|
boundShapeId: box1,
|
|
isExact: false,
|
|
isPrecise: true,
|
|
},
|
|
end: {
|
|
type: 'binding',
|
|
normalizedAnchor: { x: 0.25, y: 0.25 },
|
|
boundShapeId: box3,
|
|
isExact: false,
|
|
isPrecise: true,
|
|
},
|
|
},
|
|
},
|
|
]
|
|
})
|
|
|
|
it('Preserves the same selection bounds', () => {
|
|
editor.selectAll().deleteShapes(editor.getSelectedShapeIds()).createShapes(shapes)
|
|
editor.selectAll()
|
|
|
|
const boundsBefore = editor.getSelectionRotatedPageBounds()!
|
|
editor.duplicateShapes(editor.getSelectedShapeIds())
|
|
expect(editor.getSelectionRotatedPageBounds()).toCloselyMatchObject(boundsBefore)
|
|
})
|
|
|
|
it('Preserves the same selection bounds when only duplicating the arrows', () => {
|
|
editor.selectAll().deleteShapes(editor.getSelectedShapeIds()).createShapes(shapes)
|
|
editor.select(
|
|
...editor
|
|
.getCurrentPageShapes()
|
|
.filter((s) => editor.isShapeOfType<TLArrowShape>(s, 'arrow'))
|
|
.map((s) => s.id)
|
|
)
|
|
|
|
const boundsBefore = editor.getSelectionRotatedPageBounds()!
|
|
editor.duplicateShapes(editor.getSelectedShapeIds())
|
|
const boundsAfter = editor.getSelectionRotatedPageBounds()!
|
|
|
|
// It's not exactly exact, but close enough is plenty close
|
|
expect(Math.abs(boundsAfter.x - boundsBefore.x)).toBeLessThan(1)
|
|
expect(Math.abs(boundsAfter.y - boundsBefore.y)).toBeLessThan(1)
|
|
expect(Math.abs(boundsAfter.w - boundsBefore.w)).toBeLessThan(1)
|
|
expect(Math.abs(boundsAfter.h - boundsBefore.h)).toBeLessThan(1)
|
|
|
|
// If you're feeling up to it:
|
|
// expect(editor.selectionRotatedBounds).toCloselyMatchObject(boundsBefore)
|
|
})
|
|
})
|
|
|
|
describe('When duplicating shapes after cloning', () => {
|
|
beforeEach(() => {
|
|
editor
|
|
.selectAll()
|
|
.deleteShapes(editor.getSelectedShapeIds())
|
|
.createShape({ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } })
|
|
})
|
|
it('preserves the cloning properties (offset and shapes)', () => {
|
|
// Clone the shape by alt dragging it to a new location
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(1)
|
|
|
|
editor.keyDown('Alt')
|
|
editor.select(ids.box1).pointerDown(50, 50, ids.box1).pointerMove(30, 40).pointerUp(30, 40) // [-20, -10]
|
|
editor.keyUp('Alt')
|
|
const shape = editor.getSelectedShapes()[0]
|
|
expect(editor.getCurrentPageShapeIds().size).toBe(2)
|
|
expect(shape.id).not.toBe(ids.box1)
|
|
expect(shape.x).toBe(-20)
|
|
expect(shape.y).toBe(-10)
|
|
|
|
// Make sure the duplicate props are set
|
|
let instance = editor.getInstanceState()
|
|
let duplicateProps = instance?.duplicateProps
|
|
if (!duplicateProps) throw new Error('duplicateProps should be set')
|
|
expect(duplicateProps.shapeIds).toEqual([shape.id])
|
|
expect(duplicateProps.offset).toEqual({ x: -20, y: -10 })
|
|
|
|
// Make sure duplication with these props works (we can't invoke the duplicate action directly since it's a hook)
|
|
editor.duplicateShapes(duplicateProps.shapeIds, duplicateProps.offset)
|
|
const newShapes = editor.getSelectedShapes()
|
|
expect(newShapes.length).toBe(1)
|
|
expect(newShapes[0].x).toBe(-40)
|
|
expect(newShapes[0].y).toBe(-20)
|
|
|
|
// Make sure the duplicate props are cleared when we select a different shape
|
|
editor.select(ids.box1)
|
|
instance = editor.getInstanceState()
|
|
duplicateProps = instance?.duplicateProps
|
|
expect(duplicateProps).toBe(null)
|
|
})
|
|
})
|