瀏覽代碼

Tests for groups, more test utils (#1669)

vanilla_orig
Pete Hunt 5 年之前
父節點
當前提交
56f8bc092d
No account linked to committer's email address
共有 4 個文件被更改,包括 7391 次插入4393 次删除
  1. 2
    2
      src/actions/actionGroup.ts
  2. 1
    2
      src/element/newElement.ts
  3. 6917
    4123
      src/tests/__snapshots__/regressionTests.test.tsx.snap
  4. 471
    266
      src/tests/regressionTests.test.tsx

+ 2
- 2
src/actions/actionGroup.ts 查看文件

1
 import { KEYS } from "../keys";
1
 import { KEYS } from "../keys";
2
 import { register } from "./register";
2
 import { register } from "./register";
3
-import nanoid from "nanoid";
4
 import { newElementWith } from "../element/mutateElement";
3
 import { newElementWith } from "../element/mutateElement";
5
 import { getSelectedElements } from "../scene";
4
 import { getSelectedElements } from "../scene";
6
 import {
5
 import {
13
   isElementInGroup,
12
   isElementInGroup,
14
 } from "../groups";
13
 } from "../groups";
15
 import { getNonDeletedElements } from "../element";
14
 import { getNonDeletedElements } from "../element";
15
+import { randomId } from "../random";
16
 
16
 
17
 export const actionGroup = register({
17
 export const actionGroup = register({
18
   name: "group",
18
   name: "group",
46
         return { appState, elements, commitToHistory: false };
46
         return { appState, elements, commitToHistory: false };
47
       }
47
       }
48
     }
48
     }
49
-    const newGroupId = nanoid();
49
+    const newGroupId = randomId();
50
     const updatedElements = elements.map((element) => {
50
     const updatedElements = elements.map((element) => {
51
       if (!appState.selectedElementIds[element.id]) {
51
       if (!appState.selectedElementIds[element.id]) {
52
         return element;
52
         return element;

+ 1
- 2
src/element/newElement.ts 查看文件

11
 import { measureText, getFontString } from "../utils";
11
 import { measureText, getFontString } from "../utils";
12
 import { randomInteger, randomId } from "../random";
12
 import { randomInteger, randomId } from "../random";
13
 import { newElementWith } from "./mutateElement";
13
 import { newElementWith } from "./mutateElement";
14
-import nanoid from "nanoid";
15
 import { getNewGroupIdsForDuplication } from "../groups";
14
 import { getNewGroupIdsForDuplication } from "../groups";
16
 
15
 
17
 type ElementConstructorOpts = {
16
 type ElementConstructorOpts = {
183
     editingGroupId,
182
     editingGroupId,
184
     (groupId) => {
183
     (groupId) => {
185
       if (!groupIdMapForOperation.has(groupId)) {
184
       if (!groupIdMapForOperation.has(groupId)) {
186
-        groupIdMapForOperation.set(groupId, nanoid());
185
+        groupIdMapForOperation.set(groupId, randomId());
187
       }
186
       }
188
       return groupIdMapForOperation.get(groupId)!;
187
       return groupIdMapForOperation.get(groupId)!;
189
     },
188
     },

+ 6917
- 4123
src/tests/__snapshots__/regressionTests.test.tsx.snap
文件差異過大導致無法顯示
查看文件


+ 471
- 266
src/tests/regressionTests.test.tsx 查看文件

20
   fireEvent.click(getByToolName(toolName));
20
   fireEvent.click(getByToolName(toolName));
21
 };
21
 };
22
 
22
 
23
-let lastClientX = 0;
24
-let lastClientY = 0;
25
-let pointerType: "mouse" | "pen" | "touch" = "mouse";
26
-
27
-const 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
-const pointer2Down = (clientX: number, clientY: number) => {
46
-  fireEvent.pointerDown(canvas, {
47
-    clientX,
48
-    clientY,
49
-    pointerId: 2,
50
-    pointerType,
51
-  });
52
-};
23
+let altKey = false;
24
+let shiftKey = false;
25
+let ctrlKey = false;
26
+
27
+function withModifierKeys(
28
+  modifiers: { alt?: boolean; shift?: boolean; ctrl?: boolean },
29
+  cb: () => void,
30
+) {
31
+  const prevAltKey = altKey;
32
+  const prevShiftKey = shiftKey;
33
+  const prevCtrlKey = ctrlKey;
34
+
35
+  altKey = !!modifiers.alt;
36
+  shiftKey = !!modifiers.shift;
37
+  ctrlKey = !!modifiers.ctrl;
38
+
39
+  try {
40
+    cb();
41
+  } finally {
42
+    altKey = prevAltKey;
43
+    shiftKey = prevShiftKey;
44
+    ctrlKey = prevCtrlKey;
45
+  }
46
+}
53
 
47
 
54
-const pointer2Move = (clientX: number, clientY: number) => {
55
-  fireEvent.pointerMove(canvas, {
56
-    clientX,
57
-    clientY,
58
-    pointerId: 2,
59
-    pointerType,
60
-  });
48
+const hotkeyDown = (hotkey: Key) => {
49
+  const key = KEYS[hotkey];
50
+  if (typeof key !== "string") {
51
+    throw new Error("must provide a hotkey, not a key code");
52
+  }
53
+  keyDown(key);
61
 };
54
 };
62
 
55
 
63
-const pointer2Up = (clientX: number, clientY: number) => {
64
-  fireEvent.pointerUp(canvas, {
65
-    clientX,
66
-    clientY,
67
-    pointerId: 2,
68
-    pointerType,
69
-  });
56
+const hotkeyUp = (hotkey: Key) => {
57
+  const key = KEYS[hotkey];
58
+  if (typeof key !== "string") {
59
+    throw new Error("must provide a hotkey, not a key code");
60
+  }
61
+  keyUp(key);
70
 };
62
 };
71
 
63
 
72
-const 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,
64
+const keyDown = (key: string) => {
65
+  fireEvent.keyDown(document, {
66
+    key,
67
+    ctrlKey,
84
     shiftKey,
68
     shiftKey,
85
-    pointerId: 1,
86
-    pointerType,
87
-  });
88
-};
89
-
90
-const 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
-const hotkeyDown = (key: Key) => {
102
-  fireEvent.keyDown(document, { key: KEYS[key] });
103
-};
104
-
105
-const hotkeyUp = (key: Key) => {
106
-  fireEvent.keyUp(document, {
107
-    key: KEYS[key],
69
+    altKey,
70
+    keyCode: key.toUpperCase().charCodeAt(0),
71
+    which: key.toUpperCase().charCodeAt(0),
108
   });
72
   });
109
 };
73
 };
110
 
74
 
111
-const keyDown = (
112
-  key: string,
113
-  ctrlKey: boolean = false,
114
-  shiftKey: boolean = false,
115
-) => {
116
-  fireEvent.keyDown(document, { key, ctrlKey, shiftKey });
117
-};
118
-
119
-const keyUp = (
120
-  key: string,
121
-  ctrlKey: boolean = false,
122
-  shiftKey: boolean = false,
123
-) => {
75
+const keyUp = (key: string) => {
124
   fireEvent.keyUp(document, {
76
   fireEvent.keyUp(document, {
125
     key,
77
     key,
126
     ctrlKey,
78
     ctrlKey,
127
     shiftKey,
79
     shiftKey,
80
+    altKey,
81
+    keyCode: key.toUpperCase().charCodeAt(0),
82
+    which: key.toUpperCase().charCodeAt(0),
128
   });
83
   });
129
 };
84
 };
130
 
85
 
133
   hotkeyUp(key);
88
   hotkeyUp(key);
134
 };
89
 };
135
 
90
 
136
-const keyPress = (
137
-  key: string,
138
-  ctrlKey: boolean = false,
139
-  shiftKey: boolean = false,
140
-) => {
141
-  keyDown(key, ctrlKey, shiftKey);
142
-  keyUp(key, ctrlKey, shiftKey);
91
+const keyPress = (key: string) => {
92
+  keyDown(key);
93
+  keyUp(key);
143
 };
94
 };
144
 
95
 
96
+class Pointer {
97
+  private clientX = 0;
98
+  private clientY = 0;
99
+
100
+  constructor(
101
+    private readonly pointerType: "mouse" | "touch" | "pen",
102
+    private readonly pointerId = 1,
103
+  ) {}
104
+
105
+  reset() {
106
+    this.clientX = 0;
107
+    this.clientY = 0;
108
+  }
109
+
110
+  getPosition() {
111
+    return [this.clientX, this.clientY];
112
+  }
113
+
114
+  restorePosition(x = 0, y = 0) {
115
+    this.clientX = x;
116
+    this.clientY = y;
117
+    fireEvent.pointerMove(canvas, this.getEvent());
118
+  }
119
+
120
+  private getEvent() {
121
+    return {
122
+      clientX: this.clientX,
123
+      clientY: this.clientY,
124
+      pointerType: this.pointerType,
125
+      pointerId: this.pointerId,
126
+      altKey,
127
+      shiftKey,
128
+      ctrlKey,
129
+    };
130
+  }
131
+
132
+  move(dx: number, dy: number) {
133
+    if (dx !== 0 || dy !== 0) {
134
+      this.clientX += dx;
135
+      this.clientY += dy;
136
+      fireEvent.pointerMove(canvas, this.getEvent());
137
+    }
138
+  }
139
+
140
+  down(dx = 0, dy = 0) {
141
+    this.move(dx, dy);
142
+    fireEvent.pointerDown(canvas, this.getEvent());
143
+  }
144
+
145
+  up(dx = 0, dy = 0) {
146
+    this.move(dx, dy);
147
+    fireEvent.pointerUp(canvas, this.getEvent());
148
+  }
149
+
150
+  click(dx = 0, dy = 0) {
151
+    this.down(dx, dy);
152
+    this.up();
153
+  }
154
+
155
+  doubleClick(dx = 0, dy = 0) {
156
+    this.move(dx, dy);
157
+    fireEvent.doubleClick(canvas, this.getEvent());
158
+  }
159
+}
160
+
161
+const mouse = new Pointer("mouse");
162
+const finger1 = new Pointer("touch", 1);
163
+const finger2 = new Pointer("touch", 2);
164
+
145
 const clickLabeledElement = (label: string) => {
165
 const clickLabeledElement = (label: string) => {
146
   const element = document.querySelector(`[aria-label='${label}']`);
166
   const element = document.querySelector(`[aria-label='${label}']`);
147
   if (!element) {
167
   if (!element) {
150
   fireEvent.click(element);
170
   fireEvent.click(element);
151
 };
171
 };
152
 
172
 
173
+const getSelectedElements = (): ExcalidrawElement[] => {
174
+  return h.elements.filter((element) => h.state.selectedElementIds[element.id]);
175
+};
176
+
153
 const getSelectedElement = (): ExcalidrawElement => {
177
 const getSelectedElement = (): ExcalidrawElement => {
154
-  const selectedElements = h.elements.filter(
155
-    (element) => h.state.selectedElementIds[element.id],
156
-  );
178
+  const selectedElements = getSelectedElements();
157
   if (selectedElements.length !== 1) {
179
   if (selectedElements.length !== 1) {
158
     throw new Error(
180
     throw new Error(
159
       `expected 1 selected element; got ${selectedElements.length}`,
181
       `expected 1 selected element; got ${selectedElements.length}`,
168
 }
190
 }
169
 
191
 
170
 type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
192
 type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
171
-const getResizeHandles = () => {
193
+const getResizeHandles = (pointerType: "mouse" | "touch" | "pen") => {
172
   const rects = handlerRectangles(
194
   const rects = handlerRectangles(
173
     getSelectedElement(),
195
     getSelectedElement(),
174
     h.state.zoom,
196
     h.state.zoom,
214
   h.history.clear();
236
   h.history.clear();
215
   reseed(7);
237
   reseed(7);
216
   setDateTimeForTests("201933152653");
238
   setDateTimeForTests("201933152653");
217
-  pointerType = "mouse";
239
+
240
+  mouse.reset();
241
+  finger1.reset();
242
+  finger2.reset();
243
+  altKey = ctrlKey = shiftKey = false;
218
 
244
 
219
   const renderResult = render(<App />);
245
   const renderResult = render(<App />);
220
 
246
 
229
 describe("regression tests", () => {
255
 describe("regression tests", () => {
230
   it("draw every type of shape", () => {
256
   it("draw every type of shape", () => {
231
     clickTool("rectangle");
257
     clickTool("rectangle");
232
-    pointerDown(10, 10);
233
-    pointerMove(20, 20);
234
-    pointerUp();
258
+    mouse.down(10, 10);
259
+    mouse.up(10, 10);
235
 
260
 
236
     clickTool("diamond");
261
     clickTool("diamond");
237
-    pointerDown(30, 10);
238
-    pointerMove(40, 20);
239
-    pointerUp();
262
+    mouse.down(10, -10);
263
+    mouse.up(10, 10);
240
 
264
 
241
     clickTool("ellipse");
265
     clickTool("ellipse");
242
-    pointerDown(50, 10);
243
-    pointerMove(60, 20);
244
-    pointerUp();
266
+    mouse.down(10, -10);
267
+    mouse.up(10, 10);
245
 
268
 
246
     clickTool("arrow");
269
     clickTool("arrow");
247
-    pointerDown(70, 10);
248
-    pointerMove(80, 20);
249
-    pointerUp();
270
+    mouse.down(10, -10);
271
+    mouse.up(10, 10);
250
 
272
 
251
     clickTool("line");
273
     clickTool("line");
252
-    pointerDown(90, 10);
253
-    pointerMove(100, 20);
254
-    pointerUp();
274
+    mouse.down(10, -10);
275
+    mouse.up(10, 10);
255
 
276
 
256
     clickTool("arrow");
277
     clickTool("arrow");
257
-    pointerDown(10, 30);
258
-    pointerUp();
259
-    pointerMove(20, 40);
260
-    pointerUp();
261
-    pointerMove(10, 50);
262
-    pointerUp();
278
+    mouse.click(10, -10);
279
+    mouse.click(10, 10);
280
+    mouse.click(-10, 10);
263
     hotkeyPress("ENTER");
281
     hotkeyPress("ENTER");
264
 
282
 
265
     clickTool("line");
283
     clickTool("line");
266
-    pointerDown(30, 30);
267
-    pointerUp();
268
-    pointerMove(40, 40);
269
-    pointerUp();
270
-    pointerMove(30, 50);
271
-    pointerUp();
284
+    mouse.click(10, -20);
285
+    mouse.click(10, 10);
286
+    mouse.click(-10, 10);
272
     hotkeyPress("ENTER");
287
     hotkeyPress("ENTER");
273
 
288
 
274
     clickTool("draw");
289
     clickTool("draw");
275
-    pointerDown(30, 10);
276
-    pointerMove(40, 20);
277
-    pointerUp();
290
+    mouse.down(10, -20);
291
+    mouse.up(10, 10);
292
+
293
+    expect(h.elements.map((element) => element.type)).toEqual([
294
+      "rectangle",
295
+      "diamond",
296
+      "ellipse",
297
+      "arrow",
298
+      "line",
299
+      "arrow",
300
+      "line",
301
+      "draw",
302
+    ]);
278
   });
303
   });
279
 
304
 
280
   it("click to select a shape", () => {
305
   it("click to select a shape", () => {
281
     clickTool("rectangle");
306
     clickTool("rectangle");
282
-    pointerDown(10, 10);
283
-    pointerMove(20, 20);
284
-    pointerUp();
307
+    mouse.down(10, 10);
308
+    mouse.up(10, 10);
309
+
310
+    const firstRectPos = mouse.getPosition();
285
 
311
 
286
     clickTool("rectangle");
312
     clickTool("rectangle");
287
-    pointerDown(30, 10);
288
-    pointerMove(40, 20);
289
-    pointerUp();
313
+    mouse.down(10, -10);
314
+    mouse.up(10, 10);
290
 
315
 
291
     const prevSelectedId = getSelectedElement().id;
316
     const prevSelectedId = getSelectedElement().id;
292
-    pointerDown(10, 10);
293
-    pointerUp();
317
+    mouse.restorePosition(...firstRectPos);
318
+    mouse.click();
319
+
294
     expect(getSelectedElement().id).not.toEqual(prevSelectedId);
320
     expect(getSelectedElement().id).not.toEqual(prevSelectedId);
295
   });
321
   });
296
 
322
 
306
       it(`hotkey ${key} selects ${shape} tool`, () => {
332
       it(`hotkey ${key} selects ${shape} tool`, () => {
307
         keyPress(key);
333
         keyPress(key);
308
 
334
 
309
-        pointerDown(10, 10);
310
-        pointerMove(20, 20);
311
-        pointerUp();
335
+        mouse.down(10, 10);
336
+        mouse.up(10, 10);
312
 
337
 
313
         expect(getSelectedElement().type).toBe(shape);
338
         expect(getSelectedElement().type).toBe(shape);
314
       });
339
       });
317
 
342
 
318
   it("change the properties of a shape", () => {
343
   it("change the properties of a shape", () => {
319
     clickTool("rectangle");
344
     clickTool("rectangle");
320
-    pointerDown(10, 10);
321
-    pointerMove(20, 20);
322
-    pointerUp();
345
+    mouse.down(10, 10);
346
+    mouse.up(10, 10);
323
 
347
 
324
     clickLabeledElement("Background");
348
     clickLabeledElement("Background");
325
     clickLabeledElement("#fa5252");
349
     clickLabeledElement("#fa5252");
331
 
355
 
332
   it("resize an element, trying every resize handle", () => {
356
   it("resize an element, trying every resize handle", () => {
333
     clickTool("rectangle");
357
     clickTool("rectangle");
334
-    pointerDown(10, 10);
335
-    pointerMove(20, 20);
336
-    pointerUp();
358
+    mouse.down(10, 10);
359
+    mouse.up(10, 10);
337
 
360
 
338
-    const resizeHandles = getResizeHandles();
361
+    const resizeHandles = getResizeHandles("mouse");
339
     delete resizeHandles.rotation; // exclude rotation handle
362
     delete resizeHandles.rotation; // exclude rotation handle
340
     for (const handlePos in resizeHandles) {
363
     for (const handlePos in resizeHandles) {
341
       const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles];
364
       const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles];
342
       const { width: prevWidth, height: prevHeight } = getSelectedElement();
365
       const { width: prevWidth, height: prevHeight } = getSelectedElement();
343
-      pointerDown(x, y);
344
-      pointerMove(x - 5, y - 5);
345
-      pointerUp();
366
+      mouse.restorePosition(x, y);
367
+      mouse.down();
368
+      mouse.up(-5, -5);
369
+
346
       const {
370
       const {
347
         width: nextWidthNegative,
371
         width: nextWidthNegative,
348
         height: nextHeightNegative,
372
         height: nextHeightNegative,
352
       ).toBeTruthy();
376
       ).toBeTruthy();
353
       checkpoint(`resize handle ${handlePos} (-5, -5)`);
377
       checkpoint(`resize handle ${handlePos} (-5, -5)`);
354
 
378
 
355
-      pointerDown();
356
-      pointerMove(x, y);
357
-      pointerUp();
379
+      mouse.down();
380
+      mouse.up(5, 5);
381
+
358
       const { width, height } = getSelectedElement();
382
       const { width, height } = getSelectedElement();
359
       expect(width).toBe(prevWidth);
383
       expect(width).toBe(prevWidth);
360
       expect(height).toBe(prevHeight);
384
       expect(height).toBe(prevHeight);
361
       checkpoint(`unresize handle ${handlePos} (-5, -5)`);
385
       checkpoint(`unresize handle ${handlePos} (-5, -5)`);
362
 
386
 
363
-      pointerDown(x, y);
364
-      pointerMove(x + 5, y + 5);
365
-      pointerUp();
387
+      mouse.restorePosition(x, y);
388
+      mouse.down();
389
+      mouse.up(5, 5);
390
+
366
       const {
391
       const {
367
         width: nextWidthPositive,
392
         width: nextWidthPositive,
368
         height: nextHeightPositive,
393
         height: nextHeightPositive,
372
       ).toBeTruthy();
397
       ).toBeTruthy();
373
       checkpoint(`resize handle ${handlePos} (+5, +5)`);
398
       checkpoint(`resize handle ${handlePos} (+5, +5)`);
374
 
399
 
375
-      pointerDown();
376
-      pointerMove(x, y);
377
-      pointerUp();
400
+      mouse.down();
401
+      mouse.up(-5, -5);
402
+
378
       const { width: finalWidth, height: finalHeight } = getSelectedElement();
403
       const { width: finalWidth, height: finalHeight } = getSelectedElement();
379
       expect(finalWidth).toBe(prevWidth);
404
       expect(finalWidth).toBe(prevWidth);
380
       expect(finalHeight).toBe(prevHeight);
405
       expect(finalHeight).toBe(prevHeight);
385
 
410
 
386
   it("click on an element and drag it", () => {
411
   it("click on an element and drag it", () => {
387
     clickTool("rectangle");
412
     clickTool("rectangle");
388
-    pointerDown(10, 10);
389
-    pointerMove(20, 20);
390
-    pointerUp();
413
+    mouse.down(10, 10);
414
+    mouse.up(10, 10);
391
 
415
 
392
     const { x: prevX, y: prevY } = getSelectedElement();
416
     const { x: prevX, y: prevY } = getSelectedElement();
393
-    pointerDown(10, 10);
394
-    pointerMove(20, 20);
395
-    pointerUp();
417
+    mouse.down(-10, -10);
418
+    mouse.up(10, 10);
396
 
419
 
397
     const { x: nextX, y: nextY } = getSelectedElement();
420
     const { x: nextX, y: nextY } = getSelectedElement();
398
     expect(nextX).toBeGreaterThan(prevX);
421
     expect(nextX).toBeGreaterThan(prevX);
400
 
423
 
401
     checkpoint("dragged");
424
     checkpoint("dragged");
402
 
425
 
403
-    pointerDown();
404
-    pointerMove(10, 10);
405
-    pointerUp();
426
+    mouse.down();
427
+    mouse.up(-10, -10);
406
 
428
 
407
     const { x, y } = getSelectedElement();
429
     const { x, y } = getSelectedElement();
408
     expect(x).toBe(prevX);
430
     expect(x).toBe(prevX);
411
 
433
 
412
   it("alt-drag duplicates an element", () => {
434
   it("alt-drag duplicates an element", () => {
413
     clickTool("rectangle");
435
     clickTool("rectangle");
414
-    pointerDown(10, 10);
415
-    pointerMove(20, 20);
416
-    pointerUp();
436
+    mouse.down(10, 10);
437
+    mouse.up(10, 10);
417
 
438
 
418
     expect(
439
     expect(
419
       h.elements.filter((element) => element.type === "rectangle").length,
440
       h.elements.filter((element) => element.type === "rectangle").length,
420
     ).toBe(1);
441
     ).toBe(1);
421
-    pointerDown(10, 10, true);
422
-    pointerMove(20, 20, true);
423
-    pointerUp(20, 20, true);
442
+
443
+    withModifierKeys({ alt: true }, () => {
444
+      mouse.down(-10, -10);
445
+      mouse.up(10, 10);
446
+    });
447
+
424
     expect(
448
     expect(
425
       h.elements.filter((element) => element.type === "rectangle").length,
449
       h.elements.filter((element) => element.type === "rectangle").length,
426
     ).toBe(2);
450
     ).toBe(2);
428
 
452
 
429
   it("click-drag to select a group", () => {
453
   it("click-drag to select a group", () => {
430
     clickTool("rectangle");
454
     clickTool("rectangle");
431
-    pointerDown(10, 10);
432
-    pointerMove(20, 20);
433
-    pointerUp();
455
+    mouse.down(10, 10);
456
+    mouse.up(10, 10);
434
 
457
 
435
     clickTool("rectangle");
458
     clickTool("rectangle");
436
-    pointerDown(30, 10);
437
-    pointerMove(40, 20);
438
-    pointerUp();
459
+    mouse.down(10, -10);
460
+    mouse.up(10, 10);
461
+
462
+    const finalPosition = mouse.getPosition();
439
 
463
 
440
     clickTool("rectangle");
464
     clickTool("rectangle");
441
-    pointerDown(50, 10);
442
-    pointerMove(60, 20);
443
-    pointerUp();
465
+    mouse.down(10, -10);
466
+    mouse.up(10, 10);
444
 
467
 
445
-    pointerDown(0, 0);
446
-    pointerMove(45, 25);
447
-    pointerUp();
468
+    mouse.restorePosition(0, 0);
469
+    mouse.down();
470
+    mouse.restorePosition(...finalPosition);
471
+    mouse.up(5, 5);
448
 
472
 
449
     expect(
473
     expect(
450
       h.elements.filter((element) => h.state.selectedElementIds[element.id])
474
       h.elements.filter((element) => h.state.selectedElementIds[element.id])
452
     ).toBe(2);
476
     ).toBe(2);
453
   });
477
   });
454
 
478
 
455
-  it("shift-click to select a group, then drag", () => {
479
+  it("shift-click to multiselect, then drag", () => {
456
     clickTool("rectangle");
480
     clickTool("rectangle");
457
-    pointerDown(10, 10);
458
-    pointerMove(20, 20);
459
-    pointerUp();
481
+    mouse.down(10, 10);
482
+    mouse.up(10, 10);
460
 
483
 
461
     clickTool("rectangle");
484
     clickTool("rectangle");
462
-    pointerDown(30, 10);
463
-    pointerMove(40, 20);
464
-    pointerUp();
485
+    mouse.down(10, -10);
486
+    mouse.up(10, 10);
465
 
487
 
466
     const prevRectsXY = h.elements
488
     const prevRectsXY = h.elements
467
       .filter((element) => element.type === "rectangle")
489
       .filter((element) => element.type === "rectangle")
468
       .map((element) => ({ x: element.x, y: element.y }));
490
       .map((element) => ({ x: element.x, y: element.y }));
469
-    pointerDown(10, 10);
470
-    pointerUp();
471
-    pointerDown(30, 10, false, true);
472
-    pointerUp();
473
-    pointerDown(30, 10);
474
-    pointerMove(40, 20);
475
-    pointerUp();
491
+
492
+    mouse.reset();
493
+    mouse.click(10, 10);
494
+    withModifierKeys({ shift: true }, () => {
495
+      mouse.click(20, 0);
496
+    });
497
+
498
+    mouse.down();
499
+    mouse.up(10, 10);
500
+
476
     h.elements
501
     h.elements
477
       .filter((element) => element.type === "rectangle")
502
       .filter((element) => element.type === "rectangle")
478
       .forEach((element, i) => {
503
       .forEach((element, i) => {
483
 
508
 
484
   it("pinch-to-zoom works", () => {
509
   it("pinch-to-zoom works", () => {
485
     expect(h.state.zoom).toBe(1);
510
     expect(h.state.zoom).toBe(1);
486
-    pointerType = "touch";
487
-    pointerDown(50, 50);
488
-    pointer2Down(60, 50);
489
-    pointerMove(40, 50);
490
-    pointer2Move(60, 50);
511
+    finger1.down(50, 50);
512
+    finger2.down(60, 50);
513
+    finger1.move(-10, 0);
491
     expect(h.state.zoom).toBeGreaterThan(1);
514
     expect(h.state.zoom).toBeGreaterThan(1);
492
     const zoomed = h.state.zoom;
515
     const zoomed = h.state.zoom;
493
-    pointerMove(45, 50);
494
-    pointer2Move(55, 50);
516
+    finger1.move(5, 0);
517
+    finger2.move(-5, 0);
495
     expect(h.state.zoom).toBeLessThan(zoomed);
518
     expect(h.state.zoom).toBeLessThan(zoomed);
496
-    pointerUp(45, 50);
497
-    pointer2Up(55, 50);
498
   });
519
   });
499
 
520
 
500
   it("two-finger scroll works", () => {
521
   it("two-finger scroll works", () => {
501
     const startScrollY = h.state.scrollY;
522
     const startScrollY = h.state.scrollY;
502
-    pointerDown(50, 50);
503
-    pointer2Down(60, 50);
504
-    pointerMove(50, 40);
505
-    pointer2Move(60, 40);
506
-    pointerUp(50, 40);
507
-    pointer2Up(60, 40);
523
+    finger1.down(50, 50);
524
+    finger2.down(60, 50);
525
+
526
+    finger1.up(0, -10);
527
+    finger2.up(0, -10);
508
     expect(h.state.scrollY).toBeLessThan(startScrollY);
528
     expect(h.state.scrollY).toBeLessThan(startScrollY);
509
 
529
 
510
     const startScrollX = h.state.scrollX;
530
     const startScrollX = h.state.scrollX;
511
-    pointerDown(50, 50);
512
-    pointer2Down(50, 60);
513
-    pointerMove(60, 50);
514
-    pointer2Move(60, 60);
515
-    pointerUp(60, 50);
516
-    pointer2Up(60, 60);
531
+
532
+    finger1.restorePosition(50, 50);
533
+    finger2.restorePosition(50, 60);
534
+    finger1.down();
535
+    finger2.down();
536
+    finger1.up(10, 0);
537
+    finger2.up(10, 0);
517
     expect(h.state.scrollX).toBeGreaterThan(startScrollX);
538
     expect(h.state.scrollX).toBeGreaterThan(startScrollX);
518
   });
539
   });
519
 
540
 
520
   it("spacebar + drag scrolls the canvas", () => {
541
   it("spacebar + drag scrolls the canvas", () => {
521
     const { scrollX: startScrollX, scrollY: startScrollY } = h.state;
542
     const { scrollX: startScrollX, scrollY: startScrollY } = h.state;
522
     hotkeyDown("SPACE");
543
     hotkeyDown("SPACE");
523
-    pointerDown(50, 50);
524
-    pointerMove(60, 60);
525
-    pointerUp();
544
+    mouse.down(50, 50);
545
+    mouse.up(60, 60);
526
     hotkeyUp("SPACE");
546
     hotkeyUp("SPACE");
527
     const { scrollX, scrollY } = h.state;
547
     const { scrollX, scrollY } = h.state;
528
     expect(scrollX).not.toEqual(startScrollX);
548
     expect(scrollX).not.toEqual(startScrollX);
531
 
551
 
532
   it("arrow keys", () => {
552
   it("arrow keys", () => {
533
     clickTool("rectangle");
553
     clickTool("rectangle");
534
-    pointerDown(10, 10);
535
-    pointerMove(20, 20);
536
-    pointerUp();
554
+    mouse.down(10, 10);
555
+    mouse.up(10, 10);
537
     hotkeyPress("ARROW_LEFT");
556
     hotkeyPress("ARROW_LEFT");
538
     hotkeyPress("ARROW_LEFT");
557
     hotkeyPress("ARROW_LEFT");
539
     hotkeyPress("ARROW_RIGHT");
558
     hotkeyPress("ARROW_RIGHT");
540
     hotkeyPress("ARROW_UP");
559
     hotkeyPress("ARROW_UP");
541
     hotkeyPress("ARROW_UP");
560
     hotkeyPress("ARROW_UP");
542
     hotkeyPress("ARROW_DOWN");
561
     hotkeyPress("ARROW_DOWN");
562
+    expect(h.elements[0].x).toBe(9);
563
+    expect(h.elements[0].y).toBe(9);
543
   });
564
   });
544
 
565
 
545
   it("undo/redo drawing an element", () => {
566
   it("undo/redo drawing an element", () => {
546
     clickTool("rectangle");
567
     clickTool("rectangle");
547
-    pointerDown(10, 10);
548
-    pointerMove(20, 20);
549
-    pointerUp();
568
+    mouse.down(10, 10);
569
+    mouse.up(10, 10);
550
 
570
 
551
     clickTool("rectangle");
571
     clickTool("rectangle");
552
-    pointerDown(30, 10);
553
-    pointerMove(40, 20);
554
-    pointerUp();
572
+    mouse.down(10, -10);
573
+    mouse.up(10, 10);
555
 
574
 
556
     clickTool("arrow");
575
     clickTool("arrow");
557
-    pointerDown(10, 30);
558
-    pointerUp();
559
-    pointerMove(20, 40);
560
-    pointerDown(20, 40);
561
-    pointerUp();
562
-    pointerMove(10, 50);
563
-    pointerDown(10, 50);
564
-    pointerUp();
576
+    mouse.click(10, -10);
577
+    mouse.click(10, 10);
578
+    mouse.click(-10, 10);
565
     hotkeyPress("ENTER");
579
     hotkeyPress("ENTER");
566
 
580
 
567
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(3);
581
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(3);
568
-    keyPress("z", true); // press twice for multi arrow
569
-    keyPress("z", true);
582
+    withModifierKeys({ ctrl: true }, () => {
583
+      keyPress("z");
584
+      keyPress("z");
585
+    });
570
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
586
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
571
-    keyPress("z", true);
587
+    withModifierKeys({ ctrl: true }, () => {
588
+      keyPress("z");
589
+    });
572
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(1);
590
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(1);
573
-    keyPress("z", true, true);
591
+    withModifierKeys({ ctrl: true, shift: true }, () => {
592
+      keyPress("z");
593
+    });
574
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
594
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
575
   });
595
   });
576
 
596
 
581
     expect(getStateHistory().length).toBe(0);
601
     expect(getStateHistory().length).toBe(0);
582
 
602
 
583
     clickTool("rectangle");
603
     clickTool("rectangle");
584
-    pointerDown(10, 10);
585
-    pointerMove(20, 20);
586
-    pointerUp();
604
+    mouse.down(10, 10);
605
+    mouse.up(10, 10);
606
+
607
+    const firstElementEndPoint = mouse.getPosition();
587
 
608
 
588
     clickTool("rectangle");
609
     clickTool("rectangle");
589
-    pointerDown(30, 10);
590
-    pointerMove(40, 20);
591
-    pointerUp();
610
+    mouse.down(10, -10);
611
+    mouse.up(10, 10);
612
+
613
+    const secondElementEndPoint = mouse.getPosition();
592
 
614
 
593
     expect(getStateHistory().length).toBe(2);
615
     expect(getStateHistory().length).toBe(2);
594
 
616
 
595
-    keyPress("z", true);
617
+    withModifierKeys({ ctrl: true }, () => {
618
+      keyPress("z");
619
+    });
620
+
596
     expect(getStateHistory().length).toBe(1);
621
     expect(getStateHistory().length).toBe(1);
597
 
622
 
598
-    // clicking an element shouldn't addu to history
599
-    pointerDown(10, 10);
600
-    pointerUp();
623
+    // clicking an element shouldn't add to history
624
+    mouse.restorePosition(...firstElementEndPoint);
625
+    mouse.click();
601
     expect(getStateHistory().length).toBe(1);
626
     expect(getStateHistory().length).toBe(1);
602
 
627
 
603
-    keyPress("z", true, true);
628
+    withModifierKeys({ shift: true, ctrl: true }, () => {
629
+      keyPress("z");
630
+    });
631
+
604
     expect(getStateHistory().length).toBe(2);
632
     expect(getStateHistory().length).toBe(2);
605
 
633
 
606
-    // clicking an element shouldn't addu to history
607
-    pointerDown(10, 10);
608
-    pointerUp();
634
+    // clicking an element shouldn't add to history
635
+    mouse.click();
609
     expect(getStateHistory().length).toBe(2);
636
     expect(getStateHistory().length).toBe(2);
610
 
637
 
638
+    const firstSelectedElementId = getSelectedElement().id;
639
+
611
     // same for clicking the element just redo-ed
640
     // same for clicking the element just redo-ed
612
-    pointerDown(30, 10);
613
-    pointerUp();
641
+    mouse.restorePosition(...secondElementEndPoint);
642
+    mouse.click();
614
     expect(getStateHistory().length).toBe(2);
643
     expect(getStateHistory().length).toBe(2);
644
+
645
+    expect(getSelectedElement().id).not.toEqual(firstSelectedElementId);
615
   });
646
   });
616
 
647
 
617
   it("zoom hotkeys", () => {
648
   it("zoom hotkeys", () => {
635
     // switching to german, `hachure` label should no longer exist
666
     // switching to german, `hachure` label should no longer exist
636
     expect(screen.queryByText(/hachure/i)).toBeNull();
667
     expect(screen.queryByText(/hachure/i)).toBeNull();
637
   });
668
   });
669
+
670
+  it("make a group and duplicate it", () => {
671
+    clickTool("rectangle");
672
+    mouse.down(10, 10);
673
+    mouse.up(10, 10);
674
+
675
+    clickTool("rectangle");
676
+    mouse.down(10, -10);
677
+    mouse.up(10, 10);
678
+
679
+    clickTool("rectangle");
680
+    mouse.down(10, -10);
681
+    mouse.up(10, 10);
682
+    const end = mouse.getPosition();
683
+
684
+    mouse.reset();
685
+    mouse.down();
686
+    mouse.restorePosition(...end);
687
+    mouse.up();
688
+
689
+    expect(h.elements.length).toBe(3);
690
+    for (const element of h.elements) {
691
+      expect(element.groupIds.length).toBe(0);
692
+      expect(h.state.selectedElementIds[element.id]).toBe(true);
693
+    }
694
+
695
+    withModifierKeys({ ctrl: true }, () => {
696
+      keyPress("g");
697
+    });
698
+
699
+    for (const element of h.elements) {
700
+      expect(element.groupIds.length).toBe(1);
701
+    }
702
+
703
+    withModifierKeys({ alt: true }, () => {
704
+      mouse.restorePosition(...end);
705
+      mouse.down();
706
+      mouse.up(10, 10);
707
+    });
708
+
709
+    expect(h.elements.length).toBe(6);
710
+    const groups = new Set();
711
+    for (const element of h.elements) {
712
+      for (const groupId of element.groupIds) {
713
+        groups.add(groupId);
714
+      }
715
+    }
716
+
717
+    expect(groups.size).toBe(2);
718
+  });
719
+
720
+  it("double click to edit a group", () => {
721
+    clickTool("rectangle");
722
+    mouse.down(10, 10);
723
+    mouse.up(10, 10);
724
+
725
+    clickTool("rectangle");
726
+    mouse.down(10, -10);
727
+    mouse.up(10, 10);
728
+
729
+    clickTool("rectangle");
730
+    mouse.down(10, -10);
731
+    mouse.up(10, 10);
732
+
733
+    withModifierKeys({ ctrl: true }, () => {
734
+      keyPress("a");
735
+      keyPress("g");
736
+    });
737
+
738
+    expect(getSelectedElements().length).toBe(3);
739
+    expect(h.state.editingGroupId).toBe(null);
740
+    mouse.doubleClick();
741
+    expect(getSelectedElements().length).toBe(1);
742
+    expect(h.state.editingGroupId).not.toBe(null);
743
+  });
744
+
745
+  it("adjusts z order when grouping", () => {
746
+    const positions = [];
747
+
748
+    clickTool("rectangle");
749
+    mouse.down(10, 10);
750
+    mouse.up(10, 10);
751
+    positions.push(mouse.getPosition());
752
+
753
+    clickTool("rectangle");
754
+    mouse.down(10, -10);
755
+    mouse.up(10, 10);
756
+    positions.push(mouse.getPosition());
757
+
758
+    clickTool("rectangle");
759
+    mouse.down(10, -10);
760
+    mouse.up(10, 10);
761
+    positions.push(mouse.getPosition());
762
+
763
+    const ids = h.elements.map((element) => element.id);
764
+
765
+    mouse.restorePosition(...positions[0]);
766
+    mouse.click();
767
+    mouse.restorePosition(...positions[2]);
768
+    withModifierKeys({ shift: true }, () => {
769
+      mouse.click();
770
+    });
771
+    withModifierKeys({ ctrl: true }, () => {
772
+      keyPress("g");
773
+    });
774
+
775
+    expect(h.elements.map((element) => element.id)).toEqual([
776
+      ids[1],
777
+      ids[0],
778
+      ids[2],
779
+    ]);
780
+  });
781
+
782
+  it("supports nested groups", () => {
783
+    const positions: number[][] = [];
784
+
785
+    clickTool("rectangle");
786
+    mouse.down(10, 10);
787
+    mouse.up(10, 10);
788
+    positions.push(mouse.getPosition());
789
+
790
+    clickTool("rectangle");
791
+    mouse.down(10, -10);
792
+    mouse.up(10, 10);
793
+    positions.push(mouse.getPosition());
794
+
795
+    clickTool("rectangle");
796
+    mouse.down(10, -10);
797
+    mouse.up(10, 10);
798
+    positions.push(mouse.getPosition());
799
+
800
+    withModifierKeys({ ctrl: true }, () => {
801
+      keyPress("a");
802
+      keyPress("g");
803
+    });
804
+
805
+    mouse.doubleClick();
806
+    withModifierKeys({ shift: true }, () => {
807
+      mouse.restorePosition(...positions[0]);
808
+      mouse.click();
809
+    });
810
+    withModifierKeys({ ctrl: true }, () => {
811
+      keyPress("g");
812
+    });
813
+
814
+    const groupIds = h.elements[2].groupIds;
815
+    expect(groupIds.length).toBe(2);
816
+    expect(h.elements[1].groupIds).toEqual(groupIds);
817
+    expect(h.elements[0].groupIds).toEqual(groupIds.slice(1));
818
+
819
+    mouse.click(50, 50);
820
+    expect(getSelectedElements().length).toBe(0);
821
+    mouse.restorePosition(...positions[0]);
822
+    mouse.click();
823
+    expect(getSelectedElements().length).toBe(3);
824
+    expect(h.state.editingGroupId).toBe(null);
825
+
826
+    mouse.doubleClick();
827
+    expect(getSelectedElements().length).toBe(2);
828
+    expect(h.state.editingGroupId).toBe(groupIds[1]);
829
+
830
+    mouse.doubleClick();
831
+    expect(getSelectedElements().length).toBe(1);
832
+    expect(h.state.editingGroupId).toBe(groupIds[0]);
833
+
834
+    // click out of the group
835
+    mouse.restorePosition(...positions[1]);
836
+    mouse.click();
837
+    expect(getSelectedElements().length).toBe(0);
838
+    mouse.click();
839
+    expect(getSelectedElements().length).toBe(3);
840
+    mouse.doubleClick();
841
+    expect(getSelectedElements().length).toBe(1);
842
+  });
638
 });
843
 });

Loading…
取消
儲存