- 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]>
87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { monogramFor, monogramInk, monogramMap, speciesName } from './monogram'
|
||
|
||
describe('speciesName', () => {
|
||
it('drops a variety suffix and a parenthetical', () => {
|
||
expect(speciesName('Tomato — Cherokee Purple')).toBe('Tomato')
|
||
expect(speciesName('Melon - Hale’s Best')).toBe('Melon')
|
||
expect(speciesName('Garlic (Music)')).toBe('Garlic')
|
||
expect(speciesName('Kale')).toBe('Kale')
|
||
})
|
||
})
|
||
|
||
describe('monogramMap', () => {
|
||
it('gives a bare initial where it is unique', () => {
|
||
const m = monogramMap([
|
||
{ id: 1, name: 'Garlic' },
|
||
{ id: 2, name: 'Tomato' },
|
||
])
|
||
expect(m.get(1)).toBe('G')
|
||
expect(m.get(2)).toBe('T')
|
||
})
|
||
|
||
it('resolves a shared initial by catalog order, second letter lowercase', () => {
|
||
const m = monogramMap([
|
||
{ id: 6, name: 'Tomato' },
|
||
{ id: 26, name: 'Thyme' },
|
||
{ id: 32, name: 'Marigold' },
|
||
{ id: 29, name: 'Mint' },
|
||
])
|
||
expect(m.get(6)).toBe('T') // first into the catalog keeps the initial
|
||
expect(m.get(26)).toBe('Th')
|
||
expect(m.get(29)).toBe('M')
|
||
expect(m.get(32)).toBe('Ma')
|
||
})
|
||
|
||
it('takes the next free second letter, never repeating the initial', () => {
|
||
const m = monogramMap([
|
||
{ id: 5, name: 'Pea' },
|
||
{ id: 7, name: 'Pepper' },
|
||
{ id: 24, name: 'Parsley' },
|
||
])
|
||
expect(m.get(5)).toBe('P')
|
||
expect(m.get(7)).toBe('Pe')
|
||
expect(m.get(24)).toBe('Pa')
|
||
const n = monogramMap([
|
||
{ id: 1, name: 'Pea' },
|
||
{ id: 2, name: 'Peach' },
|
||
{ id: 3, name: 'Pepper' },
|
||
])
|
||
expect(n.get(2)).toBe('Pe')
|
||
expect(n.get(3)).toBe('Pr') // "Pp" would repeat the initial
|
||
})
|
||
|
||
it('uses the species for varieties and keeps the plain one first', () => {
|
||
const m = monogramMap([
|
||
{ id: 6, name: 'Tomato' },
|
||
{ id: 40, name: 'Tomato — Cherokee Purple' },
|
||
])
|
||
expect(m.get(6)).toBe('T')
|
||
expect(m.get(40)).toBe('To')
|
||
})
|
||
|
||
it('marks an unletterable name with a placeholder', () => {
|
||
expect(monogramMap([{ id: 1, name: '123' }]).get(1)).toBe('?')
|
||
expect(monogramFor('🌱')).toBe('?')
|
||
})
|
||
})
|
||
|
||
describe('monogramInk', () => {
|
||
it('keeps paper lettering on colors dark enough to carry it', () => {
|
||
expect(monogramInk('#c8553d')).toBe('var(--color-paper)') // tomato
|
||
expect(monogramInk('#7a8a5e')).toBe('var(--color-paper)') // sage
|
||
expect(monogramInk('#5f8f45')).toBe('var(--color-paper)')
|
||
})
|
||
|
||
it('switches to the dark marker ink on pale colors', () => {
|
||
expect(monogramInk('#d9d2c5')).toBe('var(--color-marker-ink)') // garlic
|
||
expect(monogramInk('#8bc98b')).toBe('var(--color-marker-ink)') // cabbage
|
||
expect(monogramInk('#fff')).toBe('var(--color-marker-ink)')
|
||
})
|
||
|
||
it('falls back to paper for anything it cannot read', () => {
|
||
expect(monogramInk('tomato')).toBe('var(--color-paper)')
|
||
expect(monogramInk('')).toBe('var(--color-paper)')
|
||
})
|
||
})
|