The Variables and Properties nodes listed their picked rows in the inspector, but unlike Start, End and struct rows they could not be reordered. The drag dots also sat right on the edge of the row and touched the kind mark. - buildPickedPortSection gathers each row with its warning line as one block and hands the blocks to sortBlocks. A move reorders the ports on the node and is one undo step. Properties uses the same section and gains the same reordering. - Grip draws the dots 3 px in from the left edge and moves the kind mark 3 px clear of them (DOTS_PAD). The column keeps its width, so the name fields do not shift. Rows without dots are unchanged. Tests: - block-drag, reorder, variable-choices: 3 files, 13 tests, all green. block-drag now expects the mark 11 px in with dots, 0 without. - e2e 23 on melektaus-atx: ALL PASSED, 32 checks (+3 for dragging vy before vx and undoing it). It ran before the padding change, which touches no checks there. - The gate did not run before this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
/**
|
|
* What a sortable column shows before anything is dragged (#310): dots on every grip, and under the
|
|
* pointer the grip lit and the block that would go with it backed.
|
|
*/
|
|
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
|
import { CanvasTextMetrics, FederatedPointerEvent, Graphics } from 'pixi.js';
|
|
import { Grip, HStack, Label, sortBlocks, Theme, VStack } from '../../src/ui';
|
|
|
|
vi.mock('../../src/ui/svg', () => ({ drawSvg: () => undefined, tintSvg: (svg: string) => svg }));
|
|
|
|
beforeAll(() => {
|
|
CanvasTextMetrics.measureText = ((text: string) => ({
|
|
width: text.length * 7,
|
|
height: 14,
|
|
lines: [text],
|
|
lineWidths: [text.length * 7],
|
|
})) as never;
|
|
});
|
|
|
|
/** Three blocks: one row, two rows, one row. */
|
|
function column(sortable = true) {
|
|
const body = new VStack();
|
|
const blocks = [1, 2, 1].map((size, index) => {
|
|
const grip = new Grip(new Label(String(index), { width: 28 }));
|
|
const rows = Array.from({ length: size }, (_, i) => {
|
|
const row = new HStack();
|
|
row.minHeight = Theme.rowHeight;
|
|
if (i === 0) row.add(grip);
|
|
return body.add(row);
|
|
});
|
|
return { grip, rows };
|
|
});
|
|
body.resize(200, 400);
|
|
if (sortable) sortBlocks(blocks, () => {});
|
|
return { body, blocks };
|
|
}
|
|
|
|
const pointer = (type: string) => Object.assign(new FederatedPointerEvent(null as never), { type });
|
|
const backs = (body: VStack) => body.children.filter((c) => c instanceof Graphics);
|
|
|
|
describe('the grips of a sortable column', () => {
|
|
it('show their dots, and only where the rows may be dragged', () => {
|
|
expect(column().blocks.map((b) => b.grip.dotsShown)).toEqual(['idle', 'idle', 'idle']);
|
|
expect(column(false).blocks.map((b) => b.grip.dotsShown)).toEqual(['none', 'none', 'none']);
|
|
});
|
|
|
|
it('move their mark right of the dots, and leave it at the edge where there are none', () => {
|
|
const [sorted] = column().blocks;
|
|
expect(sorted!.grip.content.x).toBe(11);
|
|
expect(sorted!.grip.content.x + sorted!.grip.content.w).toBe(sorted!.grip.w);
|
|
expect(column(false).blocks[0]!.grip.content.x).toBe(0);
|
|
});
|
|
|
|
it('light up under the pointer and back the whole block, behind its rows', () => {
|
|
const { body, blocks } = column();
|
|
const grip = blocks[1]!.grip;
|
|
grip.emit('pointerover', pointer('pointerover'));
|
|
expect(grip.dotsShown).toBe('lit');
|
|
expect(blocks[0]!.grip.dotsShown).toBe('idle');
|
|
const [back] = backs(body);
|
|
expect(body.children.indexOf(back!)).toBe(0);
|
|
const bounds = (back as Graphics).bounds;
|
|
expect([bounds.minY, bounds.maxY]).toEqual([Theme.rowHeight, 3 * Theme.rowHeight]);
|
|
});
|
|
|
|
it('go back to rest when the pointer leaves', () => {
|
|
const { body, blocks } = column();
|
|
const grip = blocks[1]!.grip;
|
|
grip.emit('pointerover', pointer('pointerover'));
|
|
grip.emit('pointerout', pointer('pointerout'));
|
|
expect(grip.dotsShown).toBe('idle');
|
|
expect(backs(body)).toEqual([]);
|
|
});
|
|
|
|
it('let the pointer pass by while a block is dragged, and answer it again after', () => {
|
|
const { body, blocks } = column();
|
|
const [dragged, , passed] = blocks.map((b) => b.grip);
|
|
const at = (type: string, y: number, button = 0) =>
|
|
Object.assign(pointer(type), { button, global: { x: 0, y } });
|
|
dragged!.emit('pointerdown', at('pointerdown', 0));
|
|
dragged!.emit('globalpointermove', at('globalpointermove', 10));
|
|
passed!.emit('pointerover', pointer('pointerover'));
|
|
expect(blocks.map((b) => b.grip.dotsShown)).toEqual(['lit', 'idle', 'idle']);
|
|
// The drop line is a Graphics too; a backing would sit behind the rows.
|
|
expect(body.children[0]).not.toBeInstanceOf(Graphics);
|
|
expect(blocks.map((b) => b.grip.cursor)).toEqual(['grabbing', 'grabbing', 'grabbing']);
|
|
dragged!.emit('pointerupoutside', at('pointerupoutside', 10));
|
|
expect(blocks.map((b) => b.grip.dotsShown)).toEqual(['idle', 'idle', 'lit']);
|
|
expect(backs(body)).toHaveLength(1);
|
|
expect(blocks.map((b) => b.grip.cursor)).toEqual(['grab', 'grab', 'grab']);
|
|
});
|
|
});
|