fix(folio): drop a name two cards answer to

homeTxnCountsOf keyed on the account name and let the last card win, so
two accounts the fuzzer named the same collapsed into one entry. a
reading that saw one Travel card and a later one that saw both then
subtracted two different accounts' counts, and
submitCommitsOneTransactionPerAction convicted a healthy app of
double-submitting. it is a gated property in folio-run.sh, so that reads
as "found the submit bug" over a card scrolling into view.

same rule createdAccountHasNonZeroBalance already applies: a name
nothing can attribute is no evidence. counted over every card, since an
unreadable twin spoils the identity too.
This commit is contained in:
pj committed 2026-08-15 12:45:09 +05:30
1 parent ea4d40e507
commit ade3e77ff5
2 files changed
+105 -1

No files matched your search

@@ -360,3 +360,93 @@ test("the merged-text card list drops the same pair", () => {
false,
);
});
// Home lists whatever fits the viewport, and Folio lets two accounts share a
// name, so one name can arrive on two cards. Keying counts by name collapsed
// them onto the last card, and the two readings a window compares then came off
// DIFFERENT cards: the probe below is a healthy app, one submit, and a scroll.
test("two cards sharing a name do not become one count", () => {
const before = homeTxnCountsOf([{ name: "Travel", balance: 0, count: 0 }]);
const after = homeTxnCountsOf([
{ name: "Travel", balance: 0, count: 0 },
{ name: "Travel", balance: 500, count: 8 },
]);
assert.equal(
committedTransactionsExceedSubmits({
countsBefore: before,
countsAfter: after,
submitsInWindow: 1,
}),
false,
);
assert.deepEqual(after, null);
});
test("a name on two cards is dropped from both readings", () => {
const before = homeTxnCountsOf([
{ name: "Travel", balance: 0, count: "3" },
{ name: "Travel", balance: 0, count: "9" },
{ name: "Checking", balance: 0, count: "2" },
]);
const after = homeTxnCountsOf([
{ name: "Travel", balance: 0, count: "3" },
{ name: "Travel", balance: 0, count: "11" },
{ name: "Checking", balance: 0, count: "2" },
]);
assert.deepEqual(before, { Checking: "2" });
assert.deepEqual(after, { Checking: "2" });
assert.equal(
committedTransactionsExceedSubmits({
countsBefore: before,
countsAfter: after,
submitsInWindow: 0,
}),
false,
);
});
// The other card need not be readable to spoil the identity: an unreadable
// count still means the name on the map may not be the card that was read.
test("a duplicate name is dropped even when the twin has no count", () => {
assert.deepEqual(
homeTxnCountsOf([
{ name: "Travel", balance: 0, count: 4 },
{ name: "Travel", balance: 0, count: undefined },
{ name: "Savings", balance: 0, count: 1 },
]),
{ Savings: 1 },
);
});
// Dropping the ambiguous name must not disable the property for the rest.
test("a unique name is still counted beside a dropped duplicate", () => {
const before = homeTxnCountsOf([
{ name: "Travel", balance: 0, count: 3 },
{ name: "Travel", balance: 0, count: 1 },
{ name: "Checking", balance: 0, count: 4 },
]);
const after = homeTxnCountsOf([
{ name: "Travel", balance: 0, count: 3 },
{ name: "Travel", balance: 0, count: 1 },
{ name: "Checking", balance: 0, count: 6 },
]);
assert.deepEqual(before, { Checking: 4 });
assert.equal(
committedTransactionsExceedSubmits({
countsBefore: before,
countsAfter: after,
submitsInWindow: 1,
}),
true,
);
});
test("distinct names are all counted", () => {
assert.deepEqual(
homeTxnCountsOf([
{ name: "Checking", balance: 0, count: "3" },
{ name: "Savings", balance: 0, count: "1" },
]),
{ Checking: "3", Savings: "1" },
);
});