From 62e8690b30bd8b55dc86bf9c7c755bfb335d74cc Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Wed, 28 Oct 2026 14:27:24 +0000 Subject: [PATCH] fix(stats): make history years accessible when overflowing Keep the all-time filter visible and expose keyboard-accessible controls for navigating long year ranges. Closes #1959 --- .../__tests__/HistoryPeriodPicker.spec.ts | 63 +++++++++++++++ .../components/HistoryPeriodPicker.vue | 79 ++++++++++++++++--- client/src/locales/en.json | 2 + 3 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 client/src/features/reading-history/__tests__/HistoryPeriodPicker.spec.ts diff --git a/client/src/features/reading-history/__tests__/HistoryPeriodPicker.spec.ts b/client/src/features/reading-history/__tests__/HistoryPeriodPicker.spec.ts new file mode 100644 index 000000000..1f6adba8f --- /dev/null +++ b/client/src/features/reading-history/__tests__/HistoryPeriodPicker.spec.ts @@ -0,0 +1,63 @@ +import { nextTick } from 'vue' +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' +import HistoryPeriodPicker from '../components/HistoryPeriodPicker.vue' + +const years = [ + { year: 2026, reads: 12 }, + { year: 2025, reads: 9 }, + { year: 1998, reads: 3 }, +] + +function mountPicker(year: number | null = 2026) { + return mount(HistoryPeriodPicker, { + props: { years, year, quarter: null, currentYear: 2026 }, + }) +} + +describe('HistoryPeriodPicker', () => { + it('keeps All time outside the scrolling years and emits it as a period', async () => { + const wrapper = mountPicker() + const scroller = wrapper.get('[data-year-scroller]') + + expect(scroller.text()).not.toContain('All time') + const allTime = wrapper.findAll('button').find((button) => button.text() === 'All time')! + await allTime.trigger('click') + + expect(wrapper.emitted('year')).toContainEqual([null]) + }) + + it('offers mouse and keyboard controls when older years overflow', async () => { + const wrapper = mountPicker() + const scroller = wrapper.get('[data-year-scroller]').element as HTMLElement + Object.defineProperties(scroller, { + clientWidth: { configurable: true, value: 240 }, + scrollWidth: { configurable: true, value: 600 }, + }) + const scrollBy = vi.fn<(options?: ScrollToOptions) => void>() + Object.defineProperty(scroller, 'scrollBy', { configurable: true, value: scrollBy }) + scroller.dispatchEvent(new Event('scroll')) + await nextTick() + + await wrapper.get('button[aria-label="Show older years"]').trigger('click') + + expect(scrollBy).toHaveBeenCalledWith({ left: 180, behavior: 'auto' }) + }) + + it('reveals a selected year that is outside the visible range', async () => { + const wrapper = mountPicker() + const scroller = wrapper.get('[data-year-scroller]').element as HTMLElement + Object.defineProperties(scroller, { + clientWidth: { configurable: true, value: 240 }, + scrollWidth: { configurable: true, value: 600 }, + }) + scroller.getBoundingClientRect = () => ({ left: 0, right: 240 }) as DOMRect + const oldYear = wrapper.findAll('[data-year-scroller] button').find((button) => button.text().includes('1998'))!.element as HTMLElement + oldYear.getBoundingClientRect = () => ({ left: 480, right: 560 }) as DOMRect + + await wrapper.setProps({ year: 1998 }) + await nextTick() + + expect(scroller.scrollLeft).toBe(480) + }) +}) diff --git a/client/src/features/reading-history/components/HistoryPeriodPicker.vue b/client/src/features/reading-history/components/HistoryPeriodPicker.vue index ae57f6880..6d5905d29 100644 --- a/client/src/features/reading-history/components/HistoryPeriodPicker.vue +++ b/client/src/features/reading-history/components/HistoryPeriodPicker.vue @@ -1,6 +1,8 @@ - + - {{ option.year }} - {{ - formatNumber(option.reads) - }} + + + + + {{ option.year }} + {{ + formatNumber(option.reads) + }} + + + + {{ t('readingHistory.period.allTime') }} diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 462a4ca8e..3dbfcd039 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -13065,6 +13065,8 @@ "period": { "year": "Year", "allTime": "All time", + "newerYears": "Show newer years", + "olderYears": "Show older years", "quarterGroup": "Quarter", "quarter": "Q{quarter}", "quarterYear": "Q{quarter} {year}"