mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
710 lines
20 KiB
TypeScript
710 lines
20 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import type { OnboardingFlow } from "@/lib/onboarding";
|
|
import { useScansStore } from "@/store";
|
|
import { ProviderProps } from "@/types";
|
|
|
|
import { ScansPageShell } from "./scans-page-shell";
|
|
|
|
const { pushMock, replaceMock, searchParamsValue } = vi.hoisted(() => ({
|
|
pushMock: vi.fn(),
|
|
replaceMock: vi.fn(),
|
|
searchParamsValue: { current: "" },
|
|
}));
|
|
|
|
const { scansFilterBarSpy } = vi.hoisted(() => ({
|
|
scansFilterBarSpy: vi.fn(),
|
|
}));
|
|
|
|
const { onboardingTriggerSpy } = vi.hoisted(() => ({
|
|
onboardingTriggerSpy: vi.fn(),
|
|
}));
|
|
|
|
const localStorageMock = (() => {
|
|
let store: Record<string, string> = {};
|
|
|
|
return {
|
|
getItem: vi.fn((key: string) => store[key] ?? null),
|
|
setItem: vi.fn((key: string, value: string) => {
|
|
store[key] = value;
|
|
}),
|
|
removeItem: vi.fn((key: string) => {
|
|
delete store[key];
|
|
}),
|
|
clear: vi.fn(() => {
|
|
store = {};
|
|
}),
|
|
get length() {
|
|
return Object.keys(store).length;
|
|
},
|
|
key: vi.fn((index: number) => Object.keys(store)[index] ?? null),
|
|
};
|
|
})();
|
|
|
|
Object.defineProperty(window, "localStorage", {
|
|
value: localStorageMock,
|
|
writable: true,
|
|
});
|
|
|
|
vi.mock("next/navigation", () => ({
|
|
usePathname: () => "/scans",
|
|
useRouter: () => ({
|
|
push: pushMock,
|
|
replace: replaceMock,
|
|
}),
|
|
useSearchParams: () => new URLSearchParams(searchParamsValue.current),
|
|
}));
|
|
|
|
vi.mock("./scans-filter-bar", () => ({
|
|
ScansFilterBar: (props: {
|
|
showStatusFilter: boolean;
|
|
onScheduleTypeChange: (value: string) => void;
|
|
onScanStatusChange: (value: string) => void;
|
|
}) => {
|
|
scansFilterBarSpy(props);
|
|
return (
|
|
<>
|
|
<div>Shared scan filters</div>
|
|
<select
|
|
aria-label="All Types"
|
|
onChange={(event) => props.onScheduleTypeChange(event.target.value)}
|
|
>
|
|
<option value="all">All Types</option>
|
|
</select>
|
|
{props.showStatusFilter && (
|
|
<select
|
|
aria-label="All Statuses"
|
|
onChange={(event) => props.onScanStatusChange(event.target.value)}
|
|
>
|
|
<option value="all">All Statuses</option>
|
|
</select>
|
|
)}
|
|
</>
|
|
);
|
|
},
|
|
}));
|
|
|
|
vi.mock("./launch-scan-modal", () => ({
|
|
LaunchScanModal: ({
|
|
open,
|
|
onOpenChange,
|
|
}: {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
}) =>
|
|
open ? (
|
|
<div role="dialog">
|
|
Launch scan
|
|
<button type="button" onClick={() => onOpenChange(false)}>
|
|
Close
|
|
</button>
|
|
</div>
|
|
) : null,
|
|
}));
|
|
|
|
vi.mock("@/components/providers/muted-findings-config-button", () => ({
|
|
MutedFindingsConfigButton: () => <a href="/mutelist">Configure Mutelist</a>,
|
|
}));
|
|
|
|
vi.mock("@/components/onboarding", () => ({
|
|
OnboardingTrigger: (props: unknown) => {
|
|
onboardingTriggerSpy(props);
|
|
return <div data-testid="onboarding-trigger" />;
|
|
},
|
|
PageReady: () => <div data-testid="page-ready" />,
|
|
}));
|
|
|
|
interface OnboardingTriggerProps {
|
|
flow: OnboardingFlow;
|
|
}
|
|
|
|
const getTriggeredTourTargets = () => {
|
|
const triggerProps = onboardingTriggerSpy.mock.calls.at(-1)?.[0] as
|
|
| OnboardingTriggerProps
|
|
| undefined;
|
|
|
|
return triggerProps?.flow.tour.steps.map((step) => step.target);
|
|
};
|
|
|
|
const providers: ProviderProps[] = [
|
|
{
|
|
id: "provider-1",
|
|
type: "providers" as const,
|
|
attributes: {
|
|
provider: "aws" as const,
|
|
is_dynamic: false,
|
|
uid: "123456789012",
|
|
alias: "Production",
|
|
status: "completed" as const,
|
|
resources: 0,
|
|
connection: {
|
|
connected: true,
|
|
last_checked_at: "2026-04-13T00:00:00Z",
|
|
},
|
|
scanner_args: {
|
|
only_logs: false,
|
|
excluded_checks: [],
|
|
aws_retries_max_attempts: 3,
|
|
},
|
|
inserted_at: "2026-04-13T00:00:00Z",
|
|
updated_at: "2026-04-13T00:00:00Z",
|
|
created_by: {
|
|
object: "user",
|
|
id: "user-1",
|
|
},
|
|
},
|
|
relationships: {
|
|
secret: {
|
|
data: null,
|
|
},
|
|
provider_groups: {
|
|
meta: {
|
|
count: 0,
|
|
},
|
|
data: [],
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
const disconnectedProviders: ProviderProps[] = [
|
|
{
|
|
...providers[0],
|
|
attributes: {
|
|
...providers[0].attributes,
|
|
connection: {
|
|
...providers[0].attributes.connection,
|
|
connected: false,
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
describe("ScansPageShell", () => {
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
vi.clearAllMocks();
|
|
localStorageMock.clear();
|
|
searchParamsValue.current = "";
|
|
useScansStore.getState().closeLaunchScanModal();
|
|
});
|
|
|
|
it("uses the shared scan filter bar for scan filters", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByText("Shared scan filters")).toBeInTheDocument();
|
|
expect(scansFilterBarSpy).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
providers,
|
|
scheduleType: "all",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("clears the active sort when switching tabs", async () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=active&sort=trigger";
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
await user.click(screen.getByRole("tab", { name: /completed/i }));
|
|
|
|
expect(pushMock).toHaveBeenCalled();
|
|
const calledUrl = pushMock.mock.calls.at(-1)?.[0] as string;
|
|
expect(calledUrl).toContain("tab=completed");
|
|
expect(calledUrl).not.toContain("sort=");
|
|
});
|
|
|
|
it("uses a generic type filter label in Cloud", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByRole("combobox", { name: /all types/i })).toBeVisible();
|
|
});
|
|
|
|
it.each(["Cloud", "Private Cloud"])(
|
|
"shows Import Findings in %s with Manage Ingestions",
|
|
() => {
|
|
// Given
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
|
|
|
// When
|
|
render(
|
|
<ScansPageShell
|
|
providers={providers}
|
|
hasManageScansPermission
|
|
hasManageIngestionsPermission
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
// Then
|
|
expect(
|
|
screen.getByRole("button", { name: /import findings/i }),
|
|
).toBeVisible();
|
|
},
|
|
);
|
|
|
|
it("hides Import Findings without Manage Ingestions", () => {
|
|
// Given
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
|
|
|
// When
|
|
render(
|
|
<ScansPageShell
|
|
providers={providers}
|
|
hasManageScansPermission
|
|
hasManageIngestionsPermission={false}
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
// Then
|
|
expect(
|
|
screen.queryByRole("button", { name: /import findings/i }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("hides Import Findings in OSS and Local Server", () => {
|
|
// Given
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
// When
|
|
render(
|
|
<ScansPageShell
|
|
providers={providers}
|
|
hasManageScansPermission
|
|
hasManageIngestionsPermission
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
// Then
|
|
expect(
|
|
screen.queryByRole("button", { name: /import findings/i }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows the CLI import banner in Cloud", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
/import findings from prowler cli/i,
|
|
);
|
|
expect(screen.getByRole("link", { name: /learn more/i })).toHaveAttribute(
|
|
"href",
|
|
"https://docs.prowler.com/user-guide/tutorials/prowler-app-import-findings",
|
|
);
|
|
});
|
|
|
|
it("hides the CLI import banner outside Cloud", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("keeps launch scan with filters and mutelist with tabs", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByRole("group", { name: /scan filters and actions/i }),
|
|
).toContainElement(screen.getByRole("button", { name: /launch scan/i }));
|
|
expect(
|
|
screen.getByRole("group", { name: /scan filters and actions/i }),
|
|
).not.toContainElement(
|
|
screen.getByRole("link", { name: /configure mutelist/i }),
|
|
);
|
|
expect(screen.getByRole("group", { name: /scan tabs/i })).toContainElement(
|
|
screen.getByRole("link", { name: /configure mutelist/i }),
|
|
);
|
|
});
|
|
|
|
it("shows the active scans count in the in progress tab", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell
|
|
providers={providers}
|
|
hasManageScansPermission
|
|
activeScanCount={3}
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByRole("tab", { name: /in progress \(3\)/i }),
|
|
).toBeVisible();
|
|
expect(screen.getByRole("tab", { name: /^completed$/i })).toBeVisible();
|
|
});
|
|
|
|
it("opens the launch scan modal from the URL", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "launchScan=true";
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByRole("dialog")).toHaveTextContent(/launch scan/i);
|
|
});
|
|
|
|
it("does not open the launch scan modal from the URL when no provider is connected", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "launchScan=true";
|
|
|
|
render(
|
|
<ScansPageShell
|
|
providers={disconnectedProviders}
|
|
hasManageScansPermission
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("does not open the launch scan modal from client state when no provider is connected", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
useScansStore.getState().openLaunchScanModal();
|
|
|
|
render(
|
|
<ScansPageShell
|
|
providers={disconnectedProviders}
|
|
hasManageScansPermission
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("does not open the launch scan modal from the URL without manage scans permission", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "launchScan=true";
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission={false}>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("strips the launchScan URL param via the History API when closing the URL-opened modal", async () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=completed&launchScan=true";
|
|
const replaceStateSpy = vi.spyOn(window.history, "replaceState");
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
await user.click(screen.getByRole("button", { name: /close/i }));
|
|
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
// History API (not router.replace) so Next.js does not refetch and reload the page;
|
|
// scanOnDemand's revalidatePath already refreshes the list when a scan launches.
|
|
expect(replaceStateSpy).toHaveBeenCalledWith(
|
|
null,
|
|
"",
|
|
"/scans?tab=completed",
|
|
);
|
|
expect(replaceMock).not.toHaveBeenCalled();
|
|
expect(pushMock).not.toHaveBeenCalled();
|
|
|
|
replaceStateSpy.mockRestore();
|
|
});
|
|
|
|
it("opens and closes the launch scan modal from client state without navigation", async () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
const user = userEvent.setup();
|
|
useScansStore.getState().openLaunchScanModal();
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByRole("dialog")).toHaveTextContent(/launch scan/i);
|
|
|
|
await user.click(screen.getByRole("button", { name: /close/i }));
|
|
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
expect(pushMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("shows the status filter only on the completed tab", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=completed";
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByRole("combobox", { name: /all statuses/i }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
it("hides the status filter outside of the completed tab", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=active";
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(
|
|
screen.queryByRole("combobox", { name: /all statuses/i }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("clears status filter when switching scan tabs", async () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=completed&filter%5Bstate__in%5D=failed";
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
await user.click(screen.getByRole("tab", { name: /in progress/i }));
|
|
|
|
const calledUrl = pushMock.mock.calls.at(-1)?.[0] as string;
|
|
expect(calledUrl).toContain("tab=active");
|
|
expect(calledUrl).not.toContain("filter%5Bstate__in%5D");
|
|
});
|
|
|
|
it("clears type filter when switching to scheduled scans", async () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=completed&filter%5Btrigger%5D=manual";
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
await user.click(screen.getByRole("tab", { name: /scheduled/i }));
|
|
|
|
const calledUrl = pushMock.mock.calls.at(-1)?.[0] as string;
|
|
expect(calledUrl).toContain("tab=scheduled");
|
|
expect(calledUrl).not.toContain("filter%5Btrigger%5D");
|
|
});
|
|
|
|
it("shows a non-blocking hint when no provider is connected, while still rendering the table", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell
|
|
providers={disconnectedProviders}
|
|
hasManageScansPermission
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByText("No Connected Providers")).toBeInTheDocument();
|
|
// The table (and therefore imported scans) must still render below the hint.
|
|
expect(screen.getByText("Scans table")).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows the no-providers hint when there are no providers, while still rendering the table", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={[]} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByText("No Providers Configured")).toBeInTheDocument();
|
|
expect(screen.getByText("Scans table")).toBeInTheDocument();
|
|
});
|
|
|
|
it("does not show the providers hint when a provider is connected", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(
|
|
screen.queryByText("No Connected Providers"),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("No Providers Configured"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("starts the view-first-scan tour when a provider is connected", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByTestId("onboarding-trigger")).toBeInTheDocument();
|
|
});
|
|
|
|
it("uses only mounted tour targets when an active scan exists on the completed tab", () => {
|
|
// Given
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=completed";
|
|
|
|
// When
|
|
render(
|
|
<ScansPageShell
|
|
providers={providers}
|
|
hasManageScansPermission
|
|
activeScanCount={1}
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
// Then
|
|
expect(getTriggeredTourTargets()).toEqual([undefined, "launch", "tabs"]);
|
|
});
|
|
|
|
it("targets the running scan when its row is mounted on the in progress tab", () => {
|
|
// Given
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
searchParamsValue.current = "tab=active";
|
|
|
|
// When
|
|
render(
|
|
<ScansPageShell
|
|
providers={providers}
|
|
hasManageScansPermission
|
|
activeScanCount={1}
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
// Then
|
|
expect(getTriggeredTourTargets()).toEqual([
|
|
undefined,
|
|
"in-progress",
|
|
"launch",
|
|
]);
|
|
});
|
|
|
|
it("suppresses the view-first-scan tour when no provider is connected, since Launch Scan is disabled", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell
|
|
providers={disconnectedProviders}
|
|
hasManageScansPermission
|
|
>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.queryByTestId("onboarding-trigger")).not.toBeInTheDocument();
|
|
// The table (and therefore imported scans) must still render even with the tour suppressed.
|
|
expect(screen.getByText("Scans table")).toBeInTheDocument();
|
|
});
|
|
|
|
it("suppresses the view-first-scan tour when there are no providers", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={[]} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.queryByTestId("onboarding-trigger")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("suppresses the view-first-scan tour when a provider is connected but manage scans is missing", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={providers} hasManageScansPermission={false}>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
// Launch Scan is disabled without manage_scans, so the tour must not anchor to it.
|
|
expect(screen.queryByTestId("onboarding-trigger")).not.toBeInTheDocument();
|
|
// The table (and therefore imported scans) must still render.
|
|
expect(screen.getByText("Scans table")).toBeInTheDocument();
|
|
});
|
|
|
|
it("still signals page-ready without a connected provider so the navbar replay fallback works", () => {
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
|
|
render(
|
|
<ScansPageShell providers={[]} hasManageScansPermission>
|
|
<div>Scans table</div>
|
|
</ScansPageShell>,
|
|
);
|
|
|
|
expect(screen.getByTestId("page-ready")).toBeInTheDocument();
|
|
});
|
|
});
|