diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx index 3251f3a7136b..cca92c289c76 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-element-change-location-strategy.spec.browser2.tsx @@ -1,7 +1,4 @@ -import { MetadataUtils } from '../../../../core/model/element-metadata-utils' -import { isLeft } from '../../../../core/shared/either' import * as EP from '../../../../core/shared/element-path' -import { isJSXElement } from '../../../../core/shared/element-template' import { getRectCenter, localRectangle, @@ -11,28 +8,24 @@ import { import { selectComponentsForTest } from '../../../../utils/utils.test-utils' import CanvasActions from '../../canvas-actions' import { GridCellTestId } from '../../controls/grid-controls-for-strategies' -import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' -import { - keyDown, - mouseDownAtPoint, - mouseMoveToPoint, - mouseUpAtPoint, -} from '../../event-helpers.test-utils' -import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import { mouseDownAtPoint, mouseMoveToPoint, mouseUpAtPoint } from '../../event-helpers.test-utils' import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' -import type { GridCellCoordinates } from './grid-cell-bounds' import { gridCellTargetId } from './grid-cell-bounds' +import { runGridMoveTest } from './grid.test-utils' describe('grid element change location strategy', () => { it('can change the location of elements on a grid', async () => { const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '7', gridColumnStart: '3', @@ -55,7 +48,7 @@ describe('grid element change location strategy', () => { 'await-first-dom-report', ) const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest( + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( editor, { scale: 1, @@ -82,7 +75,7 @@ describe('grid element change location strategy', () => { ) const testId = 'aaa' - await runMoveTest( + await runGridMoveTest( editor, { scale: 1, @@ -111,11 +104,14 @@ describe('grid element change location strategy', () => { ) const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '7', gridColumnStart: '3', @@ -129,13 +125,16 @@ describe('grid element change location strategy', () => { const testId = 'aaa' // moving a multi-cell element to the left, but it is already on the left of the grid - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - targetCell: { row: 2, column: 1 }, - draggedCell: { row: 2, column: 2 }, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + targetCell: { row: 2, column: 1 }, + draggedCell: { row: 2, column: 2 }, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '5', gridColumnStart: '1', @@ -148,12 +147,15 @@ describe('grid element change location strategy', () => { const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') const testId = 'bbb' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - tab: true, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + tab: true, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: 'auto', @@ -170,12 +172,15 @@ describe('grid element change location strategy', () => { ) const testId = 'orange' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - targetCell: { row: 3, column: 1 }, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + targetCell: { row: 3, column: 1 }, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnStart: '1', @@ -189,11 +194,14 @@ describe('grid element change location strategy', () => { const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 0.5, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 0.5, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '7', gridColumnStart: '3', @@ -206,11 +214,14 @@ describe('grid element change location strategy', () => { const editor = await renderTestEditorWithCode(ProjectCode, 'await-first-dom-report') const testId = 'aaa' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 2, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 2, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '7', gridColumnStart: '3', @@ -226,11 +237,14 @@ describe('grid element change location strategy', () => { ) const testId = 'pink' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '5', @@ -247,11 +261,14 @@ describe('grid element change location strategy', () => { ) const testId = 'cyan' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '5', @@ -268,12 +285,15 @@ describe('grid element change location strategy', () => { ) const testId = 'orange' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - targetCell: { row: 3, column: 2 }, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + targetCell: { row: 3, column: 2 }, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: 'span 3', @@ -290,11 +310,14 @@ describe('grid element change location strategy', () => { ) const testId = 'blue' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: '5', @@ -311,11 +334,14 @@ describe('grid element change location strategy', () => { ) const testId = 'purple' - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `sb/scene/grid/${testId}`, - testId: testId, - }) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `sb/scene/grid/${testId}`, + testId: testId, + }, + ) expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ gridColumnEnd: 'span 2', @@ -801,296 +827,8 @@ export var storyboard = ( }) }) }) - - describe('reorder', () => { - it('reorders an element without setting positioning (inside contiguous area)', async () => { - const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = - await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 3 }) - - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: '', - gridColumnStart: '', - gridRowEnd: '', - gridRowStart: '', - }) - - expect(cells).toEqual(['pink', 'cyan', 'orange', 'blue']) - }) - it('reorders a component (which does not take style props) inside contiguous area', async () => { - const editor = await renderTestEditorWithCode( - ProjectCodeReorderWithComponentItem, - 'await-first-dom-report', - ) - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = - await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 3 }) - - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: '', - gridColumnStart: '', - gridRowEnd: '', - gridRowStart: '', - }) - - expect(cells).toEqual(['pink', 'cyan', 'orange', 'blue']) - }) - it('reorders an element without setting positioning (edge of contiguous area)', async () => { - const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') - - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = - await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 1 }) - - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: '', - gridColumnStart: '', - gridRowEnd: '', - gridRowStart: '', - }) - - expect(cells).toEqual(['pink', 'cyan', 'blue', 'orange']) - }) - it('reorders an element setting positioning when outside of contiguous area', async () => { - const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') - - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = - await runReorderTest( - editor, - 'sb/scene/grid', - 'orange', - { row: 2, column: 2 }, - { tab: true }, - ) - - expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ - gridColumnEnd: 'auto', - gridColumnStart: '2', - gridRowEnd: 'auto', - gridRowStart: '2', - }) - - expect(cells).toEqual(['pink', 'cyan', 'blue', 'orange']) - }) - it('reorders an element setting positioning also relative to other fixed elements', async () => { - const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') - - const first = await runReorderTest( - editor, - 'sb/scene/grid', - 'orange', - { row: 2, column: 2 }, - { tab: true }, - ) - - expect({ - gridRowStart: first.gridRowStart, - gridRowEnd: first.gridRowEnd, - gridColumnStart: first.gridColumnStart, - gridColumnEnd: first.gridColumnEnd, - }).toEqual({ - gridColumnEnd: 'auto', - gridColumnStart: '2', - gridRowEnd: 'auto', - gridRowStart: '2', - }) - - expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) - - const second = await runReorderTest( - editor, - 'sb/scene/grid', - 'pink', - { row: 2, column: 3 }, - { tab: true }, - ) - - expect({ - gridRowStart: second.gridRowStart, - gridRowEnd: second.gridRowEnd, - gridColumnStart: second.gridColumnStart, - gridColumnEnd: second.gridColumnEnd, - }).toEqual({ - gridColumnEnd: 'auto', - gridColumnStart: '3', - gridRowEnd: 'auto', - gridRowStart: '2', - }) - - expect(second.cells).toEqual(['cyan', 'blue', 'orange', 'pink']) - }) - - it('reorders and removes positioning when moving back to contiguous', async () => { - const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') - - const first = await runReorderTest( - editor, - 'sb/scene/grid', - 'orange', - { row: 2, column: 2 }, - { tab: true }, - ) - expect({ - gridRowStart: first.gridRowStart, - gridRowEnd: first.gridRowEnd, - gridColumnStart: first.gridColumnStart, - gridColumnEnd: first.gridColumnEnd, - }).toEqual({ - gridColumnEnd: 'auto', - gridColumnStart: '2', - gridRowEnd: 'auto', - gridRowStart: '2', - }) - - expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) - - const second = await runReorderTest( - editor, - 'sb/scene/grid', - 'orange', - { row: 1, column: 1 }, - { tab: true }, - ) - - expect({ - gridRowStart: second.gridRowStart, - gridRowEnd: second.gridRowEnd, - gridColumnStart: second.gridColumnStart, - gridColumnEnd: second.gridColumnEnd, - }).toEqual({ - gridColumnEnd: '', - gridColumnStart: '', - gridRowEnd: '', - gridRowStart: '', - }) - - expect(second.cells).toEqual(['orange', 'pink', 'cyan', 'blue']) - }) - - it('doesnt reorder when element occupies multiple cells', async () => { - const editor = await renderTestEditorWithCode( - ProjectCodeReorderwithMultiCellChild, - 'await-first-dom-report', - ) - - const result = await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 1 }) - expect({ - gridRowStart: result.gridRowStart, - gridRowEnd: result.gridRowEnd, - gridColumnStart: result.gridColumnStart, - gridColumnEnd: result.gridColumnEnd, - }).toEqual({ - gridColumnEnd: '3', - gridColumnStart: '1', - gridRowEnd: 'auto', - gridRowStart: '1', - }) - - expect(result.cells).toEqual(['pink', 'orange', 'cyan', 'blue']) - }) - }) }) -async function runMoveTest( - editor: EditorRenderResult, - props: { - scale: number - pathString: string - testId: string - targetCell?: GridCellCoordinates - draggedCell?: GridCellCoordinates - tab?: boolean - }, - midDragCallback?: (editor: EditorRenderResult) => void, -) { - const elementPathToDrag = EP.fromString(props.pathString) - - await selectComponentsForTest(editor, [elementPathToDrag]) - - if (props.scale !== 1) { - await editor.dispatch([CanvasActions.zoom(props.scale)], true) - } - - const sourceGridCell = editor.renderedDOM.getByTestId( - props.draggedCell == null - ? GridCellTestId(elementPathToDrag) - : gridCellTargetId( - EP.fromString('sb/scene/grid'), - props.draggedCell.row, - props.draggedCell.column, - ), - ) - const targetGridCell = editor.renderedDOM.getByTestId( - gridCellTargetId( - EP.fromString('sb/scene/grid'), - props.targetCell?.row ?? 2, - props.targetCell?.column ?? 3, - ), - ) - - const sourceRect = sourceGridCell.getBoundingClientRect() - const targetRect = targetGridCell.getBoundingClientRect() - - const dragFrom = { - x: sourceRect.x + 10, - y: sourceRect.y + 10, - } - const endPoint = getRectCenter( - localRectangle({ - x: targetRect.x, - y: targetRect.y, - width: targetRect.width, - height: targetRect.height, - }), - ) - - await mouseDownAtPoint(sourceGridCell, dragFrom) - await mouseMoveToPoint(sourceGridCell, endPoint) - if (props.tab) { - await keyDown('Tab') - } - if (midDragCallback != null) { - midDragCallback(editor) - } - - await mouseUpAtPoint(editor.renderedDOM.getByTestId(CanvasControlsContainerID), endPoint) - - return editor.renderedDOM.getByTestId(props.testId).style -} - -async function runReorderTest( - editor: EditorRenderResult, - gridTestId: string, - testId: string, - targetCell: GridCellCoordinates, - options?: { tab?: boolean }, -) { - const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runMoveTest(editor, { - scale: 1, - pathString: `${gridTestId}/${testId}`, - testId: testId, - targetCell: targetCell, - tab: options?.tab, - }) - - const element = editor.getEditorState().editor.jsxMetadata[gridTestId] - if (isLeft(element.element) || !isJSXElement(element.element.value)) { - throw new Error('expected jsx element') - } - - const cells = MetadataUtils.getChildrenOrdered( - editor.getEditorState().editor.jsxMetadata, - editor.getEditorState().editor.elementPathTree, - element.elementPath, - ) - - return { - gridRowStart: gridRowStart, - gridRowEnd: gridRowEnd, - gridColumnStart: gridColumnStart, - gridColumnEnd: gridColumnEnd, - cells: cells.map((c) => EP.toUid(c.elementPath)), - } -} - const ProjectCode = `import * as React from 'react' import { Scene, Storyboard, Placeholder } from 'utopia-api' @@ -1354,241 +1092,6 @@ export function Grid(props) { } ` -const ProjectCodeReorder = `import * as React from 'react' -import { Scene, Storyboard } from 'utopia-api' - -export var storyboard = ( - - -
-
-
-
-
-
- - -) -` - -const ProjectCodeReorderWithComponentItem = `import * as React from 'react' -import { Scene, Storyboard } from 'utopia-api' - -export var storyboard = ( - - -
- - - - -
-
-
-) - -export function Item(props) { - return ( -
- ) -} -` - -const ProjectCodeReorderwithMultiCellChild = `import * as React from 'react' -import { Scene, Storyboard } from 'utopia-api' - -export var storyboard = ( - - -
-
-
-
-
-
- - -) -` - const ProjectCodeReorderwithMultiCellChildShorthand = `import * as React from 'react' import { Scene, Storyboard } from 'utopia-api' diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.spec.browser2.tsx b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.spec.browser2.tsx new file mode 100644 index 000000000000..9fffc31d25bd --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.spec.browser2.tsx @@ -0,0 +1,576 @@ +import { MetadataUtils } from '../../../../core/model/element-metadata-utils' +import { isLeft } from '../../../../core/shared/either' +import { isJSXElement } from '../../../../core/shared/element-template' +import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import { renderTestEditorWithCode } from '../../ui-jsx.test-utils' +import type { GridCellCoordinates } from './grid-cell-bounds' +import { runGridMoveTest } from './grid.test-utils' +import * as EP from '../../../../core/shared/element-path' + +describe('grid reorder', () => { + it('reorders an element without setting positioning (inside contiguous area)', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 3 }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(cells).toEqual(['pink', 'cyan', 'orange', 'blue']) + }) + it('reorders a component (which does not take style props) inside contiguous area', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderWithComponentItem, + 'await-first-dom-report', + ) + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 1, column: 3 }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(cells).toEqual(['pink', 'cyan', 'orange', 'blue']) + }) + it('reorders an element without setting positioning (edge of contiguous area)', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 1 }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + }) + it('reorders an element setting positioning when outside of contiguous area', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd, cells } = + await runReorderTest(editor, 'sb/scene/grid', 'orange', { row: 2, column: 2 }, { tab: true }) + + expect({ gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + }) + it('reorders an element setting positioning also relative to other fixed elements', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const first = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 2, column: 2 }, + { tab: true }, + ) + + expect({ + gridRowStart: first.gridRowStart, + gridRowEnd: first.gridRowEnd, + gridColumnStart: first.gridColumnStart, + gridColumnEnd: first.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + + const second = await runReorderTest( + editor, + 'sb/scene/grid', + 'pink', + { row: 2, column: 3 }, + { tab: true }, + ) + + expect({ + gridRowStart: second.gridRowStart, + gridRowEnd: second.gridRowEnd, + gridColumnStart: second.gridColumnStart, + gridColumnEnd: second.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '3', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(second.cells).toEqual(['cyan', 'blue', 'orange', 'pink']) + }) + + it('reorders and removes positioning when moving back to contiguous', async () => { + const editor = await renderTestEditorWithCode(ProjectCodeReorder, 'await-first-dom-report') + + const first = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 2, column: 2 }, + { tab: true }, + ) + expect({ + gridRowStart: first.gridRowStart, + gridRowEnd: first.gridRowEnd, + gridColumnStart: first.gridColumnStart, + gridColumnEnd: first.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: '2', + gridRowEnd: 'auto', + gridRowStart: '2', + }) + + expect(first.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + + const second = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 1, column: 1 }, + { tab: true }, + ) + + expect({ + gridRowStart: second.gridRowStart, + gridRowEnd: second.gridRowEnd, + gridColumnStart: second.gridColumnStart, + gridColumnEnd: second.gridColumnEnd, + }).toEqual({ + gridColumnEnd: '', + gridColumnStart: '', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(second.cells).toEqual(['orange', 'pink', 'cyan', 'blue']) + }) + + it('reorders when element occupies multiple cells', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderWithMultiCellChild, + 'await-first-dom-report', + ) + + const result = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 1, column: 1 }, + { tab: true }, + ) + + expect({ + gridRowStart: result.gridRowStart, + gridRowEnd: result.gridRowEnd, + gridColumnStart: result.gridColumnStart, + gridColumnEnd: result.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 2', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(result.cells).toEqual(['orange', 'pink', 'cyan', 'blue']) + }) + + it('reordering a spanning element keeps its size', async () => { + const editor = await renderTestEditorWithCode( + ProjectCodeReorderWithSpanningChild, + 'await-first-dom-report', + ) + + const result = await runReorderTest( + editor, + 'sb/scene/grid', + 'orange', + { row: 3, column: 2 }, + { tab: true }, + ) + + expect({ + gridRowStart: result.gridRowStart, + gridRowEnd: result.gridRowEnd, + gridColumnStart: result.gridColumnStart, + gridColumnEnd: result.gridColumnEnd, + }).toEqual({ + gridColumnEnd: 'auto', + gridColumnStart: 'span 3', + gridRowEnd: '', + gridRowStart: '', + }) + + expect(result.cells).toEqual(['pink', 'cyan', 'blue', 'orange']) + }) +}) + +async function runReorderTest( + editor: EditorRenderResult, + gridTestId: string, + testId: string, + targetCell: GridCellCoordinates, + options?: { tab?: boolean }, +) { + const { gridRowStart, gridRowEnd, gridColumnStart, gridColumnEnd } = await runGridMoveTest( + editor, + { + scale: 1, + pathString: `${gridTestId}/${testId}`, + testId: testId, + targetCell: targetCell, + tab: options?.tab, + }, + ) + + const element = editor.getEditorState().editor.jsxMetadata[gridTestId] + if (isLeft(element.element) || !isJSXElement(element.element.value)) { + throw new Error('expected jsx element') + } + + const cells = MetadataUtils.getChildrenOrdered( + editor.getEditorState().editor.jsxMetadata, + editor.getEditorState().editor.elementPathTree, + element.elementPath, + ) + + return { + gridRowStart: gridRowStart, + gridRowEnd: gridRowEnd, + gridColumnStart: gridColumnStart, + gridColumnEnd: gridColumnEnd, + cells: cells.map((c) => EP.toUid(c.elementPath)), + } +} + +const ProjectCodeReorder = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+ + +) +` + +const ProjectCodeReorderWithComponentItem = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+ + + + +
+
+
+) + +export function Item(props) { + return ( +
+ ) +} +` + +const ProjectCodeReorderWithMultiCellChild = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+ + +) +` + +const ProjectCodeReorderWithSpanningChild = `import * as React from 'react' +import { Scene, Storyboard } from 'utopia-api' + +export var storyboard = ( + + +
+
+
+
+
+
+ + +) +` diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.ts index bee89383e297..abec7c1437cd 100644 --- a/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.ts +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid-reorder-strategy.ts @@ -1,16 +1,14 @@ import type { ElementPath } from 'utopia-shared/src/types' import { MetadataUtils } from '../../../../core/model/element-metadata-utils' import * as EP from '../../../../core/shared/element-path' -import type { - ElementInstanceMetadata, - ElementInstanceMetadataMap, - GridContainerProperties, +import { + type ElementInstanceMetadata, + type ElementInstanceMetadataMap, + type GridContainerProperties, } from '../../../../core/shared/element-template' -import { isInfinityRectangle } from '../../../../core/shared/math-utils' import * as PP from '../../../../core/shared/property-path' import { absolute } from '../../../../utils/utils' import type { CanvasCommand } from '../../commands/commands' -import { deleteProperties } from '../../commands/delete-properties-command' import { reorderElement } from '../../commands/reorder-element-command' import { showGridControls } from '../../commands/show-grid-controls-command' import { controlsForGridPlaceholders } from '../../controls/grid-controls-for-strategies' @@ -34,6 +32,12 @@ import { } from './grid-helpers' import { getTargetGridCellData } from '../../../inspector/grid-helpers' import { gridItemIdentifier } from '../../../editor/store/editor-state' +import type { PropertyToUpdate } from '../../commands/set-property-command' +import { + propertyToDelete, + propertyToSet, + updateBulkProperties, +} from '../../commands/set-property-command' export const gridReorderStrategy: CanvasStrategyFactory = ( canvasState: InteractionCanvasState, @@ -226,16 +230,31 @@ function runGridReorder( canReorderToIndex ? targetRootCell : null, ) + const gridConfig = getOriginalElementGridConfiguration( + gridCellGlobalFrames, + interactionData, + selectedElementMetadata, + ) + + const width = gridConfig?.originalCellBounds.width ?? 1 + const height = gridConfig?.originalCellBounds.height ?? 1 + + const propsToUpdate: PropertyToUpdate[] = [ + propertyToDelete(PP.create('style', 'gridColumnStart')), + propertyToDelete(PP.create('style', 'gridColumnEnd')), + propertyToDelete(PP.create('style', 'gridRowStart')), + propertyToDelete(PP.create('style', 'gridRowEnd')), + ...(width > 1 + ? [propertyToSet(PP.create('style', 'gridColumn'), `span ${width}`)] + : [propertyToDelete(PP.create('style', 'gridColumn'))]), + ...(height > 1 + ? [propertyToSet(PP.create('style', 'gridRow'), `span ${height}`)] + : [propertyToDelete(PP.create('style', 'gridRow'))]), + ] + return [ reorderElement('always', selectedElementMetadata.elementPath, absolute(possiblyReorderIndex)), - deleteProperties('always', selectedElementMetadata.elementPath, [ - PP.create('style', 'gridColumn'), - PP.create('style', 'gridRow'), - PP.create('style', 'gridColumnStart'), - PP.create('style', 'gridColumnEnd'), - PP.create('style', 'gridRowStart'), - PP.create('style', 'gridRowEnd'), - ]), + updateBulkProperties('always', selectedElementMetadata.elementPath, propsToUpdate), updateGridControlsCommand, ] } diff --git a/editor/src/components/canvas/canvas-strategies/strategies/grid.test-utils.ts b/editor/src/components/canvas/canvas-strategies/strategies/grid.test-utils.ts new file mode 100644 index 000000000000..26eae7685310 --- /dev/null +++ b/editor/src/components/canvas/canvas-strategies/strategies/grid.test-utils.ts @@ -0,0 +1,82 @@ +import { getRectCenter, localRectangle } from '../../../../core/shared/math-utils' +import { selectComponentsForTest } from '../../../../utils/utils.test-utils' +import CanvasActions from '../../canvas-actions' +import { GridCellTestId } from '../../controls/grid-controls-for-strategies' +import { CanvasControlsContainerID } from '../../controls/new-canvas-controls' +import { + mouseDownAtPoint, + mouseMoveToPoint, + keyDown, + mouseUpAtPoint, +} from '../../event-helpers.test-utils' +import type { EditorRenderResult } from '../../ui-jsx.test-utils' +import type { GridCellCoordinates } from './grid-cell-bounds' +import { gridCellTargetId } from './grid-cell-bounds' +import * as EP from '../../../../core/shared/element-path' + +export async function runGridMoveTest( + editor: EditorRenderResult, + props: { + scale: number + pathString: string + testId: string + targetCell?: GridCellCoordinates + draggedCell?: GridCellCoordinates + tab?: boolean + }, + midDragCallback?: (editor: EditorRenderResult) => void, +) { + const elementPathToDrag = EP.fromString(props.pathString) + + await selectComponentsForTest(editor, [elementPathToDrag]) + + if (props.scale !== 1) { + await editor.dispatch([CanvasActions.zoom(props.scale)], true) + } + + const sourceGridCell = editor.renderedDOM.getByTestId( + props.draggedCell == null + ? GridCellTestId(elementPathToDrag) + : gridCellTargetId( + EP.fromString('sb/scene/grid'), + props.draggedCell.row, + props.draggedCell.column, + ), + ) + const targetGridCell = editor.renderedDOM.getByTestId( + gridCellTargetId( + EP.fromString('sb/scene/grid'), + props.targetCell?.row ?? 2, + props.targetCell?.column ?? 3, + ), + ) + + const sourceRect = sourceGridCell.getBoundingClientRect() + const targetRect = targetGridCell.getBoundingClientRect() + + const dragFrom = { + x: sourceRect.x + 10, + y: sourceRect.y + 10, + } + const endPoint = getRectCenter( + localRectangle({ + x: targetRect.x, + y: targetRect.y, + width: targetRect.width, + height: targetRect.height, + }), + ) + + await mouseDownAtPoint(sourceGridCell, dragFrom) + await mouseMoveToPoint(sourceGridCell, endPoint) + if (props.tab) { + await keyDown('Tab') + } + if (midDragCallback != null) { + midDragCallback(editor) + } + + await mouseUpAtPoint(editor.renderedDOM.getByTestId(CanvasControlsContainerID), endPoint) + + return editor.renderedDOM.getByTestId(props.testId).style +}