Importing a sheet guessed which column was the controller name and which was
the sign text, then silently acted on the guess. On a sheet whose headers it
did not recognise, the guess fell through to "first column", which is usually
the row number — so the destinations came out named 1, 2, 3.
Now the import opens a preview: the two columns are dropdowns, seeded with the
guess and showing a sample of each, above a table of exactly what will be
created. Nothing is imported until it looks right.
The guessing itself is better too:
- a top row of words above rows containing numbers is treated as a header
even when none of its labels are recognised
- with no recognisable columns, the column carrying the longest words wins
- one recognised column stands in for the other, since a destination's name
is usually exactly what the sign shows, rather than picking at random
- a truncated name no longer keeps a trailing space, which would also stop it
matching an existing destination on a later import
Also adds custom sign sizes, for boards that are not one of the three fitted
models. Widths snap to a multiple of 8 since the record stores width in bytes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
2.7 KiB
JavaScript
60 lines
2.7 KiB
JavaScript
/** Column-mapping checks for spreadsheet import. */
|
|
import { readCsv, mapRows } from '../src/codec/sheet.mjs';
|
|
|
|
let fails = 0;
|
|
function check(label, got, want) {
|
|
const ok = JSON.stringify(got) === JSON.stringify(want);
|
|
if (!ok) { fails++; console.log(` ❌ ${label}\n got ${JSON.stringify(got)}\n want ${JSON.stringify(want)}`); }
|
|
else console.log(` ✅ ${label}`);
|
|
}
|
|
|
|
// The template shipped with these buses: a "Line" row number AND a "Line Name".
|
|
// The name must win over the number — this was a real bug.
|
|
check('shipped template layout',
|
|
mapRows(readCsv(
|
|
'Company Name:,Yutong Coaches\n' +
|
|
'\n' +
|
|
'Line,Line Name,Description,Content (Display)\n' +
|
|
'1,SCHOOL BUS,morning,SCHOOL BUS\n' +
|
|
'2,CARMEL,college run,CARMEL COLLEGE\n'
|
|
)),
|
|
{ company: 'Yutong Coaches', destinations: [
|
|
{ name: 'SCHOOL BUS', text: 'SCHOOL BUS' },
|
|
{ name: 'CARMEL', text: 'CARMEL COLLEGE' },
|
|
] });
|
|
|
|
check('only a number column plus content — do not show the driver a bare number',
|
|
mapRows(readCsv('Line,Content (Display)\n1,DEPOT\n2,RAIL BUS\n')).destinations,
|
|
[{ name: 'DEPOT', text: 'DEPOT' }, { name: 'RAIL BUS', text: 'RAIL BUS' }]);
|
|
|
|
check('no header at all',
|
|
mapRows(readCsv('SCHOOL BUS\nCHARTER\n')).destinations,
|
|
[{ name: 'SCHOOL BUS', text: 'SCHOOL BUS' }, { name: 'CHARTER', text: 'CHARTER' }]);
|
|
|
|
check('destination column naming',
|
|
mapRows(readCsv('Name,Destination\nAIRPORT,AIRPORT SHUTTLE VIA CITY\n')).destinations,
|
|
[{ name: 'AIRPORT', text: 'AIRPORT SHUTTLE VIA CITY' }]);
|
|
|
|
check('blank rows and stray whitespace ignored',
|
|
mapRows(readCsv('Line Name,Content\n\n DEPOT , DEPOT VIA YARD \n\n')).destinations,
|
|
[{ name: 'DEPOT', text: 'DEPOT VIA YARD' }]);
|
|
|
|
check('controller name is capped at the 16 characters the record holds',
|
|
mapRows(readCsv('Line Name,Content\nA VERY LONG DESTINATION NAME,SOMETHING\n')).destinations[0].name,
|
|
'A VERY LONG DEST');
|
|
|
|
check('quoted fields with commas',
|
|
mapRows(readCsv('Line Name,Content\n"CITY, VIA MALL","CITY, VIA MALL"\n')).destinations,
|
|
[{ name: 'CITY, VIA MALL', text: 'CITY, VIA MALL' }]);
|
|
|
|
check('unrecognised headers are still headers, and Route beats the row number',
|
|
mapRows(readCsv('No.,Route,Notes\n1,SCHOOL BUS,am\n2,CARMEL COLLEGE,pm\n')).destinations,
|
|
[{ name: 'SCHOOL BUS', text: 'SCHOOL BUS' }, { name: 'CARMEL COLLEGE', text: 'CARMEL COLLEGE' }]);
|
|
|
|
check('no recognisable columns at all — longest words win, name mirrors the sign',
|
|
mapRows(readCsv('A,B,C\n1,x,AIRPORT SHUTTLE VIA CITY\n2,y,DEPOT RUN\n')).destinations,
|
|
[{ name: 'AIRPORT SHUTTLE', text: 'AIRPORT SHUTTLE VIA CITY' }, { name: 'DEPOT RUN', text: 'DEPOT RUN' }]);
|
|
|
|
console.log(fails === 0 ? '\n✅ spreadsheet mapping correct' : `\n❌ ${fails} failed`);
|
|
process.exit(fails ? 1 : 0);
|