134 lines
4.4 KiB
JavaScript
134 lines
4.4 KiB
JavaScript
import { test } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { parseCellFields, looksLikeAncestorCell } from './ocrParse.js'
|
||
|
||
// --- The three cases required by plan Step 3.5 ---
|
||
|
||
test('case 1: all five fields present and labelled', () => {
|
||
const cell = [
|
||
"Belowland's Thumper",
|
||
'Tattoo: AB12',
|
||
'DOB: 03/15/2024',
|
||
'Weight: 8 lbs 4 oz',
|
||
].join('\n')
|
||
|
||
assert.deepEqual(parseCellFields(cell), {
|
||
farmName: "Belowland's",
|
||
rabbitName: 'Thumper',
|
||
earNumber: 'AB12',
|
||
dob: '2024-03-15',
|
||
weight: 8.25,
|
||
})
|
||
})
|
||
|
||
test('case 2: only name and ear number (DOB and weight absent)', () => {
|
||
const cell = ['Clover', 'RZ7'].join('\n')
|
||
|
||
assert.deepEqual(parseCellFields(cell), {
|
||
farmName: null,
|
||
rabbitName: 'Clover',
|
||
earNumber: 'RZ7',
|
||
dob: null,
|
||
weight: null,
|
||
})
|
||
})
|
||
|
||
test('case 3: name has no possessive (rabbit name only, no farm)', () => {
|
||
const cell = ['Midnight', 'Ear #: XY99'].join('\n')
|
||
|
||
const result = parseCellFields(cell)
|
||
assert.equal(result.farmName, null)
|
||
assert.equal(result.rabbitName, 'Midnight')
|
||
assert.equal(result.earNumber, 'XY99')
|
||
})
|
||
|
||
// --- Per-field rule coverage ---
|
||
|
||
test('possessive split handles a plain "s without apostrophe-s', () => {
|
||
// "Lund Lops' Seabreeze" → farm "Lund Lops'", name "Seabreeze"
|
||
const result = parseCellFields("Lund Lops' Seabreeze")
|
||
assert.equal(result.farmName, "Lund Lops'")
|
||
assert.equal(result.rabbitName, 'Seabreeze')
|
||
})
|
||
|
||
test('weight: decimal lbs form', () => {
|
||
assert.equal(parseCellFields('Bunny\nWeight 8.5 lbs').weight, 8.5)
|
||
})
|
||
|
||
test('weight: bare number with no unit is not a weight', () => {
|
||
assert.equal(parseCellFields('Bunny\n8.5').weight, null)
|
||
})
|
||
|
||
test('dob: 2-digit year maps to 20xx and slashes/dashes both parse', () => {
|
||
assert.equal(parseCellFields('Bunny\nborn 5/9/24').dob, '2024-05-09')
|
||
assert.equal(parseCellFields('Bunny\n12-25-2023').dob, '2023-12-25')
|
||
})
|
||
|
||
test('dob: 2-digit year beyond the current-year pivot maps to 19xx', () => {
|
||
// Older slips carry 1990s ancestors; "98" must not become a future 2098 DOB.
|
||
assert.equal(parseCellFields('Bunny\nDOB: 3/15/98').dob, '1998-03-15')
|
||
})
|
||
|
||
test('dob: invalid month is rejected', () => {
|
||
assert.equal(parseCellFields('Bunny\n13/40/2024').dob, null)
|
||
})
|
||
|
||
test('dob: impossible day for the month is rejected', () => {
|
||
assert.equal(parseCellFields('Bunny\n2/31/2024').dob, null)
|
||
// 2024 is a leap year — 2/29 is real.
|
||
assert.equal(parseCellFields('Bunny\n2/29/2024').dob, '2024-02-29')
|
||
assert.equal(parseCellFields('Bunny\n2/29/2023').dob, null)
|
||
})
|
||
|
||
test('ear number: labelled value wins over a later bare token', () => {
|
||
const result = parseCellFields('Rocky\nTattoo: GH42\nREW')
|
||
assert.equal(result.earNumber, 'GH42')
|
||
})
|
||
|
||
// --- ALL-CAPS pedigrees (real-world: BASTET'S SHANGHEI etc.) ---
|
||
|
||
test('all-caps possessive splits farm from name on one line', () => {
|
||
const result = parseCellFields('BASTET’S SHANGHEI'.replace('’', "'"))
|
||
assert.equal(result.farmName, "BASTET'S")
|
||
assert.equal(result.rabbitName, 'SHANGHEI')
|
||
})
|
||
|
||
test('farm on its own line, rabbit name on the next line', () => {
|
||
const cell = ["BASTET'S", 'SHAMWOW', 'Ear #: BSHAMW', 'Weight: 3 # 15 oz'].join('\n')
|
||
const result = parseCellFields(cell)
|
||
assert.equal(result.farmName, "BASTET'S")
|
||
assert.equal(result.rabbitName, 'SHAMWOW')
|
||
assert.equal(result.earNumber, 'BSHAMW')
|
||
})
|
||
|
||
test('an all-caps name is not mistaken for a bare ear number', () => {
|
||
// "SHANGHEI" is all letters → a name, not an ear. The labelled ear wins.
|
||
const cell = ['MIDNIGHT', 'Ear #: XY99'].join('\n')
|
||
const result = parseCellFields(cell)
|
||
assert.equal(result.rabbitName, 'MIDNIGHT')
|
||
assert.equal(result.earNumber, 'XY99')
|
||
})
|
||
|
||
test('a bare all-letter token is not treated as an ear number', () => {
|
||
// No label, no digit → not an ear; it becomes the name instead.
|
||
const result = parseCellFields('SHAMWOW')
|
||
assert.equal(result.earNumber, null)
|
||
assert.equal(result.rabbitName, 'SHAMWOW')
|
||
})
|
||
|
||
test('looksLikeAncestorCell distinguishes a real cell from the form title', () => {
|
||
assert.equal(looksLikeAncestorCell("BASTET'S SHAMWOW\nEar #: BSHAMW\nWeight: 3 lbs"), true)
|
||
assert.equal(looksLikeAncestorCell("Bastet's Bunnies\nHolland Lop"), false)
|
||
assert.equal(looksLikeAncestorCell(''), false)
|
||
})
|
||
|
||
test('empty / whitespace cell yields all nulls', () => {
|
||
assert.deepEqual(parseCellFields(' \n \n'), {
|
||
farmName: null,
|
||
rabbitName: null,
|
||
earNumber: null,
|
||
dob: null,
|
||
weight: null,
|
||
})
|
||
})
|