import { it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'; const { confirmDialog, promptDialog } = vi.hoisted(() => ({ confirmDialog: vi.fn(), promptDialog: vi.fn() })); vi.mock('$lib/api/endpoints/recent', () => ({ clearRecent: vi.fn(), fetchRecentPage: vi.fn(), removeFromRecent: vi.fn() })); vi.mock('$lib/api/endpoints/favorites', () => ({ dateBucket: () => 'Today', resolveOwnerName: vi.fn(async () => 'me'), sizeBucket: () => 'Small', typeLabel: () => 'File' })); vi.mock('$lib/api/endpoints/files', () => ({ fileDownloadUrl: () => '/dl', renameFile: vi.fn(), deleteFile: vi.fn() })); vi.mock('$lib/api/endpoints/folders', () => ({ renameFolder: vi.fn(), deleteFolder: vi.fn() })); vi.mock('$lib/stores/dialogs.svelte', () => ({ confirmDialog, promptDialog })); import { fetchRecentPage, clearRecent, removeFromRecent } from '$lib/api/endpoints/recent'; import RecentPage from './+page.svelte'; const m = (fn: unknown) => fn as ReturnType; function withOneFile() { m(fetchRecentPage).mockResolvedValue({ items: [ { resource_type: 'file', accessed_at: '2024-01-01T00:00:00Z', resource: { category: 'Document', created_at: 0, icon_class: 'fa-file', icon_special_class: '', id: 'r1', mime_type: 'text/plain', modified_at: 0, name: 'notes.txt', created_by: 'me', updated_by: 'me', folder_id: 'root', path: '/notes.txt', size: 4, size_formatted: '4 B', sort_date: 0, etag: 'e', content_hash: 'h' } } ], next_cursor: null }); } beforeEach(() => vi.clearAllMocks()); it('renders recent items returned by the API', async () => { withOneFile(); render(RecentPage); await waitFor(() => expect(fetchRecentPage).toHaveBeenCalled()); await waitFor(() => expect(screen.getByText('notes.txt')).toBeTruthy()); }); it('clears recent activity after confirmation', async () => { withOneFile(); confirmDialog.mockResolvedValue(true); m(clearRecent).mockResolvedValue(undefined); render(RecentPage); await fireEvent.click(await screen.findByTestId('recent-clear-btn')); await waitFor(() => expect(clearRecent).toHaveBeenCalled()); }); it('removes a recent row via the broom button', async () => { // Recent no longer surfaces a favorite star (users go to the item's // real home for that). The per-row affordance is now a broom that // calls `DELETE /api/recent/{kind}/{id}` — verified end-to-end via // the `removeFromRecent` mock. withOneFile(); m(removeFromRecent).mockResolvedValue(undefined); render(RecentPage); await screen.findByText('notes.txt'); await fireEvent.click(screen.getByTestId('recent-remove-btn-r1')); await waitFor(() => expect(removeFromRecent).toHaveBeenCalledWith('file', 'r1')); }); it('batch-removes-from-recent the selection', async () => { // /recent's batch bar was intentionally trimmed to Download + // Remove-from-recent. Bulk-deleting the underlying file from // this history view (previous behaviour) confused the "this is // activity log" semantics — destructive actions belong in the // row's context menu, not in the batch bar. This test pins the // new shape: batch button just forgets the selection from history. withOneFile(); m(removeFromRecent).mockResolvedValue(undefined); render(RecentPage); await screen.findByText('notes.txt'); await fireEvent.click(screen.getByTestId('resource-list-select-r1-checkbox')); await fireEvent.click(await screen.findByTestId('recent-batch-remove-btn')); await waitFor(() => expect(removeFromRecent).toHaveBeenCalledWith('file', 'r1')); }); it('renders an empty state when there is no recent activity', async () => { m(fetchRecentPage).mockResolvedValue({ items: [], next_cursor: null }); render(RecentPage); await waitFor(() => expect(fetchRecentPage).toHaveBeenCalled()); });