The CSV export carries the hands, the standings and the settle-up. It carried neither of the two places a dealer types context. Each seat can hold custom fields -- "IOU: $20 to Ann", "Seat rent: paid to 2am", "Venmo: @ann" -- and the table has a notes box for the rest: side bets, a high-hand promo, who owes the box from last week. Both are typed precisely because they matter to settling up, and both were visible only inside the tool. The one place the context was written down was the one place it could not leave. Now the standings rows end with a Notes column carrying that seat's fields, and the table's notes go in a section of their own at the end. Player,Paid In,Won,Net,Bought In,Chips Now,Up/Down,Notes Ann,20.00,60.00,40.00,200.00,150.00,-50.00,Seat rent: paid to 2am; Venmo: @ann Ben,20.00,0.00,-20.00,200.00,200.00,0.00,IOU: $20 to Ann Cal,20.00,0.00,-20.00,200.00,250.00,50.00, Table Notes "High-hand promo until midnight. Mo owes the box $15, from last week." Notes go last so the money columns keep the shape a spreadsheet expects, and the multi-line note is quoted as one cell rather than becoming rows that read as more players. An existing test caught this, correctly "every money cell parses as a number" asserted that every column after the name was numeric, and a Notes column is text by definition. The invariant it protects is real -- the file exists so somebody can total a column -- so it now names the six money columns and checks those, rather than "everything after the first". That is the assertion it always meant. 9 tests (278 total): a seat's fields ride along, the money columns keep their order, a seat with nothing written gets an empty cell rather than the word undefined, a blank field does not leave a stray colon, a multi-line note survives as one cell, no notes means no section rather than an empty heading, and asking for the standings does not change the seats. Driven through the real Export CSV button, not just the function: the downloaded file carries both seats' notes and the table's. Audit and contrast clean, no console errors. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
98 lines
4.0 KiB
JavaScript
98 lines
4.0 KiB
JavaScript
// The things the dealer wrote down, in the file they hand over.
|
|
//
|
|
// The export already carried the hands, the standings and the settle-up. It didn't
|
|
// carry either of the two places a dealer types context — the per-seat fields and the
|
|
// table's own notes — so the one place an IOU was written down was the one place it
|
|
// couldn't leave.
|
|
const test = require("node:test");
|
|
const assert = require("node:assert");
|
|
const { HandHistoryExport, HoldemState, makeTable } = require("./load");
|
|
|
|
const tableWithNotes = () => {
|
|
const state = makeTable({ seatCount: 6, seated: [0, 1, 2], stack: 200 });
|
|
["Ann", "Ben", "Cal"].forEach((name, i) => {
|
|
state.seats[i].name = name;
|
|
state.seats[i].buyIns = 200;
|
|
});
|
|
state.seats[0].customFields = [
|
|
{ label: "Seat rent", value: "paid to 2am" },
|
|
{ label: "Venmo", value: "@ann" },
|
|
];
|
|
state.seats[1].customFields = [{ label: "IOU", value: "$20 to Ann" }];
|
|
return state;
|
|
};
|
|
|
|
test("a seat's own fields ride along with its standings row", () => {
|
|
const state = tableWithNotes();
|
|
const csv = HandHistoryExport.toCsv([], HoldemState.sessionStandings(state));
|
|
|
|
assert.match(csv, /Seat rent: paid to 2am; Venmo: @ann/);
|
|
assert.match(csv, /IOU: \$20 to Ann/);
|
|
});
|
|
|
|
test("the money columns keep their order, with notes on the end", () => {
|
|
const state = tableWithNotes();
|
|
const csv = HandHistoryExport.toCsv([], HoldemState.sessionStandings(state));
|
|
|
|
assert.match(csv, /Player,Paid In,Won,Net,Bought In,Chips Now,Up\/Down,Notes/);
|
|
});
|
|
|
|
test("a seat with nothing written against it gets an empty cell, not the word undefined", () => {
|
|
const state = tableWithNotes();
|
|
const csv = HandHistoryExport.toCsv([], HoldemState.sessionStandings(state));
|
|
const calRow = csv.split("\r\n").find(line => line.startsWith("Cal,"));
|
|
|
|
assert.ok(calRow, "Cal should have a row");
|
|
assert.ok(calRow.endsWith(","), `expected an empty last cell, got ${calRow}`);
|
|
assert.ok(!/undefined|null/.test(calRow));
|
|
});
|
|
|
|
test("a blank label or value doesn't produce a stray colon", () => {
|
|
const state = tableWithNotes();
|
|
state.seats[2].customFields = [{ label: "Note", value: "" }, { label: "", value: "" }];
|
|
const csv = HandHistoryExport.toCsv([], HoldemState.sessionStandings(state));
|
|
const calRow = csv.split("\r\n").find(line => line.startsWith("Cal,"));
|
|
|
|
assert.match(calRow, /Note/);
|
|
assert.ok(!/;\s*;/.test(calRow), "an entirely blank field shouldn't take a slot");
|
|
});
|
|
|
|
test("the table's notes are in the file", () => {
|
|
const state = tableWithNotes();
|
|
state.notes = "High-hand promo until midnight.";
|
|
const csv = HandHistoryExport.toCsv([], HoldemState.sessionStandings(state), { notes: state.notes });
|
|
|
|
assert.match(csv, /Table Notes/);
|
|
assert.match(csv, /High-hand promo until midnight\./);
|
|
});
|
|
|
|
test("a multi-line note survives as one cell", () => {
|
|
// csvField quotes anything with a line break in it, so the note doesn't turn into
|
|
// rows a spreadsheet reads as more players.
|
|
const state = tableWithNotes();
|
|
const notes = "Line one\nLine two, with a comma\nLine \"three\"";
|
|
const csv = HandHistoryExport.toCsv([], HoldemState.sessionStandings(state), { notes });
|
|
|
|
assert.match(csv, /"Line one\nLine two, with a comma\nLine ""three"""/);
|
|
});
|
|
|
|
test("no notes means no section, not an empty heading", () => {
|
|
const state = tableWithNotes();
|
|
const csv = HandHistoryExport.toCsv([], HoldemState.sessionStandings(state), { notes: " " });
|
|
|
|
assert.ok(!/Table Notes/.test(csv));
|
|
});
|
|
|
|
test("the export still works with no standings at all", () => {
|
|
assert.doesNotThrow(() => HandHistoryExport.toCsv([], [], { notes: "just a note" }));
|
|
const csv = HandHistoryExport.toCsv([], [], { notes: "just a note" });
|
|
assert.match(csv, /Table Notes/);
|
|
});
|
|
|
|
test("asking for the standings doesn't change the seats", () => {
|
|
const state = tableWithNotes();
|
|
const before = JSON.stringify(state.seats);
|
|
HoldemState.sessionStandings(state);
|
|
assert.equal(JSON.stringify(state.seats), before);
|
|
});
|