fix(frontend): refit différé des cellules terminal après mutation de layout (#61)
Le ResizeObserver de TerminalView ne déclenche pas toujours un événement utile quand une cellule voisine apparaît/disparaît (split/merge), forçant l'utilisateur à redimensionner la fenêtre pour rafraîchir le scaling xterm. useLayout expose un layoutVersion bumpé à chaque commit d'arbre (chargement initial inclus), relayé par LayoutGrid comme refitSignal à chaque TerminalView survivant pour déclencher fit.fit() sans rouvrir le PTY. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -13,6 +13,8 @@
|
||||
import { describe, it, expect, vi, beforeAll, afterAll } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
|
||||
import type {
|
||||
Gateways,
|
||||
ReattachResult,
|
||||
@ -289,4 +291,67 @@ describe("TerminalView — visible launch-failure surface (ticket #14 F3)", () =
|
||||
expect(screen.queryByRole("alert")).toBeNull();
|
||||
expect(screen.queryByTestId("terminal-error")).toBeNull();
|
||||
});
|
||||
|
||||
describe("refitSignal (ticket #61 — refit after split/merge)", () => {
|
||||
it("refits WITHOUT reopening the terminal when refitSignal changes", async () => {
|
||||
// Simulates LayoutGrid bumping `useLayout`'s layout version after a
|
||||
// split/merge: a survivor cell must refit its xterm grid, but must NOT
|
||||
// re-run open/reattach — that would tear down and relaunch its PTY.
|
||||
const fitSpy = vi.spyOn(FitAddon.prototype, "fit");
|
||||
const open = vi.fn(async () => makeHandle({ sessionId: "survivor-1" }));
|
||||
|
||||
const { rerender } = renderView(new MockTerminalGateway(), "/cwd", {
|
||||
open,
|
||||
refitSignal: 1,
|
||||
});
|
||||
await waitFor(() => expect(open).toHaveBeenCalledTimes(1));
|
||||
|
||||
const fitCallsAtMount = fitSpy.mock.calls.length;
|
||||
expect(fitCallsAtMount).toBeGreaterThan(0);
|
||||
|
||||
// jsdom reports a zero-size layout box, which the coalesced refit
|
||||
// deliberately skips (the same guard that protects the resize-observer
|
||||
// path from fitting to a transient zero size). Stub a real size on the
|
||||
// inner xterm container so the refit triggered below actually reaches
|
||||
// `fit.fit()` instead of bailing on the zero-size guard.
|
||||
const container = screen.getByTestId("terminal-view").firstElementChild as HTMLElement;
|
||||
Object.defineProperty(container, "clientWidth", { value: 400, configurable: true });
|
||||
Object.defineProperty(container, "clientHeight", { value: 200, configurable: true });
|
||||
|
||||
rerender(
|
||||
<DIProvider gateways={{ terminal: new MockTerminalGateway() } as unknown as Gateways}>
|
||||
<TerminalView cwd="/cwd" open={open} refitSignal={2} />
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fitSpy.mock.calls.length).toBeGreaterThan(fitCallsAtMount),
|
||||
);
|
||||
// The structural-mutation refit must never reopen the PTY.
|
||||
expect(open).toHaveBeenCalledTimes(1);
|
||||
|
||||
fitSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("does not refit when refitSignal is left undefined (no-op for callers that don't pass it)", async () => {
|
||||
const fitSpy = vi.spyOn(FitAddon.prototype, "fit");
|
||||
const open = vi.fn(async () => makeHandle({ sessionId: "no-signal-1" }));
|
||||
|
||||
const { rerender } = renderView(new MockTerminalGateway(), "/cwd", { open });
|
||||
await waitFor(() => expect(open).toHaveBeenCalledTimes(1));
|
||||
fitSpy.mockClear();
|
||||
|
||||
rerender(
|
||||
<DIProvider gateways={{ terminal: new MockTerminalGateway() } as unknown as Gateways}>
|
||||
<TerminalView cwd="/cwd" open={open} />
|
||||
</DIProvider>,
|
||||
);
|
||||
|
||||
// Give any stray rAF a chance to fire, then assert nothing extra happened.
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
expect(fitSpy).not.toHaveBeenCalled();
|
||||
|
||||
fitSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user