Merge pull request #35 from tldraw/21-toggle-command-tests
Adds test for the toggle command, renames files to include test
This commit is contained in:
commit
8b1072c3c3
12 changed files with 279 additions and 20 deletions
237
__tests__/commands/toggle.test.ts
Normal file
237
__tests__/commands/toggle.test.ts
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
import { ShapeType } from 'types'
|
||||||
|
import TestState from '../test-utils'
|
||||||
|
|
||||||
|
describe('toggle command', () => {
|
||||||
|
const tt = new TestState()
|
||||||
|
tt.resetDocumentState()
|
||||||
|
.createShape(
|
||||||
|
{
|
||||||
|
type: ShapeType.Rectangle,
|
||||||
|
point: [0, 0],
|
||||||
|
size: [100, 100],
|
||||||
|
childIndex: 1,
|
||||||
|
isLocked: false,
|
||||||
|
isHidden: false,
|
||||||
|
isAspectRatioLocked: false,
|
||||||
|
},
|
||||||
|
'rect1'
|
||||||
|
)
|
||||||
|
.createShape(
|
||||||
|
{
|
||||||
|
type: ShapeType.Rectangle,
|
||||||
|
point: [400, 0],
|
||||||
|
size: [100, 100],
|
||||||
|
childIndex: 2,
|
||||||
|
isHidden: false,
|
||||||
|
isLocked: false,
|
||||||
|
isAspectRatioLocked: false,
|
||||||
|
},
|
||||||
|
'rect2'
|
||||||
|
)
|
||||||
|
.createShape(
|
||||||
|
{
|
||||||
|
type: ShapeType.Rectangle,
|
||||||
|
point: [800, 0],
|
||||||
|
size: [100, 100],
|
||||||
|
childIndex: 3,
|
||||||
|
isHidden: true,
|
||||||
|
isLocked: true,
|
||||||
|
isAspectRatioLocked: true,
|
||||||
|
},
|
||||||
|
'rect3'
|
||||||
|
)
|
||||||
|
.createShape(
|
||||||
|
{
|
||||||
|
type: ShapeType.Rectangle,
|
||||||
|
point: [1000, 0],
|
||||||
|
size: [100, 100],
|
||||||
|
childIndex: 4,
|
||||||
|
isHidden: true,
|
||||||
|
isLocked: true,
|
||||||
|
isAspectRatioLocked: true,
|
||||||
|
},
|
||||||
|
'rect4'
|
||||||
|
)
|
||||||
|
.save()
|
||||||
|
|
||||||
|
describe('toggles properties on single shape', () => {
|
||||||
|
it('does command', () => {
|
||||||
|
tt.restore().clickShape('rect1')
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(true)
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('undoes and redoes command', () => {
|
||||||
|
// Restore the saved data state, with the initial selection
|
||||||
|
tt.restore().clickShape('rect1')
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
tt.send('UNDO')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(false)
|
||||||
|
|
||||||
|
tt.send('REDO')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('toggles properties on shapes with matching properties, starting from false', () => {
|
||||||
|
it('does command', () => {
|
||||||
|
// Restore the saved data state, with the initial selection
|
||||||
|
tt.restore().clickShape('rect1').clickShape('rect2', { shiftKey: true })
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(true)
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(false)
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('undoes and redoes command', () => {
|
||||||
|
// Restore the saved data state, with the initial selection
|
||||||
|
tt.restore().clickShape('rect1').clickShape('rect2', { shiftKey: true })
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK').undo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(false)
|
||||||
|
|
||||||
|
tt.redo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('toggles properties on shapes with matching properties, starting from true', () => {
|
||||||
|
it('does command', () => {
|
||||||
|
// Restore the saved data state, with the initial selection
|
||||||
|
tt.restore().clickShape('rect3').clickShape('rect4', { shiftKey: true })
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect4').isLocked).toBe(false)
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect4').isLocked).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('undoes and redoes command', () => {
|
||||||
|
// Restore the saved data state, with the initial selection
|
||||||
|
tt.restore().clickShape('rect3').clickShape('rect4', { shiftKey: true })
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK').undo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect4').isLocked).toBe(true)
|
||||||
|
|
||||||
|
tt.redo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect4').isLocked).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('toggles properties on shapes with different properties', () => {
|
||||||
|
it('does command', () => {
|
||||||
|
// Restore the saved data state, with the initial selection
|
||||||
|
tt.restore()
|
||||||
|
.clickShape('rect1')
|
||||||
|
.clickShape('rect2', { shiftKey: true })
|
||||||
|
.clickShape('rect3', { shiftKey: true })
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(true)
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK')
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('undoes and redoes command', () => {
|
||||||
|
tt.restore()
|
||||||
|
.clickShape('rect1')
|
||||||
|
.clickShape('rect2', { shiftKey: true })
|
||||||
|
.clickShape('rect3', { shiftKey: true })
|
||||||
|
|
||||||
|
tt.send('TOGGLED_SHAPE_LOCK').undo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(false)
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(true)
|
||||||
|
|
||||||
|
tt.redo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect2').isLocked).toBe(true)
|
||||||
|
expect(tt.getShape('rect3').isLocked).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('catch all for other toggle props', () => {
|
||||||
|
const eventPropertyPairs = {
|
||||||
|
TOGGLED_SHAPE_LOCK: 'isLocked',
|
||||||
|
TOGGLED_SHAPE_HIDE: 'isHidden',
|
||||||
|
TOGGLED_SHAPE_ASPECT_LOCK: 'isAspectRatioLocked',
|
||||||
|
}
|
||||||
|
|
||||||
|
it('toggles all event property pairs', () => {
|
||||||
|
Object.entries(eventPropertyPairs).forEach(([eventName, propName]) => {
|
||||||
|
// Restore the saved data state, with the initial selection
|
||||||
|
tt.restore()
|
||||||
|
.clickShape('rect1')
|
||||||
|
.clickShape('rect2', { shiftKey: true })
|
||||||
|
.clickShape('rect3', { shiftKey: true })
|
||||||
|
|
||||||
|
tt.send(eventName)
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1')[propName]).toBe(true)
|
||||||
|
expect(tt.getShape('rect2')[propName]).toBe(true)
|
||||||
|
expect(tt.getShape('rect3')[propName]).toBe(true)
|
||||||
|
|
||||||
|
tt.undo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1')[propName]).toBe(false)
|
||||||
|
expect(tt.getShape('rect2')[propName]).toBe(false)
|
||||||
|
expect(tt.getShape('rect3')[propName]).toBe(true)
|
||||||
|
|
||||||
|
tt.redo()
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1')[propName]).toBe(true)
|
||||||
|
expect(tt.getShape('rect2')[propName]).toBe(true)
|
||||||
|
expect(tt.getShape('rect3')[propName]).toBe(true)
|
||||||
|
|
||||||
|
tt.send(eventName)
|
||||||
|
|
||||||
|
expect(tt.getShape('rect1')[propName]).toBe(false)
|
||||||
|
expect(tt.getShape('rect2')[propName]).toBe(false)
|
||||||
|
expect(tt.getShape('rect3')[propName]).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -228,15 +228,18 @@ class TestState {
|
||||||
*```
|
*```
|
||||||
*/
|
*/
|
||||||
clickShape(id: string, options: PointerOptions = {}): TestState {
|
clickShape(id: string, options: PointerOptions = {}): TestState {
|
||||||
this.state.send(
|
const shape = tld.getShape(this.data, id)
|
||||||
'POINTED_SHAPE',
|
const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
|
||||||
inputs.pointerDown(TestState.point(options), id)
|
|
||||||
)
|
|
||||||
|
|
||||||
this.state.send(
|
this.state
|
||||||
'STOPPED_POINTING',
|
.send(
|
||||||
inputs.pointerUp(TestState.point(options), id)
|
'POINTED_SHAPE',
|
||||||
)
|
inputs.pointerDown(TestState.point({ x, y, ...options }), id)
|
||||||
|
)
|
||||||
|
.send(
|
||||||
|
'STOPPED_POINTING',
|
||||||
|
inputs.pointerUp(TestState.point({ x, y, ...options }), id)
|
||||||
|
)
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -251,9 +254,12 @@ class TestState {
|
||||||
*```
|
*```
|
||||||
*/
|
*/
|
||||||
startClick(id: string, options: PointerOptions = {}): TestState {
|
startClick(id: string, options: PointerOptions = {}): TestState {
|
||||||
|
const shape = tld.getShape(this.data, id)
|
||||||
|
const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
|
||||||
|
|
||||||
this.state.send(
|
this.state.send(
|
||||||
'POINTED_SHAPE',
|
'POINTED_SHAPE',
|
||||||
inputs.pointerDown(TestState.point(options), id)
|
inputs.pointerDown(TestState.point({ x, y, ...options }), id)
|
||||||
)
|
)
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
@ -269,9 +275,12 @@ class TestState {
|
||||||
*```
|
*```
|
||||||
*/
|
*/
|
||||||
stopClick(id: string, options: PointerOptions = {}): TestState {
|
stopClick(id: string, options: PointerOptions = {}): TestState {
|
||||||
|
const shape = tld.getShape(this.data, id)
|
||||||
|
const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
|
||||||
|
|
||||||
this.state.send(
|
this.state.send(
|
||||||
'STOPPED_POINTING',
|
'STOPPED_POINTING',
|
||||||
inputs.pointerUp(TestState.point(options), id)
|
inputs.pointerUp(TestState.point({ x, y, ...options }), id)
|
||||||
)
|
)
|
||||||
|
|
||||||
return this
|
return this
|
||||||
|
@ -287,12 +296,18 @@ class TestState {
|
||||||
*```
|
*```
|
||||||
*/
|
*/
|
||||||
doubleClickShape(id: string, options: PointerOptions = {}): TestState {
|
doubleClickShape(id: string, options: PointerOptions = {}): TestState {
|
||||||
|
const shape = tld.getShape(this.data, id)
|
||||||
|
const [x, y] = shape ? vec.add(shape.point, [1, 1]) : [0, 0]
|
||||||
|
|
||||||
this.state
|
this.state
|
||||||
.send(
|
.send(
|
||||||
'DOUBLE_POINTED_SHAPE',
|
'DOUBLE_POINTED_SHAPE',
|
||||||
inputs.pointerDown(TestState.point(options), id)
|
inputs.pointerDown(TestState.point({ x, y, ...options }), id)
|
||||||
|
)
|
||||||
|
.send(
|
||||||
|
'STOPPED_POINTING',
|
||||||
|
inputs.pointerUp(TestState.point({ x, y, ...options }), id)
|
||||||
)
|
)
|
||||||
.send('STOPPED_POINTING', inputs.pointerUp(TestState.point(options), id))
|
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,22 @@ import tld from 'utils/tld'
|
||||||
import { getShapeUtils } from 'state/shape-utils'
|
import { getShapeUtils } from 'state/shape-utils'
|
||||||
import { PropsOfType } from 'types'
|
import { PropsOfType } from 'types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The toggle command toggles a boolean property on all selected shapes.
|
||||||
|
* If the value is true for all selected shapes, then the property is
|
||||||
|
* set to false; the value is false for one or more of the selected
|
||||||
|
* shapes, then the value for all shapes is set to true.
|
||||||
|
*/
|
||||||
export default function toggleCommand(
|
export default function toggleCommand(
|
||||||
data: Data,
|
data: Data,
|
||||||
prop: PropsOfType<Shape>
|
prop: PropsOfType<Shape>
|
||||||
): void {
|
): void {
|
||||||
const selectedShapes = tld.getSelectedShapes(data)
|
const selectedShapes = tld.getSelectedShapes(data)
|
||||||
const isAllToggled = selectedShapes.every((shape) => shape[prop])
|
const isAllToggled = selectedShapes.every((shape) => shape[prop])
|
||||||
const initialShapes = Object.fromEntries(
|
const initialShapes = selectedShapes.map((shape) => ({
|
||||||
selectedShapes.map((shape) => [shape.id, shape[prop]])
|
id: shape.id,
|
||||||
)
|
value: shape[prop],
|
||||||
|
}))
|
||||||
|
|
||||||
history.execute(
|
history.execute(
|
||||||
data,
|
data,
|
||||||
|
@ -23,22 +30,22 @@ export default function toggleCommand(
|
||||||
do(data) {
|
do(data) {
|
||||||
const { shapes } = tld.getPage(data)
|
const { shapes } = tld.getPage(data)
|
||||||
|
|
||||||
for (const id in initialShapes) {
|
initialShapes.forEach(({ id }) => {
|
||||||
const shape = shapes[id]
|
const shape = shapes[id]
|
||||||
getShapeUtils(shape).setProperty(
|
getShapeUtils(shape).setProperty(
|
||||||
shape,
|
shape,
|
||||||
prop,
|
prop,
|
||||||
isAllToggled ? false : true
|
isAllToggled ? false : true
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
undo(data) {
|
undo(data) {
|
||||||
const { shapes } = tld.getPage(data)
|
const { shapes } = tld.getPage(data)
|
||||||
|
|
||||||
for (const id in initialShapes) {
|
initialShapes.forEach(({ id, value }) => {
|
||||||
const shape = shapes[id]
|
const shape = shapes[id]
|
||||||
getShapeUtils(shape).setProperty(shape, prop, initialShapes[id])
|
getShapeUtils(shape).setProperty(shape, prop, value)
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue