fix(kube): action namespace, race condition, stability, dark mode #86

Merged
sarman merged 7 commits from fix/kube-action-namespace-and-stability into master 2026-06-09 03:21:49 +00:00
4 changed files with 713 additions and 18 deletions
Showing only changes of commit 7dfda91cd8 - Show all commits

View File

@ -24,7 +24,7 @@ type ActiveModal =
| { type: "delete"; ds: DaemonSetInfo }
| null;
export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: DaemonSetListProps) {
export function DaemonSetList({ daemonsets, clusterId, namespace: _namespace, onRefresh }: DaemonSetListProps) {
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
const [isActing, setIsActing] = useState(false);
const [actionError, setActionError] = useState<string | null>(null);
@ -32,7 +32,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
const openEdit = async (ds: DaemonSetInfo) => {
setActionError(null);
try {
const yaml = await getResourceYamlCmd(clusterId, "daemonsets", namespace, ds.name);
const yaml = await getResourceYamlCmd(clusterId, "daemonsets", ds.namespace, ds.name);
setActiveModal({ type: "edit", ds, yaml });
} catch (err) {
setActionError(err instanceof Error ? err.message : String(err));
@ -43,7 +43,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
if (activeModal?.type !== "restart") return;
setIsActing(true);
try {
await restartDaemonsetCmd(clusterId, namespace, activeModal.ds.name);
await restartDaemonsetCmd(clusterId, activeModal.ds.namespace, activeModal.ds.name);
setActiveModal(null);
onRefresh?.();
} catch (err) {
@ -57,7 +57,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
if (activeModal?.type !== "delete") return;
setIsActing(true);
try {
await deleteResourceCmd(clusterId, "daemonsets", namespace, activeModal.ds.name);
await deleteResourceCmd(clusterId, "daemonsets", activeModal.ds.namespace, activeModal.ds.name);
setActiveModal(null);
onRefresh?.();
} finally {
@ -146,7 +146,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
<EditResourceModal
isOpen
clusterId={clusterId}
namespace={namespace}
namespace={activeModal.ds.namespace}
resourceType="daemonsets"
resourceName={activeModal.ds.name}
initialYaml={activeModal.yaml}

View File

@ -29,7 +29,7 @@ type ActiveModal =
| { type: "delete"; deployment: DeploymentInfo }
| null;
export function DeploymentList({ deployments, clusterId, namespace, onRefresh }: DeploymentListProps) {
export function DeploymentList({ deployments, clusterId, namespace: _namespace, onRefresh }: DeploymentListProps) {
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
const [isActing, setIsActing] = useState(false);
const [actionError, setActionError] = useState<string | null>(null);
@ -37,7 +37,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
const openEdit = async (deployment: DeploymentInfo) => {
setActionError(null);
try {
const yaml = await getResourceYamlCmd(clusterId, "deployments", namespace, deployment.name);
const yaml = await getResourceYamlCmd(clusterId, "deployments", deployment.namespace, deployment.name);
setActiveModal({ type: "edit", deployment, yaml });
} catch (err) {
setActionError(err instanceof Error ? err.message : String(err));
@ -48,7 +48,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
if (activeModal?.type !== "restart") return;
setIsActing(true);
try {
await restartDeploymentCmd(clusterId, namespace, activeModal.deployment.name);
await restartDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
setActiveModal(null);
onRefresh?.();
} catch (err) {
@ -62,7 +62,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
if (activeModal?.type !== "rollback") return;
setIsActing(true);
try {
await rollbackDeploymentCmd(clusterId, namespace, activeModal.deployment.name);
await rollbackDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
setActiveModal(null);
onRefresh?.();
} catch (err) {
@ -76,7 +76,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
if (activeModal?.type !== "delete") return;
setIsActing(true);
try {
await deleteResourceCmd(clusterId, "deployments", namespace, activeModal.deployment.name);
await deleteResourceCmd(clusterId, "deployments", activeModal.deployment.namespace, activeModal.deployment.name);
setActiveModal(null);
onRefresh?.();
} finally {
@ -165,7 +165,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
resourceName={activeModal.deployment.name}
currentReplicas={activeModal.deployment.replicas}
onScale={(replicas) =>
scaleDeploymentCmd(clusterId, namespace, activeModal.deployment.name, replicas).then(() => {
scaleDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name, replicas).then(() => {
setActiveModal(null);
onRefresh?.();
})
@ -201,7 +201,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
<EditResourceModal
isOpen
clusterId={clusterId}
namespace={namespace}
namespace={activeModal.deployment.namespace}
resourceType="deployments"
resourceName={activeModal.deployment.name}
initialYaml={activeModal.yaml}

View File

@ -27,7 +27,7 @@ type ActiveModal =
| { type: "delete"; ss: StatefulSetInfo }
| null;
export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh }: StatefulSetListProps) {
export function StatefulSetList({ statefulsets, clusterId, namespace: _namespace, onRefresh }: StatefulSetListProps) {
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
const [isActing, setIsActing] = useState(false);
const [actionError, setActionError] = useState<string | null>(null);
@ -35,7 +35,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
const openEdit = async (ss: StatefulSetInfo) => {
setActionError(null);
try {
const yaml = await getResourceYamlCmd(clusterId, "statefulsets", namespace, ss.name);
const yaml = await getResourceYamlCmd(clusterId, "statefulsets", ss.namespace, ss.name);
setActiveModal({ type: "edit", ss, yaml });
} catch (err) {
setActionError(err instanceof Error ? err.message : String(err));
@ -46,7 +46,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
if (activeModal?.type !== "restart") return;
setIsActing(true);
try {
await restartStatefulsetCmd(clusterId, namespace, activeModal.ss.name);
await restartStatefulsetCmd(clusterId, activeModal.ss.namespace, activeModal.ss.name);
setActiveModal(null);
onRefresh?.();
} catch (err) {
@ -60,7 +60,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
if (activeModal?.type !== "delete") return;
setIsActing(true);
try {
await deleteResourceCmd(clusterId, "statefulsets", namespace, activeModal.ss.name);
await deleteResourceCmd(clusterId, "statefulsets", activeModal.ss.namespace, activeModal.ss.name);
setActiveModal(null);
onRefresh?.();
} finally {
@ -140,7 +140,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
resourceName={activeModal.ss.name}
currentReplicas={activeModal.ss.replicas}
onScale={(replicas) =>
scaleStatefulsetCmd(clusterId, namespace, activeModal.ss.name, replicas).then(() => {
scaleStatefulsetCmd(clusterId, activeModal.ss.namespace, activeModal.ss.name, replicas).then(() => {
setActiveModal(null);
onRefresh?.();
})
@ -164,7 +164,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
<EditResourceModal
isOpen
clusterId={clusterId}
namespace={namespace}
namespace={activeModal.ss.namespace}
resourceType="statefulsets"
resourceName={activeModal.ss.name}
initialYaml={activeModal.yaml}

View File

@ -0,0 +1,695 @@
import React from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import { invoke } from "@tauri-apps/api/core";
import { DeploymentList } from "@/components/Kubernetes/DeploymentList";
import { StatefulSetList } from "@/components/Kubernetes/StatefulSetList";
import { DaemonSetList } from "@/components/Kubernetes/DaemonSetList";
import { ReplicaSetList } from "@/components/Kubernetes/ReplicaSetList";
import { JobList } from "@/components/Kubernetes/JobList";
import { CronJobList } from "@/components/Kubernetes/CronJobList";
import type {
DeploymentInfo,
StatefulSetInfo,
DaemonSetInfo,
ReplicaSetInfo,
JobInfo,
CronJobInfo,
} from "@/lib/tauriCommands";
type MockedInvoke = typeof invoke & {
mockResolvedValue: (v: unknown) => void;
mockImplementation: (fn: (cmd: string) => Promise<unknown>) => void;
};
const mockInvoke = invoke as MockedInvoke;
// Helper: open the action menu for the first Actions button, then click a menu item by label
async function openMenuAndClick(label: string) {
const btn = screen.getAllByRole("button", { name: /actions/i })[0];
fireEvent.click(btn);
const item = await screen.findByRole("button", { name: new RegExp(label, "i") });
fireEvent.click(item);
}
// ─── DeploymentList ──────────────────────────────────────────────────────────
describe("DeploymentList — actions use item.namespace not filter prop", () => {
const deployment: DeploymentInfo = {
name: "nginx",
namespace: "kube-system",
ready: "1/1",
up_to_date: "1",
available: "1",
replicas: 1,
age: "1d",
labels: {},
};
beforeEach(() => {
vi.clearAllMocks();
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
});
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
render(
<DeploymentList
deployments={[deployment]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Edit");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
clusterId: "c1",
resourceType: "deployments",
namespace: "kube-system",
resourceName: "nginx",
});
});
});
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "delete_resource") return undefined;
return "yaml";
});
render(
<DeploymentList
deployments={[deployment]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Delete");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("delete_resource", {
clusterId: "c1",
resourceType: "deployments",
namespace: "kube-system",
resourceName: "nginx",
});
});
});
it("ScaleModal onScale calls scaleDeploymentCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockResolvedValue(undefined);
render(
<DeploymentList
deployments={[deployment]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Scale");
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
fireEvent.click(scaleBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("scale_deployment", {
clusterId: "c1",
namespace: "kube-system",
deploymentName: "nginx",
replicas: expect.any(Number),
});
});
});
it("handleRestart calls restartDeploymentCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "restart_deployment") return undefined;
return "yaml";
});
render(
<DeploymentList
deployments={[deployment]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Restart");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("restart_deployment", {
clusterId: "c1",
namespace: "kube-system",
deploymentName: "nginx",
});
});
});
it("handleRollback calls rollbackDeploymentCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "rollback_deployment") return undefined;
return "yaml";
});
render(
<DeploymentList
deployments={[deployment]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Rollback");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|rollback/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("rollback_deployment", {
clusterId: "c1",
namespace: "kube-system",
deploymentName: "nginx",
});
});
});
});
// ─── StatefulSetList ─────────────────────────────────────────────────────────
describe("StatefulSetList — actions use item.namespace not filter prop", () => {
const ss: StatefulSetInfo = {
name: "postgres",
namespace: "kube-system",
ready: "1/1",
replicas: 1,
age: "2d",
labels: {},
};
beforeEach(() => {
vi.clearAllMocks();
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
});
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
render(
<StatefulSetList
statefulsets={[ss]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Edit");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
clusterId: "c1",
resourceType: "statefulsets",
namespace: "kube-system",
resourceName: "postgres",
});
});
});
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "delete_resource") return undefined;
return "yaml";
});
render(
<StatefulSetList
statefulsets={[ss]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Delete");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("delete_resource", {
clusterId: "c1",
resourceType: "statefulsets",
namespace: "kube-system",
resourceName: "postgres",
});
});
});
it("ScaleModal onScale calls scaleStatefulsetCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockResolvedValue(undefined);
render(
<StatefulSetList
statefulsets={[ss]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Scale");
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
fireEvent.click(scaleBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("scale_statefulset", {
clusterId: "c1",
namespace: "kube-system",
name: "postgres",
replicas: expect.any(Number),
});
});
});
it("handleRestart calls restartStatefulsetCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "restart_statefulset") return undefined;
return "yaml";
});
render(
<StatefulSetList
statefulsets={[ss]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Restart");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("restart_statefulset", {
clusterId: "c1",
namespace: "kube-system",
name: "postgres",
});
});
});
});
// ─── DaemonSetList ───────────────────────────────────────────────────────────
describe("DaemonSetList — actions use item.namespace not filter prop", () => {
const ds: DaemonSetInfo = {
name: "fluentd",
namespace: "kube-system",
desired: 3,
current: 3,
ready: 3,
up_to_date: 3,
available: 3,
age: "5d",
labels: {},
};
beforeEach(() => {
vi.clearAllMocks();
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
});
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
render(
<DaemonSetList
daemonsets={[ds]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Edit");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
clusterId: "c1",
resourceType: "daemonsets",
namespace: "kube-system",
resourceName: "fluentd",
});
});
});
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "delete_resource") return undefined;
return "yaml";
});
render(
<DaemonSetList
daemonsets={[ds]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Delete");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("delete_resource", {
clusterId: "c1",
resourceType: "daemonsets",
namespace: "kube-system",
resourceName: "fluentd",
});
});
});
it("handleRestart calls restartDaemonsetCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "restart_daemonset") return undefined;
return "yaml";
});
render(
<DaemonSetList
daemonsets={[ds]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Restart");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("restart_daemonset", {
clusterId: "c1",
namespace: "kube-system",
name: "fluentd",
});
});
});
});
// ─── ReplicaSetList ──────────────────────────────────────────────────────────
describe("ReplicaSetList — actions use item.namespace not filter prop", () => {
const rs: ReplicaSetInfo = {
name: "nginx-abc12",
namespace: "kube-system",
replicas: 2,
ready: "2",
age: "3d",
labels: { app: "nginx" },
};
beforeEach(() => {
vi.clearAllMocks();
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
});
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
render(
<ReplicaSetList
replicaSets={[rs]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Edit");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
clusterId: "c1",
resourceType: "replicasets",
namespace: "kube-system",
resourceName: "nginx-abc12",
});
});
});
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "delete_resource") return undefined;
return "yaml";
});
render(
<ReplicaSetList
replicaSets={[rs]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Delete");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("delete_resource", {
clusterId: "c1",
resourceType: "replicasets",
namespace: "kube-system",
resourceName: "nginx-abc12",
});
});
});
it("ScaleModal onScale calls scaleReplicasetCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockResolvedValue(undefined);
render(
<ReplicaSetList
replicaSets={[rs]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Scale");
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
fireEvent.click(scaleBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("scale_replicaset", {
clusterId: "c1",
namespace: "kube-system",
name: "nginx-abc12",
replicas: expect.any(Number),
});
});
});
});
// ─── JobList ─────────────────────────────────────────────────────────────────
describe("JobList — actions use item.namespace not filter prop", () => {
const job: JobInfo = {
name: "db-migrate",
namespace: "kube-system",
completions: "1/1",
duration: "45s",
age: "1d",
labels: {},
};
beforeEach(() => {
vi.clearAllMocks();
mockInvoke.mockResolvedValue("apiVersion: batch/v1");
});
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
render(
<JobList
jobs={[job]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Edit");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
clusterId: "c1",
resourceType: "jobs",
namespace: "kube-system",
resourceName: "db-migrate",
});
});
});
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "delete_resource") return undefined;
return "yaml";
});
render(
<JobList
jobs={[job]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Delete");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("delete_resource", {
clusterId: "c1",
resourceType: "jobs",
namespace: "kube-system",
resourceName: "db-migrate",
});
});
});
});
// ─── CronJobList ─────────────────────────────────────────────────────────────
describe("CronJobList — actions use item.namespace not filter prop", () => {
const cj: CronJobInfo = {
name: "backup",
namespace: "kube-system",
schedule: "0 2 * * *",
active: 0,
last_schedule: "1h",
age: "10d",
labels: {},
};
beforeEach(() => {
vi.clearAllMocks();
mockInvoke.mockResolvedValue("apiVersion: batch/v1");
});
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
render(
<CronJobList
cronJobs={[cj]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Edit");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
clusterId: "c1",
resourceType: "cronjobs",
namespace: "kube-system",
resourceName: "backup",
});
});
});
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "delete_resource") return undefined;
return "yaml";
});
render(
<CronJobList
cronJobs={[cj]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Delete");
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
fireEvent.click(confirmBtn);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("delete_resource", {
clusterId: "c1",
resourceType: "cronjobs",
namespace: "kube-system",
resourceName: "backup",
});
});
});
it("handleSuspend calls suspendCronjobCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "suspend_cronjob") return undefined;
return "yaml";
});
render(
<CronJobList
cronJobs={[cj]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Suspend");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("suspend_cronjob", {
clusterId: "c1",
namespace: "kube-system",
name: "backup",
});
});
});
it("handleTrigger calls triggerCronjobCmd with item.namespace, not 'all'", async () => {
mockInvoke.mockImplementation(async (cmd: string) => {
if (cmd === "trigger_cronjob") return undefined;
return "yaml";
});
render(
<CronJobList
cronJobs={[cj]}
clusterId="c1"
namespace="all"
onRefresh={vi.fn()}
/>
);
await openMenuAndClick("Trigger");
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith("trigger_cronjob", {
clusterId: "c1",
namespace: "kube-system",
name: "backup",
});
});
});
});