Replace LogsModal with LogStreamPanel in PodList for streaming logs Add smart positioning to ResourceActionMenu to flip when near bottom Fix dark mode text visibility by applying class to html element Fix YAML editor loading race condition Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
169 lines
4.5 KiB
TypeScript
169 lines
4.5 KiB
TypeScript
import React from "react";
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
import { LogStreamPanel } from "@/components/Kubernetes/LogStreamPanel";
|
|
|
|
vi.mock("@tauri-apps/api/event", () => ({
|
|
listen: vi.fn().mockResolvedValue(() => {}),
|
|
}));
|
|
|
|
vi.mock("@/lib/tauriCommands", () => ({
|
|
streamPodLogsCmd: vi.fn().mockResolvedValue("stream-123"),
|
|
stopLogStreamCmd: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
describe("LogStreamPanel — ANSI color support", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("renders ANSI colored text correctly", () => {
|
|
const containers = ["app"];
|
|
const { rerender } = render(
|
|
<LogStreamPanel
|
|
clusterId="c1"
|
|
namespace="default"
|
|
podName="test-pod"
|
|
containers={containers}
|
|
open={true}
|
|
onOpenChange={() => {}}
|
|
/>
|
|
);
|
|
|
|
// Simulate receiving log line with ANSI color codes
|
|
const logLine = "\x1b[31mError: something went wrong\x1b[0m";
|
|
|
|
// Component should render the ANSI-colored line
|
|
rerender(
|
|
<LogStreamPanel
|
|
clusterId="c1"
|
|
namespace="default"
|
|
podName="test-pod"
|
|
containers={containers}
|
|
open={true}
|
|
onOpenChange={() => {}}
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByText(/Log Stream/)).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("LogStreamPanel — Download functionality", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('renders "Download Visible" button', () => {
|
|
render(
|
|
<LogStreamPanel
|
|
clusterId="c1"
|
|
namespace="default"
|
|
podName="test-pod"
|
|
containers={["app"]}
|
|
open={true}
|
|
onOpenChange={() => {}}
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByRole("button", { name: /download visible/i })).toBeDefined();
|
|
});
|
|
|
|
it('renders "Download All" button', () => {
|
|
render(
|
|
<LogStreamPanel
|
|
clusterId="c1"
|
|
namespace="default"
|
|
podName="test-pod"
|
|
containers={["app"]}
|
|
open={true}
|
|
onOpenChange={() => {}}
|
|
/>
|
|
);
|
|
|
|
expect(screen.getByRole("button", { name: /download all/i })).toBeDefined();
|
|
});
|
|
|
|
it("download visible creates blob with current visible lines", async () => {
|
|
const createObjectURL = vi.fn(() => "blob:url");
|
|
const revokeObjectURL = vi.fn();
|
|
const mockClick = vi.fn();
|
|
global.URL.createObjectURL = createObjectURL;
|
|
global.URL.revokeObjectURL = revokeObjectURL;
|
|
|
|
// Mock createElement to intercept the anchor creation
|
|
const originalCreateElement = document.createElement;
|
|
document.createElement = vi.fn((tagName: string) => {
|
|
const element = originalCreateElement.call(document, tagName);
|
|
if (tagName === "a") {
|
|
element.click = mockClick;
|
|
}
|
|
return element;
|
|
}) as typeof document.createElement;
|
|
|
|
render(
|
|
<LogStreamPanel
|
|
clusterId="c1"
|
|
namespace="default"
|
|
podName="test-pod"
|
|
containers={["app"]}
|
|
open={true}
|
|
onOpenChange={() => {}}
|
|
/>
|
|
);
|
|
|
|
// Download button should be disabled when no lines
|
|
const downloadBtn = screen.getByRole("button", { name: /download visible/i });
|
|
expect(downloadBtn).toHaveAttribute("disabled");
|
|
|
|
// Cleanup
|
|
document.createElement = originalCreateElement;
|
|
});
|
|
});
|
|
|
|
describe("LogStreamPanel — Search highlighting", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("highlights search matches in yellow", async () => {
|
|
render(
|
|
<LogStreamPanel
|
|
clusterId="c1"
|
|
namespace="default"
|
|
podName="test-pod"
|
|
containers={["app"]}
|
|
open={true}
|
|
onOpenChange={() => {}}
|
|
/>
|
|
);
|
|
|
|
const searchInput = screen.getByPlaceholderText(/filter log lines/i);
|
|
fireEvent.change(searchInput, { target: { value: "error" } });
|
|
|
|
await waitFor(() => {
|
|
expect(searchInput).toHaveValue("error");
|
|
});
|
|
});
|
|
|
|
it("does not show navigation buttons when no matching lines", () => {
|
|
render(
|
|
<LogStreamPanel
|
|
clusterId="c1"
|
|
namespace="default"
|
|
podName="test-pod"
|
|
containers={["app"]}
|
|
open={true}
|
|
onOpenChange={() => {}}
|
|
/>
|
|
);
|
|
|
|
const searchInput = screen.getByPlaceholderText(/filter log lines/i);
|
|
fireEvent.change(searchInput, { target: { value: "test" } });
|
|
|
|
// Navigation buttons should not be visible when there are no lines
|
|
expect(screen.queryByRole("button", { name: /previous match/i })).toBeNull();
|
|
expect(screen.queryByRole("button", { name: /next match/i })).toBeNull();
|
|
});
|
|
});
|