/** * 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']); }); });