@remit/web-client 0.0.269 → 0.0.271
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/mail/MessageList.run-reporting.test.ts +1 -1
- package/src/components/mail/SelectionWizardHost.tsx +15 -50
- package/src/components/mail/bulk-run-state.test.ts +100 -0
- package/src/components/mail/bulk-run-state.ts +99 -0
- package/src/hooks/escalated-run-lifetime.render.test.ts +87 -6
- package/src/hooks/update-restart-escalation.render.test.ts +340 -0
- package/src/hooks/use-system-update.ts +49 -2
- package/src/hooks/useEscalatedActions.ts +45 -24
- package/src/lib/bulk-action-copy.test.ts +14 -4
- package/src/lib/bulk-action-copy.ts +5 -3
- package/src/lib/bulk-actions.test.ts +115 -26
- package/src/lib/bulk-actions.ts +40 -10
- package/src/lib/error-classifier.test.ts +59 -0
- package/src/lib/error-classifier.ts +48 -2
- package/src/lib/self-update-state.test.ts +1 -0
- package/src/lib/self-update-state.ts +138 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/web-client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.271",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
|
|
6
6
|
"exports": {
|
|
@@ -63,7 +63,7 @@ describe("reporting how a run ended", () => {
|
|
|
63
63
|
);
|
|
64
64
|
assert.equal(stopped?.title, "Stopped after 100");
|
|
65
65
|
assert.match(stopped?.detail ?? "", /100 of 3,000 moved to Trash\./);
|
|
66
|
-
assert.match(stopped?.detail ?? "", /
|
|
66
|
+
assert.match(stopped?.detail ?? "", /The rest were not sent/);
|
|
67
67
|
|
|
68
68
|
const covered = runEndingBanner("delete", 100, ended(), "trash");
|
|
69
69
|
assert.match(covered?.title ?? "", /^100 moved to Trash\./);
|
|
@@ -75,6 +75,7 @@ import type { OrganizeMatchPredicate } from "@/lib/organize/sender-fallback";
|
|
|
75
75
|
import { useSearchConversion } from "@/lib/search-conversion";
|
|
76
76
|
import type { WizardSelectionMessage } from "@/lib/wizard-selection";
|
|
77
77
|
import { useWizardEntryValue, useWizardStep } from "@/routing";
|
|
78
|
+
import { bulkRunReport } from "./bulk-run-state";
|
|
78
79
|
import { organizeRunState } from "./organize-run-state";
|
|
79
80
|
import {
|
|
80
81
|
retryIntent,
|
|
@@ -823,57 +824,21 @@ function SelectionWizardSession({
|
|
|
823
824
|
|
|
824
825
|
const bulkSnapshot = useCallback((): RunSnapshot => {
|
|
825
826
|
if (!bulkRun) return NOT_STARTED;
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
832
|
-
if (!outcome) {
|
|
833
|
-
const applied = runProgress?.done ?? 0;
|
|
834
|
-
// A predicate matches more by the time the run re-pages it than the count
|
|
835
|
-
// saw, so what it has covered can overtake what it was offered against.
|
|
836
|
-
// The bar never reads more done than out of, and neither does this.
|
|
837
|
-
return {
|
|
838
|
-
state: "backApplyRunning",
|
|
839
|
-
matched: Math.max(matched, applied),
|
|
840
|
-
applied,
|
|
841
|
-
failed: 0,
|
|
842
|
-
failures: [],
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
const stopped = outcome.cancelled || outcome.error !== undefined;
|
|
846
|
-
if (!stopped) {
|
|
847
|
-
return {
|
|
848
|
-
state: "backApplyComplete",
|
|
849
|
-
matched: Math.max(matched, outcome.done),
|
|
850
|
-
applied: outcome.done,
|
|
851
|
-
failed: 0,
|
|
852
|
-
failures: [],
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
if (outcome.done === 0) {
|
|
856
|
-
return { ...NOT_STARTED, state: "commitFailed" };
|
|
857
|
-
}
|
|
858
|
-
// The run stopped part-way. Nothing here was rejected: a returned bulk call
|
|
859
|
-
// accepts every id in it, so the only failure this layer sees is a call that
|
|
860
|
-
// threw, and everything after it was never sent. A bounded run hands back
|
|
861
|
-
// exactly those ids; a predicate run re-resolves its match on every pass and
|
|
862
|
-
// has no remainder to hand back, so what is left is the difference between
|
|
863
|
-
// the count and what the run covered.
|
|
864
|
-
const failures = outcome.failedIds
|
|
865
|
-
.map((id) => rowsById.get(id))
|
|
866
|
-
.filter((message): message is WizardMessage => message !== undefined);
|
|
867
|
-
const unreached =
|
|
868
|
-
outcome.failedIds.length > 0
|
|
869
|
-
? outcome.failedIds.length
|
|
870
|
-
: Math.max(matched - outcome.done, 1);
|
|
827
|
+
const report = bulkRunReport({
|
|
828
|
+
matched: bulkRun.matched,
|
|
829
|
+
outcome: bulkRun.outcome,
|
|
830
|
+
failureReason: bulkRun.failureReason,
|
|
831
|
+
progressDone: runProgress?.done ?? 0,
|
|
832
|
+
});
|
|
871
833
|
return {
|
|
872
|
-
state:
|
|
873
|
-
matched:
|
|
874
|
-
applied:
|
|
875
|
-
failed:
|
|
876
|
-
failures
|
|
834
|
+
state: report.state,
|
|
835
|
+
matched: report.matched,
|
|
836
|
+
applied: report.applied,
|
|
837
|
+
failed: report.failed,
|
|
838
|
+
failures: report.failedIds
|
|
839
|
+
.map((id) => rowsById.get(id))
|
|
840
|
+
.filter((message): message is WizardMessage => message !== undefined),
|
|
841
|
+
failureReason: report.failureReason,
|
|
877
842
|
};
|
|
878
843
|
}, [bulkRun, runProgress, rowsById]);
|
|
879
844
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { BulkRunOutcome } from "@/lib/bulk-actions";
|
|
4
|
+
import { bulkRunReport } from "./bulk-run-state";
|
|
5
|
+
|
|
6
|
+
const ended = (over: Partial<BulkRunOutcome> = {}): BulkRunOutcome => ({
|
|
7
|
+
done: 0,
|
|
8
|
+
failedIds: [],
|
|
9
|
+
cancelled: false,
|
|
10
|
+
...over,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const report = (
|
|
14
|
+
over: Partial<Parameters<typeof bulkRunReport>[0]> = {},
|
|
15
|
+
): ReturnType<typeof bulkRunReport> =>
|
|
16
|
+
bulkRunReport({
|
|
17
|
+
matched: 100,
|
|
18
|
+
outcome: undefined,
|
|
19
|
+
failureReason: undefined,
|
|
20
|
+
progressDone: 0,
|
|
21
|
+
...over,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("what the run screen says a bulk run did", () => {
|
|
25
|
+
it("shows a run still going against what it was started with", () => {
|
|
26
|
+
assert.deepEqual(report({ progressDone: 40 }), {
|
|
27
|
+
state: "backApplyRunning",
|
|
28
|
+
matched: 100,
|
|
29
|
+
applied: 40,
|
|
30
|
+
failed: 0,
|
|
31
|
+
failedIds: [],
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("never reads more done than out of", () => {
|
|
36
|
+
const going = report({ matched: 1284, progressDone: 1340 });
|
|
37
|
+
assert.equal(going.matched, 1340);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("ends a run that covered its match on the success screen", () => {
|
|
41
|
+
const covered = report({ outcome: ended({ done: 100 }) });
|
|
42
|
+
assert.equal(covered.state, "backApplyComplete");
|
|
43
|
+
assert.equal(covered.applied, 100);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("carries a commit that never started, and why", () => {
|
|
47
|
+
const never = report({ failureReason: "There is no Junk folder" });
|
|
48
|
+
assert.equal(never.state, "commitFailed");
|
|
49
|
+
assert.equal(never.failureReason, "There is no Junk folder");
|
|
50
|
+
assert.equal(never.matched, 0);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("says a run that errored before anything landed never started", () => {
|
|
54
|
+
const threw = report({ outcome: ended({ error: new Error("500") }) });
|
|
55
|
+
assert.equal(threw.state, "commitFailed");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Regression for #113. A stop used to be read only between pages, so it always
|
|
61
|
+
* landed with at least one batch already applied and `done === 0` could only
|
|
62
|
+
* mean a failure. Aborting the request in flight makes zero the ordinary
|
|
63
|
+
* outcome of stopping during the first batch — the only batch a selection of a
|
|
64
|
+
* hundred or fewer ever has — and answering that with the danger screen told
|
|
65
|
+
* the user nothing had changed while the server had most likely taken all of
|
|
66
|
+
* them.
|
|
67
|
+
*/
|
|
68
|
+
describe("a run the user stopped", () => {
|
|
69
|
+
it("reports a stop during the first batch as stopped, not as a failed start", () => {
|
|
70
|
+
const stopped = report({
|
|
71
|
+
matched: 100,
|
|
72
|
+
outcome: ended({ cancelled: true, failedIds: Array(100).fill("m") }),
|
|
73
|
+
});
|
|
74
|
+
assert.equal(stopped.state, "runStopped");
|
|
75
|
+
assert.notEqual(stopped.state, "commitFailed");
|
|
76
|
+
assert.equal(stopped.applied, 0);
|
|
77
|
+
assert.equal(stopped.failed, 100);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("reports a stop that reached nothing at all as stopped too", () => {
|
|
81
|
+
// The escalated path hands back no remainder: the predicate re-resolves on
|
|
82
|
+
// every run, so there are no ids to list, only a difference to name.
|
|
83
|
+
const stopped = report({
|
|
84
|
+
matched: 3000,
|
|
85
|
+
outcome: ended({ cancelled: true }),
|
|
86
|
+
});
|
|
87
|
+
assert.equal(stopped.state, "runStopped");
|
|
88
|
+
assert.equal(stopped.failed, 3000);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("still reports a stop that got part-way", () => {
|
|
92
|
+
const stopped = report({
|
|
93
|
+
matched: 3000,
|
|
94
|
+
outcome: ended({ done: 1200, cancelled: true }),
|
|
95
|
+
});
|
|
96
|
+
assert.equal(stopped.state, "runStopped");
|
|
97
|
+
assert.equal(stopped.applied, 1200);
|
|
98
|
+
assert.equal(stopped.failed, 1800);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { RunState } from "@remit/ui";
|
|
2
|
+
import type { BulkRunOutcome } from "@/lib/bulk-actions";
|
|
3
|
+
|
|
4
|
+
/** What the run screen knows about a chunked bulk run it is reporting on. */
|
|
5
|
+
export interface BulkRunReading {
|
|
6
|
+
/** The count the run was started against. */
|
|
7
|
+
matched: number;
|
|
8
|
+
/** Set once the run has ended, for any reason. */
|
|
9
|
+
outcome: BulkRunOutcome | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Why the commit never started, when the reason is one the same commit cannot
|
|
12
|
+
* get past (#522).
|
|
13
|
+
*/
|
|
14
|
+
failureReason: string | undefined;
|
|
15
|
+
/** Ids covered so far, read off the progress bar while the run is going. */
|
|
16
|
+
progressDone: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BulkRunReport {
|
|
20
|
+
state: RunState;
|
|
21
|
+
matched: number;
|
|
22
|
+
applied: number;
|
|
23
|
+
failed: number;
|
|
24
|
+
/** Ids the run leaves unconfirmed; the screen resolves them to rows. */
|
|
25
|
+
failedIds: readonly string[];
|
|
26
|
+
failureReason?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const NOTHING_STARTED = {
|
|
30
|
+
matched: 0,
|
|
31
|
+
applied: 0,
|
|
32
|
+
failed: 0,
|
|
33
|
+
failedIds: [] as readonly string[],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* What the run screen says a bulk run did.
|
|
38
|
+
*
|
|
39
|
+
* Stopping is read before counting, because a stop is not a failure however
|
|
40
|
+
* little it reached (#113). Pressing Stop during the first batch — the only
|
|
41
|
+
* batch a selection of 100 or fewer has — leaves `done` at zero while the
|
|
42
|
+
* server has most likely applied all of them, and answering that with
|
|
43
|
+
* "Couldn't start deleting / Nothing has changed" is both alarming and wrong.
|
|
44
|
+
* Only a run that errored before anything landed has nothing to report but the
|
|
45
|
+
* failure.
|
|
46
|
+
*/
|
|
47
|
+
export const bulkRunReport = ({
|
|
48
|
+
matched,
|
|
49
|
+
outcome,
|
|
50
|
+
failureReason,
|
|
51
|
+
progressDone,
|
|
52
|
+
}: BulkRunReading): BulkRunReport => {
|
|
53
|
+
// Nothing was sent, and nothing about sending it again resolves what was
|
|
54
|
+
// missing — so the screen carries why rather than the generic ending (#522).
|
|
55
|
+
if (failureReason !== undefined) {
|
|
56
|
+
return { ...NOTHING_STARTED, state: "commitFailed", failureReason };
|
|
57
|
+
}
|
|
58
|
+
if (!outcome) {
|
|
59
|
+
// A predicate matches more by the time the run re-pages it than the count
|
|
60
|
+
// saw, so what it has covered can overtake what it was offered against.
|
|
61
|
+
// The bar never reads more done than out of, and neither does this.
|
|
62
|
+
return {
|
|
63
|
+
state: "backApplyRunning",
|
|
64
|
+
matched: Math.max(matched, progressDone),
|
|
65
|
+
applied: progressDone,
|
|
66
|
+
failed: 0,
|
|
67
|
+
failedIds: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (!outcome.cancelled && outcome.error === undefined) {
|
|
71
|
+
return {
|
|
72
|
+
state: "backApplyComplete",
|
|
73
|
+
matched: Math.max(matched, outcome.done),
|
|
74
|
+
applied: outcome.done,
|
|
75
|
+
failed: 0,
|
|
76
|
+
failedIds: [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (!outcome.cancelled && outcome.done === 0) {
|
|
80
|
+
return { ...NOTHING_STARTED, state: "commitFailed" };
|
|
81
|
+
}
|
|
82
|
+
// The run stopped part-way. Nothing here was rejected: a returned bulk call
|
|
83
|
+
// accepts every id in it, so the only failure this layer sees is a call that
|
|
84
|
+
// threw or was aborted, and everything after it was never sent. A bounded run
|
|
85
|
+
// hands back exactly those ids; a predicate run re-resolves its match on every
|
|
86
|
+
// pass and has no remainder to hand back, so what is left is the difference
|
|
87
|
+
// between the count and what the run covered.
|
|
88
|
+
const unreached =
|
|
89
|
+
outcome.failedIds.length > 0
|
|
90
|
+
? outcome.failedIds.length
|
|
91
|
+
: Math.max(matched - outcome.done, 1);
|
|
92
|
+
return {
|
|
93
|
+
state: "runStopped",
|
|
94
|
+
matched: Math.max(matched, outcome.done),
|
|
95
|
+
applied: outcome.done,
|
|
96
|
+
failed: unreached,
|
|
97
|
+
failedIds: outcome.failedIds,
|
|
98
|
+
};
|
|
99
|
+
};
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* These mount the real hook against the real fetch seam and count the batches
|
|
8
8
|
* that actually left, so what is pinned is the run's own lifetime rather than
|
|
9
|
-
* the wording of whichever screen started it. Every run is parked
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* the wording of whichever screen started it. Every run is parked on a request
|
|
10
|
+
* the server is holding before the test acts on it: that is where leaving the
|
|
11
|
+
* selection and stopping the run tell each other apart.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import assert from "node:assert/strict";
|
|
@@ -32,8 +32,12 @@ let server: MailServer | undefined;
|
|
|
32
32
|
interface MailServer {
|
|
33
33
|
/** Every message id a delete call carried, in the order they were sent. */
|
|
34
34
|
deleted: () => string[];
|
|
35
|
-
/**
|
|
35
|
+
/** Requests queued at the boundary rather than answered. */
|
|
36
36
|
held: () => number;
|
|
37
|
+
/** Requests the client cancelled while they were held. */
|
|
38
|
+
aborted: () => number;
|
|
39
|
+
/** Hold the count request too, so a stop can be pressed during counting. */
|
|
40
|
+
holdCounts: (on: boolean) => void;
|
|
37
41
|
/** Answer everything held, and stop holding. */
|
|
38
42
|
release: () => void;
|
|
39
43
|
restore: () => void;
|
|
@@ -61,14 +65,33 @@ const searchPage = (url: URL): unknown => {
|
|
|
61
65
|
|
|
62
66
|
/**
|
|
63
67
|
* Answers the search and the bulk delete, holding every delete after the first
|
|
64
|
-
* so a run can be caught mid-flight with real progress behind it.
|
|
68
|
+
* so a run can be caught mid-flight with real progress behind it. A held
|
|
69
|
+
* request honours its own abort signal, the way the platform's `fetch` does —
|
|
70
|
+
* that is the difference a stop is now supposed to make (#113).
|
|
65
71
|
*/
|
|
66
72
|
const startMailServer = (): MailServer => {
|
|
67
73
|
const original = globalThis.fetch;
|
|
68
74
|
const deleted: string[] = [];
|
|
69
75
|
let waiting: Array<() => void> = [];
|
|
70
76
|
let holding = true;
|
|
77
|
+
let holdingCounts = false;
|
|
71
78
|
let served = 0;
|
|
79
|
+
let cancelled = 0;
|
|
80
|
+
|
|
81
|
+
const hold = (signal: AbortSignal | undefined): Promise<void> =>
|
|
82
|
+
new Promise<void>((resolve, reject) => {
|
|
83
|
+
const fail = () => {
|
|
84
|
+
cancelled += 1;
|
|
85
|
+
waiting = waiting.filter((held) => held !== resolve);
|
|
86
|
+
reject(signal?.reason);
|
|
87
|
+
};
|
|
88
|
+
if (signal?.aborted) {
|
|
89
|
+
fail();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
signal?.addEventListener("abort", fail, { once: true });
|
|
93
|
+
waiting.push(resolve);
|
|
94
|
+
});
|
|
72
95
|
|
|
73
96
|
globalThis.fetch = (async (input: RequestInfo | URL): Promise<Response> => {
|
|
74
97
|
const request = input instanceof Request ? input : undefined;
|
|
@@ -76,6 +99,9 @@ const startMailServer = (): MailServer => {
|
|
|
76
99
|
const url = new URL(request ? request.url : String(input), "http://x");
|
|
77
100
|
|
|
78
101
|
if (url.pathname.endsWith("/threads/search")) {
|
|
102
|
+
if (holdingCounts && url.searchParams.get("results") === "false") {
|
|
103
|
+
await hold(request?.signal);
|
|
104
|
+
}
|
|
79
105
|
return Response.json(searchPage(url));
|
|
80
106
|
}
|
|
81
107
|
|
|
@@ -84,7 +110,7 @@ const startMailServer = (): MailServer => {
|
|
|
84
110
|
: undefined;
|
|
85
111
|
served += 1;
|
|
86
112
|
if (holding && served > 1) {
|
|
87
|
-
await
|
|
113
|
+
await hold(request?.signal);
|
|
88
114
|
}
|
|
89
115
|
deleted.push(...(body?.messageIds ?? []));
|
|
90
116
|
return Response.json({ successCount: 0, failureCount: 0 });
|
|
@@ -93,8 +119,13 @@ const startMailServer = (): MailServer => {
|
|
|
93
119
|
return {
|
|
94
120
|
deleted: () => deleted,
|
|
95
121
|
held: () => waiting.length,
|
|
122
|
+
aborted: () => cancelled,
|
|
123
|
+
holdCounts: (on) => {
|
|
124
|
+
holdingCounts = on;
|
|
125
|
+
},
|
|
96
126
|
release: () => {
|
|
97
127
|
holding = false;
|
|
128
|
+
holdingCounts = false;
|
|
98
129
|
const queued = waiting;
|
|
99
130
|
waiting = [];
|
|
100
131
|
for (const resolve of queued) resolve();
|
|
@@ -235,6 +266,56 @@ describe("an escalated run the user walks away from", () => {
|
|
|
235
266
|
});
|
|
236
267
|
});
|
|
237
268
|
|
|
269
|
+
// Regression for #113: a stop used to be read only between pages, so the delete
|
|
270
|
+
// already on the wire ran to completion and its whole page — up to a hundred
|
|
271
|
+
// messages — was deleted after the press.
|
|
272
|
+
describe("stopping a run with a delete already on the wire", () => {
|
|
273
|
+
it("cancels that delete and sends nothing more", async () => {
|
|
274
|
+
const { hook } = mountEscalation({ query: "npm" });
|
|
275
|
+
await escalateTo(hook);
|
|
276
|
+
|
|
277
|
+
const run = press(() => hook().runAction({ kind: "delete" }));
|
|
278
|
+
await parkAtBoundary();
|
|
279
|
+
const sentBeforeStop = server?.deleted().length ?? 0;
|
|
280
|
+
press(() => hook().stop());
|
|
281
|
+
press(() => server?.release());
|
|
282
|
+
await settle();
|
|
283
|
+
const outcome = await run;
|
|
284
|
+
|
|
285
|
+
assert.equal(server?.aborted(), 1, "the delete in flight was cancelled");
|
|
286
|
+
assert.equal(
|
|
287
|
+
server?.deleted().length,
|
|
288
|
+
sentBeforeStop,
|
|
289
|
+
"no further ids reached the server",
|
|
290
|
+
);
|
|
291
|
+
assert.equal(outcome.cancelled, true);
|
|
292
|
+
assert.equal(
|
|
293
|
+
outcome.error,
|
|
294
|
+
undefined,
|
|
295
|
+
"a stop the user pressed is not a failure to report",
|
|
296
|
+
);
|
|
297
|
+
assert.equal(outcome.done, sentBeforeStop);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("cancels a count in flight without raising an error", async () => {
|
|
301
|
+
server?.holdCounts(true);
|
|
302
|
+
const { hook } = mountEscalation({ query: "npm" });
|
|
303
|
+
|
|
304
|
+
press(() => hook().escalate());
|
|
305
|
+
await parkAtBoundary();
|
|
306
|
+
press(() => hook().stop());
|
|
307
|
+
press(() => server?.release());
|
|
308
|
+
await settle();
|
|
309
|
+
|
|
310
|
+
assert.equal(server?.aborted(), 1);
|
|
311
|
+
assert.deepEqual(hook().phase, { kind: "idle" });
|
|
312
|
+
assert.equal(
|
|
313
|
+
harness?.text().includes("Couldn't count matching messages"),
|
|
314
|
+
false,
|
|
315
|
+
);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
238
319
|
describe("stopping the run, which is a different press", () => {
|
|
239
320
|
it("ends it at the next page boundary and says what it reached", async () => {
|
|
240
321
|
const { hook } = mountEscalation({ query: "npm" });
|