Files
Codeman/test/tile-grid-auto-join.test.ts
T
Codeman maintainer caf5248e22 feat(tiles): no + in the tile header (owner decision 9)
Owner: "remove the + button from these views". The tile header is now
● name ... ⋯ ⤢ ×. Gone with it, because nothing else used them: the +
menu (openTileAddMenu, closeTileAddMenu and their hooks in closeTileGrid
and the global Escape handler), its "New session in this case" entry and
runInCaseForTiles, the .tile-add-empty rules, the four i18n entries only
the menu showed, and buildTilePickerSessions' exclude argument (only the
menu passed it).

Every other way of adding tiles stays and needed nothing from the menu:
the Tiles button and its right-click picker, Ctrl/Cmd+click on a tab,
dragging a tab onto a tile or an empty slot, "Open group as tiles", and
Run joining the open grid (_joinTileGridFromRun). The picker list, the
cap helper _tileGridLimit and the user-text skip on names are shared and
kept.

Tests: the + menu cases (picker, cap, auto-join, the zh-CN harvest) are
removed; tile-grid-chrome pins the header as exactly ⋯ ⤢ × with no add
menu or runInCaseForTiles left on the app.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 02:48:40 +02:00

128 lines
5.5 KiB
TypeScript

/**
* @fileoverview New sessions started from THIS tab's Run join the open grid.
*
* Every Run path calls `_ensureCreatedSessionVisible` for each session it
* created, then `selectSession(firstId)` (a human selection). With the grid open,
* `_joinTileGridFromRun` (called from there) adds each new session to the next
* free slot, so that selection focuses its tile instead of leaving the grid.
* Sessions created elsewhere (agents, other devices, cron) arrive only by
* `session:created` and never join. A grid already holding what the window
* fits does not take it: Run's selection then shows it alone, with a hint.
*
* A tile that joined before its pane existed resends its size once the pid
* appears (the server spawned the pane at its own default size).
*
* Real code via the shared vm harness (test/mocks/tile-grid-vm.ts); the Run
* hook in session-ui.js is pinned at the source. Port: N/A.
*/
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { FakeEl, FakeTile, makeGridApp, resetGridHarness, section, type GridApp } from './mocks/tile-grid-vm.js';
const IDS = ['s-a', 's-b'];
const addSession = (app: GridApp, id: string, workingDir = '/w') =>
app.sessions.set(id, { id, name: id, mode: 'claude', pid: null, workingDir });
beforeEach(() => {
resetGridHarness();
});
describe('Run from this tab', () => {
it('the Run hook is where every Run path makes its new session visible', () => {
const src = readFileSync(resolve(import.meta.dirname, '../src/web/public/session-ui.js'), 'utf8');
const helper = src.slice(src.indexOf('async _ensureCreatedSessionVisible('), src.indexOf('async run() {'));
expect(helper).toContain('this._joinTileGridFromRun?.(sessionId);');
});
it('with the grid open, the new session joins the next free slot; Run then focuses its tile', () => {
const app = makeGridApp(IDS);
app.openTileGrid(IDS);
addSession(app, 's-new');
expect(app._joinTileGridFromRun('s-new')).toBe(true);
expect(app._tileGrid.ids).toEqual(['s-a', 's-b', 's-new']);
// What Run does next: a human selection of the session it created.
app.selectSession('s-new');
expect(app._tilesOwnTerminal()).toBe(true);
expect(app.activeSessionId).toBe('s-new');
});
it('no Attach overlay flashes on the new tile while Run starts its pane', () => {
const app = makeGridApp(IDS);
app.openTileGrid(IDS);
addSession(app, 's-new');
app._joinTileGridFromRun('s-new');
const tile = section.children.find((el) => el.dataset.sessionId === 's-new') as FakeEl;
const overlay = tile.children[1].children.find((c) => c.className === 'tile-attach');
expect(!overlay || overlay.hidden).toBe(true);
});
it('with the grid closed, nothing joins', () => {
const app = makeGridApp(IDS);
addSession(app, 's-new');
expect(app._joinTileGridFromRun('s-new')).toBe(false);
expect(app._tileGrid?.open ?? false).toBe(false);
});
it('a grid that already holds what the window fits does not take it, and says so', () => {
const app = makeGridApp(IDS);
app.openTileGrid(IDS);
addSession(app, 's-new');
section.getBoundingClientRect = () => ({ width: 1000, height: 400, top: 0, left: 0, right: 1000, bottom: 400 });
expect(app._joinTileGridFromRun('s-new')).toBe(false);
delete (section as unknown as Record<string, unknown>).getBoundingClientRect;
expect(app._tileGrid.ids).toEqual(IDS);
expect(app.showToast).toHaveBeenCalledWith(expect.stringContaining('opens on its own'), 'info');
});
it('a session created ELSEWHERE (session:created) never joins', () => {
const app = makeGridApp(IDS);
app.openTileGrid(IDS);
app.renderSessionTabs = vi.fn();
app.saveSessionOrder = vi.fn();
app.updateCost = vi.fn();
app._onSessionCreated({ id: 's-agent', name: 's-agent', mode: 'claude', pid: 1, workingDir: '/w' });
expect(app.sessions.has('s-agent')).toBe(true);
expect(app._tileGrid.ids).toEqual(IDS);
});
});
describe('a pane that starts after its tile connected (#464)', () => {
// The tile sent its size before there was a PTY; the server dropped it and
// spawned the pane at its default size, so the size must go out again.
const tileOf = (id: string) => FakeTile.all.filter((t) => t.sessionId === id).at(-1)!;
const setPid = (app: GridApp, id: string, pid: number | null) =>
app.sessions.set(id, { ...app.sessions.get(id), pid });
it('resends the size once when the pid appears, and again only for a new PTY', () => {
const app = makeGridApp(IDS);
app.openTileGrid(IDS);
addSession(app, 's-new');
app._joinTileGridFromRun('s-new');
const tile = tileOf('s-new');
app._renderTileChrome();
expect(tile.paneStarted).not.toHaveBeenCalled();
setPid(app, 's-new', 4242);
app._renderTileChrome();
expect(tile.paneStarted).toHaveBeenCalledTimes(1);
app._renderTileChrome();
expect(tile.paneStarted).toHaveBeenCalledTimes(1);
// The pane went away and a new one started (an Attach, a respawned pane).
setPid(app, 's-new', null);
app._renderTileChrome();
setPid(app, 's-new', 4343);
app._renderTileChrome();
expect(tile.paneStarted).toHaveBeenCalledTimes(2);
});
it('a tile made for a session that already runs never asks', () => {
const app = makeGridApp(IDS);
app.openTileGrid(IDS);
app._renderTileChrome();
app._renderTileChrome();
for (const id of IDS) expect(tileOf(id).paneStarted).not.toHaveBeenCalled();
});
});