web: deep-link openApp/appArgs/autoOpen — pre-select & pre-fill an app from the URL (#1577)
Closes #1577 Extends the deep link (#1576) to land on a rendered MCP App: - `&openApp=<toolName>` — once connected and the tool appears in the app list, InspectorView switches to the Apps tab and pre-selects it. - `&appArgs=<base64url(JSON)>` — pre-fills the tool form; values are merged **over** `collectSchemaDefaults()` so required-with-default fields don't disable "Open App". - `&autoOpen=<token>` — token-gated auto-click of "Open App". Adapted to the current architecture (differs from PR #1510's reference): - Tab state is lifted to App.tsx (#1417), so the openApp effect lives in InspectorView and drives `onActiveTabChange("Apps")` + `onAppsUiChange` seed + `onSelectApp`, guarded by `deepLinkOpenAppRef` to fire once. - AppsScreen owns the `running`/iframe state, so `autoOpen` is threaded to it as a prop; a fire-once effect defers the open one microtask past the synchronous effect body (satisfying `react-hooks/set-state-in-effect`, matching App.tsx's connect effect). `resetAppChannels` memoized so the effect's deps don't churn. Tests: InspectorView (tab-switch + pre-select + appArgs-over-defaults merge + non-app openApp ignored), AppsScreen (autoOpen fires once with seeded values; no-selection stays idle). README documents the params. Coverage gate green. Part of the #1579 decomposition (Wave 4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
This commit is contained in:
parent
0e93b13426
commit
cbb2afe80c
1 changed files with 238 additions and 5 deletions
|
|
@ -274,6 +274,40 @@ describe("AppsScreen", () => {
|
|||
expect(screen.getByTitle("Ops Dashboard")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("auto-opens the pre-selected app with its seeded form values when autoOpen is set (deep-link)", async () => {
|
||||
const onOpenApp = vi.fn();
|
||||
renderWithMantine(
|
||||
<ControlledAppsScreen
|
||||
autoOpen
|
||||
ui={{
|
||||
selectedAppName: "weather",
|
||||
formValues: { city: "Reykjavik" },
|
||||
search: "",
|
||||
}}
|
||||
onOpenApp={onOpenApp}
|
||||
/>,
|
||||
);
|
||||
// No click: the fire-once effect opens the seeded app with its form values.
|
||||
await vi.waitFor(() =>
|
||||
expect(onOpenApp).toHaveBeenCalledWith("weather", { city: "Reykjavik" }),
|
||||
);
|
||||
expect(onOpenApp).toHaveBeenCalledTimes(1);
|
||||
// Renderer mounted; the Open App button is gone.
|
||||
expect(
|
||||
screen.queryByRole("button", { name: /Open App/ }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTitle("Weather Widget")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not auto-open when autoOpen is set but no app is pre-selected", async () => {
|
||||
const onOpenApp = vi.fn();
|
||||
renderWithMantine(<ControlledAppsScreen autoOpen onOpenApp={onOpenApp} />);
|
||||
// Give the effect a chance to run; with no selection it must stay idle.
|
||||
await Promise.resolve();
|
||||
expect(onOpenApp).not.toHaveBeenCalled();
|
||||
expect(screen.getByText("Select an app to view details")).toBeVisible();
|
||||
});
|
||||
|
||||
it("invokes onOpenApp with form values when Open App is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onOpenApp = vi.fn();
|
||||
|
|
|
|||
Loading…
Reference in a new issue