Files
TREK/client/src/components/Files/FileManagerPreviewModals.test.tsx
T
Maurice 0dd07befa7 feat(planner): bring the plan tab and every trip dialog into the new look
The Plan tab was the last trip tab still in the old design. It now reads
like Bookings, Costs, Lists, Files and Collab, without losing a function:

- Day plan: each day is a card with a head band (day tile with weather,
  stay and rental pills, a "+" menu for place, stay, transport, transit
  and note), rows with "..." menus, white badges and a white route bar.
- Places column: add, search and filters in three rows, a show dropdown,
  category and rating filters, and a selection bar at the foot.
- Place inspector, day details and the stay editor on the shared dialog
  frame; the place form in the editor look with the name in the head band.
- Clicking a booking or transport anywhere on the plan opens the booking
  detail popup first; Edit leads to the editor. The popup is one host
  shared with the Bookings and Transports tabs.
- Files and Collab dialogs, notes, polls, links and chat moved to the same
  frame; shared DialogShell and dialogParts hold the building blocks.
- The map hover card shows the place photo and follows the theme.

Every new and reworded string is translated into all locales, and a few
older English leftovers (help center, cost split, import hints) with them.
Phone screens keep their own look.
2026-09-27 21:00:19 +02:00

190 lines
7.4 KiB
TypeScript

