Files
Codeman/test/tile-grid-paced-connect.test.ts
T

191 lines
7.0 KiB
TypeScript
Raw Normal View History

/**
* @fileoverview Opening the grid paints the tiles first and builds their
* terminals one per frame (owner answer: "paced connect: in").
*
* - `openTileGrid` connects no tile inside the click: every tile is mounted
* and the grid laid out first, then one tile connects per animation frame,
* the focused one first, then reading order. Each still connects once, into
* its final cell (one fit, one PTY resize).
* - The keyboard goes to the focused tile's terminal as soon as it exists
* (`focusOnConnect`): not before, never for a `focus: false` selection, and
* to the newly focused tile when focus moved while they were being built.
* - A grid closed (or opened again) meanwhile stops the old run; a tile
* removed before its turn is skipped; a tile already connected (one a
* re-form added) is never connected twice; a remount after Attach connects
* its new terminal.
*
* Real code via the shared vm harness (test/mocks/tile-grid-vm.ts). Port: N/A.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
FakeEl,
FakeTile,
bySelector,
flushFrames,
makeGridApp,
rafCallbacks,
resetGridHarness,
type GridApp,
} from './mocks/tile-grid-vm.js';
const IDS = ['s-a', 's-b', 's-c', 's-d'];
const connected = () => FakeTile.all.filter((t) => t.connect.mock.calls.length > 0).map((t) => t.sessionId);
const tileOf = (id: string) => FakeTile.all.find((t) => t.sessionId === id && !t._destroyed)!;
/** Runs the frames queued so far, once (one step of the paced connect). */
const oneFrame = () => {
for (const cb of rafCallbacks.splice(0)) cb();
};
beforeEach(() => {
resetGridHarness();
const btn = new FakeEl();
btn.className = 'btn-icon-header btn-tile-grid';
bySelector.set('.btn-tile-grid', btn);
});
function gridApp(ids = IDS): GridApp {
const app = makeGridApp(ids);
app.selectSession = vi.fn((id: string) => app._selectTiledSession(id, {}));
return app;
}
describe('opening paints first, then one terminal per frame', () => {
it('nothing connects inside the click; then the focused tile first, then reading order, one per frame', () => {
const app = gridApp();
app.openTileGrid(IDS, { focusedId: 's-c' });
expect(app._tilesOwnTerminal()).toBe(true);
expect(FakeTile.all).toHaveLength(4);
expect(connected()).toEqual([]);
oneFrame();
expect(connected()).toEqual(['s-c']);
oneFrame();
expect(connected()).toEqual(['s-a', 's-c']);
oneFrame();
oneFrame();
expect(connected().sort()).toEqual(IDS);
oneFrame();
expect(rafCallbacks).toHaveLength(0);
expect(FakeTile.all.every((t) => t.connect.mock.calls.length === 1)).toBe(true);
});
it('every tile connects into its final cell: the grid was laid out before the first', () => {
const app = gridApp();
app.openTileGrid(IDS);
const seen: string[] = [];
for (const t of FakeTile.all) {
t.connect.mockImplementation(async () => {
seen.push(`${t.sessionId}:${app._tileGrid.cells.length}:${app._tileGrid.cells.indexOf(t.sessionId)}`);
});
}
flushFrames();
expect(seen).toEqual(['s-a:4:0', 's-b:4:1', 's-c:4:2', 's-d:4:3']);
});
});
describe('the keyboard', () => {
it('lands in the focused tile once its terminal exists, not before', () => {
const app = gridApp();
app.openTileGrid(IDS, { focusedId: 's-b' });
expect(tileOf('s-b').terminal.focus).not.toHaveBeenCalled();
oneFrame();
expect(tileOf('s-b').terminal.focus).toHaveBeenCalledTimes(1);
flushFrames();
expect(tileOf('s-b').terminal.focus).toHaveBeenCalledTimes(1);
expect(tileOf('s-a').terminal.focus).not.toHaveBeenCalled();
});
it('follows a focus moved while the terminals are being built', () => {
const app = gridApp();
app.openTileGrid(IDS, { focusedId: 's-a' });
app._selectTiledSession('s-d', {});
flushFrames();
expect(tileOf('s-d').terminal.focus).toHaveBeenCalled();
expect(tileOf('s-a').terminal.focus).not.toHaveBeenCalled();
});
it('never for a selection that asked for no focus', () => {
const app = gridApp();
app.openTileGrid(IDS, { focusedId: 's-a' });
app._selectTiledSession('s-a', { focus: false });
flushFrames();
expect(FakeTile.all.some((t) => t.terminal.focus.mock.calls.length > 0)).toBe(false);
});
it('a tile already built takes the keyboard at once', () => {
const app = gridApp();
app.openTileGrid(IDS, { focusedId: 's-a' });
flushFrames();
app._selectTiledSession('s-c', {});
expect(tileOf('s-c').terminal.focus).toHaveBeenCalledTimes(1);
});
});
describe('a grid that changes meanwhile', () => {
it('closed: nothing connects any more, and no frame is asked for after it', () => {
const app = gridApp();
app.openTileGrid(IDS);
oneFrame();
app.closeTileGrid({ reselect: false });
oneFrame();
expect(rafCallbacks).toHaveLength(0);
flushFrames();
expect(connected()).toEqual(['s-a']);
});
it('opened again: the old run stops at once, the new one starts with its own focused tile', () => {
const app = gridApp();
app.openTileGrid(IDS, { focusedId: 's-a' });
oneFrame();
app.closeTileGrid({ reselect: false });
app.activeSessionId = null;
app.openTileGrid(IDS, { focusedId: 's-d' });
oneFrame();
const live = () => FakeTile.all.filter((t) => !t._destroyed && t.connect.mock.calls.length).map((t) => t.sessionId);
expect(live()).toEqual(['s-d']);
});
it('closed and opened again: the old run stops, the new one connects each tile once', () => {
const app = gridApp();
app.openTileGrid(IDS);
oneFrame();
app.closeTileGrid({ reselect: false });
app.activeSessionId = null;
app.openTileGrid(IDS);
flushFrames();
const live = FakeTile.all.filter((t) => !t._destroyed);
expect(live).toHaveLength(4);
expect(live.every((t) => t.connect.mock.calls.length === 1)).toBe(true);
});
it('a tile removed before its turn is skipped; one a re-form connected is not connected again', () => {
const app = gridApp(['s-a', 's-b', 's-c', 's-d', 's-e']);
app.openTileGrid(['s-a', 's-b', 's-c']);
app.removeTile('s-b');
app.addTile('s-e');
expect(tileOf('s-e').connect).toHaveBeenCalledTimes(1);
flushFrames();
expect(FakeTile.all.find((t) => t.sessionId === 's-b')!.connect).not.toHaveBeenCalled();
expect(tileOf('s-e').connect).toHaveBeenCalledTimes(1);
expect(tileOf('s-a').connect).toHaveBeenCalledTimes(1);
expect(tileOf('s-c').connect).toHaveBeenCalledTimes(1);
});
it('a tile remounted before its turn (Attach) is connected once, not again by the paced run', () => {
const app = gridApp();
app.openTileGrid(IDS);
app._remountTile('s-c');
flushFrames();
expect(tileOf('s-c').connect).toHaveBeenCalledTimes(1);
});
it('a remount after Attach connects its new terminal', () => {
const app = gridApp();
app.openTileGrid(IDS);
flushFrames();
const old = tileOf('s-b');
app._remountTile('s-b');
const fresh = tileOf('s-b');
expect(fresh).not.toBe(old);
expect(fresh.connect).toHaveBeenCalledTimes(1);
});
});