From b86602b66441c313d83434079e5216aa99738f8c Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 4 Aug 2025 21:50:12 +0000 Subject: [PATCH 1/6] feat: clean up task list in HistoryPreview and History components - Remove all metadata except cost from task footer - Move datetime below task text with time-ago formatting using date-fns - Show up to 3 lines of task text (increased from 2 for compact variant) - Display up to 5 tasks in HistoryPreview (increased from 3) - Add hover effect with background color change - Update tests to reflect the new component structure --- .../src/components/history/HistoryPreview.tsx | 2 +- .../src/components/history/TaskItem.tsx | 14 ++++--- .../src/components/history/TaskItemFooter.tsx | 38 ++++-------------- .../src/components/history/TaskItemHeader.tsx | 32 ++++++--------- .../history/__tests__/HistoryPreview.spec.tsx | 36 ++++++++++++----- .../history/__tests__/TaskItem.spec.tsx | 30 ++++---------- .../history/__tests__/TaskItemFooter.spec.tsx | 39 +++++++------------ .../history/__tests__/TaskItemHeader.spec.tsx | 17 +++++--- webview-ui/src/utils/format.ts | 5 +++ 9 files changed, 90 insertions(+), 123 deletions(-) diff --git a/webview-ui/src/components/history/HistoryPreview.tsx b/webview-ui/src/components/history/HistoryPreview.tsx index 753b4b84e7c..04964f49676 100644 --- a/webview-ui/src/components/history/HistoryPreview.tsx +++ b/webview-ui/src/components/history/HistoryPreview.tsx @@ -18,7 +18,7 @@ const HistoryPreview = () => {
{tasks.length !== 0 && ( <> - {tasks.slice(0, 3).map((item) => ( + {tasks.slice(0, 5).map((item) => ( ))} diff --git a/webview-ui/src/components/history/DeleteButton.tsx b/webview-ui/src/components/history/DeleteButton.tsx index 3e99027546f..bd91803627a 100644 --- a/webview-ui/src/components/history/DeleteButton.tsx +++ b/webview-ui/src/components/history/DeleteButton.tsx @@ -31,7 +31,7 @@ export const DeleteButton = ({ itemId, onDelete }: DeleteButtonProps) => { size="icon" data-testid="delete-task-button" onClick={handleDeleteClick} - className="group-hover:opacity-100 opacity-50 transition-opacity"> + className="opacity-70"> diff --git a/webview-ui/src/components/history/HistoryPreview.tsx b/webview-ui/src/components/history/HistoryPreview.tsx index 04964f49676..753b4b84e7c 100644 --- a/webview-ui/src/components/history/HistoryPreview.tsx +++ b/webview-ui/src/components/history/HistoryPreview.tsx @@ -18,7 +18,7 @@ const HistoryPreview = () => {
{tasks.length !== 0 && ( <> - {tasks.slice(0, 5).map((item) => ( + {tasks.slice(0, 3).map((item) => ( ))}
- + { isSelected={selectedTaskIds.includes(item.id)} onToggleSelection={toggleTaskSelection} onDelete={setDeleteTaskId} - className="m-2 mr-0" + className="m-2" /> )} /> @@ -251,7 +251,7 @@ const HistoryView = ({ onDone }: HistoryViewProps) => { {/* Fixed action bar at bottom - only shown in selection mode with selected items */} {isSelectionMode && selectedTaskIds.length > 0 && ( -
+
{t("history:selectedItems", { selected: selectedTaskIds.length, total: tasks.length })}
diff --git a/webview-ui/src/components/history/TaskItem.tsx b/webview-ui/src/components/history/TaskItem.tsx index a02c0353ba6..d661d999300 100644 --- a/webview-ui/src/components/history/TaskItem.tsx +++ b/webview-ui/src/components/history/TaskItem.tsx @@ -5,7 +5,6 @@ import { vscode } from "@/utils/vscode" import { cn } from "@/lib/utils" import { Checkbox } from "@/components/ui/checkbox" -import TaskItemHeader from "./TaskItemHeader" import TaskItemFooter from "./TaskItemFooter" interface DisplayHistoryItem extends HistoryItem { @@ -52,7 +51,7 @@ const TaskItem = ({ className, )} onClick={handleClick}> -
+
{/* Selection checkbox - only in full variant */} {!isCompact && isSelectionMode && (
- {/* Header with metadata */} - - - {/* Task content - always show up to 3 lines */}
{item.highlight ? undefined : item.task}
+ - {/* Task Item Footer */} - - - {/* Workspace info */} {showWorkspace && item.workspace && (
diff --git a/webview-ui/src/components/history/TaskItemFooter.tsx b/webview-ui/src/components/history/TaskItemFooter.tsx index 2e4100a39f2..1f18419fa0f 100644 --- a/webview-ui/src/components/history/TaskItemFooter.tsx +++ b/webview-ui/src/components/history/TaskItemFooter.tsx @@ -3,20 +3,25 @@ import type { HistoryItem } from "@roo-code/types" import { formatTimeAgo } from "@/utils/format" import { CopyButton } from "./CopyButton" import { ExportButton } from "./ExportButton" +import { DeleteButton } from "./DeleteButton" +import { StandardTooltip } from "../ui/standard-tooltip" export interface TaskItemFooterProps { item: HistoryItem variant: "compact" | "full" isSelectionMode?: boolean + onDelete?: (taskId: string) => void } -const TaskItemFooter: React.FC = ({ item, variant, isSelectionMode = false }) => { +const TaskItemFooter: React.FC = ({ item, variant, isSelectionMode = false, onDelete }) => { return ( -
-
+
+
{/* Datetime with time-ago format */} - {formatTimeAgo(item.ts)} - + + {formatTimeAgo(item.ts)} + + · {/* Cost */} {!!item.totalCost && ( @@ -27,9 +32,10 @@ const TaskItemFooter: React.FC = ({ item, variant, isSelect {/* Action Buttons for non-compact view */} {!isSelectionMode && ( -
+
{variant === "full" && } + {onDelete && }
)}
diff --git a/webview-ui/src/components/history/TaskItemHeader.tsx b/webview-ui/src/components/history/TaskItemHeader.tsx deleted file mode 100644 index 7ad414222cd..00000000000 --- a/webview-ui/src/components/history/TaskItemHeader.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react" -import type { HistoryItem } from "@roo-code/types" -import { DeleteButton } from "./DeleteButton" - -export interface TaskItemHeaderProps { - item: HistoryItem - isSelectionMode: boolean - onDelete?: (taskId: string) => void -} - -const TaskItemHeader: React.FC = ({ item, isSelectionMode, onDelete }) => { - // Only show delete button if needed - if (!isSelectionMode && onDelete) { - return ( -
-
- -
-
- ) - } - - // Return null if no header content is needed - return null -} - -export default TaskItemHeader diff --git a/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx b/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx index 4e7d4d0b830..d46cda32f2c 100644 --- a/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx +++ b/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx @@ -56,5 +56,24 @@ describe("TaskItemFooter", () => { // Should not show any action buttons expect(screen.queryByTestId("copy-prompt-button")).not.toBeInTheDocument() expect(screen.queryByTestId("export")).not.toBeInTheDocument() + expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument() + }) + + it("shows delete button when not in selection mode and onDelete is provided", () => { + render() + + expect(screen.getByTestId("delete-task-button")).toBeInTheDocument() + }) + + it("does not show delete button in selection mode", () => { + render() + + expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument() + }) + + it("does not show delete button when onDelete is not provided", () => { + render() + + expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument() }) }) diff --git a/webview-ui/src/components/history/__tests__/TaskItemHeader.spec.tsx b/webview-ui/src/components/history/__tests__/TaskItemHeader.spec.tsx deleted file mode 100644 index c9b1908afe3..00000000000 --- a/webview-ui/src/components/history/__tests__/TaskItemHeader.spec.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { render, screen } from "@/utils/test-utils" - -import TaskItemHeader from "../TaskItemHeader" - -vi.mock("@src/i18n/TranslationContext", () => ({ - useAppTranslation: () => ({ - t: (key: string) => key, - }), -})) - -const mockItem = { - id: "1", - number: 1, - task: "Test task", - ts: Date.now(), - tokensIn: 100, - tokensOut: 50, - totalCost: 0.002, - workspace: "/test/workspace", -} - -describe("TaskItemHeader", () => { - it("shows delete button when not in selection mode and onDelete is provided", () => { - render() - - expect(screen.getByTestId("delete-task-button")).toBeInTheDocument() - }) - - it("does not show delete button in selection mode", () => { - render() - - expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument() - }) - - it("does not show delete button when onDelete is not provided", () => { - render() - - expect(screen.queryByTestId("delete-task-button")).not.toBeInTheDocument() - }) -}) From 0ef9c9aa4811c39eaff7bb4b5d452586ae240620 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Tue, 5 Aug 2025 12:21:44 +0100 Subject: [PATCH 3/6] Fixes test --- .../history/__tests__/HistoryPreview.spec.tsx | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx b/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx index b4605712f7d..20e7fcbdf3c 100644 --- a/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx +++ b/webview-ui/src/components/history/__tests__/HistoryPreview.spec.tsx @@ -104,7 +104,7 @@ describe("HistoryPreview", () => { expect(screen.queryByTestId(/task-item-/)).not.toBeInTheDocument() }) - it("renders up to 5 tasks when tasks are available", () => { + it("renders up to 3 tasks when tasks are available", () => { mockUseTaskSearch.mockReturnValue({ tasks: mockTasks, searchQuery: "", @@ -119,19 +119,19 @@ describe("HistoryPreview", () => { render() - // Should render only the first 5 tasks + // Should render only the first 3 tasks expect(screen.getByTestId("task-item-task-1")).toBeInTheDocument() expect(screen.getByTestId("task-item-task-2")).toBeInTheDocument() expect(screen.getByTestId("task-item-task-3")).toBeInTheDocument() - expect(screen.getByTestId("task-item-task-4")).toBeInTheDocument() - expect(screen.getByTestId("task-item-task-5")).toBeInTheDocument() + expect(screen.queryByTestId("task-item-task-4")).not.toBeInTheDocument() + expect(screen.queryByTestId("task-item-task-5")).not.toBeInTheDocument() expect(screen.queryByTestId("task-item-task-6")).not.toBeInTheDocument() }) - it("renders all tasks when there are 5 or fewer", () => { - const fiveTasks = mockTasks.slice(0, 5) + it("renders all tasks when there are 3 or fewer", () => { + const threeTasks = mockTasks.slice(0, 3) mockUseTaskSearch.mockReturnValue({ - tasks: fiveTasks, + tasks: threeTasks, searchQuery: "", setSearchQuery: vi.fn(), sortOption: "newest", @@ -147,8 +147,8 @@ describe("HistoryPreview", () => { expect(screen.getByTestId("task-item-task-1")).toBeInTheDocument() expect(screen.getByTestId("task-item-task-2")).toBeInTheDocument() expect(screen.getByTestId("task-item-task-3")).toBeInTheDocument() - expect(screen.getByTestId("task-item-task-4")).toBeInTheDocument() - expect(screen.getByTestId("task-item-task-5")).toBeInTheDocument() + expect(screen.queryByTestId("task-item-task-4")).not.toBeInTheDocument() + expect(screen.queryByTestId("task-item-task-5")).not.toBeInTheDocument() expect(screen.queryByTestId("task-item-task-6")).not.toBeInTheDocument() }) @@ -174,7 +174,7 @@ describe("HistoryPreview", () => { it("passes correct props to TaskItem components", () => { mockUseTaskSearch.mockReturnValue({ - tasks: mockTasks.slice(0, 5), + tasks: mockTasks.slice(0, 3), searchQuery: "", setSearchQuery: vi.fn(), sortOption: "newest", @@ -187,7 +187,7 @@ describe("HistoryPreview", () => { render() - // Verify TaskItem was called with correct props for first 5 tasks + // Verify TaskItem was called with correct props for first 3 tasks expect(mockTaskItem).toHaveBeenCalledWith( expect.objectContaining({ item: mockTasks[0], @@ -209,20 +209,6 @@ describe("HistoryPreview", () => { }), expect.anything(), ) - expect(mockTaskItem).toHaveBeenCalledWith( - expect.objectContaining({ - item: mockTasks[3], - variant: "compact", - }), - expect.anything(), - ) - expect(mockTaskItem).toHaveBeenCalledWith( - expect.objectContaining({ - item: mockTasks[4], - variant: "compact", - }), - expect.anything(), - ) }) it("renders with correct container classes", () => { From cffc22e785bccd0fd22da957fbea1d8f01d5b04a Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 5 Aug 2025 13:34:43 +0000 Subject: [PATCH 4/6] feat: internationalize formatTimeAgo function - Replaced date-fns formatDistanceToNow with custom implementation - Added time_ago translation strings to all locale files - Created comprehensive tests for formatTimeAgo function - Removed date-fns dependency from format.ts --- webview-ui/src/i18n/locales/ca/common.json | 16 ++ webview-ui/src/i18n/locales/de/common.json | 16 ++ webview-ui/src/i18n/locales/en/common.json | 16 ++ webview-ui/src/i18n/locales/es/common.json | 16 ++ webview-ui/src/i18n/locales/fr/common.json | 16 ++ webview-ui/src/i18n/locales/hi/common.json | 16 ++ webview-ui/src/i18n/locales/id/common.json | 16 ++ webview-ui/src/i18n/locales/it/common.json | 16 ++ webview-ui/src/i18n/locales/ja/common.json | 16 ++ webview-ui/src/i18n/locales/ko/common.json | 16 ++ webview-ui/src/i18n/locales/nl/common.json | 16 ++ webview-ui/src/i18n/locales/pl/common.json | 16 ++ webview-ui/src/i18n/locales/pt-BR/common.json | 16 ++ webview-ui/src/i18n/locales/ru/common.json | 16 ++ webview-ui/src/i18n/locales/tr/common.json | 16 ++ webview-ui/src/i18n/locales/vi/common.json | 16 ++ webview-ui/src/i18n/locales/zh-CN/common.json | 16 ++ webview-ui/src/i18n/locales/zh-TW/common.json | 16 ++ webview-ui/src/utils/__tests__/format.spec.ts | 167 ++++++++++++++---- webview-ui/src/utils/format.ts | 47 ++++- 20 files changed, 468 insertions(+), 34 deletions(-) diff --git a/webview-ui/src/i18n/locales/ca/common.json b/webview-ui/src/i18n/locales/ca/common.json index 13ad7a2ca70..2351c99d01b 100644 --- a/webview-ui/src/i18n/locales/ca/common.json +++ b/webview-ui/src/i18n/locales/ca/common.json @@ -67,5 +67,21 @@ "editMessage": "Editar missatge", "editWarning": "Editar aquest missatge eliminarà tots els missatges posteriors de la conversa. Vols continuar?", "proceed": "Continuar" + }, + "time_ago": { + "just_now": "ara mateix", + "seconds_ago": "fa {{count}} segons", + "minute_ago": "fa un minut", + "minutes_ago": "fa {{count}} minuts", + "hour_ago": "fa una hora", + "hours_ago": "fa {{count}} hores", + "day_ago": "fa un dia", + "days_ago": "fa {{count}} dies", + "week_ago": "fa una setmana", + "weeks_ago": "fa {{count}} setmanes", + "month_ago": "fa un mes", + "months_ago": "fa {{count}} mesos", + "year_ago": "fa un any", + "years_ago": "fa {{count}} anys" } } diff --git a/webview-ui/src/i18n/locales/de/common.json b/webview-ui/src/i18n/locales/de/common.json index c873e25d129..0fec0ba9649 100644 --- a/webview-ui/src/i18n/locales/de/common.json +++ b/webview-ui/src/i18n/locales/de/common.json @@ -67,5 +67,21 @@ "editMessage": "Nachricht bearbeiten", "editWarning": "Das Bearbeiten dieser Nachricht wird alle nachfolgenden Nachrichten in der Unterhaltung löschen. Möchtest du fortfahren?", "proceed": "Fortfahren" + }, + "time_ago": { + "just_now": "gerade eben", + "seconds_ago": "vor {{count}} Sekunden", + "minute_ago": "vor einer Minute", + "minutes_ago": "vor {{count}} Minuten", + "hour_ago": "vor einer Stunde", + "hours_ago": "vor {{count}} Stunden", + "day_ago": "vor einem Tag", + "days_ago": "vor {{count}} Tagen", + "week_ago": "vor einer Woche", + "weeks_ago": "vor {{count}} Wochen", + "month_ago": "vor einem Monat", + "months_ago": "vor {{count}} Monaten", + "year_ago": "vor einem Jahr", + "years_ago": "vor {{count}} Jahren" } } diff --git a/webview-ui/src/i18n/locales/en/common.json b/webview-ui/src/i18n/locales/en/common.json index af0dfcdf803..b4bc816a2b4 100644 --- a/webview-ui/src/i18n/locales/en/common.json +++ b/webview-ui/src/i18n/locales/en/common.json @@ -67,5 +67,21 @@ "editMessage": "Edit Message", "editWarning": "Editing this message will delete all subsequent messages in the conversation. Do you want to proceed?", "proceed": "Proceed" + }, + "time_ago": { + "just_now": "just now", + "seconds_ago": "{{count}} seconds ago", + "minute_ago": "a minute ago", + "minutes_ago": "{{count}} minutes ago", + "hour_ago": "an hour ago", + "hours_ago": "{{count}} hours ago", + "day_ago": "a day ago", + "days_ago": "{{count}} days ago", + "week_ago": "a week ago", + "weeks_ago": "{{count}} weeks ago", + "month_ago": "a month ago", + "months_ago": "{{count}} months ago", + "year_ago": "a year ago", + "years_ago": "{{count}} years ago" } } diff --git a/webview-ui/src/i18n/locales/es/common.json b/webview-ui/src/i18n/locales/es/common.json index ee0a924d43f..a7336734706 100644 --- a/webview-ui/src/i18n/locales/es/common.json +++ b/webview-ui/src/i18n/locales/es/common.json @@ -67,5 +67,21 @@ "editMessage": "Editar mensaje", "editWarning": "Editar este mensaje eliminará todos los mensajes posteriores en la conversación. ¿Deseas continuar?", "proceed": "Continuar" + }, + "time_ago": { + "just_now": "ahora mismo", + "seconds_ago": "hace {{count}} segundos", + "minute_ago": "hace un minuto", + "minutes_ago": "hace {{count}} minutos", + "hour_ago": "hace una hora", + "hours_ago": "hace {{count}} horas", + "day_ago": "hace un día", + "days_ago": "hace {{count}} días", + "week_ago": "hace una semana", + "weeks_ago": "hace {{count}} semanas", + "month_ago": "hace un mes", + "months_ago": "hace {{count}} meses", + "year_ago": "hace un año", + "years_ago": "hace {{count}} años" } } diff --git a/webview-ui/src/i18n/locales/fr/common.json b/webview-ui/src/i18n/locales/fr/common.json index 40c12e2afba..4c4ad83bc40 100644 --- a/webview-ui/src/i18n/locales/fr/common.json +++ b/webview-ui/src/i18n/locales/fr/common.json @@ -67,5 +67,21 @@ "editMessage": "Modifier le message", "editWarning": "Modifier ce message supprimera tous les messages suivants dans la conversation. Voulez-vous continuer ?", "proceed": "Continuer" + }, + "time_ago": { + "just_now": "à l'instant", + "seconds_ago": "il y a {{count}} secondes", + "minute_ago": "il y a une minute", + "minutes_ago": "il y a {{count}} minutes", + "hour_ago": "il y a une heure", + "hours_ago": "il y a {{count}} heures", + "day_ago": "il y a un jour", + "days_ago": "il y a {{count}} jours", + "week_ago": "il y a une semaine", + "weeks_ago": "il y a {{count}} semaines", + "month_ago": "il y a un mois", + "months_ago": "il y a {{count}} mois", + "year_ago": "il y a un an", + "years_ago": "il y a {{count}} ans" } } diff --git a/webview-ui/src/i18n/locales/hi/common.json b/webview-ui/src/i18n/locales/hi/common.json index 227e25637e8..7e809bd0a7e 100644 --- a/webview-ui/src/i18n/locales/hi/common.json +++ b/webview-ui/src/i18n/locales/hi/common.json @@ -67,5 +67,21 @@ "editMessage": "संदेश संपादित करें", "editWarning": "इस संदेश को संपादित करने से बातचीत के सभी बाद के संदेश हट जाएंगे। क्या आप जारी रखना चाहते हैं?", "proceed": "जारी रखें" + }, + "time_ago": { + "just_now": "अभी", + "seconds_ago": "{{count}} सेकंड पहले", + "minute_ago": "एक मिनट पहले", + "minutes_ago": "{{count}} मिनट पहले", + "hour_ago": "एक घंटे पहले", + "hours_ago": "{{count}} घंटे पहले", + "day_ago": "एक दिन पहले", + "days_ago": "{{count}} दिन पहले", + "week_ago": "एक सप्ताह पहले", + "weeks_ago": "{{count}} सप्ताह पहले", + "month_ago": "एक महीने पहले", + "months_ago": "{{count}} महीने पहले", + "year_ago": "एक साल पहले", + "years_ago": "{{count}} साल पहले" } } diff --git a/webview-ui/src/i18n/locales/id/common.json b/webview-ui/src/i18n/locales/id/common.json index 3a3a3f5a78d..86818bb0842 100644 --- a/webview-ui/src/i18n/locales/id/common.json +++ b/webview-ui/src/i18n/locales/id/common.json @@ -67,5 +67,21 @@ "editMessage": "Edit Pesan", "editWarning": "Mengedit pesan ini akan menghapus semua pesan selanjutnya dalam percakapan. Apakah kamu ingin melanjutkan?", "proceed": "Lanjutkan" + }, + "time_ago": { + "just_now": "baru saja", + "seconds_ago": "{{count}} detik yang lalu", + "minute_ago": "satu menit yang lalu", + "minutes_ago": "{{count}} menit yang lalu", + "hour_ago": "satu jam yang lalu", + "hours_ago": "{{count}} jam yang lalu", + "day_ago": "satu hari yang lalu", + "days_ago": "{{count}} hari yang lalu", + "week_ago": "satu minggu yang lalu", + "weeks_ago": "{{count}} minggu yang lalu", + "month_ago": "satu bulan yang lalu", + "months_ago": "{{count}} bulan yang lalu", + "year_ago": "satu tahun yang lalu", + "years_ago": "{{count}} tahun yang lalu" } } diff --git a/webview-ui/src/i18n/locales/it/common.json b/webview-ui/src/i18n/locales/it/common.json index 20886d126ba..94f637ac3dd 100644 --- a/webview-ui/src/i18n/locales/it/common.json +++ b/webview-ui/src/i18n/locales/it/common.json @@ -67,5 +67,21 @@ "editMessage": "Modifica Messaggio", "editWarning": "Modificando questo messaggio verranno eliminati tutti i messaggi successivi nella conversazione. Vuoi procedere?", "proceed": "Procedi" + }, + "time_ago": { + "just_now": "proprio ora", + "seconds_ago": "{{count}} secondi fa", + "minute_ago": "un minuto fa", + "minutes_ago": "{{count}} minuti fa", + "hour_ago": "un'ora fa", + "hours_ago": "{{count}} ore fa", + "day_ago": "un giorno fa", + "days_ago": "{{count}} giorni fa", + "week_ago": "una settimana fa", + "weeks_ago": "{{count}} settimane fa", + "month_ago": "un mese fa", + "months_ago": "{{count}} mesi fa", + "year_ago": "un anno fa", + "years_ago": "{{count}} anni fa" } } diff --git a/webview-ui/src/i18n/locales/ja/common.json b/webview-ui/src/i18n/locales/ja/common.json index a7390de32ab..a3f6a90a22b 100644 --- a/webview-ui/src/i18n/locales/ja/common.json +++ b/webview-ui/src/i18n/locales/ja/common.json @@ -67,5 +67,21 @@ "editMessage": "メッセージを編集", "editWarning": "このメッセージを編集すると、会話内の後続のメッセージもすべて削除されます。続行しますか?", "proceed": "続行" + }, + "time_ago": { + "just_now": "たった今", + "seconds_ago": "{{count}}秒前", + "minute_ago": "1分前", + "minutes_ago": "{{count}}分前", + "hour_ago": "1時間前", + "hours_ago": "{{count}}時間前", + "day_ago": "1日前", + "days_ago": "{{count}}日前", + "week_ago": "1週間前", + "weeks_ago": "{{count}}週間前", + "month_ago": "1ヶ月前", + "months_ago": "{{count}}ヶ月前", + "year_ago": "1年前", + "years_ago": "{{count}}年前" } } diff --git a/webview-ui/src/i18n/locales/ko/common.json b/webview-ui/src/i18n/locales/ko/common.json index 2164c656248..83d56930df4 100644 --- a/webview-ui/src/i18n/locales/ko/common.json +++ b/webview-ui/src/i18n/locales/ko/common.json @@ -67,5 +67,21 @@ "editMessage": "메시지 편집", "editWarning": "이 메시지를 편집하면 대화의 모든 후속 메시지가 삭제됩니다. 계속하시겠습니까?", "proceed": "계속" + }, + "time_ago": { + "just_now": "방금", + "seconds_ago": "{{count}}초 전", + "minute_ago": "1분 전", + "minutes_ago": "{{count}}분 전", + "hour_ago": "1시간 전", + "hours_ago": "{{count}}시간 전", + "day_ago": "1일 전", + "days_ago": "{{count}}일 전", + "week_ago": "1주일 전", + "weeks_ago": "{{count}}주일 전", + "month_ago": "1개월 전", + "months_ago": "{{count}}개월 전", + "year_ago": "1년 전", + "years_ago": "{{count}}년 전" } } diff --git a/webview-ui/src/i18n/locales/nl/common.json b/webview-ui/src/i18n/locales/nl/common.json index 4b72bccb9c5..d81570c7057 100644 --- a/webview-ui/src/i18n/locales/nl/common.json +++ b/webview-ui/src/i18n/locales/nl/common.json @@ -67,5 +67,21 @@ "editMessage": "Bericht Bewerken", "editWarning": "Het bewerken van dit bericht zal alle volgende berichten in het gesprek verwijderen. Wil je doorgaan?", "proceed": "Doorgaan" + }, + "time_ago": { + "just_now": "zojuist", + "seconds_ago": "{{count}} seconden geleden", + "minute_ago": "een minuut geleden", + "minutes_ago": "{{count}} minuten geleden", + "hour_ago": "een uur geleden", + "hours_ago": "{{count}} uur geleden", + "day_ago": "een dag geleden", + "days_ago": "{{count}} dagen geleden", + "week_ago": "een week geleden", + "weeks_ago": "{{count}} weken geleden", + "month_ago": "een maand geleden", + "months_ago": "{{count}} maanden geleden", + "year_ago": "een jaar geleden", + "years_ago": "{{count}} jaar geleden" } } diff --git a/webview-ui/src/i18n/locales/pl/common.json b/webview-ui/src/i18n/locales/pl/common.json index 6ec9e6661ad..77679ef7c5e 100644 --- a/webview-ui/src/i18n/locales/pl/common.json +++ b/webview-ui/src/i18n/locales/pl/common.json @@ -67,5 +67,21 @@ "editMessage": "Edytuj Wiadomość", "editWarning": "Edycja tej wiadomości spowoduje usunięcie wszystkich kolejnych wiadomości w rozmowie. Czy chcesz kontynuować?", "proceed": "Kontynuuj" + }, + "time_ago": { + "just_now": "przed chwilą", + "seconds_ago": "{{count}} sekund temu", + "minute_ago": "minutę temu", + "minutes_ago": "{{count}} minut temu", + "hour_ago": "godzinę temu", + "hours_ago": "{{count}} godzin temu", + "day_ago": "dzień temu", + "days_ago": "{{count}} dni temu", + "week_ago": "tydzień temu", + "weeks_ago": "{{count}} tygodni temu", + "month_ago": "miesiąc temu", + "months_ago": "{{count}} miesięcy temu", + "year_ago": "rok temu", + "years_ago": "{{count}} lat temu" } } diff --git a/webview-ui/src/i18n/locales/pt-BR/common.json b/webview-ui/src/i18n/locales/pt-BR/common.json index 964ba893f27..3fb4273d89c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/common.json +++ b/webview-ui/src/i18n/locales/pt-BR/common.json @@ -67,5 +67,21 @@ "editMessage": "Editar Mensagem", "editWarning": "Editar esta mensagem irá excluir todas as mensagens subsequentes na conversa. Deseja prosseguir?", "proceed": "Prosseguir" + }, + "time_ago": { + "just_now": "agora mesmo", + "seconds_ago": "há {{count}} segundos", + "minute_ago": "há um minuto", + "minutes_ago": "há {{count}} minutos", + "hour_ago": "há uma hora", + "hours_ago": "há {{count}} horas", + "day_ago": "há um dia", + "days_ago": "há {{count}} dias", + "week_ago": "há uma semana", + "weeks_ago": "há {{count}} semanas", + "month_ago": "há um mês", + "months_ago": "há {{count}} meses", + "year_ago": "há um ano", + "years_ago": "há {{count}} anos" } } diff --git a/webview-ui/src/i18n/locales/ru/common.json b/webview-ui/src/i18n/locales/ru/common.json index 772b797bba7..f43c2e9e8bb 100644 --- a/webview-ui/src/i18n/locales/ru/common.json +++ b/webview-ui/src/i18n/locales/ru/common.json @@ -67,5 +67,21 @@ "editMessage": "Редактировать Сообщение", "editWarning": "Редактирование этого сообщения приведет к удалению всех последующих сообщений в разговоре. Хотите продолжить?", "proceed": "Продолжить" + }, + "time_ago": { + "just_now": "только что", + "seconds_ago": "{{count}} секунд назад", + "minute_ago": "минуту назад", + "minutes_ago": "{{count}} минут назад", + "hour_ago": "час назад", + "hours_ago": "{{count}} часов назад", + "day_ago": "день назад", + "days_ago": "{{count}} дней назад", + "week_ago": "неделю назад", + "weeks_ago": "{{count}} недель назад", + "month_ago": "месяц назад", + "months_ago": "{{count}} месяцев назад", + "year_ago": "год назад", + "years_ago": "{{count}} лет назад" } } diff --git a/webview-ui/src/i18n/locales/tr/common.json b/webview-ui/src/i18n/locales/tr/common.json index 7bbb6f3d841..2f3a7c957cd 100644 --- a/webview-ui/src/i18n/locales/tr/common.json +++ b/webview-ui/src/i18n/locales/tr/common.json @@ -67,5 +67,21 @@ "editMessage": "Mesajı Düzenle", "editWarning": "Bu mesajı düzenlemek, konuşmadaki sonraki tüm mesajları da silecektir. Devam etmek istiyor musun?", "proceed": "Devam Et" + }, + "time_ago": { + "just_now": "şimdi", + "seconds_ago": "{{count}} saniye önce", + "minute_ago": "bir dakika önce", + "minutes_ago": "{{count}} dakika önce", + "hour_ago": "bir saat önce", + "hours_ago": "{{count}} saat önce", + "day_ago": "bir gün önce", + "days_ago": "{{count}} gün önce", + "week_ago": "bir hafta önce", + "weeks_ago": "{{count}} hafta önce", + "month_ago": "bir ay önce", + "months_ago": "{{count}} ay önce", + "year_ago": "bir yıl önce", + "years_ago": "{{count}} yıl önce" } } diff --git a/webview-ui/src/i18n/locales/vi/common.json b/webview-ui/src/i18n/locales/vi/common.json index 2d364824955..92aa029b018 100644 --- a/webview-ui/src/i18n/locales/vi/common.json +++ b/webview-ui/src/i18n/locales/vi/common.json @@ -67,5 +67,21 @@ "editMessage": "Chỉnh Sửa Tin Nhắn", "editWarning": "Chỉnh sửa tin nhắn này sẽ xóa tất cả các tin nhắn tiếp theo trong cuộc trò chuyện. Bạn có muốn tiếp tục không?", "proceed": "Tiếp Tục" + }, + "time_ago": { + "just_now": "vừa xong", + "seconds_ago": "{{count}} giây trước", + "minute_ago": "một phút trước", + "minutes_ago": "{{count}} phút trước", + "hour_ago": "một giờ trước", + "hours_ago": "{{count}} giờ trước", + "day_ago": "một ngày trước", + "days_ago": "{{count}} ngày trước", + "week_ago": "một tuần trước", + "weeks_ago": "{{count}} tuần trước", + "month_ago": "một tháng trước", + "months_ago": "{{count}} tháng trước", + "year_ago": "một năm trước", + "years_ago": "{{count}} năm trước" } } diff --git a/webview-ui/src/i18n/locales/zh-CN/common.json b/webview-ui/src/i18n/locales/zh-CN/common.json index de6d1cd7feb..6ff0132370a 100644 --- a/webview-ui/src/i18n/locales/zh-CN/common.json +++ b/webview-ui/src/i18n/locales/zh-CN/common.json @@ -67,5 +67,21 @@ "editMessage": "编辑消息", "editWarning": "编辑此消息将删除对话中的所有后续消息。是否继续?", "proceed": "继续" + }, + "time_ago": { + "just_now": "刚刚", + "seconds_ago": "{{count}}秒前", + "minute_ago": "1分钟前", + "minutes_ago": "{{count}}分钟前", + "hour_ago": "1小时前", + "hours_ago": "{{count}}小时前", + "day_ago": "1天前", + "days_ago": "{{count}}天前", + "week_ago": "1周前", + "weeks_ago": "{{count}}周前", + "month_ago": "1个月前", + "months_ago": "{{count}}个月前", + "year_ago": "1年前", + "years_ago": "{{count}}年前" } } diff --git a/webview-ui/src/i18n/locales/zh-TW/common.json b/webview-ui/src/i18n/locales/zh-TW/common.json index a3949a2a9d0..3a3310797b4 100644 --- a/webview-ui/src/i18n/locales/zh-TW/common.json +++ b/webview-ui/src/i18n/locales/zh-TW/common.json @@ -67,5 +67,21 @@ "editMessage": "編輯訊息", "editWarning": "編輯此訊息將刪除對話中的所有後續訊息。是否繼續?", "proceed": "繼續" + }, + "time_ago": { + "just_now": "剛剛", + "seconds_ago": "{{count}}秒前", + "minute_ago": "1分鐘前", + "minutes_ago": "{{count}}分鐘前", + "hour_ago": "1小時前", + "hours_ago": "{{count}}小時前", + "day_ago": "1天前", + "days_ago": "{{count}}天前", + "week_ago": "1週前", + "weeks_ago": "{{count}}週前", + "month_ago": "1個月前", + "months_ago": "{{count}}個月前", + "year_ago": "1年前", + "years_ago": "{{count}}年前" } } diff --git a/webview-ui/src/utils/__tests__/format.spec.ts b/webview-ui/src/utils/__tests__/format.spec.ts index 4ebd357b6d7..a3cbfd34adc 100644 --- a/webview-ui/src/utils/__tests__/format.spec.ts +++ b/webview-ui/src/utils/__tests__/format.spec.ts @@ -1,51 +1,154 @@ -// npx vitest src/utils/__tests__/format.spec.ts +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" +import { formatLargeNumber, formatDate, formatTimeAgo } from "../format" -import { formatDate } from "../format" +// Mock i18next +vi.mock("i18next", () => ({ + default: { + t: vi.fn((key: string, options?: any) => { + // Mock translations for testing + const translations: Record = { + "common:number_format.billion_suffix": "b", + "common:number_format.million_suffix": "m", + "common:number_format.thousand_suffix": "k", + "common:time_ago.just_now": "just now", + "common:time_ago.seconds_ago": "{{count}} seconds ago", + "common:time_ago.minute_ago": "a minute ago", + "common:time_ago.minutes_ago": "{{count}} minutes ago", + "common:time_ago.hour_ago": "an hour ago", + "common:time_ago.hours_ago": "{{count}} hours ago", + "common:time_ago.day_ago": "a day ago", + "common:time_ago.days_ago": "{{count}} days ago", + "common:time_ago.week_ago": "a week ago", + "common:time_ago.weeks_ago": "{{count}} weeks ago", + "common:time_ago.month_ago": "a month ago", + "common:time_ago.months_ago": "{{count}} months ago", + "common:time_ago.year_ago": "a year ago", + "common:time_ago.years_ago": "{{count}} years ago", + } + + let result = translations[key] || key + if (options?.count !== undefined) { + result = result.replace("{{count}}", options.count.toString()) + } + return result + }), + language: "en", + }, +})) + +describe("formatLargeNumber", () => { + it("should format billions", () => { + expect(formatLargeNumber(1500000000)).toBe("1.5b") + expect(formatLargeNumber(2000000000)).toBe("2.0b") + }) + + it("should format millions", () => { + expect(formatLargeNumber(1500000)).toBe("1.5m") + expect(formatLargeNumber(2000000)).toBe("2.0m") + }) + + it("should format thousands", () => { + expect(formatLargeNumber(1500)).toBe("1.5k") + expect(formatLargeNumber(2000)).toBe("2.0k") + }) + + it("should return string for small numbers", () => { + expect(formatLargeNumber(999)).toBe("999") + expect(formatLargeNumber(100)).toBe("100") + }) +}) describe("formatDate", () => { - it("formats a timestamp correctly", () => { - // January 15, 2023, 10:30 AM - const timestamp = new Date(2023, 0, 15, 10, 30).getTime() + it("should format date in English", () => { + const timestamp = new Date("2024-01-15T14:30:00").getTime() const result = formatDate(timestamp) + // The exact format depends on the locale, but it should contain the date components + expect(result).toMatch(/JANUARY|JAN/) + expect(result).toMatch(/15/) + }) +}) + +describe("formatTimeAgo", () => { + let originalDateNow: () => number + + beforeEach(() => { + // Mock Date.now to have a consistent "now" time + originalDateNow = Date.now + Date.now = vi.fn(() => new Date("2024-01-15T12:00:00").getTime()) + }) + + afterEach(() => { + // Restore original Date.now + Date.now = originalDateNow + }) + + it('should return "just now" for very recent times', () => { + const timestamp = new Date("2024-01-15T11:59:35").getTime() // 25 seconds ago + expect(formatTimeAgo(timestamp)).toBe("just now") + }) - expect(result).toBe("JANUARY 15, 10:30 AM") + it("should format seconds ago", () => { + const timestamp = new Date("2024-01-15T11:59:15").getTime() // 45 seconds ago + expect(formatTimeAgo(timestamp)).toBe("45 seconds ago") }) - it("handles different months correctly", () => { - // February 28, 2023, 3:45 PM - const timestamp1 = new Date(2023, 1, 28, 15, 45).getTime() - expect(formatDate(timestamp1)).toBe("FEBRUARY 28, 3:45 PM") + it("should format a minute ago", () => { + const timestamp = new Date("2024-01-15T11:59:00").getTime() // 1 minute ago + expect(formatTimeAgo(timestamp)).toBe("a minute ago") + }) - // December 31, 2023, 11:59 PM - const timestamp2 = new Date(2023, 11, 31, 23, 59).getTime() - expect(formatDate(timestamp2)).toBe("DECEMBER 31, 11:59 PM") + it("should format minutes ago", () => { + const timestamp = new Date("2024-01-15T11:45:00").getTime() // 15 minutes ago + expect(formatTimeAgo(timestamp)).toBe("15 minutes ago") }) - it("handles AM/PM correctly", () => { - // Morning time - 7:05 AM - const morningTimestamp = new Date(2023, 5, 15, 7, 5).getTime() - expect(formatDate(morningTimestamp)).toBe("JUNE 15, 7:05 AM") + it("should format an hour ago", () => { + const timestamp = new Date("2024-01-15T11:00:00").getTime() // 1 hour ago + expect(formatTimeAgo(timestamp)).toBe("an hour ago") + }) - // Noon - 12:00 PM - const noonTimestamp = new Date(2023, 5, 15, 12, 0).getTime() - expect(formatDate(noonTimestamp)).toBe("JUNE 15, 12:00 PM") + it("should format hours ago", () => { + const timestamp = new Date("2024-01-15T09:00:00").getTime() // 3 hours ago + expect(formatTimeAgo(timestamp)).toBe("3 hours ago") + }) - // Evening time - 8:15 PM - const eveningTimestamp = new Date(2023, 5, 15, 20, 15).getTime() - expect(formatDate(eveningTimestamp)).toBe("JUNE 15, 8:15 PM") + it("should format a day ago", () => { + const timestamp = new Date("2024-01-14T12:00:00").getTime() // 1 day ago + expect(formatTimeAgo(timestamp)).toBe("a day ago") }) - it("handles single-digit minutes with leading zeros", () => { - // 9:05 AM - const timestamp = new Date(2023, 3, 10, 9, 5).getTime() - expect(formatDate(timestamp)).toBe("APRIL 10, 9:05 AM") + it("should format days ago", () => { + const timestamp = new Date("2024-01-12T12:00:00").getTime() // 3 days ago + expect(formatTimeAgo(timestamp)).toBe("3 days ago") }) - it("converts the result to uppercase", () => { - const timestamp = new Date(2023, 8, 21, 16, 45).getTime() - const result = formatDate(timestamp) + it("should format a week ago", () => { + const timestamp = new Date("2024-01-08T12:00:00").getTime() // 7 days ago + expect(formatTimeAgo(timestamp)).toBe("a week ago") + }) + + it("should format weeks ago", () => { + const timestamp = new Date("2024-01-01T12:00:00").getTime() // 14 days ago + expect(formatTimeAgo(timestamp)).toBe("2 weeks ago") + }) + + it("should format a month ago", () => { + const timestamp = new Date("2023-12-15T12:00:00").getTime() // ~1 month ago + expect(formatTimeAgo(timestamp)).toBe("a month ago") + }) + + it("should format months ago", () => { + const timestamp = new Date("2023-10-15T12:00:00").getTime() // ~3 months ago + expect(formatTimeAgo(timestamp)).toBe("3 months ago") + }) + + it("should format a year ago", () => { + const timestamp = new Date("2023-01-15T12:00:00").getTime() // 1 year ago + expect(formatTimeAgo(timestamp)).toBe("a year ago") + }) - expect(result).toBe(result.toUpperCase()) - expect(result).toBe("SEPTEMBER 21, 4:45 PM") + it("should format years ago", () => { + const timestamp = new Date("2021-01-15T12:00:00").getTime() // 3 years ago + expect(formatTimeAgo(timestamp)).toBe("3 years ago") }) }) diff --git a/webview-ui/src/utils/format.ts b/webview-ui/src/utils/format.ts index c392704d66c..e8cca9e3394 100644 --- a/webview-ui/src/utils/format.ts +++ b/webview-ui/src/utils/format.ts @@ -1,5 +1,4 @@ import i18next from "i18next" -import { formatDistanceToNow } from "date-fns" export function formatLargeNumber(num: number): string { if (num >= 1e9) { @@ -36,5 +35,49 @@ export const formatDate = (timestamp: number) => { } export const formatTimeAgo = (timestamp: number) => { - return formatDistanceToNow(new Date(timestamp), { addSuffix: true }) + const now = Date.now() + const diff = now - timestamp + const seconds = Math.floor(diff / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const weeks = Math.floor(days / 7) + const months = Math.floor(days / 30) + const years = Math.floor(days / 365) + + if (years > 0) { + return years === 1 + ? i18next.t("common:time_ago.year_ago") + : i18next.t("common:time_ago.years_ago", { count: years }) + } + if (months > 0) { + return months === 1 + ? i18next.t("common:time_ago.month_ago") + : i18next.t("common:time_ago.months_ago", { count: months }) + } + if (weeks > 0) { + return weeks === 1 + ? i18next.t("common:time_ago.week_ago") + : i18next.t("common:time_ago.weeks_ago", { count: weeks }) + } + if (days > 0) { + return days === 1 + ? i18next.t("common:time_ago.day_ago") + : i18next.t("common:time_ago.days_ago", { count: days }) + } + if (hours > 0) { + return hours === 1 + ? i18next.t("common:time_ago.hour_ago") + : i18next.t("common:time_ago.hours_ago", { count: hours }) + } + if (minutes > 0) { + return minutes === 1 + ? i18next.t("common:time_ago.minute_ago") + : i18next.t("common:time_ago.minutes_ago", { count: minutes }) + } + if (seconds > 30) { + return i18next.t("common:time_ago.seconds_ago", { count: seconds }) + } + + return i18next.t("common:time_ago.just_now") } From 8f54d788f9de76b77d5c174d00fd01d47c6c6a67 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Tue, 5 Aug 2025 16:23:48 +0100 Subject: [PATCH 5/6] Minor fixes --- webview-ui/src/components/history/TaskItemFooter.tsx | 2 +- webview-ui/src/utils/format.ts | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/webview-ui/src/components/history/TaskItemFooter.tsx b/webview-ui/src/components/history/TaskItemFooter.tsx index 1f18419fa0f..135d24d2c03 100644 --- a/webview-ui/src/components/history/TaskItemFooter.tsx +++ b/webview-ui/src/components/history/TaskItemFooter.tsx @@ -19,7 +19,7 @@ const TaskItemFooter: React.FC = ({ item, variant, isSelect
{/* Datetime with time-ago format */} - {formatTimeAgo(item.ts)} + {formatTimeAgo(item.ts)} · {/* Cost */} diff --git a/webview-ui/src/utils/format.ts b/webview-ui/src/utils/format.ts index e8cca9e3394..29c7a2c9663 100644 --- a/webview-ui/src/utils/format.ts +++ b/webview-ui/src/utils/format.ts @@ -17,21 +17,13 @@ export const formatDate = (timestamp: number) => { const date = new Date(timestamp) const locale = i18next.language || "en" - // Get date format style from translations or use default transformations - const dateStr = date.toLocaleString(locale, { + return date.toLocaleString(locale, { month: "long", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true, }) - - // Apply transformations based on locale or use default - if (locale === "en") { - return dateStr.replace(", ", " ").replace(" at", ",").toUpperCase() - } - - return dateStr.toUpperCase() } export const formatTimeAgo = (timestamp: number) => { From 8be6dd11428c54d60fd6a3c863ddf3805e80138f Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 5 Aug 2025 08:33:32 -0700 Subject: [PATCH 6/6] Fix tests --- .../src/components/history/__tests__/TaskItem.spec.tsx | 6 ++++++ .../components/history/__tests__/TaskItemFooter.spec.tsx | 6 ++++++ webview-ui/src/utils/__tests__/format.spec.ts | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx b/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx index 536e8c0e05e..6995d5840c0 100644 --- a/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx +++ b/webview-ui/src/components/history/__tests__/TaskItem.spec.tsx @@ -9,6 +9,12 @@ vi.mock("@src/i18n/TranslationContext", () => ({ }), })) +vi.mock("@/utils/format", () => ({ + formatTimeAgo: vi.fn(() => "2 hours ago"), + formatDate: vi.fn(() => "January 15 at 2:30 PM"), + formatLargeNumber: vi.fn((num: number) => num.toString()), +})) + const mockTask = { id: "1", number: 1, diff --git a/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx b/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx index d46cda32f2c..5c568bb65bc 100644 --- a/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx +++ b/webview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsx @@ -8,6 +8,12 @@ vi.mock("@src/i18n/TranslationContext", () => ({ }), })) +vi.mock("@/utils/format", () => ({ + formatTimeAgo: vi.fn(() => "2 hours ago"), + formatDate: vi.fn(() => "January 15 at 2:30 PM"), + formatLargeNumber: vi.fn((num: number) => num.toString()), +})) + const mockItem = { id: "1", number: 1, diff --git a/webview-ui/src/utils/__tests__/format.spec.ts b/webview-ui/src/utils/__tests__/format.spec.ts index a3cbfd34adc..4d642f3f44f 100644 --- a/webview-ui/src/utils/__tests__/format.spec.ts +++ b/webview-ui/src/utils/__tests__/format.spec.ts @@ -63,7 +63,7 @@ describe("formatDate", () => { const timestamp = new Date("2024-01-15T14:30:00").getTime() const result = formatDate(timestamp) // The exact format depends on the locale, but it should contain the date components - expect(result).toMatch(/JANUARY|JAN/) + expect(result).toMatch(/january|jan/i) expect(result).toMatch(/15/) }) })