Files
webmail/components/ui/__tests__/toast-progress.test.tsx
T
Linus Rath cd5fe770e4 feat: show file uploads as a toast like every other notice
Uploads in Files had their own card with a thick progress bar, larger
corners and a heavier shadow, so they looked unlike the toasts around
them.

Toasts can now show work in progress: the thin line at the bottom fills
with progress instead of counting down, and the toast stays until it is
updated or removed. Uploads use it, with the file and percentage below
the title and Cancel as the toast action.
2026-10-11 16:38:05 +02:00

56 lines
2.1 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { act, render, screen } from '@testing-library/react';
import { ToastContainer } from '../toast';
import { toast, useToastStore } from '@/stores/toast-store';
function Toasts() {
const toasts = useToastStore((s) => s.toasts);
const removeToast = useToastStore((s) => s.removeToast);
return <ToastContainer toasts={toasts} onClose={removeToast} />;
}
describe('progress toasts', () => {
beforeEach(() => {
vi.useFakeTimers();
useToastStore.setState({ toasts: [] });
});
afterEach(() => {
vi.useRealTimers();
});
it('shows progress on the bottom line and stays until removed', () => {
render(<Toasts />);
let id = '';
act(() => { id = toast.progress('Uploading...', { message: 'a.stl', progress: 0.25 }); });
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '25');
act(() => { vi.advanceTimersByTime(60_000); });
expect(screen.getByText('Uploading...')).toBeInTheDocument();
act(() => { toast.update(id, { progress: 0.8, message: 'a.stl · 80%' }); });
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '80');
expect(screen.getByText('a.stl · 80%')).toBeInTheDocument();
act(() => { toast.dismiss(id); });
expect(screen.queryByText('Uploading...')).toBeNull();
});
it('runs the action, e.g. Cancel, from the toast', () => {
const onCancel = vi.fn();
render(<Toasts />);
act(() => { toast.progress('Uploading...', { progress: null, action: { label: 'Cancel', onClick: onCancel } }); });
act(() => { screen.getByText('Cancel').click(); });
expect(onCancel).toHaveBeenCalled();
});
it('counts down normally once turned into a regular toast', () => {
render(<Toasts />);
let id = '';
act(() => { id = toast.progress('Uploading...', { progress: 0.5 }); });
act(() => { toast.update(id, { title: 'Uploaded', progress: undefined, duration: 1000, type: 'success' }); });
expect(screen.getByText('Uploaded')).toBeInTheDocument();
act(() => { vi.advanceTimersByTime(1500); });
expect(useToastStore.getState().toasts).toHaveLength(0);
});
});