feat(finale): per-project presentation/Q&A durations in m:ss + config-save merge fix
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m30s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m30s
- setProjectTiming stores per-project overrides in round config; phase starts resolve: explicit input > project override > round default - Run Order rows get m:ss inputs per project; PhaseControls one-off overrides now also m:ss (shared parseClock: '7:30', '12:05', plain '7') - CRITICAL: round.update now MERGES validated form config over the existing configJson — saving the Config tab was wiping projectOrder (would have destroyed a running ceremony) and the finals-docs upload toggle Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -175,6 +175,58 @@ describe('voting session sync', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('per-project timing overrides', () => {
|
||||
it('setProjectTiming stores per-project durations in round config', async () => {
|
||||
await adminCaller.setProjectTiming({
|
||||
roundId: round.id,
|
||||
projectId: p1.id,
|
||||
presentationSeconds: 480,
|
||||
qaSeconds: 90,
|
||||
})
|
||||
const r = await prisma.round.findUniqueOrThrow({ where: { id: round.id } })
|
||||
const overrides = (r.configJson as any).projectTimingOverrides
|
||||
expect(overrides[p1.id]).toEqual({ presentationSeconds: 480, qaSeconds: 90 })
|
||||
})
|
||||
|
||||
it('startPresentation/startQA use the project override over the config default', async () => {
|
||||
await adminCaller.sendToScreens({ roundId: round.id, projectId: p1.id })
|
||||
const pres = await adminCaller.startPresentation({ roundId: round.id })
|
||||
expect(pres.phaseDurationSeconds).toBe(480) // override, not the 120s config default
|
||||
const qa = await adminCaller.startQA({ roundId: round.id })
|
||||
expect(qa.phaseDurationSeconds).toBe(90)
|
||||
})
|
||||
|
||||
it('an explicit durationSeconds input still wins over the project override', async () => {
|
||||
await adminCaller.sendToScreens({ roundId: round.id, projectId: p1.id })
|
||||
const pres = await adminCaller.startPresentation({ roundId: round.id, durationSeconds: 33 })
|
||||
expect(pres.phaseDurationSeconds).toBe(33)
|
||||
})
|
||||
|
||||
it('projects without an override keep the config default', async () => {
|
||||
await adminCaller.sendToScreens({ roundId: round.id, projectId: p2.id })
|
||||
const pres = await adminCaller.startPresentation({ roundId: round.id })
|
||||
expect(pres.phaseDurationSeconds).toBe(120)
|
||||
})
|
||||
|
||||
it('clearing an override falls back to defaults', async () => {
|
||||
await adminCaller.setProjectTiming({
|
||||
roundId: round.id,
|
||||
projectId: p1.id,
|
||||
presentationSeconds: null,
|
||||
qaSeconds: null,
|
||||
})
|
||||
await adminCaller.sendToScreens({ roundId: round.id, projectId: p1.id })
|
||||
const pres = await adminCaller.startPresentation({ roundId: round.id })
|
||||
expect(pres.phaseDurationSeconds).toBe(120)
|
||||
})
|
||||
|
||||
it('getCursor exposes the overrides for the admin UI', async () => {
|
||||
await adminCaller.setProjectTiming({ roundId: round.id, projectId: p2.id, qaSeconds: 240 })
|
||||
const cursor = await adminCaller.getCursor({ roundId: round.id })
|
||||
expect(cursor?.projectTimingOverrides?.[p2.id]?.qaSeconds).toBe(240)
|
||||
})
|
||||
})
|
||||
|
||||
describe('juror notes', () => {
|
||||
it('saveNote upserts one note per (round, project, juror)', async () => {
|
||||
await jurorCaller.saveNote({ roundId: round.id, projectId: p1.id, content: 'first draft' })
|
||||
|
||||
Reference in New Issue
Block a user