mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
fix(cover): prevent series badge clicks from opening reader
treat the series-position badge as a non-open interactive target and exclude those clicks from card-open handling. add regression tests for hover hit-testing and badge-click behavior. type vitest mock functions in the series detail spec to satisfy the repository pre-push lint checks. Closes #99
This commit is contained in:
@@ -475,6 +475,14 @@ describe('BookCoverCard', () => {
|
||||
expect(wrapper.text()).toContain('#3')
|
||||
})
|
||||
|
||||
it('marks badge as a card-click blocker and disables hit-testing on hover', () => {
|
||||
mockCardOverlays.value = ['series-position']
|
||||
const wrapper = mountCard({ book: makeBook({ seriesIndex: 3, seriesName: 'Dune' }) })
|
||||
const badge = wrapper.find('[data-card-click-blocker]')
|
||||
expect(badge.exists()).toBe(true)
|
||||
expect(badge.classes()).toContain('group-hover:pointer-events-none')
|
||||
})
|
||||
|
||||
it('shows #1.5 badge when seriesIndex is 1.5', () => {
|
||||
mockCardOverlays.value = ['series-position']
|
||||
const wrapper = mountCard({ book: makeBook({ seriesIndex: 1.5, seriesName: 'Dune' }) })
|
||||
@@ -517,5 +525,15 @@ describe('BookCoverCard', () => {
|
||||
const wrapper = mountCard({ book: makeBook({ seriesIndex: 3, seriesName: null }) })
|
||||
expect(wrapper.text()).toContain('#3')
|
||||
})
|
||||
|
||||
it('does not open the reader when the series badge is clicked', async () => {
|
||||
setTouchMode(false)
|
||||
mockCardOverlays.value = ['series-position']
|
||||
const wrapper = mountCard({ book: makeBook({ seriesIndex: 3, seriesName: 'Dune' }) })
|
||||
const badge = wrapper.find('[data-card-click-blocker]')
|
||||
expect(badge.exists()).toBe(true)
|
||||
await badge.trigger('click')
|
||||
expect(mockRouterPush).not.toHaveBeenCalledWith(expect.objectContaining({ name: 'reader' }))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -192,7 +192,8 @@ function openFile(file: BookFileRef) {
|
||||
}
|
||||
|
||||
function handleCardClick(event: MouseEvent) {
|
||||
if ((event.target as Element).closest('button')) return
|
||||
const target = event.target
|
||||
if (target instanceof Element && target.closest('button, [data-card-click-blocker]')) return
|
||||
if (props.selectionMode) {
|
||||
emit('select', event)
|
||||
return
|
||||
@@ -333,7 +334,8 @@ async function handleSetStatus(status: ReadStatus) {
|
||||
<Tooltip v-if="showSeriesPositionBadge">
|
||||
<TooltipTrigger as-child>
|
||||
<div
|
||||
class="pointer-events-auto flex items-center bg-black/60 rounded-full px-1.5 py-0.5 group-hover:opacity-0 transition-opacity duration-150 cursor-default"
|
||||
data-card-click-blocker
|
||||
class="pointer-events-auto group-hover:pointer-events-none flex items-center bg-black/60 rounded-full px-1.5 py-0.5 group-hover:opacity-0 transition-opacity duration-150 cursor-default"
|
||||
>
|
||||
<span class="text-[9px] font-bold text-white leading-none">{{ seriesPositionLabel }}</span>
|
||||
</div>
|
||||
|
||||
@@ -290,6 +290,14 @@ describe('BookCoverCard — series position overlay', () => {
|
||||
expect(wrapper.text()).toContain('#3')
|
||||
})
|
||||
|
||||
it('marks the series badge as a card click blocker and disables hit-testing on hover', () => {
|
||||
cardOverlays.value = ['series-position']
|
||||
const wrapper = mountCard(bookWithSeries)
|
||||
const badge = wrapper.find('[data-card-click-blocker]')
|
||||
expect(badge.exists()).toBe(true)
|
||||
expect(badge.classes()).toContain('group-hover:pointer-events-none')
|
||||
})
|
||||
|
||||
it('does not render the badge when series-position is not in overlays', () => {
|
||||
cardOverlays.value = []
|
||||
const wrapper = mountCard(bookWithSeries)
|
||||
|
||||
@@ -7,10 +7,10 @@ import SeriesDetailView from './SeriesDetailView.vue'
|
||||
type ViewMode = 'grid' | 'list' | 'table'
|
||||
|
||||
class MockIntersectionObserver {
|
||||
observe = vi.fn()
|
||||
unobserve = vi.fn()
|
||||
disconnect = vi.fn()
|
||||
takeRecords = vi.fn(() => [])
|
||||
observe = vi.fn<(target: Element) => void>()
|
||||
unobserve = vi.fn<(target: Element) => void>()
|
||||
disconnect = vi.fn<() => void>()
|
||||
takeRecords = vi.fn<() => IntersectionObserverEntry[]>(() => [])
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
@@ -30,8 +30,8 @@ const mocks = vi.hoisted(() => ({
|
||||
order: null as unknown as { value: 'asc' | 'desc' },
|
||||
libraryId: null as unknown as { value: number | null },
|
||||
effectiveViewMode: null as unknown as { value: ViewMode },
|
||||
fetchSeriesBooks: vi.fn(),
|
||||
api: vi.fn(),
|
||||
fetchSeriesBooks: vi.fn<(...args: unknown[]) => Promise<unknown>>(),
|
||||
api: vi.fn<(...args: unknown[]) => Promise<unknown>>(),
|
||||
}))
|
||||
|
||||
vi.mock('vue-router', async (importOriginal) => {
|
||||
|
||||
Reference in New Issue
Block a user