You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

duplicateShapes.spec.ts 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { Utils } from '@tldraw/core'
  3. import { TLDrawState } from '~state'
  4. import { TLDR } from '~state/TLDR'
  5. import { mockDocument } from '~test'
  6. import { ArrowShape, SessionType, TLDrawShapeType } from '~types'
  7. describe('Duplicate command', () => {
  8. const tlstate = new TLDrawState()
  9. beforeEach(() => {
  10. tlstate.loadDocument(mockDocument)
  11. })
  12. describe('when no shape is selected', () => {
  13. it('does nothing', () => {
  14. const initialState = tlstate.state
  15. tlstate.duplicate()
  16. const currentState = tlstate.state
  17. expect(currentState).toEqual(initialState)
  18. })
  19. })
  20. it('does, undoes and redoes command', () => {
  21. tlstate.select('rect1')
  22. expect(Object.keys(tlstate.getPage().shapes).length).toBe(3)
  23. tlstate.duplicate()
  24. expect(Object.keys(tlstate.getPage().shapes).length).toBe(4)
  25. tlstate.undo()
  26. expect(Object.keys(tlstate.getPage().shapes).length).toBe(3)
  27. tlstate.redo()
  28. expect(Object.keys(tlstate.getPage().shapes).length).toBe(4)
  29. })
  30. describe('when duplicating a shape', () => {
  31. it.todo('sets the correct props (parent and childIndex)')
  32. })
  33. describe('when duplicating a bound shape', () => {
  34. it('removed the binding when the target is not selected', () => {
  35. tlstate.resetDocument().createShapes(
  36. {
  37. id: 'target1',
  38. type: TLDrawShapeType.Rectangle,
  39. point: [0, 0],
  40. size: [100, 100],
  41. },
  42. {
  43. type: TLDrawShapeType.Arrow,
  44. id: 'arrow1',
  45. point: [200, 200],
  46. }
  47. )
  48. const beforeShapeIds = Object.keys(tlstate.page.shapes)
  49. tlstate
  50. .select('arrow1')
  51. .startSession(SessionType.Arrow, [200, 200], 'start')
  52. .updateSession([50, 50])
  53. .completeSession()
  54. const beforeArrow = tlstate.getShape<ArrowShape>('arrow1')
  55. expect(beforeArrow.handles.start.bindingId).toBeTruthy()
  56. tlstate.select('arrow1').duplicate()
  57. const afterShapeIds = Object.keys(tlstate.page.shapes)
  58. const newShapeIds = afterShapeIds.filter((id) => !beforeShapeIds.includes(id))
  59. expect(newShapeIds.length).toBe(1)
  60. const duplicatedArrow = tlstate.getShape<ArrowShape>(newShapeIds[0])
  61. expect(duplicatedArrow.handles.start.bindingId).toBeUndefined()
  62. })
  63. it('duplicates the binding when the target is selected', () => {
  64. tlstate.resetDocument().createShapes(
  65. {
  66. id: 'target1',
  67. type: TLDrawShapeType.Rectangle,
  68. point: [0, 0],
  69. size: [100, 100],
  70. },
  71. {
  72. type: TLDrawShapeType.Arrow,
  73. id: 'arrow1',
  74. point: [200, 200],
  75. }
  76. )
  77. const beforeShapeIds = Object.keys(tlstate.page.shapes)
  78. tlstate
  79. .select('arrow1')
  80. .startSession(SessionType.Arrow, [200, 200], 'start')
  81. .updateSession([50, 50])
  82. .completeSession()
  83. const oldBindingId = tlstate.getShape<ArrowShape>('arrow1').handles.start.bindingId
  84. expect(oldBindingId).toBeTruthy()
  85. tlstate.select('arrow1', 'target1').duplicate()
  86. const afterShapeIds = Object.keys(tlstate.page.shapes)
  87. const newShapeIds = afterShapeIds.filter((id) => !beforeShapeIds.includes(id))
  88. expect(newShapeIds.length).toBe(2)
  89. const newBindingId = tlstate.getShape<ArrowShape>(newShapeIds[0]).handles.start.bindingId
  90. expect(newBindingId).toBeTruthy()
  91. tlstate.undo()
  92. expect(tlstate.getBinding(newBindingId!)).toBeUndefined()
  93. expect(tlstate.getShape<ArrowShape>(newShapeIds[0])).toBeUndefined()
  94. tlstate.redo()
  95. expect(tlstate.getBinding(newBindingId!)).toBeTruthy()
  96. expect(tlstate.getShape<ArrowShape>(newShapeIds[0]).handles.start.bindingId).toBe(
  97. newBindingId
  98. )
  99. })
  100. it('duplicates groups', () => {
  101. tlstate.group(['rect1', 'rect2'], 'newGroup').select('newGroup')
  102. const beforeShapeIds = Object.keys(tlstate.page.shapes)
  103. tlstate.duplicate()
  104. expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 3)
  105. tlstate.undo()
  106. expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length)
  107. tlstate.redo()
  108. expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 3)
  109. })
  110. it('duplicates grouped shapes', () => {
  111. tlstate.group(['rect1', 'rect2'], 'newGroup').select('rect1')
  112. const beforeShapeIds = Object.keys(tlstate.page.shapes)
  113. tlstate.duplicate()
  114. expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 1)
  115. tlstate.undo()
  116. expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length)
  117. tlstate.redo()
  118. expect(Object.keys(tlstate.page.shapes).length).toBe(beforeShapeIds.length + 1)
  119. })
  120. })
  121. it.todo('Does not delete uneffected bindings.')
  122. })
  123. describe('when point-duplicating', () => {
  124. it('duplicates without crashing', () => {
  125. const tlstate = new TLDrawState()
  126. tlstate
  127. .loadDocument(mockDocument)
  128. .group(['rect1', 'rect2'])
  129. .selectAll()
  130. .duplicate(tlstate.selectedIds, [200, 200])
  131. })
  132. it('duplicates in the correct place', () => {
  133. const tlstate = new TLDrawState()
  134. tlstate.loadDocument(mockDocument).group(['rect1', 'rect2']).selectAll()
  135. const before = tlstate.shapes.map((shape) => shape.id)
  136. tlstate.duplicate(tlstate.selectedIds, [200, 200])
  137. const after = tlstate.shapes.filter((shape) => !before.includes(shape.id))
  138. expect(
  139. Utils.getBoundsCenter(Utils.getCommonBounds(after.map((shape) => TLDR.getBounds(shape))))
  140. ).toStrictEqual([200, 200])
  141. })
  142. })