mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(series): count duplicate formats as one volume
Preserve raw book counts for compatibility while reporting logical ownership and reading progress for duplicate series positions. Closes #1871
This commit is contained in:
@@ -3,6 +3,7 @@ import { computed, defineComponent, nextTick, reactive, ref, type PropType } fro
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import type { BookCard, SeriesBooksPage, SeriesDetail } from '@bookorbit/types'
|
||||
import SeriesDetailView from './SeriesDetailView.vue'
|
||||
import SeriesCompletionBar from '../components/SeriesCompletionBar.vue'
|
||||
|
||||
const GROUP_BY_MEDIA_STORAGE_KEY = 'bookorbit:series-detail:group-by-media'
|
||||
|
||||
@@ -319,7 +320,7 @@ describe('SeriesDetailView', () => {
|
||||
expect(wrapper.find('[data-testid="series-media-group-books"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('shows the ownership bar only once a provider has supplied a series total', async () => {
|
||||
it('falls back to book counts when a server does not return volume counts', async () => {
|
||||
mocks.seriesInfo = ref(makeSeriesInfo({ bookCount: 4, expectedBookCount: 7 }))
|
||||
|
||||
const wrapper = mountView()
|
||||
@@ -328,9 +329,37 @@ describe('SeriesDetailView', () => {
|
||||
const bar = wrapper.get('[role="progressbar"]')
|
||||
expect(bar.attributes('aria-valuenow')).toBe('4')
|
||||
expect(bar.attributes('aria-valuemax')).toBe('7')
|
||||
expect(wrapper.text()).toContain('4 volumes')
|
||||
const completion = wrapper.getComponent(SeriesCompletionBar)
|
||||
expect(completion.props('readCount')).toBe(0)
|
||||
expect(completion.props('totalCount')).toBe(4)
|
||||
expect(wrapper.text()).toContain('4 of 7')
|
||||
})
|
||||
|
||||
it('uses logical volume counts for the header, reading progress, and ownership', async () => {
|
||||
mocks.seriesInfo = ref(
|
||||
makeSeriesInfo({
|
||||
bookCount: 4,
|
||||
readCount: 2,
|
||||
ownedVolumeCount: 2,
|
||||
readVolumeCount: 1,
|
||||
expectedBookCount: 7,
|
||||
}),
|
||||
)
|
||||
|
||||
const wrapper = mountView()
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.text()).toContain('2 volumes')
|
||||
const completion = wrapper.getComponent(SeriesCompletionBar)
|
||||
expect(completion.props('readCount')).toBe(1)
|
||||
expect(completion.props('totalCount')).toBe(2)
|
||||
const ownership = wrapper.get('[role="progressbar"]')
|
||||
expect(ownership.attributes('aria-valuenow')).toBe('2')
|
||||
expect(ownership.attributes('aria-valuemax')).toBe('7')
|
||||
expect(wrapper.text()).toContain('2 of 7')
|
||||
})
|
||||
|
||||
it('hides the ownership bar when no provider total is known', async () => {
|
||||
mocks.seriesInfo = ref(makeSeriesInfo({ bookCount: 4, expectedBookCount: null }))
|
||||
|
||||
|
||||
@@ -110,6 +110,8 @@ const canEditMetadata = computed(() => hasPermissionInAnyLibrary('library_edit_m
|
||||
const safeLeadDescription = useSafeHtml(() => leadBook.value?.description)
|
||||
const visibleSeriesAuthors = computed(() => (seriesInfo.value?.authors ?? []).slice(0, 5))
|
||||
const hiddenSeriesAuthorsCount = computed(() => Math.max(0, (seriesInfo.value?.authors.length ?? 0) - visibleSeriesAuthors.value.length))
|
||||
const ownedVolumeCount = computed(() => seriesInfo.value?.ownedVolumeCount ?? seriesInfo.value?.bookCount ?? 0)
|
||||
const readVolumeCount = computed(() => seriesInfo.value?.readVolumeCount ?? seriesInfo.value?.readCount ?? 0)
|
||||
const addToCollectionOpen = ref(false)
|
||||
const addToCollectionBookId = ref<number | null>(null)
|
||||
const leadFallbackStyle = computed(() => {
|
||||
@@ -538,7 +540,7 @@ defineOptions({ name: 'SeriesDetailView' })
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-xl font-bold text-foreground">{{ seriesInfo.name }}</h1>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-3 text-sm text-muted-foreground">
|
||||
<span>{{ t('series.detail.bookCount', { count: seriesInfo.bookCount }) }}</span>
|
||||
<span>{{ t('series.card.volumeCount', { count: ownedVolumeCount }) }}</span>
|
||||
<span v-if="visibleSeriesAuthors.length > 0">
|
||||
{{ t('series.detail.byAuthors', { authors: visibleSeriesAuthors.join(', ') }) }}
|
||||
<span v-if="hiddenSeriesAuthorsCount > 0"> {{ t('series.detail.moreAuthors', { count: hiddenSeriesAuthorsCount }) }}</span>
|
||||
@@ -558,10 +560,10 @@ defineOptions({ name: 'SeriesDetailView' })
|
||||
</div>
|
||||
|
||||
<div class="mt-3 grid max-w-md gap-3 sm:grid-cols-2">
|
||||
<SeriesCompletionBar :read-count="seriesInfo.readCount" :total-count="seriesInfo.bookCount" />
|
||||
<SeriesCompletionBar :read-count="readVolumeCount" :total-count="ownedVolumeCount" />
|
||||
<SeriesOwnershipBar
|
||||
v-if="seriesInfo.expectedBookCount !== null"
|
||||
:owned-count="seriesInfo.bookCount"
|
||||
:owned-count="ownedVolumeCount"
|
||||
:expected-count="seriesInfo.expectedBookCount"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user