mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-20 09:09:35 +02:00
191 lines
7.0 KiB
TypeScript
191 lines
7.0 KiB
TypeScript
/**
|
|||
|
|
* @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);
|
||
|
|
});
|
||
|
|
});
|