mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 13:00:37 +00:00
Imported from andrewyng/aisuite@1b4bbf303e (contents of its platform/ directory, hoisted to the repo root). Development history prior to this commit lives in that repository. Co-authored-by: Devika <devikaverma11@gmail.com>
84 lines
3.5 KiB
TypeScript
84 lines
3.5 KiB
TypeScript
// FB-004/FB-005: the transcript follows a streaming turn only while the reader is at the
|
|
// bottom — scrolling up PINS the viewport (reading must never be yanked away) and surfaces
|
|
// a jump-to-latest pill; bubbles grow hover affordances (copy + timestamp) that reveal
|
|
// without shifting layout. Driven against the fixtures' slow "stream the epic" turn.
|
|
import { expect } from "@playwright/test";
|
|
import { test } from "./fixtures";
|
|
|
|
// The copy test asserts real clipboard writes — grant instead of relying on defaults.
|
|
test.use({ permissions: ["clipboard-write"] });
|
|
|
|
const scrollerState = `(() => {
|
|
const el = document.querySelector(".main-scroll");
|
|
return el ? { top: el.scrollTop, height: el.scrollHeight, client: el.clientHeight } : null;
|
|
})()`;
|
|
|
|
test("scrolling up mid-stream pins the viewport; jump-to-latest re-engages", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByText("Draft the launch note").first().click();
|
|
const box = page.getByPlaceholder(/Ask the coworker/);
|
|
await box.fill("stream the epic");
|
|
await box.press("Enter");
|
|
|
|
// Let the stream outgrow the viewport, then read something "above".
|
|
await page.waitForFunction(
|
|
() => {
|
|
const el = document.querySelector(".main-scroll");
|
|
return !!el && el.scrollHeight > el.clientHeight + 400;
|
|
},
|
|
{ timeout: 10_000 },
|
|
);
|
|
await page.locator(".main-scroll").evaluate((el) => (el.scrollTop = 0));
|
|
|
|
// The stream keeps growing below…
|
|
const h1 = (await page.evaluate(scrollerState))!.height;
|
|
await page.waitForFunction(
|
|
(prev) => {
|
|
const el = document.querySelector(".main-scroll");
|
|
return !!el && el.scrollHeight > prev;
|
|
},
|
|
h1,
|
|
{ timeout: 5_000 },
|
|
);
|
|
// …but the viewport stays where the reader put it (the old behavior yanked to bottom
|
|
// on every delta), and the pill offers the way back.
|
|
const pinned = (await page.evaluate(scrollerState))!;
|
|
expect(pinned.top).toBeLessThan(50);
|
|
await expect(page.getByTestId("jump-to-latest")).toBeVisible();
|
|
|
|
await page.getByTestId("jump-to-latest").click();
|
|
await page.waitForFunction(
|
|
() => {
|
|
const el = document.querySelector(".main-scroll");
|
|
return !!el && el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
},
|
|
{ timeout: 5_000 },
|
|
);
|
|
await expect(page.getByTestId("jump-to-latest")).toHaveCount(0);
|
|
|
|
// Re-engaged: the follow survives the rest of the stream to the turn's end.
|
|
await expect(page.getByText("The epic concludes.").first()).toBeVisible({ timeout: 10_000 });
|
|
const done = (await page.evaluate(scrollerState))!;
|
|
expect(done.height - done.top - done.client).toBeLessThan(80);
|
|
});
|
|
|
|
test("bubbles carry hover copy + timestamp without layout shift", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByText("Draft the launch note").first().click();
|
|
const box = page.getByPlaceholder(/Ask the coworker/);
|
|
await box.fill("hello meta");
|
|
await box.press("Enter");
|
|
await expect(page.getByText("Echo: hello meta", { exact: false }).first()).toBeVisible();
|
|
|
|
// Live items are stamped client-side, so both bubbles expose the affordance strip.
|
|
const userBubble = page.locator(".bubble-user").last();
|
|
await userBubble.hover();
|
|
const meta = page.getByTestId("bubble-copy");
|
|
await expect(meta.first()).toBeVisible();
|
|
await expect(page.getByTestId("bubble-ts").first()).toBeVisible();
|
|
|
|
// Copy actually copies (the fixture page runs with clipboard permission in Chromium).
|
|
await meta.first().click();
|
|
await expect(page.getByText("Copied").first()).toBeVisible();
|
|
});
|