Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 76 additions & 25 deletions ui/src/pages/chat/chat-responsive.browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const VIEWPORTS = [
[1440, 900],
] as const;
const TOUCH_TARGET_MIN_PX = 43.5;
const LONG_SIDE_RESULT_BODY = Array.from(
{ length: 80 },
(_, index) => `<p>Line ${index + 1}: keep the complete side result readable.</p>`,
).join("");
const chromiumExecutablePath = resolvePlaywrightChromiumExecutablePath(chromium.executablePath());
const describeBrowserLayout = canRunPlaywrightChromium(chromiumExecutablePath)
? describe
Expand All @@ -40,6 +44,13 @@ type ControlRect = {
display?: string;
};

type ChatFixtureOptions = {
composerAttachment?: boolean;
sideResultBody?: string;
singleAgent?: boolean;
slashMenu?: boolean;
};

function expectFiniteRect(rect: Pick<ControlRect, "x" | "y" | "width" | "height">) {
for (const key of ["x", "y", "width", "height"] as const) {
expect(Number.isFinite(rect[key])).toBe(true);
Expand Down Expand Up @@ -234,14 +245,7 @@ function chatHeaderControlsHtml(hidden = false) {
`;
}

function chatHtml(
opts: {
composerAttachment?: boolean;
sideResult?: boolean;
singleAgent?: boolean;
slashMenu?: boolean;
} = {},
) {
function chatHtml(opts: ChatFixtureOptions = {}) {
return `
<div class="shell shell--chat" data-chat-responsive-fixture>
<header class="topbar">
Expand Down Expand Up @@ -288,14 +292,14 @@ function chatHtml(
</div>
</div>
${
opts.sideResult
opts.sideResultBody !== undefined
? `<section class="chat-side-result" role="status" aria-live="polite">
<div class="chat-side-result__header">
<div class="chat-side-result__label-row"><span class="chat-side-result__label">BTW</span><span class="chat-side-result__meta">Not saved to chat history</span></div>
<button class="btn chat-side-result__dismiss">${iconSvg()}</button>
</div>
<div class="chat-side-result__question">What should I check next?</div>
<div class="chat-side-result__body"><p>Inspect the responsive controls and keep the transcript usable.</p></div>
<div class="chat-side-result__body">${opts.sideResultBody}</div>
</section>`
: ""
}
Expand Down Expand Up @@ -392,16 +396,7 @@ function chatHtml(
`;
}

async function openFixture(
width: number,
height: number,
opts: {
composerAttachment?: boolean;
sideResult?: boolean;
singleAgent?: boolean;
slashMenu?: boolean;
} = {},
) {
async function openFixture(width: number, height: number, opts: ChatFixtureOptions = {}) {
const page = await openBrowserPage(width, height);
try {
await page.setContent(
Expand Down Expand Up @@ -1674,14 +1669,70 @@ describeBrowserLayout("chat responsive browser layout", () => {
}
});

it.each([
[1024, 768],
[1366, 900],
] as const)(
"scrolls long BTW side result bodies instead of expanding the card at %sx%s",
async (width, height) => {
const page = await openFixture(width, height, {
sideResultBody: LONG_SIDE_RESULT_BODY,
});
try {
const body = await page.locator(".chat-side-result__body").evaluate((node) => {
const style = getComputedStyle(node as HTMLElement);
return {
overflowY: style.overflowY,
clientHeight: (node as HTMLElement).clientHeight,
scrollHeight: (node as HTMLElement).scrollHeight,
};
});
expect(body.overflowY).toBe("auto");
expect(body.clientHeight).toBeLessThan(body.scrollHeight);
expect(body.clientHeight).toBeLessThanOrEqual(480);

const scrollTop = await page.locator(".chat-side-result__body").evaluate((node) => {
const element = node as HTMLElement;
element.scrollTop = element.scrollHeight;
return element.scrollTop;
});
expect(scrollTop).toBeGreaterThan(0);
} finally {
await closeBrowserPage(page);
}
},
);

it("renders BTW side results as a mobile overlay without horizontal overflow", async () => {
const page = await openFixture(320, 568, { sideResult: true });
const page = await openFixture(320, 568, {
sideResultBody: LONG_SIDE_RESULT_BODY,
});
try {
await expectNoHorizontalOverflow(page);
const position = await page
.locator(".chat-side-result")
.evaluate((node) => getComputedStyle(node).position);
expect(position).toBe("fixed");
const card = await page.locator(".chat-side-result").evaluate((node) => {
const element = node as HTMLElement;
const style = getComputedStyle(element);
return {
clientHeight: element.clientHeight,
overflowY: style.overflowY,
position: style.position,
scrollHeight: element.scrollHeight,
};
});
const bodyOverflowY = await page
.locator(".chat-side-result__body")
.evaluate((node) => getComputedStyle(node).overflowY);
expect(card.position).toBe("fixed");
expect(card.overflowY).toBe("auto");
expect(card.clientHeight).toBeLessThan(card.scrollHeight);
expect(bodyOverflowY).toBe("visible");

const scrollTop = await page.locator(".chat-side-result").evaluate((node) => {
const element = node as HTMLElement;
element.scrollTop = element.scrollHeight;
return element.scrollTop;
});
expect(scrollTop).toBeGreaterThan(0);
} finally {
await closeBrowserPage(page);
}
Expand Down
9 changes: 9 additions & 0 deletions ui/src/styles/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -2218,6 +2218,9 @@
.chat-side-result__body {
font-size: 14px;
line-height: 1.55;
max-height: min(55vh, 480px);
overflow-y: auto;
overscroll-behavior-y: contain;
}

.chat-side-result__body > :first-child {
Expand Down Expand Up @@ -2252,6 +2255,12 @@
min-width: 44px;
height: 44px;
}

.chat-side-result__body {
max-height: none;
overflow-y: visible;
overscroll-behavior-y: auto;
}
}

/* ===========================================
Expand Down
Loading