fix(timetable): mechanical-layer stabilization — seeder Model A, Zod decimal drift, freeze-panes layout, ?day URL flicker #19
73
apps/app/tests/component/StageHeaderCell.test.ts
Normal file
73
apps/app/tests/component/StageHeaderCell.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mountWithVuexy } from '../utils/mountWithVuexy'
|
||||
import StageHeaderCell from '@/components/timetable/StageHeaderCell.vue'
|
||||
import type { Stage } from '@/types/timetable'
|
||||
|
||||
/**
|
||||
* jsdom-runnable assertions for the B2 + B3 prop seam:
|
||||
*
|
||||
* - the explicit row-height-px prop applies as inline blockSize
|
||||
* - the prop's absence falls back to the legacy CSS path (no inline style)
|
||||
* - the conflict count badge appears only when > 0
|
||||
*
|
||||
* Visual proof of the freeze panes — sticky-left alignment, pixel-perfect
|
||||
* stacking against StageRow — is jsdom-blind and lives on TEST-VISUAL-001.
|
||||
*/
|
||||
const stage: Stage = {
|
||||
id: 's1',
|
||||
event_id: 'ev1',
|
||||
name: 'Hardstyle District',
|
||||
color: '#e85d75',
|
||||
capacity: 1000,
|
||||
sort_order: 0,
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
}
|
||||
|
||||
describe('StageHeaderCell — explicit row-height-px prop (B3 freeze-pane alignment)', () => {
|
||||
it('applies blockSize inline when rowHeightPx prop is provided', () => {
|
||||
const { wrapper } = mountWithVuexy(StageHeaderCell, {
|
||||
props: { stage, rowHeightPx: 148 },
|
||||
})
|
||||
|
||||
const root = wrapper.find('[data-stage-id="s1"]')
|
||||
|
||||
expect(root.attributes('style')).toMatch(/block-size:\s*148px/)
|
||||
})
|
||||
|
||||
it('does not set blockSize inline when rowHeightPx is omitted (legacy CSS path)', () => {
|
||||
const { wrapper } = mountWithVuexy(StageHeaderCell, {
|
||||
props: { stage },
|
||||
})
|
||||
|
||||
const root = wrapper.find('[data-stage-id="s1"]')
|
||||
const style = root.attributes('style') ?? ''
|
||||
|
||||
expect(style).not.toMatch(/block-size/)
|
||||
})
|
||||
|
||||
it('passes through laneCount=10 row height (484px) without truncation', () => {
|
||||
const { wrapper } = mountWithVuexy(StageHeaderCell, {
|
||||
props: { stage, rowHeightPx: 484 },
|
||||
})
|
||||
|
||||
const root = wrapper.find('[data-stage-id="s1"]')
|
||||
|
||||
expect(root.attributes('style')).toMatch(/block-size:\s*484px/)
|
||||
})
|
||||
|
||||
it('shows the conflict badge only when conflictCount > 0', () => {
|
||||
const { wrapper: withZero } = mountWithVuexy(StageHeaderCell, {
|
||||
props: { stage, conflictCount: 0 },
|
||||
})
|
||||
|
||||
expect(withZero.find('.tt-stage-header__conflict').exists()).toBe(false)
|
||||
|
||||
const { wrapper: withTwo } = mountWithVuexy(StageHeaderCell, {
|
||||
props: { stage, conflictCount: 2 },
|
||||
})
|
||||
|
||||
expect(withTwo.find('.tt-stage-header__conflict').exists()).toBe(true)
|
||||
expect(withTwo.find('.tt-stage-header__conflict').text()).toContain('2')
|
||||
})
|
||||
})
|
||||
33
apps/app/tests/unit/lib/timetable/row-height.test.ts
Normal file
33
apps/app/tests/unit/lib/timetable/row-height.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { computeStageRowHeight } from '@/lib/timetable/row-height'
|
||||
|
||||
/**
|
||||
* One-line helper, four assertion cases — keep this surface tight; this
|
||||
* is the canonical row-height math for both StageRow content and the
|
||||
* sticky-left StageHeaderCell. If either side drifts, the freeze panes
|
||||
* misalign in the browser. The math:
|
||||
*
|
||||
* max(1, laneCount) × (laneHeight + lanePad) + lanePad
|
||||
*
|
||||
* with the canonical constants laneHeight=44, lanePad=4 → 48 per lane + 4.
|
||||
*/
|
||||
const LANE_HEIGHT = 44
|
||||
const LANE_PAD = 4
|
||||
|
||||
describe('computeStageRowHeight', () => {
|
||||
it('falls back to single-lane height when laneCount is 0', () => {
|
||||
expect(computeStageRowHeight(0, LANE_HEIGHT, LANE_PAD)).toBe(52)
|
||||
})
|
||||
|
||||
it('returns 52px for laneCount=1', () => {
|
||||
expect(computeStageRowHeight(1, LANE_HEIGHT, LANE_PAD)).toBe(52)
|
||||
})
|
||||
|
||||
it('returns 148px for laneCount=3', () => {
|
||||
expect(computeStageRowHeight(3, LANE_HEIGHT, LANE_PAD)).toBe(148)
|
||||
})
|
||||
|
||||
it('returns 484px for laneCount=10 (10 × 48 + 4)', () => {
|
||||
expect(computeStageRowHeight(10, LANE_HEIGHT, LANE_PAD)).toBe(484)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user