mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
fix(folio): decline the two demanding properties across a relaunch
The runner now keeps lastAction and marks it relaunched: true where it used to report nothing at all, so the two properties that demand an effect judge a step whose process may have died before the write landed. submitChangesBalanceByTypedAmount and createdAccountHasNonZeroBalance both decline there. The counting bound does not: a relaunch cannot manufacture a transaction, and the submit is counted, so declining would throw away the detection the runner fix restored.
This commit is contained in:
1 parent
0e096c00b3
commit
9770537aa6
3 files changed
+94
No files matched your search
@@ -181,6 +181,7 @@ export interface ObservedAction {
|
|||||||
kind?: string;
|
kind?: string;
|
||||||
on?: string | object;
|
on?: string | object;
|
||||||
applied?: true | null;
|
applied?: true | null;
|
||||||
|
relaunched?: true | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTapOn(lastAction: ObservedAction | null, target: string): boolean {
|
function isTapOn(lastAction: ObservedAction | null, target: string): boolean {
|
||||||
@@ -216,6 +217,19 @@ export function confirmedApplied(lastAction: ObservedAction | null): boolean {
|
|||||||
return lastAction != null && lastAction.applied === true;
|
return lastAction != null && lastAction.applied === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The runner reports this when its foreground guard had to relaunch the app
|
||||||
|
// after the action. The action still happened, so it still counts toward how
|
||||||
|
// many submits a window could hold; what nobody can promise across it is that
|
||||||
|
// the process survived long enough to commit, or that Home is showing the same
|
||||||
|
// slice of the account list it was.
|
||||||
|
//
|
||||||
|
// `true | null` for the same reason `applied` is: web and iOS cannot read the
|
||||||
|
// foreground at all, so "no relaunch reported" is not "the app never
|
||||||
|
// restarted", and only an explicit true licenses declining.
|
||||||
|
export function acrossRelaunch(lastAction: ObservedAction | null): boolean {
|
||||||
|
return lastAction != null && lastAction.relaunched === true;
|
||||||
|
}
|
||||||
|
|
||||||
// Counts the submit actions inside the window the balance property compares
|
// Counts the submit actions inside the window the balance property compares
|
||||||
// over: from the last Home total we read to this step, inclusive of this step's
|
// over: from the last Home total we read to this step, inclusive of this step's
|
||||||
// action.
|
// action.
|
||||||
@@ -453,6 +467,10 @@ export function createdAccountHasNonZeroBalance(args: {
|
|||||||
// card to: the card that turned up may be an older account of the same name
|
// card to: the card that turned up may be an older account of the same name
|
||||||
// scrolling into view.
|
// scrolling into view.
|
||||||
if (!confirmedApplied(lastAction)) return false;
|
if (!confirmedApplied(lastAction)) return false;
|
||||||
|
// A relaunch draws Home from the top again, so the card that carries the
|
||||||
|
// typed name may be an older account of that name laid out where the new one
|
||||||
|
// used to be, and the create may not have reached sqlite at all.
|
||||||
|
if (acrossRelaunch(lastAction)) return false;
|
||||||
if (before === null || after === null) return false;
|
if (before === null || after === null) return false;
|
||||||
const typed = (args.typedName ?? "").trim();
|
const typed = (args.typedName ?? "").trim();
|
||||||
if (typed === "") return false;
|
if (typed === "") return false;
|
||||||
@@ -641,6 +659,10 @@ export function submitChangesBalanceByTypedAmount(args: {
|
|||||||
// runner could not confirm may have committed nothing, and a balance that
|
// runner could not confirm may have committed nothing, and a balance that
|
||||||
// did not move is then exactly what a healthy app looks like.
|
// did not move is then exactly what a healthy app looks like.
|
||||||
if (!confirmedApplied(lastAction)) return true;
|
if (!confirmedApplied(lastAction)) return true;
|
||||||
|
// The runner restarted the app after this tap, so the process may have died
|
||||||
|
// between the commit and the sqlite write. A balance that did not move is
|
||||||
|
// then a healthy app, exactly as it is for a submit that may not have landed.
|
||||||
|
if (acrossRelaunch(lastAction)) return true;
|
||||||
if (submitsInWindow !== 1) return true;
|
if (submitsInWindow !== 1) return true;
|
||||||
if (typedAmount === 0) return true;
|
if (typedAmount === 0) return true;
|
||||||
// An unknown total on either side is not evidence of anything. Comparing one
|
// An unknown total on either side is not evidence of anything. Comparing one
|
||||||
|
|||||||
@@ -322,6 +322,40 @@ test("a card that was already there is not a card that was just created", () =>
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The runner's foreground guard restarted the app after the create. A fresh
|
||||||
|
// launch draws Home from the top, so the visible set is whatever the new layout
|
||||||
|
// fits rather than what was there a step ago, and "appeared in the reading" is
|
||||||
|
// even less like "was created" than usual. The process may also have died
|
||||||
|
// before the write landed, which makes the card that carries the typed name an
|
||||||
|
// older account of that name coming into view.
|
||||||
|
test("a create the runner relaunched across attributes nothing", () => {
|
||||||
|
assert.equal(
|
||||||
|
createdAccountHasNonZeroBalance({
|
||||||
|
route: "home",
|
||||||
|
lastAction: { ...created, relaunched: true },
|
||||||
|
typedName: "Travel",
|
||||||
|
before: [account("Checking", 0)],
|
||||||
|
after: [account("Checking", 0), account("Travel", 5000)],
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("no relaunch reported still judges the account that was created", () => {
|
||||||
|
for (const relaunched of [null, undefined]) {
|
||||||
|
assert.equal(
|
||||||
|
createdAccountHasNonZeroBalance({
|
||||||
|
route: "home",
|
||||||
|
lastAction: { ...created, relaunched },
|
||||||
|
typedName: "Travel",
|
||||||
|
before: [account("Checking", 0)],
|
||||||
|
after: [account("Checking", 0), account("Travel", 5000)],
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// The apply call failed with the gesture possibly already delivered, so nobody
|
// The apply call failed with the gesture possibly already delivered, so nobody
|
||||||
// knows whether that account was created. The card carrying the typed name may
|
// knows whether that account was created. The card carrying the typed name may
|
||||||
// be an older one that scrolled into view, and attributing it to a creation
|
// be an older one that scrolled into view, and attributing it to a creation
|
||||||
|
|||||||
@@ -520,3 +520,41 @@ test("a submit the runner could not confirm demands no balance move", () => {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// relaunched: true is the runner saying its foreground guard restarted the app
|
||||||
|
// after this action. The tap landed, so the window still counts it, but nobody
|
||||||
|
// can promise the process lived long enough for the write to reach sqlite. A
|
||||||
|
// balance still sitting where it was is exactly what a healthy app looks like
|
||||||
|
// across a relaunch, and demanding the typed amount of movement convicts it for
|
||||||
|
// the runner's own restart.
|
||||||
|
test("a submit the runner relaunched across demands no balance move", () => {
|
||||||
|
assert.equal(
|
||||||
|
submitChangesBalanceByTypedAmount({
|
||||||
|
route: "home",
|
||||||
|
lastAction: { kind: "Tap", on: submitOn, applied: true, relaunched: true },
|
||||||
|
submitsInWindow: 1,
|
||||||
|
typedAmount: 500,
|
||||||
|
prevTotalBalance: 1000,
|
||||||
|
currTotalBalance: 1000,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The guard must not become a way of switching the property off. No relaunch
|
||||||
|
// reported is the ordinary case, and web and iOS cannot report one at all.
|
||||||
|
test("no relaunch reported still convicts a double submit", () => {
|
||||||
|
for (const relaunched of [null, undefined]) {
|
||||||
|
assert.equal(
|
||||||
|
submitChangesBalanceByTypedAmount({
|
||||||
|
route: "home",
|
||||||
|
lastAction: { kind: "DoubleTap", on: submitOn, applied: true, relaunched },
|
||||||
|
submitsInWindow: 1,
|
||||||
|
typedAmount: 500,
|
||||||
|
prevTotalBalance: 1000,
|
||||||
|
currTotalBalance: 2000,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user