import { describe, test, expect } from "vitest"; import { createRenderStream, disableActEnvironment, renderHookToSnapshotStream, } from "@testing-library/react-render-stream"; import { InMemoryCache, gql, type TypedDocumentNode } from "@apollo/client"; import { createHydrationUtils } from "../createHydrationUtils.js"; import { reactive } from "../reactive.js"; import { eachHostEnv, mockApplicationManifest, parseManifestOperation, spyOnConsole, } from "../../testing/internal/index.js"; import { ApolloProvider } from "../ApolloProvider.js"; import { StrictMode } from "react"; import assert from "node:assert"; const PRODUCTS_QUERY: TypedDocumentNode< { products: Array<{ __typename: "Product"; id: string }> }, { category: string; page: number; sortBy: string } > = gql` query Products($category: String!, $page: Int!, $sortBy: String!) @tool(name: "GetProductsByCategory") { products(category: $category, page: $page, sortBy: $sortBy) { id } } `; eachHostEnv((setupHost, ApolloClient) => { test("returns tool input value when tool matches", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); await expect(takeSnapshot).not.toRerender(); }); test("returns tool input value when @tool name is defined by operation name", async () => { using _ = spyOnConsole("debug"); const query: TypedDocumentNode< { products: Array<{ __typename: "Product"; id: string }> }, { category: string; page: number; sortBy: string } > = gql` "Get products by category" query Products($category: String!, $page: Int!, $sortBy: String!) @tool { products(category: $category, page: $page, sortBy: $sortBy) { id } } `; const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest({ operations: [parseManifestOperation(query)], }), }); using env = await setupHost({ client, toolCall: { name: "Products", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(query); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); await expect(takeSnapshot).not.toRerender(); }); test("returns user-provided variables when tool name does not match", async () => { using _ = spyOnConsole("debug"); const query = gql` query Product($id: ID!) @tool(name: "OtherTool", description: "A different tool") { product(id: $id) { id title __typename } } `; const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest({ operations: [parseManifestOperation(query)], }), }); using env = await setupHost({ client, toolCall: { name: "OtherTool", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { product: null } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "music", page: 1, sortBy: "name", }); await expect(takeSnapshot).not.toRerender(); }); test("does not add user-provided variables not in document's variable definitions when tool matches", async () => { using _ = spyOnConsole("debug"); const query: TypedDocumentNode = gql` query GetProduct($id: ID!) @tool(name: "GetProduct") { product(id: $id) { id } } `; const { useHydratedVariables } = createHydrationUtils(query); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProduct", input: { id: "1" }, result: { structuredContent: { result: { data: { product: null } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ id: "default", notInDoc: true } as any), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ id: "1" }); await expect(takeSnapshot).not.toRerender(); }); test("filters user-provided variables not in document's variable definitions when tool does not match", async () => { using _ = spyOnConsole("debug"); const query: TypedDocumentNode = gql` query GetProduct($id: ID!) @tool(name: "GetProduct") { product(id: $id) { id } } `; const { useHydratedVariables } = createHydrationUtils(query); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "OtherTool", input: { id: "1" }, result: { structuredContent: { result: { data: { product: null } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ id: "default", notInDoc: true } as any), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ id: "default" }); await expect(takeSnapshot).not.toRerender(); }); test("setVariables shallow-merges a partial update", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [initialVariables, setVariables] = await takeSnapshot(); expect(initialVariables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); setVariables({ page: 2 }); const [updatedVariables] = await takeSnapshot(); expect(updatedVariables).toStrictEqual({ category: "electronics", page: 2, sortBy: "title", }); await expect(takeSnapshot).not.toRerender(); }); test("setVariables supports updater function", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [initialVariables, setVariables] = await takeSnapshot(); expect(initialVariables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); setVariables((prev) => ({ page: prev.page + 1 })); const [updatedVariables] = await takeSnapshot(); expect(updatedVariables).toStrictEqual({ category: "electronics", page: 2, sortBy: "title", }); await expect(takeSnapshot).not.toRerender(); }); test("setVariables ignores reactive variable keys (object form)", async () => { using _ = spyOnConsole("debug", "warn"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: reactive("music"), page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [initialVariables, setVariables] = await takeSnapshot(); expect(initialVariables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); // @ts-expect-error category is reactive setVariables({ category: "sports" }); expect(console.warn).toHaveBeenCalledWith( expect.stringContaining('Attempted to set reactive variable "category"') ); await expect(takeSnapshot).not.toRerender(); }); test("setVariables ignores reactive variable keys (callback form)", async () => { using _ = spyOnConsole("debug", "warn"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: reactive("music"), page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [initialVariables, setVariables] = await takeSnapshot(); expect(initialVariables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); // @ts-expect-error category is reactive setVariables(() => ({ category: "sports" })); expect(console.warn).toHaveBeenCalledWith( expect.stringContaining('Attempted to set reactive variable "category"') ); await expect(takeSnapshot).not.toRerender(); }); test("state variable is not reset when component re-renders with new input", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot, getCurrentSnapshot, rerender } = await renderHookToSnapshotStream( ({ sortBy }) => useHydratedVariables({ category: "music", page: 1, sortBy, }), { initialProps: { sortBy: "name" }, wrapper: ({ children }) => ( {children} ), } ); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); } const [, setVariables] = getCurrentSnapshot(); setVariables({ sortBy: "price" }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1, sortBy: "price", }); } rerender({ sortBy: "ignored" }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1, sortBy: "price", }); } await expect(takeSnapshot).not.toRerender(); }); test("returns tool input value for reactive variable when tool matches", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: reactive("music"), page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); await expect(takeSnapshot).not.toRerender(); }); test("reactive variable follows the provided value after it updates", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot, rerender } = await renderHookToSnapshotStream( ({ category }) => useHydratedVariables({ category: reactive(category), page: 1, sortBy: "name", }), { initialProps: { category: "default" }, wrapper: ({ children }) => ( {children} ), } ); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); } rerender({ category: "music" }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "music", page: 1, sortBy: "title", }); } rerender({ category: "sports" }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "sports", page: 1, sortBy: "title", }); } rerender({ category: "default" }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "default", page: 1, sortBy: "title", }); } await expect(takeSnapshot).not.toRerender(); }); test("reactive variable returns provided value when tool name does not match", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest({ operations: [ { ...mockApplicationManifest().operations[0], tools: [{ name: "OtherTool", description: "A different tool" }], }, ], }), }); using env = await setupHost({ client, toolCall: { name: "OtherTool", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { product: null } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: reactive("music"), page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "music", page: 1, sortBy: "name", }); await expect(takeSnapshot).not.toRerender(); }); test("optional state variable is omitted from result when tool input omits it", async () => { using _ = spyOnConsole("debug"); const query: TypedDocumentNode< any, { category: string; page?: number | null } > = gql` query OptionalProduct($category: String!, $page: Int) @tool(name: "GetProductsByCategory") { products(category: $category, page: $page) { id } } `; const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(query); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1 }), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics" }); await expect(takeSnapshot).not.toRerender(); }); test("optional reactive variable is omitted from result when tool input omits it, then appears once reactive prop changes", async () => { using _ = spyOnConsole("debug"); const query: TypedDocumentNode< unknown, { category: string; page?: number | null } > = gql` query OptionalProduct($category: String!, $page: Int) @tool(name: "GetProductsByCategory") { products(category: $category, page: $page) { id } } `; const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(query); using _disabledAct = disableActEnvironment(); const { takeSnapshot, rerender } = await renderHookToSnapshotStream( ({ page }) => useHydratedVariables({ category: "music", page: reactive(page), }), { initialProps: { page: 1 }, wrapper: ({ children }) => ( {children} ), } ); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics" }); } rerender({ page: 2 }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 2 }); } await expect(takeSnapshot).not.toRerender(); }); test("handles optional variables initially omitted from user-variables when tool input matches", async () => { using _ = spyOnConsole("debug"); const query: TypedDocumentNode< any, { category: string; page?: number | null } > = gql` query OptionalProduct($category: String!, $page: Int) @tool(name: "GetProductsByCategory") { products(category: $category, page: $page) { id } } `; const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1 }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(query); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music" }), { wrapper: ({ children }) => ( {children} ), } ); const [variables, setVariables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 1 }); setVariables({ page: 2 }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 2, }); } await expect(takeSnapshot).not.toRerender(); }); test("handles optional variables initially omitted from user-variables when tool input doesn't match", async () => { using _ = spyOnConsole("debug"); const query: TypedDocumentNode< any, { category: string; page?: number | null } > = gql` query OptionalProduct($category: String!, $page: Int) @tool(name: "GetProductsByCategory") { products(category: $category, page: $page) { id } } `; const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "OtherTool", input: { category: "electronics", page: 1 }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(query); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music" }), { wrapper: ({ children }) => ( {children} ), } ); const [variables, setVariables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "music" }); setVariables({ page: 2 }); { const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "music", page: 2, }); } await expect(takeSnapshot).not.toRerender(); }); test("returned variables are referentially stable between re-renders when nothing changes", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot, rerender } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [initialVariables] = await takeSnapshot(); expect(initialVariables).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); rerender(); { const [variables] = await takeSnapshot(); expect(variables).toBe(initialVariables); } rerender(); { const [variables] = await takeSnapshot(); expect(variables).toBe(initialVariables); } await expect(takeSnapshot).not.toRerender(); }); test("state variable is hydrated when tool name matches one of multiple @tool directives", async () => { using _ = spyOnConsole("debug"); const MULTI_TOOL_QUERY: TypedDocumentNode< unknown, { category: string; page: number; sortBy: string } > = gql` query Products($category: String!, $page: Int!, $sortBy: String!) @tool(name: "GetProducts") @tool(name: "GetProductsAlt") { products(category: $category, page: $page, sortBy: $sortBy) { id } } `; const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsAlt", input: { category: "electronics", page: 2, sortBy: "price" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(MULTI_TOOL_QUERY); using _disabledAct = disableActEnvironment(); const { takeSnapshot } = await renderHookToSnapshotStream( () => useHydratedVariables({ category: "music", page: 1, sortBy: "name", }), { wrapper: ({ children }) => ( {children} ), } ); const [variables] = await takeSnapshot(); expect(variables).toStrictEqual({ category: "electronics", page: 2, sortBy: "price", }); await expect(takeSnapshot).not.toRerender(); }); test("hydrated variables are only used the first time the component mounts, then uses user vars after", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { replaceSnapshot, render, takeRender } = createRenderStream< ReturnType | undefined >(); function HydratedVariables() { replaceSnapshot( useHydratedVariables({ category: "music", page: 1, sortBy: "name" }) ); return null; } function App({ mounted }: { mounted: boolean }) { replaceSnapshot(undefined); return mounted && ; } const { rerender } = await render(, { wrapper: ({ children }) => ( {children} ), }); { const { snapshot } = await takeRender(); assert(snapshot); expect(snapshot[0]).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); } await rerender(); { const { snapshot } = await takeRender(); expect(snapshot).toBeUndefined(); } await rerender(); { const { snapshot } = await takeRender(); assert(snapshot); expect(snapshot[0]).toStrictEqual({ category: "music", page: 1, sortBy: "name", }); } expect(takeRender).not.toRerender(); }); test("hydrated variables are only used the first time the component mounts, then uses user vars after in React strict mode", async () => { using _ = spyOnConsole("debug"); const client = new ApolloClient({ cache: new InMemoryCache(), manifest: mockApplicationManifest(), }); using env = await setupHost({ client, toolCall: { name: "GetProductsByCategory", input: { category: "electronics", page: 1, sortBy: "title" }, result: { structuredContent: { result: { data: { products: [] } } } }, }, }); const { host, params } = env; host.sendToolInput(params.toolInput); host.sendToolResult(params.toolResult); const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); using _disabledAct = disableActEnvironment(); const { replaceSnapshot, render, takeRender } = createRenderStream< ReturnType | undefined >(); function HydratedVariables() { replaceSnapshot( useHydratedVariables({ category: "music", page: 1, sortBy: "name" }) ); return null; } function App({ mounted }: { mounted: boolean }) { replaceSnapshot(undefined); return mounted && ; } const { rerender } = await render(, { wrapper: ({ children }) => ( {children} ), }); { const { snapshot } = await takeRender(); assert(snapshot); expect(snapshot[0]).toStrictEqual({ category: "electronics", page: 1, sortBy: "title", }); } await rerender(); { const { snapshot } = await takeRender(); expect(snapshot).toBeUndefined(); } await rerender(); { const { snapshot } = await takeRender(); assert(snapshot); expect(snapshot[0]).toStrictEqual({ category: "music", page: 1, sortBy: "name", }); } expect(takeRender).not.toRerender(); }); }); describe.skip("type tests", () => { test("TypeScript rejects variables not defined in TVariables", () => { const { useHydratedVariables } = createHydrationUtils(PRODUCTS_QUERY); useHydratedVariables({ category: "test", page: 1, sortBy: "asc", // @ts-expect-error extra key 'unknownVar' is not in TVariables unknownVar: "bad", }); }); });