Ein Tipp auf das Pill öffnet eine Leiste mit allen Jahren, die Ereignisse enthalten, samt Dichte (Punkte bis drei, sonst Zahl); ab zwölf Jahren kommt eine Jahrzehnte-Zeile zum Scrubben dazu. Hat das gewählte Jahr Ereignisse in mehreren Monaten, erscheint eine Monatszeile, in der nur belegte Monate aktiv sind. Ein Jahr springt sofort, ein Monat zentriert dessen Ereignisse; Tippen außerhalb oder Escape schließt. Ersetzt das q-menu im Pill, das durch doppeltes Umschalten nie sichtbar wurde. Die Timeline liefert dafür einen Jahr/Monat-Index und kann auf einen Monat zentrieren. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
150 lines
5.9 KiB
JavaScript
150 lines
5.9 KiB
JavaScript
import { describe, expect, it } from 'vitest'
|
||
import { mount } from '@vue/test-utils'
|
||
import TimelineNav from 'components/TimelineNav.vue'
|
||
|
||
function yearEntry(year, months) {
|
||
return {
|
||
year,
|
||
count: months.reduce((sum, [, count]) => sum + count, 0),
|
||
months: months.map(([month, count]) => ({ month, count }))
|
||
}
|
||
}
|
||
|
||
const YEARS = [
|
||
yearEntry(2025, [[3, 1]]),
|
||
yearEntry(2026, [[0, 2], [5, 1], [11, 4]]),
|
||
yearEntry(2027, [[7, 1]])
|
||
]
|
||
|
||
function mountNav(props = {}) {
|
||
return mount(TimelineNav, {
|
||
props: {
|
||
year: 2026,
|
||
month: 'Januar',
|
||
monthIndex: 0,
|
||
yearIndex: YEARS,
|
||
canPrev: true,
|
||
canNext: true,
|
||
...props
|
||
}
|
||
})
|
||
}
|
||
|
||
describe('TimelineNav — the position pill', () => {
|
||
it('shows month and year of the centred event', () => {
|
||
const wrapper = mountNav()
|
||
|
||
expect(wrapper.find('.timeline-nav__month').text()).toBe('Januar')
|
||
expect(wrapper.find('.timeline-nav__year').text()).toBe('2026')
|
||
})
|
||
|
||
it('emits prev and next from the arrows', async () => {
|
||
const wrapper = mountNav()
|
||
const arrows = wrapper.findAll('.timeline-nav__arrow')
|
||
|
||
await arrows[0].trigger('click')
|
||
await arrows[1].trigger('click')
|
||
|
||
expect(wrapper.emitted('prev')).toHaveLength(1)
|
||
expect(wrapper.emitted('next')).toHaveLength(1)
|
||
})
|
||
|
||
it('disables the arrows at either end of the data', () => {
|
||
const wrapper = mountNav({ canPrev: false, canNext: false })
|
||
const arrows = wrapper.findAll('.timeline-nav__arrow')
|
||
|
||
expect(arrows[0].attributes('disabled')).toBeDefined()
|
||
expect(arrows[1].attributes('disabled')).toBeDefined()
|
||
})
|
||
|
||
it('shows a dash and no month while there is no event', () => {
|
||
const wrapper = mountNav({ year: null, month: '', monthIndex: null, yearIndex: [], canPrev: false, canNext: false })
|
||
|
||
expect(wrapper.find('.timeline-nav__year').text()).toBe('–')
|
||
expect(wrapper.find('.timeline-nav__month').exists()).toBe(false)
|
||
expect(wrapper.find('.timeline-nav__label').attributes('disabled')).toBeDefined()
|
||
})
|
||
})
|
||
|
||
describe('TimelineNav — the quick-jump strip', () => {
|
||
it('opens on tap with every year, its density and the months of the year in view', async () => {
|
||
const wrapper = mountNav()
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(false)
|
||
|
||
await wrapper.find('.timeline-nav__label').trigger('click')
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(true)
|
||
|
||
const chips = wrapper.findAll('.timeline-nav__chip')
|
||
expect(chips.map(chip => chip.find('.timeline-nav__chip-year').text())).toEqual(['2025', '2026', '2027'])
|
||
// Up to three events are dots, more is a number.
|
||
expect(chips[0].findAll('.timeline-nav__chip-dot')).toHaveLength(1)
|
||
expect(chips[1].find('.timeline-nav__chip-count').text()).toBe('7')
|
||
expect(chips[1].classes()).toContain('timeline-nav__chip--current')
|
||
|
||
// 2026 spreads over three months, so the month row is there right away.
|
||
const cells = wrapper.findAll('.timeline-nav__month-cell')
|
||
expect(cells).toHaveLength(12)
|
||
expect(cells.filter(cell => cell.attributes('disabled') === undefined).map(cell => cell.text()))
|
||
.toEqual(['Jan', 'Jun', 'Dez'])
|
||
expect(cells[0].classes()).toContain('timeline-nav__month-cell--current')
|
||
// Three years: no decade row.
|
||
expect(wrapper.find('.timeline-nav__decades').exists()).toBe(false)
|
||
})
|
||
|
||
it('jumps to a single-month year and closes', async () => {
|
||
const wrapper = mountNav()
|
||
await wrapper.find('.timeline-nav__label').trigger('click')
|
||
|
||
await wrapper.findAll('.timeline-nav__chip')[2].trigger('click')
|
||
|
||
expect(wrapper.emitted('jump')).toEqual([[2027]])
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(false)
|
||
})
|
||
|
||
it('stays open for a year with several months and jumps to the chosen month', async () => {
|
||
const wrapper = mountNav({ year: 2025, month: 'April', monthIndex: 3 })
|
||
await wrapper.find('.timeline-nav__label').trigger('click')
|
||
// The year in view has one month only: no month row yet.
|
||
expect(wrapper.find('.timeline-nav__months').exists()).toBe(false)
|
||
|
||
await wrapper.findAll('.timeline-nav__chip')[1].trigger('click')
|
||
expect(wrapper.emitted('jump')).toEqual([[2026]])
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(true)
|
||
expect(wrapper.findAll('.timeline-nav__chip')[1].classes()).toContain('timeline-nav__chip--expanded')
|
||
|
||
const cells = wrapper.findAll('.timeline-nav__month-cell')
|
||
expect(cells[3].attributes('disabled')).toBeDefined()
|
||
await cells[11].trigger('click')
|
||
|
||
expect(wrapper.emitted('jump-month')).toEqual([[{ year: 2026, month: 11 }]])
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(false)
|
||
})
|
||
|
||
it('adds a decade row once there are many years', async () => {
|
||
const years = Array.from({ length: 30 }, (_, i) => yearEntry(1990 + i, [[i % 12, 1]]))
|
||
const wrapper = mountNav({ year: 2005, month: 'Mai', monthIndex: 4, yearIndex: years })
|
||
await wrapper.find('.timeline-nav__label').trigger('click')
|
||
|
||
const decades = wrapper.findAll('.timeline-nav__decade')
|
||
expect(decades.map(decade => decade.text())).toEqual(['1990er', '2000er', '2010er'])
|
||
expect(decades[1].classes()).toContain('timeline-nav__decade--current')
|
||
})
|
||
|
||
it('closes on Escape and when tapping elsewhere', async () => {
|
||
const wrapper = mountNav({ attachTo: document.body })
|
||
await wrapper.find('.timeline-nav__label').trigger('click')
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(true)
|
||
|
||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
||
await wrapper.vm.$nextTick()
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(false)
|
||
|
||
await wrapper.find('.timeline-nav__label').trigger('click')
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(true)
|
||
document.body.dispatchEvent(new Event('pointerdown', { bubbles: true }))
|
||
await wrapper.vm.$nextTick()
|
||
expect(wrapper.find('.timeline-nav__panel').exists()).toBe(false)
|
||
|
||
wrapper.unmount()
|
||
})
|
||
})
|