From 54ea0e3a2de1c1587910c6c0a5fdfbe45421e9a2 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Fri, 9 Jul 2021 09:59:43 +0100 Subject: [PATCH 1/3] Adds testMode, tests for RectangleShape --- .github/workflows/main.yml | 10 +- __tests__/__mocks__/document.json | 8 +- __tests__/__snapshots__/project.test.ts.snap | 32 +- .../__snapshots__/transform.test.ts.snap | 852 ++++++++++++++++++ __tests__/commands/align.test.ts | 40 + __tests__/commands/change-page.ts | 21 + __tests__/commands/delete-page.ts | 57 ++ __tests__/commands/delete-selected.ts | 40 + __tests__/{ => commands}/delete.test.ts | 4 +- __tests__/commands/distribute.ts | 37 + __tests__/commands/draw.ts | 21 + __tests__/commands/duplicate.ts | 40 + __tests__/commands/edit.ts | 21 + __tests__/commands/generate.ts | 21 + __tests__/commands/group.ts | 40 + __tests__/commands/move-to-page.ts | 40 + __tests__/commands/transform.test.ts | 347 +++++++ __tests__/{ => commands}/translate.test.ts | 10 +- __tests__/locked.test.ts | 2 +- __tests__/test-utils.ts | 101 ++- __tests__/transform.test.ts | 91 -- components/code-panel/types-import.ts | 8 - components/style-panel/shapes-functions.tsx | 4 +- package.json | 2 +- state/code/index.ts | 4 +- state/commands/command.ts | 3 +- state/commands/create-page.ts | 2 +- state/commands/group.ts | 2 +- state/commands/move-to-page.ts | 12 +- state/commands/move.ts | 3 +- state/commands/paste.ts | 4 +- state/commands/style.ts | 4 +- state/hacks.ts | 4 +- state/logger.ts | 19 +- state/sessions/brush-session.ts | 21 +- state/shape-utils/rectangle.tsx | 17 +- state/state.ts | 54 +- state/storage.ts | 16 +- types.ts | 3 +- utils/tld.ts | 12 +- utils/utils.ts | 8 - 41 files changed, 1800 insertions(+), 237 deletions(-) create mode 100644 __tests__/commands/__snapshots__/transform.test.ts.snap create mode 100644 __tests__/commands/align.test.ts create mode 100644 __tests__/commands/change-page.ts create mode 100644 __tests__/commands/delete-page.ts create mode 100644 __tests__/commands/delete-selected.ts rename __tests__/{ => commands}/delete.test.ts (96%) create mode 100644 __tests__/commands/distribute.ts create mode 100644 __tests__/commands/draw.ts create mode 100644 __tests__/commands/duplicate.ts create mode 100644 __tests__/commands/edit.ts create mode 100644 __tests__/commands/generate.ts create mode 100644 __tests__/commands/group.ts create mode 100644 __tests__/commands/move-to-page.ts create mode 100644 __tests__/commands/transform.test.ts rename __tests__/{ => commands}/translate.test.ts (67%) delete mode 100644 __tests__/transform.test.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3d1853f63..5dbc8665d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,8 +4,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install modules - run: yarn - - name: Run tests - run: yarn test + - uses: actions/checkout@v2 + - name: Install modules + run: yarn + - name: Run tests + run: yarn test diff --git a/__tests__/__mocks__/document.json b/__tests__/__mocks__/document.json index 2c5927473..9350b3b1a 100644 --- a/__tests__/__mocks__/document.json +++ b/__tests__/__mocks__/document.json @@ -15,8 +15,8 @@ "name": "Rectangle", "parentId": "page1", "childIndex": 3, - "point": [171.47, 288.63], - "size": [176.22, 192.26], + "point": [100, 100], + "size": [100, 100], "radius": 2, "rotation": 0, "style": { @@ -32,8 +32,8 @@ "name": "Rectangle", "parentId": "page1", "childIndex": 4, - "point": [511.7, 404.19], - "size": [181.08999999999992, 150.40999999999997], + "point": [500, 400], + "size": [200, 200], "radius": 2, "rotation": 0, "style": { diff --git a/__tests__/__snapshots__/project.test.ts.snap b/__tests__/__snapshots__/project.test.ts.snap index 6f9b3d340..ea5db5a39 100644 --- a/__tests__/__snapshots__/project.test.ts.snap +++ b/__tests__/__snapshots__/project.test.ts.snap @@ -64,14 +64,14 @@ for (let i = 0; i < count; i++) { "name": "Rectangle", "parentId": "page1", "point": Array [ - 511.7, - 404.19, + 500, + 400, ], "radius": 2, "rotation": 0, "size": Array [ - 181.08999999999992, - 150.40999999999997, + 200, + 200, ], "style": Object { "color": "Black", @@ -330,14 +330,14 @@ for (let i = 0; i < count; i++) { "name": "Rectangle", "parentId": "page1", "point": Array [ - 171.47, - 288.63, + 100, + 100, ], "radius": 2, "rotation": 0, "size": Array [ - 176.22, - 192.26, + 100, + 100, ], "style": Object { "color": "Black", @@ -468,14 +468,14 @@ for (let i = 0; i < count; i++) { "name": "Rectangle", "parentId": "page1", "point": Array [ - 511.7, - 404.19, + 500, + 400, ], "radius": 2, "rotation": 0, "size": Array [ - 181.08999999999992, - 150.40999999999997, + 200, + 200, ], "style": Object { "color": "Black", @@ -734,14 +734,14 @@ for (let i = 0; i < count; i++) { "name": "Rectangle", "parentId": "page1", "point": Array [ - 171.47, - 288.63, + 100, + 100, ], "radius": 2, "rotation": 0, "size": Array [ - 176.22, - 192.26, + 100, + 100, ], "style": Object { "color": "Black", diff --git a/__tests__/commands/__snapshots__/transform.test.ts.snap b/__tests__/commands/__snapshots__/transform.test.ts.snap new file mode 100644 index 000000000..cd0c460ac --- /dev/null +++ b/__tests__/commands/__snapshots__/transform.test.ts.snap @@ -0,0 +1,852 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transform command snapshot tests shift-transforms corners 1`] = ` +Object { + "height": 593.73, + "maxX": 700, + "maxY": 600, + "minX": -12.476, + "minY": 6.27, + "width": 712.476, +} +`; + +exports[`transform command snapshot tests shift-transforms corners 2`] = ` +Object { + "rect1": Object { + "point": Array [ + -12.476, + 6.27, + ], + "size": Array [ + 118.75, + 118.75, + ], + }, + "rect2": Object { + "point": Array [ + 462.51, + 362.51, + ], + "size": Array [ + 237.49, + 237.49, + ], + }, +} +`; + +exports[`transform command snapshot tests shift-transforms corners 3`] = ` +Object { + "height": 531.6320000000001, + "maxX": 737.9599999999999, + "maxY": 600, + "minX": 100, + "minY": 68.368, + "width": 637.9599999999999, +} +`; + +exports[`transform command snapshot tests shift-transforms corners 4`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 68.368, + ], + "size": Array [ + 106.33, + 106.33, + ], + }, + "rect2": Object { + "point": Array [ + 525.31, + 387.35, + ], + "size": Array [ + 212.65, + 212.65, + ], + }, +} +`; + +exports[`transform command snapshot tests shift-transforms corners 5`] = ` +Object { + "height": 533.62, + "maxX": 740.3499999999999, + "maxY": 633.62, + "minX": 100, + "minY": 100, + "width": 640.3499999999999, +} +`; + +exports[`transform command snapshot tests shift-transforms corners 6`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 106.72, + 106.72, + ], + }, + "rect2": Object { + "point": Array [ + 526.9, + 420.17, + ], + "size": Array [ + 213.45, + 213.45, + ], + }, +} +`; + +exports[`transform command snapshot tests shift-transforms corners 7`] = ` +Object { + "height": 490.52, + "maxX": 700, + "maxY": 590.52, + "minX": 111.38, + "minY": 100, + "width": 588.62, +} +`; + +exports[`transform command snapshot tests shift-transforms corners 8`] = ` +Object { + "rect1": Object { + "point": Array [ + 111.38, + 100, + ], + "size": Array [ + 98.104, + 98.104, + ], + }, + "rect2": Object { + "point": Array [ + 503.79, + 394.31, + ], + "size": Array [ + 196.21, + 196.21, + ], + }, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 1`] = ` +Object { + "height": 593.73, + "maxX": 756.24, + "maxY": 600, + "minX": 43.762, + "minY": 6.27, + "width": 712.4780000000001, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 2`] = ` +Object { + "rect1": Object { + "point": Array [ + 43.762, + 6.27, + ], + "size": Array [ + 118.75, + 118.75, + ], + }, + "rect2": Object { + "point": Array [ + 518.75, + 362.51, + ], + "size": Array [ + 237.49, + 237.49, + ], + }, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 3`] = ` +Object { + "height": 531.6260000000001, + "maxX": 737.9599999999999, + "maxY": 615.8100000000001, + "minX": 100, + "minY": 84.184, + "width": 637.9599999999999, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 4`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 84.184, + ], + "size": Array [ + 106.33, + 106.33, + ], + }, + "rect2": Object { + "point": Array [ + 525.31, + 403.16, + ], + "size": Array [ + 212.65, + 212.65, + ], + }, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 5`] = ` +Object { + "height": 411.35, + "maxX": 646.81, + "maxY": 511.35, + "minX": 153.19, + "minY": 100, + "width": 493.61999999999995, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 6`] = ` +Object { + "rect1": Object { + "point": Array [ + 153.19, + 100, + ], + "size": Array [ + 82.269, + 82.269, + ], + }, + "rect2": Object { + "point": Array [ + 482.27, + 346.81, + ], + "size": Array [ + 164.54, + 164.54, + ], + }, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 7`] = ` +Object { + "height": 490.52, + "maxX": 700, + "maxY": 595.26, + "minX": 111.38, + "minY": 104.74, + "width": 588.62, +} +`; + +exports[`transform command snapshot tests shift-transforms edges 8`] = ` +Object { + "rect1": Object { + "point": Array [ + 111.38, + 104.74, + ], + "size": Array [ + 98.104, + 98.104, + ], + }, + "rect2": Object { + "point": Array [ + 503.79, + 399.05, + ], + "size": Array [ + 196.21, + 196.21, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms corners 1`] = ` +Object { + "height": 593.73, + "maxX": 700, + "maxY": 600, + "minX": 27.892, + "minY": 6.27, + "width": 672.108, +} +`; + +exports[`transform command snapshot tests transforms corners 2`] = ` +Object { + "rect1": Object { + "point": Array [ + 27.892, + 6.27, + ], + "size": Array [ + 112.02, + 118.75, + ], + }, + "rect2": Object { + "point": Array [ + 475.96, + 362.51, + ], + "size": Array [ + 224.04, + 237.49, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms corners 3`] = ` +Object { + "height": 490.17, + "maxX": 737.9599999999999, + "maxY": 600, + "minX": 100, + "minY": 109.83, + "width": 637.9599999999999, +} +`; + +exports[`transform command snapshot tests transforms corners 4`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 109.83, + ], + "size": Array [ + 106.33, + 98.034, + ], + }, + "rect2": Object { + "point": Array [ + 525.31, + 403.93, + ], + "size": Array [ + 212.65, + 196.07, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms corners 5`] = ` +Object { + "height": 411.35, + "maxX": 740.3499999999999, + "maxY": 511.35, + "minX": 100, + "minY": 100, + "width": 640.3499999999999, +} +`; + +exports[`transform command snapshot tests transforms corners 6`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 106.72, + 82.269, + ], + }, + "rect2": Object { + "point": Array [ + 526.9, + 346.81, + ], + "size": Array [ + 213.45, + 164.54, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms corners 7`] = ` +Object { + "height": 437.4, + "maxX": 700, + "maxY": 537.4, + "minX": 111.38, + "minY": 100, + "width": 588.62, +} +`; + +exports[`transform command snapshot tests transforms corners 8`] = ` +Object { + "rect1": Object { + "point": Array [ + 111.38, + 100, + ], + "size": Array [ + 98.104, + 87.479, + ], + }, + "rect2": Object { + "point": Array [ + 503.79, + 362.44, + ], + "size": Array [ + 196.21, + 174.96, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms edges 1`] = ` +Object { + "height": 593.73, + "maxX": 700, + "maxY": 600, + "minX": 100, + "minY": 6.27, + "width": 600, +} +`; + +exports[`transform command snapshot tests transforms edges 2`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 6.27, + ], + "size": Array [ + 100, + 118.75, + ], + }, + "rect2": Object { + "point": Array [ + 500, + 362.51, + ], + "size": Array [ + 200, + 237.49, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms edges 3`] = ` +Object { + "height": 500, + "maxX": 737.9599999999999, + "maxY": 600, + "minX": 100, + "minY": 100, + "width": 637.9599999999999, +} +`; + +exports[`transform command snapshot tests transforms edges 4`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 106.33, + 100, + ], + }, + "rect2": Object { + "point": Array [ + 525.31, + 400, + ], + "size": Array [ + 212.65, + 200, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms edges 5`] = ` +Object { + "height": 411.35, + "maxX": 700, + "maxY": 511.35, + "minX": 100, + "minY": 100, + "width": 600, +} +`; + +exports[`transform command snapshot tests transforms edges 6`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 100, + 82.269, + ], + }, + "rect2": Object { + "point": Array [ + 500, + 346.81, + ], + "size": Array [ + 200, + 164.54, + ], + }, +} +`; + +exports[`transform command snapshot tests transforms edges 7`] = ` +Object { + "height": 500, + "maxX": 700, + "maxY": 600, + "minX": 111.38, + "minY": 100, + "width": 588.62, +} +`; + +exports[`transform command snapshot tests transforms edges 8`] = ` +Object { + "rect1": Object { + "point": Array [ + 111.38, + 100, + ], + "size": Array [ + 98.104, + 100, + ], + }, + "rect2": Object { + "point": Array [ + 503.79, + 400, + ], + "size": Array [ + 196.21, + 200, + ], + }, +} +`; + +exports[`transform command transforms from the bottom edge 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 100, + 120, + ], + }, + "rect2": Object { + "point": Array [ + 500, + 460, + ], + "size": Array [ + 200, + 240, + ], + }, +} +`; + +exports[`transform command transforms from the bottom-left corner 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 200, + 100, + ], + "size": Array [ + 83.333, + 120, + ], + }, + "rect2": Object { + "point": Array [ + 533.33, + 460, + ], + "size": Array [ + 166.67, + 240, + ], + }, +} +`; + +exports[`transform command transforms from the bottom-right corner 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 116.67, + 120, + ], + }, + "rect2": Object { + "point": Array [ + 566.67, + 460, + ], + "size": Array [ + 233.33, + 240, + ], + }, +} +`; + +exports[`transform command transforms from the left edge 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 200, + 100, + ], + "size": Array [ + 83.333, + 100, + ], + }, + "rect2": Object { + "point": Array [ + 533.33, + 400, + ], + "size": Array [ + 166.67, + 200, + ], + }, +} +`; + +exports[`transform command transforms from the right edge 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 116.67, + 100, + ], + }, + "rect2": Object { + "point": Array [ + 566.67, + 400, + ], + "size": Array [ + 233.33, + 200, + ], + }, +} +`; + +exports[`transform command transforms from the top edge 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 200, + ], + "size": Array [ + 100, + 80, + ], + }, + "rect2": Object { + "point": Array [ + 500, + 440, + ], + "size": Array [ + 200, + 160, + ], + }, +} +`; + +exports[`transform command transforms from the top-left corner 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 200, + 200, + ], + "size": Array [ + 83.333, + 80, + ], + }, + "rect2": Object { + "point": Array [ + 533.33, + 440, + ], + "size": Array [ + 166.67, + 160, + ], + }, +} +`; + +exports[`transform command transforms from the top-right corner 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 200, + ], + "size": Array [ + 116.67, + 80, + ], + }, + "rect2": Object { + "point": Array [ + 566.67, + 440, + ], + "size": Array [ + 233.33, + 160, + ], + }, +} +`; + +exports[`transform command when transforming from the bottom-right corner does command 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 116.67, + 120, + ], + }, + "rect2": Object { + "point": Array [ + 566.67, + 460, + ], + "size": Array [ + 233.33, + 240, + ], + }, +} +`; + +exports[`transform command when transforming from the bottom-right corner re-does command 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 116.67, + 120, + ], + }, + "rect2": Object { + "point": Array [ + 566.67, + 460, + ], + "size": Array [ + 233.33, + 240, + ], + }, +} +`; + +exports[`transform command when transforming from the bottom-right corner un-does command 1`] = ` +Object { + "rect1": Object { + "point": Array [ + 100, + 100, + ], + "size": Array [ + 100, + 100, + ], + }, + "rect2": Object { + "point": Array [ + 500, + 400, + ], + "size": Array [ + 200, + 200, + ], + }, +} +`; diff --git a/__tests__/commands/align.test.ts b/__tests__/commands/align.test.ts new file mode 100644 index 000000000..cc2de7824 --- /dev/null +++ b/__tests__/commands/align.test.ts @@ -0,0 +1,40 @@ +import TestState from '../test-utils' + +describe('align command', () => { + const tt = new TestState() + tt.resetDocumentState() + + describe('when one item is selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) + + describe('when multiple items are selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) +}) diff --git a/__tests__/commands/change-page.ts b/__tests__/commands/change-page.ts new file mode 100644 index 000000000..266dddca8 --- /dev/null +++ b/__tests__/commands/change-page.ts @@ -0,0 +1,21 @@ +import TestState from '../test-utils' + +describe('change page command', () => { + const tt = new TestState() + tt.resetDocumentState() + + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) +}) diff --git a/__tests__/commands/delete-page.ts b/__tests__/commands/delete-page.ts new file mode 100644 index 000000000..872f3d03f --- /dev/null +++ b/__tests__/commands/delete-page.ts @@ -0,0 +1,57 @@ +import TestState from '../test-utils' + +describe('delete page command', () => { + const tt = new TestState() + tt.resetDocumentState() + + describe('when last page is selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) + + describe('when first page is selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) + + describe('when project only has one page', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) +}) diff --git a/__tests__/commands/delete-selected.ts b/__tests__/commands/delete-selected.ts new file mode 100644 index 000000000..7df2eacc8 --- /dev/null +++ b/__tests__/commands/delete-selected.ts @@ -0,0 +1,40 @@ +import TestState from '../test-utils' + +describe('delete-selected command', () => { + const tt = new TestState() + tt.resetDocumentState() + + describe('when one item is selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) + + describe('when multiple items are selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) +}) diff --git a/__tests__/delete.test.ts b/__tests__/commands/delete.test.ts similarity index 96% rename from __tests__/delete.test.ts rename to __tests__/commands/delete.test.ts index b9d539605..2ce7fd65b 100644 --- a/__tests__/delete.test.ts +++ b/__tests__/commands/delete.test.ts @@ -1,7 +1,7 @@ import { ShapeType } from 'types' -import TestState, { rectangleId, arrowId } from './test-utils' +import TestState, { rectangleId, arrowId } from '../test-utils' -describe('deleting single shapes', () => { +describe('delete command', () => { const tt = new TestState() describe('deleting single shapes', () => { diff --git a/__tests__/commands/distribute.ts b/__tests__/commands/distribute.ts new file mode 100644 index 000000000..dd0a86868 --- /dev/null +++ b/__tests__/commands/distribute.ts @@ -0,0 +1,37 @@ +import TestState from '../test-utils' + +describe('distribute command', () => { + const tt = new TestState() + tt.resetDocumentState() + + describe('when one item is selected', () => { + it('does not change anything', () => { + // TODO + null + }) + }) + + describe('when two items are selected', () => { + it('does not change anything', () => { + // TODO + null + }) + }) + + describe('when three or more items are selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) +}) diff --git a/__tests__/commands/draw.ts b/__tests__/commands/draw.ts new file mode 100644 index 000000000..545b5ac9d --- /dev/null +++ b/__tests__/commands/draw.ts @@ -0,0 +1,21 @@ +import TestState from '../test-utils' + +describe('draw command', () => { + const tt = new TestState() + tt.resetDocumentState() + + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) +}) diff --git a/__tests__/commands/duplicate.ts b/__tests__/commands/duplicate.ts new file mode 100644 index 000000000..21317d071 --- /dev/null +++ b/__tests__/commands/duplicate.ts @@ -0,0 +1,40 @@ +import TestState from '../test-utils' + +describe('duplicate command', () => { + const tt = new TestState() + tt.resetDocumentState() + + describe('when one item is selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) + + describe('when multiple items are selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) +}) diff --git a/__tests__/commands/edit.ts b/__tests__/commands/edit.ts new file mode 100644 index 000000000..5d9d714a8 --- /dev/null +++ b/__tests__/commands/edit.ts @@ -0,0 +1,21 @@ +import TestState from '../test-utils' + +describe('edit command', () => { + const tt = new TestState() + tt.resetDocumentState() + + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) +}) diff --git a/__tests__/commands/generate.ts b/__tests__/commands/generate.ts new file mode 100644 index 000000000..74e37e2a3 --- /dev/null +++ b/__tests__/commands/generate.ts @@ -0,0 +1,21 @@ +import TestState from '../test-utils' + +describe('generate command', () => { + const tt = new TestState() + tt.resetDocumentState() + + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) +}) diff --git a/__tests__/commands/group.ts b/__tests__/commands/group.ts new file mode 100644 index 000000000..d2857e382 --- /dev/null +++ b/__tests__/commands/group.ts @@ -0,0 +1,40 @@ +import TestState from '../test-utils' + +describe('group command', () => { + const tt = new TestState() + tt.resetDocumentState() + + describe('when one item is selected', () => { + it('does not change anything', () => { + // TODO + null + }) + }) + + describe('when multiple items are selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) + + it('groups shapes with different parents', () => { + // TODO + null + }) + + it('does not group a parent group shape and its child', () => { + // TODO + null + }) +}) diff --git a/__tests__/commands/move-to-page.ts b/__tests__/commands/move-to-page.ts new file mode 100644 index 000000000..f098128bb --- /dev/null +++ b/__tests__/commands/move-to-page.ts @@ -0,0 +1,40 @@ +import TestState from '../test-utils' + +describe('move-to-page command', () => { + const tt = new TestState() + tt.resetDocumentState() + + describe('when one item is selected', () => { + it('does not change anything', () => { + // TODO + null + }) + }) + + describe('when multiple items are selected', () => { + it('does command', () => { + // TODO + null + }) + + it('un-does command', () => { + // TODO + null + }) + + it('re-does command', () => { + // TODO + null + }) + }) + + it('reparents children of groups to page', () => { + // TODO + null + }) + + it('correctly preserves moved groups', () => { + // TODO + null + }) +}) diff --git a/__tests__/commands/transform.test.ts b/__tests__/commands/transform.test.ts new file mode 100644 index 000000000..b8e859647 --- /dev/null +++ b/__tests__/commands/transform.test.ts @@ -0,0 +1,347 @@ +import { Corner, Edge, RectangleShape, ShapeType } from 'types' +import { rng } from 'utils' +import TestState from '../test-utils' + +describe('transform command', () => { + const tt = new TestState() + tt.resetDocumentState() + .createShape( + { + type: ShapeType.Rectangle, + point: [100, 100], + size: [100, 100], + childIndex: 1, + }, + 'rect1' + ) + .createShape( + { + type: ShapeType.Rectangle, + point: [500, 400], + size: [200, 200], + childIndex: 2, + }, + 'rect2' + ) + .clickShape('rect1') + .clickShape('rect2', { shiftKey: true }) + .save() + + function getSnapInfo() { + return { + rect1: { + point: tt.getShape('rect1').point, + size: tt.getShape('rect1').size, + }, + rect2: { + point: tt.getShape('rect2').point, + size: tt.getShape('rect2').size, + }, + } + } + + it('sets up initial bounds', () => { + expect(tt.selectedIds).toEqual(['rect1', 'rect2']) + + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 100, + maxX: 700, + maxY: 600, + width: 600, + height: 500, + }) + }) + + describe('when transforming from the bottom-right corner', () => { + it('does command', () => { + // Restore the saved data state, with the initial selection + tt.restore() + + // Move the bounds handle + tt.startClickingBoundsHandle(Corner.BottomRight) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 100, + maxX: 800, + maxY: 700, + width: 700, + height: 600, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('un-does command', () => { + // Repeat the same actions, but add an undo at the end + tt.restore() + .startClickingBoundsHandle(Corner.BottomRight) + .movePointerBy([100, 100]) + .stopClickingBounds() + .undo() + + // Expect the bounds to be the initial bounds + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 100, + maxX: 700, + maxY: 600, + width: 600, + height: 500, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('re-does command', () => { + // Repeat the same actions but add an undo and a redo at the end + tt.restore() + .startClickingBoundsHandle(Corner.BottomRight) + .movePointerBy([100, 100]) + .stopClickingBounds() + .undo() + .redo() + + // Expect the bounds to be the transformed bounds + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 100, + maxX: 800, + maxY: 700, + width: 700, + height: 600, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + }) + + // From here on, let's assume that the undo and redos work as expected, + // so let's only test the command's execution. + + it('transforms from the top edge', () => { + tt.restore() + .startClickingBoundsHandle(Edge.Top) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 200, + maxX: 700, + maxY: 600, + width: 600, + height: 400, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('transforms from the right edge', () => { + tt.restore() + .startClickingBoundsHandle(Edge.Right) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 100, + maxX: 800, + maxY: 600, + width: 700, + height: 500, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('transforms from the bottom edge', () => { + tt.restore() + .startClickingBoundsHandle(Edge.Bottom) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 100, + maxX: 700, + maxY: 700, + width: 600, + height: 600, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('transforms from the left edge', () => { + tt.restore() + .startClickingBoundsHandle(Edge.Left) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 200, + minY: 100, + maxX: 700, + maxY: 600, + width: 500, + height: 500, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('transforms from the top-left corner', () => { + tt.restore() + .startClickingBoundsHandle(Corner.TopLeft) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 200, + minY: 200, + maxX: 700, + maxY: 600, + width: 500, + height: 400, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('transforms from the top-right corner', () => { + tt.restore() + .startClickingBoundsHandle(Corner.TopRight) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 200, + maxX: 800, + maxY: 600, + width: 700, + height: 400, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('transforms from the bottom-right corner', () => { + tt.restore() + .startClickingBoundsHandle(Corner.BottomRight) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 100, + minY: 100, + maxX: 800, + maxY: 700, + width: 700, + height: 600, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + it('transforms from the bottom-left corner', () => { + tt.restore() + .startClickingBoundsHandle(Corner.BottomLeft) + .movePointerBy([100, 100]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchObject({ + minX: 200, + minY: 100, + maxX: 700, + maxY: 700, + width: 500, + height: 600, + }) + + expect(getSnapInfo()).toMatchSnapshot() + }) + + describe('snapshot tests', () => { + it('transforms corners', () => { + const getRandom = rng('transform-tests-random-number-generator') + + for (const corner of Object.values(Corner)) { + tt.restore() + .startClickingBoundsHandle(corner) + .movePointerBy([getRandom() * 200, getRandom() * 200]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchSnapshot() + + expect(getSnapInfo()).toMatchSnapshot() + } + }) + + it('transforms edges', () => { + const getRandom = rng('transform-tests-random-number-generator') + + for (const edge of Object.values(Edge)) { + tt.restore() + .startClickingBoundsHandle(edge) + .movePointerBy([getRandom() * 200, getRandom() * 200]) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchSnapshot() + + expect(getSnapInfo()).toMatchSnapshot() + } + }) + + it('shift-transforms corners', () => { + const getRandom = rng('transform-tests-random-number-generator') + + for (const corner of Object.values(Corner)) { + tt.restore() + .startClickingBoundsHandle(corner) + .movePointerBy([getRandom() * 200, getRandom() * 200], { + shiftKey: true, + }) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchSnapshot() + + expect(getSnapInfo()).toMatchSnapshot() + } + }) + + it('shift-transforms edges', () => { + const getRandom = rng('transform-tests-random-number-generator') + + for (const edge of Object.values(Edge)) { + tt.restore() + .startClickingBoundsHandle(edge) + .movePointerBy([getRandom() * 200, getRandom() * 200], { + shiftKey: true, + }) + .stopClickingBounds() + + // Ensure the bounds have been transformed + expect(tt.state.values.selectedBounds).toMatchSnapshot() + + expect(getSnapInfo()).toMatchSnapshot() + } + }) + }) +}) diff --git a/__tests__/translate.test.ts b/__tests__/commands/translate.test.ts similarity index 67% rename from __tests__/translate.test.ts rename to __tests__/commands/translate.test.ts index 6edcf263b..d9ea22c8a 100644 --- a/__tests__/translate.test.ts +++ b/__tests__/commands/translate.test.ts @@ -1,11 +1,9 @@ -import state from 'state' -import * as json from './__mocks__/document.json' +import TestState from '../test-utils' -state.reset() -state.send('MOUNTED').send('LOADED_FROM_FILE', { json: JSON.stringify(json) }) -state.send('CLEARED_PAGE') +describe('translate command', () => { + const tt = new TestState() + tt.resetDocumentState() -describe('translates shapes', () => { it('translates a single selected shape', () => { // TODO null diff --git a/__tests__/locked.test.ts b/__tests__/locked.test.ts index 12eaca8b0..b0f089e09 100644 --- a/__tests__/locked.test.ts +++ b/__tests__/locked.test.ts @@ -1,6 +1,6 @@ import TestState from './test-utils' -describe('locked shapes', () => { +describe('lock command', () => { const tt = new TestState() tt.resetDocumentState() diff --git a/__tests__/test-utils.ts b/__tests__/test-utils.ts index d4162f0c1..94daad003 100644 --- a/__tests__/test-utils.ts +++ b/__tests__/test-utils.ts @@ -2,8 +2,8 @@ import _state from 'state' import tld from 'utils/tld' import inputs from 'state/inputs' import { createShape, getShapeUtils } from 'state/shape-utils' -import { Data, Shape, ShapeType, ShapeUtility } from 'types' -import { deepCompareArrays, setToArray, uniqueId, vec } from 'utils' +import { Corner, Data, Edge, Shape, ShapeType, ShapeUtility } from 'types' +import { deepClone, deepCompareArrays, uniqueId, vec } from 'utils' import * as mockDocument from './__mocks__/document.json' type State = typeof _state @@ -22,9 +22,12 @@ interface PointerOptions { class TestState { state: State + snapshot: Data constructor() { this.state = _state + this.state.send('TOGGLED_TEST_MODE') + this.snapshot = deepClone(this.state.data) this.reset() } @@ -57,7 +60,7 @@ class TestState { *``` */ resetDocumentState(): TestState { - this.state.send('RESET_DOCUMENT_STATE') + this.state.send('RESET_DOCUMENT_STATE').send('TOGGLED_TEST_MODE') return this } @@ -122,13 +125,13 @@ class TestState { idsAreSelected(ids: string[], strict = true): boolean { const selectedIds = tld.getSelectedIds(this.data) return ( - (strict ? selectedIds.size === ids.length : true) && - ids.every((id) => selectedIds.has(id)) + (strict ? selectedIds.length === ids.length : true) && + ids.every((id) => selectedIds.includes(id)) ) } get selectedIds(): string[] { - return setToArray(tld.getSelectedIds(this.data)) + return tld.getSelectedIds(this.data) } /** @@ -340,6 +343,63 @@ class TestState { return this } + /** + * Start clicking bounds. + * + * ### Example + * + *```ts + * tt.startClickingBounds() + *``` + */ + startClickingBounds(options: PointerOptions = {}): TestState { + this.state.send( + 'POINTED_BOUNDS', + inputs.pointerDown(TestState.point(options), 'bounds') + ) + + return this + } + + /** + * Stop clicking the bounding box. + * + * ### Example + * + *```ts + * tt.stopClickingBounds() + *``` + */ + stopClickingBounds(options: PointerOptions = {}): TestState { + this.state.send( + 'STOPPED_POINTING', + inputs.pointerUp(TestState.point(options), 'bounds') + ) + + return this + } + + /** + * Start clicking a bounds handle. + * + * ### Example + * + *```ts + * tt.startClickingBoundsHandle(Edge.Top) + *``` + */ + startClickingBoundsHandle( + handle: Corner | Edge | 'center', + options: PointerOptions = {} + ): TestState { + this.state.send( + 'POINTED_BOUNDS_HANDLE', + inputs.pointerDown(TestState.point(options), handle) + ) + + return this + } + /** * Move the pointer to a new point, or to several points in order. * @@ -572,6 +632,35 @@ class TestState { return this } + /** + * Save a snapshot of the state's current data. + * + * ### Example + * + *```ts + * tt.save() + *``` + */ + save(): TestState { + this.snapshot = deepClone(this.data) + return this + } + + /** + * Restore the state's saved data. + * + * ### Example + * + *```ts + * tt.save() + * tt.restore() + *``` + */ + restore(): TestState { + this.state.forceData(this.snapshot) + return this + } + /** * Get the state's current data. * diff --git a/__tests__/transform.test.ts b/__tests__/transform.test.ts deleted file mode 100644 index e3c87506b..000000000 --- a/__tests__/transform.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import state from 'state' -import * as json from './__mocks__/document.json' - -state.reset() -state.send('MOUNTED').send('LOADED_FROM_FILE', { json: JSON.stringify(json) }) -state.send('CLEARED_PAGE') - -describe('transforms shapes', () => { - it('transforms from the top edge', () => { - // TODO - null - }) - - it('transforms from the right edge', () => { - // TODO - null - }) - - it('transforms from the bottom edge', () => { - // TODO - null - }) - - it('transforms from the left edge', () => { - // TODO - null - }) - - it('transforms from the top-left corner', () => { - // TODO - null - }) - - it('transforms from the top-right corner', () => { - // TODO - null - }) - - it('transforms from the bottom-right corner', () => { - // TODO - null - }) - - it('transforms from the bottom-left corner', () => { - // TODO - null - }) -}) - -describe('transforms shapes while aspect-ratio locked', () => { - // Fixed - - it('transforms from the top edge while aspect-ratio locked', () => { - // TODO - null - }) - - it('transforms from the right edge while aspect-ratio locked', () => { - // TODO - null - }) - - it('transforms from the bottom edge while aspect-ratio locked', () => { - // TODO - null - }) - it('transforms from the left edge while aspect-ratio locked', () => { - // TODO - null - }) - - it('transforms from the top-left corner while aspect-ratio locked', () => { - // TODO - null - }) - - it('transforms from the top-right corner while aspect-ratio locked', () => { - // TODO - null - }) - - it('transforms from the bottom-right corner while aspect-ratio locked', () => { - // TODO - null - }) - - it('transforms from the bottom-left corner while aspect-ratio locked', () => { - // TODO - null - }) -}) diff --git a/components/code-panel/types-import.ts b/components/code-panel/types-import.ts index 72e514ab2..79333837a 100644 --- a/components/code-panel/types-import.ts +++ b/components/code-panel/types-import.ts @@ -1828,14 +1828,6 @@ type RequiredKeys = { return Array.from(new Set(items).values()) } - /** - * Convert a set to an array. - * @param set - */ - static setToArray(set: Set): T[] { - return Array.from(set.values()) - } - /** * Get the outer of between a circle and a point. * @param C The circle's center. diff --git a/components/style-panel/shapes-functions.tsx b/components/style-panel/shapes-functions.tsx index de21a7822..ae2068b86 100644 --- a/components/style-panel/shapes-functions.tsx +++ b/components/style-panel/shapes-functions.tsx @@ -86,11 +86,11 @@ function ShapesFunctions() { }) const hasSelection = useSelector((s) => { - return tld.getSelectedIds(s.data).size > 0 + return tld.getSelectedIds(s.data).length > 0 }) const hasMultipleSelection = useSelector((s) => { - return tld.getSelectedIds(s.data).size > 1 + return tld.getSelectedIds(s.data).length > 1 }) return ( diff --git a/package.json b/package.json index 684180aee..275eeb160 100644 --- a/package.json +++ b/package.json @@ -95,4 +95,4 @@ "tabWidth": 2, "useTabs": false } -} \ No newline at end of file +} diff --git a/state/code/index.ts b/state/code/index.ts index 2f9d9556b..21f3969f4 100644 --- a/state/code/index.ts +++ b/state/code/index.ts @@ -7,13 +7,13 @@ import { SizeStyle, } from 'types' import { createShape, getShapeUtils } from 'state/shape-utils' -import { setToArray, uniqueId } from 'utils' +import { uniqueId } from 'utils' import Vec from 'utils/vec' export const codeShapes = new Set>([]) function getOrderedShapes() { - return setToArray(codeShapes).sort( + return Array.from(codeShapes.values()).sort( (a, b) => a.shape.childIndex - b.shape.childIndex ) } diff --git a/state/commands/command.ts b/state/commands/command.ts index 5dc83dcaf..c9ed31e29 100644 --- a/state/commands/command.ts +++ b/state/commands/command.ts @@ -1,5 +1,4 @@ import { Data } from 'types' -import { setToArray } from 'utils' import tld from 'utils/tld' /* ------------------ Command Class ----------------- */ @@ -85,7 +84,7 @@ export class BaseCommand { export default class Command extends BaseCommand { saveSelectionState = (data: Data): ((next: Data) => void) => { const { currentPageId } = data - const selectedIds = setToArray(tld.getSelectedIds(data)) + const selectedIds = [...tld.getSelectedIds(data)] return (next: Data) => { next.currentPageId = currentPageId next.hoveredId = undefined diff --git a/state/commands/create-page.ts b/state/commands/create-page.ts index b4dd21863..4852c4f1e 100644 --- a/state/commands/create-page.ts +++ b/state/commands/create-page.ts @@ -54,7 +54,7 @@ function getSnapshot(data: Data) { const pageState: PageState = { id, - selectedIds: new Set([]), + selectedIds: [], camera: { point: [0, 0], zoom: 1, diff --git a/state/commands/group.ts b/state/commands/group.ts index 4ef835c3a..130c00312 100644 --- a/state/commands/group.ts +++ b/state/commands/group.ts @@ -100,7 +100,7 @@ export default function groupCommand(data: Data): void { getShapeUtils(oldParent).setProperty( oldParent, 'children', - oldParent.children.filter((id) => !oldSelectedIds.has(id)) + oldParent.children.filter((id) => !oldSelectedIds.includes(id)) ) } diff --git a/state/commands/move-to-page.ts b/state/commands/move-to-page.ts index 7b907a133..2dceae819 100644 --- a/state/commands/move-to-page.ts +++ b/state/commands/move-to-page.ts @@ -1,7 +1,7 @@ import Command from './command' import history from '../history' import { Data } from 'types' -import { setToArray, uniqueArray } from 'utils' +import { uniqueArray } from 'utils' import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' import storage from 'state/storage' @@ -9,7 +9,7 @@ import storage from 'state/storage' export default function moveToPageCommand(data: Data, newPageId: string): void { const { currentPageId: oldPageId } = data const oldPage = tld.getPage(data) - const selectedIds = setToArray(tld.getSelectedIds(data)) + const selectedIds = [...tld.getSelectedIds(data)] const idsToMove = uniqueArray( ...selectedIds.flatMap((id) => tld.getDocumentBranch(data, id)) @@ -59,7 +59,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { }) // Clear the current page state's selected ids - tld.getPageState(data).selectedIds.clear() + tld.setSelectedIds(data, []) // Save the "from" page storage.savePage(data, data.document.id, fromPageId) @@ -83,7 +83,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { }) // Select the selected ids on the new page - tld.getPageState(data).selectedIds = new Set(selectedIds) + tld.setSelectedIds(data, [...selectedIds]) // Move to the new page data.currentPageId = toPageId @@ -113,7 +113,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { delete fromPage.shapes[shape.id] }) - tld.getPageState(data).selectedIds.clear() + tld.setSelectedIds(data, []) storage.savePage(data, data.document.id, fromPageId) @@ -138,7 +138,7 @@ export default function moveToPageCommand(data: Data, newPageId: string): void { } }) - tld.getPageState(data).selectedIds = new Set(selectedIds) + tld.setSelectedIds(data, [...selectedIds]) data.currentPageId = toPageId }, diff --git a/state/commands/move.ts b/state/commands/move.ts index 54bab0c8e..ab9f51f37 100644 --- a/state/commands/move.ts +++ b/state/commands/move.ts @@ -1,14 +1,13 @@ import Command from './command' import history from '../history' import { Data, MoveType, Shape } from 'types' -import { setToArray } from 'utils' import tld from 'utils/tld' import { getShapeUtils } from 'state/shape-utils' export default function moveCommand(data: Data, type: MoveType): void { const page = tld.getPage(data) - const selectedIds = setToArray(tld.getSelectedIds(data)) + const selectedIds = [...tld.getSelectedIds(data)] const initialIndices = Object.fromEntries( selectedIds.map((id) => [id, page.shapes[id].childIndex]) diff --git a/state/commands/paste.ts b/state/commands/paste.ts index e057c65ac..c80b9b159 100644 --- a/state/commands/paste.ts +++ b/state/commands/paste.ts @@ -1,7 +1,7 @@ import Command from './command' import history from '../history' import { Data, Shape } from 'types' -import { getCommonBounds, setToArray } from 'utils' +import { getCommonBounds } from 'utils' import tld from 'utils/tld' import { uniqueId } from 'utils/utils' import vec from 'utils/vec' @@ -26,7 +26,7 @@ export default function pasteCommand(data: Data, initialShapes: Shape[]): void { initialShapes.map((shape) => [shape.id, uniqueId()]) ) - const oldSelectedIds = setToArray(tld.getSelectedIds(data)) + const oldSelectedIds = [...tld.getSelectedIds(data)] history.execute( data, diff --git a/state/commands/style.ts b/state/commands/style.ts index 4c320d1da..33e1729e2 100644 --- a/state/commands/style.ts +++ b/state/commands/style.ts @@ -2,7 +2,7 @@ import Command from './command' import history from '../history' import { Data, ShapeStyles } from 'types' import tld from 'utils/tld' -import { deepClone, setToArray } from 'utils' +import { deepClone } from 'utils' import { getShapeUtils } from 'state/shape-utils' export default function styleCommand( @@ -11,7 +11,7 @@ export default function styleCommand( ): void { const page = tld.getPage(data) - const selectedIds = setToArray(tld.getSelectedIds(data)) + const selectedIds = [...tld.getSelectedIds(data)] const shapesToStyle = selectedIds .flatMap((id) => tld.getDocumentBranch(data, id)) diff --git a/state/hacks.ts b/state/hacks.ts index 5ee96df47..84d3b4596 100644 --- a/state/hacks.ts +++ b/state/hacks.ts @@ -1,5 +1,5 @@ import { DrawShape, PointerInfo } from 'types' -import { deepClone, setToArray } from 'utils' +import { deepClone } from 'utils' import tld from 'utils/tld' import { freeze } from 'immer' import session from './session' @@ -56,7 +56,7 @@ export function fastDrawUpdate(info: PointerInfo): void { info.shiftKey ) - const selectedId = setToArray(tld.getSelectedIds(data))[0] + const selectedId = [...tld.getSelectedIds(data)][0] const { shapes } = data.document.pages[data.currentPageId] diff --git a/state/logger.ts b/state/logger.ts index ec3482df2..25c2a5532 100644 --- a/state/logger.ts +++ b/state/logger.ts @@ -2,7 +2,6 @@ import { Data } from 'types' import clipboard from './clipboard' import state from './state' import { isDraft, current } from 'immer' -import { setToArray } from 'utils' import tld from 'utils/tld' import inputs from './inputs' @@ -59,9 +58,9 @@ class Logger { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - this.snapshotStart.pageStates[data.currentPageId].selectedIds = setToArray( - tld.getSelectedIds(data) - ) + this.snapshotStart.pageStates[data.currentPageId].selectedIds = [ + ...tld.getSelectedIds(data), + ] return this } @@ -84,9 +83,9 @@ class Logger { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - this.snapshotEnd.pageStates[data.currentPageId].selectedIds = setToArray( - tld.getSelectedIds(data) - ) + this.snapshotEnd.pageStates[data.currentPageId].selectedIds = [ + ...tld.getSelectedIds(data), + ] // if (window.confirm('Stopped logging. Copy to clipboard?')) { // this.copyToJson() @@ -138,9 +137,9 @@ class Logger { this.isSimulating = true try { - data.pageStates[data.currentPageId].selectedIds = new Set( - start.pageStates[start.currentPageId].selectedIds - ) + data.pageStates[data.currentPageId].selectedIds = [ + ...start.pageStates[start.currentPageId].selectedIds, + ] state.send('RESET_DOCUMENT_STATE').forceData(start) diff --git a/state/sessions/brush-session.ts b/state/sessions/brush-session.ts index de9971ad7..5c7aac63c 100644 --- a/state/sessions/brush-session.ts +++ b/state/sessions/brush-session.ts @@ -1,7 +1,7 @@ import { Bounds, Data, ShapeType } from 'types' import BaseSession from './base-session' import { getShapeUtils } from 'state/shape-utils' -import { deepClone, getBoundsFromPoints, setToArray } from 'utils' +import { deepClone, getBoundsFromPoints } from 'utils' import vec from 'utils/vec' import tld from 'utils/tld' @@ -24,10 +24,10 @@ export default class BrushSession extends BaseSession { const hits = new Set([]) - const selectedIds = new Set(snapshot.selectedIds) + const selectedIds = [...snapshot.selectedIds] for (const id in snapshot.shapeHitTests) { - if (selectedIds.has(id)) continue + if (selectedIds.includes(id)) continue const { test, selectId } = snapshot.shapeHitTests[id] if (!hits.has(selectId)) { @@ -35,11 +35,11 @@ export default class BrushSession extends BaseSession { hits.add(selectId) // When brushing a shape, select its top group parent. - if (!selectedIds.has(selectId)) { - selectedIds.add(selectId) + if (!selectedIds.includes(selectId)) { + selectedIds.push(selectId) } - } else if (selectedIds.has(selectId)) { - selectedIds.delete(selectId) + } else if (selectedIds.includes(selectId)) { + selectedIds.splice(selectedIds.indexOf(selectId), 1) } } } @@ -73,12 +73,15 @@ export function getBrushSnapshot(data: Data) { .getShapes(cData) .filter((shape) => shape.type !== ShapeType.Group && !shape.isHidden) .filter( - (shape) => !(selectedIds.has(shape.id) || selectedIds.has(shape.parentId)) + (shape) => + !( + selectedIds.includes(shape.id) || selectedIds.includes(shape.parentId) + ) ) .map(deepClone) return { - selectedIds: setToArray(selectedIds), + selectedIds: [...selectedIds], shapeHitTests: Object.fromEntries( shapesToTest.map((shape) => { return [ diff --git a/state/shape-utils/rectangle.tsx b/state/shape-utils/rectangle.tsx index 2f17241be..ce0f9b763 100644 --- a/state/shape-utils/rectangle.tsx +++ b/state/shape-utils/rectangle.tsx @@ -133,22 +133,21 @@ const rectangle = registerShapeUtils({ transform(shape, bounds, { initialShape, transformOrigin, scaleX, scaleY }) { if (shape.rotation === 0 && !shape.isAspectRatioLocked) { - shape.size = [bounds.width, bounds.height] - shape.point = [bounds.minX, bounds.minY] + shape.size = vec.round([bounds.width, bounds.height]) + shape.point = vec.round([bounds.minX, bounds.minY]) } else { - shape.size = vec.mul( - initialShape.size, - Math.min(Math.abs(scaleX), Math.abs(scaleY)) + shape.size = vec.round( + vec.mul(initialShape.size, Math.min(Math.abs(scaleX), Math.abs(scaleY))) ) - shape.point = [ + shape.point = vec.round([ bounds.minX + (bounds.width - shape.size[0]) * (scaleX < 0 ? 1 - transformOrigin[0] : transformOrigin[0]), bounds.minY + (bounds.height - shape.size[1]) * (scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]), - ] + ]) shape.rotation = (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0) @@ -160,8 +159,8 @@ const rectangle = registerShapeUtils({ }, transformSingle(shape, bounds) { - shape.size = [bounds.width, bounds.height] - shape.point = [bounds.minX, bounds.minY] + shape.size = vec.round([bounds.width, bounds.height]) + shape.point = vec.round([bounds.minX, bounds.minY]) return this }, }) diff --git a/state/state.ts b/state/state.ts index 7043f8391..86ed7659d 100644 --- a/state/state.ts +++ b/state/state.ts @@ -13,7 +13,6 @@ import { getCommonBounds, rotateBounds, getBoundsCenter, - setToArray, deepClone, pointInBounds, uniqueId, @@ -43,6 +42,7 @@ const initialData: Data = { isReadOnly: false, settings: { fontSize: 13, + isTestMode: false, isDarkMode: false, isCodeOpen: false, isDebugMode: false, @@ -133,7 +133,7 @@ for (let i = 0; i < count; i++) { pageStates: { page1: { id: 'page1', - selectedIds: new Set([]), + selectedIds: [], camera: { point: [0, 0], zoom: 1, @@ -147,6 +147,7 @@ const state = createState({ on: { TOGGLED_DEBUG_PANEL: 'toggleDebugPanel', TOGGLED_DEBUG_MODE: 'toggleDebugMode', + TOGGLED_TEST_MODE: 'toggleTestMode', TOGGLED_LOGGER: 'toggleLogger', COPIED_DEBUG_LOG: 'copyDebugLog', LOADED_FROM_SNAPSHOT: { @@ -588,7 +589,10 @@ const state = createState({ onEnter: 'startTransformSession', onExit: 'completeSession', on: { - // MOVED_POINTER: 'updateTransformSession', using hacks.fastTransform + MOVED_POINTER: { + ifAny: ['isSimulating', 'isTestMode'], + do: 'updateTransformSession', + }, PANNED_CAMERA: 'updateTransformSession', PRESSED_SHIFT_KEY: 'keyUpdateTransformSession', RELEASED_SHIFT_KEY: 'keyUpdateTransformSession', @@ -638,7 +642,7 @@ const state = createState({ 'startBrushSession', ], on: { - // MOVED_POINTER: 'updateBrushSession', using hacks.fastBrushSelect + MOVED_POINTER: { if: 'isTestMode', do: 'updateBrushSession' }, PANNED_CAMERA: 'updateBrushSession', STOPPED_POINTING: { to: 'selecting' }, STARTED_PINCHING: { to: 'pinching' }, @@ -694,7 +698,7 @@ const state = createState({ }, pinching: { on: { - // PINCHED: { do: 'pinchCamera' }, using hacks.fastPinchCamera + PINCHED: { if: 'isTestMode', do: 'pinchCamera' }, }, initial: 'selectPinching', onExit: { secretlyDo: 'updateZoomCSS' }, @@ -760,7 +764,7 @@ const state = createState({ RELEASED_SHIFT: 'keyUpdateDrawSession', PANNED_CAMERA: 'updateDrawSession', MOVED_POINTER: { - if: 'isSimulating', + ifAny: ['isSimulating', 'isTestMode'], do: 'updateDrawSession', }, }, @@ -1115,6 +1119,9 @@ const state = createState({ isSimulating() { return logger.isSimulating }, + isTestMode(data) { + return data.settings.isTestMode + }, isEditingShape(data, payload: { id: string }) { return payload.id === data.editingId }, @@ -1131,7 +1138,7 @@ const state = createState({ return tld.getShape(data, payload.target)?.type === ShapeType.Text }, isPointingBounds(data, payload: PointerInfo) { - return tld.getSelectedIds(data).size > 0 && payload.target === 'bounds' + return tld.getSelectedIds(data).length > 0 && payload.target === 'bounds' }, isPointingShape(data, payload: PointerInfo) { return ( @@ -1156,7 +1163,7 @@ const state = createState({ return payload.target !== undefined }, isPointedShapeSelected(data) { - return tld.getSelectedIds(data).has(data.pointedId) + return tld.getSelectedIds(data).includes(data.pointedId) }, isPressingShiftKey(data, payload: PointerInfo) { return payload.shiftKey @@ -1192,13 +1199,13 @@ const state = createState({ return payload.target === 'rotate' }, hasSelection(data) { - return tld.getSelectedIds(data).size > 0 + return tld.getSelectedIds(data).length > 0 }, hasSingleSelection(data) { - return tld.getSelectedIds(data).size === 1 + return tld.getSelectedIds(data).length === 1 }, hasMultipleSelection(data) { - return tld.getSelectedIds(data).size > 1 + return tld.getSelectedIds(data).length > 1 }, hasCurrentParentShape(data) { return data.currentParentId !== data.currentPageId @@ -1230,6 +1237,9 @@ const state = createState({ toggleDebugMode(data) { data.settings.isDebugMode = !data.settings.isDebugMode }, + toggleTestMode(data) { + data.settings.isTestMode = !data.settings.isTestMode + }, toggleDebugPanel(data) { data.settings.isDebugOpen = !data.settings.isDebugOpen }, @@ -1304,7 +1314,7 @@ const state = createState({ data.pageStates = { [newPageId]: { id: newPageId, - selectedIds: new Set(), + selectedIds: [], camera: { point: [0, 0], zoom: 1, @@ -1464,7 +1474,7 @@ const state = createState({ // Handles doublePointHandle(data, payload: PointerInfo) { - const id = setToArray(tld.getSelectedIds(data))[0] + const id = tld.getSelectedIds(data)[0] commands.doublePointHandle(data, id, payload) }, @@ -1511,7 +1521,7 @@ const state = createState({ ) { const point = tld.screenToWorld(inputs.pointer.origin, data) session.begin( - tld.getSelectedIds(data).size === 1 + tld.getSelectedIds(data).length === 1 ? new Sessions.TransformSingleSession(data, payload.target, point) : new Sessions.TransformSession(data, payload.target, point) ) @@ -1618,7 +1628,7 @@ const state = createState({ inputs.clear() }, deselectAll(data) { - tld.getSelectedIds(data).clear() + tld.setSelectedIds(data, []) }, selectAll(data) { tld.setSelectedIds( @@ -1673,10 +1683,10 @@ const state = createState({ pullPointedIdFromSelectedIds(data) { const { pointedId } = data const selectedIds = tld.getSelectedIds(data) - selectedIds.delete(pointedId) + selectedIds.splice(selectedIds.indexOf(pointedId), 1) }, pushPointedIdToSelectedIds(data) { - tld.getSelectedIds(data).add(data.pointedId) + tld.getSelectedIds(data).push(data.pointedId) }, moveSelection(data, payload: { type: MoveType }) { commands.move(data, payload.type) @@ -1732,7 +1742,7 @@ const state = createState({ data.editingId = selectedShape.id } - tld.getPageState(data).selectedIds = new Set([selectedShape.id]) + tld.getPageState(data).selectedIds = [selectedShape.id] }, clearEditingId(data) { data.editingId = null @@ -2094,7 +2104,7 @@ const state = createState({ }, values: { selectedIds(data) { - return setToArray(tld.getSelectedIds(data)) + return tld.getSelectedIds(data) }, selectedBounds(data) { return getSelectionBounds(data) @@ -2107,7 +2117,7 @@ const state = createState({ .sort((a, b) => a.childIndex - b.childIndex) }, selectedStyle(data) { - const selectedIds = setToArray(tld.getSelectedIds(data)) + const selectedIds = tld.getSelectedIds(data) const { currentStyle } = data if (selectedIds.length === 0) { @@ -2195,9 +2205,9 @@ function getSelectionBounds(data: Data) { const shapes = tld.getSelectedShapes(data) - if (selectedIds.size === 0) return null + if (selectedIds.length === 0) return null - if (selectedIds.size === 1) { + if (selectedIds.length === 1) { if (!shapes[0]) { console.warn('Could not find that shape! Clearing selected IDs.') tld.setSelectedIds(data, []) diff --git a/state/storage.ts b/state/storage.ts index a8edf9838..54a923f12 100644 --- a/state/storage.ts +++ b/state/storage.ts @@ -1,5 +1,5 @@ import { Data, PageState, TLDocument } from 'types' -import { decompress, compress, setToArray } from 'utils' +import { decompress, compress } from 'utils' import state from './state' import { uniqueId } from 'utils/utils' import * as idb from 'idb-keyval' @@ -132,14 +132,11 @@ class Storage { if (savedPageState !== null) { // If we've found a page state in local storage, set it into state. data.pageStates[pageId] = JSON.parse(decompress(savedPageState)) - data.pageStates[pageId].selectedIds = new Set( - data.pageStates[pageId].selectedIds - ) } else { // Or else create a new one. data.pageStates[pageId] = { id: pageId, - selectedIds: new Set([]), + selectedIds: [], camera: { point: [0, 0], zoom: 1, @@ -161,13 +158,13 @@ class Storage { throw new Error('Page state id not in document') } - pageState.selectedIds = new Set([]) + pageState.selectedIds = [] data.pageStates[pageState.id] = pageState data.currentPageId = pageState.id } catch (e) { data.pageStates[data.currentPageId] = { id: data.currentPageId, - selectedIds: new Set([]), + selectedIds: [], camera: { point: [0, 0], zoom: 1, @@ -249,7 +246,7 @@ class Storage { storageId(fileId, 'pageState', pageId), JSON.stringify({ ...currentPageState, - selectedIds: setToArray(currentPageState.selectedIds), + selectedIds: [...currentPageState.selectedIds], }) ) } @@ -286,7 +283,6 @@ class Storage { // If we have a page, move it into state const restored: PageState = JSON.parse(savedPageState) data.pageStates[pageId] = restored - data.pageStates[pageId].selectedIds = new Set(restored.selectedIds) } else { data.pageStates[pageId] = { id: pageId, @@ -294,7 +290,7 @@ class Storage { point: [0, 0], zoom: 1, }, - selectedIds: new Set([]), + selectedIds: [], } } diff --git a/types.ts b/types.ts index 52875d41b..1d5227a29 100644 --- a/types.ts +++ b/types.ts @@ -8,6 +8,7 @@ export interface Data { fontSize: number isDarkMode: boolean isCodeOpen: boolean + isTestMode: boolean isDebugOpen: boolean isDebugMode: boolean isStyleOpen: boolean @@ -66,7 +67,7 @@ export interface Page { export interface PageState { id: string - selectedIds: Set + selectedIds: string[] camera: { point: number[] zoom: number diff --git a/utils/tld.ts b/utils/tld.ts index 52285186f..7ada7baf7 100644 --- a/utils/tld.ts +++ b/utils/tld.ts @@ -1,4 +1,4 @@ -import { clamp, deepClone, getCommonBounds, setToArray } from 'utils' +import { clamp, deepClone, getCommonBounds } from 'utils' import { getShapeUtils } from 'state/shape-utils' import vec from './vec' import { @@ -98,7 +98,7 @@ export default class StateUtils { */ static getSelectedShapes(data: Data): Shape[] { const page = this.getPage(data) - const ids = setToArray(this.getSelectedIds(data)) + const ids = this.getSelectedIds(data) return ids.map((id) => page.shapes[id]) } @@ -306,12 +306,12 @@ export default class StateUtils { ] } - static getSelectedIds(data: Data): Set { + static getSelectedIds(data: Data): string[] { return data.pageStates[data.currentPageId].selectedIds } - static setSelectedIds(data: Data, ids: string[]): Set { - data.pageStates[data.currentPageId].selectedIds = new Set(ids) + static setSelectedIds(data: Data, ids: string[]): string[] { + data.pageStates[data.currentPageId].selectedIds = [...ids] return data.pageStates[data.currentPageId].selectedIds } @@ -347,7 +347,7 @@ export default class StateUtils { >(data: Data, fn?: F): (Shape | K)[] { const page = this.getPage(data) - const copies = setToArray(this.getSelectedIds(data)) + const copies = this.getSelectedIds(data) .flatMap((id) => this.getDocumentBranch(data, id).map((id) => page.shapes[id]) ) diff --git a/utils/utils.ts b/utils/utils.ts index 8d8c3652d..0838fe9fa 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1572,14 +1572,6 @@ export function uniqueArray(...items: T[]): T[] { return Array.from(new Set(items).values()) } -/** - * Convert a set to an array. - * @param set - */ -export function setToArray(set: Set): T[] { - return Array.from(set.values()) -} - /* -------------------------------------------------- */ /* Browser and DOM */ /* -------------------------------------------------- */ From 96a678dab9b74e2b1a9ace1ad7b63fcff0ae0fc5 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Fri, 9 Jul 2021 10:28:57 +0100 Subject: [PATCH 2/3] Update main.yml --- .github/workflows/main.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5db813ac..0d332cab1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,11 +4,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install modules - run: yarn # unit tests - name: Jest Annotations & Coverage uses: mattallty/jest-github-action@v1.0.3 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d995211e8e49f4335787c87a72f5d1ab5e6ed2d3 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Fri, 9 Jul 2021 10:30:26 +0100 Subject: [PATCH 3/3] Update main.yml --- .github/workflows/main.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d332cab1..2af738278 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,8 +4,13 @@ jobs: build: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 + - name: Install modules + run: yarn # unit tests - name: Jest Annotations & Coverage uses: mattallty/jest-github-action@v1.0.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + test-command: 'yarn test'