Merge pull request #2039 from modelcontextprotocol/v2/chore/1959-no-floating-promises
chore(lint): enable @typescript-eslint/no-floating-promises in all five ESLint scopes
This commit is contained in:
commit
405d0f4d0d
2 changed files with 656 additions and 113 deletions
|
|
@ -706,6 +706,91 @@ describe("App (foundation)", () => {
|
|||
expect(h.disconnect).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("surfaces a disconnect failure instead of floating the rejection", async () => {
|
||||
// 'd' is a key handler, so it cannot await handleDisconnect — the handler
|
||||
// has to own the failure itself or it escapes as an unhandled rejection
|
||||
// and fails the whole run from somewhere else (#1959).
|
||||
// The banner is deliberately independent of connection status: a rejected
|
||||
// disconnect leaves the status "connected", so anything gated on
|
||||
// `status === "error"` would never be seen.
|
||||
h.ctrl.status = "connected";
|
||||
h.disconnect.mockRejectedValue(new Error("discfail"));
|
||||
const r = await mount(oneStdio());
|
||||
r.stdin.write("d");
|
||||
await expectFrame(r, "Disconnect failed: discfail");
|
||||
});
|
||||
|
||||
it("owns a non-Error disconnect rejection too", async () => {
|
||||
// The catch stringifies a non-Error rejection rather than reading
|
||||
// `.message` off it; a throw here would escape the same way.
|
||||
h.ctrl.status = "connected";
|
||||
h.disconnect.mockRejectedValue("plainstring");
|
||||
const r = await mount(oneStdio());
|
||||
r.stdin.write("d");
|
||||
await expectFrame(r, "Disconnect failed: plainstring");
|
||||
});
|
||||
|
||||
it("clears a disconnect failure once a retry succeeds", async () => {
|
||||
// A stale banner would keep reporting a failure the user has since fixed.
|
||||
h.ctrl.status = "connected";
|
||||
h.disconnect.mockRejectedValueOnce(new Error("discfail"));
|
||||
const r = await mount(oneStdio());
|
||||
r.stdin.write("d");
|
||||
await expectFrame(r, "Disconnect failed: discfail");
|
||||
r.stdin.write("d");
|
||||
await waitUntil(() => !(r.lastFrame() ?? "").includes("Disconnect failed"));
|
||||
expect(r.lastFrame() ?? "").not.toContain("Disconnect failed");
|
||||
});
|
||||
|
||||
it("drops a disconnect rejection that lands after the user switched servers", async () => {
|
||||
// The stale attempt is the one that usually rejects, so without an
|
||||
// attempt token server alpha's failure would surface in beta's header.
|
||||
h.ctrl.status = "connected";
|
||||
let rejectDisconnect: (err: Error) => void = () => {};
|
||||
h.disconnect.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise<void>((_resolve, reject) => {
|
||||
rejectDisconnect = reject;
|
||||
}),
|
||||
);
|
||||
const r = await mount(stdioServer());
|
||||
r.stdin.write("d"); // alpha's disconnect starts, and hangs
|
||||
await tick();
|
||||
await press(r, [DOWN]); // switch to beta
|
||||
await expectFrame(r, "beta");
|
||||
rejectDisconnect(new Error("stale-alpha-failure"));
|
||||
// Give the rejection every chance to be published before asserting it
|
||||
// wasn't — a bare tick would pass even with the guard removed.
|
||||
await waitUntil(() =>
|
||||
(r.lastFrame() ?? "").includes("stale-alpha-failure"),
|
||||
);
|
||||
expect(r.lastFrame() ?? "").not.toContain("stale-alpha-failure");
|
||||
});
|
||||
|
||||
it("drops a stale disconnect rejection across an A → B → A round trip", async () => {
|
||||
// The server-name check alone passes here: by the time the rejection
|
||||
// lands, alpha is selected again. Only retiring the attempt token on
|
||||
// every switch catches it.
|
||||
h.ctrl.status = "connected";
|
||||
let rejectDisconnect: (err: Error) => void = () => {};
|
||||
h.disconnect.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise<void>((_resolve, reject) => {
|
||||
rejectDisconnect = reject;
|
||||
}),
|
||||
);
|
||||
const r = await mount(stdioServer());
|
||||
r.stdin.write("d"); // alpha's disconnect starts, and hangs
|
||||
await tick();
|
||||
await press(r, [DOWN]); // alpha -> beta
|
||||
await expectFrame(r, "b.js");
|
||||
await press(r, [UP]); // beta -> alpha again
|
||||
await expectFrame(r, "s.js");
|
||||
rejectDisconnect(new Error("round-trip-failure"));
|
||||
await waitUntil(() => (r.lastFrame() ?? "").includes("round-trip-failure"));
|
||||
expect(r.lastFrame() ?? "").not.toContain("round-trip-failure");
|
||||
});
|
||||
|
||||
it("switches tabs via accelerator keys", async () => {
|
||||
const r = await mount(stdioServer());
|
||||
await press(r, ["t"]); // tools tab (server is auto-selected)
|
||||
|
|
|
|||
|
|
@ -207,7 +207,9 @@ describe("tui tsup config", () => {
|
|||
// the double cast is bridging a real structural gap, not hiding a mismatch.
|
||||
// A hook the patch called but the stub lacks fails as undefined here rather
|
||||
// than passing silently.
|
||||
inkFormLabelPatch.setup(
|
||||
// esbuild types `setup` as `void | Promise<void>`; await it so the
|
||||
// assertions below can't run before an async setup has registered.
|
||||
await inkFormLabelPatch.setup(
|
||||
build as unknown as Parameters<typeof inkFormLabelPatch.setup>[0],
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue