Files
pansy/web/src/lib/monogram.test.ts
T
steveandClaude Fable 5 27f658c1f7
Build image / build-and-push (push) Successful in 2m55s
Gadfly review (reusable) / review (pull_request) Successful in 8m59s
Adversarial Review (Gadfly) / review (pull_request) Successful in 8m59s
Smoke-sweep fixes: exact saves, local dates, safer remove, readable markers
- 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]>
2026-08-22 22:11:12 -04:00

87 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 - Hales 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)')
})
})