|
| 1 | +import { Rect } from '@/util/data/rect' |
| 2 | +import { Vec2 } from '@/util/data/vec2' |
| 3 | +import { expect, test } from 'vitest' |
| 4 | +import { proxyRefs, ref, type Ref } from 'vue' |
| 5 | +import { useSelection } from '../selection' |
| 6 | + |
| 7 | +function selectionWithMockData(sceneMousePos?: Ref<Vec2>) { |
| 8 | + const rects = new Map() |
| 9 | + rects.set(1, Rect.FromBounds(1, 1, 10, 10)) |
| 10 | + rects.set(2, Rect.FromBounds(20, 1, 30, 10)) |
| 11 | + rects.set(3, Rect.FromBounds(1, 20, 10, 30)) |
| 12 | + rects.set(4, Rect.FromBounds(20, 20, 30, 30)) |
| 13 | + const selection = useSelection( |
| 14 | + proxyRefs({ sceneMousePos: sceneMousePos ?? ref(Vec2.Zero), scale: 1 }), |
| 15 | + rects, |
| 16 | + 0, |
| 17 | + ) |
| 18 | + selection.setSelection(new Set([1, 2])) |
| 19 | + return selection |
| 20 | +} |
| 21 | + |
| 22 | +// TODO[ao]: We should read the modifiers from bindings.ts, but I don't know how yet. |
| 23 | + |
| 24 | +test.each` |
| 25 | + click | modifiers | expected |
| 26 | + ${1} | ${[]} | ${[1]} |
| 27 | + ${3} | ${[]} | ${[3]} |
| 28 | + ${1} | ${['Shift']} | ${[2]} |
| 29 | + ${3} | ${['Shift']} | ${[1, 2, 3]} |
| 30 | + ${1} | ${['Mod', 'Shift']} | ${[1, 2]} |
| 31 | + ${3} | ${['Mod', 'Shift']} | ${[1, 2, 3]} |
| 32 | + ${1} | ${['Mod', 'Shift']} | ${[1, 2]} |
| 33 | + ${3} | ${['Mod', 'Shift']} | ${[1, 2, 3]} |
| 34 | + ${1} | ${['Alt', 'Shift']} | ${[2]} |
| 35 | + ${3} | ${['Alt', 'Shift']} | ${[1, 2]} |
| 36 | + ${1} | ${['Mod', 'Alt', 'Shift']} | ${[2]} |
| 37 | + ${3} | ${['Mod', 'Alt', 'Shift']} | ${[1, 2, 3]} |
| 38 | +`('Selection by single click at $click with $modifiers', ({ click, modifiers, expected }) => { |
| 39 | + const selection = selectionWithMockData() |
| 40 | + // Position is zero, because this method should not depend on click position |
| 41 | + selection.handleSelectionOf(mockPointerEvent('click', Vec2.Zero, modifiers), new Set([click])) |
| 42 | + expect(Array.from(selection.selected)).toEqual(expected) |
| 43 | +}) |
| 44 | + |
| 45 | +const areas: Record<string, Rect> = { |
| 46 | + left: Rect.FromBounds(0, 0, 10, 30), |
| 47 | + right: Rect.FromBounds(20, 0, 30, 30), |
| 48 | + top: Rect.FromBounds(0, 0, 30, 10), |
| 49 | + bottom: Rect.FromBounds(0, 20, 30, 30), |
| 50 | + all: Rect.FromBounds(0, 0, 30, 30), |
| 51 | +} |
| 52 | + |
| 53 | +test.each` |
| 54 | + areaId | modifiers | expected |
| 55 | + ${'left'} | ${[]} | ${[1, 3]} |
| 56 | + ${'right'} | ${[]} | ${[2, 4]} |
| 57 | + ${'top'} | ${[]} | ${[1, 2]} |
| 58 | + ${'bottom'} | ${[]} | ${[3, 4]} |
| 59 | + ${'all'} | ${[]} | ${[1, 2, 3, 4]} |
| 60 | + ${'left'} | ${['Shift']} | ${[1, 2, 3]} |
| 61 | + ${'right'} | ${['Shift']} | ${[1, 2, 4]} |
| 62 | + ${'top'} | ${['Shift']} | ${[]} |
| 63 | + ${'bottom'} | ${['Shift']} | ${[1, 2, 3, 4]} |
| 64 | + ${'all'} | ${['Shift']} | ${[1, 2, 3, 4]} |
| 65 | + ${'left'} | ${['Mod', 'Shift']} | ${[1, 2, 3]} |
| 66 | + ${'right'} | ${['Mod', 'Shift']} | ${[1, 2, 4]} |
| 67 | + ${'top'} | ${['Mod', 'Shift']} | ${[1, 2]} |
| 68 | + ${'bottom'} | ${['Mod', 'Shift']} | ${[1, 2, 3, 4]} |
| 69 | + ${'all'} | ${['Mod', 'Shift']} | ${[1, 2, 3, 4]} |
| 70 | + ${'left'} | ${['Alt', 'Shift']} | ${[2]} |
| 71 | + ${'right'} | ${['Alt', 'Shift']} | ${[1]} |
| 72 | + ${'top'} | ${['Alt', 'Shift']} | ${[]} |
| 73 | + ${'bottom'} | ${['Alt', 'Shift']} | ${[1, 2]} |
| 74 | + ${'all'} | ${['Alt', 'Shift']} | ${[]} |
| 75 | + ${'left'} | ${['Mod', 'Alt', 'Shift']} | ${[2, 3]} |
| 76 | + ${'right'} | ${['Mod', 'Alt', 'Shift']} | ${[1, 4]} |
| 77 | + ${'top'} | ${['Mod', 'Alt', 'Shift']} | ${[]} |
| 78 | + ${'bottom'} | ${['Mod', 'Alt', 'Shift']} | ${[1, 2, 3, 4]} |
| 79 | + ${'all'} | ${['Mod', 'Alt', 'Shift']} | ${[3, 4]} |
| 80 | +`('Selection by dragging $area area with $modifiers', ({ areaId, modifiers, expected }) => { |
| 81 | + const area = areas[areaId]! |
| 82 | + const dragCase = (start: Vec2, stop: Vec2) => { |
| 83 | + const mousePos = ref(start) |
| 84 | + const selection = selectionWithMockData(mousePos) |
| 85 | + |
| 86 | + selection.events.pointerdown(mockPointerEvent('pointerdown', mousePos.value, modifiers)) |
| 87 | + selection.events.pointermove(mockPointerEvent('pointermove', mousePos.value, modifiers)) |
| 88 | + mousePos.value = stop |
| 89 | + selection.events.pointermove(mockPointerEvent('pointermove', mousePos.value, modifiers)) |
| 90 | + expect(selection.selected).toEqual(new Set(expected)) |
| 91 | + selection.events.pointerdown(mockPointerEvent('pointerup', mousePos.value, modifiers)) |
| 92 | + expect(selection.selected).toEqual(new Set(expected)) |
| 93 | + } |
| 94 | + |
| 95 | + // We should select same set of nodes, regardless of drag direction |
| 96 | + dragCase(new Vec2(area.left, area.top), new Vec2(area.right, area.bottom)) |
| 97 | + dragCase(new Vec2(area.right, area.bottom), new Vec2(area.left, area.top)) |
| 98 | + dragCase(new Vec2(area.left, area.bottom), new Vec2(area.right, area.top)) |
| 99 | + dragCase(new Vec2(area.right, area.top), new Vec2(area.left, area.bottom)) |
| 100 | +}) |
| 101 | + |
| 102 | +class MockPointerEvent extends MouseEvent { |
| 103 | + currentTarget: EventTarget | null |
| 104 | + pointerId: number |
| 105 | + constructor(type: string, options: MouseEventInit & { currentTarget?: Element | undefined }) { |
| 106 | + super(type, options) |
| 107 | + this.currentTarget = options.currentTarget ?? null |
| 108 | + this.pointerId = 4 |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +function mockPointerEvent(type: string, pos: Vec2, modifiers: string[]): PointerEvent { |
| 113 | + const modifiersSet = new Set(modifiers) |
| 114 | + const event = new MockPointerEvent(type, { |
| 115 | + altKey: modifiersSet.has('Alt'), |
| 116 | + ctrlKey: modifiersSet.has('Mod'), |
| 117 | + shiftKey: modifiersSet.has('Shift'), |
| 118 | + metaKey: modifiersSet.has('Meta'), |
| 119 | + clientX: pos.x, |
| 120 | + clientY: pos.y, |
| 121 | + button: 0, |
| 122 | + buttons: 1, |
| 123 | + currentTarget: document.createElement('div'), |
| 124 | + }) as PointerEvent |
| 125 | + return event |
| 126 | +} |
0 commit comments