| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- /* eslint-disable @typescript-eslint/ban-ts-comment */
- import { TLDrawState } from '~state'
- import { mockDocument } from '~test'
- import { GroupShape, TLDrawShape, TLDrawShapeType } from '~types'
-
- describe('Group command', () => {
- const tlstate = new TLDrawState()
-
- beforeEach(() => {
- tlstate.loadDocument(mockDocument)
- })
-
- it('does, undoes and redoes command', () => {
- tlstate.group(['rect1', 'rect2'], 'newGroup')
-
- expect(tlstate.getShape<GroupShape>('newGroup')).toBeTruthy()
-
- tlstate.undo()
-
- expect(tlstate.getShape<GroupShape>('newGroup')).toBeUndefined()
-
- tlstate.redo()
-
- expect(tlstate.getShape<GroupShape>('newGroup')).toBeTruthy()
- })
-
- describe('when less than two shapes are selected', () => {
- it('does nothing', () => {
- tlstate.deselectAll()
-
- // @ts-ignore
- const stackLength = tlstate.stack.length
-
- tlstate.group([], 'newGroup')
- expect(tlstate.getShape<GroupShape>('newGroup')).toBeUndefined()
- // @ts-ignore
- expect(tlstate.stack.length).toBe(stackLength)
-
- tlstate.group(['rect1'], 'newGroup')
- expect(tlstate.getShape<GroupShape>('newGroup')).toBeUndefined()
- // @ts-ignore
- expect(tlstate.stack.length).toBe(stackLength)
- })
- })
-
- describe('when grouping shapes on the page', () => {
- /*
- When the parent is a page, the group is created as a child of the page
- and the shapes are reparented to the group. The group's child
- index should be the minimum child index of the selected shapes.
- */
-
- it('creates a group with the correct props', () => {
- tlstate.updateShapes(
- {
- id: 'rect1',
- point: [300, 300],
- childIndex: 3,
- },
- {
- id: 'rect2',
- point: [20, 20],
- childIndex: 4,
- }
- )
-
- tlstate.group(['rect1', 'rect2'], 'newGroup')
- const group = tlstate.getShape<GroupShape>('newGroup')
- expect(group).toBeTruthy()
- expect(group.parentId).toBe('page1')
- expect(group.childIndex).toBe(3)
- expect(group.point).toStrictEqual([20, 20])
- expect(group.children).toStrictEqual(['rect1', 'rect2'])
- })
-
- it('reparents the grouped shapes', () => {
- tlstate.updateShapes(
- {
- id: 'rect1',
- childIndex: 2.5,
- },
- {
- id: 'rect2',
- childIndex: 4.7,
- }
- )
-
- tlstate.group(['rect1', 'rect2'], 'newGroup')
-
- let rect1: TLDrawShape
- let rect2: TLDrawShape
-
- rect1 = tlstate.getShape('rect1')
- rect2 = tlstate.getShape('rect2')
- // Reparents the shapes
- expect(rect1.parentId).toBe('newGroup')
- expect(rect2.parentId).toBe('newGroup')
- // Sets and preserves the order of the grouped shapes
- expect(rect1.childIndex).toBe(1)
- expect(rect2.childIndex).toBe(2)
-
- tlstate.undo()
-
- rect1 = tlstate.getShape('rect1')
- rect2 = tlstate.getShape('rect2')
- // Restores the shapes' parentIds
- expect(rect1.parentId).toBe('page1')
- expect(rect2.parentId).toBe('page1')
- // Restores the shapes' childIndexs
- expect(rect1.childIndex).toBe(2.5)
- expect(rect2.childIndex).toBe(4.7)
- })
- })
-
- describe('when grouping shapes that already belong to a group', () => {
- /*
- Do not allow groups to nest. All groups should be children of
- the page: a group should never be the child of a different group.
- This is a UX decision as much as a technical one.
- */
-
- it('creates a new group on the page', () => {
- /*
- When the selected shapes are the children of another group, and so
- long as the children do not represent ALL of the group's children,
- then a new group should be created from the selected shapes and the
- original group be updated to only contain the remaining ones.
- */
-
- tlstate.resetDocument().createShapes(
- {
- id: 'rect1',
- type: TLDrawShapeType.Rectangle,
- childIndex: 1,
- },
- {
- id: 'rect2',
- type: TLDrawShapeType.Rectangle,
- childIndex: 2,
- },
- {
- id: 'rect3',
- type: TLDrawShapeType.Rectangle,
- childIndex: 3,
- },
- {
- id: 'rect4',
- type: TLDrawShapeType.Rectangle,
- childIndex: 4,
- }
- )
-
- tlstate.group(['rect1', 'rect2', 'rect3', 'rect4'], 'newGroupA')
-
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeTruthy()
- expect(tlstate.getShape('rect1').childIndex).toBe(1)
- expect(tlstate.getShape('rect2').childIndex).toBe(2)
- expect(tlstate.getShape('rect3').childIndex).toBe(3)
- expect(tlstate.getShape('rect4').childIndex).toBe(4)
- expect(tlstate.getShape<GroupShape>('newGroupA').children).toStrictEqual([
- 'rect1',
- 'rect2',
- 'rect3',
- 'rect4',
- ])
-
- tlstate.group(['rect1', 'rect3'], 'newGroupB')
-
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeTruthy()
- expect(tlstate.getShape('rect2').childIndex).toBe(2)
- expect(tlstate.getShape('rect4').childIndex).toBe(4)
- expect(tlstate.getShape<GroupShape>('newGroupA').children).toStrictEqual(['rect2', 'rect4'])
-
- expect(tlstate.getShape<GroupShape>('newGroupB')).toBeTruthy()
- expect(tlstate.getShape('rect1').childIndex).toBe(1)
- expect(tlstate.getShape('rect3').childIndex).toBe(2)
- expect(tlstate.getShape<GroupShape>('newGroupB').children).toStrictEqual(['rect1', 'rect3'])
-
- tlstate.undo()
-
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeTruthy()
- expect(tlstate.getShape('rect1').childIndex).toBe(1)
- expect(tlstate.getShape('rect2').childIndex).toBe(2)
- expect(tlstate.getShape('rect3').childIndex).toBe(3)
- expect(tlstate.getShape('rect4').childIndex).toBe(4)
- expect(tlstate.getShape<GroupShape>('newGroupA').children).toStrictEqual([
- 'rect1',
- 'rect2',
- 'rect3',
- 'rect4',
- ])
-
- expect(tlstate.getShape<GroupShape>('newGroupB')).toBeUndefined()
-
- tlstate.redo()
-
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeTruthy()
- expect(tlstate.getShape('rect2').childIndex).toBe(2)
- expect(tlstate.getShape('rect4').childIndex).toBe(4)
- expect(tlstate.getShape<GroupShape>('newGroupA').children).toStrictEqual(['rect2', 'rect4'])
-
- expect(tlstate.getShape<GroupShape>('newGroupB')).toBeTruthy()
- expect(tlstate.getShape('rect1').childIndex).toBe(1)
- expect(tlstate.getShape('rect3').childIndex).toBe(2)
- expect(tlstate.getShape<GroupShape>('newGroupB').children).toStrictEqual(['rect1', 'rect3'])
- })
-
- it('does nothing if all shapes in the group are selected', () => {
- /*
- If the selected shapes represent ALL of the children of the a
- group, then no effect should occur.
- */
- tlstate.resetDocument().createShapes(
- {
- id: 'rect1',
- type: TLDrawShapeType.Rectangle,
- childIndex: 1,
- },
- {
- id: 'rect2',
- type: TLDrawShapeType.Rectangle,
- childIndex: 2,
- },
- {
- id: 'rect3',
- type: TLDrawShapeType.Rectangle,
- childIndex: 3,
- }
- )
-
- tlstate.group(['rect1', 'rect2', 'rect3'], 'newGroupA')
- tlstate.group(['rect1', 'rect2', 'rect3'], 'newGroupB')
- expect(tlstate.getShape<GroupShape>('newGroupB')).toBeUndefined()
- })
-
- it('deletes any groups that no longer have children', () => {
- /*
- If the selected groups included the children of another group
- in addition to other shapes then that group should be destroyed.
- Other rules around deleted shapes should here apply: bindings
- connected to the group should be deleted, etc.
- */
- tlstate.resetDocument().createShapes(
- {
- id: 'rect1',
- type: TLDrawShapeType.Rectangle,
- childIndex: 1,
- },
- {
- id: 'rect2',
- type: TLDrawShapeType.Rectangle,
- childIndex: 2,
- },
- {
- id: 'rect3',
- type: TLDrawShapeType.Rectangle,
- childIndex: 3,
- }
- )
-
- tlstate.group(['rect1', 'rect2'], 'newGroupA')
- tlstate.group(['rect1', 'rect2', 'rect3'], 'newGroupB')
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeUndefined()
- expect(tlstate.getShape<GroupShape>('newGroupB').children).toStrictEqual([
- 'rect1',
- 'rect2',
- 'rect3',
- ])
- })
-
- it('merges selected groups that no longer have children', () => {
- /*
- If the user is creating a group while having selected other
- groups, then the selected groups should be destroyed and a new
- group created with the selected shapes and the group(s)' children.
- */
- tlstate.resetDocument().createShapes(
- {
- id: 'rect1',
- type: TLDrawShapeType.Rectangle,
- childIndex: 1,
- },
- {
- id: 'rect2',
- type: TLDrawShapeType.Rectangle,
- childIndex: 2,
- },
- {
- id: 'rect3',
- type: TLDrawShapeType.Rectangle,
- childIndex: 3,
- }
- )
-
- tlstate.group(['rect1', 'rect2'], 'newGroupA')
- tlstate.group(['newGroupA', 'rect3'], 'newGroupB')
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeUndefined()
- expect(tlstate.getShape<GroupShape>('newGroupB').children).toStrictEqual([
- 'rect1',
- 'rect2',
- 'rect3',
- ])
-
- tlstate.undo()
-
- expect(tlstate.getShape<GroupShape>('newGroupB')).toBeUndefined()
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeDefined()
- expect(tlstate.getShape<GroupShape>('newGroupA').children).toStrictEqual(['rect1', 'rect2'])
-
- tlstate.redo()
-
- expect(tlstate.getShape<GroupShape>('newGroupA')).toBeUndefined()
- expect(tlstate.getShape<GroupShape>('newGroupB')).toBeDefined()
- expect(tlstate.getShape<GroupShape>('newGroupB').children).toStrictEqual([
- 'rect1',
- 'rect2',
- 'rect3',
- ])
- })
-
- it('Ungroups if the only shape selected is a group', () => {
- tlstate.resetDocument().createShapes(
- {
- id: 'rect1',
- type: TLDrawShapeType.Rectangle,
- childIndex: 1,
- },
- {
- id: 'rect2',
- type: TLDrawShapeType.Rectangle,
- childIndex: 2,
- },
- {
- id: 'rect3',
- type: TLDrawShapeType.Rectangle,
- childIndex: 3,
- }
- )
-
- expect(tlstate.shapes.length).toBe(3)
-
- tlstate.selectAll().group()
-
- expect(tlstate.shapes.length).toBe(4)
-
- tlstate.selectAll().group()
-
- expect(tlstate.shapes.length).toBe(3)
- })
-
- /*
- The layers should be in the same order as the original layers as
- they would have appeared on a layers tree (lowest child index
- first, parent inclusive).
- */
-
- it.todo('preserves the child index order')
-
- /* --------------------- Nesting -------------------- */
-
- // it.todo('creates the new group as a child of the parent group')
- /*
- The new group should be a child of the parent group.
- */
-
- // it.todo('moves the selected layers to the new group')
- /*
- The new group should have the selected children. The old parents
- should no longer have the selected shapes among their children.
- All of the selected shapes should be assigned the new parent.
- */
- })
-
- // describe('when grouping shapes with different parents', () => {
- /*
- When two shapes with different parents are grouped, the new parent
- group should have the same parent as the shape nearest to the top
- of the layer tree. The new group's child index should be that
- shape's child index.
-
- For example, if the shapes are grouped in the following order:
-
- - page1
- - group1
- - arrow1
- - rect1 (x)
- - arrow2
- - rect2 (x)
-
- The new parent group should have the same parent as rect1.
-
- - page1
- - group1
- - arrow1
- - group2
- - rect1 (x)
- - rect2 (x)
- - arrow2
-
- If, instead, the shapes are grouped in the following order:
-
- - page1
- - arrow1
- - rect1 (x)
- - group1
- - arrow2
- - rect2 (x)
-
- Then the new parent group should have the same parent as
- rect2.
-
- - page1
- - arrow1
- - group2 (x)
- - rect1
- - rect2
- - group1
- - arrow2
-
- We can find this by searching the tree for the nearest shape to
- the top.
- */
-
- // it.todo('creates a group in the correct place')
- /*
- The new group should be a child of the nearest shape to the top
- of the tree.
- */
-
- /*
- If the selected groups included the children of another group, then
- that group should be destroyed. Other rules around deleted
- shapes should here apply: bindings connected to the group
- should be deleted, etc.
- */
-
- // it.todo('deletes any groups that no longer have children')
-
- // })
- })
|