


























@@ -68,6 +68,19 @@ function iconSvg() {
6868return `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14"></path></svg>`;
6969}
707071+function chatBubbleActionsHtml() {
72+return `
73+ <div class="chat-bubble-actions">
74+ <button class="btn btn--xs chat-expand-btn" type="button" aria-label="Open in canvas">
75+ <span class="chat-expand-btn__icon" aria-hidden="true">${iconSvg()}</span>
76+ </button>
77+ <button class="btn btn--xs chat-copy-btn" type="button" aria-label="Copy as markdown">
78+ <span class="chat-copy-btn__icon" aria-hidden="true">${iconSvg()}</span>
79+ </button>
80+ </div>
81+ `;
82+}
83+7184function chatControlsHtml(opts: { agent?: boolean } = {}) {
7285const showAgent = opts.agent !== false;
7386return `
@@ -168,6 +181,7 @@ function chatHtml(opts: { sideResult?: boolean; singleAgent?: boolean } = {}) {
168181 <div class="chat-avatar assistant">A</div>
169182 <div class="chat-group-messages">
170183 <div class="chat-bubble has-copy">
184+ ${chatBubbleActionsHtml()}
171185 <div class="chat-text">
172186 <p>The chat shell should stay compact and readable.</p>
173187 <pre><code>const importantLongIdentifier = "control-ui-chat-responsive-regression-fixture-keeps-code-scrollable"; console.log(importantLongIdentifier);</code></pre>
@@ -226,6 +240,41 @@ async function openFixture(
226240return page;
227241}
228242243+async function getRect(page: Page, selector: string) {
244+const rect = await page.locator(selector).evaluate((node) => {
245+const bounds = (node as HTMLElement).getBoundingClientRect();
246+return {
247+left: bounds.left,
248+right: bounds.right,
249+top: bounds.top,
250+bottom: bounds.bottom,
251+width: bounds.width,
252+height: bounds.height,
253+};
254+});
255+expectFiniteRect({ x: rect.left, y: rect.top, width: rect.width, height: rect.height });
256+return rect;
257+}
258+259+async function getTextContentRect(page: Page, selector: string) {
260+const rect = await page.locator(selector).evaluate((node) => {
261+const range = document.createRange();
262+range.selectNodeContents(node);
263+const bounds = range.getBoundingClientRect();
264+range.detach();
265+return {
266+left: bounds.left,
267+right: bounds.right,
268+top: bounds.top,
269+bottom: bounds.bottom,
270+width: bounds.width,
271+height: bounds.height,
272+};
273+});
274+expectFiniteRect({ x: rect.left, y: rect.top, width: rect.width, height: rect.height });
275+return rect;
276+}
277+229278async function openHeaderFixture(width: number, height: number, opts: { hidden?: boolean } = {}) {
230279const page = await browser.newPage({ viewport: { width, height } });
231280await page.setContent(
@@ -326,6 +375,42 @@ describeBrowserLayout("chat responsive browser layout", () => {
326375}
327376});
328377378+it.each([
379+[320, 568],
380+[1366, 900],
381+] as const)(
382+"keeps short assistant text clear of bubble actions at %sx%s",
383+async (width, height) => {
384+const page = await browser.newPage({ viewport: { width, height } });
385+try {
386+await page.setContent(
387+`<!doctype html><html><head><style>${readUiCss()}</style></head><body>
388+ <div class="chat-thread" role="log">
389+ <div class="chat-thread-inner">
390+ <div class="chat-group assistant">
391+ <div class="chat-avatar assistant">A</div>
392+ <div class="chat-group-messages">
393+ <div class="chat-bubble has-copy">
394+ ${chatBubbleActionsHtml()}
395+ <div class="chat-text"><p>Done.</p></div>
396+ </div>
397+ </div>
398+ </div>
399+ </div>
400+ </div>
401+ </body></html>`,
402+);
403+await page.locator(".chat-bubble").hover();
404+405+const text = await getTextContentRect(page, ".chat-text p");
406+const actions = await getRect(page, ".chat-bubble-actions");
407+expect(text.right).toBeLessThanOrEqual(actions.left - 1);
408+} finally {
409+await page.close();
410+}
411+},
412+);
413+329414it.each(["dark", "light"] as const)(
330415"keeps mobile controls inside the viewport with touch targets in %s mode",
331416async (themeMode) => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。