@tambo-ai/react 0.67.1 → 0.69.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/README.md +3 -5
- package/dist/context-helpers/context-helpers.test.js +16 -4
- package/dist/context-helpers/context-helpers.test.js.map +1 -1
- package/dist/context-helpers/current-interactables-context-helper.d.ts +2 -2
- package/dist/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
- package/dist/context-helpers/current-interactables-context-helper.js +31 -12
- package/dist/context-helpers/current-interactables-context-helper.js.map +1 -1
- package/dist/context-helpers/registry.d.ts +2 -2
- package/dist/context-helpers/registry.d.ts.map +1 -1
- package/dist/context-helpers/registry.js.map +1 -1
- package/dist/context-helpers/types.d.ts +2 -2
- package/dist/context-helpers/types.d.ts.map +1 -1
- package/dist/context-helpers/types.js.map +1 -1
- package/dist/hoc/with-tambo-interactable.d.ts +50 -4
- package/dist/hoc/with-tambo-interactable.d.ts.map +1 -1
- package/dist/hoc/with-tambo-interactable.js +20 -5
- package/dist/hoc/with-tambo-interactable.js.map +1 -1
- package/dist/hooks/use-component-state.d.ts +3 -8
- package/dist/hooks/use-component-state.d.ts.map +1 -1
- package/dist/hooks/use-component-state.js +8 -0
- package/dist/hooks/use-component-state.js.map +1 -1
- package/dist/hooks/use-component-state.test.js +37 -0
- package/dist/hooks/use-component-state.test.js.map +1 -1
- package/dist/hooks/use-message-images.test.js +174 -37
- package/dist/hooks/use-message-images.test.js.map +1 -1
- package/dist/hooks/use-tambo-threads.js +1 -1
- package/dist/hooks/use-tambo-threads.js.map +1 -1
- package/dist/hooks/use-tambo-voice.d.ts +1 -1
- package/dist/hooks/use-tambo-voice.js +1 -1
- package/dist/hooks/use-tambo-voice.js.map +1 -1
- package/dist/hooks/use-tambo-voice.test.d.ts +2 -0
- package/dist/hooks/use-tambo-voice.test.d.ts.map +1 -0
- package/dist/hooks/use-tambo-voice.test.js +239 -0
- package/dist/hooks/use-tambo-voice.test.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/elicitation.d.ts.map +1 -1
- package/dist/mcp/elicitation.js +12 -0
- package/dist/mcp/elicitation.js.map +1 -1
- package/dist/mcp/elicitation.test.js +8 -1
- package/dist/mcp/elicitation.test.js.map +1 -1
- package/dist/mcp/mcp-client.d.ts +6 -10
- package/dist/mcp/mcp-client.d.ts.map +1 -1
- package/dist/mcp/mcp-client.js.map +1 -1
- package/dist/mcp/mcp-constants.d.ts +19 -0
- package/dist/mcp/mcp-constants.d.ts.map +1 -0
- package/dist/mcp/mcp-constants.js +21 -0
- package/dist/mcp/mcp-constants.js.map +1 -0
- package/dist/mcp/mcp-hooks.d.ts +21 -40
- package/dist/mcp/mcp-hooks.d.ts.map +1 -1
- package/dist/mcp/mcp-hooks.js +130 -39
- package/dist/mcp/mcp-hooks.js.map +1 -1
- package/dist/mcp/mcp-hooks.test.js +431 -5
- package/dist/mcp/mcp-hooks.test.js.map +1 -1
- package/dist/mcp/tambo-mcp-provider.d.ts +7 -0
- package/dist/mcp/tambo-mcp-provider.d.ts.map +1 -1
- package/dist/mcp/tambo-mcp-provider.js +205 -155
- package/dist/mcp/tambo-mcp-provider.js.map +1 -1
- package/dist/mcp/tambo-mcp-provider.test.js +37 -0
- package/dist/mcp/tambo-mcp-provider.test.js.map +1 -1
- package/dist/model/component-metadata.d.ts +54 -21
- package/dist/model/component-metadata.d.ts.map +1 -1
- package/dist/model/component-metadata.js.map +1 -1
- package/dist/model/tambo-interactable.d.ts +13 -5
- package/dist/model/tambo-interactable.d.ts.map +1 -1
- package/dist/model/tambo-interactable.js.map +1 -1
- package/dist/providers/__tests__/thread-input-resource-resolution.test.d.ts +2 -0
- package/dist/providers/__tests__/thread-input-resource-resolution.test.d.ts.map +1 -0
- package/dist/providers/__tests__/thread-input-resource-resolution.test.js +592 -0
- package/dist/providers/__tests__/thread-input-resource-resolution.test.js.map +1 -0
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/tambo-client-provider.d.ts +8 -0
- package/dist/providers/tambo-client-provider.d.ts.map +1 -1
- package/dist/providers/tambo-client-provider.js +10 -11
- package/dist/providers/tambo-client-provider.js.map +1 -1
- package/dist/providers/tambo-client-provider.test.d.ts +2 -0
- package/dist/providers/tambo-client-provider.test.d.ts.map +1 -0
- package/dist/providers/tambo-client-provider.test.js +208 -0
- package/dist/providers/tambo-client-provider.test.js.map +1 -0
- package/dist/providers/tambo-context-attachment-provider.d.ts +34 -92
- package/dist/providers/tambo-context-attachment-provider.d.ts.map +1 -1
- package/dist/providers/tambo-context-attachment-provider.js +62 -105
- package/dist/providers/tambo-context-attachment-provider.js.map +1 -1
- package/dist/providers/tambo-context-attachment-provider.test.js +229 -463
- package/dist/providers/tambo-context-attachment-provider.test.js.map +1 -1
- package/dist/providers/tambo-interactable-provider-partial-updates.test.js +22 -21
- package/dist/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
- package/dist/providers/tambo-interactable-provider.d.ts +5 -2
- package/dist/providers/tambo-interactable-provider.d.ts.map +1 -1
- package/dist/providers/tambo-interactable-provider.js +126 -17
- package/dist/providers/tambo-interactable-provider.js.map +1 -1
- package/dist/providers/tambo-interactable-provider.test.js +242 -0
- package/dist/providers/tambo-interactable-provider.test.js.map +1 -1
- package/dist/providers/tambo-interactables-additional-context.test.js +2 -5
- package/dist/providers/tambo-interactables-additional-context.test.js.map +1 -1
- package/dist/providers/tambo-provider.d.ts +2 -3
- package/dist/providers/tambo-provider.d.ts.map +1 -1
- package/dist/providers/tambo-provider.js +6 -5
- package/dist/providers/tambo-provider.js.map +1 -1
- package/dist/providers/tambo-registry-provider.test.js +16 -0
- package/dist/providers/tambo-registry-provider.test.js.map +1 -1
- package/dist/providers/tambo-registry-schema-compat.test.js +31 -0
- package/dist/providers/tambo-registry-schema-compat.test.js.map +1 -1
- package/dist/providers/tambo-thread-input-provider.d.ts +1 -1
- package/dist/providers/tambo-thread-input-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-input-provider.js +26 -4
- package/dist/providers/tambo-thread-input-provider.js.map +1 -1
- package/dist/providers/tambo-thread-provider-initial-messages.test.js +84 -2
- package/dist/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
- package/dist/providers/tambo-thread-provider.d.ts.map +1 -1
- package/dist/providers/tambo-thread-provider.js +53 -42
- package/dist/providers/tambo-thread-provider.js.map +1 -1
- package/dist/providers/tambo-thread-provider.test.js +368 -262
- package/dist/providers/tambo-thread-provider.test.js.map +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +2 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/json-schema.d.ts +7 -0
- package/dist/schema/json-schema.d.ts.map +1 -1
- package/dist/schema/json-schema.js +40 -29
- package/dist/schema/json-schema.js.map +1 -1
- package/dist/schema/json-schema.test.d.ts +2 -0
- package/dist/schema/json-schema.test.d.ts.map +1 -0
- package/dist/schema/json-schema.test.js +204 -0
- package/dist/schema/json-schema.test.js.map +1 -0
- package/dist/schema/schema.test.js +237 -0
- package/dist/schema/schema.test.js.map +1 -1
- package/dist/schema/standard-schema.d.ts +1 -0
- package/dist/schema/standard-schema.d.ts.map +1 -1
- package/dist/schema/standard-schema.js +18 -13
- package/dist/schema/standard-schema.js.map +1 -1
- package/dist/schema/standard-schema.test.d.ts +2 -0
- package/dist/schema/standard-schema.test.d.ts.map +1 -0
- package/dist/schema/standard-schema.test.js +165 -0
- package/dist/schema/standard-schema.test.js.map +1 -0
- package/dist/schema/validate.test.js +149 -0
- package/dist/schema/validate.test.js.map +1 -1
- package/dist/schema/zod.d.ts +7 -4
- package/dist/schema/zod.d.ts.map +1 -1
- package/dist/schema/zod.js +65 -22
- package/dist/schema/zod.js.map +1 -1
- package/dist/schema/zod.test.js +112 -0
- package/dist/schema/zod.test.js.map +1 -1
- package/dist/setupTests.js +3 -0
- package/dist/setupTests.js.map +1 -1
- package/dist/testing/tools.d.ts +4 -1
- package/dist/testing/tools.d.ts.map +1 -1
- package/dist/testing/tools.js +6 -1
- package/dist/testing/tools.js.map +1 -1
- package/dist/util/generate-component.d.ts.map +1 -1
- package/dist/util/generate-component.js +18 -3
- package/dist/util/generate-component.js.map +1 -1
- package/dist/util/generate-component.test.d.ts +2 -0
- package/dist/util/generate-component.test.d.ts.map +1 -0
- package/dist/util/generate-component.test.js +340 -0
- package/dist/util/generate-component.test.js.map +1 -0
- package/dist/util/is-promise.d.ts +9 -0
- package/dist/util/is-promise.d.ts.map +1 -0
- package/dist/util/is-promise.js +20 -0
- package/dist/util/is-promise.js.map +1 -0
- package/dist/util/is-promise.test.d.ts +2 -0
- package/dist/util/is-promise.test.d.ts.map +1 -0
- package/dist/util/is-promise.test.js +48 -0
- package/dist/util/is-promise.test.js.map +1 -0
- package/dist/util/message-builder.d.ts +3 -1
- package/dist/util/message-builder.d.ts.map +1 -1
- package/dist/util/message-builder.js +20 -3
- package/dist/util/message-builder.js.map +1 -1
- package/dist/util/message-builder.test.js +269 -0
- package/dist/util/message-builder.test.js.map +1 -1
- package/dist/util/query-utils.test.d.ts +2 -0
- package/dist/util/query-utils.test.d.ts.map +1 -0
- package/dist/util/query-utils.test.js +382 -0
- package/dist/util/query-utils.test.js.map +1 -0
- package/dist/util/registry-validators.d.ts.map +1 -1
- package/dist/util/registry-validators.js +7 -0
- package/dist/util/registry-validators.js.map +1 -1
- package/dist/util/registry-validators.test.js +57 -0
- package/dist/util/registry-validators.test.js.map +1 -1
- package/dist/util/registry.d.ts.map +1 -1
- package/dist/util/registry.js +9 -0
- package/dist/util/registry.js.map +1 -1
- package/dist/util/registry.test.js +323 -1
- package/dist/util/registry.test.js.map +1 -1
- package/dist/util/resource-content-resolver.d.ts +20 -0
- package/dist/util/resource-content-resolver.d.ts.map +1 -0
- package/dist/util/resource-content-resolver.js +93 -0
- package/dist/util/resource-content-resolver.js.map +1 -0
- package/dist/util/resource-content-resolver.test.d.ts +2 -0
- package/dist/util/resource-content-resolver.test.d.ts.map +1 -0
- package/dist/util/resource-content-resolver.test.js +254 -0
- package/dist/util/resource-content-resolver.test.js.map +1 -0
- package/dist/util/resource-validators.test.d.ts +2 -0
- package/dist/util/resource-validators.test.d.ts.map +1 -0
- package/dist/util/resource-validators.test.js +90 -0
- package/dist/util/resource-validators.test.js.map +1 -0
- package/dist/util/tool-caller.d.ts +2 -2
- package/dist/util/tool-caller.d.ts.map +1 -1
- package/dist/util/tool-caller.js +8 -8
- package/dist/util/tool-caller.js.map +1 -1
- package/dist/util/validate-component-name.test.d.ts +2 -0
- package/dist/util/validate-component-name.test.d.ts.map +1 -0
- package/dist/util/validate-component-name.test.js +35 -0
- package/dist/util/validate-component-name.test.js.map +1 -0
- package/esm/context-helpers/context-helpers.test.js +16 -4
- package/esm/context-helpers/context-helpers.test.js.map +1 -1
- package/esm/context-helpers/current-interactables-context-helper.d.ts +2 -2
- package/esm/context-helpers/current-interactables-context-helper.d.ts.map +1 -1
- package/esm/context-helpers/current-interactables-context-helper.js +31 -12
- package/esm/context-helpers/current-interactables-context-helper.js.map +1 -1
- package/esm/context-helpers/registry.d.ts +2 -2
- package/esm/context-helpers/registry.d.ts.map +1 -1
- package/esm/context-helpers/registry.js.map +1 -1
- package/esm/context-helpers/types.d.ts +2 -2
- package/esm/context-helpers/types.d.ts.map +1 -1
- package/esm/context-helpers/types.js.map +1 -1
- package/esm/hoc/with-tambo-interactable.d.ts +50 -4
- package/esm/hoc/with-tambo-interactable.d.ts.map +1 -1
- package/esm/hoc/with-tambo-interactable.js +20 -5
- package/esm/hoc/with-tambo-interactable.js.map +1 -1
- package/esm/hooks/use-component-state.d.ts +3 -8
- package/esm/hooks/use-component-state.d.ts.map +1 -1
- package/esm/hooks/use-component-state.js +8 -0
- package/esm/hooks/use-component-state.js.map +1 -1
- package/esm/hooks/use-component-state.test.js +37 -0
- package/esm/hooks/use-component-state.test.js.map +1 -1
- package/esm/hooks/use-message-images.test.js +174 -37
- package/esm/hooks/use-message-images.test.js.map +1 -1
- package/esm/hooks/use-tambo-threads.js +1 -1
- package/esm/hooks/use-tambo-threads.js.map +1 -1
- package/esm/hooks/use-tambo-voice.d.ts +1 -1
- package/esm/hooks/use-tambo-voice.js +1 -1
- package/esm/hooks/use-tambo-voice.js.map +1 -1
- package/esm/hooks/use-tambo-voice.test.d.ts +2 -0
- package/esm/hooks/use-tambo-voice.test.d.ts.map +1 -0
- package/esm/hooks/use-tambo-voice.test.js +234 -0
- package/esm/hooks/use-tambo-voice.test.js.map +1 -0
- package/esm/index.d.ts +2 -2
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js.map +1 -1
- package/esm/mcp/elicitation.d.ts.map +1 -1
- package/esm/mcp/elicitation.js +12 -0
- package/esm/mcp/elicitation.js.map +1 -1
- package/esm/mcp/elicitation.test.js +8 -1
- package/esm/mcp/elicitation.test.js.map +1 -1
- package/esm/mcp/mcp-client.d.ts +6 -10
- package/esm/mcp/mcp-client.d.ts.map +1 -1
- package/esm/mcp/mcp-client.js.map +1 -1
- package/esm/mcp/mcp-constants.d.ts +19 -0
- package/esm/mcp/mcp-constants.d.ts.map +1 -0
- package/esm/mcp/mcp-constants.js +18 -0
- package/esm/mcp/mcp-constants.js.map +1 -0
- package/esm/mcp/mcp-hooks.d.ts +21 -40
- package/esm/mcp/mcp-hooks.d.ts.map +1 -1
- package/esm/mcp/mcp-hooks.js +97 -40
- package/esm/mcp/mcp-hooks.js.map +1 -1
- package/esm/mcp/mcp-hooks.test.js +431 -5
- package/esm/mcp/mcp-hooks.test.js.map +1 -1
- package/esm/mcp/tambo-mcp-provider.d.ts +7 -0
- package/esm/mcp/tambo-mcp-provider.d.ts.map +1 -1
- package/esm/mcp/tambo-mcp-provider.js +204 -154
- package/esm/mcp/tambo-mcp-provider.js.map +1 -1
- package/esm/mcp/tambo-mcp-provider.test.js +37 -0
- package/esm/mcp/tambo-mcp-provider.test.js.map +1 -1
- package/esm/model/component-metadata.d.ts +54 -21
- package/esm/model/component-metadata.d.ts.map +1 -1
- package/esm/model/component-metadata.js.map +1 -1
- package/esm/model/tambo-interactable.d.ts +13 -5
- package/esm/model/tambo-interactable.d.ts.map +1 -1
- package/esm/model/tambo-interactable.js.map +1 -1
- package/esm/providers/__tests__/thread-input-resource-resolution.test.d.ts +2 -0
- package/esm/providers/__tests__/thread-input-resource-resolution.test.d.ts.map +1 -0
- package/esm/providers/__tests__/thread-input-resource-resolution.test.js +587 -0
- package/esm/providers/__tests__/thread-input-resource-resolution.test.js.map +1 -0
- package/esm/providers/index.d.ts +1 -1
- package/esm/providers/index.d.ts.map +1 -1
- package/esm/providers/index.js.map +1 -1
- package/esm/providers/tambo-client-provider.d.ts +8 -0
- package/esm/providers/tambo-client-provider.d.ts.map +1 -1
- package/esm/providers/tambo-client-provider.js +11 -12
- package/esm/providers/tambo-client-provider.js.map +1 -1
- package/esm/providers/tambo-client-provider.test.d.ts +2 -0
- package/esm/providers/tambo-client-provider.test.d.ts.map +1 -0
- package/esm/providers/tambo-client-provider.test.js +203 -0
- package/esm/providers/tambo-client-provider.test.js.map +1 -0
- package/esm/providers/tambo-context-attachment-provider.d.ts +34 -92
- package/esm/providers/tambo-context-attachment-provider.d.ts.map +1 -1
- package/esm/providers/tambo-context-attachment-provider.js +63 -106
- package/esm/providers/tambo-context-attachment-provider.js.map +1 -1
- package/esm/providers/tambo-context-attachment-provider.test.js +230 -464
- package/esm/providers/tambo-context-attachment-provider.test.js.map +1 -1
- package/esm/providers/tambo-interactable-provider-partial-updates.test.js +22 -21
- package/esm/providers/tambo-interactable-provider-partial-updates.test.js.map +1 -1
- package/esm/providers/tambo-interactable-provider.d.ts +5 -2
- package/esm/providers/tambo-interactable-provider.d.ts.map +1 -1
- package/esm/providers/tambo-interactable-provider.js +126 -17
- package/esm/providers/tambo-interactable-provider.js.map +1 -1
- package/esm/providers/tambo-interactable-provider.test.js +242 -0
- package/esm/providers/tambo-interactable-provider.test.js.map +1 -1
- package/esm/providers/tambo-interactables-additional-context.test.js +2 -5
- package/esm/providers/tambo-interactables-additional-context.test.js.map +1 -1
- package/esm/providers/tambo-provider.d.ts +2 -3
- package/esm/providers/tambo-provider.d.ts.map +1 -1
- package/esm/providers/tambo-provider.js +6 -5
- package/esm/providers/tambo-provider.js.map +1 -1
- package/esm/providers/tambo-registry-provider.test.js +16 -0
- package/esm/providers/tambo-registry-provider.test.js.map +1 -1
- package/esm/providers/tambo-registry-schema-compat.test.js +31 -0
- package/esm/providers/tambo-registry-schema-compat.test.js.map +1 -1
- package/esm/providers/tambo-thread-input-provider.d.ts +1 -1
- package/esm/providers/tambo-thread-input-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-input-provider.js +26 -4
- package/esm/providers/tambo-thread-input-provider.js.map +1 -1
- package/esm/providers/tambo-thread-provider-initial-messages.test.js +84 -2
- package/esm/providers/tambo-thread-provider-initial-messages.test.js.map +1 -1
- package/esm/providers/tambo-thread-provider.d.ts.map +1 -1
- package/esm/providers/tambo-thread-provider.js +53 -42
- package/esm/providers/tambo-thread-provider.js.map +1 -1
- package/esm/providers/tambo-thread-provider.test.js +368 -262
- package/esm/providers/tambo-thread-provider.test.js.map +1 -1
- package/esm/schema/index.d.ts +1 -1
- package/esm/schema/index.d.ts.map +1 -1
- package/esm/schema/index.js +1 -1
- package/esm/schema/index.js.map +1 -1
- package/esm/schema/json-schema.d.ts +7 -0
- package/esm/schema/json-schema.d.ts.map +1 -1
- package/esm/schema/json-schema.js +11 -1
- package/esm/schema/json-schema.js.map +1 -1
- package/esm/schema/json-schema.test.d.ts +2 -0
- package/esm/schema/json-schema.test.d.ts.map +1 -0
- package/esm/schema/json-schema.test.js +202 -0
- package/esm/schema/json-schema.test.js.map +1 -0
- package/esm/schema/schema.test.js +238 -1
- package/esm/schema/schema.test.js.map +1 -1
- package/esm/schema/standard-schema.d.ts +1 -0
- package/esm/schema/standard-schema.d.ts.map +1 -1
- package/esm/schema/standard-schema.js +18 -13
- package/esm/schema/standard-schema.js.map +1 -1
- package/esm/schema/standard-schema.test.d.ts +2 -0
- package/esm/schema/standard-schema.test.d.ts.map +1 -0
- package/esm/schema/standard-schema.test.js +130 -0
- package/esm/schema/standard-schema.test.js.map +1 -0
- package/esm/schema/validate.test.js +149 -0
- package/esm/schema/validate.test.js.map +1 -1
- package/esm/schema/zod.d.ts +7 -4
- package/esm/schema/zod.d.ts.map +1 -1
- package/esm/schema/zod.js +65 -22
- package/esm/schema/zod.js.map +1 -1
- package/esm/schema/zod.test.js +113 -1
- package/esm/schema/zod.test.js.map +1 -1
- package/esm/setupTests.js +3 -0
- package/esm/setupTests.js.map +1 -1
- package/esm/testing/tools.d.ts +4 -1
- package/esm/testing/tools.d.ts.map +1 -1
- package/esm/testing/tools.js +6 -1
- package/esm/testing/tools.js.map +1 -1
- package/esm/util/generate-component.d.ts.map +1 -1
- package/esm/util/generate-component.js +18 -3
- package/esm/util/generate-component.js.map +1 -1
- package/esm/util/generate-component.test.d.ts +2 -0
- package/esm/util/generate-component.test.d.ts.map +1 -0
- package/esm/util/generate-component.test.js +302 -0
- package/esm/util/generate-component.test.js.map +1 -0
- package/esm/util/is-promise.d.ts +9 -0
- package/esm/util/is-promise.d.ts.map +1 -0
- package/esm/util/is-promise.js +17 -0
- package/esm/util/is-promise.js.map +1 -0
- package/esm/util/is-promise.test.d.ts +2 -0
- package/esm/util/is-promise.test.d.ts.map +1 -0
- package/esm/util/is-promise.test.js +46 -0
- package/esm/util/is-promise.test.js.map +1 -0
- package/esm/util/message-builder.d.ts +3 -1
- package/esm/util/message-builder.d.ts.map +1 -1
- package/esm/util/message-builder.js +20 -3
- package/esm/util/message-builder.js.map +1 -1
- package/esm/util/message-builder.test.js +269 -0
- package/esm/util/message-builder.test.js.map +1 -1
- package/esm/util/query-utils.test.d.ts +2 -0
- package/esm/util/query-utils.test.d.ts.map +1 -0
- package/esm/util/query-utils.test.js +380 -0
- package/esm/util/query-utils.test.js.map +1 -0
- package/esm/util/registry-validators.d.ts.map +1 -1
- package/esm/util/registry-validators.js +7 -0
- package/esm/util/registry-validators.js.map +1 -1
- package/esm/util/registry-validators.test.js +57 -0
- package/esm/util/registry-validators.test.js.map +1 -1
- package/esm/util/registry.d.ts.map +1 -1
- package/esm/util/registry.js +9 -0
- package/esm/util/registry.js.map +1 -1
- package/esm/util/registry.test.js +324 -2
- package/esm/util/registry.test.js.map +1 -1
- package/esm/util/resource-content-resolver.d.ts +20 -0
- package/esm/util/resource-content-resolver.d.ts.map +1 -0
- package/esm/util/resource-content-resolver.js +89 -0
- package/esm/util/resource-content-resolver.js.map +1 -0
- package/esm/util/resource-content-resolver.test.d.ts +2 -0
- package/esm/util/resource-content-resolver.test.d.ts.map +1 -0
- package/esm/util/resource-content-resolver.test.js +252 -0
- package/esm/util/resource-content-resolver.test.js.map +1 -0
- package/esm/util/resource-validators.test.d.ts +2 -0
- package/esm/util/resource-validators.test.d.ts.map +1 -0
- package/esm/util/resource-validators.test.js +88 -0
- package/esm/util/resource-validators.test.js.map +1 -0
- package/esm/util/tool-caller.d.ts +2 -2
- package/esm/util/tool-caller.d.ts.map +1 -1
- package/esm/util/tool-caller.js +8 -8
- package/esm/util/tool-caller.js.map +1 -1
- package/esm/util/validate-component-name.test.d.ts +2 -0
- package/esm/util/validate-component-name.test.d.ts.map +1 -0
- package/esm/util/validate-component-name.test.js +33 -0
- package/esm/util/validate-component-name.test.js.map +1 -0
- package/package.json +15 -23
- package/dist/schema/alias.d.ts +0 -3
- package/dist/schema/alias.d.ts.map +0 -1
- package/dist/schema/alias.js +0 -6
- package/dist/schema/alias.js.map +0 -1
- package/esm/schema/alias.d.ts +0 -3
- package/esm/schema/alias.d.ts.map +0 -1
- package/esm/schema/alias.js +0 -13
- package/esm/schema/alias.js.map +0 -1
|
@@ -4,15 +4,16 @@ import { useMessageImages } from "./use-message-images";
|
|
|
4
4
|
global.crypto = {
|
|
5
5
|
randomUUID: jest.fn(() => "mock-uuid-" + Math.random()),
|
|
6
6
|
};
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
// Track FileReader instances for error simulation
|
|
8
|
+
let fileReaderInstances = [];
|
|
9
|
+
// Default FileReader mock that succeeds
|
|
10
|
+
const createSuccessfulFileReader = () => {
|
|
11
|
+
const reader = {
|
|
12
|
+
readAsDataURL: jest.fn(),
|
|
13
|
+
onload: null,
|
|
14
|
+
onerror: null,
|
|
15
|
+
result: "data:image/png;base64,mock-data",
|
|
16
|
+
};
|
|
16
17
|
reader.readAsDataURL = jest.fn(() => {
|
|
17
18
|
setTimeout(() => {
|
|
18
19
|
if (reader.onload) {
|
|
@@ -20,45 +21,181 @@ global.FileReader = jest.fn(() => {
|
|
|
20
21
|
}
|
|
21
22
|
}, 0);
|
|
22
23
|
});
|
|
24
|
+
fileReaderInstances.push(reader);
|
|
23
25
|
return reader;
|
|
24
|
-
}
|
|
26
|
+
};
|
|
27
|
+
// FileReader mock that fails
|
|
28
|
+
const createFailingFileReader = () => {
|
|
29
|
+
const reader = {
|
|
30
|
+
readAsDataURL: jest.fn(),
|
|
31
|
+
onload: null,
|
|
32
|
+
onerror: null,
|
|
33
|
+
result: "",
|
|
34
|
+
};
|
|
35
|
+
reader.readAsDataURL = jest.fn(() => {
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
if (reader.onerror) {
|
|
38
|
+
reader.onerror(new Error("Failed to read file"));
|
|
39
|
+
}
|
|
40
|
+
}, 0);
|
|
41
|
+
});
|
|
42
|
+
fileReaderInstances.push(reader);
|
|
43
|
+
return reader;
|
|
44
|
+
};
|
|
45
|
+
// Default to successful FileReader
|
|
46
|
+
global.FileReader = jest.fn(() => createSuccessfulFileReader());
|
|
25
47
|
describe("useMessageImages", () => {
|
|
26
48
|
beforeEach(() => {
|
|
27
49
|
jest.clearAllMocks();
|
|
50
|
+
fileReaderInstances = [];
|
|
51
|
+
// Reset to default successful FileReader
|
|
52
|
+
global.FileReader = jest.fn(() => createSuccessfulFileReader());
|
|
28
53
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
describe("Initialization", () => {
|
|
55
|
+
it("should initialize with empty images array", () => {
|
|
56
|
+
const { result } = renderHook(() => useMessageImages());
|
|
57
|
+
expect(result.current.images).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
it("should expose all management functions", () => {
|
|
60
|
+
const { result } = renderHook(() => useMessageImages());
|
|
61
|
+
expect(typeof result.current.addImage).toBe("function");
|
|
62
|
+
expect(typeof result.current.addImages).toBe("function");
|
|
63
|
+
expect(typeof result.current.removeImage).toBe("function");
|
|
64
|
+
expect(typeof result.current.clearImages).toBe("function");
|
|
65
|
+
});
|
|
32
66
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
67
|
+
describe("addImage", () => {
|
|
68
|
+
it("should add a valid image file", async () => {
|
|
69
|
+
const { result } = renderHook(() => useMessageImages());
|
|
70
|
+
const mockFile = new File(["image data"], "photo.png", {
|
|
71
|
+
type: "image/png",
|
|
72
|
+
});
|
|
73
|
+
await act(async () => {
|
|
74
|
+
await result.current.addImage(mockFile);
|
|
75
|
+
});
|
|
76
|
+
expect(result.current.images).toHaveLength(1);
|
|
77
|
+
expect(result.current.images[0].name).toBe("photo.png");
|
|
78
|
+
expect(result.current.images[0].type).toBe("image/png");
|
|
79
|
+
expect(result.current.images[0].dataUrl).toBe("data:image/png;base64,mock-data");
|
|
80
|
+
});
|
|
81
|
+
it("should reject non-image files", async () => {
|
|
82
|
+
const { result } = renderHook(() => useMessageImages());
|
|
83
|
+
const mockFile = new File(["test"], "test-document.pdf", {
|
|
84
|
+
type: "application/pdf",
|
|
85
|
+
});
|
|
86
|
+
await expect(result.current.addImage(mockFile)).rejects.toThrow("Only image files are allowed");
|
|
87
|
+
});
|
|
88
|
+
it("should reject when FileReader fails", async () => {
|
|
89
|
+
// Use failing FileReader
|
|
90
|
+
global.FileReader = jest.fn(() => createFailingFileReader());
|
|
91
|
+
const { result } = renderHook(() => useMessageImages());
|
|
92
|
+
const mockFile = new File(["image data"], "photo.png", {
|
|
93
|
+
type: "image/png",
|
|
94
|
+
});
|
|
95
|
+
await expect(result.current.addImage(mockFile)).rejects.toThrow();
|
|
37
96
|
});
|
|
38
|
-
await expect(result.current.addImage(mockFile)).rejects.toThrow("Only image files are allowed");
|
|
39
97
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
98
|
+
describe("addImages (batch)", () => {
|
|
99
|
+
it("should add multiple valid images at once", async () => {
|
|
100
|
+
const { result } = renderHook(() => useMessageImages());
|
|
101
|
+
const mockFiles = [
|
|
102
|
+
new File(["image1"], "photo1.png", { type: "image/png" }),
|
|
103
|
+
new File(["image2"], "photo2.jpg", { type: "image/jpeg" }),
|
|
104
|
+
new File(["image3"], "photo3.gif", { type: "image/gif" }),
|
|
105
|
+
];
|
|
106
|
+
await act(async () => {
|
|
107
|
+
await result.current.addImages(mockFiles);
|
|
108
|
+
});
|
|
109
|
+
expect(result.current.images).toHaveLength(3);
|
|
110
|
+
expect(result.current.images[0].name).toBe("photo1.png");
|
|
111
|
+
expect(result.current.images[1].name).toBe("photo2.jpg");
|
|
112
|
+
expect(result.current.images[2].name).toBe("photo3.gif");
|
|
113
|
+
});
|
|
114
|
+
it("should filter non-images from batch and add valid ones", async () => {
|
|
115
|
+
const { result } = renderHook(() => useMessageImages());
|
|
116
|
+
const mockFiles = [
|
|
117
|
+
new File(["image"], "photo.png", { type: "image/png" }),
|
|
118
|
+
new File(["pdf"], "document.pdf", { type: "application/pdf" }),
|
|
119
|
+
new File(["image"], "another.jpg", { type: "image/jpeg" }),
|
|
120
|
+
];
|
|
121
|
+
await act(async () => {
|
|
122
|
+
await result.current.addImages(mockFiles);
|
|
123
|
+
});
|
|
124
|
+
// Should only add the 2 valid images
|
|
125
|
+
expect(result.current.images).toHaveLength(2);
|
|
126
|
+
expect(result.current.images[0].name).toBe("photo.png");
|
|
127
|
+
expect(result.current.images[1].name).toBe("another.jpg");
|
|
128
|
+
});
|
|
129
|
+
it("should reject batch with zero valid images", async () => {
|
|
130
|
+
const { result } = renderHook(() => useMessageImages());
|
|
131
|
+
const mockFiles = [
|
|
132
|
+
new File(["test"], "document.pdf", { type: "application/pdf" }),
|
|
133
|
+
new File(["test"], "text.txt", { type: "text/plain" }),
|
|
134
|
+
];
|
|
135
|
+
await expect(result.current.addImages(mockFiles)).rejects.toThrow("No valid image files provided");
|
|
44
136
|
});
|
|
45
|
-
expect(result.current.images).toHaveLength(0);
|
|
46
137
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
138
|
+
describe("removeImage", () => {
|
|
139
|
+
it("should remove image by id", async () => {
|
|
140
|
+
const { result } = renderHook(() => useMessageImages());
|
|
141
|
+
const mockFile = new File(["image"], "photo.png", { type: "image/png" });
|
|
142
|
+
await act(async () => {
|
|
143
|
+
await result.current.addImage(mockFile);
|
|
144
|
+
});
|
|
145
|
+
const imageId = result.current.images[0].id;
|
|
146
|
+
act(() => {
|
|
147
|
+
result.current.removeImage(imageId);
|
|
148
|
+
});
|
|
149
|
+
expect(result.current.images).toHaveLength(0);
|
|
150
|
+
});
|
|
151
|
+
it("should handle removing non-existent image gracefully", async () => {
|
|
152
|
+
const { result } = renderHook(() => useMessageImages());
|
|
153
|
+
const mockFile = new File(["image"], "photo.png", { type: "image/png" });
|
|
154
|
+
await act(async () => {
|
|
155
|
+
await result.current.addImage(mockFile);
|
|
156
|
+
});
|
|
157
|
+
// Try to remove with a fake ID - should not throw or affect existing images
|
|
158
|
+
act(() => {
|
|
159
|
+
result.current.removeImage("non-existent-id");
|
|
160
|
+
});
|
|
161
|
+
// Original image should still be there
|
|
162
|
+
expect(result.current.images).toHaveLength(1);
|
|
163
|
+
});
|
|
164
|
+
it("should only remove the targeted image", async () => {
|
|
165
|
+
const { result } = renderHook(() => useMessageImages());
|
|
166
|
+
const files = [
|
|
167
|
+
new File(["image1"], "photo1.png", { type: "image/png" }),
|
|
168
|
+
new File(["image2"], "photo2.png", { type: "image/png" }),
|
|
169
|
+
new File(["image3"], "photo3.png", { type: "image/png" }),
|
|
170
|
+
];
|
|
171
|
+
await act(async () => {
|
|
172
|
+
await result.current.addImages(files);
|
|
173
|
+
});
|
|
174
|
+
const middleImageId = result.current.images[1].id;
|
|
175
|
+
act(() => {
|
|
176
|
+
result.current.removeImage(middleImageId);
|
|
177
|
+
});
|
|
178
|
+
expect(result.current.images).toHaveLength(2);
|
|
179
|
+
expect(result.current.images[0].name).toBe("photo1.png");
|
|
180
|
+
expect(result.current.images[1].name).toBe("photo3.png");
|
|
181
|
+
});
|
|
54
182
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
183
|
+
describe("clearImages", () => {
|
|
184
|
+
it("should remove all images", async () => {
|
|
185
|
+
const { result } = renderHook(() => useMessageImages());
|
|
186
|
+
const files = [
|
|
187
|
+
new File(["image1"], "photo1.png", { type: "image/png" }),
|
|
188
|
+
new File(["image2"], "photo2.png", { type: "image/png" }),
|
|
189
|
+
];
|
|
190
|
+
await act(async () => {
|
|
191
|
+
await result.current.addImages(files);
|
|
192
|
+
});
|
|
193
|
+
expect(result.current.images).toHaveLength(2);
|
|
194
|
+
act(() => {
|
|
195
|
+
result.current.clearImages();
|
|
196
|
+
});
|
|
197
|
+
expect(result.current.images).toHaveLength(0);
|
|
198
|
+
});
|
|
62
199
|
});
|
|
63
200
|
});
|
|
64
201
|
//# sourceMappingURL=use-message-images.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-message-images.test.js","sourceRoot":"","sources":["../../src/hooks/use-message-images.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { act, renderHook } from \"@testing-library/react\";\nimport { useMessageImages } from \"./use-message-images\";\n\n// Mock crypto.randomUUID\nglobal.crypto = {\n randomUUID: jest.fn(() => \"mock-uuid-\" + Math.random()),\n} as any;\n\n// Mock FileReader\nconst mockFileReader = {\n readAsDataURL: jest.fn(),\n onload: null as any,\n onerror: null as any,\n result: \"data:image/png;base64,mock-data\",\n};\n\n(global as any).FileReader = jest.fn(() => {\n const reader = { ...mockFileReader };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onload) {\n reader.onload({} as any);\n }\n }, 0);\n });\n return reader;\n});\n\ndescribe(\"useMessageImages\", () => {\n beforeEach(() => {\n jest.clearAllMocks();\n });\n\n it(\"should initialize with empty images array\", () => {\n const { result } = renderHook(() => useMessageImages());\n expect(result.current.images).toEqual([]);\n });\n\n it(\"should reject non-image files\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"test\"], \"test-document.pdf\", {\n type: \"application/pdf\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow(\n \"Only image files are allowed\",\n );\n });\n\n it(\"should clear all images\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n act(() => {\n result.current.clearImages();\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n\n it(\"should handle image validation correctly\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n // Test that hooks are available\n expect(typeof result.current.addImage).toBe(\"function\");\n expect(typeof result.current.addImages).toBe(\"function\");\n expect(typeof result.current.removeImage).toBe(\"function\");\n expect(typeof result.current.clearImages).toBe(\"function\");\n });\n\n it(\"should reject when no valid image files provided to addImages\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"test\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"test\"], \"text.txt\", { type: \"text/plain\" }),\n ];\n\n await expect(result.current.addImages(mockFiles)).rejects.toThrow(\n \"No valid image files provided\",\n );\n });\n});\n"]}
|
|
1
|
+
{"version":3,"file":"use-message-images.test.js","sourceRoot":"","sources":["../../src/hooks/use-message-images.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { act, renderHook } from \"@testing-library/react\";\nimport { useMessageImages } from \"./use-message-images\";\n\n// Mock crypto.randomUUID\nglobal.crypto = {\n randomUUID: jest.fn(() => \"mock-uuid-\" + Math.random()),\n} as any;\n\n// Track FileReader instances for error simulation\nlet fileReaderInstances: {\n readAsDataURL: jest.Mock;\n onload: ((e: unknown) => void) | null;\n onerror: ((e: unknown) => void) | null;\n result: string;\n}[] = [];\n\n// Default FileReader mock that succeeds\nconst createSuccessfulFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: any) => void) | null,\n onerror: null as ((e: any) => void) | null,\n result: \"data:image/png;base64,mock-data\",\n };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onload) {\n reader.onload({} as any);\n }\n }, 0);\n });\n fileReaderInstances.push(reader);\n return reader;\n};\n\n// FileReader mock that fails\nconst createFailingFileReader = () => {\n const reader = {\n readAsDataURL: jest.fn(),\n onload: null as ((e: any) => void) | null,\n onerror: null as ((e: any) => void) | null,\n result: \"\",\n };\n reader.readAsDataURL = jest.fn(() => {\n setTimeout(() => {\n if (reader.onerror) {\n reader.onerror(new Error(\"Failed to read file\"));\n }\n }, 0);\n });\n fileReaderInstances.push(reader);\n return reader;\n};\n\n// Default to successful FileReader\n(global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n\ndescribe(\"useMessageImages\", () => {\n beforeEach(() => {\n jest.clearAllMocks();\n fileReaderInstances = [];\n // Reset to default successful FileReader\n (global as any).FileReader = jest.fn(() => createSuccessfulFileReader());\n });\n\n describe(\"Initialization\", () => {\n it(\"should initialize with empty images array\", () => {\n const { result } = renderHook(() => useMessageImages());\n expect(result.current.images).toEqual([]);\n });\n\n it(\"should expose all management functions\", () => {\n const { result } = renderHook(() => useMessageImages());\n\n expect(typeof result.current.addImage).toBe(\"function\");\n expect(typeof result.current.addImages).toBe(\"function\");\n expect(typeof result.current.removeImage).toBe(\"function\");\n expect(typeof result.current.clearImages).toBe(\"function\");\n });\n });\n\n describe(\"addImage\", () => {\n it(\"should add a valid image file\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image data\"], \"photo.png\", {\n type: \"image/png\",\n });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n expect(result.current.images).toHaveLength(1);\n expect(result.current.images[0].name).toBe(\"photo.png\");\n expect(result.current.images[0].type).toBe(\"image/png\");\n expect(result.current.images[0].dataUrl).toBe(\n \"data:image/png;base64,mock-data\",\n );\n });\n\n it(\"should reject non-image files\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"test\"], \"test-document.pdf\", {\n type: \"application/pdf\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow(\n \"Only image files are allowed\",\n );\n });\n\n it(\"should reject when FileReader fails\", async () => {\n // Use failing FileReader\n (global as any).FileReader = jest.fn(() => createFailingFileReader());\n\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image data\"], \"photo.png\", {\n type: \"image/png\",\n });\n\n await expect(result.current.addImage(mockFile)).rejects.toThrow();\n });\n });\n\n describe(\"addImages (batch)\", () => {\n it(\"should add multiple valid images at once\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.jpg\", { type: \"image/jpeg\" }),\n new File([\"image3\"], \"photo3.gif\", { type: \"image/gif\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(mockFiles);\n });\n\n expect(result.current.images).toHaveLength(3);\n expect(result.current.images[0].name).toBe(\"photo1.png\");\n expect(result.current.images[1].name).toBe(\"photo2.jpg\");\n expect(result.current.images[2].name).toBe(\"photo3.gif\");\n });\n\n it(\"should filter non-images from batch and add valid ones\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"image\"], \"photo.png\", { type: \"image/png\" }),\n new File([\"pdf\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"image\"], \"another.jpg\", { type: \"image/jpeg\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(mockFiles);\n });\n\n // Should only add the 2 valid images\n expect(result.current.images).toHaveLength(2);\n expect(result.current.images[0].name).toBe(\"photo.png\");\n expect(result.current.images[1].name).toBe(\"another.jpg\");\n });\n\n it(\"should reject batch with zero valid images\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFiles = [\n new File([\"test\"], \"document.pdf\", { type: \"application/pdf\" }),\n new File([\"test\"], \"text.txt\", { type: \"text/plain\" }),\n ];\n\n await expect(result.current.addImages(mockFiles)).rejects.toThrow(\n \"No valid image files provided\",\n );\n });\n });\n\n describe(\"removeImage\", () => {\n it(\"should remove image by id\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image\"], \"photo.png\", { type: \"image/png\" });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n const imageId = result.current.images[0].id;\n\n act(() => {\n result.current.removeImage(imageId);\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n\n it(\"should handle removing non-existent image gracefully\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const mockFile = new File([\"image\"], \"photo.png\", { type: \"image/png\" });\n\n await act(async () => {\n await result.current.addImage(mockFile);\n });\n\n // Try to remove with a fake ID - should not throw or affect existing images\n act(() => {\n result.current.removeImage(\"non-existent-id\");\n });\n\n // Original image should still be there\n expect(result.current.images).toHaveLength(1);\n });\n\n it(\"should only remove the targeted image\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const files = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.png\", { type: \"image/png\" }),\n new File([\"image3\"], \"photo3.png\", { type: \"image/png\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(files);\n });\n\n const middleImageId = result.current.images[1].id;\n\n act(() => {\n result.current.removeImage(middleImageId);\n });\n\n expect(result.current.images).toHaveLength(2);\n expect(result.current.images[0].name).toBe(\"photo1.png\");\n expect(result.current.images[1].name).toBe(\"photo3.png\");\n });\n });\n\n describe(\"clearImages\", () => {\n it(\"should remove all images\", async () => {\n const { result } = renderHook(() => useMessageImages());\n const files = [\n new File([\"image1\"], \"photo1.png\", { type: \"image/png\" }),\n new File([\"image2\"], \"photo2.png\", { type: \"image/png\" }),\n ];\n\n await act(async () => {\n await result.current.addImages(files);\n });\n\n expect(result.current.images).toHaveLength(2);\n\n act(() => {\n result.current.clearImages();\n });\n\n expect(result.current.images).toHaveLength(0);\n });\n });\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-tambo-threads.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-threads.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,EAAE,SAAS,KAA+B,EAAE,EAC5C,UAEI,EAAE;IAEN,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;IACxC,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,cAAc,EAAE,GAAG,aAAa,CAAC;QAClE,GAAI,OAA8C;QAClD,QAAQ,EAAE,CAAC,WAAW,CAAC;QACvB,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC;QACtD,CAAC;KACF,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,SAAS,IAAI,gBAAgB,CAAC;IAEvD,MAAM,WAAW,GAAG,aAAa,CAAC;QAChC,GAAG,OAAO;QACV,OAAO,EAAE,CAAC,CAAC,gBAAgB;QAC3B,QAAQ,EAAE,CAAC,SAAS,EAAE,gBAAgB,EAAE,UAAU,CAAC;QACnD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE;gBAClE,UAAU
|
|
1
|
+
{"version":3,"file":"use-tambo-threads.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-threads.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,EAAE,SAAS,KAA+B,EAAE,EAC5C,UAEI,EAAE;IAEN,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;IACxC,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,cAAc,EAAE,GAAG,aAAa,CAAC;QAClE,GAAI,OAA8C;QAClD,QAAQ,EAAE,CAAC,WAAW,CAAC;QACvB,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC;QACtD,CAAC;KACF,CAAC,CAAC;IACH,MAAM,gBAAgB,GAAG,SAAS,IAAI,gBAAgB,CAAC;IAEvD,MAAM,WAAW,GAAG,aAAa,CAAC;QAChC,GAAG,OAAO;QACV,OAAO,EAAE,CAAC,CAAC,gBAAgB;QAC3B,QAAQ,EAAE,CAAC,SAAS,EAAE,gBAAgB,EAAE,UAAU,CAAC;QACnD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtB,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE;gBAClE,UAAU;aACX,CAAC,CAAC;YACH,OAAO,UAAU,CAAC;QACpB,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAAC;AAC5E,CAAC","sourcesContent":["import type TamboAI from \"@tambo-ai/typescript-sdk\";\nimport { UseQueryOptions } from \"@tanstack/react-query\";\nimport { useTamboThread } from \"../providers\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboQuery } from \"./react-query-hooks\";\n\ninterface UseTamboThreadListConfig {\n /**\n * The projectId to get the threads for. If not provided, the current project\n * will be used.\n */\n projectId?: string;\n}\n\n/**\n * Get all the threads for the specified project.\n *\n * If contextKey is empty, then all threads for the project will be returned.\n * If contextKey is not empty, then only the threads for the specified context\n * key will be returned. The contextKey is obtained from the TamboThreadProvider\n * via useTamboThread().\n * @param config - The config for the useTamboThreadList hook\n * @param config.projectId - The projectId to get the threads for\n * @returns The threads for the specified project and optional context key\n */\nexport function useTamboThreadList(\n { projectId }: UseTamboThreadListConfig = {},\n options: Partial<\n UseQueryOptions<TamboAI.Beta.Threads.ThreadsOffsetAndLimit | null>\n > = {},\n) {\n const client = useTamboClient();\n const { contextKey } = useTamboThread();\n const { data: queriedProjectId, ...projectIdState } = useTamboQuery({\n ...(options as unknown as UseQueryOptions<string>),\n queryKey: [\"projectId\"],\n queryFn: async () => {\n return (await client.beta.projects.getCurrent()).id;\n },\n });\n const currentProjectId = projectId ?? queriedProjectId;\n\n const threadState = useTamboQuery({\n ...options,\n enabled: !!currentProjectId,\n queryKey: [\"threads\", currentProjectId, contextKey],\n queryFn: async () => {\n if (!currentProjectId) {\n return null;\n }\n const threadIter = await client.beta.threads.list(currentProjectId, {\n contextKey,\n });\n return threadIter;\n },\n });\n\n return currentProjectId ? threadState : { data: null, ...projectIdState };\n}\n"]}
|
|
@@ -13,7 +13,7 @@ export declare function useTamboVoice(): {
|
|
|
13
13
|
startRecording: () => void;
|
|
14
14
|
stopRecording: () => void;
|
|
15
15
|
isRecording: boolean;
|
|
16
|
-
mediaAccessError: string;
|
|
16
|
+
mediaAccessError: string | null;
|
|
17
17
|
isTranscribing: boolean;
|
|
18
18
|
transcript: string | null;
|
|
19
19
|
transcriptionError: string | null;
|
|
@@ -62,7 +62,7 @@ export function useTamboVoice() {
|
|
|
62
62
|
startRecording,
|
|
63
63
|
stopRecording,
|
|
64
64
|
isRecording,
|
|
65
|
-
mediaAccessError: mediaAccessError
|
|
65
|
+
mediaAccessError: mediaAccessError === "" ? null : mediaAccessError,
|
|
66
66
|
isTranscribing: transcriptionMutation.isPending,
|
|
67
67
|
transcript,
|
|
68
68
|
transcriptionError: transcriptionMutation.error?.message ?? null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-tambo-voice.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EACJ,MAAM,EACN,cAAc,EAAE,mBAAmB,EACnC,aAAa,EAAE,kBAAkB,EACjC,YAAY,EACZ,KAAK,EAAE,gBAAgB,GACxB,GAAG,qBAAqB,CAAC;QACxB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;QACZ,eAAe,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,WAAW,CAAC;IAE3C,MAAM,qBAAqB,GAAG,gBAAgB,CAI5C;QACA,UAAU,EAAE,KAAK,EAAE,OAAe,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE;gBACnD,IAAI,EAAE,YAAY;aACnB,CAAC,CAAC;YAEH,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,EAAE,CAAC,aAAqB,EAAE,EAAE;YACnC,aAAa,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;IAEH,oEAAoE;IACpE,MAAM,gBAAgB,GACpB,MAAM,KAAK,SAAS;QACpB,YAAY;QACZ,CAAC,qBAAqB,CAAC,SAAS;QAChC,CAAC,qBAAqB,CAAC,SAAS,CAAC;IAEnC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,gBAAgB,EAAE,CAAC;YACrB,qBAAqB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE5D,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,IAAI,WAAW;YAAE,OAAO;QAExB,0CAA0C;QAC1C,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,oCAAoC;QACnE,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,IAAI,WAAW,EAAE,CAAC;YAChB,kBAAkB,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEtC,OAAO;QACL,cAAc;QACd,aAAa;QACb,WAAW;QACX,gBAAgB,EAAE,gBAAgB,IAAI,
|
|
1
|
+
{"version":3,"file":"use-tambo-voice.js","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,EACJ,MAAM,EACN,cAAc,EAAE,mBAAmB,EACnC,aAAa,EAAE,kBAAkB,EACjC,YAAY,EACZ,KAAK,EAAE,gBAAgB,GACxB,GAAG,qBAAqB,CAAC;QACxB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;QACZ,eAAe,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,MAAM,KAAK,WAAW,CAAC;IAE3C,MAAM,qBAAqB,GAAG,gBAAgB,CAI5C;QACA,UAAU,EAAE,KAAK,EAAE,OAAe,EAAE,EAAE;YACpC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE;gBACnD,IAAI,EAAE,YAAY;aACnB,CAAC,CAAC;YAEH,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,SAAS,EAAE,CAAC,aAAqB,EAAE,EAAE;YACnC,aAAa,CAAC,aAAa,CAAC,CAAC;QAC/B,CAAC;KACF,CAAC,CAAC;IAEH,oEAAoE;IACpE,MAAM,gBAAgB,GACpB,MAAM,KAAK,SAAS;QACpB,YAAY;QACZ,CAAC,qBAAqB,CAAC,SAAS;QAChC,CAAC,qBAAqB,CAAC,SAAS,CAAC;IAEnC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,gBAAgB,EAAE,CAAC;YACrB,qBAAqB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC,EAAE,CAAC,gBAAgB,EAAE,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE5D,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,IAAI,WAAW;YAAE,OAAO;QAExB,0CAA0C;QAC1C,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,oCAAoC;QACnE,mBAAmB,EAAE,CAAC;IACxB,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAE9D,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,IAAI,WAAW,EAAE,CAAC;YAChB,kBAAkB,EAAE,CAAC;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IAEtC,OAAO;QACL,cAAc;QACd,aAAa;QACb,WAAW;QACX,gBAAgB,EAAE,gBAAgB,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB;QACnE,cAAc,EAAE,qBAAqB,CAAC,SAAS;QAC/C,UAAU;QACV,kBAAkB,EAAE,qBAAqB,CAAC,KAAK,EAAE,OAAO,IAAI,IAAI;KACjE,CAAC;AACJ,CAAC","sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { useReactMediaRecorder } from \"react-media-recorder\";\nimport { useTamboClient } from \"../providers/tambo-client-provider\";\nimport { useTamboMutation } from \"./react-query-hooks\";\n\n/**\n * Exposes functionality to record speech and transcribe it using the Tambo API.\n * @returns An object with:\n * - startRecording: A function to start recording audio and reset the current transcript.\n * - stopRecording: A function to stop recording audio and automatically kick off transcription.\n * - isRecording: A boolean indicating if the user is recording audio.\n * - isTranscribing: A boolean indicating if the audio is being transcribed.\n * - transcript: The transcript of the recorded audio.\n * - transcriptionError: An error message if the transcription fails.\n * - mediaAccessError: An error message if microphone access fails.\n */\nexport function useTamboVoice() {\n const [transcript, setTranscript] = useState<string | null>(null);\n const client = useTamboClient();\n const {\n status,\n startRecording: startMediaRecording,\n stopRecording: stopMediaRecording,\n mediaBlobUrl,\n error: mediaAccessError,\n } = useReactMediaRecorder({\n audio: true,\n video: false,\n blobPropertyBag: { type: \"audio/webm\" },\n });\n\n const isRecording = status === \"recording\";\n\n const transcriptionMutation = useTamboMutation<\n string,\n Error,\n string // blobUrl parameter\n >({\n mutationFn: async (blobUrl: string) => {\n const response = await fetch(blobUrl);\n const audioBlob = await response.blob();\n const file = new File([audioBlob], \"recording.webm\", {\n type: \"audio/webm\",\n });\n\n return await client.beta.audio.transcribe({ file });\n },\n onSuccess: (transcription: string) => {\n setTranscript(transcription);\n },\n });\n\n // Trigger transcription when recording stops and we have a blob URL\n const shouldTranscribe =\n status === \"stopped\" &&\n mediaBlobUrl &&\n !transcriptionMutation.isPending &&\n !transcriptionMutation.isSuccess;\n\n useEffect(() => {\n if (shouldTranscribe) {\n transcriptionMutation.mutate(mediaBlobUrl);\n }\n }, [shouldTranscribe, mediaBlobUrl, transcriptionMutation]);\n\n const startRecording = useCallback(() => {\n if (isRecording) return;\n\n // Reset state when starting new recording\n setTranscript(null);\n transcriptionMutation.reset(); // Clear any previous mutation state\n startMediaRecording();\n }, [isRecording, startMediaRecording, transcriptionMutation]);\n\n const stopRecording = useCallback(() => {\n if (isRecording) {\n stopMediaRecording();\n }\n }, [isRecording, stopMediaRecording]);\n\n return {\n startRecording,\n stopRecording,\n isRecording,\n mediaAccessError: mediaAccessError === \"\" ? null : mediaAccessError,\n isTranscribing: transcriptionMutation.isPending,\n transcript,\n transcriptionError: transcriptionMutation.error?.message ?? null,\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-tambo-voice.test.d.ts","sourceRoot":"","sources":["../../src/hooks/use-tambo-voice.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { useReactMediaRecorder } from "react-media-recorder";
|
|
5
|
+
import { useTamboVoice } from "./use-tambo-voice";
|
|
6
|
+
import { TamboClientContext } from "../providers/tambo-client-provider";
|
|
7
|
+
// Override the global mock from setupTests.ts with a controllable version
|
|
8
|
+
jest.mock("react-media-recorder", () => ({
|
|
9
|
+
useReactMediaRecorder: jest.fn(),
|
|
10
|
+
}));
|
|
11
|
+
// Mock the client provider
|
|
12
|
+
jest.mock("../providers/tambo-client-provider", () => ({
|
|
13
|
+
...jest.requireActual("../providers/tambo-client-provider"),
|
|
14
|
+
useTamboClient: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
import { useTamboClient } from "../providers/tambo-client-provider";
|
|
17
|
+
// Mock fetch globally
|
|
18
|
+
const mockFetch = jest.fn();
|
|
19
|
+
describe("useTamboVoice", () => {
|
|
20
|
+
let previousFetch;
|
|
21
|
+
let mockStartRecording;
|
|
22
|
+
let mockStopRecording;
|
|
23
|
+
let mockTranscribe;
|
|
24
|
+
let queryClient;
|
|
25
|
+
const createWrapper = () => {
|
|
26
|
+
const mockClient = {
|
|
27
|
+
beta: {
|
|
28
|
+
audio: {
|
|
29
|
+
transcribe: mockTranscribe,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const Wrapper = ({ children }) => (React.createElement(TamboClientContext.Provider, { value: {
|
|
34
|
+
client: mockClient,
|
|
35
|
+
queryClient,
|
|
36
|
+
isUpdatingToken: false,
|
|
37
|
+
} },
|
|
38
|
+
React.createElement(QueryClientProvider, { client: queryClient }, children)));
|
|
39
|
+
Wrapper.displayName = "TestWrapper";
|
|
40
|
+
return Wrapper;
|
|
41
|
+
};
|
|
42
|
+
const setupMediaRecorderMock = (overrides = {}) => {
|
|
43
|
+
const value = {
|
|
44
|
+
status: "idle",
|
|
45
|
+
startRecording: mockStartRecording,
|
|
46
|
+
stopRecording: mockStopRecording,
|
|
47
|
+
mediaBlobUrl: undefined,
|
|
48
|
+
error: "",
|
|
49
|
+
...overrides,
|
|
50
|
+
};
|
|
51
|
+
jest
|
|
52
|
+
.mocked(useReactMediaRecorder)
|
|
53
|
+
.mockReturnValue(value);
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
jest.clearAllMocks();
|
|
58
|
+
mockFetch.mockReset();
|
|
59
|
+
previousFetch = global.fetch;
|
|
60
|
+
global.fetch = mockFetch;
|
|
61
|
+
mockStartRecording = jest.fn();
|
|
62
|
+
mockStopRecording = jest.fn();
|
|
63
|
+
mockTranscribe = jest.fn();
|
|
64
|
+
queryClient = new QueryClient({
|
|
65
|
+
defaultOptions: {
|
|
66
|
+
queries: { retry: false },
|
|
67
|
+
mutations: { retry: false },
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// Setup default client mock
|
|
71
|
+
const mockClient = {
|
|
72
|
+
beta: {
|
|
73
|
+
audio: {
|
|
74
|
+
transcribe: mockTranscribe,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
jest
|
|
79
|
+
.mocked(useTamboClient)
|
|
80
|
+
.mockReturnValue(mockClient);
|
|
81
|
+
// Setup default media recorder mock
|
|
82
|
+
setupMediaRecorderMock();
|
|
83
|
+
// Setup default fetch mock
|
|
84
|
+
mockFetch.mockResolvedValue({
|
|
85
|
+
blob: async () => await Promise.resolve(new Blob(["audio data"], { type: "audio/webm" })),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
afterEach(() => {
|
|
89
|
+
global.fetch = previousFetch;
|
|
90
|
+
});
|
|
91
|
+
describe("Initial State", () => {
|
|
92
|
+
it("should initialize with idle state and no transcript", () => {
|
|
93
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
94
|
+
wrapper: createWrapper(),
|
|
95
|
+
});
|
|
96
|
+
expect(result.current.isRecording).toBe(false);
|
|
97
|
+
expect(result.current.isTranscribing).toBe(false);
|
|
98
|
+
expect(result.current.transcript).toBeNull();
|
|
99
|
+
expect(result.current.transcriptionError).toBeNull();
|
|
100
|
+
expect(result.current.mediaAccessError).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("Recording Flow", () => {
|
|
104
|
+
it("should expose isRecording=true during active recording", () => {
|
|
105
|
+
setupMediaRecorderMock({ status: "recording" });
|
|
106
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
107
|
+
wrapper: createWrapper(),
|
|
108
|
+
});
|
|
109
|
+
expect(result.current.isRecording).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
it("should call startRecording on the media recorder", () => {
|
|
112
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
113
|
+
wrapper: createWrapper(),
|
|
114
|
+
});
|
|
115
|
+
act(() => {
|
|
116
|
+
result.current.startRecording();
|
|
117
|
+
});
|
|
118
|
+
expect(mockStartRecording).toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
it("should call stopRecording on the media recorder when recording", () => {
|
|
121
|
+
setupMediaRecorderMock({ status: "recording" });
|
|
122
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
123
|
+
wrapper: createWrapper(),
|
|
124
|
+
});
|
|
125
|
+
act(() => {
|
|
126
|
+
result.current.stopRecording();
|
|
127
|
+
});
|
|
128
|
+
expect(mockStopRecording).toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
it("should reset transcript when starting a new recording", async () => {
|
|
131
|
+
mockTranscribe.mockResolvedValue("first transcript");
|
|
132
|
+
// Start with completed transcription
|
|
133
|
+
setupMediaRecorderMock({
|
|
134
|
+
status: "stopped",
|
|
135
|
+
mediaBlobUrl: "blob:http://localhost/audio1",
|
|
136
|
+
});
|
|
137
|
+
const { result, rerender } = renderHook(() => useTamboVoice(), {
|
|
138
|
+
wrapper: createWrapper(),
|
|
139
|
+
});
|
|
140
|
+
// Wait for transcription to complete
|
|
141
|
+
await waitFor(() => {
|
|
142
|
+
expect(result.current.transcript).toBe("first transcript");
|
|
143
|
+
});
|
|
144
|
+
// Now simulate starting a new recording
|
|
145
|
+
setupMediaRecorderMock({ status: "idle" });
|
|
146
|
+
rerender();
|
|
147
|
+
act(() => {
|
|
148
|
+
result.current.startRecording();
|
|
149
|
+
});
|
|
150
|
+
expect(result.current.transcript).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe("Transcription", () => {
|
|
154
|
+
it("should trigger transcription after recording stops with blob URL", async () => {
|
|
155
|
+
mockTranscribe.mockResolvedValue("Hello world");
|
|
156
|
+
setupMediaRecorderMock({
|
|
157
|
+
status: "stopped",
|
|
158
|
+
mediaBlobUrl: "blob:http://localhost/audio",
|
|
159
|
+
});
|
|
160
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
161
|
+
wrapper: createWrapper(),
|
|
162
|
+
});
|
|
163
|
+
await waitFor(() => {
|
|
164
|
+
expect(result.current.transcript).toBe("Hello world");
|
|
165
|
+
});
|
|
166
|
+
expect(mockFetch).toHaveBeenCalledWith("blob:http://localhost/audio");
|
|
167
|
+
expect(mockTranscribe).toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
it("should expose isTranscribing=true during API call", async () => {
|
|
170
|
+
let resolveTranscription;
|
|
171
|
+
mockTranscribe.mockImplementation(async () => await new Promise((resolve) => {
|
|
172
|
+
resolveTranscription = resolve;
|
|
173
|
+
}));
|
|
174
|
+
setupMediaRecorderMock({
|
|
175
|
+
status: "stopped",
|
|
176
|
+
mediaBlobUrl: "blob:http://localhost/audio",
|
|
177
|
+
});
|
|
178
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
179
|
+
wrapper: createWrapper(),
|
|
180
|
+
});
|
|
181
|
+
await waitFor(() => {
|
|
182
|
+
expect(result.current.isTranscribing).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
// Complete the transcription
|
|
185
|
+
act(() => {
|
|
186
|
+
resolveTranscription("transcribed text");
|
|
187
|
+
});
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
expect(result.current.isTranscribing).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe("Error Handling", () => {
|
|
194
|
+
it("should expose mediaAccessError when microphone access fails", () => {
|
|
195
|
+
setupMediaRecorderMock({
|
|
196
|
+
error: "Permission denied",
|
|
197
|
+
});
|
|
198
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
199
|
+
wrapper: createWrapper(),
|
|
200
|
+
});
|
|
201
|
+
expect(result.current.mediaAccessError).toBe("Permission denied");
|
|
202
|
+
});
|
|
203
|
+
it("should expose transcriptionError when API call fails", async () => {
|
|
204
|
+
mockTranscribe.mockRejectedValue(new Error("Transcription service unavailable"));
|
|
205
|
+
setupMediaRecorderMock({
|
|
206
|
+
status: "stopped",
|
|
207
|
+
mediaBlobUrl: "blob:http://localhost/audio",
|
|
208
|
+
});
|
|
209
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
210
|
+
wrapper: createWrapper(),
|
|
211
|
+
});
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(result.current.transcriptionError).toBe("Transcription service unavailable");
|
|
214
|
+
});
|
|
215
|
+
expect(result.current.isTranscribing).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
it("should handle blob fetch failure gracefully", async () => {
|
|
218
|
+
mockFetch.mockRejectedValue(new Error("Network error"));
|
|
219
|
+
setupMediaRecorderMock({
|
|
220
|
+
status: "stopped",
|
|
221
|
+
mediaBlobUrl: "blob:http://localhost/audio",
|
|
222
|
+
});
|
|
223
|
+
const { result } = renderHook(() => useTamboVoice(), {
|
|
224
|
+
wrapper: createWrapper(),
|
|
225
|
+
});
|
|
226
|
+
await waitFor(() => {
|
|
227
|
+
expect(result.current.transcriptionError).toBe("Network error");
|
|
228
|
+
});
|
|
229
|
+
expect(result.current.isTranscribing).toBe(false);
|
|
230
|
+
expect(result.current.transcript).toBeNull();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
//# sourceMappingURL=use-tambo-voice.test.js.map
|