Build image / build-and-push (push) Successful in 9s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
116 lines
4.0 KiB
TypeScript
116 lines
4.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { describeConflict, describeCounts, describeUndo, totalChanges, type ChangeSet } from './history'
|
|
|
|
function changeSet(over: Partial<ChangeSet> = {}): ChangeSet {
|
|
return {
|
|
id: 1,
|
|
gardenId: 1,
|
|
actorId: 1,
|
|
actorName: 'Steve',
|
|
source: 'ui',
|
|
summary: 'Did a thing',
|
|
counts: [],
|
|
createdAt: '2026-07-21T00:00:00Z',
|
|
...over,
|
|
}
|
|
}
|
|
|
|
describe('describeCounts', () => {
|
|
it('pluralizes per entity and names the operation', () => {
|
|
const cs = changeSet({
|
|
counts: [
|
|
{ entityType: 'planting', op: 'create', n: 3 },
|
|
{ entityType: 'object', op: 'update', n: 1 },
|
|
],
|
|
})
|
|
expect(describeCounts(cs)).toBe('3 plantings added, 1 bed changed')
|
|
})
|
|
|
|
it('says nothing rather than "0 changes"', () => {
|
|
expect(describeCounts(changeSet())).toBe('')
|
|
expect(describeCounts(changeSet({ counts: [{ entityType: 'object', op: 'create', n: 0 }] }))).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('describeConflict', () => {
|
|
it('names the entity when it has a name', () => {
|
|
expect(describeConflict({ entityType: 'object', entityId: 4, reason: 'changed', name: 'North Bed' })).toBe(
|
|
'“North Bed” was edited since, so it was left alone',
|
|
)
|
|
})
|
|
|
|
it('falls back to the entity kind when it has no name', () => {
|
|
expect(describeConflict({ entityType: 'planting', entityId: 9, reason: 'missing' })).toBe(
|
|
'that planting no longer exists',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('describeUndo', () => {
|
|
const target = changeSet({ counts: [{ entityType: 'object', op: 'update', n: 3 }] })
|
|
|
|
it('is a plain success when nothing conflicted', () => {
|
|
const out = describeUndo(target, {
|
|
changeSet: changeSet({ id: 2, counts: [{ entityType: 'object', op: 'update', n: 3 }] }),
|
|
conflicts: [],
|
|
})
|
|
expect(out).toEqual({ tone: 'ok', message: 'Undone.' })
|
|
})
|
|
|
|
// The server answers 200 with a null change set when every revision was
|
|
// already a no-op — reachable by undoing a creation whose object is gone.
|
|
// Claiming "Undone." there reports work that didn't happen.
|
|
it("doesn't claim to have undone a no-op", () => {
|
|
const out = describeUndo(target, { changeSet: null, conflicts: [] })
|
|
expect(out.tone).toBe('ok')
|
|
expect(out.message).toBe('Nothing left to undo — this was already reversed.')
|
|
})
|
|
|
|
// The case the issue was specific about: a partial revert really did change
|
|
// things, so reporting a bare failure would be a lie about what applied.
|
|
it('says how much applied and what was skipped', () => {
|
|
const out = describeUndo(target, {
|
|
changeSet: changeSet({ id: 2, counts: [{ entityType: 'object', op: 'update', n: 2 }] }),
|
|
conflicts: [{ entityType: 'object', entityId: 7, reason: 'changed', name: 'North Bed' }],
|
|
})
|
|
expect(out.tone).toBe('partial')
|
|
expect(out.message).toBe('2 of 3 changes undone — “North Bed” was edited since, so it was left alone.')
|
|
})
|
|
|
|
it('is an error when nothing applied at all', () => {
|
|
const out = describeUndo(target, {
|
|
changeSet: null,
|
|
conflicts: [{ entityType: 'object', entityId: 7, reason: 'changed', name: 'North Bed' }],
|
|
})
|
|
expect(out.tone).toBe('error')
|
|
expect(out.message).toBe('Nothing was undone — “North Bed” was edited since, so it was left alone.')
|
|
})
|
|
|
|
it('lists every conflict, not just the first', () => {
|
|
const out = describeUndo(target, {
|
|
changeSet: changeSet({ id: 2, counts: [{ entityType: 'object', op: 'update', n: 1 }] }),
|
|
conflicts: [
|
|
{ entityType: 'object', entityId: 7, reason: 'changed', name: 'North Bed' },
|
|
{ entityType: 'planting', entityId: 8, reason: 'missing' },
|
|
],
|
|
})
|
|
expect(out.message).toContain('“North Bed”')
|
|
expect(out.message).toContain('that planting no longer exists')
|
|
})
|
|
})
|
|
|
|
describe('totalChanges', () => {
|
|
it('sums every tally', () => {
|
|
expect(
|
|
totalChanges(
|
|
changeSet({
|
|
counts: [
|
|
{ entityType: 'planting', op: 'create', n: 12 },
|
|
{ entityType: 'object', op: 'update', n: 1 },
|
|
],
|
|
}),
|
|
),
|
|
).toBe(13)
|
|
})
|
|
})
|