瀏覽代碼

Tests for groups, more test utils (#1669)

vanilla_orig
Pete Hunt 5 年之前
父節點
當前提交
56f8bc092d
沒有連結到貢獻者的電子郵件帳戶。
共有 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,6 +1,5 @@
1 1
 import { KEYS } from "../keys";
2 2
 import { register } from "./register";
3
-import nanoid from "nanoid";
4 3
 import { newElementWith } from "../element/mutateElement";
5 4
 import { getSelectedElements } from "../scene";
6 5
 import {
@@ -13,6 +12,7 @@ import {
13 12
   isElementInGroup,
14 13
 } from "../groups";
15 14
 import { getNonDeletedElements } from "../element";
15
+import { randomId } from "../random";
16 16
 
17 17
 export const actionGroup = register({
18 18
   name: "group",
@@ -46,7 +46,7 @@ export const actionGroup = register({
46 46
         return { appState, elements, commitToHistory: false };
47 47
       }
48 48
     }
49
-    const newGroupId = nanoid();
49
+    const newGroupId = randomId();
50 50
     const updatedElements = elements.map((element) => {
51 51
       if (!appState.selectedElementIds[element.id]) {
52 52
         return element;

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

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

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


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

@@ -20,111 +20,66 @@ const clickTool = (toolName: ToolName) => {
20 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 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 76
   fireEvent.keyUp(document, {
125 77
     key,
126 78
     ctrlKey,
127 79
     shiftKey,
80
+    altKey,
81
+    keyCode: key.toUpperCase().charCodeAt(0),
82
+    which: key.toUpperCase().charCodeAt(0),
128 83
   });
129 84
 };
130 85
 
@@ -133,15 +88,80 @@ const hotkeyPress = (key: Key) => {
133 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 165
 const clickLabeledElement = (label: string) => {
146 166
   const element = document.querySelector(`[aria-label='${label}']`);
147 167
   if (!element) {
@@ -150,10 +170,12 @@ const clickLabeledElement = (label: string) => {
150 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 177
 const getSelectedElement = (): ExcalidrawElement => {
154
-  const selectedElements = h.elements.filter(
155
-    (element) => h.state.selectedElementIds[element.id],
156
-  );
178
+  const selectedElements = getSelectedElements();
157 179
   if (selectedElements.length !== 1) {
158 180
     throw new Error(
159 181
       `expected 1 selected element; got ${selectedElements.length}`,
@@ -168,7 +190,7 @@ function getStateHistory() {
168 190
 }
169 191
 
170 192
 type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
171
-const getResizeHandles = () => {
193
+const getResizeHandles = (pointerType: "mouse" | "touch" | "pen") => {
172 194
   const rects = handlerRectangles(
173 195
     getSelectedElement(),
174 196
     h.state.zoom,
@@ -214,7 +236,11 @@ beforeEach(() => {
214 236
   h.history.clear();
215 237
   reseed(7);
216 238
   setDateTimeForTests("201933152653");
217
-  pointerType = "mouse";
239
+
240
+  mouse.reset();
241
+  finger1.reset();
242
+  finger2.reset();
243
+  altKey = ctrlKey = shiftKey = false;
218 244
 
219 245
   const renderResult = render(<App />);
220 246
 
@@ -229,68 +255,68 @@ afterEach(() => {
229 255
 describe("regression tests", () => {
230 256
   it("draw every type of shape", () => {
231 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 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 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 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 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 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 281
     hotkeyPress("ENTER");
264 282
 
265 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 287
     hotkeyPress("ENTER");
273 288
 
274 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 305
   it("click to select a shape", () => {
281 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 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 316
     const prevSelectedId = getSelectedElement().id;
292
-    pointerDown(10, 10);
293
-    pointerUp();
317
+    mouse.restorePosition(...firstRectPos);
318
+    mouse.click();
319
+
294 320
     expect(getSelectedElement().id).not.toEqual(prevSelectedId);
295 321
   });
296 322
 
@@ -306,9 +332,8 @@ describe("regression tests", () => {
306 332
       it(`hotkey ${key} selects ${shape} tool`, () => {
307 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 338
         expect(getSelectedElement().type).toBe(shape);
314 339
       });
@@ -317,9 +342,8 @@ describe("regression tests", () => {
317 342
 
318 343
   it("change the properties of a shape", () => {
319 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 348
     clickLabeledElement("Background");
325 349
     clickLabeledElement("#fa5252");
@@ -331,18 +355,18 @@ describe("regression tests", () => {
331 355
 
332 356
   it("resize an element, trying every resize handle", () => {
333 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 362
     delete resizeHandles.rotation; // exclude rotation handle
340 363
     for (const handlePos in resizeHandles) {
341 364
       const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles];
342 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 370
       const {
347 371
         width: nextWidthNegative,
348 372
         height: nextHeightNegative,
@@ -352,17 +376,18 @@ describe("regression tests", () => {
352 376
       ).toBeTruthy();
353 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 382
       const { width, height } = getSelectedElement();
359 383
       expect(width).toBe(prevWidth);
360 384
       expect(height).toBe(prevHeight);
361 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 391
       const {
367 392
         width: nextWidthPositive,
368 393
         height: nextHeightPositive,
@@ -372,9 +397,9 @@ describe("regression tests", () => {
372 397
       ).toBeTruthy();
373 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 403
       const { width: finalWidth, height: finalHeight } = getSelectedElement();
379 404
       expect(finalWidth).toBe(prevWidth);
380 405
       expect(finalHeight).toBe(prevHeight);
@@ -385,14 +410,12 @@ describe("regression tests", () => {
385 410
 
386 411
   it("click on an element and drag it", () => {
387 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 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 420
     const { x: nextX, y: nextY } = getSelectedElement();
398 421
     expect(nextX).toBeGreaterThan(prevX);
@@ -400,9 +423,8 @@ describe("regression tests", () => {
400 423
 
401 424
     checkpoint("dragged");
402 425
 
403
-    pointerDown();
404
-    pointerMove(10, 10);
405
-    pointerUp();
426
+    mouse.down();
427
+    mouse.up(-10, -10);
406 428
 
407 429
     const { x, y } = getSelectedElement();
408 430
     expect(x).toBe(prevX);
@@ -411,16 +433,18 @@ describe("regression tests", () => {
411 433
 
412 434
   it("alt-drag duplicates an element", () => {
413 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 439
     expect(
419 440
       h.elements.filter((element) => element.type === "rectangle").length,
420 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 448
     expect(
425 449
       h.elements.filter((element) => element.type === "rectangle").length,
426 450
     ).toBe(2);
@@ -428,23 +452,23 @@ describe("regression tests", () => {
428 452
 
429 453
   it("click-drag to select a group", () => {
430 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 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 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 473
     expect(
450 474
       h.elements.filter((element) => h.state.selectedElementIds[element.id])
@@ -452,27 +476,28 @@ describe("regression tests", () => {
452 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 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 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 488
     const prevRectsXY = h.elements
467 489
       .filter((element) => element.type === "rectangle")
468 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 501
     h.elements
477 502
       .filter((element) => element.type === "rectangle")
478 503
       .forEach((element, i) => {
@@ -483,46 +508,41 @@ describe("regression tests", () => {
483 508
 
484 509
   it("pinch-to-zoom works", () => {
485 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 514
     expect(h.state.zoom).toBeGreaterThan(1);
492 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 518
     expect(h.state.zoom).toBeLessThan(zoomed);
496
-    pointerUp(45, 50);
497
-    pointer2Up(55, 50);
498 519
   });
499 520
 
500 521
   it("two-finger scroll works", () => {
501 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 528
     expect(h.state.scrollY).toBeLessThan(startScrollY);
509 529
 
510 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 538
     expect(h.state.scrollX).toBeGreaterThan(startScrollX);
518 539
   });
519 540
 
520 541
   it("spacebar + drag scrolls the canvas", () => {
521 542
     const { scrollX: startScrollX, scrollY: startScrollY } = h.state;
522 543
     hotkeyDown("SPACE");
523
-    pointerDown(50, 50);
524
-    pointerMove(60, 60);
525
-    pointerUp();
544
+    mouse.down(50, 50);
545
+    mouse.up(60, 60);
526 546
     hotkeyUp("SPACE");
527 547
     const { scrollX, scrollY } = h.state;
528 548
     expect(scrollX).not.toEqual(startScrollX);
@@ -531,46 +551,46 @@ describe("regression tests", () => {
531 551
 
532 552
   it("arrow keys", () => {
533 553
     clickTool("rectangle");
534
-    pointerDown(10, 10);
535
-    pointerMove(20, 20);
536
-    pointerUp();
554
+    mouse.down(10, 10);
555
+    mouse.up(10, 10);
537 556
     hotkeyPress("ARROW_LEFT");
538 557
     hotkeyPress("ARROW_LEFT");
539 558
     hotkeyPress("ARROW_RIGHT");
540 559
     hotkeyPress("ARROW_UP");
541 560
     hotkeyPress("ARROW_UP");
542 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 566
   it("undo/redo drawing an element", () => {
546 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 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 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 579
     hotkeyPress("ENTER");
566 580
 
567 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 586
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
571
-    keyPress("z", true);
587
+    withModifierKeys({ ctrl: true }, () => {
588
+      keyPress("z");
589
+    });
572 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 594
     expect(h.elements.filter((element) => !element.isDeleted).length).toBe(2);
575 595
   });
576 596
 
@@ -581,37 +601,48 @@ describe("regression tests", () => {
581 601
     expect(getStateHistory().length).toBe(0);
582 602
 
583 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 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 615
     expect(getStateHistory().length).toBe(2);
594 616
 
595
-    keyPress("z", true);
617
+    withModifierKeys({ ctrl: true }, () => {
618
+      keyPress("z");
619
+    });
620
+
596 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 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 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 636
     expect(getStateHistory().length).toBe(2);
610 637
 
638
+    const firstSelectedElementId = getSelectedElement().id;
639
+
611 640
     // same for clicking the element just redo-ed
612
-    pointerDown(30, 10);
613
-    pointerUp();
641
+    mouse.restorePosition(...secondElementEndPoint);
642
+    mouse.click();
614 643
     expect(getStateHistory().length).toBe(2);
644
+
645
+    expect(getSelectedElement().id).not.toEqual(firstSelectedElementId);
615 646
   });
616 647
 
617 648
   it("zoom hotkeys", () => {
@@ -635,4 +666,178 @@ describe("regression tests", () => {
635 666
     // switching to german, `hachure` label should no longer exist
636 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…
取消
儲存