@tambo-ai/react 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/use-component-state.js +0 -1
- package/dist/hooks/use-component-state.js.map +1 -1
- package/dist/mcp/mcp-client.d.ts +15 -15
- package/dist/providers/__tests__/tambo-prop-stream-provider.test.js +144 -224
- package/dist/providers/__tests__/tambo-prop-stream-provider.test.js.map +1 -1
- package/dist/providers/__tests__/tambo-thread-provider.test.js +7 -7
- package/dist/providers/__tests__/tambo-thread-provider.test.js.map +1 -1
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +4 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/tambo-prop-stream-provider/index.d.ts +19 -0
- package/dist/providers/tambo-prop-stream-provider/index.d.ts.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/index.js +43 -0
- package/dist/providers/tambo-prop-stream-provider/index.js.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/pending.d.ts +12 -0
- package/dist/providers/tambo-prop-stream-provider/pending.d.ts.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/pending.js +31 -0
- package/dist/providers/tambo-prop-stream-provider/pending.js.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/provider.d.ts +17 -0
- package/dist/providers/tambo-prop-stream-provider/provider.d.ts.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/provider.js +107 -0
- package/dist/providers/tambo-prop-stream-provider/provider.js.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/streaming.d.ts +12 -0
- package/dist/providers/tambo-prop-stream-provider/streaming.d.ts.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/streaming.js +28 -0
- package/dist/providers/tambo-prop-stream-provider/streaming.js.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/success.d.ts +12 -0
- package/dist/providers/tambo-prop-stream-provider/success.d.ts.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/success.js +28 -0
- package/dist/providers/tambo-prop-stream-provider/success.js.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/types.d.ts +25 -0
- package/dist/providers/tambo-prop-stream-provider/types.d.ts.map +1 -0
- package/dist/providers/tambo-prop-stream-provider/types.js +6 -0
- package/dist/providers/tambo-prop-stream-provider/types.js.map +1 -0
- package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-provider.js +3 -9
- package/dist/providers/tambo-thread-provider.js.map +1 -1
- package/esm/hooks/use-component-state.js +0 -1
- package/esm/hooks/use-component-state.js.map +1 -1
- package/esm/mcp/mcp-client.d.ts +15 -15
- package/esm/providers/__tests__/tambo-prop-stream-provider.test.js +144 -224
- package/esm/providers/__tests__/tambo-prop-stream-provider.test.js.map +1 -1
- package/esm/providers/__tests__/tambo-thread-provider.test.js +7 -7
- package/esm/providers/__tests__/tambo-thread-provider.test.js.map +1 -1
- package/esm/providers/index.d.ts +2 -2
- package/esm/providers/index.d.ts.map +1 -1
- package/esm/providers/index.js +1 -1
- package/esm/providers/index.js.map +1 -1
- package/esm/providers/tambo-prop-stream-provider/index.d.ts +19 -0
- package/esm/providers/tambo-prop-stream-provider/index.d.ts.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/index.js +22 -0
- package/esm/providers/tambo-prop-stream-provider/index.js.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/pending.d.ts +12 -0
- package/esm/providers/tambo-prop-stream-provider/pending.d.ts.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/pending.js +24 -0
- package/esm/providers/tambo-prop-stream-provider/pending.js.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/provider.d.ts +17 -0
- package/esm/providers/tambo-prop-stream-provider/provider.d.ts.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/provider.js +70 -0
- package/esm/providers/tambo-prop-stream-provider/provider.js.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/streaming.d.ts +12 -0
- package/esm/providers/tambo-prop-stream-provider/streaming.d.ts.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/streaming.js +21 -0
- package/esm/providers/tambo-prop-stream-provider/streaming.js.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/success.d.ts +12 -0
- package/esm/providers/tambo-prop-stream-provider/success.d.ts.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/success.js +21 -0
- package/esm/providers/tambo-prop-stream-provider/success.js.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/types.d.ts +25 -0
- package/esm/providers/tambo-prop-stream-provider/types.d.ts.map +1 -0
- package/esm/providers/tambo-prop-stream-provider/types.js +3 -0
- package/esm/providers/tambo-prop-stream-provider/types.js.map +1 -0
- package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-provider.js +3 -9
- package/esm/providers/tambo-thread-provider.js.map +1 -1
- package/package.json +7 -7
- package/dist/providers/tambo-prop-stream-provider.d.ts +0 -96
- package/dist/providers/tambo-prop-stream-provider.d.ts.map +0 -1
- package/dist/providers/tambo-prop-stream-provider.js +0 -185
- package/dist/providers/tambo-prop-stream-provider.js.map +0 -1
- package/esm/providers/tambo-prop-stream-provider.d.ts +0 -96
- package/esm/providers/tambo-prop-stream-provider.d.ts.map +0 -1
- package/esm/providers/tambo-prop-stream-provider.js +0 -148
- package/esm/providers/tambo-prop-stream-provider.js.map +0 -1
|
@@ -6,7 +6,6 @@ const react_1 = require("react");
|
|
|
6
6
|
const use_debounce_1 = require("use-debounce");
|
|
7
7
|
const providers_1 = require("../providers");
|
|
8
8
|
const use_current_message_1 = require("./use-current-message");
|
|
9
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
10
9
|
function useTamboComponentState(keyName, initialValue, debounceTime = 500) {
|
|
11
10
|
const { messageId } = (0, use_current_message_1.useTamboMessageContext)();
|
|
12
11
|
const { updateThreadMessage, thread } = (0, providers_1.useTamboThread)();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-component-state.js","sourceRoot":"","sources":["../../src/hooks/use-component-state.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { useTamboClient, useTamboThread } from \"../providers\";\nimport {\n useTamboCurrentMessage,\n useTamboMessageContext,\n} from \"./use-current-message\";\n// Define metadata interface for better extensibility\ninterface ComponentStateMeta {\n isPending: boolean;\n}\n\ntype StateUpdateResult<T> = [\n currentState: T,\n setState: (newState: T) => void,\n meta: ComponentStateMeta,\n];\n\n/**\n * A React hook that provides state management and passes user updates to Tambo.\n * Benefits: Passes user changes to AI, and when threads are returned, state is preserved.\n * @param keyName - The unique key to identify this state within the message's componentState object\n * @param initialValue - Optional initial value for the state, used if no value exists in the message\n * @param debounceTime - Optional debounce time in milliseconds (default: 300ms) to limit API calls\n * @returns A tuple containing:\n * - The current state value\n * - A setter function to update the state (updates UI immediately, debounces server sync)\n * - A metadata object with properties like isPending to track sync status\n * @example\n * // Basic usage\n * const [count, setCount, { isPending }] = useTamboComponentState(\"counter\", 0);\n *\n * // Usage with object state\n * const [formState, setFormState] = useTamboComponentState(\"myForm\", {\n * name: \"\",\n * email: \"\",\n * message: \"\"\n * });\n *\n * // Handling form input\n * const handleChange = (e) => {\n * setFormState({\n * ...formState,\n * [e.target.name]: e.target.value\n * });\n * };\n */\nexport function useTamboComponentState<S = undefined>(\n keyName: string,\n initialValue?: S,\n debounceTime?: number,\n): StateUpdateResult<S | undefined>;\nexport function useTamboComponentState<S>(\n keyName: string,\n initialValue: S,\n debounceTime?: number,\n): StateUpdateResult<S>;\n// eslint-disable-next-line jsdoc/require-jsdoc\nexport function useTamboComponentState<S>(\n keyName: string,\n initialValue?: S,\n debounceTime = 500,\n): StateUpdateResult<S> {\n const { messageId } = useTamboMessageContext();\n const { updateThreadMessage, thread } = useTamboThread();\n const client = useTamboClient();\n const message = useTamboCurrentMessage();\n const threadId = thread.id;\n\n // Initial value management\n const [cachedInitialValue] = useState(() => initialValue);\n // UI state management\n const [localState, setLocalState] = useState<S | undefined>(\n cachedInitialValue,\n );\n // Synchronization state\n const [isPending, setIsPending] = useState(false);\n // Track the last user-initiated value instead of a simple boolean flag\n const [lastUserValue, setLastUserValue] = useState<S | null>(null);\n const [haveInitialized, setHaveInitialized] = useState(false);\n\n // Determine if we need to initialize state\n const shouldInitialize =\n !haveInitialized &&\n message &&\n cachedInitialValue !== undefined &&\n (!message.componentState || !(keyName in message.componentState));\n\n // Sync local state with message state on initial load and when message changes\n useEffect(() => {\n if (message?.componentState && keyName in message.componentState) {\n const messageState = message.componentState[keyName] as S;\n\n // Only update local state if we haven't had any user changes yet\n if (lastUserValue === null) {\n setLocalState(messageState);\n }\n }\n // Otherwise fall back to initial value if we have one and no user changes\n else if (\n cachedInitialValue !== undefined &&\n !localState &&\n lastUserValue === null\n ) {\n setLocalState(cachedInitialValue);\n }\n }, [\n keyName,\n message?.componentState,\n cachedInitialValue,\n lastUserValue,\n localState,\n ]);\n\n // Create debounced save function for efficient server synchronization\n const debouncedServerWrite = useDebouncedCallback(async (newValue: S) => {\n setIsPending(true);\n try {\n const componentStateUpdate = {\n state: { [keyName]: newValue },\n };\n\n await client.beta.threads.messages.updateComponentState(\n threadId,\n messageId,\n componentStateUpdate,\n );\n } catch (err) {\n console.error(\n `Failed to save component state for key \"${keyName}\":`,\n err,\n );\n } finally {\n setIsPending(false);\n }\n }, debounceTime);\n\n // Initialize state on first render if needed\n const initializeState = useCallback(async () => {\n if (!message) {\n console.warn(\n `Cannot initialize state for missing message ${messageId} with key \"${keyName}\"`,\n );\n return;\n }\n\n try {\n const messageUpdate = {\n ...message,\n componentState: {\n ...message.componentState,\n [keyName]: cachedInitialValue,\n },\n };\n\n const componentStateUpdate = {\n state: { [keyName]: cachedInitialValue },\n };\n\n await Promise.all([\n updateThreadMessage(messageId, messageUpdate, false),\n client.beta.threads.messages.updateComponentState(\n threadId,\n messageId,\n componentStateUpdate,\n ),\n ]);\n } catch (err) {\n console.warn(\n `Failed to initialize component state for key \"${keyName}\":`,\n err,\n );\n }\n }, [\n cachedInitialValue,\n client.beta.threads.messages,\n keyName,\n message,\n messageId,\n threadId,\n updateThreadMessage,\n ]);\n\n // Send initial state when component mounts\n useEffect(() => {\n if (shouldInitialize) {\n initializeState();\n setHaveInitialized(true);\n }\n }, [initializeState, shouldInitialize]);\n\n // setValue function for updating state\n // Updates local state immediately and schedules debounced server sync\n const setValue = useCallback(\n (newValue: S) => {\n // Track this as a user-initiated update\n setLastUserValue(newValue);\n setLocalState(newValue);\n\n // Only trigger server updates if we have a message\n if (message) {\n debouncedServerWrite(newValue);\n const messageUpdate = {\n ...message,\n componentState: {\n ...message.componentState,\n [keyName]: newValue,\n },\n };\n\n updateThreadMessage(messageId, messageUpdate, false);\n } else {\n console.warn(\n `Cannot update server for missing message ${messageId} with key \"${keyName}\"`,\n );\n }\n },\n [message, debouncedServerWrite, keyName, updateThreadMessage, messageId],\n );\n\n // Ensure pending changes are flushed on unmount\n useEffect(() => {\n return () => {\n debouncedServerWrite.flush();\n };\n }, [debouncedServerWrite]);\n\n // Return the local state for immediate UI rendering\n return [localState as S, setValue, { isPending }];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-component-state.js","sourceRoot":"","sources":["../../src/hooks/use-component-state.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { useTamboClient, useTamboThread } from \"../providers\";\nimport {\n useTamboCurrentMessage,\n useTamboMessageContext,\n} from \"./use-current-message\";\n// Define metadata interface for better extensibility\ninterface ComponentStateMeta {\n isPending: boolean;\n}\n\ntype StateUpdateResult<T> = [\n currentState: T,\n setState: (newState: T) => void,\n meta: ComponentStateMeta,\n];\n\n/**\n * A React hook that provides state management and passes user updates to Tambo.\n * Benefits: Passes user changes to AI, and when threads are returned, state is preserved.\n * @param keyName - The unique key to identify this state within the message's componentState object\n * @param initialValue - Optional initial value for the state, used if no value exists in the message\n * @param debounceTime - Optional debounce time in milliseconds (default: 300ms) to limit API calls\n * @returns A tuple containing:\n * - The current state value\n * - A setter function to update the state (updates UI immediately, debounces server sync)\n * - A metadata object with properties like isPending to track sync status\n * @example\n * // Basic usage\n * const [count, setCount, { isPending }] = useTamboComponentState(\"counter\", 0);\n *\n * // Usage with object state\n * const [formState, setFormState] = useTamboComponentState(\"myForm\", {\n * name: \"\",\n * email: \"\",\n * message: \"\"\n * });\n *\n * // Handling form input\n * const handleChange = (e) => {\n * setFormState({\n * ...formState,\n * [e.target.name]: e.target.value\n * });\n * };\n */\nexport function useTamboComponentState<S = undefined>(\n keyName: string,\n initialValue?: S,\n debounceTime?: number,\n): StateUpdateResult<S | undefined>;\nexport function useTamboComponentState<S>(\n keyName: string,\n initialValue: S,\n debounceTime?: number,\n): StateUpdateResult<S>;\n\nexport function useTamboComponentState<S>(\n keyName: string,\n initialValue?: S,\n debounceTime = 500,\n): StateUpdateResult<S> {\n const { messageId } = useTamboMessageContext();\n const { updateThreadMessage, thread } = useTamboThread();\n const client = useTamboClient();\n const message = useTamboCurrentMessage();\n const threadId = thread.id;\n\n // Initial value management\n const [cachedInitialValue] = useState(() => initialValue);\n // UI state management\n const [localState, setLocalState] = useState<S | undefined>(\n cachedInitialValue,\n );\n // Synchronization state\n const [isPending, setIsPending] = useState(false);\n // Track the last user-initiated value instead of a simple boolean flag\n const [lastUserValue, setLastUserValue] = useState<S | null>(null);\n const [haveInitialized, setHaveInitialized] = useState(false);\n\n // Determine if we need to initialize state\n const shouldInitialize =\n !haveInitialized &&\n message &&\n cachedInitialValue !== undefined &&\n (!message.componentState || !(keyName in message.componentState));\n\n // Sync local state with message state on initial load and when message changes\n useEffect(() => {\n if (message?.componentState && keyName in message.componentState) {\n const messageState = message.componentState[keyName] as S;\n\n // Only update local state if we haven't had any user changes yet\n if (lastUserValue === null) {\n setLocalState(messageState);\n }\n }\n // Otherwise fall back to initial value if we have one and no user changes\n else if (\n cachedInitialValue !== undefined &&\n !localState &&\n lastUserValue === null\n ) {\n setLocalState(cachedInitialValue);\n }\n }, [\n keyName,\n message?.componentState,\n cachedInitialValue,\n lastUserValue,\n localState,\n ]);\n\n // Create debounced save function for efficient server synchronization\n const debouncedServerWrite = useDebouncedCallback(async (newValue: S) => {\n setIsPending(true);\n try {\n const componentStateUpdate = {\n state: { [keyName]: newValue },\n };\n\n await client.beta.threads.messages.updateComponentState(\n threadId,\n messageId,\n componentStateUpdate,\n );\n } catch (err) {\n console.error(\n `Failed to save component state for key \"${keyName}\":`,\n err,\n );\n } finally {\n setIsPending(false);\n }\n }, debounceTime);\n\n // Initialize state on first render if needed\n const initializeState = useCallback(async () => {\n if (!message) {\n console.warn(\n `Cannot initialize state for missing message ${messageId} with key \"${keyName}\"`,\n );\n return;\n }\n\n try {\n const messageUpdate = {\n ...message,\n componentState: {\n ...message.componentState,\n [keyName]: cachedInitialValue,\n },\n };\n\n const componentStateUpdate = {\n state: { [keyName]: cachedInitialValue },\n };\n\n await Promise.all([\n updateThreadMessage(messageId, messageUpdate, false),\n client.beta.threads.messages.updateComponentState(\n threadId,\n messageId,\n componentStateUpdate,\n ),\n ]);\n } catch (err) {\n console.warn(\n `Failed to initialize component state for key \"${keyName}\":`,\n err,\n );\n }\n }, [\n cachedInitialValue,\n client.beta.threads.messages,\n keyName,\n message,\n messageId,\n threadId,\n updateThreadMessage,\n ]);\n\n // Send initial state when component mounts\n useEffect(() => {\n if (shouldInitialize) {\n initializeState();\n setHaveInitialized(true);\n }\n }, [initializeState, shouldInitialize]);\n\n // setValue function for updating state\n // Updates local state immediately and schedules debounced server sync\n const setValue = useCallback(\n (newValue: S) => {\n // Track this as a user-initiated update\n setLastUserValue(newValue);\n setLocalState(newValue);\n\n // Only trigger server updates if we have a message\n if (message) {\n debouncedServerWrite(newValue);\n const messageUpdate = {\n ...message,\n componentState: {\n ...message.componentState,\n [keyName]: newValue,\n },\n };\n\n updateThreadMessage(messageId, messageUpdate, false);\n } else {\n console.warn(\n `Cannot update server for missing message ${messageId} with key \"${keyName}\"`,\n );\n }\n },\n [message, debouncedServerWrite, keyName, updateThreadMessage, messageId],\n );\n\n // Ensure pending changes are flushed on unmount\n useEffect(() => {\n return () => {\n debouncedServerWrite.flush();\n };\n }, [debouncedServerWrite]);\n\n // Return the local state for immediate UI rendering\n return [localState as S, setValue, { isPending }];\n}\n"]}
|
package/dist/mcp/mcp-client.d.ts
CHANGED
|
@@ -65,32 +65,32 @@ export declare class MCPClient {
|
|
|
65
65
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
66
66
|
}, import("zod").ZodTypeAny, "passthrough">>, import("zod").ZodObject<{
|
|
67
67
|
type: import("zod").ZodLiteral<"image">;
|
|
68
|
-
data: import("zod").ZodString
|
|
68
|
+
data: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
69
69
|
mimeType: import("zod").ZodString;
|
|
70
70
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
71
71
|
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
|
|
72
72
|
type: import("zod").ZodLiteral<"image">;
|
|
73
|
-
data: import("zod").ZodString
|
|
73
|
+
data: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
74
74
|
mimeType: import("zod").ZodString;
|
|
75
75
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
76
76
|
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
|
|
77
77
|
type: import("zod").ZodLiteral<"image">;
|
|
78
|
-
data: import("zod").ZodString
|
|
78
|
+
data: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
79
79
|
mimeType: import("zod").ZodString;
|
|
80
80
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
81
81
|
}, import("zod").ZodTypeAny, "passthrough">>, import("zod").ZodObject<{
|
|
82
82
|
type: import("zod").ZodLiteral<"audio">;
|
|
83
|
-
data: import("zod").ZodString
|
|
83
|
+
data: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
84
84
|
mimeType: import("zod").ZodString;
|
|
85
85
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
86
86
|
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
|
|
87
87
|
type: import("zod").ZodLiteral<"audio">;
|
|
88
|
-
data: import("zod").ZodString
|
|
88
|
+
data: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
89
89
|
mimeType: import("zod").ZodString;
|
|
90
90
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
91
91
|
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
|
|
92
92
|
type: import("zod").ZodLiteral<"audio">;
|
|
93
|
-
data: import("zod").ZodString
|
|
93
|
+
data: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
94
94
|
mimeType: import("zod").ZodString;
|
|
95
95
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
96
96
|
}, import("zod").ZodTypeAny, "passthrough">>, import("zod").ZodObject<import("zod").objectUtil.extendShape<import("zod").objectUtil.extendShape<{
|
|
@@ -148,19 +148,19 @@ export declare class MCPClient {
|
|
|
148
148
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
149
149
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
150
150
|
}, {
|
|
151
|
-
blob: import("zod").ZodString
|
|
151
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
152
152
|
}>, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<import("zod").objectUtil.extendShape<{
|
|
153
153
|
uri: import("zod").ZodString;
|
|
154
154
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
155
155
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
156
156
|
}, {
|
|
157
|
-
blob: import("zod").ZodString
|
|
157
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
158
158
|
}>, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<import("zod").objectUtil.extendShape<{
|
|
159
159
|
uri: import("zod").ZodString;
|
|
160
160
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
161
161
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
162
162
|
}, {
|
|
163
|
-
blob: import("zod").ZodString
|
|
163
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
164
164
|
}>, import("zod").ZodTypeAny, "passthrough">>]>;
|
|
165
165
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
166
166
|
}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{
|
|
@@ -188,19 +188,19 @@ export declare class MCPClient {
|
|
|
188
188
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
189
189
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
190
190
|
}, {
|
|
191
|
-
blob: import("zod").ZodString
|
|
191
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
192
192
|
}>, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<import("zod").objectUtil.extendShape<{
|
|
193
193
|
uri: import("zod").ZodString;
|
|
194
194
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
195
195
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
196
196
|
}, {
|
|
197
|
-
blob: import("zod").ZodString
|
|
197
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
198
198
|
}>, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<import("zod").objectUtil.extendShape<{
|
|
199
199
|
uri: import("zod").ZodString;
|
|
200
200
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
201
201
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
202
202
|
}, {
|
|
203
|
-
blob: import("zod").ZodString
|
|
203
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
204
204
|
}>, import("zod").ZodTypeAny, "passthrough">>]>;
|
|
205
205
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
206
206
|
}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{
|
|
@@ -228,19 +228,19 @@ export declare class MCPClient {
|
|
|
228
228
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
229
229
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
230
230
|
}, {
|
|
231
|
-
blob: import("zod").ZodString
|
|
231
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
232
232
|
}>, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<import("zod").objectUtil.extendShape<{
|
|
233
233
|
uri: import("zod").ZodString;
|
|
234
234
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
235
235
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
236
236
|
}, {
|
|
237
|
-
blob: import("zod").ZodString
|
|
237
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
238
238
|
}>, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<import("zod").objectUtil.extendShape<{
|
|
239
239
|
uri: import("zod").ZodString;
|
|
240
240
|
mimeType: import("zod").ZodOptional<import("zod").ZodString>;
|
|
241
241
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
242
242
|
}, {
|
|
243
|
-
blob: import("zod").ZodString
|
|
243
|
+
blob: import("zod").ZodEffects<import("zod").ZodString, string, string>;
|
|
244
244
|
}>, import("zod").ZodTypeAny, "passthrough">>]>;
|
|
245
245
|
_meta: import("zod").ZodOptional<import("zod").ZodObject<{}, "passthrough", import("zod").ZodTypeAny, import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough">, import("zod").objectInputType<{}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
246
246
|
}, import("zod").ZodTypeAny, "passthrough">>]>, "many">>;
|
|
@@ -5,17 +5,63 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const react_1 = require("@testing-library/react");
|
|
7
7
|
const react_2 = __importDefault(require("react"));
|
|
8
|
+
const use_current_message_1 = require("../../hooks/use-current-message");
|
|
9
|
+
const generate_component_response_1 = require("../../model/generate-component-response");
|
|
10
|
+
const tambo_thread_provider_1 = require("../../providers/tambo-thread-provider");
|
|
8
11
|
const tambo_prop_stream_provider_1 = require("../tambo-prop-stream-provider");
|
|
12
|
+
// Mock the required providers
|
|
13
|
+
jest.mock("../../providers/tambo-thread-provider", () => ({
|
|
14
|
+
useTamboThread: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
jest.mock("../../hooks/use-current-message", () => ({
|
|
17
|
+
useTamboCurrentMessage: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
// Mock window for SSR tests
|
|
20
|
+
const originalWindow = global.window;
|
|
21
|
+
// Helper function to create mock ComponentDecisionV2
|
|
22
|
+
const createMockComponent = (props = {}) => ({
|
|
23
|
+
componentName: "TestComponent",
|
|
24
|
+
componentState: {},
|
|
25
|
+
message: "Component generated",
|
|
26
|
+
props,
|
|
27
|
+
reasoning: "Test reasoning",
|
|
28
|
+
});
|
|
29
|
+
// Helper function to create mock TamboThreadMessage
|
|
30
|
+
const createMockMessage = (overrides = {}) => ({
|
|
31
|
+
id: "test-message",
|
|
32
|
+
componentState: {},
|
|
33
|
+
content: [{ type: "text", text: "test content" }],
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
role: "assistant",
|
|
36
|
+
threadId: "test-thread",
|
|
37
|
+
...overrides,
|
|
38
|
+
});
|
|
9
39
|
// Helper component to test hook usage
|
|
10
40
|
const TestHookComponent = ({ testKey = "default", }) => {
|
|
11
|
-
const {
|
|
41
|
+
const { streamStatus, getStatusForKey } = (0, tambo_prop_stream_provider_1.useTamboStream)();
|
|
12
42
|
const status = getStatusForKey(testKey);
|
|
13
43
|
return (react_2.default.createElement("div", null,
|
|
14
|
-
react_2.default.createElement("div", { "data-testid": "data" }, JSON.stringify(data)),
|
|
15
44
|
react_2.default.createElement("div", { "data-testid": "stream-status" }, JSON.stringify(streamStatus)),
|
|
16
45
|
react_2.default.createElement("div", { "data-testid": "key-status" }, JSON.stringify(status))));
|
|
17
46
|
};
|
|
18
47
|
describe("TamboPropStreamProvider", () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
// Restore window for client-side tests
|
|
50
|
+
global.window = originalWindow;
|
|
51
|
+
// Default mock implementations
|
|
52
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
53
|
+
generationStage: generate_component_response_1.GenerationStage.IDLE,
|
|
54
|
+
});
|
|
55
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue({
|
|
56
|
+
id: "test-message",
|
|
57
|
+
component: {
|
|
58
|
+
props: {},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
jest.clearAllMocks();
|
|
64
|
+
});
|
|
19
65
|
describe("Hook Error Handling", () => {
|
|
20
66
|
it("should throw error when useTamboStream is used outside provider", () => {
|
|
21
67
|
// Suppress console.error for this test
|
|
@@ -27,250 +73,124 @@ describe("TamboPropStreamProvider", () => {
|
|
|
27
73
|
console.error = originalError;
|
|
28
74
|
});
|
|
29
75
|
});
|
|
30
|
-
describe("Basic Functionality", () => {
|
|
31
|
-
it("should provide data and stream status through context", () => {
|
|
32
|
-
const testData = { message: "Hello World" };
|
|
33
|
-
const testStreamStatus = {
|
|
34
|
-
isPending: false,
|
|
35
|
-
isStreaming: false,
|
|
36
|
-
isSuccess: true,
|
|
37
|
-
isError: false,
|
|
38
|
-
streamError: undefined,
|
|
39
|
-
};
|
|
40
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: testData, streamStatus: testStreamStatus },
|
|
41
|
-
react_2.default.createElement(TestHookComponent, null)));
|
|
42
|
-
expect(react_1.screen.getByTestId("data")).toHaveTextContent(JSON.stringify(testData));
|
|
43
|
-
expect(react_1.screen.getByTestId("stream-status")).toHaveTextContent(JSON.stringify(testStreamStatus));
|
|
44
|
-
});
|
|
45
|
-
it("should use default stream status when none provided", () => {
|
|
46
|
-
const testData = { message: "Hello World" };
|
|
47
|
-
const expectedDefaultStatus = {
|
|
48
|
-
isPending: false,
|
|
49
|
-
isStreaming: false,
|
|
50
|
-
isSuccess: true,
|
|
51
|
-
isError: false,
|
|
52
|
-
streamError: undefined,
|
|
53
|
-
};
|
|
54
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: testData },
|
|
55
|
-
react_2.default.createElement(TestHookComponent, null)));
|
|
56
|
-
expect(react_1.screen.getByTestId("stream-status")).toHaveTextContent(JSON.stringify(expectedDefaultStatus));
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
76
|
describe("Compound Components", () => {
|
|
60
|
-
describe("
|
|
61
|
-
it("should render
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
};
|
|
68
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider,
|
|
69
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.
|
|
77
|
+
describe("Streaming Component", () => {
|
|
78
|
+
it("should render streaming when isPending is true", () => {
|
|
79
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
80
|
+
generationStage: generate_component_response_1.GenerationStage.IDLE,
|
|
81
|
+
});
|
|
82
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
83
|
+
component: createMockComponent({ title: "" }),
|
|
84
|
+
}));
|
|
85
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
86
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Streaming, null,
|
|
70
87
|
react_2.default.createElement("div", { "data-testid": "loading" }, "Loading..."))));
|
|
71
88
|
expect(react_1.screen.getByTestId("loading")).toBeInTheDocument();
|
|
72
89
|
});
|
|
73
|
-
it("should render
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
80
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider,
|
|
81
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.
|
|
90
|
+
it("should render streaming when isStreaming is true", () => {
|
|
91
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
92
|
+
generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
|
|
93
|
+
});
|
|
94
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
95
|
+
component: createMockComponent({ title: "Partial" }),
|
|
96
|
+
}));
|
|
97
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
98
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Streaming, null,
|
|
82
99
|
react_2.default.createElement("div", { "data-testid": "loading" }, "Loading..."))));
|
|
83
100
|
expect(react_1.screen.getByTestId("loading")).toBeInTheDocument();
|
|
84
101
|
});
|
|
85
|
-
it("should not render loading when not pending or streaming", () => {
|
|
86
|
-
const streamStatus = {
|
|
87
|
-
isPending: false,
|
|
88
|
-
isStreaming: false,
|
|
89
|
-
isSuccess: true,
|
|
90
|
-
isError: false,
|
|
91
|
-
};
|
|
92
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: "test", streamStatus: streamStatus },
|
|
93
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Loading, null,
|
|
94
|
-
react_2.default.createElement("div", { "data-testid": "loading" }, "Loading..."))));
|
|
95
|
-
expect(react_1.screen.queryByTestId("loading")).not.toBeInTheDocument();
|
|
96
|
-
});
|
|
97
102
|
});
|
|
98
|
-
describe("
|
|
99
|
-
it("should render
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
};
|
|
106
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider,
|
|
107
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.
|
|
108
|
-
react_2.default.createElement("div", { "data-testid": "complete" }, "Complete!"))));
|
|
109
|
-
expect(react_1.screen.getByTestId("complete")).toBeInTheDocument();
|
|
110
|
-
});
|
|
111
|
-
it("should not render complete when data is null", () => {
|
|
112
|
-
const streamStatus = {
|
|
113
|
-
isPending: false,
|
|
114
|
-
isStreaming: false,
|
|
115
|
-
isSuccess: true,
|
|
116
|
-
isError: false,
|
|
117
|
-
};
|
|
118
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: null, streamStatus: streamStatus },
|
|
119
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Complete, null,
|
|
120
|
-
react_2.default.createElement("div", { "data-testid": "complete" }, "Complete!"))));
|
|
121
|
-
expect(react_1.screen.queryByTestId("complete")).not.toBeInTheDocument();
|
|
122
|
-
});
|
|
123
|
-
it("should not render complete when data is undefined", () => {
|
|
124
|
-
const streamStatus = {
|
|
125
|
-
isPending: false,
|
|
126
|
-
isStreaming: false,
|
|
127
|
-
isSuccess: true,
|
|
128
|
-
isError: false,
|
|
129
|
-
};
|
|
130
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: undefined, streamStatus: streamStatus },
|
|
131
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Complete, null,
|
|
103
|
+
describe("Success Component", () => {
|
|
104
|
+
it("should not render success when isSuccess is false", () => {
|
|
105
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
106
|
+
generationStage: generate_component_response_1.GenerationStage.IDLE,
|
|
107
|
+
});
|
|
108
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
109
|
+
component: createMockComponent({ title: "" }),
|
|
110
|
+
}));
|
|
111
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
112
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Success, null,
|
|
132
113
|
react_2.default.createElement("div", { "data-testid": "complete" }, "Complete!"))));
|
|
133
114
|
expect(react_1.screen.queryByTestId("complete")).not.toBeInTheDocument();
|
|
134
115
|
});
|
|
135
116
|
});
|
|
136
|
-
describe("
|
|
137
|
-
it("should render
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
};
|
|
144
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider,
|
|
145
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.
|
|
146
|
-
react_2.default.createElement("div", { "data-testid": "empty" }, "
|
|
117
|
+
describe("Pending Component", () => {
|
|
118
|
+
it("should render pending when no active status", () => {
|
|
119
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
120
|
+
generationStage: generate_component_response_1.GenerationStage.IDLE,
|
|
121
|
+
});
|
|
122
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
123
|
+
component: createMockComponent({ title: "" }),
|
|
124
|
+
}));
|
|
125
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
126
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Pending, null,
|
|
127
|
+
react_2.default.createElement("div", { "data-testid": "empty" }, "Empty!"))));
|
|
147
128
|
expect(react_1.screen.getByTestId("empty")).toBeInTheDocument();
|
|
148
129
|
});
|
|
149
|
-
it("should render
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
};
|
|
156
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider,
|
|
157
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.
|
|
158
|
-
react_2.default.createElement("div", { "data-testid": "empty" }, "
|
|
159
|
-
expect(react_1.screen.getByTestId("empty")).toBeInTheDocument();
|
|
160
|
-
});
|
|
161
|
-
it("should not render empty when loading", () => {
|
|
162
|
-
const streamStatus = {
|
|
163
|
-
isPending: true,
|
|
164
|
-
isStreaming: false,
|
|
165
|
-
isSuccess: false,
|
|
166
|
-
isError: false,
|
|
167
|
-
};
|
|
168
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: null, streamStatus: streamStatus },
|
|
169
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Empty, null,
|
|
170
|
-
react_2.default.createElement("div", { "data-testid": "empty" }, "No data"))));
|
|
171
|
-
expect(react_1.screen.queryByTestId("empty")).not.toBeInTheDocument();
|
|
172
|
-
});
|
|
173
|
-
it("should not render empty when successful", () => {
|
|
174
|
-
const streamStatus = {
|
|
175
|
-
isPending: false,
|
|
176
|
-
isStreaming: false,
|
|
177
|
-
isSuccess: true,
|
|
178
|
-
isError: false,
|
|
179
|
-
};
|
|
180
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: null, streamStatus: streamStatus },
|
|
181
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Empty, null,
|
|
182
|
-
react_2.default.createElement("div", { "data-testid": "empty" }, "No data"))));
|
|
130
|
+
it("should not render pending when isPending is true", () => {
|
|
131
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
132
|
+
generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
|
|
133
|
+
});
|
|
134
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
135
|
+
component: createMockComponent({ title: "Partial" }),
|
|
136
|
+
}));
|
|
137
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
138
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Pending, null,
|
|
139
|
+
react_2.default.createElement("div", { "data-testid": "empty" }, "Empty!"))));
|
|
183
140
|
expect(react_1.screen.queryByTestId("empty")).not.toBeInTheDocument();
|
|
184
141
|
});
|
|
185
142
|
});
|
|
186
143
|
});
|
|
187
|
-
describe("
|
|
188
|
-
it("should
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
expect(keyStatus.isSuccess).toBe(true); // name has data
|
|
200
|
-
});
|
|
201
|
-
it("should show loading for keys without data", () => {
|
|
202
|
-
const testData = { name: "John", age: null };
|
|
203
|
-
const streamStatus = {
|
|
204
|
-
isPending: false,
|
|
205
|
-
isStreaming: false,
|
|
206
|
-
isSuccess: true,
|
|
207
|
-
isError: false,
|
|
208
|
-
};
|
|
209
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: testData, streamStatus: streamStatus },
|
|
210
|
-
react_2.default.createElement(TestHookComponent, { testKey: "age" })));
|
|
211
|
-
const keyStatus = JSON.parse(react_1.screen.getByTestId("key-status").textContent ?? "{}");
|
|
212
|
-
expect(keyStatus.isPending).toBe(true); // age has no data
|
|
213
|
-
});
|
|
214
|
-
it("should handle per-key loading states", () => {
|
|
215
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: { name: "John", age: null } },
|
|
216
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Loading, { streamKey: "name" },
|
|
217
|
-
react_2.default.createElement("div", { "data-testid": "name-loading" }, "Name loading...")),
|
|
218
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Loading, { streamKey: "age" },
|
|
219
|
-
react_2.default.createElement("div", { "data-testid": "age-loading" }, "Age loading...")),
|
|
220
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Complete, { streamKey: "name" },
|
|
221
|
-
react_2.default.createElement("div", { "data-testid": "name-complete" }, "Name: John"))));
|
|
222
|
-
// Name should be complete (has data)
|
|
223
|
-
expect(react_1.screen.getByTestId("name-complete")).toBeInTheDocument();
|
|
224
|
-
expect(react_1.screen.queryByTestId("name-loading")).not.toBeInTheDocument();
|
|
225
|
-
// Age should be loading (no data)
|
|
226
|
-
expect(react_1.screen.getByTestId("age-loading")).toBeInTheDocument();
|
|
144
|
+
describe("Key-based Status", () => {
|
|
145
|
+
it("should provide status for keys not in propStatus", () => {
|
|
146
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
147
|
+
generationStage: generate_component_response_1.GenerationStage.COMPLETE,
|
|
148
|
+
});
|
|
149
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
150
|
+
component: createMockComponent({ name: "John" }),
|
|
151
|
+
}));
|
|
152
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
153
|
+
react_2.default.createElement(TestHookComponent, { testKey: "nonexistent" })));
|
|
154
|
+
const keyStatus = JSON.parse(react_1.screen.getByTestId("key-status").textContent);
|
|
155
|
+
expect(keyStatus.isPending).toBe(true);
|
|
227
156
|
});
|
|
228
157
|
});
|
|
229
|
-
describe("
|
|
230
|
-
it("should
|
|
231
|
-
(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
react_2.default.createElement(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
expect(react_1.screen.getByTestId("
|
|
250
|
-
|
|
251
|
-
it("should fallback to default status for unknown keys", () => {
|
|
252
|
-
const testData = { name: "John" };
|
|
253
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, { data: testData },
|
|
254
|
-
react_2.default.createElement(TestHookComponent, { testKey: "unknown-key" })));
|
|
255
|
-
const keyStatus = JSON.parse(react_1.screen.getByTestId("key-status").textContent ?? "{}");
|
|
256
|
-
// Should fallback to default status
|
|
257
|
-
expect(keyStatus.isSuccess).toBe(true);
|
|
158
|
+
describe("Compound Components with Keys", () => {
|
|
159
|
+
it("should render loading for specific key when pending", () => {
|
|
160
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
161
|
+
generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
|
|
162
|
+
});
|
|
163
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
164
|
+
component: createMockComponent({
|
|
165
|
+
name: "Partial",
|
|
166
|
+
age: 25,
|
|
167
|
+
}),
|
|
168
|
+
}));
|
|
169
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
170
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Streaming, { streamKey: "name" },
|
|
171
|
+
react_2.default.createElement("div", { "data-testid": "name-loading" }, "Name Loading...")),
|
|
172
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Streaming, { streamKey: "age" },
|
|
173
|
+
react_2.default.createElement("div", { "data-testid": "age-loading" }, "Age Loading...")),
|
|
174
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Success, { streamKey: "name" },
|
|
175
|
+
react_2.default.createElement("div", { "data-testid": "name-complete" }, "Name Complete!"))));
|
|
176
|
+
// Both props should be loading since they're in streaming stage
|
|
177
|
+
expect(react_1.screen.getByTestId("name-loading")).toBeInTheDocument();
|
|
178
|
+
expect(react_1.screen.getByTestId("age-loading")).toBeInTheDocument();
|
|
179
|
+
expect(react_1.screen.queryByTestId("name-complete")).not.toBeInTheDocument();
|
|
258
180
|
});
|
|
259
181
|
});
|
|
260
|
-
describe("
|
|
261
|
-
it("should
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
};
|
|
268
|
-
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider,
|
|
269
|
-
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.
|
|
270
|
-
react_2.default.createElement("div",
|
|
271
|
-
const loadingElement = react_1.screen.
|
|
272
|
-
expect(loadingElement).toHaveAttribute("data-stream-key", "default");
|
|
273
|
-
expect(loadingElement).toHaveAttribute("data-stream-state", "loading");
|
|
182
|
+
describe("Styling", () => {
|
|
183
|
+
it("should apply className to loading component", () => {
|
|
184
|
+
jest.mocked(tambo_thread_provider_1.useTamboThread).mockReturnValue({
|
|
185
|
+
generationStage: generate_component_response_1.GenerationStage.STREAMING_RESPONSE,
|
|
186
|
+
});
|
|
187
|
+
jest.mocked(use_current_message_1.useTamboCurrentMessage).mockReturnValue(createMockMessage({
|
|
188
|
+
component: createMockComponent({ title: "Partial" }),
|
|
189
|
+
}));
|
|
190
|
+
(0, react_1.render)(react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider, null,
|
|
191
|
+
react_2.default.createElement(tambo_prop_stream_provider_1.TamboPropStreamProvider.Streaming, { className: "loading-class" },
|
|
192
|
+
react_2.default.createElement("div", { "data-testid": "loading" }, "Loading..."))));
|
|
193
|
+
const loadingElement = react_1.screen.getByTestId("loading").parentElement;
|
|
274
194
|
expect(loadingElement).toHaveClass("loading-class");
|
|
275
195
|
});
|
|
276
196
|
});
|