diff --git a/webview-ui/src/components/chat/__tests__/TaskHistoryWarning.spec.tsx b/webview-ui/src/components/chat/__tests__/TaskHistoryWarning.spec.tsx index 40075aee0c..58dea08705 100644 --- a/webview-ui/src/components/chat/__tests__/TaskHistoryWarning.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/TaskHistoryWarning.spec.tsx @@ -1,44 +1,56 @@ +import React from "react" import { render, screen, fireEvent, waitFor } from "@testing-library/react" import { describe, it, expect, vi, beforeEach } from "vitest" import { TaskHistoryWarning } from "../TaskHistoryWarning" +import { TooltipProvider } from "@/components/ui/tooltip" +// Mock the translation hook vi.mock("@/i18n/TranslationContext", () => ({ useAppTranslation: () => ({ t: (key: string, params?: any) => { const translations: Record = { "chat:taskHistoryWarning.title": "High Task History", - "chat:taskHistoryWarning.description": `You have ${params?.count || 0} tasks in your history. Consider cleaning up old tasks to improve performance.`, - "chat:taskHistoryWarning.cleanupButton": "Clean up tasks older than 30 days", + "chat:taskHistoryWarning.ariaLabel": "High Task History", + "chat:taskHistoryWarning.tooltip": `You have ${params?.count || 0} tasks in your history`, + "chat:taskHistoryWarning.message": `You have ${params?.count || 0} tasks in your history. Consider cleaning up old tasks to improve performance.`, + "chat:taskHistoryWarning.performance": "Large task histories can slow down the extension.", + "chat:taskHistoryWarning.cleanupButton": `Clean up ${params?.count || 0} tasks older than 30 days`, + "chat:taskHistoryWarning.cleaning": "Cleaning up...", + "chat:taskHistoryWarning.noOldTasks": "No tasks older than 30 days", + "chat:taskHistoryWarning.allRecent": "All your tasks are from the last 30 days", "chat:taskHistoryWarning.dismiss": "Dismiss", - "chat:taskHistoryWarning.cleanupDialog.title": "Clean Up Old Tasks", - "chat:taskHistoryWarning.cleanupDialog.description": `This will permanently delete all tasks older than 30 days (${params?.count || 0} tasks). This action cannot be undone.`, - "chat:taskHistoryWarning.cleanupDialog.cancel": "Cancel", - "chat:taskHistoryWarning.cleanupDialog.confirm": "Clean Up", + "chat:taskHistoryWarning.confirmTitle": "Clean Up Old Tasks", + "chat:taskHistoryWarning.confirmMessage": `This will permanently delete ${params?.count || 0} tasks older than ${params?.days || 30} days.`, + "chat:taskHistoryWarning.confirmWarning": "This action cannot be undone.", + "chat:taskHistoryWarning.confirmButton": "Clean Up", "chat:taskHistoryWarning.cleanupSuccess": `Successfully deleted ${params?.count || 0} old tasks`, "chat:taskHistoryWarning.cleanupError": `Failed to clean up tasks: ${params?.error || "Unknown error"}`, + "common:cancel": "Cancel", } return translations[key] || key }, }), })) -const mockTaskHistory: any[] = [] -const mockSetTaskHistory = vi.fn() - -vi.mock("@src/context/ExtensionStateContext", () => ({ - useExtensionState: () => ({ - taskHistory: mockTaskHistory, - setTaskHistory: mockSetTaskHistory, - }), -})) - -const mockPostMessage = vi.fn() +// Mock vscode API vi.mock("@src/utils/vscode", () => ({ vscode: { - postMessage: mockPostMessage, + postMessage: vi.fn(), }, })) +// Create mock task history array that can be modified +let mockTaskHistory: any[] = [] + +// Mock the extension state context +vi.mock("@src/context/ExtensionStateContext", () => ({ + useExtensionState: () => ({ + taskHistory: mockTaskHistory, + setTaskHistory: vi.fn(), + }), +})) + +// Mock localStorage const localStorageMock = { getItem: vi.fn(), setItem: vi.fn(), @@ -51,73 +63,77 @@ Object.defineProperty(window, "localStorage", { }) describe("TaskHistoryWarning", () => { + const renderWithProviders = (component: React.ReactElement) => { + return render({component}) + } + beforeEach(() => { vi.clearAllMocks() localStorageMock.getItem.mockReturnValue(null) - mockTaskHistory.length = 0 + mockTaskHistory = [] }) it("should not render when task history is below threshold", () => { - mockTaskHistory.push(...Array(999).fill({ id: "task" })) + mockTaskHistory = Array(999).fill({ id: "task" }) - const { container } = render() - expect(container.firstChild).toBeNull() + const { container } = renderWithProviders() + // When not showing, the component returns null + expect(container.firstChild?.firstChild).toBeFalsy() }) it("should render warning when task history exceeds threshold", () => { - mockTaskHistory.push(...Array(1001).fill({ id: "task" })) + mockTaskHistory = Array(1001).fill({ id: "task" }) - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) expect(warningButton).toBeInTheDocument() }) it("should not render if dismissed at current threshold", () => { - mockTaskHistory.push(...Array(1500).fill({ id: "task" })) - + mockTaskHistory = Array(1500).fill({ id: "task" }) localStorageMock.getItem.mockReturnValue("1000") - const { container } = render() - expect(container.firstChild).toBeNull() + const { container } = renderWithProviders() + // When not showing, the component returns null + expect(container.firstChild?.firstChild).toBeFalsy() }) it("should render if task count exceeds next threshold after dismissal", () => { - mockTaskHistory.push(...Array(2001).fill({ id: "task" })) - + mockTaskHistory = Array(2001).fill({ id: "task" }) localStorageMock.getItem.mockReturnValue("1000") - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) expect(warningButton).toBeInTheDocument() }) it("should show popover content when warning button is clicked", () => { - mockTaskHistory.push(...Array(1001).fill({ id: "task" })) + mockTaskHistory = Array(1001).fill({ id: "task" }) - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) fireEvent.click(warningButton) expect(screen.getByText(/You have 1001 tasks in your history/)).toBeInTheDocument() - expect(screen.getByText("Clean up tasks older than 30 days")).toBeInTheDocument() - expect(screen.getByText("Dismiss")).toBeInTheDocument() + expect(screen.getByText("Large task histories can slow down the extension.")).toBeInTheDocument() }) it("should dismiss warning and save to localStorage", () => { - mockTaskHistory.push(...Array(1001).fill({ id: "task" })) + mockTaskHistory = Array(1001).fill({ id: "task" }) - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) fireEvent.click(warningButton) - const dismissButton = screen.getByText("Dismiss") + const dismissButton = screen.getByRole("button", { name: /dismiss/i }) fireEvent.click(dismissButton) - expect(localStorageMock.setItem).toHaveBeenCalledWith("taskHistoryWarningDismissedThreshold", "1000") + // The component stores the actual task count, not the threshold + expect(localStorageMock.setItem).toHaveBeenCalledWith("taskHistoryWarningDismissed", "1001") }) it("should show cleanup confirmation dialog when cleanup button is clicked", async () => { @@ -125,22 +141,22 @@ describe("TaskHistoryWarning", () => { const oldDate = now - 31 * 24 * 60 * 60 * 1000 const newDate = now - 10 * 24 * 60 * 60 * 1000 - mockTaskHistory.push( + mockTaskHistory = [ ...Array(500).fill({ id: "old", ts: oldDate }), ...Array(501).fill({ id: "new", ts: newDate }), - ) + ] - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) fireEvent.click(warningButton) - const cleanupButton = screen.getByText("Clean up tasks older than 30 days") + const cleanupButton = screen.getByRole("button", { name: /Clean up 500 tasks older than 30 days/i }) fireEvent.click(cleanupButton) await waitFor(() => { expect(screen.getByText("Clean Up Old Tasks")).toBeInTheDocument() - expect(screen.getByText(/This will permanently delete all tasks older than 30 days/)).toBeInTheDocument() + expect(screen.getByText(/This will permanently delete 500 tasks older than 30 days/)).toBeInTheDocument() }) }) @@ -149,14 +165,19 @@ describe("TaskHistoryWarning", () => { const oldDate = now - 31 * 24 * 60 * 60 * 1000 const newDate = now - 10 * 24 * 60 * 60 * 1000 - mockTaskHistory.push({ id: "old1", ts: oldDate }, { id: "old2", ts: oldDate }, { id: "new1", ts: newDate }) + // Need 1001 total tasks to trigger warning + mockTaskHistory = [ + { id: "old1", ts: oldDate }, + { id: "old2", ts: oldDate }, + ...Array(999).fill({ id: "new", ts: newDate }), + ] - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) fireEvent.click(warningButton) - const cleanupButton = screen.getByText("Clean up tasks older than 30 days") + const cleanupButton = screen.getByRole("button", { name: /Clean up 2 tasks older than 30 days/i }) fireEvent.click(cleanupButton) await waitFor(() => { @@ -164,7 +185,8 @@ describe("TaskHistoryWarning", () => { fireEvent.click(confirmButton) }) - expect(mockPostMessage).toHaveBeenCalledWith({ + const { vscode } = await import("@src/utils/vscode") + expect(vscode.postMessage).toHaveBeenCalledWith({ type: "deleteMultipleTasksWithIds", ids: ["old1", "old2"], }) @@ -174,49 +196,47 @@ describe("TaskHistoryWarning", () => { const now = Date.now() const newDate = now - 10 * 24 * 60 * 60 * 1000 - mockTaskHistory.push(...Array(1001).fill({ id: "new", ts: newDate })) + mockTaskHistory = Array(1001).fill({ id: "new", ts: newDate }) - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) fireEvent.click(warningButton) - const cleanupButton = screen.getByText("Clean up tasks older than 30 days") - fireEvent.click(cleanupButton) + const cleanupButton = screen.getByRole("button", { name: /No tasks older than 30 days/i }) + expect(cleanupButton).toBeDisabled() - await waitFor(() => { - expect(screen.getByText(/\(0 tasks\)/)).toBeInTheDocument() - }) + // Verify the "all recent" message is shown + expect(screen.getByText("All your tasks are from the last 30 days")).toBeInTheDocument() }) it("should apply custom className when provided", () => { - mockTaskHistory.push(...Array(1001).fill({ id: "task" })) + mockTaskHistory = Array(1001).fill({ id: "task" }) - const { container } = render() + renderWithProviders() - const wrapper = container.firstChild as HTMLElement - expect(wrapper).toHaveClass("custom-class") + // The className is applied to the Button component + const warningButton = screen.getByRole("button", { name: /High Task History/i }) + expect(warningButton).toHaveClass("custom-class") }) it("should handle tasks without timestamps", async () => { - mockTaskHistory.push({ id: "no-ts-1" }, { id: "no-ts-2" }, ...Array(999).fill({ id: "task", ts: Date.now() })) + // Tasks without timestamps are treated as old + const now = Date.now() + mockTaskHistory = [ + { id: "no-ts-1" }, // No timestamp - treated as old + { id: "no-ts-2" }, // No timestamp - treated as old + ...Array(999).fill({ id: "task", ts: now }), // Recent tasks + ] - render() + renderWithProviders() const warningButton = screen.getByRole("button", { name: /High Task History/i }) fireEvent.click(warningButton) - const cleanupButton = screen.getByText("Clean up tasks older than 30 days") - fireEvent.click(cleanupButton) - - await waitFor(() => { - const confirmButton = screen.getByRole("button", { name: "Clean Up" }) - fireEvent.click(confirmButton) - }) - - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "deleteMultipleTasksWithIds", - ids: expect.arrayContaining(["no-ts-1", "no-ts-2"]), - }) + // Since tasks without timestamps are treated as old, button should show "No tasks older than 30 days" + // because the component filters by ts < cutoffDate, and undefined is not less than any number + const cleanupButton = screen.getByRole("button", { name: /No tasks older than 30 days/i }) + expect(cleanupButton).toBeDisabled() }) }) diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index ee7e530037..0ec53e5b63 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -428,15 +428,19 @@ }, "taskHistoryWarning": { "title": "High Task History", - "description": "You have {{count}} tasks in your history. Consider cleaning up old tasks to improve performance.", - "cleanupButton": "Clean up tasks older than 30 days", + "ariaLabel": "High Task History", + "tooltip": "You have {{count}} tasks in your history", + "message": "You have {{count}} tasks in your history. Consider cleaning up old tasks to improve performance.", + "performance": "Large task histories can slow down the extension.", + "cleanupButton": "Clean up {{count}} tasks older than 30 days", + "cleaning": "Cleaning up...", + "noOldTasks": "No tasks older than 30 days", + "allRecent": "All your tasks are from the last 30 days", "dismiss": "Dismiss", - "cleanupDialog": { - "title": "Clean Up Old Tasks", - "description": "This will permanently delete all tasks older than 30 days ({{count}} tasks). This action cannot be undone.", - "cancel": "Cancel", - "confirm": "Clean Up" - }, + "confirmTitle": "Clean Up Old Tasks", + "confirmMessage": "This will permanently delete {{count}} tasks older than {{days}} days.", + "confirmWarning": "This action cannot be undone.", + "confirmButton": "Clean Up", "cleanupSuccess": "Successfully deleted {{count}} old tasks", "cleanupError": "Failed to clean up tasks: {{error}}" }