mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 16:59:43 +02:00
perf(tiles): opening paints the tiles first, one terminal per frame after
Owner answer: "paced connect: in". Measured on the checkpoint build: of the 140 ms between the click on Tiles and its first frame, about 100 ms was the six xterms being built inside the click, so nothing moved on screen for that long and the entrance could not start. openTileGrid now mounts every tile and lays the grid out as before, then _connectTilesPaced builds one terminal per animation frame, the focused tile's first, then reading order. The click paints its empty tiles in about 20 ms and the entrance plays while the terminals are built. The time until every tile has painted does not change: the load queue serves one capture at a time, so only the focused tile's connect is on its path, one frame later. Each tile still connects once, into its final cell (one fit, one PTY resize). - openTileGrid returns before the terminals exist, so a selection that focuses a tile whose terminal is not built yet hands the keyboard over in _connectTile (focusOnConnect), never when focus: false was asked, and to the newly focused tile when focus moved meanwhile. - _connectTile connects a tile once (entry.connected; a remount after Attach resets it), so a re-form or a remount before a tile's turn is never connected twice. - A grid closed or opened again meanwhile stops the old run (a run token), and asks for no further frames. Tests: tile-grid-paced-connect.test.ts (the order, the final cells, the keyboard, close and reopen, removed and remounted tiles); the tests that read connect or the terminal's focus right after openTileGrid now run the queued frames first (flushFrames in the vm harness), every assertion kept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+52
-10
@@ -289,15 +289,17 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
const section = this._tileGridSection();
|
const section = this._tileGridSection();
|
||||||
this.hideWelcome();
|
this.hideWelcome();
|
||||||
// Mount every tile and lay the grid out BEFORE any tile connects, so each
|
// Mount every tile and lay the grid out BEFORE any tile connects, so each
|
||||||
// first fit measures its real cell; the focused tile connects first, so
|
// first fit measures its real cell. Each tile enters in
|
||||||
// its capture is the one the queue starts with. Each tile enters in
|
|
||||||
// reading order (opacity and transform only: the fit measures the final
|
// reading order (opacity and transform only: the fit measures the final
|
||||||
// cell, so the animation adds no resize).
|
// cell, so the animation adds no resize).
|
||||||
wanted.forEach((id, k) => this._mountTile(id, { enterIndex: k }));
|
wanted.forEach((id, k) => this._mountTile(id, { enterIndex: k }));
|
||||||
// Packed from the first cell (_applyTileLayout pads the shape with empty cells).
|
// Packed from the first cell (_applyTileLayout pads the shape with empty cells).
|
||||||
grid.cells = wanted.filter((id) => grid.tiles.has(id));
|
grid.cells = wanted.filter((id) => grid.tiles.has(id));
|
||||||
this._applyTileLayout();
|
this._applyTileLayout();
|
||||||
for (const id of [focus, ...wanted.filter((id) => id !== focus)]) this._connectTile(id);
|
// The tiles' frames paint first; their terminals are built one per frame
|
||||||
|
// after it, the focused tile's first, so its capture is the one the queue
|
||||||
|
// starts with (_connectTilesPaced).
|
||||||
|
this._connectTilesPaced([focus, ...wanted.filter((id) => id !== focus)]);
|
||||||
if (!grid.resizeObserver && typeof ResizeObserver !== 'undefined') {
|
if (!grid.resizeObserver && typeof ResizeObserver !== 'undefined') {
|
||||||
// The main terminal's observer watches a node that is now hidden; this one
|
// The main terminal's observer watches a node that is now hidden; this one
|
||||||
// catches window resizes, sidebar toggles and rail drags for the grid.
|
// catches window resizes, sidebar toggles and rail drags for the grid.
|
||||||
@@ -1397,6 +1399,7 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
const hadKeyboard = this._focusedTile === entry.tile;
|
const hadKeyboard = this._focusedTile === entry.tile;
|
||||||
this._destroyTerminalTile(entry.tile);
|
this._destroyTerminalTile(entry.tile);
|
||||||
entry.tile = this._newTerminalTile(sessionId, entry.body);
|
entry.tile = this._newTerminalTile(sessionId, entry.body);
|
||||||
|
entry.connected = false;
|
||||||
this._connectTile(sessionId);
|
this._connectTile(sessionId);
|
||||||
if (hadKeyboard) this._noteFocusedTile(entry.tile);
|
if (hadKeyboard) this._noteFocusedTile(entry.tile);
|
||||||
},
|
},
|
||||||
@@ -1779,13 +1782,46 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
input.select?.();
|
input.select?.();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a tile's terminal and socket (TerminalTile.connect builds the xterm
|
||||||
|
* synchronously, then loads through the grid's queue). Once per tile. A
|
||||||
|
* focus that selected this tile before its terminal existed lands now.
|
||||||
|
*/
|
||||||
_connectTile(sessionId) {
|
_connectTile(sessionId) {
|
||||||
this._tileGrid?.tiles
|
const grid = this._tileGrid;
|
||||||
.get(sessionId)
|
const entry = grid?.tiles.get(sessionId);
|
||||||
?.tile.connect()
|
if (!entry || entry.connected) return;
|
||||||
.catch(() => {
|
entry.connected = true;
|
||||||
/* Best-effort, as the split's Pane B: live output arrives once the socket opens. */
|
entry.tile.connect().catch(() => {
|
||||||
});
|
/* Best-effort, as the split's Pane B: live output arrives once the socket opens. */
|
||||||
|
});
|
||||||
|
if (grid.focusOnConnect === sessionId) {
|
||||||
|
grid.focusOnConnect = null;
|
||||||
|
entry.tile.terminal?.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opening the grid: the tiles' terminals are built one per animation frame,
|
||||||
|
* in `order` (the focused tile first), instead of all of them inside the
|
||||||
|
* click. Building six xterms took about 100 ms of the click's 140 ms before
|
||||||
|
* the first frame; paced, the click paints its empty frames in about 20 ms
|
||||||
|
* and the entrance plays while the terminals are built. Nothing waits for
|
||||||
|
* it: the load queue serves one capture at a time anyway, so only the
|
||||||
|
* focused tile's connect is on its critical path, one frame later. Each
|
||||||
|
* tile still fits once, at its final size (laid out before any connect),
|
||||||
|
* and sends one PTY resize. A tile removed, replaced or already connected
|
||||||
|
* meanwhile is skipped; a grid closed (or opened again) meanwhile stops it.
|
||||||
|
*/
|
||||||
|
_connectTilesPaced(order) {
|
||||||
|
const grid = this._tileGrid;
|
||||||
|
const run = (grid.paceRun = (grid.paceRun || 0) + 1);
|
||||||
|
const step = (k) => {
|
||||||
|
if (!grid.open || grid.paceRun !== run || k >= order.length) return;
|
||||||
|
this._connectTile(order[k]);
|
||||||
|
requestAnimationFrame(() => step(k + 1));
|
||||||
|
};
|
||||||
|
requestAnimationFrame(() => step(0));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2143,8 +2179,14 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
const idleCb = typeof requestIdleCallback === 'function' ? requestIdleCallback : (cb) => setTimeout(cb, 16);
|
const idleCb = typeof requestIdleCallback === 'function' ? requestIdleCallback : (cb) => setTimeout(cb, 16);
|
||||||
idleCb(() => this._refreshSessionPanels(sessionId, selectGen));
|
idleCb(() => this._refreshSessionPanels(sessionId, selectGen));
|
||||||
// The keyboard follows focus: shortcuts, voice and paste act on this tile.
|
// The keyboard follows focus: shortcuts, voice and paste act on this tile.
|
||||||
|
// A tile whose terminal is not built yet (the grid just opened, paced)
|
||||||
|
// takes it as soon as it is (_connectTile).
|
||||||
this._noteFocusedTile(entry.tile);
|
this._noteFocusedTile(entry.tile);
|
||||||
if (options.focus !== false) entry.tile.terminal?.focus();
|
grid.focusOnConnect = null;
|
||||||
|
if (options.focus !== false) {
|
||||||
|
if (entry.connected) entry.tile.terminal?.focus();
|
||||||
|
else grid.focusOnConnect = sessionId;
|
||||||
|
}
|
||||||
this._persistTileGrid();
|
this._persistTileGrid();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,13 @@ export const clockNow = () => clock;
|
|||||||
export const perfObserverCallbacks: Array<(list: { getEntries(): unknown[] }) => void> = [];
|
export const perfObserverCallbacks: Array<(list: { getEntries(): unknown[] }) => void> = [];
|
||||||
/** Animation-frame callbacks the code under test queued (id = index + 1); a test runs them. */
|
/** Animation-frame callbacks the code under test queued (id = index + 1); a test runs them. */
|
||||||
export const rafCallbacks: Array<() => void> = [];
|
export const rafCallbacks: Array<() => void> = [];
|
||||||
|
/**
|
||||||
|
* Runs every queued animation frame, and the frames those queue in turn (the
|
||||||
|
* grid builds its tiles' terminals one per frame), until none is left.
|
||||||
|
*/
|
||||||
|
export function flushFrames(limit = 100) {
|
||||||
|
for (let n = 0; n < limit && rafCallbacks.length; n++) for (const cb of rafCallbacks.splice(0)) cb();
|
||||||
|
}
|
||||||
/** What the code under test deferred with requestIdleCallback; a test runs them. */
|
/** What the code under test deferred with requestIdleCallback; a test runs them. */
|
||||||
export const idleCallbacks: Array<() => void> = [];
|
export const idleCallbacks: Array<() => void> = [];
|
||||||
export const windowStub: Record<string, unknown> = {
|
export const windowStub: Record<string, unknown> = {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
activeElement,
|
activeElement,
|
||||||
body,
|
body,
|
||||||
bySelector,
|
bySelector,
|
||||||
|
flushFrames,
|
||||||
localStore,
|
localStore,
|
||||||
makeGridApp,
|
makeGridApp,
|
||||||
resetGridHarness,
|
resetGridHarness,
|
||||||
@@ -370,6 +371,7 @@ describe('re-forming an open grid', () => {
|
|||||||
it('2 to 6 keeps both in their cells and fills from tab order; nothing remounts', () => {
|
it('2 to 6 keeps both in their cells and fills from tab order; nothing remounts', () => {
|
||||||
const app = gridApp();
|
const app = gridApp();
|
||||||
app.openTileGrid(['s-c', 's-a']);
|
app.openTileGrid(['s-c', 's-a']);
|
||||||
|
flushFrames();
|
||||||
const before = new Map(FakeTile.all.map((t) => [t.sessionId, t]));
|
const before = new Map(FakeTile.all.map((t) => [t.sessionId, t]));
|
||||||
open(app);
|
open(app);
|
||||||
item(6).dispatch('click', { stopPropagation: vi.fn() });
|
item(6).dispatch('click', { stopPropagation: vi.fn() });
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
FakeEl,
|
FakeEl,
|
||||||
FakeTile,
|
FakeTile,
|
||||||
body,
|
body,
|
||||||
|
flushFrames,
|
||||||
makeGridApp,
|
makeGridApp,
|
||||||
rafCallbacks,
|
rafCallbacks,
|
||||||
resetGridHarness,
|
resetGridHarness,
|
||||||
@@ -32,6 +33,8 @@ const tile = (id: string) => FakeTile.all.find((t) => t.sessionId === id) as Fak
|
|||||||
function grid(ids: string[]): GridApp {
|
function grid(ids: string[]): GridApp {
|
||||||
const app = makeGridApp(ids);
|
const app = makeGridApp(ids);
|
||||||
app.openTileGrid(ids);
|
app.openTileGrid(ids);
|
||||||
|
// The tiles' terminals are built one per frame (_connectTilesPaced).
|
||||||
|
flushFrames();
|
||||||
for (const t of FakeTile.all) {
|
for (const t of FakeTile.all) {
|
||||||
t.fit.mockClear();
|
t.fit.mockClear();
|
||||||
t.localFit.mockClear();
|
t.localFit.mockClear();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
FakeTile,
|
FakeTile,
|
||||||
body,
|
body,
|
||||||
bySelector,
|
bySelector,
|
||||||
|
flushFrames,
|
||||||
main,
|
main,
|
||||||
makeGridApp,
|
makeGridApp,
|
||||||
resetGridHarness,
|
resetGridHarness,
|
||||||
@@ -134,6 +135,7 @@ describe('opening', () => {
|
|||||||
reducedMotion(!motion);
|
reducedMotion(!motion);
|
||||||
const app = gridApp();
|
const app = gridApp();
|
||||||
app.openTileGrid(IDS);
|
app.openTileGrid(IDS);
|
||||||
|
flushFrames();
|
||||||
vi.advanceTimersByTime(1000);
|
vi.advanceTimersByTime(1000);
|
||||||
return FakeTile.all.map((t) => [t.connect.mock.calls.length, t.fit.mock.calls.length, t.localFit.mock.calls.length]);
|
return FakeTile.all.map((t) => [t.connect.mock.calls.length, t.fit.mock.calls.length, t.localFit.mock.calls.length]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
FakeTile,
|
FakeTile,
|
||||||
advanceClock,
|
advanceClock,
|
||||||
clockNow,
|
clockNow,
|
||||||
|
flushFrames,
|
||||||
localStore,
|
localStore,
|
||||||
main,
|
main,
|
||||||
makeGridApp,
|
makeGridApp,
|
||||||
@@ -118,6 +119,8 @@ describe('parking the main terminal', () => {
|
|||||||
expect(app._cleanupPreviousSession).toHaveBeenCalledTimes(1);
|
expect(app._cleanupPreviousSession).toHaveBeenCalledTimes(1);
|
||||||
expect(main.classList.contains('tiles-active')).toBe(true);
|
expect(main.classList.contains('tiles-active')).toBe(true);
|
||||||
expect(FakeTile.all.map((t) => t.sessionId)).toEqual(IDS);
|
expect(FakeTile.all.map((t) => t.sessionId)).toEqual(IDS);
|
||||||
|
// Built one per frame after the click (_connectTilesPaced), each once.
|
||||||
|
flushFrames();
|
||||||
expect(FakeTile.all.every((t) => t.connect.mock.calls.length === 1)).toBe(true);
|
expect(FakeTile.all.every((t) => t.connect.mock.calls.length === 1)).toBe(true);
|
||||||
expect(app.activeSessionId).toBe('s-b');
|
expect(app.activeSessionId).toBe('s-b');
|
||||||
expect(app._tilesOwnTerminal()).toBe(true);
|
expect(app._tilesOwnTerminal()).toBe(true);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
FakeEl,
|
FakeEl,
|
||||||
FakeTile,
|
FakeTile,
|
||||||
bySelector,
|
bySelector,
|
||||||
|
flushFrames,
|
||||||
idleCallbacks,
|
idleCallbacks,
|
||||||
localStore,
|
localStore,
|
||||||
makeGridApp,
|
makeGridApp,
|
||||||
@@ -36,6 +37,8 @@ const IDS = ['s-a', 's-b', 's-c'];
|
|||||||
function openGrid(): GridApp {
|
function openGrid(): GridApp {
|
||||||
const app = makeGridApp(IDS);
|
const app = makeGridApp(IDS);
|
||||||
app.openTileGrid(IDS);
|
app.openTileGrid(IDS);
|
||||||
|
// The tiles' terminals are built one per frame (_connectTilesPaced).
|
||||||
|
flushFrames();
|
||||||
delete app.selectSession; // the prototype's, for real
|
delete app.selectSession; // the prototype's, for real
|
||||||
app._cleanupPreviousSession.mockClear();
|
app._cleanupPreviousSession.mockClear();
|
||||||
app.markIdleAlertSeen.mockClear();
|
app.markIdleAlertSeen.mockClear();
|
||||||
|
|||||||
Reference in New Issue
Block a user