// FE-W4FPM-001 to FE-W4FPM-014
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import type { TripFile } from '../../types'
import { render, screen, fireEvent, waitFor } from '../../../tests/helpers/render'
import type { FileManagerState } from './useFileManager'
const openFile = vi.fn(async (_url: string, _name: string) => {})
const downloadFile = vi.fn(async (_url: string, _name: string) => {})
vi.mock('../../utils/fileDownload', () => ({
openFile: (url: string, name: string) => openFile(url, name),
downloadFile: (url: string, name: string) => downloadFile(url, name),
}))
import { PdfPreviewModal } from './FileManagerPdfPreviewModal'
import { MarkdownPreviewModal } from './FileManagerMarkdownPreviewModal'
const toast = { success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() }
const setPreviewFile = vi.fn()
function state(overrides: Partial<FileManagerState> = {}): FileManagerState {
return {
previewFile: { id: 1, original_name: 'ticket.pdf', url: '/uploads/files/ticket.pdf' } as unknown as TripFile,
previewFileUrl: '/uploads/files/ticket.pdf?token=abc',
setPreviewFile,
toast,
t: (key: string) => key,
...overrides,
} as unknown as FileManagerState
}
beforeEach(() => {
openFile.mockReset()
openFile.mockResolvedValue(undefined)
downloadFile.mockReset()
downloadFile.mockResolvedValue(undefined)
setPreviewFile.mockClear()
Object.values(toast).forEach(f => f.mockClear())
})
afterEach(() => {
vi.unstubAllGlobals()
})
describe('PdfPreviewModal', () => {
it('FE-W4FPM-001: portals the viewer with the signed url and the file name', () => {
const { container, baseElement } = render(<PdfPreviewModal {...state()} />)
expect(container).toBeEmptyDOMElement()
expect(screen.getByText('ticket.pdf')).toBeInTheDocument()
const object = baseElement.querySelector('object') as HTMLObjectElement
expect(object).toHaveAttribute('data', '/uploads/files/ticket.pdf?token=abc#view=FitH')
expect(object).toHaveAttribute('type', 'application/pdf')
})
it('FE-W4FPM-002: leaves the viewer source unset while no signed url exists yet', () => {
const { baseElement } = render(<PdfPreviewModal {...state({ previewFileUrl: null })} />)
expect(baseElement.querySelector('object')).not.toHaveAttribute('data')
})
it('FE-W4FPM-003: the open-in-tab button uses the unsigned url', () => {
render(<PdfPreviewModal {...state()} />)
fireEvent.click(screen.getByRole('button', { name: /openTab/ }))
expect(openFile).toHaveBeenCalledWith('/uploads/files/ticket.pdf', 'ticket.pdf')
})
it('FE-W4FPM-004: a failed open toasts the error', async () => {
openFile.mockRejectedValue(new Error('blocked'))
render(<PdfPreviewModal {...state()} />)
fireEvent.click(screen.getByRole('button', { name: /openTab/ }))
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('files.openError'))
})
it('FE-W4FPM-005: the download button hands the file to the download helper', () => {
render(<PdfPreviewModal {...state()} />)
fireEvent.click(screen.getByRole('button', { name: 'files.download' }))
expect(downloadFile).toHaveBeenCalledWith('/uploads/files/ticket.pdf', 'ticket.pdf')
})
it('FE-W4FPM-006: the close button and the backdrop clear the preview, the card does not', () => {
const { baseElement } = render(<PdfPreviewModal {...state()} />)
const backdrop = baseElement.querySelector('.trek-modal-backdrop') as HTMLElement
fireEvent.click(screen.getByRole('button', { name: 'Close' }))
expect(setPreviewFile).toHaveBeenCalledWith(null)
setPreviewFile.mockClear()
fireEvent.click(screen.getByRole('dialog'))
expect(setPreviewFile).not.toHaveBeenCalled()
fireEvent.mouseDown(backdrop)
fireEvent.click(backdrop)
expect(setPreviewFile).toHaveBeenCalledWith(null)
})
it('FE-W4FPM-007: the no-plugin fallback also opens the file', () => {
render(<PdfPreviewModal {...state()} />)
fireEvent.click(screen.getByRole('button', { name: 'files.downloadPdf' }))
expect(openFile).toHaveBeenCalledWith('/uploads/files/ticket.pdf', 'ticket.pdf')
})
it('FE-W4FPM-014: the head band carries the name, open, download and close', () => {
render(<PdfPreviewModal {...state()} />)
expect(screen.getByRole('dialog', { name: 'ticket.pdf' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /openTab/ })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'files.download' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument()
})
})
describe('MarkdownPreviewModal', () => {
const mdState = (overrides: Partial<FileManagerState> = {}) => state({
previewFile: { id: 2, original_name: 'notes.md', url: '/uploads/files/notes.md' } as unknown as TripFile,
previewFileUrl: '/uploads/files/notes.md?token=abc',
...overrides,
})
it('FE-W4FPM-008: fetches the markdown with credentials and renders it', async () => {
const fetchMock = vi.fn(async () => ({ ok: true, text: async () => '# Packing\n\nBring a **towel**.' }))
vi.stubGlobal('fetch', fetchMock)
render(<MarkdownPreviewModal {...mdState()} />)
expect(fetchMock).toHaveBeenCalledWith('/uploads/files/notes.md?token=abc', { credentials: 'include' })
expect(await screen.findByRole('heading', { name: 'Packing' })).toBeInTheDocument()
expect(screen.getByText('towel')).toBeInTheDocument()
})
it('FE-W4FPM-009: shows the error copy when the fetch fails', async () => {
vi.stubGlobal('fetch', vi.fn(async () => { throw new Error('offline') }))
render(<MarkdownPreviewModal {...mdState()} />)
expect(await screen.findByText('files.openError')).toBeInTheDocument()
})
it('FE-W4FPM-010: shows the error copy on a non-ok response', async () => {
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: false, text: async () => '' })))
render(<MarkdownPreviewModal {...mdState()} />)
expect(await screen.findByText('files.openError')).toBeInTheDocument()
})
it('FE-W4FPM-011: skips the fetch until a signed url exists', () => {
const fetchMock = vi.fn()
vi.stubGlobal('fetch', fetchMock)
render(<MarkdownPreviewModal {...mdState({ previewFileUrl: null })} />)
expect(fetchMock).not.toHaveBeenCalled()
expect(screen.getByText('notes.md')).toBeInTheDocument()
})
it('FE-W4FPM-012: the header buttons open, download and close', async () => {
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, text: async () => 'hi' })))
render(<MarkdownPreviewModal {...mdState()} />)
fireEvent.click(screen.getByRole('button', { name: /openTab/ }))
expect(openFile).toHaveBeenCalledWith('/uploads/files/notes.md', 'notes.md')
fireEvent.click(screen.getByRole('button', { name: 'files.download' }))
expect(downloadFile).toHaveBeenCalledWith('/uploads/files/notes.md', 'notes.md')
fireEvent.click(screen.getByRole('button', { name: 'Close' }))
expect(setPreviewFile).toHaveBeenCalledWith(null)
await waitFor(() => expect(screen.getByText('hi')).toBeInTheDocument())
})
it('FE-W4FPM-013: a failed open toasts the error', async () => {
vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true, text: async () => '' })))
openFile.mockRejectedValue(new Error('blocked'))
render(<MarkdownPreviewModal {...mdState()} />)
fireEvent.click(screen.getByRole('button', { name: /openTab/ }))
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('files.openError'))
})
})