feat(FN-3037): add focus-visible styling to AgentLogViewer log action butto

Adds `focus-visible` styling to action buttons in the AgentLogViewer component, improving keyboard accessibility for interactive button elements.

Fusion-Task-Id: FN-3037
This commit is contained in:
Fusion
2026-04-30 19:16:41 -07:00
committed by gsxdsm
parent c553920b40
commit 135b0f0b16
13 changed files with 593 additions and 432 deletions

View File

@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, act, waitFor } from "@testing-library/react";
import { MAX_LOG_ENTRIES, useAgentLogs } from "../useAgentLogs";
import { useAgentLogs } from "../useAgentLogs";
import { fetchAgentLogsWithMeta } from "../../api";
// Mock the api module
@@ -191,8 +191,9 @@ describe("useAgentLogs", () => {
expect(es.close).toHaveBeenCalled();
});
it("truncates oversized historical logs to the most recent entries", async () => {
const historicalLogs = Array.from({ length: MAX_LOG_ENTRIES + 25 }, (_, index) => ({
it("keeps oversized historical logs without truncating older entries", async () => {
const oversizedCount = 525;
const historicalLogs = Array.from({ length: oversizedCount }, (_, index) => ({
timestamp: `2026-01-01T00:${String(index).padStart(2, "0")}:00Z`,
taskId: "FN-001",
text: `entry-${index}`,
@@ -207,15 +208,16 @@ describe("useAgentLogs", () => {
const { result } = renderHook(() => useAgentLogs("FN-001", true));
await waitFor(() => {
expect(result.current.entries).toHaveLength(MAX_LOG_ENTRIES);
expect(result.current.entries).toHaveLength(oversizedCount);
});
expect(result.current.entries[0].text).toBe("entry-25");
expect(result.current.entries.at(-1)?.text).toBe(`entry-${MAX_LOG_ENTRIES + 24}`);
expect(result.current.entries[0].text).toBe("entry-0");
expect(result.current.entries.at(-1)?.text).toBe(`entry-${oversizedCount - 1}`);
});
it("truncates live SSE entries to the most recent entries", async () => {
mockFetchAgentLogsWithMeta.mockResolvedValueOnce({ entries: [], total: MAX_LOG_ENTRIES + 20, hasMore: false });
it("keeps oversized live SSE history without truncation", async () => {
const streamedCount = 520;
mockFetchAgentLogsWithMeta.mockResolvedValueOnce({ entries: [], total: streamedCount, hasMore: false });
const { result } = renderHook(() => useAgentLogs("FN-001", true));
@@ -225,7 +227,7 @@ describe("useAgentLogs", () => {
const es = MockEventSource.instances[0];
act(() => {
for (let index = 0; index < MAX_LOG_ENTRIES + 20; index++) {
for (let index = 0; index < streamedCount; index++) {
es._emit("agent:log", {
timestamp: `2026-01-01T00:${String(index).padStart(2, "0")}:00Z`,
taskId: "FN-001",
@@ -236,11 +238,11 @@ describe("useAgentLogs", () => {
});
await waitFor(() => {
expect(result.current.entries).toHaveLength(MAX_LOG_ENTRIES);
expect(result.current.entries).toHaveLength(streamedCount);
});
expect(result.current.entries[0].text).toBe("live-20");
expect(result.current.entries.at(-1)?.text).toBe(`live-${MAX_LOG_ENTRIES + 19}`);
expect(result.current.entries[0].text).toBe("live-0");
expect(result.current.entries.at(-1)?.text).toBe(`live-${streamedCount - 1}`);
});
it("does not fetch when taskId is null", () => {
@@ -370,6 +372,56 @@ describe("useAgentLogs", () => {
]);
});
it("keeps full history across initial load, loadMore, and live streaming", async () => {
const initialLogs = Array.from({ length: 300 }, (_, index) => ({
timestamp: `2026-01-02T00:${String(index).padStart(2, "0")}:00Z`,
taskId: "FN-001",
text: `initial-${index}`,
type: "text" as const,
}));
const olderLogs = Array.from({ length: 250 }, (_, index) => ({
timestamp: `2026-01-01T00:${String(index).padStart(2, "0")}:00Z`,
taskId: "FN-001",
text: `older-${index}`,
type: "text" as const,
}));
mockFetchAgentLogsWithMeta
.mockResolvedValueOnce({ entries: initialLogs, total: 550, hasMore: true })
.mockResolvedValueOnce({ entries: olderLogs, total: 550, hasMore: false });
const { result } = renderHook(() => useAgentLogs("FN-001", true));
await waitFor(() => {
expect(result.current.entries).toHaveLength(300);
});
await act(async () => {
await result.current.loadMore();
});
await waitFor(() => {
expect(result.current.entries).toHaveLength(550);
});
const es = MockEventSource.instances[0];
act(() => {
es._emit("agent:log", {
timestamp: "2026-01-03T00:00:00Z",
taskId: "FN-001",
text: "live-after-large-history",
type: "text",
});
});
await waitFor(() => {
expect(result.current.entries).toHaveLength(551);
});
expect(result.current.entries[0].text).toBe("older-0");
expect(result.current.entries.at(-1)?.text).toBe("live-after-large-history");
});
it("loadMore does not trigger when already loading more", async () => {
mockFetchAgentLogsWithMeta.mockResolvedValue({ entries: [], total: 200, hasMore: true });