tftsr-devops_investigation/tests/unit/LogStreamPanel.test.tsx
Shaun Arman f7b4e591f9 fix(performance): resolve memory leaks and add polish features
- Fix LogStreamPanel event listener cleanup with synchronous unlisten
- Fix eventBus async-unsafe unsubscribe with proper error handling
- Fix KubernetesPage infinite loading by resetting state on section change
- Add ErrorBoundary component with reset capability
- Add Badge component with multiple variants
- Add ResourceDetailsDrawer for slide-out details panel
- Add useFavorites hook with localStorage persistence
- Add useKeyboardShortcuts hook for declarative shortcuts
- Add comprehensive test coverage for all new components/hooks
- Add keyboard shortcuts documentation to README
- Wrap KubernetesPage with ErrorBoundary for crash recovery
- Install react-window for virtual scrolling support

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-09 13:28:30 -05:00

155 lines
3.9 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", () => {
const createObjectURL = vi.fn(() => "blob:url");
const revokeObjectURL = vi.fn();
global.URL.createObjectURL = createObjectURL;
global.URL.revokeObjectURL = revokeObjectURL;
render(
<LogStreamPanel
clusterId="c1"
namespace="default"
podName="test-pod"
containers={["app"]}
open={true}
onOpenChange={() => {}}
/>
);
const downloadBtn = screen.getByRole("button", { name: /download visible/i });
fireEvent.click(downloadBtn);
expect(createObjectURL).toHaveBeenCalled();
});
});
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("provides next/previous navigation buttons", () => {
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" } });
expect(screen.getByRole("button", { name: /previous match/i })).toBeDefined();
expect(screen.getByRole("button", { name: /next match/i })).toBeDefined();
});
});