216 lines
6.3 KiB
TypeScript
216 lines
6.3 KiB
TypeScript
import { cleanup, render, screen } from "@testing-library/react";
|
|
import type { ReactNode } from "react";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
let pathname = "/settings/integrations/github";
|
|
const COPY_CONFIG_TEST_ID = "mock-copy-config";
|
|
|
|
const state = {
|
|
configChat: { isOpen: false },
|
|
workspaces: {
|
|
activeId: "ws-1",
|
|
items: [
|
|
{ id: "ws-1", name: "Default" },
|
|
{ id: "ws-2", name: "Archive" },
|
|
],
|
|
},
|
|
setActiveWorkspace: vi.fn(),
|
|
};
|
|
|
|
vi.mock("@/lib/routing/client-router", () => ({
|
|
usePathname: () => pathname,
|
|
useRouter: () => ({ replace: vi.fn() }),
|
|
useSearchParams: () => new URLSearchParams(),
|
|
}));
|
|
|
|
vi.mock("@/components/state-provider", () => ({
|
|
useAppStore: (selector: (s: typeof state) => unknown) => selector(state),
|
|
useOptionalAppStore: (selector: (s: typeof state) => unknown, fallback: unknown) =>
|
|
selector(state) ?? fallback,
|
|
}));
|
|
|
|
vi.mock("@/components/page-topbar", () => ({
|
|
PageTopbar: ({
|
|
actions,
|
|
parents,
|
|
title,
|
|
}: {
|
|
actions?: ReactNode;
|
|
parents?: Array<{ label: string; href: string }>;
|
|
title: string;
|
|
}) => (
|
|
<div>
|
|
<nav data-testid="page-topbar-breadcrumbs">
|
|
{parents?.map((parent) => (
|
|
<a key={parent.href} href={parent.href}>
|
|
{parent.label}
|
|
</a>
|
|
))}
|
|
<span data-testid="page-topbar-title">{title}</span>
|
|
</nav>
|
|
<div data-testid="page-topbar-actions">{actions}</div>
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
vi.mock("@kandev/ui/tooltip", () => ({
|
|
TooltipProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
|
|
}));
|
|
|
|
vi.mock("@/components/integrations/integration-copy-config-menu", () => ({
|
|
IntegrationCopyConfigMenu: ({ sourceWorkspaceId }: { sourceWorkspaceId: string }) => (
|
|
<div data-testid={COPY_CONFIG_TEST_ID} data-source-workspace-id={sourceWorkspaceId} />
|
|
),
|
|
}));
|
|
|
|
import { SettingsLayoutClient } from "./settings-layout-client";
|
|
import { useSettingsSaveContributor } from "./settings-save-provider";
|
|
import { i18n } from "@/lib/i18n";
|
|
|
|
function DirtySettings() {
|
|
useSettingsSaveContributor({
|
|
id: "dirty-settings",
|
|
revision: 1,
|
|
isDirty: true,
|
|
save: vi.fn(),
|
|
discard: vi.fn(),
|
|
});
|
|
return <div>Dirty settings</div>;
|
|
}
|
|
|
|
describe("SettingsLayoutClient", () => {
|
|
beforeEach(() => {
|
|
pathname = "/settings/integrations/github";
|
|
state.workspaces.activeId = "ws-1";
|
|
state.setActiveWorkspace.mockClear();
|
|
});
|
|
|
|
afterEach(() => cleanup());
|
|
|
|
it("keeps copy config available without rendering the workspace switcher", () => {
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<div>Settings page</div>
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
expect(screen.queryByTestId("integration-workspace-switcher")).toBeNull();
|
|
expect(screen.getByTestId(COPY_CONFIG_TEST_ID).dataset.sourceWorkspaceId).toBe("ws-1");
|
|
});
|
|
|
|
it("shows copy config on workspace-scoped integration pages", () => {
|
|
pathname = "/settings/workspace/ws-1/integrations/github";
|
|
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<div>Settings page</div>
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
expect(screen.getByTestId(COPY_CONFIG_TEST_ID).dataset.sourceWorkspaceId).toBe("ws-1");
|
|
});
|
|
|
|
it("uses the workspace from scoped integration routes before store hydration catches up", () => {
|
|
pathname = "/settings/workspace/ws-2/integrations/github";
|
|
state.workspaces.activeId = "ws-1";
|
|
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<div>Settings page</div>
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
expect(screen.getByTestId(COPY_CONFIG_TEST_ID).dataset.sourceWorkspaceId).toBe("ws-2");
|
|
});
|
|
|
|
it("falls back to the active workspace when a scoped route has invalid encoding", () => {
|
|
pathname = "/settings/workspace/%E0%A4%A/integrations/github";
|
|
state.workspaces.activeId = "ws-1";
|
|
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<div>Settings page</div>
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
expect(screen.getByTestId(COPY_CONFIG_TEST_ID).dataset.sourceWorkspaceId).toBe("ws-1");
|
|
});
|
|
|
|
it("hosts the route save action and reserves safe-area scroll space", async () => {
|
|
pathname = "/settings/general/appearance";
|
|
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<DirtySettings />
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
expect(await screen.findByTestId("settings-floating-save")).toBeTruthy();
|
|
expect(screen.getByTestId("settings-scroll-container").className).toContain(
|
|
"safe-area-inset-bottom",
|
|
);
|
|
expect(screen.getByTestId("settings-scroll-container").className).toContain(
|
|
"app-status-bar-height",
|
|
);
|
|
});
|
|
|
|
it("translates the Message Queue breadcrumb and keeps the shared scroll owner", async () => {
|
|
pathname = "/settings/general/message-queue";
|
|
await i18n.changeLanguage("pseudo");
|
|
try {
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<div>Queue settings</div>
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
expect(screen.getByTestId("page-topbar-title").textContent).toBe("Ḿēśśàĝē Qũēũē");
|
|
expect(screen.getByTestId("settings-scroll-container").className).toContain(
|
|
"overflow-y-auto",
|
|
);
|
|
} finally {
|
|
await i18n.changeLanguage("en");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("SettingsLayoutClient workspace breadcrumbs", () => {
|
|
beforeEach(() => {
|
|
pathname = "/settings/workspace/ws-2/secrets";
|
|
state.workspaces.activeId = "ws-1";
|
|
state.setActiveWorkspace.mockClear();
|
|
});
|
|
|
|
afterEach(() => cleanup());
|
|
|
|
it("includes the workspace name in workspace-scoped breadcrumbs", () => {
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<div>Settings page</div>
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
expect(screen.getByTestId("page-topbar-breadcrumbs").textContent).toBe(
|
|
"SettingsArchiveSecrets",
|
|
);
|
|
expect(screen.getByRole("link", { name: "Archive" }).getAttribute("href")).toBe(
|
|
"/settings/workspace/ws-2",
|
|
);
|
|
});
|
|
|
|
it("keeps the automations parent after the workspace breadcrumb", () => {
|
|
pathname = "/settings/workspace/ws-2/automations/new";
|
|
|
|
render(
|
|
<SettingsLayoutClient>
|
|
<div>Settings page</div>
|
|
</SettingsLayoutClient>,
|
|
);
|
|
|
|
const breadcrumbs = screen.getByTestId("page-topbar-breadcrumbs");
|
|
expect(
|
|
Array.from(breadcrumbs.querySelectorAll("a, span")).map((element) => element.textContent),
|
|
).toEqual(["Settings", "Archive", "Automations", "New"]);
|
|
});
|
|
});
|