Retained tabs stay mounted under display:none while inactive, which reports a zero-width onLayout. ControlledAgentControls fed that straight into its density resolver, so a hidden tab's toolbar collapsed to its tightest density and painted one frame collapsed before re-expanding when the tab became visible again. Extract the zero-width guard that useContainerWidthBelow already had into a shared readMeasuredWidth helper, and use it in the composer toolbar's own layout handler so a display:none measurement can never drive its density. Also bump the chat outline rail's MIN_PANEL_WIDTH breakpoint (818 -> 918) so the rail disappears earlier under a narrower panel, and widen the chat-outline e2e spec's viewport so its panel keeps healthy margin over the new threshold.
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
import { expect, type Page } from "@playwright/test";
|
|
|
|
/**
|
|
* The composer toolbar collapses its controls by measured width. A collapsed
|
|
* frame is observable from the DOM: the mode trigger drops its label and
|
|
* shrinks to the icon-only box, so a visible trigger with no text is a
|
|
* collapsed paint.
|
|
*/
|
|
interface ToolbarFrame {
|
|
atMs: number;
|
|
labels: string[];
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
__composerToolbarFrames?: ToolbarFrame[];
|
|
__composerToolbarStop?: () => void;
|
|
}
|
|
}
|
|
|
|
/** Records every painted frame's visible mode-trigger labels until stopped. */
|
|
export async function recordComposerToolbarFrames(page: Page): Promise<void> {
|
|
await page.evaluate(() => {
|
|
const frames: ToolbarFrame[] = [];
|
|
window.__composerToolbarFrames = frames;
|
|
const start = performance.now();
|
|
let running = true;
|
|
const tick = () => {
|
|
if (!running) return;
|
|
const labels = Array.from(document.querySelectorAll('[data-testid="mode-control"]'))
|
|
.filter((node) => node.getClientRects().length > 0)
|
|
.map((node) => (node.textContent ?? "").trim());
|
|
frames.push({ atMs: Math.round(performance.now() - start), labels });
|
|
requestAnimationFrame(tick);
|
|
};
|
|
requestAnimationFrame(tick);
|
|
window.__composerToolbarStop = () => {
|
|
running = false;
|
|
};
|
|
});
|
|
}
|
|
|
|
async function readComposerToolbarFrames(page: Page): Promise<ToolbarFrame[]> {
|
|
return page.evaluate(() => {
|
|
window.__composerToolbarStop?.();
|
|
return window.__composerToolbarFrames ?? [];
|
|
});
|
|
}
|
|
|
|
export async function expectNoCollapsedComposerToolbarFrame(page: Page): Promise<void> {
|
|
const frames = await readComposerToolbarFrames(page);
|
|
expect(frames.length).toBeGreaterThan(0);
|
|
const collapsed = frames.filter((frame) => frame.labels.some((label) => label === ""));
|
|
expect(collapsed, `collapsed toolbar frames: ${JSON.stringify(collapsed)}`).toEqual([]);
|
|
}
|