openclaw-matrix-multiaccounts/ui/src/ui/navigation.browser.test.ts
2026-01-07 20:20:32 +01:00

153 lines
4.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ClawdbotApp } from "./app";
const originalConnect = ClawdbotApp.prototype.connect;
function mountApp(pathname: string) {
window.history.replaceState({}, "", pathname);
const app = document.createElement("clawdbot-app") as ClawdbotApp;
document.body.append(app);
return app;
}
function nextFrame() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => resolve());
});
}
beforeEach(() => {
ClawdbotApp.prototype.connect = () => {
// no-op: avoid real gateway WS connections in browser tests
};
window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined;
localStorage.clear();
document.body.innerHTML = "";
});
afterEach(() => {
ClawdbotApp.prototype.connect = originalConnect;
window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined;
localStorage.clear();
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("infers nested base paths", async () => {
const app = mountApp("/apps/clawdbot/cron");
await app.updateComplete;
expect(app.basePath).toBe("/apps/clawdbot");
expect(app.tab).toBe("cron");
expect(window.location.pathname).toBe("/apps/clawdbot/cron");
});
it("honors explicit base path overrides", async () => {
window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = "/clawdbot";
const app = mountApp("/clawdbot/sessions");
await app.updateComplete;
expect(app.basePath).toBe("/clawdbot");
expect(app.tab).toBe("sessions");
expect(window.location.pathname).toBe("/clawdbot/sessions");
});
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;
for (let i = 0; i < 6; i++) {
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);
for (let i = 0; i < 10; i++) {
if (container.scrollTop === maxScroll) break;
await nextFrame();
}
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("");
});
it("hydrates password from URL params and strips it", async () => {
const app = mountApp("/ui/overview?password=sekret");
await app.updateComplete;
expect(app.password).toBe("sekret");
expect(window.location.pathname).toBe("/ui/overview");
expect(window.location.search).toBe("");
});
it("strips auth params even when settings already set", async () => {
localStorage.setItem(
"clawdbot.control.settings.v1",
JSON.stringify({ token: "existing-token" }),
);
const app = mountApp("/ui/overview?token=abc123");
await app.updateComplete;
expect(app.settings.token).toBe("existing-token");
expect(window.location.pathname).toBe("/ui/overview");
expect(window.location.search).toBe("");
});
});