feat(folio): add noDuplicateTxnPerStep invariant and doubleSubmitTxn action

This commit is contained in:
pj committed 2026-05-30 11:07:31 +05:30
1 parent 87834caf96
commit 9f17afecc2
1 file changed
+27 -4
+27 -4
View File
@@ -1,4 +1,5 @@
import { import {
DoubleTap,
InputText, InputText,
Tap, Tap,
actions, actions,
@@ -95,7 +96,17 @@ const newAccountBalanceIsZero = always(
}) })
); );
// Property 2: a newly-added ledger row changes the ledger balance by exactly its signed amount. // Property 2: no single user action grows the ledger by more than one row.
// Reuses the existing ledgerRows extractor.
const noDuplicateTxnPerStep = always(
next(() => {
const prev = ledgerRows.previous?.length ?? 0;
const curr = ledgerRows.current.length;
return curr - prev <= 1;
})
);
// Property 3: a newly-added ledger row changes the ledger balance by exactly its signed amount.
const newTxnChangesBalance = always( const newTxnChangesBalance = always(
now(() => route.current === "ledger").implies( now(() => route.current === "ledger").implies(
next(() => { next(() => {
@@ -165,8 +176,19 @@ const addTxn = whenRoute(route, ["home", "ledger", "add-transaction"], () => {
return opts; return opts;
}); });
const doubleSubmitTxn = whenRoute(route, "add-transaction", () => {
const field = txnAmountField.current;
const submit = txnSubmit.current;
if (!field || !submit) return [];
return [
InputText({ into: field, text: amounts.generate() }),
DoubleTap({ on: submit }),
];
});
export const properties = { export const properties = {
newAccountBalanceIsZero, newAccountBalanceIsZero,
noDuplicateTxnPerStep,
newTxnChangesBalance, newTxnChangesBalance,
}; };
@@ -176,9 +198,10 @@ export const setup = login;
// adds breadth so the fuzzer wanders the whole app and types edge-case values // adds breadth so the fuzzer wanders the whole app and types edge-case values
// into every field, stressing the balance invariants above. // into every field, stressing the balance invariants above.
export const actionsRoot = weighted( export const actionsRoot = weighted(
[50, addAccount], [40, addAccount],
[40, addTxn], [30, addTxn],
[20, defaultActions], [15, doubleSubmitTxn],
[15, defaultActions],
); );
(globalThis as { actions?: unknown; properties?: unknown; setup?: unknown }).actions = actionsRoot; (globalThis as { actions?: unknown; properties?: unknown; setup?: unknown }).actions = actionsRoot;