- Garden and plant dialogs keep centimeters as the source of truth (LengthField in lib/units.ts): a no-change Save no longer rewrites 900 cm as 899.922 or a 45 cm spacing as 44.958, bumping versions and writing bogus history entries on the way. - The UI stamps every date with the browser's local day (lib/dates.ts). Journal notes already did; plop placement, fill and removal now do too, so a 9 pm placement isn't "planted tomorrow". The fill endpoint gained an optional plantedAt; API and agent callers still default to UTC today. - Removing an object that holds plants asks first and says how many go with it. An empty one still goes straight away (one Undo restores it). - The expanded plant card's action row wraps instead of clipping "Delete". - Monogram lettering switches to a dark ink on pale marker colors (garlic, cabbage, marigold) instead of near-white on near-white. - Copy-as-plan proposes the next free year and warns when the typed name already exists, so two gardens can't both read as "the 2027 plan". - Plan cards show the base name with a "2027 plan" tag, so the year — the point of the name — survives truncation. - A rejected model spec now says which model and why: a wrapped ErrInvalidInput's reason reaches the client as the 400's message, and the Settings field shows it inline instead of toasting "invalid input". Also defuses a clock bomb in TestRemainingReturnsWhenAPlantingIsRemoved, which only passed while the real date was before 2026-08-01. Co-Authored-By: Claude Fable 5 <[email protected]>
50 lines
2.0 KiB
TypeScript
50 lines
2.0 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { nextPlanYear, parsePlanName, planGardensOf, planNameFor, planYearOf } from './plan'
|
||
|
||
describe('plan names', () => {
|
||
it('round-trips the default copy name', () => {
|
||
expect(planNameFor('Home Garden', 2027)).toBe('Home Garden — 2027')
|
||
expect(parsePlanName('Home Garden — 2027')).toEqual({ base: 'Home Garden', year: 2027 })
|
||
})
|
||
|
||
it('accepts a plain hyphen or en dash with spaces around it', () => {
|
||
expect(parsePlanName('Back forty - 2028')).toEqual({ base: 'Back forty', year: 2028 })
|
||
expect(parsePlanName('Back forty – 2028')).toEqual({ base: 'Back forty', year: 2028 })
|
||
})
|
||
|
||
it('does not read a bare year or an unspaced dash as a plan', () => {
|
||
expect(parsePlanName('2027')).toBeNull()
|
||
expect(parsePlanName('Plot-2027')).toBeNull()
|
||
expect(parsePlanName('Home Garden')).toBeNull()
|
||
})
|
||
|
||
it('tags only this year and later as plans', () => {
|
||
const now = new Date(2026, 7, 22)
|
||
expect(planYearOf('Home Garden — 2027', now)).toBe(2027)
|
||
expect(planYearOf('Home Garden — 2026', now)).toBe(2026)
|
||
expect(planYearOf('Home Garden — 2024', now)).toBeNull()
|
||
})
|
||
|
||
it('finds a garden’s plan copies in year order', () => {
|
||
const gardens = [
|
||
{ id: 1, name: 'Home Garden' },
|
||
{ id: 2, name: 'Home Garden — 2028' },
|
||
{ id: 3, name: 'Home Garden — 2027' },
|
||
{ id: 4, name: 'Balcony — 2027' },
|
||
]
|
||
expect(planGardensOf('Home Garden', gardens).map((g) => g.id)).toEqual([3, 2])
|
||
})
|
||
})
|
||
|
||
describe('nextPlanYear', () => {
|
||
it('skips years that already have a plan copy, however the dash was typed', () => {
|
||
const names = ['Back Yard', 'Back Yard — 2027', 'Back Yard - 2028', 'Front Strip — 2029']
|
||
expect(nextPlanYear('Back Yard', names, 2027)).toBe(2029)
|
||
expect(nextPlanYear('Front Strip', names, 2027)).toBe(2027)
|
||
})
|
||
|
||
it('is the starting year when nothing is taken', () => {
|
||
expect(nextPlanYear('Plot', [], 2030)).toBe(2030)
|
||
})
|
||
})
|