/** * @fileoverview The Tiles button opens the grid at once (owner decision 8). * * - `tileGridOpenSet` (constants.js, pure): what opens, in order: (a) the grid * this tab last had, if any of its sessions survive; (b) else an open * split's two sessions; (c) else the open sessions in tab order up to the * limit (never past the cap of 6), the active one always among them and * focused. Detached sessions and ones that no longer exist are left out, as * in the picker. * - The button's click and Ctrl+Shift+G are the same function * (`toggleTileGrid`); right-click (contextmenu) opens the picker. With the * grid open the picker shows the current tiles, and Open replaces them. * * Pure helper via `vm`, the app via the shared harness (test/mocks/tile-grid-vm.ts). * Port: N/A. */ import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import vm from 'node:vm'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { FakeEl, body, bySelector, documentAddEventListener, makeGridApp, resetGridHarness, } from './mocks/tile-grid-vm.js'; type OpenSet = { source: string; ids: string[]; focusedId: string | null } | null; type Helpers = { tileGridOpenSet(p: Record): OpenSet; TILE_GRID_MAX: number }; function loadHelpers(): Helpers { const context = vm.createContext({ window: {}, globalThis: {} }); vm.runInContext(readFileSync(resolve(import.meta.dirname, '../src/web/public/constants.js'), 'utf8'), context); return (context.window as { CodemanTileGrid: Helpers }).CodemanTileGrid; } const T = loadHelpers(); const INDEX_HTML = readFileSync(resolve(import.meta.dirname, '../src/web/public/index.html'), 'utf8'); const order = Array.from({ length: 9 }, (_, i) => `t${i + 1}`); const sessions = new Map(order.map((id) => [id, { id, name: id }])); const base = { sessions, sessionOrder: order, limit: 6 }; describe('tileGridOpenSet (what the Tiles button opens)', () => { it('a: the grid this tab last had, its focus (or its zoom) included', () => { const stored = { ids: ['t4', 't2'], focused: 't2', zoomed: null }; expect(T.tileGridOpenSet({ ...base, stored, split: ['t1', 't9'], activeId: 't1' })).toEqual({ source: 'stored', ids: ['t4', 't2'], focusedId: 't2', }); expect(T.tileGridOpenSet({ ...base, stored: { ids: ['t4', 't2'], focused: 't4', zoomed: 't2' } })?.focusedId).toBe( 't2' ); }); it('b: else an open split, Pane A focused', () => { expect(T.tileGridOpenSet({ ...base, stored: { ids: [] }, split: ['t5', 't7'], activeId: 't5' })).toEqual({ source: 'split', ids: ['t5', 't7'], focusedId: 't5', }); }); it('c: else the open sessions in tab order up to the limit, the active one focused', () => { expect(T.tileGridOpenSet({ ...base, activeId: 't3' })).toEqual({ source: 'tabs', ids: order.slice(0, 6), focusedId: 't3', }); }); it('c: an active session past the limit still comes, with the first ones before it', () => { expect(T.tileGridOpenSet({ ...base, activeId: 't9' })).toEqual({ source: 'tabs', ids: ['t1', 't2', 't3', 't4', 't5', 't9'], focusedId: 't9', }); }); it('c: a window that takes fewer gets fewer; nothing ever passes the cap', () => { expect(T.tileGridOpenSet({ ...base, limit: 4, activeId: 't1' })?.ids).toEqual(['t1', 't2', 't3', 't4']); expect(T.tileGridOpenSet({ ...base, limit: 99, activeId: 't1' })?.ids).toHaveLength(T.TILE_GRID_MAX); }); it('leaves out detached sessions and ones that no longer exist, as the picker does', () => { const detachedIds = new Set(['t2']); const out = T.tileGridOpenSet({ ...base, sessionOrder: ['gone', ...order], detachedIds, activeId: 't1' }); expect(out?.ids).toEqual(['t1', 't3', 't4', 't5', 't6', 't7']); // A split whose Pane B was popped out: Pane A alone. expect(T.tileGridOpenSet({ ...base, split: ['t1', 't2'], detachedIds, activeId: 't1' })?.ids).toEqual(['t1']); }); it('is null when there is nothing to open', () => { expect(T.tileGridOpenSet({ sessions: new Map(), sessionOrder: [], limit: 6, activeId: null })).toBeNull(); }); }); describe('the Tiles button and Ctrl+Shift+G', () => { const button = () => { const html = INDEX_HTML.match(/