|
| 1 | +/** |
| 2 | + * Copyright 2026 Scratch Foundation |
| 3 | + * SPDX-License-Identifier: Apache-2.0 |
| 4 | + */ |
| 5 | +import * as Blockly from 'blockly/core' |
| 6 | +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' |
| 7 | +import '../../src/css' |
| 8 | +import { ScratchDragger } from '../../src/scratch_dragger' |
| 9 | + |
| 10 | +beforeAll(() => { |
| 11 | + Blockly.Blocks.test_block = { |
| 12 | + init(this: Blockly.Block) { |
| 13 | + this.jsonInit({ |
| 14 | + message0: 'test', |
| 15 | + previousStatement: null, |
| 16 | + nextStatement: null, |
| 17 | + }) |
| 18 | + }, |
| 19 | + } |
| 20 | +}) |
| 21 | + |
| 22 | +afterAll(() => { |
| 23 | + delete Blockly.Blocks.test_block |
| 24 | +}) |
| 25 | + |
| 26 | +let container: HTMLElement |
| 27 | +let workspace: Blockly.WorkspaceSvg |
| 28 | + |
| 29 | +beforeEach(() => { |
| 30 | + container = document.createElement('div') |
| 31 | + container.style.width = '800px' |
| 32 | + container.style.height = '600px' |
| 33 | + document.body.appendChild(container) |
| 34 | + workspace = Blockly.inject(container, {}) |
| 35 | +}) |
| 36 | + |
| 37 | +afterEach(() => { |
| 38 | + workspace.dispose() |
| 39 | + container.remove() |
| 40 | + vi.restoreAllMocks() |
| 41 | +}) |
| 42 | + |
| 43 | +describe('ScratchDragger', () => { |
| 44 | + describe('wouldDeleteDraggable', () => { |
| 45 | + it('returns false when the block is outside the workspace, even over a delete area', () => { |
| 46 | + const block = workspace.newBlock('test_block') |
| 47 | + block.initSvg() |
| 48 | + block.render() |
| 49 | + |
| 50 | + const dragger = new ScratchDragger(block, workspace) |
| 51 | + dragger.draggedOutOfBounds = true |
| 52 | + |
| 53 | + // Mock getDragTarget to return a delete area (simulating the |
| 54 | + // flyout being at the pointer position). |
| 55 | + const fakeDragTarget = { id: 'fake-flyout', wouldDelete: () => true } |
| 56 | + vi.spyOn(workspace, 'getDragTarget').mockReturnValue(fakeDragTarget as unknown as Blockly.IDragTarget) |
| 57 | + vi.spyOn(workspace.getComponentManager(), 'hasCapability').mockReturnValue(true) |
| 58 | + |
| 59 | + const event = new PointerEvent('pointerup', { clientX: 100, clientY: 100 }) |
| 60 | + expect(dragger.wouldDeleteDraggable(event, block)).toBe(false) |
| 61 | + }) |
| 62 | + |
| 63 | + it('allows deletion when the block is inside the workspace over a delete area', () => { |
| 64 | + const block = workspace.newBlock('test_block') |
| 65 | + block.initSvg() |
| 66 | + block.render() |
| 67 | + |
| 68 | + const dragger = new ScratchDragger(block, workspace) |
| 69 | + dragger.draggedOutOfBounds = false |
| 70 | + |
| 71 | + const fakeDragTarget = { id: 'fake-flyout', wouldDelete: () => true } |
| 72 | + vi.spyOn(workspace, 'getDragTarget').mockReturnValue(fakeDragTarget as unknown as Blockly.IDragTarget) |
| 73 | + vi.spyOn(workspace.getComponentManager(), 'hasCapability').mockReturnValue(true) |
| 74 | + |
| 75 | + const event = new PointerEvent('pointerup', { clientX: 100, clientY: 100 }) |
| 76 | + expect(dragger.wouldDeleteDraggable(event, block)).toBe(true) |
| 77 | + }) |
| 78 | + }) |
| 79 | + |
| 80 | + describe('pointer-events on drag surface', () => { |
| 81 | + it('overrides pointer-events:auto on dragged blocks', () => { |
| 82 | + const dragSurface = container.querySelector('.blocklyBlockDragSurface') |
| 83 | + expect(dragSurface).not.toBeNull() |
| 84 | + |
| 85 | + // Blockly core sets pointer-events:auto on .blocklyDragging so |
| 86 | + // the grab cursor works during drags. Our CSS rule overrides |
| 87 | + // this for children of the drag surface so that pointer events |
| 88 | + // pass through to elements underneath (backpack, sprite tiles). |
| 89 | + const child = document.createElementNS('http://www.w3.org/2000/svg', 'g') |
| 90 | + child.setAttribute('class', 'blocklyDragging') |
| 91 | + const dragGroup = dragSurface?.querySelector('g') |
| 92 | + expect(dragGroup).not.toBeNull() |
| 93 | + dragGroup?.appendChild(child) |
| 94 | + |
| 95 | + const style = window.getComputedStyle(child) |
| 96 | + expect(style.pointerEvents).toBe('none') |
| 97 | + }) |
| 98 | + }) |
| 99 | +}) |
0 commit comments