Files
kandev/apps/web/components/sentry/sentry-settings.test.tsx
T
2026-07-19 14:21:36 +01:00

173 lines
4.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ReactNode } from "react";
import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
import { TooltipProvider } from "@kandev/ui/tooltip";
import { listSentryInstances } from "@/lib/api/domains/sentry-api";
import type { SentryConfig } from "@/lib/types/sentry";
import { SettingsSaveProvider } from "@/components/settings/settings-save-provider";
const mocks = vi.hoisted(() => ({
activeWorkspaceId: "ws-active",
setEnabled: vi.fn(),
toast: vi.fn(),
workspaces: [
{ id: "ws-active", name: "Active" },
{ id: "ws-route", name: "Route" },
],
}));
vi.mock("@/components/toast-provider", () => ({
useToast: () => ({ toast: mocks.toast }),
}));
vi.mock("@/hooks/domains/sentry/use-sentry-enabled", () => ({
useSentryEnabled: () => ({ enabled: true, setEnabled: mocks.setEnabled }),
}));
vi.mock("@/hooks/domains/integrations/use-integration-availability", () => ({
INTEGRATION_STATUS_REFRESH_MS: 1,
}));
vi.mock("@kandev/ui/switch", () => ({
Switch: ({ onCheckedChange: _onCheckedChange, ...props }: Record<string, unknown>) => (
<button {...props} />
),
}));
vi.mock("@/components/state-provider", () => ({
useAppStore: (
selector: (state: {
workspaces: { activeId: string; items: { id: string; name: string }[] };
}) => unknown,
) =>
selector({
workspaces: {
activeId: mocks.activeWorkspaceId,
items: mocks.workspaces,
},
}),
}));
vi.mock("./sentry-issue-watchers-section", () => ({
SentryIssueWatchersSection: ({ workspaceId }: { workspaceId: string }) => (
<div data-testid="sentry-watchers-workspace">{workspaceId}</div>
),
}));
vi.mock("@/lib/api/domains/sentry-api", () => ({
createSentryInstance: vi.fn(),
deleteSentryInstance: vi.fn(),
listSentryInstances: vi.fn(),
sentryErrorCode: vi.fn(),
sentryInUseWatchCount: vi.fn(),
testSentryConnection: vi.fn(),
testSentryInstance: vi.fn(),
updateSentryInstance: vi.fn(),
SENTRY_ERROR_CODES: { nameTaken: "SENTRY_INSTANCE_NAME_TAKEN" },
}));
import { SentryConnectionSection, SentryIntegrationPage } from "./sentry-settings";
function SettingsHarness({ children }: { children: ReactNode }) {
return (
<SettingsSaveProvider>
<TooltipProvider>{children}</TooltipProvider>
</SettingsSaveProvider>
);
}
const instance: SentryConfig = {
id: "instance-1",
workspaceId: "workspace-1",
name: "Production",
authMethod: "auth_token",
url: "https://sentry.example.com",
hasSecret: true,
lastOk: true,
createdAt: "2026-01-01T00:00:00Z",
updatedAt: "2026-01-01T00:00:00Z",
};
beforeEach(() => {
vi.useFakeTimers();
mocks.activeWorkspaceId = "ws-active";
});
afterEach(() => {
cleanup();
vi.useRealTimers();
vi.clearAllMocks();
});
describe("SentryConnectionSection", () => {
it("restores the add path when a polling refresh removes the instance being edited", async () => {
vi.mocked(listSentryInstances).mockResolvedValueOnce([instance]).mockResolvedValueOnce([]);
render(
<SettingsHarness>
<SentryConnectionSection workspaceId="workspace-1" />
</SettingsHarness>,
);
await act(async () => {
await Promise.resolve();
});
expect(screen.getByTestId("sentry-instance-edit-button")).toBeTruthy();
fireEvent.click(screen.getByTestId("sentry-instance-edit-button"));
expect(screen.getByTestId("sentry-edit-form")).toBeTruthy();
await act(async () => {
await vi.advanceTimersByTimeAsync(1);
});
expect(screen.getByRole("button", { name: "Add instance" })).toBeTruthy();
});
it("keeps initial load failures visible but silences recurring poll failures", async () => {
vi.mocked(listSentryInstances).mockRejectedValue(new Error("offline"));
render(
<SettingsHarness>
<SentryConnectionSection workspaceId="workspace-1" />
</SettingsHarness>,
);
await act(async () => {
await Promise.resolve();
});
expect(mocks.toast).toHaveBeenCalledTimes(1);
await act(async () => {
await vi.advanceTimersByTimeAsync(1);
});
expect(mocks.toast).toHaveBeenCalledTimes(1);
});
});
describe("SentryIntegrationPage workspace scope", () => {
it("passes the route workspace to watchers before the global active workspace", () => {
vi.mocked(listSentryInstances).mockResolvedValue([]);
render(
<SettingsHarness>
<SentryIntegrationPage workspaceId="ws-route" />
</SettingsHarness>,
);
expect(screen.getByTestId("sentry-watchers-workspace").textContent).toBe("ws-route");
});
it("uses the global active workspace when no route workspace is supplied", () => {
vi.mocked(listSentryInstances).mockResolvedValue([]);
render(
<SettingsHarness>
<SentryIntegrationPage />
</SettingsHarness>,
);
expect(screen.getByTestId("sentry-watchers-workspace").textContent).toBe("ws-active");
});
});