fix(client): show authors on collapsed series rows in table view

The table rendered a dash in every column of a collapsed series row,
including Authors, although the row already carries the first
volume's authors and the grid and list views show them. Render them
as the same read-only author chips book rows use.

Closes #1645
This commit is contained in:
neon
2026-10-12 15:48:10 +00:00
parent 2327847bff
commit 190d837280
4 changed files with 49 additions and 5 deletions
@@ -878,7 +878,12 @@ defineExpose({
@click="focusTableCell(vItem.index, colIdx, $event, rowBook(vItem.index).id, col.id)"
>
<template v-if="rowBook(vItem.index).collapsedSeries">
<BookTableCollapsedSeriesCell :book="rowBook(vItem.index)" :col-id="col.id" @open-series="handleOpenSeries(rowBook(vItem.index))" />
<BookTableCollapsedSeriesCell
:book="rowBook(vItem.index)"
:col-id="col.id"
:link-fn="getChipsLinkFn(col.id)"
@open-series="handleOpenSeries(rowBook(vItem.index))"
/>
</template>
<template v-else>
<BookTableCellDispatcher
@@ -68,6 +68,7 @@ function makeBook(id: number, overrides: Partial<BookCard> = {}): BookCard {
const collapsedSeriesBook = makeBook(1, {
seriesId: 7,
seriesName: 'Discworld',
authors: ['Terry Pratchett'],
collapsedSeries: { bookCount: 3, readCount: 0, coverBookIds: [1], seriesLatestAddedAt: null },
})
const plainBook = makeBook(2)
@@ -139,4 +140,13 @@ describe('VirtualBookTable collapsed series rows', () => {
expect(wrapper.emitted('select')?.[0]?.[0]).toBe(plainBook.id)
})
it('gives a collapsed series row the author link used by book rows', () => {
const wrapper = mountTable()
const authorsCell = wrapper.findAllComponents({ name: 'BookTableCollapsedSeriesCell' }).find((cell) => cell.props('colId') === 'authors')
const linkFn = authorsCell?.props('linkFn') as ((chip: string) => string | null) | undefined
expect(authorsCell?.props('book')).toMatchObject({ authors: ['Terry Pratchett'] })
expect(linkFn?.('Terry Pratchett')).toBe('/authors?q=Terry%20Pratchett')
})
})
@@ -4,12 +4,14 @@ import { useI18n } from 'vue-i18n'
import { ChevronRight, LibraryBig } from '@lucide/vue'
import { FORMAT_TO_GROUP, type BookCard } from '@bookorbit/types'
import BookCoverSurface from '../BookCoverSurface.vue'
import BookTableChipsCell from './BookTableChipsCell.vue'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useCoverVersions } from '@/features/book/composables/useCoverVersions'
const props = defineProps<{
book: BookCard
colId: string
linkFn?: (chip: string) => string | null
}>()
const emit = defineEmits<{
@@ -95,6 +97,8 @@ function handleOpenSeries() {
>
</div>
<BookTableChipsCell v-else-if="colId === 'authors'" :value="book.authors" :is-active="false" is-read-only :link-fn="linkFn" />
<div v-else-if="colId === 'actions'" class="flex h-full w-6 items-center justify-center">
<Tooltip>
<TooltipTrigger as-child>
@@ -1,4 +1,4 @@
import { mount } from '@vue/test-utils'
import { mount, RouterLinkStub } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import type { BookCard } from '@bookorbit/types'
import BookTableCollapsedSeriesCell from '../BookTableCollapsedSeriesCell.vue'
@@ -12,7 +12,7 @@ vi.mock('@/features/book/composables/useCoverVersions', () => ({
}),
}))
function makeBook(format: string | null): BookCard {
function makeBook(format: string | null, overrides: Partial<BookCard> = {}): BookCard {
return {
id: 1,
status: 'present',
@@ -50,6 +50,7 @@ function makeBook(format: string | null): BookCard {
coverUpdatedAtByBookId: { 10: '2024-01-01T00:00:00.000Z', 11: '2024-02-01T00:00:00.000Z' },
seriesLatestAddedAt: null,
},
...overrides,
} as BookCard
}
@@ -59,11 +60,16 @@ const CoverSurfaceStub = {
template: '<div data-testid="surface" :data-comic="isComic"><slot /></div>',
}
function mountCell(format: string | null, colId = 'cover') {
function mountCell(
format: string | null,
colId = 'cover',
options: { overrides?: Partial<BookCard>; linkFn?: (chip: string) => string | null } = {},
) {
return mount(BookTableCollapsedSeriesCell, {
props: { book: makeBook(format), colId },
props: { book: makeBook(format, options.overrides), colId, linkFn: options.linkFn },
global: {
stubs: {
RouterLink: RouterLinkStub,
BookCoverSurface: CoverSurfaceStub,
Tooltip: { template: '<div><slot /></div>' },
TooltipTrigger: { template: '<div><slot /></div>' },
@@ -115,4 +121,23 @@ describe('BookTableCollapsedSeriesCell comic flag', () => {
await button.trigger('click')
expect(wrapper.emitted('open-series')).toEqual([[]])
})
it('links the series authors in the authors column', () => {
const wrapper = mountCell('epub', 'authors', {
overrides: { authors: ['Terry Pratchett', 'Neil Gaiman', 'Stephen Baxter'] },
linkFn: (name) => `/authors?q=${encodeURIComponent(name)}`,
})
const links = wrapper.findAllComponents(RouterLinkStub)
expect(links.map((link) => link.text())).toEqual(['Terry Pratchett', 'Neil Gaiman'])
expect(links.map((link) => link.props('to'))).toEqual(['/authors?q=Terry%20Pratchett', '/authors?q=Neil%20Gaiman'])
expect(wrapper.text()).toContain('+1')
})
it('shows a dash in the authors column when the series has no authors', () => {
const wrapper = mountCell('epub', 'authors', { linkFn: (name) => `/authors?q=${name}` })
expect(wrapper.findAllComponents(RouterLinkStub)).toHaveLength(0)
expect(wrapper.text()).toBe('-')
})
})