openclaw-vainplex/ui/src/ui/navigation.browser.test.ts
2026-01-02 15:13:05 +01:00

102 lines
3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ClawdisApp } from "./app";
const originalConnect = ClawdisApp.prototype.connect;
function mountApp(pathname: string) {
window.history.replaceState({}, "", pathname);
const app = document.createElement("clawdis-app") as ClawdisApp;
document.body.append(app);
return app;
}
function nextFrame() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => resolve());
});
}
beforeEach(() => {
ClawdisApp.prototype.connect = () => {
// no-op: avoid real gateway WS connections in browser tests
};
document.body.innerHTML = "";
});
afterEach(() => {
ClawdisApp.prototype.connect = originalConnect;
document.body.innerHTML = "";
});
describe("control UI routing", () => {
it("hydrates the tab from the location", async () => {
const app = mountApp("/sessions");
await app.updateComplete;
expect(app.tab).toBe("sessions");
expect(window.location.pathname).toBe("/sessions");
});
it("respects /ui base paths", async () => {
const app = mountApp("/ui/cron");
await app.updateComplete;
expect(app.basePath).toBe("/ui");
expect(app.tab).toBe("cron");
expect(window.location.pathname).toBe("/ui/cron");
});
it("updates the URL when clicking nav items", async () => {
const app = mountApp("/chat");
await app.updateComplete;
const link = app.querySelector<HTMLAnchorElement>(
'a.nav-item[href="/connections"]',
);
expect(link).not.toBeNull();
link?.dispatchEvent(
new MouseEvent("click", { bubbles: true, cancelable: true, button: 0 }),
);
await app.updateComplete;
expect(app.tab).toBe("connections");
expect(window.location.pathname).toBe("/connections");
});
it("auto-scrolls chat history to the latest message", async () => {
const app = mountApp("/chat");
await app.updateComplete;
const initialContainer = app.querySelector(".chat-thread") as HTMLElement | null;
expect(initialContainer).not.toBeNull();
if (!initialContainer) return;
initialContainer.style.maxHeight = "180px";
initialContainer.style.overflow = "auto";
app.chatMessages = Array.from({ length: 60 }, (_, index) => ({
role: "assistant",
content: `Line ${index} - ${"x".repeat(200)}`,
timestamp: Date.now() + index,
}));
await app.updateComplete;
await nextFrame();
const container = app.querySelector(".chat-thread") as HTMLElement | null;
expect(container).not.toBeNull();
if (!container) return;
const maxScroll = container.scrollHeight - container.clientHeight;
expect(maxScroll).toBeGreaterThan(0);
expect(container.scrollTop).toBe(maxScroll);
});
it("hydrates token from URL params and strips it", async () => {
const app = mountApp("/ui/overview?token=abc123");
await app.updateComplete;
expect(app.settings.token).toBe("abc123");
expect(window.location.pathname).toBe("/ui/overview");
expect(window.location.search).toBe("");
});
});