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",
});
});
});