128 lines
5.1 KiB
TypeScript
128 lines
5.1 KiB
TypeScript
import { type Page } from "@playwright/test";
|
|
import { test, expect } from "../../fixtures/test-base";
|
|
import type { ApiClient } from "../../helpers/api-client";
|
|
import type { SeedData } from "../../fixtures/test-base";
|
|
import { planScript } from "../../helpers/seed-session-messages";
|
|
import { dwell } from "../../helpers/causal-waits";
|
|
import { SessionPage } from "../../pages/session-page";
|
|
|
|
const PLAN_MARKER = "Plan table resize marker";
|
|
const PLAN_MARKDOWN = [
|
|
"## Resizable table",
|
|
"",
|
|
"| Setting | Effect | Notes |",
|
|
"| --- | --- | --- |",
|
|
`| strictDepBuilds | Blocks unapproved scripts | ${PLAN_MARKER} |`,
|
|
"| allowBuilds | Allows approved scripts | Ephemeral width |",
|
|
].join("\n");
|
|
|
|
async function openPlanPanel(session: SessionPage) {
|
|
if (await session.planPanel.isVisible()) return;
|
|
await session.togglePlanMode();
|
|
await expect(session.planPanel).toBeVisible({ timeout: 10_000 });
|
|
}
|
|
|
|
async function seedPlanTable(testPage: Page, apiClient: ApiClient, seedData: SeedData) {
|
|
const task = await apiClient.createTaskWithAgent(
|
|
seedData.workspaceId,
|
|
"Resizable Plan table",
|
|
seedData.agentProfileId,
|
|
{
|
|
description: planScript(PLAN_MARKDOWN),
|
|
workflow_id: seedData.workflowId,
|
|
workflow_step_id: seedData.startStepId,
|
|
repository_ids: [seedData.repositoryId],
|
|
},
|
|
);
|
|
await testPage.goto(`/t/${task.id}`);
|
|
const session = new SessionPage(testPage);
|
|
await session.waitForLoad();
|
|
await expect.poll(() => apiClient.getTaskPlan(task.id), { timeout: 30_000 }).not.toBeNull();
|
|
const initialPlan = await apiClient.getTaskPlan(task.id);
|
|
await session.waitForChatIdle({ timeout: 45_000 });
|
|
await session.composerReady();
|
|
await openPlanPanel(session);
|
|
await expect(session.planPanel).toContainText(PLAN_MARKER, { timeout: 15_000 });
|
|
if (!initialPlan) throw new Error("seeded plan was not available");
|
|
return { initialPlan: initialPlan.content, session, taskId: task.id };
|
|
}
|
|
|
|
test.describe("Plan table column resizing", () => {
|
|
test("resizes an internal boundary without persisting the width", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
}) => {
|
|
// Covers AC-UI-RESIZABLE-MARKDOWN-TABLES-001.10 and .11.
|
|
test.setTimeout(120_000);
|
|
await testPage.setViewportSize({ width: 1100, height: 800 });
|
|
const { initialPlan, session, taskId } = await seedPlanTable(testPage, apiClient, seedData);
|
|
|
|
const editor = session.planEditor();
|
|
const table = editor.locator("table", { hasText: PLAN_MARKER });
|
|
const wrapper = table.locator("xpath=..");
|
|
const firstHeader = table.locator("th").first();
|
|
await expect(table).toBeVisible();
|
|
|
|
const initialHeaderBox = await firstHeader.boundingBox();
|
|
expect(initialHeaderBox).not.toBeNull();
|
|
const boundaryY = initialHeaderBox!.y + initialHeaderBox!.height / 2;
|
|
await testPage.mouse.move(initialHeaderBox!.x + initialHeaderBox!.width - 2, boundaryY);
|
|
|
|
const handle = editor.locator(".column-resize-handle").first();
|
|
await expect(handle).toBeVisible();
|
|
const initialWidth = initialHeaderBox!.width;
|
|
const boundaryX = initialHeaderBox!.x + initialHeaderBox!.width - 2;
|
|
await testPage.mouse.down();
|
|
await testPage.mouse.move(boundaryX + 60, boundaryY);
|
|
await testPage.mouse.up();
|
|
|
|
const grownWidth = await firstHeader.evaluate(
|
|
(element) => element.getBoundingClientRect().width,
|
|
);
|
|
expect(grownWidth - initialWidth).toBeCloseTo(60, 0);
|
|
|
|
const grownHeaderBox = await firstHeader.boundingBox();
|
|
expect(grownHeaderBox).not.toBeNull();
|
|
const grownBoundaryX = grownHeaderBox!.x + grownHeaderBox!.width - 2;
|
|
const grownBoundaryY = grownHeaderBox!.y + grownHeaderBox!.height / 2;
|
|
await testPage.mouse.move(grownBoundaryX, grownBoundaryY);
|
|
await testPage.mouse.down();
|
|
await testPage.mouse.move(grownBoundaryX - 1000, grownBoundaryY);
|
|
await testPage.mouse.up();
|
|
expect(
|
|
await firstHeader.evaluate((element) => element.getBoundingClientRect().width),
|
|
).toBeCloseTo(64, 0);
|
|
|
|
expect(
|
|
await wrapper.evaluate((element) =>
|
|
["auto", "scroll"].includes(getComputedStyle(element).overflowX),
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
await testPage.evaluate(
|
|
() => document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1,
|
|
),
|
|
).toBe(true);
|
|
// Stay beyond the Plan panel's 1.5-second autosave window. A resize-only
|
|
// transaction must not enqueue a later canonicalized Markdown save.
|
|
await dwell(
|
|
testPage,
|
|
2_500,
|
|
"negative-assertion",
|
|
"a resize-only transaction must never trigger the 1.5-second Plan autosave, so there is no save event to await",
|
|
);
|
|
expect((await apiClient.getTaskPlan(taskId))?.content).toBe(initialPlan);
|
|
|
|
await testPage.reload();
|
|
await session.waitForLoad();
|
|
await openPlanPanel(session);
|
|
const reloadedFirstHeader = session.planEditor().locator("table th").first();
|
|
await expect(reloadedFirstHeader).toBeVisible({ timeout: 15_000 });
|
|
expect(
|
|
await reloadedFirstHeader.evaluate((element) => element.getBoundingClientRect().width),
|
|
).toBeGreaterThan(64);
|
|
expect((await apiClient.getTaskPlan(taskId))?.content).toBe(initialPlan);
|
|
});
|
|
});
|