Quellcode durchsuchen

Much more thorough tests! (#1053)

vanilla_orig
Pete Hunt vor 5 Jahren
Ursprung
Commit
bd7856adf3
Es ist kein Account mit der E-Mail-Adresse des Committers verbunden

+ 1
- 0
.eslintignore Datei anzeigen

@@ -2,3 +2,4 @@ node_modules/
2 2
 build/
3 3
 package-lock.json
4 4
 .vscode/
5
+firebase/

+ 1
- 2
src/appState.ts Datei anzeigen

@@ -1,7 +1,6 @@
1 1
 import { AppState, FlooredNumber } from "./types";
2 2
 import { getDateTime } from "./utils";
3 3
 
4
-const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
5 4
 export const DEFAULT_FONT = "20px Virgil";
6 5
 
7 6
 export function getDefaultAppState(): AppState {
@@ -26,7 +25,7 @@ export function getDefaultAppState(): AppState {
26 25
     cursorX: 0,
27 26
     cursorY: 0,
28 27
     scrolledOutside: false,
29
-    name: DEFAULT_PROJECT_NAME,
28
+    name: `excalidraw-${getDateTime()}`,
30 29
     isCollaborating: false,
31 30
     isResizing: false,
32 31
     selectionElement: null,

+ 2
- 2
src/data/restore.ts Datei anzeigen

@@ -4,8 +4,8 @@ import { ExcalidrawElement } from "../element/types";
4 4
 import { AppState } from "../types";
5 5
 import { DataState } from "./types";
6 6
 import { isInvisiblySmallElement, normalizeDimensions } from "../element";
7
-import nanoid from "nanoid";
8 7
 import { calculateScrollCenter } from "../scene";
8
+import { randomId } from "../random";
9 9
 
10 10
 export function restore(
11 11
   // we're making the elements mutable for this API because we want to
@@ -62,7 +62,7 @@ export function restore(
62 62
         ...element,
63 63
         // all elements must have version > 0 so getDrawingVersion() will pick up newly added elements
64 64
         version: element.version || 1,
65
-        id: element.id || nanoid(),
65
+        id: element.id || randomId(),
66 66
         fillStyle: element.fillStyle || "hachure",
67 67
         strokeWidth: element.strokeWidth || 1,
68 68
         roughness: element.roughness ?? 1,

+ 3
- 3
src/element/mutateElement.ts Datei anzeigen

@@ -1,8 +1,8 @@
1 1
 import { ExcalidrawElement } from "./types";
2
-import { randomSeed } from "roughjs/bin/math";
3 2
 import { invalidateShapeForElement } from "../renderer/renderElement";
4 3
 import { globalSceneState } from "../scene";
5 4
 import { getSizeFromPoints } from "../points";
5
+import { randomInteger } from "../random";
6 6
 
7 7
 type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
8 8
   Partial<TElement>,
@@ -42,7 +42,7 @@ export function mutateElement<TElement extends Mutable<ExcalidrawElement>>(
42 42
   }
43 43
 
44 44
   element.version++;
45
-  element.versionNonce = randomSeed();
45
+  element.versionNonce = randomInteger();
46 46
 
47 47
   globalSceneState.informMutation();
48 48
 }
@@ -54,7 +54,7 @@ export function newElementWith<TElement extends ExcalidrawElement>(
54 54
   return {
55 55
     ...element,
56 56
     version: element.version + 1,
57
-    versionNonce: randomSeed(),
57
+    versionNonce: randomInteger(),
58 58
     ...updates,
59 59
   };
60 60
 }

+ 5
- 7
src/element/newElement.ts Datei anzeigen

@@ -1,6 +1,3 @@
1
-import { randomSeed } from "roughjs/bin/math";
2
-import nanoid from "nanoid";
3
-
4 1
 import {
5 2
   ExcalidrawElement,
6 3
   ExcalidrawTextElement,
@@ -8,6 +5,7 @@ import {
8 5
   ExcalidrawGenericElement,
9 6
 } from "../element/types";
10 7
 import { measureText } from "../utils";
8
+import { randomInteger, randomId } from "../random";
11 9
 
12 10
 type ElementConstructorOpts = {
13 11
   x: ExcalidrawGenericElement["x"];
@@ -39,7 +37,7 @@ function _newElementBase<T extends ExcalidrawElement>(
39 37
   }: ElementConstructorOpts & Partial<ExcalidrawGenericElement>,
40 38
 ) {
41 39
   return {
42
-    id: rest.id || nanoid(),
40
+    id: rest.id || randomId(),
43 41
     type,
44 42
     x,
45 43
     y,
@@ -51,7 +49,7 @@ function _newElementBase<T extends ExcalidrawElement>(
51 49
     strokeWidth,
52 50
     roughness,
53 51
     opacity,
54
-    seed: rest.seed ?? randomSeed(),
52
+    seed: rest.seed ?? randomInteger(),
55 53
     version: rest.version || 1,
56 54
     versionNonce: rest.versionNonce ?? 0,
57 55
     isDeleted: rest.isDeleted ?? false,
@@ -145,8 +143,8 @@ export function duplicateElement<TElement extends Mutable<ExcalidrawElement>>(
145 143
   overrides?: Partial<TElement>,
146 144
 ): TElement {
147 145
   let copy: TElement = _duplicateElement(element);
148
-  copy.id = nanoid();
149
-  copy.seed = randomSeed();
146
+  copy.id = randomId();
147
+  copy.seed = randomInteger();
150 148
   if (overrides) {
151 149
     copy = Object.assign(copy, overrides);
152 150
   }

+ 8
- 0
src/history.ts Datei anzeigen

@@ -14,6 +14,14 @@ export class SceneHistory {
14 14
   private stateHistory: string[] = [];
15 15
   private redoStack: string[] = [];
16 16
 
17
+  getSnapshotForTest() {
18
+    return {
19
+      recording: this.recording,
20
+      stateHistory: this.stateHistory.map((s) => JSON.parse(s)),
21
+      redoStack: this.redoStack.map((s) => JSON.parse(s)),
22
+    };
23
+  }
24
+
17 25
   clear() {
18 26
     this.stateHistory.length = 0;
19 27
     this.redoStack.length = 0;

+ 2
- 0
src/keys.ts Datei anzeigen

@@ -14,6 +14,8 @@ export const KEYS = {
14 14
   SPACE: " ",
15 15
 } as const;
16 16
 
17
+export type Key = keyof typeof KEYS;
18
+
17 19
 export function isArrowKey(keyCode: string) {
18 20
   return (
19 21
     keyCode === KEYS.ARROW_LEFT ||

+ 18
- 0
src/random.ts Datei anzeigen

@@ -0,0 +1,18 @@
1
+import { Random } from "roughjs/bin/math";
2
+import nanoid from "nanoid";
3
+
4
+let random = new Random(Date.now());
5
+let testIdBase = 0;
6
+
7
+export function randomInteger() {
8
+  return Math.floor(random.next() * 2 ** 31);
9
+}
10
+
11
+export function reseed(seed: number) {
12
+  random = new Random(seed);
13
+  testIdBase = 0;
14
+}
15
+
16
+export function randomId() {
17
+  return process.env.NODE_ENV === "test" ? `id${testIdBase++}` : nanoid();
18
+}

+ 136
- 0
src/tests/__snapshots__/dragCreate.test.tsx.snap Datei anzeigen

@@ -0,0 +1,136 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`add element to the scene when pointer dragging long enough arrow 1`] = `1`;
4
+
5
+exports[`add element to the scene when pointer dragging long enough arrow 2`] = `
6
+Object {
7
+  "backgroundColor": "transparent",
8
+  "fillStyle": "hachure",
9
+  "height": 50,
10
+  "id": "id0",
11
+  "isDeleted": false,
12
+  "lastCommittedPoint": null,
13
+  "opacity": 100,
14
+  "points": Array [
15
+    Array [
16
+      0,
17
+      0,
18
+    ],
19
+    Array [
20
+      30,
21
+      50,
22
+    ],
23
+  ],
24
+  "roughness": 1,
25
+  "seed": 337897,
26
+  "strokeColor": "#000000",
27
+  "strokeWidth": 1,
28
+  "type": "arrow",
29
+  "version": 3,
30
+  "versionNonce": 449462985,
31
+  "width": 30,
32
+  "x": 30,
33
+  "y": 20,
34
+}
35
+`;
36
+
37
+exports[`add element to the scene when pointer dragging long enough diamond 1`] = `1`;
38
+
39
+exports[`add element to the scene when pointer dragging long enough diamond 2`] = `
40
+Object {
41
+  "backgroundColor": "transparent",
42
+  "fillStyle": "hachure",
43
+  "height": 50,
44
+  "id": "id0",
45
+  "isDeleted": false,
46
+  "opacity": 100,
47
+  "roughness": 1,
48
+  "seed": 337897,
49
+  "strokeColor": "#000000",
50
+  "strokeWidth": 1,
51
+  "type": "diamond",
52
+  "version": 2,
53
+  "versionNonce": 1278240551,
54
+  "width": 30,
55
+  "x": 30,
56
+  "y": 20,
57
+}
58
+`;
59
+
60
+exports[`add element to the scene when pointer dragging long enough ellipse 1`] = `1`;
61
+
62
+exports[`add element to the scene when pointer dragging long enough ellipse 2`] = `
63
+Object {
64
+  "backgroundColor": "transparent",
65
+  "fillStyle": "hachure",
66
+  "height": 50,
67
+  "id": "id0",
68
+  "isDeleted": false,
69
+  "opacity": 100,
70
+  "roughness": 1,
71
+  "seed": 337897,
72
+  "strokeColor": "#000000",
73
+  "strokeWidth": 1,
74
+  "type": "ellipse",
75
+  "version": 2,
76
+  "versionNonce": 1278240551,
77
+  "width": 30,
78
+  "x": 30,
79
+  "y": 20,
80
+}
81
+`;
82
+
83
+exports[`add element to the scene when pointer dragging long enough line 1`] = `
84
+Object {
85
+  "backgroundColor": "transparent",
86
+  "fillStyle": "hachure",
87
+  "height": 50,
88
+  "id": "id0",
89
+  "isDeleted": false,
90
+  "lastCommittedPoint": null,
91
+  "opacity": 100,
92
+  "points": Array [
93
+    Array [
94
+      0,
95
+      0,
96
+    ],
97
+    Array [
98
+      30,
99
+      50,
100
+    ],
101
+  ],
102
+  "roughness": 1,
103
+  "seed": 337897,
104
+  "strokeColor": "#000000",
105
+  "strokeWidth": 1,
106
+  "type": "line",
107
+  "version": 3,
108
+  "versionNonce": 449462985,
109
+  "width": 30,
110
+  "x": 30,
111
+  "y": 20,
112
+}
113
+`;
114
+
115
+exports[`add element to the scene when pointer dragging long enough rectangle 1`] = `1`;
116
+
117
+exports[`add element to the scene when pointer dragging long enough rectangle 2`] = `
118
+Object {
119
+  "backgroundColor": "transparent",
120
+  "fillStyle": "hachure",
121
+  "height": 50,
122
+  "id": "id0",
123
+  "isDeleted": false,
124
+  "opacity": 100,
125
+  "roughness": 1,
126
+  "seed": 337897,
127
+  "strokeColor": "#000000",
128
+  "strokeWidth": 1,
129
+  "type": "rectangle",
130
+  "version": 2,
131
+  "versionNonce": 1278240551,
132
+  "width": 30,
133
+  "x": 30,
134
+  "y": 20,
135
+}
136
+`;

+ 64
- 0
src/tests/__snapshots__/move.test.tsx.snap Datei anzeigen

@@ -0,0 +1,64 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`duplicate element on move when ALT is clicked rectangle 1`] = `
4
+Object {
5
+  "backgroundColor": "transparent",
6
+  "fillStyle": "hachure",
7
+  "height": 50,
8
+  "id": "id1",
9
+  "isDeleted": false,
10
+  "opacity": 100,
11
+  "roughness": 1,
12
+  "seed": 453191,
13
+  "strokeColor": "#000000",
14
+  "strokeWidth": 1,
15
+  "type": "rectangle",
16
+  "version": 2,
17
+  "versionNonce": 1278240551,
18
+  "width": 30,
19
+  "x": 30,
20
+  "y": 20,
21
+}
22
+`;
23
+
24
+exports[`duplicate element on move when ALT is clicked rectangle 2`] = `
25
+Object {
26
+  "backgroundColor": "transparent",
27
+  "fillStyle": "hachure",
28
+  "height": 50,
29
+  "id": "id0",
30
+  "isDeleted": false,
31
+  "opacity": 100,
32
+  "roughness": 1,
33
+  "seed": 337897,
34
+  "strokeColor": "#000000",
35
+  "strokeWidth": 1,
36
+  "type": "rectangle",
37
+  "version": 3,
38
+  "versionNonce": 2019559783,
39
+  "width": 30,
40
+  "x": 0,
41
+  "y": 40,
42
+}
43
+`;
44
+
45
+exports[`move element rectangle 1`] = `
46
+Object {
47
+  "backgroundColor": "transparent",
48
+  "fillStyle": "hachure",
49
+  "height": 50,
50
+  "id": "id0",
51
+  "isDeleted": false,
52
+  "opacity": 100,
53
+  "roughness": 1,
54
+  "seed": 337897,
55
+  "strokeColor": "#000000",
56
+  "strokeWidth": 1,
57
+  "type": "rectangle",
58
+  "version": 3,
59
+  "versionNonce": 401146281,
60
+  "width": 30,
61
+  "x": 0,
62
+  "y": 40,
63
+}
64
+`;

+ 79
- 0
src/tests/__snapshots__/multiPointCreate.test.tsx.snap Datei anzeigen

@@ -0,0 +1,79 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`multi point mode in linear elements arrow 1`] = `
4
+Object {
5
+  "backgroundColor": "transparent",
6
+  "fillStyle": "hachure",
7
+  "height": 110,
8
+  "id": "id0",
9
+  "isDeleted": false,
10
+  "lastCommittedPoint": Array [
11
+    70,
12
+    110,
13
+  ],
14
+  "opacity": 100,
15
+  "points": Array [
16
+    Array [
17
+      0,
18
+      0,
19
+    ],
20
+    Array [
21
+      20,
22
+      30,
23
+    ],
24
+    Array [
25
+      70,
26
+      110,
27
+    ],
28
+  ],
29
+  "roughness": 1,
30
+  "seed": 337897,
31
+  "strokeColor": "#000000",
32
+  "strokeWidth": 1,
33
+  "type": "arrow",
34
+  "version": 7,
35
+  "versionNonce": 1116226695,
36
+  "width": 70,
37
+  "x": 30,
38
+  "y": 30,
39
+}
40
+`;
41
+
42
+exports[`multi point mode in linear elements line 1`] = `
43
+Object {
44
+  "backgroundColor": "transparent",
45
+  "fillStyle": "hachure",
46
+  "height": 110,
47
+  "id": "id0",
48
+  "isDeleted": false,
49
+  "lastCommittedPoint": Array [
50
+    70,
51
+    110,
52
+  ],
53
+  "opacity": 100,
54
+  "points": Array [
55
+    Array [
56
+      0,
57
+      0,
58
+    ],
59
+    Array [
60
+      20,
61
+      30,
62
+    ],
63
+    Array [
64
+      70,
65
+      110,
66
+    ],
67
+  ],
68
+  "roughness": 1,
69
+  "seed": 337897,
70
+  "strokeColor": "#000000",
71
+  "strokeWidth": 1,
72
+  "type": "line",
73
+  "version": 7,
74
+  "versionNonce": 1116226695,
75
+  "width": 70,
76
+  "x": 30,
77
+  "y": 30,
78
+}
79
+`;

+ 10762
- 0
src/tests/__snapshots__/regressionTests.test.tsx.snap
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 43
- 0
src/tests/__snapshots__/resize.test.tsx.snap Datei anzeigen

@@ -0,0 +1,43 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`resize element rectangle 1`] = `
4
+Object {
5
+  "backgroundColor": "transparent",
6
+  "fillStyle": "hachure",
7
+  "height": 50,
8
+  "id": "id0",
9
+  "isDeleted": false,
10
+  "opacity": 100,
11
+  "roughness": 1,
12
+  "seed": 337897,
13
+  "strokeColor": "#000000",
14
+  "strokeWidth": 1,
15
+  "type": "rectangle",
16
+  "version": 3,
17
+  "versionNonce": 1150084233,
18
+  "width": 30,
19
+  "x": 29,
20
+  "y": 47,
21
+}
22
+`;
23
+
24
+exports[`resize element with aspect ratio when SHIFT is clicked rectangle 1`] = `
25
+Object {
26
+  "backgroundColor": "transparent",
27
+  "fillStyle": "hachure",
28
+  "height": 50,
29
+  "id": "id0",
30
+  "isDeleted": false,
31
+  "opacity": 100,
32
+  "roughness": 1,
33
+  "seed": 337897,
34
+  "strokeColor": "#000000",
35
+  "strokeWidth": 1,
36
+  "type": "rectangle",
37
+  "version": 3,
38
+  "versionNonce": 1150084233,
39
+  "width": 30,
40
+  "x": 29,
41
+  "y": 47,
42
+}
43
+`;

+ 128
- 0
src/tests/__snapshots__/selection.test.tsx.snap Datei anzeigen

@@ -0,0 +1,128 @@
1
+// Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+exports[`select single element on the scene arrow 1`] = `
4
+Object {
5
+  "backgroundColor": "transparent",
6
+  "fillStyle": "hachure",
7
+  "height": 50,
8
+  "id": "id0",
9
+  "isDeleted": false,
10
+  "lastCommittedPoint": null,
11
+  "opacity": 100,
12
+  "points": Array [
13
+    Array [
14
+      0,
15
+      0,
16
+    ],
17
+    Array [
18
+      30,
19
+      50,
20
+    ],
21
+  ],
22
+  "roughness": 1,
23
+  "seed": 337897,
24
+  "strokeColor": "#000000",
25
+  "strokeWidth": 1,
26
+  "type": "arrow",
27
+  "version": 3,
28
+  "versionNonce": 449462985,
29
+  "width": 30,
30
+  "x": 30,
31
+  "y": 20,
32
+}
33
+`;
34
+
35
+exports[`select single element on the scene arrow escape 1`] = `
36
+Object {
37
+  "backgroundColor": "transparent",
38
+  "fillStyle": "hachure",
39
+  "height": 50,
40
+  "id": "id0",
41
+  "isDeleted": false,
42
+  "lastCommittedPoint": null,
43
+  "opacity": 100,
44
+  "points": Array [
45
+    Array [
46
+      0,
47
+      0,
48
+    ],
49
+    Array [
50
+      30,
51
+      50,
52
+    ],
53
+  ],
54
+  "roughness": 1,
55
+  "seed": 337897,
56
+  "strokeColor": "#000000",
57
+  "strokeWidth": 1,
58
+  "type": "line",
59
+  "version": 3,
60
+  "versionNonce": 449462985,
61
+  "width": 30,
62
+  "x": 30,
63
+  "y": 20,
64
+}
65
+`;
66
+
67
+exports[`select single element on the scene diamond 1`] = `
68
+Object {
69
+  "backgroundColor": "transparent",
70
+  "fillStyle": "hachure",
71
+  "height": 50,
72
+  "id": "id0",
73
+  "isDeleted": false,
74
+  "opacity": 100,
75
+  "roughness": 1,
76
+  "seed": 337897,
77
+  "strokeColor": "#000000",
78
+  "strokeWidth": 1,
79
+  "type": "diamond",
80
+  "version": 2,
81
+  "versionNonce": 1278240551,
82
+  "width": 30,
83
+  "x": 30,
84
+  "y": 20,
85
+}
86
+`;
87
+
88
+exports[`select single element on the scene ellipse 1`] = `
89
+Object {
90
+  "backgroundColor": "transparent",
91
+  "fillStyle": "hachure",
92
+  "height": 50,
93
+  "id": "id0",
94
+  "isDeleted": false,
95
+  "opacity": 100,
96
+  "roughness": 1,
97
+  "seed": 337897,
98
+  "strokeColor": "#000000",
99
+  "strokeWidth": 1,
100
+  "type": "ellipse",
101
+  "version": 2,
102
+  "versionNonce": 1278240551,
103
+  "width": 30,
104
+  "x": 30,
105
+  "y": 20,
106
+}
107
+`;
108
+
109
+exports[`select single element on the scene rectangle 1`] = `
110
+Object {
111
+  "backgroundColor": "transparent",
112
+  "fillStyle": "hachure",
113
+  "height": 50,
114
+  "id": "id0",
115
+  "isDeleted": false,
116
+  "opacity": 100,
117
+  "roughness": 1,
118
+  "seed": 337897,
119
+  "strokeColor": "#000000",
120
+  "strokeWidth": 1,
121
+  "type": "rectangle",
122
+  "version": 2,
123
+  "versionNonce": 1278240551,
124
+  "width": 30,
125
+  "x": 30,
126
+  "y": 20,
127
+}
128
+`;

+ 16
- 0
src/tests/dragCreate.test.tsx Datei anzeigen

@@ -5,6 +5,7 @@ import * as Renderer from "../renderer/renderScene";
5 5
 import { KEYS } from "../keys";
6 6
 import { render, fireEvent } from "./test-utils";
7 7
 import { ExcalidrawLinearElement } from "../element/types";
8
+import { reseed } from "../random";
8 9
 
9 10
 // Unmount ReactDOM from root
10 11
 ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -13,6 +14,7 @@ const renderScene = jest.spyOn(Renderer, "renderScene");
13 14
 beforeEach(() => {
14 15
   localStorage.clear();
15 16
   renderScene.mockClear();
17
+  reseed(7);
16 18
 });
17 19
 
18 20
 const { h } = window;
@@ -44,6 +46,9 @@ describe("add element to the scene when pointer dragging long enough", () => {
44 46
     expect(h.elements[0].y).toEqual(20);
45 47
     expect(h.elements[0].width).toEqual(30); // 60 - 30
46 48
     expect(h.elements[0].height).toEqual(50); // 70 - 20
49
+
50
+    expect(h.elements.length).toMatchSnapshot();
51
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
47 52
   });
48 53
 
49 54
   it("ellipse", () => {
@@ -72,6 +77,9 @@ describe("add element to the scene when pointer dragging long enough", () => {
72 77
     expect(h.elements[0].y).toEqual(20);
73 78
     expect(h.elements[0].width).toEqual(30); // 60 - 30
74 79
     expect(h.elements[0].height).toEqual(50); // 70 - 20
80
+
81
+    expect(h.elements.length).toMatchSnapshot();
82
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
75 83
   });
76 84
 
77 85
   it("diamond", () => {
@@ -100,6 +108,9 @@ describe("add element to the scene when pointer dragging long enough", () => {
100 108
     expect(h.elements[0].y).toEqual(20);
101 109
     expect(h.elements[0].width).toEqual(30); // 60 - 30
102 110
     expect(h.elements[0].height).toEqual(50); // 70 - 20
111
+
112
+    expect(h.elements.length).toMatchSnapshot();
113
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
103 114
   });
104 115
 
105 116
   it("arrow", () => {
@@ -132,6 +143,9 @@ describe("add element to the scene when pointer dragging long enough", () => {
132 143
     expect(element.points.length).toEqual(2);
133 144
     expect(element.points[0]).toEqual([0, 0]);
134 145
     expect(element.points[1]).toEqual([30, 50]); // (60 - 30, 70 - 20)
146
+
147
+    expect(h.elements.length).toMatchSnapshot();
148
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
135 149
   });
136 150
 
137 151
   it("line", () => {
@@ -164,6 +178,8 @@ describe("add element to the scene when pointer dragging long enough", () => {
164 178
     expect(element.points.length).toEqual(2);
165 179
     expect(element.points[0]).toEqual([0, 0]);
166 180
     expect(element.points[1]).toEqual([30, 50]); // (60 - 30, 70 - 20)
181
+
182
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
167 183
   });
168 184
 });
169 185
 

+ 6
- 0
src/tests/move.test.tsx Datei anzeigen

@@ -3,6 +3,7 @@ import ReactDOM from "react-dom";
3 3
 import { render, fireEvent } from "./test-utils";
4 4
 import { App } from "../components/App";
5 5
 import * as Renderer from "../renderer/renderScene";
6
+import { reseed } from "../random";
6 7
 
7 8
 // Unmount ReactDOM from root
8 9
 ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -11,6 +12,7 @@ const renderScene = jest.spyOn(Renderer, "renderScene");
11 12
 beforeEach(() => {
12 13
   localStorage.clear();
13 14
   renderScene.mockClear();
15
+  reseed(7);
14 16
 });
15 17
 
16 18
 const { h } = window;
@@ -45,6 +47,8 @@ describe("move element", () => {
45 47
     expect(h.state.selectionElement).toBeNull();
46 48
     expect(h.elements.length).toEqual(1);
47 49
     expect([h.elements[0].x, h.elements[0].y]).toEqual([0, 40]);
50
+
51
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
48 52
   });
49 53
 });
50 54
 
@@ -81,5 +85,7 @@ describe("duplicate element on move when ALT is clicked", () => {
81 85
     // previous element should stay intact
82 86
     expect([h.elements[0].x, h.elements[0].y]).toEqual([30, 20]);
83 87
     expect([h.elements[1].x, h.elements[1].y]).toEqual([0, 40]);
88
+
89
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
84 90
   });
85 91
 });

+ 6
- 0
src/tests/multiPointCreate.test.tsx Datei anzeigen

@@ -5,6 +5,7 @@ import { App } from "../components/App";
5 5
 import * as Renderer from "../renderer/renderScene";
6 6
 import { KEYS } from "../keys";
7 7
 import { ExcalidrawLinearElement } from "../element/types";
8
+import { reseed } from "../random";
8 9
 
9 10
 // Unmount ReactDOM from root
10 11
 ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -13,6 +14,7 @@ const renderScene = jest.spyOn(Renderer, "renderScene");
13 14
 beforeEach(() => {
14 15
   localStorage.clear();
15 16
   renderScene.mockClear();
17
+  reseed(7);
16 18
 });
17 19
 
18 20
 const { h } = window;
@@ -99,6 +101,8 @@ describe("multi point mode in linear elements", () => {
99 101
       [20, 30],
100 102
       [70, 110],
101 103
     ]);
104
+
105
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
102 106
   });
103 107
 
104 108
   it("line", () => {
@@ -138,5 +142,7 @@ describe("multi point mode in linear elements", () => {
138 142
       [20, 30],
139 143
       [70, 110],
140 144
     ]);
145
+
146
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
141 147
   });
142 148
 });

+ 12
- 10
src/tests/queries/toolQueries.ts Datei anzeigen

@@ -1,16 +1,18 @@
1 1
 import { queries, buildQueries } from "@testing-library/react";
2 2
 
3
-const _getAllByToolName = (container: HTMLElement, tool: string) => {
4
-  const toolMap: { [propKey: string]: string } = {
5
-    selection: "Selection — S, 1",
6
-    rectangle: "Rectangle — R, 2",
7
-    diamond: "Diamond — D, 3",
8
-    ellipse: "Ellipse — E, 4",
9
-    arrow: "Arrow — A, 5",
10
-    line: "Line — L, 6",
11
-  };
3
+const toolMap = {
4
+  selection: "Selection — S, 1",
5
+  rectangle: "Rectangle — R, 2",
6
+  diamond: "Diamond — D, 3",
7
+  ellipse: "Ellipse — E, 4",
8
+  arrow: "Arrow — A, 5",
9
+  line: "Line — L, 6",
10
+};
12 11
 
13
-  const toolTitle = toolMap[tool as string];
12
+export type ToolName = keyof typeof toolMap;
13
+
14
+const _getAllByToolName = (container: HTMLElement, tool: string) => {
15
+  const toolTitle = toolMap[tool as ToolName];
14 16
   return queries.getAllByTitle(container, toolTitle);
15 17
 };
16 18
 

+ 564
- 0
src/tests/regressionTests.test.tsx Datei anzeigen

@@ -0,0 +1,564 @@
1
+import { reseed } from "../random";
2
+import React from "react";
3
+import ReactDOM from "react-dom";
4
+import * as Renderer from "../renderer/renderScene";
5
+import { render, fireEvent } from "./test-utils";
6
+import { App } from "../components/App";
7
+import { ToolName } from "./queries/toolQueries";
8
+import { KEYS, Key } from "../keys";
9
+import { setDateTimeForTests } from "../utils";
10
+import { ExcalidrawElement } from "../element/types";
11
+import { handlerRectangles } from "../element";
12
+
13
+const { h } = window;
14
+
15
+const renderScene = jest.spyOn(Renderer, "renderScene");
16
+let getByToolName: (name: string) => HTMLElement = null!;
17
+let canvas: HTMLCanvasElement = null!;
18
+
19
+function clickTool(toolName: ToolName) {
20
+  fireEvent.click(getByToolName(toolName));
21
+}
22
+
23
+let lastClientX = 0;
24
+let lastClientY = 0;
25
+let pointerType: "mouse" | "pen" | "touch" = "mouse";
26
+
27
+function pointerDown(
28
+  clientX: number = lastClientX,
29
+  clientY: number = lastClientY,
30
+  altKey: boolean = false,
31
+  shiftKey: boolean = false,
32
+) {
33
+  lastClientX = clientX;
34
+  lastClientY = clientY;
35
+  fireEvent.pointerDown(canvas, {
36
+    clientX,
37
+    clientY,
38
+    altKey,
39
+    shiftKey,
40
+    pointerId: 1,
41
+    pointerType,
42
+  });
43
+}
44
+
45
+function pointer2Down(clientX: number, clientY: number) {
46
+  fireEvent.pointerDown(canvas, {
47
+    clientX,
48
+    clientY,
49
+    pointerId: 2,
50
+    pointerType,
51
+  });
52
+}
53
+
54
+function pointer2Move(clientX: number, clientY: number) {
55
+  fireEvent.pointerMove(canvas, {
56
+    clientX,
57
+    clientY,
58
+    pointerId: 2,
59
+    pointerType,
60
+  });
61
+}
62
+
63
+function pointer2Up(clientX: number, clientY: number) {
64
+  fireEvent.pointerUp(canvas, {
65
+    clientX,
66
+    clientY,
67
+    pointerId: 2,
68
+    pointerType,
69
+  });
70
+}
71
+
72
+function pointerMove(
73
+  clientX: number = lastClientX,
74
+  clientY: number = lastClientY,
75
+  altKey: boolean = false,
76
+  shiftKey: boolean = false,
77
+) {
78
+  lastClientX = clientX;
79
+  lastClientY = clientY;
80
+  fireEvent.pointerMove(canvas, {
81
+    clientX,
82
+    clientY,
83
+    altKey,
84
+    shiftKey,
85
+    pointerId: 1,
86
+    pointerType,
87
+  });
88
+}
89
+
90
+function pointerUp(
91
+  clientX: number = lastClientX,
92
+  clientY: number = lastClientY,
93
+  altKey: boolean = false,
94
+  shiftKey: boolean = false,
95
+) {
96
+  lastClientX = clientX;
97
+  lastClientY = clientY;
98
+  fireEvent.pointerUp(canvas, { pointerId: 1, pointerType, shiftKey, altKey });
99
+}
100
+
101
+function hotkeyDown(key: Key) {
102
+  fireEvent.keyDown(document, { key: KEYS[key] });
103
+}
104
+
105
+function hotkeyUp(key: Key) {
106
+  fireEvent.keyUp(document, {
107
+    key: KEYS[key],
108
+  });
109
+}
110
+
111
+function keyDown(
112
+  key: string,
113
+  ctrlKey: boolean = false,
114
+  shiftKey: boolean = false,
115
+) {
116
+  fireEvent.keyDown(document, { key, ctrlKey, shiftKey });
117
+}
118
+
119
+function keyUp(
120
+  key: string,
121
+  ctrlKey: boolean = false,
122
+  shiftKey: boolean = false,
123
+) {
124
+  fireEvent.keyUp(document, {
125
+    key,
126
+    ctrlKey,
127
+    shiftKey,
128
+  });
129
+}
130
+
131
+function hotkeyPress(key: Key) {
132
+  hotkeyDown(key);
133
+  hotkeyUp(key);
134
+}
135
+
136
+function keyPress(
137
+  key: string,
138
+  ctrlKey: boolean = false,
139
+  shiftKey: boolean = false,
140
+) {
141
+  keyDown(key, ctrlKey, shiftKey);
142
+  keyUp(key, ctrlKey, shiftKey);
143
+}
144
+
145
+function clickLabeledElement(label: string) {
146
+  const element = document.querySelector(`[aria-label='${label}']`);
147
+  if (!element) {
148
+    throw new Error(`No labeled element found: ${label}`);
149
+  }
150
+  fireEvent.click(element);
151
+}
152
+
153
+function getSelectedElement(): ExcalidrawElement {
154
+  const selectedElements = h.elements.filter(
155
+    (element) => h.state.selectedElementIds[element.id],
156
+  );
157
+  if (selectedElements.length !== 1) {
158
+    throw new Error(
159
+      `expected 1 selected element; got ${selectedElements.length}`,
160
+    );
161
+  }
162
+  return selectedElements[0];
163
+}
164
+
165
+function getResizeHandles() {
166
+  const rects = handlerRectangles(
167
+    getSelectedElement(),
168
+    h.state.zoom,
169
+    pointerType,
170
+  );
171
+
172
+  const rv: { [K in keyof typeof rects]: [number, number] } = {} as any;
173
+
174
+  for (const handlePos in rects) {
175
+    const [x, y, width, height] = rects[handlePos as keyof typeof rects];
176
+
177
+    rv[handlePos as keyof typeof rects] = [x + width / 2, y + height / 2];
178
+  }
179
+
180
+  return rv;
181
+}
182
+
183
+/**
184
+ * This is always called at the end of your test, so usually you don't need to call it.
185
+ * However, if you have a long test, you might want to call it during the test so it's easier
186
+ * to debug where a test failure came from.
187
+ */
188
+function checkpoint(name: string) {
189
+  expect(renderScene.mock.calls.length).toMatchSnapshot(
190
+    `[${name}] number of renders`,
191
+  );
192
+  expect(h.state).toMatchSnapshot(`[${name}] appState`);
193
+  expect(h.history.getSnapshotForTest()).toMatchSnapshot(`[${name}] history`);
194
+  expect(h.elements.length).toMatchSnapshot(`[${name}] number of elements`);
195
+  h.elements.forEach((element, i) =>
196
+    expect(element).toMatchSnapshot(`[${name}] element ${i}`),
197
+  );
198
+}
199
+
200
+beforeEach(() => {
201
+  // Unmount ReactDOM from root
202
+  ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
203
+
204
+  localStorage.clear();
205
+  renderScene.mockClear();
206
+  h.history.clear();
207
+  reseed(7);
208
+  setDateTimeForTests("201933152653");
209
+  pointerType = "mouse";
210
+
211
+  const renderResult = render(<App />);
212
+
213
+  getByToolName = renderResult.getByToolName;
214
+  canvas = renderResult.container.querySelector("canvas")!;
215
+});
216
+
217
+afterEach(() => {
218
+  checkpoint("end of test");
219
+});
220
+
221
+describe("regression tests", () => {
222
+  it("draw every type of shape", () => {
223
+    clickTool("rectangle");
224
+    pointerDown(10, 10);
225
+    pointerMove(20, 20);
226
+    pointerUp();
227
+
228
+    clickTool("diamond");
229
+    pointerDown(30, 10);
230
+    pointerMove(40, 20);
231
+    pointerUp();
232
+
233
+    clickTool("ellipse");
234
+    pointerDown(50, 10);
235
+    pointerMove(60, 20);
236
+    pointerUp();
237
+
238
+    clickTool("arrow");
239
+    pointerDown(70, 10);
240
+    pointerMove(80, 20);
241
+    pointerUp();
242
+
243
+    clickTool("line");
244
+    pointerDown(90, 10);
245
+    pointerMove(100, 20);
246
+    pointerUp();
247
+
248
+    clickTool("arrow");
249
+    pointerDown(10, 30);
250
+    pointerUp();
251
+    pointerMove(20, 40);
252
+    pointerUp();
253
+    pointerMove(10, 50);
254
+    pointerUp();
255
+    hotkeyPress("ENTER");
256
+
257
+    clickTool("line");
258
+    pointerDown(30, 30);
259
+    pointerUp();
260
+    pointerMove(40, 40);
261
+    pointerUp();
262
+    pointerMove(30, 50);
263
+    pointerUp();
264
+    hotkeyPress("ENTER");
265
+  });
266
+
267
+  it("click to select a shape", () => {
268
+    clickTool("rectangle");
269
+    pointerDown(10, 10);
270
+    pointerMove(20, 20);
271
+    pointerUp();
272
+
273
+    clickTool("rectangle");
274
+    pointerDown(30, 10);
275
+    pointerMove(40, 20);
276
+    pointerUp();
277
+
278
+    const prevSelectedId = getSelectedElement().id;
279
+    pointerDown(10, 10);
280
+    pointerUp();
281
+    expect(getSelectedElement().id).not.toEqual(prevSelectedId);
282
+  });
283
+
284
+  for (const [keys, shape] of [
285
+    ["2r", "rectangle"],
286
+    ["3d", "diamond"],
287
+    ["4e", "ellipse"],
288
+    ["5a", "arrow"],
289
+    ["6l", "line"],
290
+  ] as [string, ExcalidrawElement["type"]][]) {
291
+    for (const key of keys) {
292
+      it(`hotkey ${key} selects ${shape} tool`, () => {
293
+        keyPress(key);
294
+
295
+        pointerDown(10, 10);
296
+        pointerMove(20, 20);
297
+        pointerUp();
298
+
299
+        expect(getSelectedElement().type).toBe(shape);
300
+      });
301
+    }
302
+  }
303
+
304
+  it("change the properties of a shape", () => {
305
+    clickTool("rectangle");
306
+    pointerDown(10, 10);
307
+    pointerMove(20, 20);
308
+    pointerUp();
309
+
310
+    clickLabeledElement("Background");
311
+    clickLabeledElement("#fa5252");
312
+    clickLabeledElement("Stroke");
313
+    clickLabeledElement("#5f3dc4");
314
+    expect(getSelectedElement().backgroundColor).toBe("#fa5252");
315
+    expect(getSelectedElement().strokeColor).toBe("#5f3dc4");
316
+  });
317
+
318
+  it("resize an element, trying every resize handle", () => {
319
+    clickTool("rectangle");
320
+    pointerDown(10, 10);
321
+    pointerMove(20, 20);
322
+    pointerUp();
323
+
324
+    const resizeHandles = getResizeHandles();
325
+    for (const handlePos in resizeHandles) {
326
+      const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles];
327
+      const { width: prevWidth, height: prevHeight } = getSelectedElement();
328
+      pointerDown(x, y);
329
+      pointerMove(x - 5, y - 5);
330
+      pointerUp();
331
+      const {
332
+        width: nextWidthNegative,
333
+        height: nextHeightNegative,
334
+      } = getSelectedElement();
335
+      expect(
336
+        prevWidth !== nextWidthNegative || prevHeight !== nextHeightNegative,
337
+      ).toBeTruthy();
338
+      checkpoint(`resize handle ${handlePos} (-5, -5)`);
339
+
340
+      pointerDown();
341
+      pointerMove(x, y);
342
+      pointerUp();
343
+      const { width, height } = getSelectedElement();
344
+      expect(width).toBe(prevWidth);
345
+      expect(height).toBe(prevHeight);
346
+      checkpoint(`unresize handle ${handlePos} (-5, -5)`);
347
+
348
+      pointerDown(x, y);
349
+      pointerMove(x + 5, y + 5);
350
+      pointerUp();
351
+      const {
352
+        width: nextWidthPositive,
353
+        height: nextHeightPositive,
354
+      } = getSelectedElement();
355
+      expect(
356
+        prevWidth !== nextWidthPositive || prevHeight !== nextHeightPositive,
357
+      ).toBeTruthy();
358
+      checkpoint(`resize handle ${handlePos} (+5, +5)`);
359
+
360
+      pointerDown();
361
+      pointerMove(x, y);
362
+      pointerUp();
363
+      const { width: finalWidth, height: finalHeight } = getSelectedElement();
364
+      expect(finalWidth).toBe(prevWidth);
365
+      expect(finalHeight).toBe(prevHeight);
366
+
367
+      checkpoint(`unresize handle ${handlePos} (+5, +5)`);
368
+    }
369
+  });
370
+
371
+  it("click on an element and drag it", () => {
372
+    clickTool("rectangle");
373
+    pointerDown(10, 10);
374
+    pointerMove(20, 20);
375
+    pointerUp();
376
+
377
+    const { x: prevX, y: prevY } = getSelectedElement();
378
+    pointerDown(10, 10);
379
+    pointerMove(20, 20);
380
+    pointerUp();
381
+
382
+    const { x: nextX, y: nextY } = getSelectedElement();
383
+    expect(nextX).toBeGreaterThan(prevX);
384
+    expect(nextY).toBeGreaterThan(prevY);
385
+
386
+    checkpoint("dragged");
387
+
388
+    pointerDown();
389
+    pointerMove(10, 10);
390
+    pointerUp();
391
+
392
+    const { x, y } = getSelectedElement();
393
+    expect(x).toBe(prevX);
394
+    expect(y).toBe(prevY);
395
+  });
396
+
397
+  it("alt-drag duplicates an element", () => {
398
+    clickTool("rectangle");
399
+    pointerDown(10, 10);
400
+    pointerMove(20, 20);
401
+    pointerUp();
402
+
403
+    expect(
404
+      h.elements.filter((element) => element.type === "rectangle").length,
405
+    ).toBe(1);
406
+    pointerDown(10, 10, true);
407
+    pointerMove(20, 20, true);
408
+    pointerUp(20, 20, true);
409
+    expect(
410
+      h.elements.filter((element) => element.type === "rectangle").length,
411
+    ).toBe(2);
412
+  });
413
+
414
+  it("click-drag to select a group", () => {
415
+    clickTool("rectangle");
416
+    pointerDown(10, 10);
417
+    pointerMove(20, 20);
418
+    pointerUp();
419
+
420
+    clickTool("rectangle");
421
+    pointerDown(30, 10);
422
+    pointerMove(40, 20);
423
+    pointerUp();
424
+
425
+    clickTool("rectangle");
426
+    pointerDown(50, 10);
427
+    pointerMove(60, 20);
428
+    pointerUp();
429
+
430
+    pointerDown(0, 0);
431
+    pointerMove(45, 25);
432
+    pointerUp();
433
+
434
+    expect(
435
+      h.elements.filter((element) => h.state.selectedElementIds[element.id])
436
+        .length,
437
+    ).toBe(2);
438
+  });
439
+
440
+  it("shift-click to select a group, then drag", () => {
441
+    clickTool("rectangle");
442
+    pointerDown(10, 10);
443
+    pointerMove(20, 20);
444
+    pointerUp();
445
+
446
+    clickTool("rectangle");
447
+    pointerDown(30, 10);
448
+    pointerMove(40, 20);
449
+    pointerUp();
450
+
451
+    const prevRectsXY = h.elements
452
+      .filter((element) => element.type === "rectangle")
453
+      .map((element) => ({ x: element.x, y: element.y }));
454
+    pointerDown(10, 10);
455
+    pointerUp();
456
+    pointerDown(30, 10, false, true);
457
+    pointerUp();
458
+    pointerDown(30, 10);
459
+    pointerMove(40, 20);
460
+    pointerUp();
461
+    h.elements
462
+      .filter((element) => element.type === "rectangle")
463
+      .forEach((element, i) => {
464
+        expect(element.x).toBeGreaterThan(prevRectsXY[i].x);
465
+        expect(element.y).toBeGreaterThan(prevRectsXY[i].y);
466
+      });
467
+  });
468
+
469
+  it("pinch-to-zoom works", () => {
470
+    expect(h.state.zoom).toBe(1);
471
+    pointerType = "touch";
472
+    pointerDown(50, 50);
473
+    pointer2Down(60, 50);
474
+    pointerMove(40, 50);
475
+    pointer2Move(60, 50);
476
+    expect(h.state.zoom).toBeGreaterThan(1);
477
+    const zoomed = h.state.zoom;
478
+    pointerMove(45, 50);
479
+    pointer2Move(55, 50);
480
+    expect(h.state.zoom).toBeLessThan(zoomed);
481
+    pointerUp(45, 50);
482
+    pointer2Up(55, 50);
483
+  });
484
+
485
+  it("two-finger scroll works", () => {
486
+    const startScrollY = h.state.scrollY;
487
+    pointerDown(50, 50);
488
+    pointer2Down(60, 50);
489
+    pointerMove(50, 40);
490
+    pointer2Move(60, 40);
491
+    pointerUp(50, 40);
492
+    pointer2Up(60, 40);
493
+    expect(h.state.scrollY).toBeLessThan(startScrollY);
494
+
495
+    const startScrollX = h.state.scrollX;
496
+    pointerDown(50, 50);
497
+    pointer2Down(50, 60);
498
+    pointerMove(60, 50);
499
+    pointer2Move(60, 60);
500
+    pointerUp(60, 50);
501
+    pointer2Up(60, 60);
502
+    expect(h.state.scrollX).toBeGreaterThan(startScrollX);
503
+  });
504
+
505
+  it("spacebar + drag scrolls the canvas", () => {
506
+    const { scrollX: startScrollX, scrollY: startScrollY } = h.state;
507
+    hotkeyDown("SPACE");
508
+    pointerDown(50, 50);
509
+    pointerMove(60, 60);
510
+    pointerUp();
511
+    hotkeyUp("SPACE");
512
+    const { scrollX, scrollY } = h.state;
513
+    expect(scrollX).not.toEqual(startScrollX);
514
+    expect(scrollY).not.toEqual(startScrollY);
515
+  });
516
+
517
+  it("arrow keys", () => {
518
+    clickTool("rectangle");
519
+    pointerDown(10, 10);
520
+    pointerMove(20, 20);
521
+    pointerUp();
522
+    hotkeyPress("ARROW_LEFT");
523
+    hotkeyPress("ARROW_LEFT");
524
+    hotkeyPress("ARROW_RIGHT");
525
+    hotkeyPress("ARROW_UP");
526
+    hotkeyPress("ARROW_UP");
527
+    hotkeyPress("ARROW_DOWN");
528
+  });
529
+
530
+  it("undo/redo drawing an element", () => {
531
+    clickTool("rectangle");
532
+    pointerDown(10, 10);
533
+    pointerMove(20, 20);
534
+    pointerUp();
535
+
536
+    clickTool("rectangle");
537
+    pointerDown(30, 10);
538
+    pointerMove(40, 20);
539
+    pointerUp();
540
+
541
+    clickTool("rectangle");
542
+    pointerDown(50, 10);
543
+    pointerMove(60, 20);
544
+    pointerUp();
545
+
546
+    expect(h.elements.filter((element) => !element.isDeleted).length).toBe(3);
547
+    keyPress("z", true);
548
+    expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
549
+    keyPress("z", true);
550
+    expect(h.elements.filter((element) => !element.isDeleted).length).toBe(1);
551
+    keyPress("z", true, true);
552
+    expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
553
+  });
554
+
555
+  it("zoom hotkeys", () => {
556
+    expect(h.state.zoom).toBe(1);
557
+    fireEvent.keyDown(document, { code: "Equal", ctrlKey: true });
558
+    fireEvent.keyUp(document, { code: "Equal", ctrlKey: true });
559
+    expect(h.state.zoom).toBeGreaterThan(1);
560
+    fireEvent.keyDown(document, { code: "Minus", ctrlKey: true });
561
+    fireEvent.keyUp(document, { code: "Minus", ctrlKey: true });
562
+    expect(h.state.zoom).toBe(1);
563
+  });
564
+});

+ 6
- 0
src/tests/resize.test.tsx Datei anzeigen

@@ -3,6 +3,7 @@ import ReactDOM from "react-dom";
3 3
 import { render, fireEvent } from "./test-utils";
4 4
 import { App } from "../components/App";
5 5
 import * as Renderer from "../renderer/renderScene";
6
+import { reseed } from "../random";
6 7
 
7 8
 // Unmount ReactDOM from root
8 9
 ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -11,6 +12,7 @@ const renderScene = jest.spyOn(Renderer, "renderScene");
11 12
 beforeEach(() => {
12 13
   localStorage.clear();
13 14
   renderScene.mockClear();
15
+  reseed(7);
14 16
 });
15 17
 
16 18
 const { h } = window;
@@ -53,6 +55,8 @@ describe("resize element", () => {
53 55
     expect(h.elements.length).toEqual(1);
54 56
     expect([h.elements[0].x, h.elements[0].y]).toEqual([29, 47]);
55 57
     expect([h.elements[0].width, h.elements[0].height]).toEqual([30, 50]);
58
+
59
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
56 60
   });
57 61
 });
58 62
 
@@ -94,5 +98,7 @@ describe("resize element with aspect ratio when SHIFT is clicked", () => {
94 98
     expect(h.elements.length).toEqual(1);
95 99
     expect([h.elements[0].x, h.elements[0].y]).toEqual([29, 47]);
96 100
     expect([h.elements[0].width, h.elements[0].height]).toEqual([30, 50]);
101
+
102
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
97 103
   });
98 104
 });

+ 11
- 0
src/tests/selection.test.tsx Datei anzeigen

@@ -4,6 +4,7 @@ import { render, fireEvent } from "./test-utils";
4 4
 import { App } from "../components/App";
5 5
 import * as Renderer from "../renderer/renderScene";
6 6
 import { KEYS } from "../keys";
7
+import { reseed } from "../random";
7 8
 
8 9
 // Unmount ReactDOM from root
9 10
 ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -12,6 +13,7 @@ const renderScene = jest.spyOn(Renderer, "renderScene");
12 13
 beforeEach(() => {
13 14
   localStorage.clear();
14 15
   renderScene.mockClear();
16
+  reseed(7);
15 17
 });
16 18
 
17 19
 const { h } = window;
@@ -98,6 +100,8 @@ describe("select single element on the scene", () => {
98 100
     expect(h.state.selectionElement).toBeNull();
99 101
     expect(h.elements.length).toEqual(1);
100 102
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
103
+
104
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
101 105
   });
102 106
 
103 107
   it("diamond", () => {
@@ -123,6 +127,8 @@ describe("select single element on the scene", () => {
123 127
     expect(h.state.selectionElement).toBeNull();
124 128
     expect(h.elements.length).toEqual(1);
125 129
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
130
+
131
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
126 132
   });
127 133
 
128 134
   it("ellipse", () => {
@@ -148,6 +154,8 @@ describe("select single element on the scene", () => {
148 154
     expect(h.state.selectionElement).toBeNull();
149 155
     expect(h.elements.length).toEqual(1);
150 156
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
157
+
158
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
151 159
   });
152 160
 
153 161
   it("arrow", () => {
@@ -186,6 +194,7 @@ describe("select single element on the scene", () => {
186 194
     expect(h.state.selectionElement).toBeNull();
187 195
     expect(h.elements.length).toEqual(1);
188 196
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
197
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
189 198
   });
190 199
 
191 200
   it("arrow escape", () => {
@@ -224,5 +233,7 @@ describe("select single element on the scene", () => {
224 233
     expect(h.state.selectionElement).toBeNull();
225 234
     expect(h.elements.length).toEqual(1);
226 235
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
236
+
237
+    h.elements.forEach((element) => expect(element).toMatchSnapshot());
227 238
   });
228 239
 });

+ 10
- 0
src/utils.ts Datei anzeigen

@@ -3,7 +3,17 @@ import { getZoomOrigin } from "./scene";
3 3
 
4 4
 export const SVG_NS = "http://www.w3.org/2000/svg";
5 5
 
6
+let mockDateTime: string | null = null;
7
+
8
+export function setDateTimeForTests(dateTime: string) {
9
+  mockDateTime = dateTime;
10
+}
11
+
6 12
 export function getDateTime() {
13
+  if (mockDateTime) {
14
+    return mockDateTime;
15
+  }
16
+
7 17
   const date = new Date();
8 18
   const year = date.getFullYear();
9 19
   const month = date.getMonth() + 1;

Laden…
Abbrechen
Speichern