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:
Steve Ruiz 2021-07-10 15:03:30 +01:00 committed by GitHub
commit 8b1072c3c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 279 additions and 20 deletions

View 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)
})
})
})
})

View file

@ -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
} }

View file

@ -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)
} })
}, },
}) })
) )