import { describe, it, expect } from "vitest";
import { act } from "react";
import type { ServerEntry } from "@inspector/core/mcp/types.js";
import { renderWithMantine } from "../test/renderWithMantine";
import {
usePaginatedListsOverride,
type PaginatedListsOverride,
} from "./usePaginatedListsOverride";
/**
* One `servers` entry, a fresh object per call — every test here turns on
* object identity, so a shared fixture would defeat them. A list rebuilt from
* fresh calls is what a successful `GET /api/servers` produces.
*/
const entry = (id: string): ServerEntry => ({
id,
name: id,
config: { type: "stdio", command: "node" },
connection: { status: "disconnected" },
});
interface Harness {
/** The hook's API as of the latest render. */
api: () => PaginatedListsOverride;
/** Deliver a new server list, as a successful list read would. */
setServers: (next: ServerEntry[]) => void;
}
function harness(initial: ServerEntry[]): Harness {
let latest: PaginatedListsOverride | undefined;
function Probe({ servers }: { servers: ServerEntry[] }) {
latest = usePaginatedListsOverride(servers);
return null;
}
const { rerender } = renderWithMantine();
return {
api: () => {
if (!latest) throw new Error("hook did not render");
return latest;
},
setServers: (next) => {
act(() => rerender());
},
};
}
describe("usePaginatedListsOverride", () => {
it("reports no override before anything is recorded", () => {
const { api } = harness([entry("A")]);
expect(api().valueFor("A")).toBeUndefined();
});
it("reports nothing for an undefined server id", () => {
// No server is active before the first connect, and the composition root
// asks for the display value on every render regardless.
const { api } = harness([entry("A")]);
act(() => api().record("A", true));
expect(api().valueFor(undefined)).toBeUndefined();
});
it("returns what was recorded for that server", () => {
const { api } = harness([entry("A")]);
act(() => api().record("A", true));
expect(api().valueFor("A")).toBe(true);
});
it("does not apply one server's override to another", () => {
// The override is the *display* value, and the settings modal can be saved
// for a server that is not the connected one — so a record made for B must
// not change what A renders.
const { api } = harness([entry("A"), entry("B")]);
act(() => api().record("B", true));
expect(api().valueFor("A")).toBeUndefined();
expect(api().valueFor("B")).toBe(true);
});
it("survives a server switch away and back (#2095)", () => {
// Nothing about the list changes here — only which server is being asked
// about, which is all a switch is. The previous app-wide slot was cleared
// on every change of the active entry, which is what dropped A's value.
const { api } = harness([entry("A"), entry("B")]);
act(() => api().record("A", true));
expect(api().valueFor("B")).toBeUndefined();
expect(api().valueFor("A")).toBe(true);
});
it("keeps a record for each server", () => {
const { api } = harness([entry("A"), entry("B")]);
act(() => api().record("A", true));
act(() => api().record("B", false));
expect(api().valueFor("A")).toBe(true);
expect(api().valueFor("B")).toBe(false);
});
it("replaces its own record for the same server", () => {
const { api } = harness([entry("A")]);
act(() => api().record("A", true));
act(() => api().record("A", false));
expect(api().valueFor("A")).toBe(false);
});
it("stops applying once a fresh list read replaces that entry", () => {
const { api, setServers } = harness([entry("A")]);
act(() => api().record("A", true));
// A successful `GET /api/servers` rebuilds the list, so the entry is a new
// object even when the values are unchanged — which is exactly the case
// that has to supersede the override, since an edit made outside the
// Inspector can report back the value the override replaced.
setServers([entry("A")]);
expect(api().valueFor("A")).toBeUndefined();
});
it("keeps a record whose own entry was not re-read", () => {
// A read that rebuilds B says nothing about A. In practice the list is
// rebuilt wholesale, but the check is per entry and must not be widened to
// "any list change" — a reorder replaces the array while keeping every
// entry object, and is not evidence of a fresher read.
const a = entry("A");
const { api, setServers } = harness([a, entry("B")]);
act(() => api().record("A", true));
setServers([entry("B"), a]);
expect(api().valueFor("A")).toBe(true);
});
it("records against the list as it stands when the record is written", () => {
// A rollback records from a continuation that may be several renders old;
// pairing with the list captured then would make the record look superseded
// the moment it was written.
const { api, setServers } = harness([entry("A")]);
const stale = api();
setServers([entry("A")]);
act(() => stale.record("A", true));
expect(api().valueFor("A")).toBe(true);
});
it("drops superseded records rather than accumulating them", () => {
// Housekeeping, asserted through the only surface it has: a record dropped
// on the way past cannot come back, so re-reading after the entry is
// restored to the list still reports nothing.
const a = entry("A");
const { api, setServers } = harness([a, entry("B")]);
act(() => api().record("A", true));
setServers([entry("A"), entry("B")]);
act(() => api().record("B", true));
setServers([a, entry("B")]);
expect(api().valueFor("A")).toBeUndefined();
});
});