feat: add project-aware agent workspace
This commit is contained in:
+175
-594
@@ -1,9 +1,11 @@
|
||||
import { expect, test, type Locator } from "@playwright/test";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
import { mockWorkbenchContext } from "./support/mock-workbench-context";
|
||||
import { expectThemeColor, readColorAlpha, readThemeColor } from "./support/theme-colors";
|
||||
import { expectThemeColor } from "./support/theme-colors";
|
||||
|
||||
// Retain the historical filename; the Agent now has an independent solid theme.
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await mockWorkbenchContext(page);
|
||||
await mockAgentApi(page, {
|
||||
sessions: [
|
||||
@@ -18,325 +20,189 @@ test.beforeEach(async ({ page }) => {
|
||||
});
|
||||
});
|
||||
|
||||
type BoundingBox = {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
async function waitForAnimations(locator: Locator) {
|
||||
await locator.evaluate(async (element) => {
|
||||
const animations = element
|
||||
.getAnimations({ subtree: true })
|
||||
.filter((animation) => animation.effect?.getTiming().iterations !== Infinity);
|
||||
await Promise.all(animations.map((animation) => animation.finished.catch(() => undefined)));
|
||||
await Promise.all(
|
||||
element
|
||||
.getAnimations({ subtree: true })
|
||||
.filter((animation) => animation.effect?.getTiming().iterations !== Infinity)
|
||||
.map((animation) => animation.finished.catch(() => undefined))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test("desktop Agent docks beside the workspace with a solid reading surface", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
|
||||
const topBar = page.locator("header.acrylic-navigation");
|
||||
const workspace = page.locator("#main-workspace");
|
||||
const agentPanel = page.locator('aside[aria-label="Agent 命令面板"]');
|
||||
const agentHeader = agentPanel.locator(".agent-panel-header");
|
||||
const agentConversationScroll = agentPanel.locator(".agent-conversation-scroll");
|
||||
const agentControls = agentPanel.locator(".agent-panel-band").last();
|
||||
|
||||
await expect(topBar).toBeVisible();
|
||||
await expect(workspace).toBeVisible();
|
||||
await expect(agentPanel).toBeVisible();
|
||||
await expect(agentHeader).toBeVisible();
|
||||
await expect(agentControls).toBeVisible();
|
||||
await waitForAnimations(agentPanel);
|
||||
|
||||
await expectAcrylicSurface(topBar);
|
||||
await expectThemeColor(agentPanel, "background-color", "--agent-dock-surface");
|
||||
await expect(agentPanel).toHaveCSS("backdrop-filter", "none");
|
||||
await expect(agentPanel).toHaveCSS("border-radius", "0px");
|
||||
await expect(agentPanel).toHaveCSS("border-top-width", "0px");
|
||||
await expect(agentPanel).toHaveCSS("border-bottom-width", "0px");
|
||||
await expect(agentPanel).toHaveCSS("border-left-width", "0px");
|
||||
await expect(agentPanel).toHaveCSS("border-right-width", "1px");
|
||||
await expectThemeColor(agentPanel, "border-right-color", "--surface-divider");
|
||||
await expect(agentPanel).toHaveCSS("box-shadow", "none");
|
||||
await expect(agentPanel).toHaveAttribute("data-agent-starting", "true");
|
||||
await expect(agentControls).toHaveCSS("border-top-width", "0px");
|
||||
await expect(agentControls).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
await expectAcrylicSurface(agentPanel.locator("form.agent-panel-control"));
|
||||
const inputBody = agentPanel.locator(".agent-panel-input-body");
|
||||
const inputTools = agentPanel.locator(".agent-panel-input-tools");
|
||||
await expectThemeColor(inputBody, "background-color", "--agent-dock-reading");
|
||||
await expectThemeColor(inputBody, "border-color", "--surface-border");
|
||||
await expect(inputBody).toHaveCSS("display", "block");
|
||||
await expect(inputTools).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
const startComposer = agentPanel.locator(".agent-panel-start-composer");
|
||||
const suggestions = agentPanel.getByRole("group", { name: "推荐问题" });
|
||||
await expect(suggestions.getByRole("button")).toHaveCount(3);
|
||||
await expect(agentPanel.getByRole("heading", { name: "我能帮你处理什么?" })).toBeVisible();
|
||||
await expect(agentPanel.locator(".agent-panel-main-content")).toBeHidden();
|
||||
|
||||
const panelBox = await agentPanel.boundingBox();
|
||||
expect(panelBox).not.toBeNull();
|
||||
expect(panelBox!.x).toBe(0);
|
||||
const topBarBox = await topBar.boundingBox();
|
||||
expect(panelBox!.y).toBe(topBarBox!.y + topBarBox!.height);
|
||||
expect(panelBox!.y + panelBox!.height).toBe(page.viewportSize()!.height);
|
||||
expect(panelBox!.width).toBe(500);
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(500);
|
||||
const headerBox = await agentHeader.boundingBox();
|
||||
const workspaceBox = await workspace.boundingBox();
|
||||
expect(headerBox!.height).toBe(topBarBox!.height);
|
||||
expect(headerBox!.y).toBe(topBarBox!.y + topBarBox!.height);
|
||||
expect(workspaceBox!.y).toBe(topBarBox!.y);
|
||||
await expect(topBar).toHaveCSS("box-shadow", "none");
|
||||
await expectAcrylicSurface(agentHeader);
|
||||
await expect(agentHeader.locator(":scope > div").first()).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
expect(await topBar.evaluate((element) => getComputedStyle(element).borderBottomColor)).toBe(
|
||||
await agentPanel.evaluate((element) => getComputedStyle(element).borderRightColor)
|
||||
);
|
||||
await expect(agentConversationScroll).toHaveCSS("scrollbar-gutter", "stable both-edges");
|
||||
|
||||
const beforeFocus = await getSurfacePresentation(agentPanel);
|
||||
await page.getByPlaceholder("询问供水问题,或描述需要处理的任务").focus();
|
||||
await expectThemeColor(inputBody, "border-color", "--field-focus-border");
|
||||
await expect(inputBody).toHaveCSS("box-shadow", "none");
|
||||
await expect(agentPanel.locator("form.agent-panel-control > [data-slot=input-group]")).toHaveCSS("border-width", "0px");
|
||||
await expect.poll(() => getSurfacePresentation(agentPanel)).toEqual(beforeFocus);
|
||||
await agentPanel.getByRole("button", { name: "选择对话归属" }).click();
|
||||
const projectMenu = page.getByRole("menu");
|
||||
await expect(projectMenu.getByRole("menuitem", { name: "个人会话" })).toBeVisible();
|
||||
await expect(projectMenu.getByRole("menuitem", { name: "当前项目" })).toBeVisible();
|
||||
await expect(projectMenu.getByRole("menuitem", { name: "新建项目" })).toBeVisible();
|
||||
await expect(projectMenu.getByRole("menuitem", { name: "切换项目" })).toHaveCount(0);
|
||||
await page.screenshot({ path: "test-results/agent-history-ui/project-menu.png" });
|
||||
await page.keyboard.press("Escape");
|
||||
await page.screenshot({ path: "test-results/agent-history-ui/desktop-dock.png" });
|
||||
|
||||
for (const viewport of [
|
||||
{ width: 1024, height: 768, panelWidth: 500 },
|
||||
{ width: 1920, height: 1080, panelWidth: 540 }
|
||||
]) {
|
||||
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
||||
await expect(agentPanel).toHaveCSS("width", `${viewport.panelWidth}px`);
|
||||
await expect.poll(async () => (await workspace.boundingBox())?.x).toBe(viewport.panelWidth);
|
||||
const resizedPanelBox = await agentPanel.boundingBox();
|
||||
expect(resizedPanelBox!.x).toBe(0);
|
||||
expect(resizedPanelBox!.y).toBe(topBarBox!.y + topBarBox!.height);
|
||||
expect(resizedPanelBox!.y + resizedPanelBox!.height).toBe(viewport.height);
|
||||
await page.screenshot({
|
||||
path: `test-results/agent-history-ui/desktop-dock-${viewport.width}.png`
|
||||
});
|
||||
}
|
||||
|
||||
await page.setViewportSize({ width: 1440, height: 640 });
|
||||
const handle = page.getByRole("separator", { name: "调整 Agent 面板宽度" });
|
||||
for (const width of [400, 640]) {
|
||||
const box = (await handle.boundingBox())!;
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(width, box.y + box.height / 2);
|
||||
await page.mouse.up();
|
||||
await page.mouse.move(1000, 450);
|
||||
await expect(agentPanel).toHaveCSS("width", `${width}px`);
|
||||
expect(await startComposer.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true);
|
||||
expect(await inputTools.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(true);
|
||||
const textarea = agentPanel.locator("textarea");
|
||||
await textarea.fill("核对近期供水压力变化,说明监测证据和需要现场核验的事项。".repeat(20));
|
||||
const composerBox = (await startComposer.boundingBox())!;
|
||||
expect(composerBox.y).toBeGreaterThanOrEqual(topBarBox!.height * 2);
|
||||
expect(composerBox.y + composerBox.height).toBeLessThanOrEqual(640);
|
||||
await textarea.fill("");
|
||||
await agentPanel.screenshot({ path: `test-results/agent-history-ui/desktop-material-${width}.png` });
|
||||
}
|
||||
});
|
||||
|
||||
test("desktop Agent portal menus keep local material and respect reduced transparency", async ({ page }) => {
|
||||
test("Agent separates conversation navigation, context and composer from the map theme", async ({
|
||||
page
|
||||
}) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
const header = panel.locator(".agent-panel-header");
|
||||
const context = panel.locator(".agent-chat-context-bar");
|
||||
const form = panel.locator("form");
|
||||
await waitForAnimations(panel);
|
||||
await expect(page.locator("header.acrylic-navigation")).toHaveCSS("backdrop-filter", /blur/);
|
||||
await expect(header).toHaveCSS("background-color", "rgb(255, 255, 255)");
|
||||
await expect(header).toHaveCSS("backdrop-filter", "none");
|
||||
await expect(header.getByLabel("Agent 状态")).toHaveCSS("width", "64px");
|
||||
await expect(header.getByRole("group", { name: "对话操作" }).getByRole("button")).toHaveCount(3);
|
||||
await expect(panel.getByRole("heading", { name: "有什么可以帮你?" })).toBeVisible();
|
||||
await expect(context).toHaveCount(0);
|
||||
await expect(context.getByRole("button")).toHaveCount(0);
|
||||
await expect(form).toHaveCount(1);
|
||||
await expect(form).toHaveCSS("background-color", "rgb(255, 255, 255)");
|
||||
const panelBox = (await panel.boundingBox())!;
|
||||
const headerBox = (await header.boundingBox())!;
|
||||
const divider = panel.locator("[data-agent-header-divider]");
|
||||
const contextBox = (await divider.boundingBox())!;
|
||||
expect(panelBox.x).toBe(0);
|
||||
expect(contextBox.y).toBeGreaterThanOrEqual(headerBox.y + headerBox.height);
|
||||
await form.locator("textarea").focus();
|
||||
await expectThemeColor(form, "border-color", "--action-blue");
|
||||
await header.getByRole("button", { name: "选择主题" }).click();
|
||||
await expect(page.getByRole("menuitem", { name: "新建主题", exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("menuitem", { name: "新建项目", exact: true })).toHaveCount(0);
|
||||
await page.keyboard.press("Escape");
|
||||
await panel.screenshot({ path: "test-results/agent-history-ui/agent-new-start.png" });
|
||||
const handle = page.getByRole("separator", { name: "调整 Agent 面板宽度" });
|
||||
for (const [key, width] of [["Home", 400], ["End", 640]] as const) {
|
||||
await handle.focus();
|
||||
await page.keyboard.press(key);
|
||||
await expect.poll(async () => Math.round((await panel.boundingBox())!.width)).toBe(width);
|
||||
const bounds = (await header.boundingBox())!;
|
||||
const contextBounds = (await divider.boundingBox())!;
|
||||
const actions = (await header.getByRole("group", { name: "对话操作" }).boundingBox())!;
|
||||
const identity = (await header.getByText("供水助手", { exact: true }).boundingBox())!;
|
||||
expect(actions.x).toBeGreaterThan(identity.x + identity.width);
|
||||
expect(actions.x + actions.width).toBeLessThanOrEqual(bounds.x + bounds.width - 12);
|
||||
await page.screenshot({
|
||||
path: `/tmp/nexttjwater-orb-64/header-${width}.png`,
|
||||
clip: { ...bounds, height: contextBounds.y + contextBounds.height - bounds.y }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("Agent portal menus use solid surfaces and return focus with reduced transparency", async ({
|
||||
page
|
||||
}) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
const cdp = await page.context().newCDPSession(page);
|
||||
await cdp.send("Emulation.setEmulatedMedia", {
|
||||
features: [{ name: "prefers-reduced-transparency", value: "reduce" }]
|
||||
});
|
||||
for (const name of ["选择 Agent 模型", "权限批准模式"]) {
|
||||
const trigger = panel.getByRole("button", { name });
|
||||
await trigger.click();
|
||||
const menu = page.getByRole("menu");
|
||||
await expect(menu).toBeVisible();
|
||||
await waitForAnimations(menu);
|
||||
await expect(menu).toHaveAttribute("data-agent-presentation", "desktop-dock");
|
||||
await expect(menu).toHaveCSS("background-color", "rgb(255, 255, 255)");
|
||||
await expect(menu).toHaveCSS("backdrop-filter", "none");
|
||||
expect(await menu.evaluate((element) => element.closest("aside"))).toBeNull();
|
||||
await expectThemeColor(menu, "background-color", "--agent-dock-overlay-surface");
|
||||
await expectAcrylicSurface(menu);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(trigger).toBeFocused();
|
||||
}
|
||||
|
||||
const cdp = await page.context().newCDPSession(page);
|
||||
await cdp.send("Emulation.setEmulatedMedia", {
|
||||
features: [{ name: "prefers-reduced-transparency", value: "reduce" }]
|
||||
});
|
||||
expect(await page.evaluate(() => matchMedia("(prefers-reduced-transparency: reduce)").matches)).toBe(true);
|
||||
await panel.getByRole("button", { name: "选择 Agent 模型" }).click();
|
||||
const menu = page.getByRole("menu");
|
||||
await expectThemeColor(menu, "background-color", "--palette-paper");
|
||||
await expect(menu).toHaveCSS("backdrop-filter", "none");
|
||||
await page.keyboard.press("Escape");
|
||||
await panel.getByRole("button", { name: "打开 Agent 历史记录" }).click();
|
||||
const history = panel.getByRole("region", { name: "Agent 历史记录" });
|
||||
await expectThemeColor(panel.locator(".agent-panel-header"), "background-color", "--palette-paper");
|
||||
await expect(panel.locator(".agent-panel-header")).toHaveCSS("backdrop-filter", "none");
|
||||
await expect(history).toHaveCSS("backdrop-filter", "none");
|
||||
});
|
||||
|
||||
test("desktop Agent history remains usable inside the docked navigation pane", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
|
||||
const agentPanel = page.locator('aside[aria-label="Agent 命令面板"]');
|
||||
const historyButton = agentPanel.getByRole("button", {
|
||||
name: "打开 Agent 历史记录"
|
||||
});
|
||||
|
||||
await historyButton.click();
|
||||
|
||||
const history = agentPanel.getByRole("region", { name: "Agent 历史记录" });
|
||||
await expect(history).toBeVisible();
|
||||
await waitForAnimations(history);
|
||||
await expect(historyButton).toHaveAttribute("aria-expanded", "true");
|
||||
await expectUnifiedHistoryStructure(history);
|
||||
await expectThemeColor(agentPanel.locator(".agent-panel-header"), "background-color", "--agent-dock-overlay-surface");
|
||||
await expectAcrylicSurface(agentPanel.locator(".agent-panel-header"));
|
||||
await expect(history).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
await expect(history).toHaveCSS("backdrop-filter", "none");
|
||||
|
||||
await history.getByRole("button", { name: "重命名对话" }).first().click();
|
||||
const renameInput = history.locator("input").first();
|
||||
const editingItem = renameInput.locator(
|
||||
"xpath=ancestor::*[contains(@class, 'agent-history-item')][1]"
|
||||
);
|
||||
await expect(renameInput).toBeFocused();
|
||||
await expect(renameInput).toHaveCSS("box-shadow", "none");
|
||||
await expectThemeColor(renameInput, "border-color", "--action-blue-hover");
|
||||
await expect(editingItem).toHaveCSS("border-color", "rgba(0, 0, 0, 0)");
|
||||
await page.screenshot({ path: "test-results/agent-history-ui/desktop-rename.png" });
|
||||
await history.getByRole("button", { name: "取消重命名" }).click();
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(history).toBeHidden();
|
||||
await expect(historyButton).toBeFocused();
|
||||
await expect(historyButton).toHaveAttribute("aria-expanded", "false");
|
||||
});
|
||||
|
||||
test("desktop Agent press feedback keeps pointer clicks and keyboard activation stable", async ({ page }) => {
|
||||
test("Agent history keeps rename, cancel and focus restoration usable", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
await waitForAnimations(panel);
|
||||
const trigger = panel.getByRole("button", { name: "打开 Agent 历史记录" });
|
||||
const history = panel.getByRole("region", { name: "Agent 历史记录" });
|
||||
const before = await trigger.boundingBox();
|
||||
await page.mouse.move(before!.x + before!.width / 2, before!.y + before!.height / 2);
|
||||
await page.mouse.down();
|
||||
const pressed = await trigger.boundingBox();
|
||||
expect(pressed!.y).toBe(before!.y + 1);
|
||||
expect(pressed!.width).toBe(before!.width);
|
||||
await page.mouse.up();
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "true");
|
||||
await trigger.click();
|
||||
const history = page.getByRole("region", { name: "Agent 历史记录" });
|
||||
await expect(history).toBeVisible();
|
||||
await waitForAnimations(panel);
|
||||
|
||||
await page.keyboard.press("Tab");
|
||||
await trigger.focus();
|
||||
expect(await trigger.evaluate((element) => element.matches(":focus-visible"))).toBe(true);
|
||||
const focused = await trigger.boundingBox();
|
||||
await page.keyboard.down("Space");
|
||||
expect(await trigger.boundingBox()).toEqual(focused);
|
||||
await page.keyboard.up("Space");
|
||||
await history.getByRole("button", { name: /会话操作/ }).first().click();
|
||||
await page.getByRole("menuitem", { name: "重命名对话", exact: true }).click();
|
||||
await expect(history.getByRole("textbox")).toBeFocused();
|
||||
await history.getByRole("textbox").fill("新的供水核验主题");
|
||||
await history.getByRole("button", { name: "取消重命名" }).click();
|
||||
await expect(history.getByText("城南供水压力复核", { exact: true })).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(history).toBeHidden();
|
||||
await expect(trigger).toBeFocused();
|
||||
});
|
||||
|
||||
test("mobile Agent mirrors the desktop floating history surface", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
test("Agent navigation responds to both pointer and keyboard activation", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
const trigger = panel.getByRole("button", { name: "打开 Agent 历史记录" });
|
||||
await trigger.click();
|
||||
await expect(panel.getByRole("button", { name: "打开 Agent 历史记录", includeHidden: true })).toHaveAttribute("aria-expanded", "true");
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(trigger).toBeFocused();
|
||||
await page.keyboard.press("Space");
|
||||
await expect(page.getByRole("dialog", { name: "历史对话" }).getByRole("searchbox")).toBeFocused();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(trigger).toHaveAttribute("aria-expanded", "false");
|
||||
await expect(trigger).toBeFocused();
|
||||
});
|
||||
|
||||
test("mobile Agent opens full height with an unobstructed message region and layered Escape", async ({
|
||||
page
|
||||
}) => {
|
||||
await page.setViewportSize({ width: 320, height: 844 });
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: "打开 Agent 面板" }).click();
|
||||
|
||||
const agentPanel = page.locator('aside[aria-label="Agent 命令面板"]').filter({ visible: true });
|
||||
await expect(agentPanel).toBeVisible();
|
||||
await waitForAnimations(agentPanel);
|
||||
const mobileSheet = page.getByRole("region", { name: "Agent 工作台抽屉" });
|
||||
const sheetHandle = mobileSheet.locator(".mobile-workbench-sheet-handle");
|
||||
const agentHeader = agentPanel.locator(".agent-panel-header");
|
||||
const agentConversation = agentPanel.getByRole("log");
|
||||
const historyButton = agentPanel.getByRole("button", {
|
||||
name: "打开 Agent 历史记录"
|
||||
const sheet = page.getByRole("region", { name: "Agent 工作台抽屉" });
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
await expect(sheet).toHaveAttribute("data-snap", "full");
|
||||
await expect(panel).toBeVisible();
|
||||
const header = panel.locator(".agent-panel-header");
|
||||
await expect(header.getByLabel("Agent 状态")).toHaveCSS("width", "64px");
|
||||
const controls = await panel
|
||||
.locator(".agent-panel-header > div")
|
||||
.first()
|
||||
.getByRole("button")
|
||||
.evaluateAll((elements) =>
|
||||
elements.map((element) => {
|
||||
const { left, right } = element.getBoundingClientRect();
|
||||
return { left, right };
|
||||
})
|
||||
);
|
||||
for (let index = 0; index < controls.length; index += 1) {
|
||||
expect(controls[index].left).toBeGreaterThanOrEqual(0);
|
||||
expect(controls[index].right).toBeLessThanOrEqual(320);
|
||||
if (index > 0) expect(controls[index].left).toBeGreaterThanOrEqual(controls[index - 1].right);
|
||||
}
|
||||
const headerBounds = (await header.boundingBox())!;
|
||||
const contextBounds = (await panel.locator("[data-agent-header-divider]").boundingBox())!;
|
||||
await page.screenshot({
|
||||
path: "/tmp/nexttjwater-orb-64/header-320.png",
|
||||
clip: { x: 0, y: headerBounds.y, width: 320, height: contextBounds.y + contextBounds.height - headerBounds.y }
|
||||
});
|
||||
const headerBefore = await agentHeader.boundingBox();
|
||||
const conversationBefore = await agentConversation.boundingBox();
|
||||
const historyButtonPresentationBefore = await getSurfacePresentation(historyButton);
|
||||
const mobileSheetFrameBefore = await expectAcrylicOutline(mobileSheet);
|
||||
|
||||
await historyButton.click();
|
||||
|
||||
const history = agentPanel.getByRole("region", { name: "Agent 历史记录" });
|
||||
await expect(history).toBeVisible();
|
||||
await waitForAnimations(history);
|
||||
await page.mouse.move(10, 100);
|
||||
await expectAttachedHistoryExtension(history);
|
||||
await expectTransparentExpandedHeader(agentHeader, mobileSheet);
|
||||
await expectUnifiedHistoryStructure(history);
|
||||
await expect.poll(() => getAcrylicFrame(mobileSheet)).toEqual(mobileSheetFrameBefore);
|
||||
await expect(agentPanel).toHaveCSS("box-shadow", "none");
|
||||
await expect
|
||||
.poll(() => getSurfacePresentation(historyButton))
|
||||
.toEqual(historyButtonPresentationBefore);
|
||||
await expect(sheetHandle).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
|
||||
const panelBox = await agentPanel.boundingBox();
|
||||
const headerAfter = await agentHeader.boundingBox();
|
||||
const historyBox = await history.boundingBox();
|
||||
const conversationAfter = await agentConversation.boundingBox();
|
||||
expect(panelBox).not.toBeNull();
|
||||
expect(headerBefore).not.toBeNull();
|
||||
expect(headerAfter).not.toBeNull();
|
||||
expect(historyBox).not.toBeNull();
|
||||
expect(conversationBefore).not.toBeNull();
|
||||
expect(conversationAfter).not.toBeNull();
|
||||
expectRoundedValue(headerAfter!.y, headerBefore!.y);
|
||||
expectRoundedValue(headerAfter!.x, panelBox!.x);
|
||||
expectRoundedValue(headerAfter!.y, panelBox!.y - 28);
|
||||
expectRoundedValue(headerAfter!.width, panelBox!.width);
|
||||
expect(headerAfter!.height).toBeGreaterThan(headerBefore!.height);
|
||||
expectRoundedValue(historyBox!.y, headerBefore!.y + headerBefore!.height);
|
||||
expectRoundedValue(historyBox!.width, headerAfter!.width);
|
||||
expect(headerAfter!.height).toBeLessThan(panelBox!.height);
|
||||
expectRoundedBox(conversationAfter!, conversationBefore!);
|
||||
await expect(agentConversation).toHaveClass(/agent-panel-conversation-canvas/);
|
||||
await expect(agentPanel.locator(".agent-panel-composer")).toBeVisible();
|
||||
await page.screenshot({ path: "test-results/agent-history-ui/mobile-history.png" });
|
||||
|
||||
const model = panel.getByRole("button", { name: "选择 Agent 模型" });
|
||||
await model.click();
|
||||
await expect(page.getByRole("menu")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(history).toBeHidden();
|
||||
await expect(page.getByRole("menu")).toBeHidden();
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(model).toBeFocused();
|
||||
const historyButton = panel.getByRole("button", { name: "打开 Agent 历史记录" });
|
||||
await historyButton.click();
|
||||
await expect(page.getByRole("region", { name: "Agent 历史记录" })).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByRole("region", { name: "Agent 历史记录" })).toBeHidden();
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(historyButton).toBeFocused();
|
||||
});
|
||||
|
||||
test("docked Agent respects forced color mode", async ({ page }) => {
|
||||
test("Agent keeps system borders and visible controls in forced color mode", async ({ page }) => {
|
||||
await page.emulateMedia({ forcedColors: "active" });
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
|
||||
const navigation = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
const workspace = page.locator("#main-workspace");
|
||||
await expect(navigation).toBeVisible();
|
||||
await expect(navigation).toHaveCSS("backdrop-filter", "none");
|
||||
await expect(navigation).toHaveCSS("box-shadow", "none");
|
||||
await expect(navigation).toHaveCSS("border-right-width", "1px");
|
||||
await expect(navigation).toHaveCSS("border-right-color", "rgb(0, 0, 0)");
|
||||
await expect(workspace).toBeVisible();
|
||||
await expect(page.getByRole("toolbar", { name: "工作区窗口任务栏" })).toHaveCount(0);
|
||||
await page.screenshot({ path: "test-results/agent-history-ui/forced-colors.png" });
|
||||
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
|
||||
await expect(panel).toHaveCSS("backdrop-filter", "none");
|
||||
await expect(panel).toHaveCSS("border-right-width", "1px");
|
||||
await expect(panel).toHaveCSS("border-right-color", "rgb(0, 0, 0)");
|
||||
await expect(panel.getByRole("button", { name: "发送 Agent 指令" })).toBeVisible();
|
||||
await panel.getByRole("button", { name: "打开 Agent 历史记录" }).click();
|
||||
const history = page.getByRole("dialog", { name: "历史对话", exact: true });
|
||||
await expect(history).toHaveCSS("background-color", "rgb(255, 255, 255)");
|
||||
await expect(history).toHaveCSS("border-top-color", "rgb(0, 0, 0)");
|
||||
});
|
||||
|
||||
test("long history titles keep rename and explicit delete usable across viewport sizes", async ({
|
||||
@@ -349,25 +215,25 @@ test("long history titles keep rename and explicit delete usable across viewport
|
||||
let title = originalTitle;
|
||||
let deleted = false;
|
||||
const writes: Array<{ method: string; path: string; body: unknown }> = [];
|
||||
await page.route(/\/api\/v1\/agent\/chat\/sessions(?:\?.*)?$/, (route) =>
|
||||
await page.route(/\/api\/v1\/sessions(?:\?.*)?$/, (route) =>
|
||||
route.request().method() === "OPTIONS"
|
||||
? route.fallback()
|
||||
: route.fulfill({
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
json: {
|
||||
sessions: deleted
|
||||
? []
|
||||
: [
|
||||
{
|
||||
session_id: sessionId,
|
||||
title,
|
||||
created_at: 100,
|
||||
updated_at: 200,
|
||||
run_status: "completed"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
json: {
|
||||
sessions: deleted
|
||||
? []
|
||||
: [
|
||||
{
|
||||
session_id: sessionId,
|
||||
title,
|
||||
created_at: 100,
|
||||
updated_at: 200,
|
||||
run_status: "completed"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
await page.route("**/api/v1/session/**", async (route) => {
|
||||
const request = route.request();
|
||||
@@ -441,14 +307,15 @@ test("long history titles keep rename and explicit delete usable across viewport
|
||||
await expect(approvalButton).toBeFocused();
|
||||
const historyButton = panel.getByRole("button", { name: "打开 Agent 历史记录" });
|
||||
await historyButton.click();
|
||||
const history = panel.getByRole("region", { name: "Agent 历史记录" });
|
||||
const history = page.getByRole("region", { name: "Agent 历史记录" });
|
||||
await expect(history.locator(".agent-history-session-trigger")).toContainText(originalTitle);
|
||||
await history.getByRole("button", { name: "重命名对话", exact: true }).click();
|
||||
await history.getByRole("button", { name: /会话操作/ }).click();
|
||||
await page.getByRole("menuitem", { name: "重命名对话", exact: true }).click();
|
||||
const input = history.getByRole("textbox");
|
||||
await expect(input).toBeFocused();
|
||||
await expectThemeColor(input, "border-color", "--action-blue-hover");
|
||||
await expectThemeColor(input, "border-color", "--field-focus-border");
|
||||
await input.fill(`${originalTitle}(已复核)`);
|
||||
await expect(history.getByRole("button", { name: "保存对话主题" })).toBeVisible();
|
||||
await expect(history.getByRole("button", { name: "保存对话标题" })).toBeVisible();
|
||||
await waitForAnimations(history);
|
||||
const box = await history.boundingBox();
|
||||
expect(box!.x).toBeGreaterThanOrEqual(0);
|
||||
@@ -457,7 +324,7 @@ test("long history titles keep rename and explicit delete usable across viewport
|
||||
true
|
||||
);
|
||||
const inputBox = await input.boundingBox();
|
||||
const saveBox = await history.getByRole("button", { name: "保存对话主题" }).boundingBox();
|
||||
const saveBox = await history.getByRole("button", { name: "保存对话标题" }).boundingBox();
|
||||
expect(inputBox!.width).toBeGreaterThan(40);
|
||||
expect(inputBox!.x + inputBox!.width).toBeLessThanOrEqual(saveBox!.x);
|
||||
await page.screenshot({ path: `test-results/agent-history-ui/${width}-long-rename.png` });
|
||||
@@ -465,9 +332,10 @@ test("long history titles keep rename and explicit delete usable across viewport
|
||||
await expect(history.locator(".agent-history-session-trigger")).toContainText(originalTitle);
|
||||
expect(writes).toEqual([]);
|
||||
|
||||
await history.getByRole("button", { name: "重命名对话", exact: true }).click();
|
||||
await history.getByRole("button", { name: /会话操作/ }).click();
|
||||
await page.getByRole("menuitem", { name: "重命名对话", exact: true }).click();
|
||||
await input.fill("城南压力复核(已复核)");
|
||||
await history.getByRole("button", { name: "保存对话主题" }).click();
|
||||
await history.getByRole("button", { name: "保存对话标题" }).click();
|
||||
await expect(history.locator(".agent-history-session-trigger")).toContainText(
|
||||
"城南压力复核(已复核)"
|
||||
);
|
||||
@@ -478,12 +346,14 @@ test("long history titles keep rename and explicit delete usable across viewport
|
||||
body: { title: "城南压力复核(已复核)", is_title_manually_edited: true }
|
||||
}
|
||||
]);
|
||||
await history.getByRole("button", { name: "删除对话", exact: true }).click();
|
||||
await history.getByRole("button", { name: /会话操作/ }).click();
|
||||
await page.getByRole("menuitem", { name: "删除对话", exact: true }).click();
|
||||
await expect(history.getByText("删除后无法从历史记录中恢复")).toBeVisible();
|
||||
await expect(history.getByRole("button", { name: "确认", exact: true })).toBeVisible();
|
||||
await history.getByRole("button", { name: "取消删除" }).click();
|
||||
expect(writes).toHaveLength(1);
|
||||
await history.getByRole("button", { name: "删除对话", exact: true }).click();
|
||||
await history.getByRole("button", { name: /会话操作/ }).click();
|
||||
await page.getByRole("menuitem", { name: "删除对话", exact: true }).click();
|
||||
await history.getByRole("button", { name: "确认", exact: true }).click();
|
||||
await expect(history.getByText("暂无历史记录", { exact: true })).toBeVisible();
|
||||
expect(writes).toHaveLength(2);
|
||||
@@ -497,292 +367,3 @@ test("long history titles keep rename and explicit delete usable across viewport
|
||||
await expect(historyButton).toBeFocused();
|
||||
}
|
||||
});
|
||||
|
||||
async function expectAcrylicSurface(shell: Locator, allowedFilteredDescendant?: string) {
|
||||
const composition = await shell.evaluate((element, allowedSelector) => {
|
||||
const style = getComputedStyle(element);
|
||||
const filteredDescendants: string[] = [];
|
||||
|
||||
for (const descendant of element.querySelectorAll("*")) {
|
||||
if (
|
||||
getComputedStyle(descendant).backdropFilter !== "none" &&
|
||||
!(allowedSelector && descendant.matches(allowedSelector))
|
||||
) {
|
||||
filteredDescendants.push(descendant.className);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
backdropFilter: style.backdropFilter,
|
||||
backgroundColor: style.backgroundColor,
|
||||
filteredDescendants,
|
||||
transform: style.transform,
|
||||
willChange: style.willChange
|
||||
};
|
||||
}, allowedFilteredDescendant);
|
||||
|
||||
expect(composition.backdropFilter).toContain("blur(");
|
||||
expect(getAlpha(composition.backgroundColor)).toBeLessThan(1);
|
||||
expect(composition.filteredDescendants).toEqual([]);
|
||||
expect(composition.transform).toBe("none");
|
||||
expect(composition.willChange).toBe("auto");
|
||||
}
|
||||
|
||||
async function expectContextualAcrylicSurface(
|
||||
context: Locator,
|
||||
{
|
||||
backdropFilter = "none",
|
||||
maximumAlpha = 0.7,
|
||||
minimumAlpha = 0.35
|
||||
}: {
|
||||
backdropFilter?: string;
|
||||
maximumAlpha?: number;
|
||||
minimumAlpha?: number;
|
||||
} = {}
|
||||
) {
|
||||
const composition = await context.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
backdropFilter: style.backdropFilter,
|
||||
backgroundColor: style.backgroundColor,
|
||||
backgroundImage: style.backgroundImage,
|
||||
boxShadow: style.boxShadow
|
||||
};
|
||||
});
|
||||
|
||||
if (backdropFilter === "none") {
|
||||
expect(composition.backdropFilter).toBe("none");
|
||||
} else {
|
||||
expect(composition.backdropFilter).toContain(backdropFilter);
|
||||
}
|
||||
expect(getAlpha(composition.backgroundColor)).toBeGreaterThan(minimumAlpha);
|
||||
expect(getAlpha(composition.backgroundColor)).toBeLessThan(maximumAlpha);
|
||||
expect(composition.backgroundImage).not.toBe("none");
|
||||
expect(composition.boxShadow).not.toBe("none");
|
||||
}
|
||||
|
||||
async function expectAttachedHistoryExtension(history: Locator) {
|
||||
const presentation = await history.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
backdropFilter: style.backdropFilter,
|
||||
backgroundColor: style.backgroundColor,
|
||||
backgroundImage: style.backgroundImage,
|
||||
borderBottomLeftRadius: style.borderBottomLeftRadius,
|
||||
borderBottomRightRadius: style.borderBottomRightRadius,
|
||||
borderBottomWidth: style.borderBottomWidth,
|
||||
borderLeftWidth: style.borderLeftWidth,
|
||||
borderRightWidth: style.borderRightWidth,
|
||||
borderTopWidth: style.borderTopWidth,
|
||||
borderTopLeftRadius: style.borderTopLeftRadius,
|
||||
borderTopRightRadius: style.borderTopRightRadius,
|
||||
boxShadow: style.boxShadow
|
||||
};
|
||||
});
|
||||
|
||||
expect(presentation.backdropFilter).toContain("blur(");
|
||||
expect(getAlpha(presentation.backgroundColor)).toBeGreaterThanOrEqual(0.78);
|
||||
expect(getAlpha(presentation.backgroundColor)).toBeLessThanOrEqual(0.9);
|
||||
expect(presentation.backgroundImage).not.toBe("none");
|
||||
expect(presentation.borderBottomWidth).toBe("0px");
|
||||
expect(presentation.borderLeftWidth).toBe("0px");
|
||||
expect(presentation.borderRightWidth).toBe("0px");
|
||||
expect(presentation.borderTopWidth).toBe("0px");
|
||||
expect(presentation.borderTopLeftRadius).toBe("0px");
|
||||
expect(presentation.borderTopRightRadius).toBe("0px");
|
||||
expect(presentation.borderBottomLeftRadius).toBe("16px");
|
||||
expect(presentation.borderBottomRightRadius).toBe("16px");
|
||||
expect(presentation.boxShadow).toBe("none");
|
||||
}
|
||||
|
||||
async function getAcrylicFrame(surface: Locator) {
|
||||
return surface.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
borderBottomColor: style.borderBottomColor,
|
||||
borderLeftColor: style.borderLeftColor,
|
||||
borderRightColor: style.borderRightColor,
|
||||
borderTopColor: style.borderTopColor,
|
||||
boxShadow: style.boxShadow
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function expectAcrylicOutline(surface: Locator) {
|
||||
const frame = await getAcrylicFrame(surface);
|
||||
|
||||
expect(frame.borderTopColor).not.toBe("rgba(0, 0, 0, 0)");
|
||||
expect(frame.borderRightColor).not.toBe("rgba(0, 0, 0, 0)");
|
||||
expect(frame.borderBottomColor).not.toBe("rgba(0, 0, 0, 0)");
|
||||
expect(frame.borderLeftColor).not.toBe("rgba(0, 0, 0, 0)");
|
||||
expect(frame.boxShadow).toContain("0px 1px 0px 0px inset");
|
||||
expect(frame.boxShadow).toContain("0px 0px 0px 1px");
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
async function expectUnifiedHistoryStructure(history: Locator) {
|
||||
const structure = await history.evaluate((element) => {
|
||||
const paragraphs = Array.from(element.querySelectorAll("p"));
|
||||
const heading = paragraphs.find((paragraph) => paragraph.textContent?.trim() === "历史记录");
|
||||
const count = paragraphs.find((paragraph) =>
|
||||
/^(?:\d+ 个会话|暂无历史会话)$/.test(paragraph.textContent?.trim() ?? "")
|
||||
);
|
||||
|
||||
return {
|
||||
countTagName: count?.tagName ?? null,
|
||||
headingTagName: heading?.tagName ?? null,
|
||||
siblings: Boolean(heading && count && heading.parentElement === count.parentElement)
|
||||
};
|
||||
});
|
||||
|
||||
expect(structure).toEqual({
|
||||
countTagName: "P",
|
||||
headingTagName: "P",
|
||||
siblings: true
|
||||
});
|
||||
await expect(history.getByRole("button", { name: "重命名对话" }).first()).toBeVisible();
|
||||
await expect(history.getByRole("button", { name: "删除对话" }).first()).toBeVisible();
|
||||
}
|
||||
|
||||
async function expectTransparentExpandedHeader(header: Locator, agentSurface: Locator) {
|
||||
const [presentation, agentOutlineColor] = await Promise.all([
|
||||
header.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
backgroundColor: style.backgroundColor,
|
||||
borderBottomColor: style.borderBottomColor,
|
||||
borderBottomLeftRadius: style.borderBottomLeftRadius,
|
||||
borderBottomRightRadius: style.borderBottomRightRadius,
|
||||
borderBottomWidth: style.borderBottomWidth,
|
||||
borderLeftWidth: style.borderLeftWidth,
|
||||
borderRightWidth: style.borderRightWidth,
|
||||
borderTopWidth: style.borderTopWidth,
|
||||
boxShadow: style.boxShadow
|
||||
};
|
||||
}),
|
||||
readThemeColor(agentSurface, "--acrylic-outline")
|
||||
]);
|
||||
|
||||
expect(getAlpha(presentation.backgroundColor)).toBe(0);
|
||||
expect(presentation.borderBottomColor).toBe(agentOutlineColor);
|
||||
expect(presentation.borderBottomLeftRadius).toBe("16px");
|
||||
expect(presentation.borderBottomRightRadius).toBe("16px");
|
||||
expect(presentation.borderBottomWidth).toBe("1px");
|
||||
expect(presentation.borderLeftWidth).toBe("0px");
|
||||
expect(presentation.borderRightWidth).toBe("0px");
|
||||
expect(presentation.borderTopWidth).toBe("0px");
|
||||
expect(presentation.boxShadow).toContain("0px 12px 12px -12px");
|
||||
}
|
||||
|
||||
async function getSurfacePresentation(surface: Locator) {
|
||||
return surface.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
backdropFilter: style.backdropFilter,
|
||||
backgroundColor: style.backgroundColor,
|
||||
backgroundImage: style.backgroundImage,
|
||||
borderColor: style.borderColor,
|
||||
borderRadius: style.borderRadius,
|
||||
boxShadow: style.boxShadow,
|
||||
color: style.color
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getAlpha(color: string) {
|
||||
return readColorAlpha(color);
|
||||
}
|
||||
|
||||
async function expectLightweightMaterialContext(context: Locator) {
|
||||
const style = await context.evaluate((element) => {
|
||||
const computedStyle = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
backdropFilter: computedStyle.backdropFilter,
|
||||
backgroundColor: computedStyle.backgroundColor,
|
||||
webkitBackdropFilter: computedStyle.getPropertyValue("-webkit-backdrop-filter")
|
||||
};
|
||||
});
|
||||
|
||||
expect(style.backdropFilter).toBe("none");
|
||||
expect(getAlpha(style.backgroundColor)).toBeGreaterThan(0);
|
||||
expect(getAlpha(style.backgroundColor)).toBeLessThanOrEqual(0.3);
|
||||
expect(["", "none"]).toContain(style.webkitBackdropFilter);
|
||||
}
|
||||
|
||||
async function expectUnfilteredInternalSurfaces(surfaces: Locator) {
|
||||
const surfaceStyles = await surfaces.evaluateAll((elements) =>
|
||||
elements.map((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
backdropFilter: style.backdropFilter,
|
||||
className: element.className,
|
||||
webkitBackdropFilter: style.getPropertyValue("-webkit-backdrop-filter")
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
expect(surfaceStyles.length).toBeGreaterThan(0);
|
||||
for (const style of surfaceStyles) {
|
||||
expect(style.backdropFilter).toBe("none");
|
||||
expect(["", "none"]).toContain(style.webkitBackdropFilter);
|
||||
expect(String(style.className)).not.toContain("acrylic-");
|
||||
}
|
||||
}
|
||||
|
||||
function expectRoundedBox(actual: BoundingBox, expected: BoundingBox) {
|
||||
expect(Math.round(actual.x)).toBe(Math.round(expected.x));
|
||||
expect(Math.round(actual.y)).toBe(Math.round(expected.y));
|
||||
expect(Math.round(actual.width)).toBe(Math.round(expected.width));
|
||||
expect(Math.round(actual.height)).toBe(Math.round(expected.height));
|
||||
}
|
||||
|
||||
function expectRoundedValue(actual: number, expected: number) {
|
||||
expect(Math.abs(Math.round(actual) - Math.round(expected))).toBeLessThanOrEqual(1);
|
||||
}
|
||||
|
||||
async function getFloatingSurfaceStyles(header: Locator, composer: Locator) {
|
||||
const control = composer.locator(".agent-panel-control");
|
||||
const [headerStyles, composerStyles, controlStyles] = await Promise.all([
|
||||
header.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
shadow: style.boxShadow,
|
||||
transform: style.transform
|
||||
};
|
||||
}),
|
||||
composer.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
shadow: style.boxShadow,
|
||||
transform: style.transform
|
||||
};
|
||||
}),
|
||||
control.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
|
||||
return {
|
||||
borderColor: style.borderColor,
|
||||
boxShadow: style.boxShadow
|
||||
};
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
composerShadow: composerStyles.shadow,
|
||||
composerTransform: composerStyles.transform,
|
||||
controlBorderColor: controlStyles.borderColor,
|
||||
controlBoxShadow: controlStyles.boxShadow,
|
||||
headerShadow: headerStyles.shadow,
|
||||
headerTransform: headerStyles.transform
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user