feat: migrate drainage frontend to Vite
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("renders the migrated WebGIS workbench shell", async ({ page }) => {
|
||||
const riveRequests: string[] = [];
|
||||
page.on("request", (request) => {
|
||||
if (/\.riv(?:\?|$)/.test(request.url())) {
|
||||
riveRequests.push(request.url());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/");
|
||||
await expect(page.getByText("排水管网智能调度平台")).toBeVisible();
|
||||
await expect(page.getByText("调度工作台")).toBeVisible();
|
||||
await expect(page.getByLabel("推荐问题")).toBeVisible();
|
||||
|
||||
const runtimeConfig = await page.evaluate(() => globalThis.__DRAINAGE_CONFIG__);
|
||||
expect(runtimeConfig).toMatchObject({
|
||||
DRAINAGE_MAP_URL: expect.any(String),
|
||||
DRAINAGE_GEOSERVER_WORKSPACE: expect.any(String),
|
||||
DRAINAGE_AGENT_API_BASE_URL: expect.any(String),
|
||||
DRAINAGE_TTS_API_URL: expect.any(String)
|
||||
});
|
||||
expect(Object.keys(runtimeConfig as Record<string, unknown>)).not.toContainEqual(
|
||||
expect.stringMatching(/^(NEXT_PUBLIC_|VITE_)/)
|
||||
);
|
||||
|
||||
const mapAsset = await page.request.get("/icons/scada-integrated-monitoring.png");
|
||||
expect(mapAsset.ok()).toBe(true);
|
||||
expect(mapAsset.headers()["content-type"]).toBe("image/png");
|
||||
await expect(page.getByLabel("Agent 状态").first()).toBeVisible();
|
||||
await expect.poll(() => riveRequests.length, { timeout: 25_000 }).toBe(1);
|
||||
});
|
||||
|
||||
test("animates the compact header menu without overflowing", async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "no-preference" });
|
||||
await page.setViewportSize({ width: 375, height: 812 });
|
||||
await page.goto("/");
|
||||
|
||||
await page.locator('button[aria-label^="查看异常处置面板"]:visible').click();
|
||||
|
||||
const menu = page.locator('[role="menu"][data-state="open"]');
|
||||
await expect(menu).toBeVisible();
|
||||
|
||||
const animation = await menu.evaluate((element) => {
|
||||
const style = window.getComputedStyle(element);
|
||||
return {
|
||||
duration: Number.parseFloat(style.animationDuration) * 1_000,
|
||||
name: style.animationName
|
||||
};
|
||||
});
|
||||
expect(animation.name).toContain("enter");
|
||||
expect(animation.duration).toBeGreaterThan(0);
|
||||
|
||||
const menuBounds = await menu.boundingBox();
|
||||
expect(menuBounds).not.toBeNull();
|
||||
expect(menuBounds!.x).toBeGreaterThanOrEqual(0);
|
||||
expect(menuBounds!.x + menuBounds!.width).toBeLessThanOrEqual(375);
|
||||
});
|
||||
|
||||
test("renders map notices through a single toaster", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.locator("[data-sonner-toaster]")).toHaveCount(1);
|
||||
await page.getByLabel("打开用户菜单").click();
|
||||
await page.getByRole("menuitem", { name: /查看运行状态/ }).click();
|
||||
|
||||
await expect(page.locator("[data-sonner-toast]")).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("preserves compact typography in header and agent controls", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.goto("/");
|
||||
|
||||
const headerControl = page.locator("header.acrylic-navigation button.font-semibold:visible").first();
|
||||
const approvalControl = page.getByRole("button", { name: "权限批准模式" });
|
||||
const suggestion = page.locator("[aria-label=推荐问题] button").first();
|
||||
|
||||
await expect(headerControl).toHaveCSS("font-weight", "600");
|
||||
await expect(approvalControl).toHaveCSS("font-size", "12px");
|
||||
await expect(approvalControl).toHaveCSS("line-height", "16px");
|
||||
await expect(suggestion).toHaveCSS("font-size", "12px");
|
||||
await expect(suggestion).toHaveCSS("font-weight", "500");
|
||||
});
|
||||
|
||||
test("lets interactive utilities override surface materials", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.addStyleTag({ content: "*,*::before,*::after{transition:none!important}" });
|
||||
|
||||
const suggestion = page.locator("[aria-label=推荐问题] button").first();
|
||||
const restingBackground = await suggestion.evaluate(
|
||||
(element) => window.getComputedStyle(element).backgroundColor
|
||||
);
|
||||
|
||||
await suggestion.hover();
|
||||
|
||||
const hoverBackground = await suggestion.evaluate(
|
||||
(element) => window.getComputedStyle(element).backgroundColor
|
||||
);
|
||||
expect(hoverBackground).not.toBe(restingBackground);
|
||||
});
|
||||
|
||||
test("preserves v3 menu and panel styling through Tailwind v4", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByLabel("打开用户菜单").click();
|
||||
const menu = page.locator('[role="menu"][data-state="open"]');
|
||||
const menuTransformOrigin = await menu.evaluate(
|
||||
(element) => window.getComputedStyle(element).transformOrigin
|
||||
);
|
||||
expect(menuTransformOrigin).not.toBe("50% 50%");
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
const agentCommandControl = page.locator(".agent-panel-control.shadow-xs").first();
|
||||
const commandShadow = await agentCommandControl.evaluate(
|
||||
(element) => window.getComputedStyle(element).boxShadow
|
||||
);
|
||||
expect(commandShadow).toContain("rgba(0, 0, 0, 0.05) 0px 1px 2px 0px");
|
||||
expect(commandShadow).not.toContain("0px 1px 3px 0px");
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { MapToaster } from "@/features/map/core/components/notice";
|
||||
import { MapWorkbenchPage } from "@/features/workbench";
|
||||
import { AppProviders } from "./providers";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<AppProviders>
|
||||
<MapWorkbenchPage />
|
||||
<MapToaster />
|
||||
</AppProviders>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { SWRConfig } from "swr";
|
||||
|
||||
async function fetcher<T>(url: string): Promise<T> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export function AppProviders({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
fetcher,
|
||||
revalidateOnFocus: false,
|
||||
shouldRetryOnError: false
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SWRConfig>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user