Files
FabledCurator/frontend/test/usePreviewCommit.spec.js
bvandeusen 7c94d99b9f
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 17s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m16s
refactor(settings): canonical usePreviewCommit for maintenance preview→commit tiles (#753)
Frontend pattern-consistency sweep (note #1026, the last DRY-thread item).
TagMaintenanceCard (4 flows) + PostMaintenanceCard (2 flows) each hand-rolled the
same sync preview→commit state machine: a previewData/previewing/committing
triple + onPreview/onCommit that dry-run-previews, then applies and collapses the
projection (the apply shares the backend predicate, so afterward it's empty).

Extract usePreviewCommit({preview, commit, emptyPreview}) owning that lifecycle.
The 6 flows become declarative: supply the two thunks + the collapse shape. The
normalize flow (commit dispatches a self-resuming background task, not a sync
apply) omits emptyPreview so the projection stays and a truthy result = queued.

Composable returns are aliased to the cards' existing local names, so the
templates only change where they read the apply result (the success badges).
Long-Celery-task cards (GatedPurge/VideoDedup) keep useMaintenanceTask — a
different pattern (navigable-away task lifecycle), deliberately not merged.

Exhaustiveness: no card hand-rolls the refs anymore; the only dryRun:false
callers are these two cards, both via the composable. Added a vitest spec for
the primitive (collapse static + fn, dispatch-variant, re-preview clears result).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 19:39:56 -04:00

67 lines
2.4 KiB
JavaScript

import { describe, it, expect } from 'vitest'
import { usePreviewCommit } from '../src/composables/usePreviewCommit.js'
// Canonical sync preview→commit flow for maintenance tiles (DRY pattern sweep,
// #753) — the primitive TagMaintenanceCard + PostMaintenanceCard's 6 flows route
// through.
describe('usePreviewCommit', () => {
it('runPreview loads the projection and toggles previewing', async () => {
const pc = usePreviewCommit({
preview: async () => ({ count: 3 }),
commit: async () => ({ deleted: 3 }),
})
expect(pc.previewData.value).toBe(null)
const p = pc.runPreview()
expect(pc.previewing.value).toBe(true)
await p
expect(pc.previewing.value).toBe(false)
expect(pc.previewData.value).toEqual({ count: 3 })
})
it('runCommit stores the result and collapses to a static emptyPreview', async () => {
const pc = usePreviewCommit({
preview: async () => ({ count: 3 }),
commit: async () => ({ deleted: 3 }),
emptyPreview: { count: 0, sample_names: [] },
})
await pc.runPreview()
await pc.runCommit()
expect(pc.result.value).toEqual({ deleted: 3 })
expect(pc.previewData.value).toEqual({ count: 0, sample_names: [] })
})
it('emptyPreview as a function derives the collapsed shape from the result', async () => {
const pc = usePreviewCommit({
preview: async () => ({ count: 2 }),
commit: async () => ({ sample_names: ['a', 'b'] }),
emptyPreview: (r) => ({ count: 0, sample_names: r.sample_names }),
})
await pc.runCommit()
expect(pc.previewData.value).toEqual({ count: 0, sample_names: ['a', 'b'] })
})
it('without emptyPreview the projection stays (dispatch-on-commit variant)', async () => {
const pc = usePreviewCommit({
preview: async () => ({ total_changes: 5 }),
commit: async () => ({ status: 'queued' }),
})
await pc.runPreview()
await pc.runCommit()
expect(pc.result.value).toEqual({ status: 'queued' })
expect(pc.previewData.value).toEqual({ total_changes: 5 }) // unchanged
})
it('runPreview clears a prior apply result (re-preview hides the badge)', async () => {
const pc = usePreviewCommit({
preview: async () => ({ count: 1 }),
commit: async () => ({ deleted: 1 }),
emptyPreview: { count: 0 },
})
await pc.runCommit()
expect(pc.result.value).toEqual({ deleted: 1 })
await pc.runPreview()
expect(pc.result.value).toBe(null)
})
})