import { describe, expect, it } from 'vitest'; import type { DrawItem } from '../../src/viewport/items'; import { menuTargetOf, pressOn, stackAt } from '../../src/viewport/pick'; /** Only what the pick order reads: the type, the `z`, and a map's layers with their own `z`. */ function item( id: string, type: DrawItem['type'], z: number, layers: { id: string; z: number }[] = [], ): DrawItem { return { id, type, z, tilemap: type === 'tilemap' ? { layers: layers.map((l) => ({ ...l, tiles: [] })) } : null, } as unknown as DrawItem; } describe('what lies under the pointer (#203)', () => { const map = item('map', 'tilemap', 0, [ { id: 'ground', z: -5 }, { id: 'roofs', z: 5 }, ]); const hero = item('hero', 'sprite', -1); it('puts a sprite in front of a map whose only tile there is on a layer below it', () => { expect( stackAt( [map, hero], [ { id: 'map', layers: ['ground'] }, { id: 'hero', layers: null }, ], ), ).toEqual(['hero', 'map']); }); it('puts the map in front where a layer above the sprite has a tile', () => { expect( stackAt( [map, hero], [ { id: 'map', layers: ['ground', 'roofs'] }, { id: 'hero', layers: null }, ], ), ).toEqual(['map', 'hero']); }); it('leaves out a map without a tile under the pointer', () => { expect( stackAt( [map, hero], [ { id: 'map', layers: [] }, { id: 'hero', layers: null }, ], ), ).toEqual(['hero']); }); it('orders sprites by z, ties in hierarchy order, with symbols in front of all', () => { const items = [ item('a', 'sprite', 2), item('b', 'sprite', 0), item('c', 'sprite', 2), item('box', 'collider', -9), ]; const hits = items.map((i) => ({ id: i.id, layers: null })); expect(stackAt(items, hits)).toEqual(['box', 'c', 'a', 'b']); }); }); describe('what a press picks (#203)', () => { const stack = ['top', 'middle', 'bottom']; it('selects the topmost when nothing selected is under the pointer', () => { expect(pressOn(stack, ['elsewhere'], 'elsewhere')).toEqual({ select: 'top', onClick: null }); }); it('keeps a selection hidden under others, so a drag moves it', () => { expect(pressOn(stack, ['bottom'], 'bottom')?.select).toBeNull(); }); it('passes a click on to the next one below, and round to the top again', () => { expect(pressOn(stack, ['top'], 'top')?.onClick).toBe('middle'); expect(pressOn(stack, ['middle'], 'middle')?.onClick).toBe('bottom'); expect(pressOn(stack, ['bottom'], 'bottom')?.onClick).toBe('top'); }); it('goes on from the primary one of a group', () => { expect(pressOn(stack, ['top', 'middle'], 'middle')).toEqual({ select: null, onClick: 'bottom' }); }); it('changes nothing on a click when the selected object is alone there', () => { expect(pressOn(['only'], ['only'], 'only')).toEqual({ select: null, onClick: null }); }); it('is nothing on empty space', () => { expect(pressOn([], ['top'], 'top')).toBeNull(); }); }); describe('what a right-click opens the menu for (#204)', () => { const stack = ['top', 'middle', 'bottom']; it('selects the topmost first when nothing selected is under the pointer', () => { expect(menuTargetOf(stack, ['elsewhere'], 'elsewhere')).toEqual({ id: 'top', select: true }); }); it('keeps a selection hidden under others and opens on it', () => { expect(menuTargetOf(stack, ['elsewhere', 'bottom'], 'elsewhere')).toEqual({ id: 'bottom', select: false, }); }); it('prefers the primary one of a group', () => { expect(menuTargetOf(stack, ['top', 'middle'], 'middle')).toEqual({ id: 'middle', select: false }); }); it('opens none on empty space', () => { expect(menuTargetOf([], ['top'], 'top')).toBeNull(); }); });