Files
bookorbit/client/src/views/__tests__/BookDockView.spec.ts
T
neon 070201e163 fix(client): report failed Book Dock uploads
The Book Dock discarded every upload failure, so a rejected file looked like nothing happened.
List failed files with a translated reason, offer retry where it can help, and stop a failed
token refresh, abort or timeout from stalling the upload queue. Dropped files of unsupported
types are reported instead of ignored.

Refs #1739
2026-10-13 00:24:07 +00:00

92 lines
3.5 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import { ref } from 'vue'
import BookDockView from '../BookDockView.vue'
const apiMock = vi.fn<(url: string) => Promise<Response>>(
async (_url: string) =>
({
ok: true,
json: async () => ({ items: [], total: 7000, page: 1, size: 20 }),
}) as Response,
)
vi.mock('@/lib/api', () => ({ api: (url: string) => apiMock(url) }))
vi.mock('@/features/auth/composables/usePermissions', () => ({
usePermissions: () => ({ isDemoRestrictedAccount: ref(false), hasPermission: () => false }),
}))
vi.mock('@/features/book-dock/composables/useBookDockSummary', () => ({
useBookDockSummary: () => ({
summary: ref({ total: 7000, paused: false }),
fetchSummary: vi.fn<() => void>(),
subscribe: vi.fn<() => void>(),
onBookDockChange: () => vi.fn<() => void>(),
socketConnected: ref(true),
}),
}))
vi.mock('@/features/book-dock/composables/useBookDockStatistics', () => ({
useBookDockStatistics: () => ({ statistics: ref(null), fetchStatistics: vi.fn<() => void>() }),
}))
const { addFilesMock } = vi.hoisted(() => ({ addFilesMock: vi.fn<(files: FileList | File[]) => void>() }))
vi.mock('@/features/book-dock/composables/useBookDockUpload', () => ({
useBookDockUpload: () => ({ addFiles: addFilesMock, isUploading: ref(false) }),
SUPPORTED_FORMATS: ['epub'],
SUPPORTED_FORMATS_ACCEPT: '.epub',
}))
vi.mock('@/features/book-dock/composables/useBookDockConflicts', () => ({
useBookDockConflicts: () => ({ conflicts: ref([]), scheduleConflicts: vi.fn<() => void>(), cancelConflicts: vi.fn<() => void>() }),
}))
describe('BookDockView pagination', () => {
it('keeps a 7000-file dock navigable without rendering hundreds of buttons', async () => {
const wrapper = shallowMount(BookDockView, { global: { stubs: { PaginationNav: false } } })
await vi.waitFor(() => expect(wrapper.findAll('nav button')).toHaveLength(6))
expect(
wrapper
.findAll('nav button')
.map((button) => button.text())
.filter(Boolean),
).toEqual(['1', '2', '349', '350'])
expect(wrapper.get('[aria-current="page"]').text()).toBe('1')
expect(String(apiMock.mock.calls[0]?.[0])).toContain('limit=20')
await wrapper.get('[aria-label="Go to page 350"]').trigger('click')
await vi.waitFor(() => expect(String(apiMock.mock.calls.at(-1)?.[0])).toContain('page=350'))
expect(wrapper.get('[aria-current="page"]').text()).toBe('350')
wrapper.unmount()
})
})
describe('BookDockView uploads', () => {
it('shows the upload failures panel on the page', () => {
const wrapper = shallowMount(BookDockView)
expect(wrapper.findComponent({ name: 'BookDockUploadFailures' }).exists()).toBe(true)
wrapper.unmount()
})
it('hands every dropped file to the uploader, including ones it will refuse, so the refusal is reported', async () => {
addFilesMock.mockClear()
const wrapper = shallowMount(BookDockView)
const files = [new File(['x'], 'dune.epub'), new File(['x'], 'notes.docx')]
await wrapper.get('main').trigger('drop', { dataTransfer: { files } })
expect(addFilesMock).toHaveBeenCalledTimes(1)
expect([...addFilesMock.mock.calls[0]![0]].map((file) => file.name)).toEqual(['dune.epub', 'notes.docx'])
wrapper.unmount()
})
it('ignores a drop that carries no files', async () => {
addFilesMock.mockClear()
const wrapper = shallowMount(BookDockView)
await wrapper.get('main').trigger('drop', { dataTransfer: { files: [] } })
expect(addFilesMock).not.toHaveBeenCalled()
wrapper.unmount()
})
})