test(timetable): StageRow lane stacking + Wachtrij rendering & drag (Step 7)
StageRow.test.ts (5): - renders one PerformanceBlock per performance - lane_resolved drives vertical stacking (different lanes → different topPx) - empty stage row renders zero blocks - horizontal position = (start_at - gridStart) × pxPerMin (60 min × 2 = 120px) - block-pointerdown event bubbles up as blockPointerdown Wachtrij.test.ts (5): - one card per parked performance - empty wachtrij shows "Geen optredens" copy - card pointerdown emits cardPointerdown with the parked performance - card click emits cardSelect with the performance + DOMRect - count badge reflects performances.length Test count: 350 → 360. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
131
apps/app/tests/component/StageRow.test.ts
Normal file
131
apps/app/tests/component/StageRow.test.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mountWithVuexy } from '../utils/mountWithVuexy'
|
||||
import StageRow from '@/components/timetable/StageRow.vue'
|
||||
import { ArtistEngagementStatus, type Performance, type Stage } from '@/types/timetable'
|
||||
|
||||
const stage: Stage = {
|
||||
id: 's1',
|
||||
event_id: 'ev1',
|
||||
name: 'Hardstyle District',
|
||||
color: '#e85d75',
|
||||
capacity: 1000,
|
||||
sort_order: 0,
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
}
|
||||
|
||||
function perf(id: string, lane_resolved: number, start: string, end: string): Performance {
|
||||
return {
|
||||
id,
|
||||
engagement_id: 'e1',
|
||||
event_id: 'ev1',
|
||||
stage_id: 's1',
|
||||
lane: lane_resolved,
|
||||
lane_resolved,
|
||||
start_at: start,
|
||||
end_at: end,
|
||||
version: 1,
|
||||
notes: null,
|
||||
warnings: [],
|
||||
engagement: {
|
||||
booking_status: { value: ArtistEngagementStatus.CONFIRMED, label: 'Bevestigd' },
|
||||
} as Performance['engagement'],
|
||||
stage,
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
deleted_at: null,
|
||||
}
|
||||
}
|
||||
|
||||
const baseProps = {
|
||||
stage,
|
||||
gridStartIso: '2026-07-10T18:00:00.000Z',
|
||||
totalMinutes: 13 * 60,
|
||||
pxPerMin: 2,
|
||||
b2bLeftSet: new Set<string>(),
|
||||
b2bRightSet: new Set<string>(),
|
||||
pulseSet: new Set<string>(),
|
||||
selectedId: null,
|
||||
dragOriginId: null,
|
||||
}
|
||||
|
||||
describe('StageRow — lane stacking + rendering', () => {
|
||||
it('renders one PerformanceBlock per performance', () => {
|
||||
const { wrapper } = mountWithVuexy(StageRow, {
|
||||
props: {
|
||||
...baseProps,
|
||||
performances: [
|
||||
perf('a', 0, '2026-07-10T18:00:00Z', '2026-07-10T19:00:00Z'),
|
||||
perf('b', 0, '2026-07-10T19:00:00Z', '2026-07-10T20:00:00Z'),
|
||||
],
|
||||
laneCount: 1,
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('[data-perf-id]')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('stacks performances by lane_resolved (different lanes = different topPx)', () => {
|
||||
const { wrapper } = mountWithVuexy(StageRow, {
|
||||
props: {
|
||||
...baseProps,
|
||||
performances: [
|
||||
perf('a', 0, '2026-07-10T18:00:00Z', '2026-07-10T19:30:00Z'),
|
||||
perf('b', 1, '2026-07-10T19:00:00Z', '2026-07-10T20:00:00Z'),
|
||||
],
|
||||
laneCount: 2,
|
||||
},
|
||||
})
|
||||
|
||||
const blockA = wrapper.find('[data-perf-id="a"]')
|
||||
const blockB = wrapper.find('[data-perf-id="b"]')
|
||||
|
||||
const topA = Number(/inset-block-start: (\d+)px/.exec(blockA.attributes('style') ?? '')?.[1] ?? -1)
|
||||
const topB = Number(/inset-block-start: (\d+)px/.exec(blockB.attributes('style') ?? '')?.[1] ?? -1)
|
||||
|
||||
expect(topA).toBeGreaterThanOrEqual(0)
|
||||
expect(topB).toBeGreaterThan(topA)
|
||||
})
|
||||
|
||||
it('renders zero blocks for an empty stage row', () => {
|
||||
const { wrapper } = mountWithVuexy(StageRow, {
|
||||
props: {
|
||||
...baseProps,
|
||||
performances: [],
|
||||
laneCount: 1,
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('[data-perf-id]')).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('positions blocks horizontally based on minutes-since-gridStart × pxPerMin', () => {
|
||||
const { wrapper } = mountWithVuexy(StageRow, {
|
||||
props: {
|
||||
...baseProps,
|
||||
|
||||
// Anchor 18:00; perf starts 19:00 (60 min later) at 2px/min → leftPx=120
|
||||
performances: [perf('a', 0, '2026-07-10T19:00:00Z', '2026-07-10T20:00:00Z')],
|
||||
laneCount: 1,
|
||||
},
|
||||
})
|
||||
|
||||
const block = wrapper.find('[data-perf-id="a"]')
|
||||
const left = Number(/inset-inline-start: (\d+)px/.exec(block.attributes('style') ?? '')?.[1] ?? -1)
|
||||
|
||||
expect(left).toBe(120)
|
||||
})
|
||||
|
||||
it('forwards block-pointerdown events from a child PerformanceBlock', async () => {
|
||||
const { wrapper } = mountWithVuexy(StageRow, {
|
||||
props: {
|
||||
...baseProps,
|
||||
performances: [perf('a', 0, '2026-07-10T18:00:00Z', '2026-07-10T19:00:00Z')],
|
||||
laneCount: 1,
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.find('[data-perf-id="a"]').trigger('pointerdown')
|
||||
expect(wrapper.emitted('blockPointerdown')).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
89
apps/app/tests/component/Wachtrij.test.ts
Normal file
89
apps/app/tests/component/Wachtrij.test.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mountWithVuexy } from '../utils/mountWithVuexy'
|
||||
import Wachtrij from '@/components/timetable/Wachtrij.vue'
|
||||
import { ArtistEngagementStatus, type Performance } from '@/types/timetable'
|
||||
|
||||
function parked(id: string, name: string, status = ArtistEngagementStatus.REQUESTED): Performance {
|
||||
return {
|
||||
id,
|
||||
engagement_id: `e_${id}`,
|
||||
event_id: 'ev1',
|
||||
stage_id: null,
|
||||
lane: 0,
|
||||
lane_resolved: 0,
|
||||
start_at: null,
|
||||
end_at: null,
|
||||
version: 1,
|
||||
notes: null,
|
||||
warnings: [],
|
||||
engagement: {
|
||||
booking_status: { value: status, label: status },
|
||||
artist: { name } as Performance['engagement']['artist'],
|
||||
} as Performance['engagement'],
|
||||
stage: null,
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
deleted_at: null,
|
||||
}
|
||||
}
|
||||
|
||||
describe('Wachtrij — parked-card list', () => {
|
||||
it('renders one card per parked performance', () => {
|
||||
const { wrapper } = mountWithVuexy(Wachtrij, {
|
||||
props: {
|
||||
performances: [parked('p1', 'Devin Wild'), parked('p2', 'Da Tweekaz')],
|
||||
selectedId: null,
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('[data-perf-id]')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('renders the empty-state copy when no performances are parked', () => {
|
||||
const { wrapper } = mountWithVuexy(Wachtrij, {
|
||||
props: { performances: [], selectedId: null },
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toMatch(/Geen optredens/i)
|
||||
})
|
||||
|
||||
it('forwards a card pointerdown as cardPointerdown', async () => {
|
||||
const { wrapper } = mountWithVuexy(Wachtrij, {
|
||||
props: {
|
||||
performances: [parked('p1', 'Devin Wild')],
|
||||
selectedId: null,
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.find('[data-perf-id="p1"]').trigger('pointerdown')
|
||||
|
||||
expect(wrapper.emitted('cardPointerdown')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('forwards a card click as cardSelect with performance + DOMRect', async () => {
|
||||
const { wrapper } = mountWithVuexy(Wachtrij, {
|
||||
props: {
|
||||
performances: [parked('p1', 'Devin Wild')],
|
||||
selectedId: null,
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.find('[data-perf-id="p1"]').trigger('click')
|
||||
|
||||
const events = wrapper.emitted('cardSelect')
|
||||
|
||||
expect(events).toHaveLength(1)
|
||||
expect(events![0][0]).toMatchObject({ id: 'p1' })
|
||||
})
|
||||
|
||||
it('shows the wachtrij item count badge', () => {
|
||||
const { wrapper } = mountWithVuexy(Wachtrij, {
|
||||
props: {
|
||||
performances: [parked('a', 'A'), parked('b', 'B'), parked('c', 'C')],
|
||||
selectedId: null,
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.tt-wachtrij__count').text()).toBe('3')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user