diff --git a/index.ts b/index.ts index b9187ff..31e8026 100644 --- a/index.ts +++ b/index.ts @@ -7,6 +7,4 @@ export * from '@/lib/Client'; export * from '@/lib/types'; export * from '@/lib/Utils'; -export { TraceType } from '@voiceflow/general-types'; - export default RuntimeClientFactory; diff --git a/lib/Client/adapters/addAudioSrc.ts b/lib/Client/adapters/addAudioSrc.ts new file mode 100644 index 0000000..41f4bbe --- /dev/null +++ b/lib/Client/adapters/addAudioSrc.ts @@ -0,0 +1,47 @@ +import { GeneralTrace as DBGeneralTrace, SpeakTrace, TraceType } from '@voiceflow/general-types'; +import { SpeakType } from '@voiceflow/general-types/build/nodes/speak'; +import htmlParse from 'html-parse-stringify'; + +import { DBResponseContext } from './types'; + +/** + * WORK-AROUND function to deal with bug where enabling `tts` will cause the Audio Step's audio + * file url to not generate. Better solution is to decouple the TTS and audio handlers in our + * backend and remove `parseAudioStepSrc` and `adaptResponseContext` + */ +export const parseAudioStepSrc = (trace: DBGeneralTrace): DBGeneralTrace => { + if (trace.type !== TraceType.SPEAK) { + return trace; + } + + const node = htmlParse.parse(trace.payload.message)[0]; + + if (!node || node.name !== 'audio') { + return { + ...trace, + payload: { + ...trace.payload, + type: SpeakType.MESSAGE, + }, + } as SpeakTrace; + } + + const audioSrc = node.attrs.src; + + return { + ...trace, + payload: { + ...trace.payload, + type: SpeakType.AUDIO, + src: audioSrc, + }, + }; +}; + +/** + * WORK-AROUND function, see `parseAudioStepSrc` above + */ +export const adaptResponseContext = (context: DBResponseContext): DBResponseContext => ({ + ...context, + trace: context.trace.map(parseAudioStepSrc), +}); diff --git a/lib/Client/adapters/extractAudioStep.ts b/lib/Client/adapters/extractAudioStep.ts new file mode 100644 index 0000000..eaa1e5a --- /dev/null +++ b/lib/Client/adapters/extractAudioStep.ts @@ -0,0 +1,21 @@ +import { GeneralTrace as DBGeneralTrace, TraceType as DBTraceType } from '@voiceflow/general-types'; +import { SpeakType } from '@voiceflow/general-types/build/nodes/speak'; + +import { GeneralTrace, ResponseContext, TraceType } from '@/lib/types'; + +export const extractAudioStep = (context: Omit & { trace: DBGeneralTrace[] }) => ({ + ...context, + trace: context.trace.map((trace) => { + if (trace.type !== DBTraceType.SPEAK) { + return (trace as unknown) as GeneralTrace; + } + const { type, ...payload } = trace.payload; + + return ({ + type: type === SpeakType.MESSAGE ? TraceType.SPEAK : TraceType.AUDIO, + payload, + } as unknown) as GeneralTrace; + }), +}); + +export default extractAudioStep; diff --git a/lib/Client/adapters/index.ts b/lib/Client/adapters/index.ts index 8bb8de5..8f78120 100644 --- a/lib/Client/adapters/index.ts +++ b/lib/Client/adapters/index.ts @@ -1,47 +1,2 @@ -import { GeneralTrace, SpeakTrace, TraceType } from '@voiceflow/general-types'; -import { SpeakType } from '@voiceflow/general-types/build/nodes/speak'; -import htmlParse from 'html-parse-stringify'; - -import { ResponseContext } from '../../types'; - -/** - * WORK-AROUND function to deal with bug where enabling `tts` will cause the Audio Step's audio - * file url to not generate. Better solution is to decouple the TTS and audio handlers in our - * backend and remove `parseAudioStepSrc` and `adaptResponseContext` - */ -export const parseAudioStepSrc = (trace: GeneralTrace): GeneralTrace => { - if (trace.type !== TraceType.SPEAK) { - return trace; - } - - const node = htmlParse.parse(trace.payload.message)[0]; - - if (!node || node.name !== 'audio') { - return { - ...trace, - payload: { - ...trace.payload, - type: SpeakType.MESSAGE, - }, - } as SpeakTrace; - } - - const audioSrc = node.attrs.src; - - return { - ...trace, - payload: { - ...trace.payload, - type: SpeakType.AUDIO, - src: audioSrc, - }, - }; -}; - -/** - * WORK-AROUND function, see `parseAudioStepSrc` above - */ -export const adaptResponseContext = (context: ResponseContext) => ({ - ...context, - trace: context.trace.map(parseAudioStepSrc), -}); +export { adaptResponseContext } from './addAudioSrc'; +export { extractAudioStep } from './extractAudioStep'; diff --git a/lib/Client/adapters/types.ts b/lib/Client/adapters/types.ts new file mode 100644 index 0000000..99e9d4f --- /dev/null +++ b/lib/Client/adapters/types.ts @@ -0,0 +1,7 @@ +import { GeneralTrace as DBGeneralTrace } from '@voiceflow/general-types'; + +import { ResponseContext } from '@/lib/types'; + +export type DBResponseContext = Omit & { + trace: DBGeneralTrace[]; +}; diff --git a/lib/Client/index.ts b/lib/Client/index.ts index 146be9f..843c296 100644 --- a/lib/Client/index.ts +++ b/lib/Client/index.ts @@ -4,7 +4,7 @@ import _cloneDeep from 'lodash/cloneDeep'; import { RequestContext, ResponseContext } from '@/lib/types'; -import { adaptResponseContext } from './adapters'; +import { adaptResponseContext, extractAudioStep } from './adapters'; export type ClientConfig = { variables?: Partial; endpoint: string; versionID: string }; @@ -42,7 +42,8 @@ export class Client = Record> { return this.axios .post(`/interact/${this.versionID}`, body) .then((response) => response.data) - .then((context) => adaptResponseContext(context)); + .then((context) => adaptResponseContext(context)) + .then((context) => extractAudioStep(context)); } getVersionID() { diff --git a/lib/Common/index.ts b/lib/Common/index.ts index b9e1791..b405077 100644 --- a/lib/Common/index.ts +++ b/lib/Common/index.ts @@ -1,4 +1,4 @@ -import { TraceType } from '@voiceflow/general-types'; +import { TraceType } from '../types'; export const validTraceTypes = new Set(Object.keys(TraceType)); diff --git a/lib/Context/index.ts b/lib/Context/index.ts index 8f14586..e958eed 100644 --- a/lib/Context/index.ts +++ b/lib/Context/index.ts @@ -1,7 +1,5 @@ -import { TraceType } from '@voiceflow/general-types'; - import DataFilterer from '@/lib/DataFilterer'; -import { Choice, DataConfig, ResponseContext } from '@/lib/types'; +import { Choice, DataConfig, ResponseContext, TraceType } from '@/lib/types'; import VariableManager from '../Variables'; diff --git a/lib/DataFilterer/index.ts b/lib/DataFilterer/index.ts index 8a49efa..79656b0 100644 --- a/lib/DataFilterer/index.ts +++ b/lib/DataFilterer/index.ts @@ -1,7 +1,5 @@ -import { GeneralTrace, TraceType } from '@voiceflow/general-types'; - import { VFTypeError } from '@/lib/Common'; -import { DataConfig } from '@/lib/types'; +import { DataConfig, GeneralTrace, TraceType } from '@/lib/types'; import { isValidTraceType, stripSSMLFromSpeak } from './utils'; diff --git a/lib/DataFilterer/utils.ts b/lib/DataFilterer/utils.ts index 4ba0637..76a5a4b 100644 --- a/lib/DataFilterer/utils.ts +++ b/lib/DataFilterer/utils.ts @@ -1,6 +1,5 @@ -import { GeneralTrace, TraceType } from '@voiceflow/general-types'; - import { validTraceTypes } from '@/lib/Common'; +import { GeneralTrace, TraceType } from '@/lib/types'; export const SSML_TAG_REGEX = /<\/?[^>]+(>|$)/g; diff --git a/lib/Events/index.ts b/lib/Events/index.ts new file mode 100644 index 0000000..c410c43 --- /dev/null +++ b/lib/Events/index.ts @@ -0,0 +1,40 @@ +import { GeneralTrace, TraceMap, TraceType } from '@/lib/types'; + +import Context from '../Context'; + +export type TraceEventHandler> = (object: TraceMap[T], context: Context) => void; + +export type GeneralTraceEventHandler> = (object: GeneralTrace, context: Context) => void; + +type _Map, K extends TraceType = TraceType> = Map>>; + +export class EventManager> { + private specHandlers: _Map; + + private genHandlers: GeneralTraceEventHandler[]; + + constructor() { + this.specHandlers = new Map(); + const traceTypeVals = Object.keys(TraceType).map((type) => type.toLowerCase()) as TraceType[]; + traceTypeVals.forEach((traceType) => this.specHandlers.set(traceType, [])); + + this.genHandlers = []; + } + + on(event: T, handler: TraceEventHandler) { + const handlerList = this.specHandlers.get(event)! as TraceEventHandler[]; + handlerList.push(handler); + } + + onAny(handler: GeneralTraceEventHandler) { + this.genHandlers.push(handler); + } + + handle(trace: TraceMap[T], context: Context) { + this.specHandlers.get(trace.type)!.forEach((handler: TraceEventHandler) => handler(trace, context)); + + this.genHandlers.forEach((handler: GeneralTraceEventHandler) => handler(trace, context)); + } +} + +export default EventManager; diff --git a/lib/RuntimeClient/index.ts b/lib/RuntimeClient/index.ts index 22d8116..f18ef22 100644 --- a/lib/RuntimeClient/index.ts +++ b/lib/RuntimeClient/index.ts @@ -2,32 +2,43 @@ import { GeneralRequest, RequestType } from '@voiceflow/general-types'; import { State } from '@voiceflow/runtime'; import Client from '@/lib/Client'; -import { VFClientError } from '@/lib/Common'; +import { VFClientError, VFTypeError } from '@/lib/Common'; import Context from '@/lib/Context'; -import { DataConfig, ResponseContext } from '@/lib/types'; +import EventManager, { GeneralTraceEventHandler, TraceEventHandler } from '@/lib/Events'; +import { DataConfig, ResponseContext, TRACE_EVENT, TraceType } from '@/lib/types'; +import { isValidTraceType } from '../DataFilterer/utils'; import { makeRequestBody, resetContext } from './utils'; -export class RuntimeClient = Record> { - private client: Client; +type OnMethodHandlerArgMap = { + [K in TraceType]: TraceEventHandler; +} & { + trace: GeneralTraceEventHandler; +}; + +export class RuntimeClient = Record> { + private client: Client; private dataConfig: DataConfig; - private context: Context; + private context: Context; + + private events: EventManager; - constructor(state: State, { client, dataConfig = {} }: { client: Client; dataConfig?: DataConfig }) { + constructor(state: State, { client, dataConfig = {} }: { client: Client; dataConfig?: DataConfig }) { this.client = client; this.dataConfig = dataConfig; + this.events = new EventManager(); this.context = new Context({ request: null, state, trace: [] }, this.dataConfig); } - async start(): Promise> { + async start(): Promise> { this.context = new Context(resetContext(this.context.toJSON()), this.dataConfig); return this.sendRequest(null); } - async sendText(userInput: string): Promise> { + async sendText(userInput: string): Promise> { if (!userInput?.trim?.()) { return this.sendRequest(null); } @@ -44,9 +55,53 @@ export class RuntimeClient = Record> this.context!.getResponse().forEach(this.dataConfig.traceProcessor); } + this.context!.getTrace().forEach((trace) => this.events.handle(trace, this.context!)); + return this.context; } + on(event: T, handler: OnMethodHandlerArgMap[T]) { + if (event === TRACE_EVENT) { + return this.events.onAny(handler as GeneralTraceEventHandler); + } + if (isValidTraceType(event)) { + return this.events.on(event as any, handler as any); + } + throw new VFTypeError(`event "${event}" is not valid`); + } + + onSpeak(handler: TraceEventHandler) { + this.events.on(TraceType.SPEAK, handler); + } + + onAudio(handler: TraceEventHandler) { + this.events.on(TraceType.AUDIO, handler); + } + + onBlock(handler: TraceEventHandler) { + this.events.on(TraceType.BLOCK, handler); + } + + onDebug(handler: TraceEventHandler) { + this.events.on(TraceType.DEBUG, handler); + } + + onEnd(handler: TraceEventHandler) { + this.events.on(TraceType.END, handler); + } + + onFlow(handler: TraceEventHandler) { + this.events.on(TraceType.FLOW, handler); + } + + onVisual(handler: TraceEventHandler) { + this.events.on(TraceType.VISUAL, handler); + } + + onChoice(handler: TraceEventHandler) { + this.events.on(TraceType.CHOICE, handler); + } + setContext(contextJSON: ResponseContext) { this.context = new Context(contextJSON, this.dataConfig); } diff --git a/lib/Utils/makeTraceProcessor/audio.ts b/lib/Utils/makeTraceProcessor/audio.ts new file mode 100644 index 0000000..274ac8d --- /dev/null +++ b/lib/Utils/makeTraceProcessor/audio.ts @@ -0,0 +1,9 @@ +import { AudioTrace, AudioTraceHandler } from '@/lib/types'; + +export const invokeAudioHandler = (trace: AudioTrace, handler: AudioTraceHandler) => { + const { + payload: { message, src }, + } = trace; + return handler(message, src); +}; +export default invokeAudioHandler; diff --git a/lib/Utils/makeTraceProcessor/block.ts b/lib/Utils/makeTraceProcessor/block.ts index df51ea0..002a8a0 100644 --- a/lib/Utils/makeTraceProcessor/block.ts +++ b/lib/Utils/makeTraceProcessor/block.ts @@ -1,5 +1,4 @@ -import { BlockTrace } from '@voiceflow/general-types'; - -export type BlockTraceHandler = (blockID: BlockTrace['payload']['blockID']) => any; +import { BlockTrace, BlockTraceHandler } from '@/lib/types'; export const invokeBlockHandler = (trace: BlockTrace, handler: BlockTraceHandler) => handler(trace.payload.blockID); +export default invokeBlockHandler; diff --git a/lib/Utils/makeTraceProcessor/choice.ts b/lib/Utils/makeTraceProcessor/choice.ts index 23d8e4a..864fb2c 100644 --- a/lib/Utils/makeTraceProcessor/choice.ts +++ b/lib/Utils/makeTraceProcessor/choice.ts @@ -1,5 +1,4 @@ -import { ChoiceTrace } from '@voiceflow/general-types'; - -export type ChoiceTraceHandler = (choices: ChoiceTrace['payload']['choices']) => any; +import { ChoiceTrace, ChoiceTraceHandler } from '@/lib/types'; export const invokeChoiceHandler = (trace: ChoiceTrace, handler: ChoiceTraceHandler) => handler(trace.payload.choices); +export default invokeChoiceHandler; diff --git a/lib/Utils/makeTraceProcessor/debug.ts b/lib/Utils/makeTraceProcessor/debug.ts index adddb2c..c6048c5 100644 --- a/lib/Utils/makeTraceProcessor/debug.ts +++ b/lib/Utils/makeTraceProcessor/debug.ts @@ -1,5 +1,4 @@ -import { DebugTrace } from '@voiceflow/general-types'; - -export type DebugTraceHandler = (message: DebugTrace['payload']['message']) => any; +import { DebugTrace, DebugTraceHandler } from '@/lib/types'; export const invokeDebugHandler = (trace: DebugTrace, handler: DebugTraceHandler) => handler(trace.payload.message); +export default invokeDebugHandler; diff --git a/lib/Utils/makeTraceProcessor/end.ts b/lib/Utils/makeTraceProcessor/end.ts index 03fb978..de5efad 100644 --- a/lib/Utils/makeTraceProcessor/end.ts +++ b/lib/Utils/makeTraceProcessor/end.ts @@ -1,5 +1,4 @@ -import { ExitTrace } from '@voiceflow/general-types'; +import { EndTrace, EndTraceHandler } from '@/lib/types'; -export type EndTraceHandler = () => any; - -export const invokeEndHandler = (_: ExitTrace, handler: EndTraceHandler) => handler(); +export const invokeEndHandler = (_: EndTrace, handler: EndTraceHandler) => handler(); +export default invokeEndHandler; diff --git a/lib/Utils/makeTraceProcessor/flow.ts b/lib/Utils/makeTraceProcessor/flow.ts index 3d57dd5..9d375ae 100644 --- a/lib/Utils/makeTraceProcessor/flow.ts +++ b/lib/Utils/makeTraceProcessor/flow.ts @@ -1,5 +1,4 @@ -import { FlowTrace } from '@voiceflow/general-types'; - -export type FlowTraceHandler = (diagramID: FlowTrace['payload']['diagramID']) => any; +import { FlowTrace, FlowTraceHandler } from '@/lib/types'; export const invokeFlowHandler = (trace: FlowTrace, handler: FlowTraceHandler) => handler(trace.payload.diagramID); +export default invokeFlowHandler; diff --git a/lib/Utils/makeTraceProcessor/index.ts b/lib/Utils/makeTraceProcessor/index.ts index bf65f25..a72cea2 100644 --- a/lib/Utils/makeTraceProcessor/index.ts +++ b/lib/Utils/makeTraceProcessor/index.ts @@ -1,52 +1 @@ -import { GeneralTrace, TraceType } from '@voiceflow/general-types'; - -import { VFClientError } from '@/lib/Common'; - -import { BlockTraceHandler, invokeBlockHandler } from './block'; -import { ChoiceTraceHandler, invokeChoiceHandler } from './choice'; -import { DebugTraceHandler, invokeDebugHandler } from './debug'; -import { EndTraceHandler, invokeEndHandler } from './end'; -import { FlowTraceHandler, invokeFlowHandler } from './flow'; -import { invokeSpeakHandler, SpeakTraceHandler } from './speak'; -import { invokeStreamHandler, StreamTraceHandler } from './stream'; -import { invokeVisualHandler, VisualTraceHandler } from './visual'; - -export type TraceHandlerMap = Partial<{ - [TraceType.BLOCK]: BlockTraceHandler; - [TraceType.CHOICE]: ChoiceTraceHandler; - [TraceType.DEBUG]: DebugTraceHandler; - [TraceType.END]: EndTraceHandler; - [TraceType.FLOW]: FlowTraceHandler; - [TraceType.SPEAK]: SpeakTraceHandler; - [TraceType.STREAM]: StreamTraceHandler; - [TraceType.VISUAL]: VisualTraceHandler; -}>; - -export type InvokeHandler = (trace: any, handler: any) => any; - -const invokeHandlerMap = new Map([ - [TraceType.BLOCK, invokeBlockHandler], - [TraceType.CHOICE, invokeChoiceHandler], - [TraceType.DEBUG, invokeDebugHandler], - [TraceType.END, invokeEndHandler], - [TraceType.FLOW, invokeFlowHandler], - [TraceType.SPEAK, invokeSpeakHandler], - [TraceType.STREAM, invokeStreamHandler], - [TraceType.VISUAL, invokeVisualHandler], -]); - -export const makeTraceProcessor = (handlers: TraceHandlerMap) => (trace: GeneralTrace) => { - const invokeHandler = invokeHandlerMap.get(trace.type); - - if (!invokeHandler) { - throw new VFClientError(`invalid trace type "${trace.type}" was passed into makeTraceProcessor`); - } - - const handler = handlers[trace.type]; - - if (!handler) { - throw new VFClientError(`handler for "${trace.type}" was not implemented`); - } - - return invokeHandler(trace, handlers[trace.type]); -}; +export { makeTraceProcessor, TraceProcessorMap } from './traceProcessor'; diff --git a/lib/Utils/makeTraceProcessor/speak.ts b/lib/Utils/makeTraceProcessor/speak.ts index e59f365..f0e67b4 100644 --- a/lib/Utils/makeTraceProcessor/speak.ts +++ b/lib/Utils/makeTraceProcessor/speak.ts @@ -1,41 +1,9 @@ -import { SpeakTrace } from '@voiceflow/general-types'; -import _ from 'lodash'; +import { SpeakTrace, SpeakTraceHandler } from '@/lib/types'; -import { VFClientError, VFTypeError } from '@/lib/Common'; - -export type SpeakTraceAudioHandler = (message: SpeakTrace['payload']['message'], src: SpeakTrace['payload']['src']) => any; -export type SpeakTraceTTSHandler = (message: SpeakTrace['payload']['message'], src: SpeakTrace['payload']['src']) => any; -export type SpeakTraceHandlerFunction = ( - message: SpeakTrace['payload']['message'], - src: SpeakTrace['payload']['src'], - type: SpeakTrace['payload']['type'] -) => any; -export type SpeakTraceHandlerMap = Partial<{ - handleAudio: SpeakTraceAudioHandler; - handleSpeech: SpeakTraceTTSHandler; -}>; -export type SpeakTraceHandler = SpeakTraceHandlerFunction | SpeakTraceHandlerMap; - -export const invokeSpeakHandler = (trace: SpeakTrace, speakHandler: SpeakTraceHandler) => { +export const invokeSpeakHandler = (trace: SpeakTrace, handler: SpeakTraceHandler) => { const { - payload: { type: speakTraceType, message: speakMessage, src: speakSrc }, + payload: { message, src }, } = trace; - - if (_.isFunction(speakHandler)) { - return speakHandler(speakMessage, speakSrc, speakTraceType); - } - - if (speakTraceType === 'message') { - if (!speakHandler.handleSpeech) { - throw new VFClientError("missing handler for SpeakTrace's speak subtype"); - } - return speakHandler.handleSpeech(speakMessage, speakSrc); - } - if (speakTraceType === 'audio') { - if (!speakHandler.handleAudio) { - throw new VFClientError("missing handler for SpeakTrace's audio subtype"); - } - return speakHandler.handleAudio(speakMessage, speakSrc); - } - throw new VFTypeError("makeTraceProcessor's returned callback received an unknown SpeakTrace subtype"); + return handler(message, src); }; +export default invokeSpeakHandler; diff --git a/lib/Utils/makeTraceProcessor/stream.ts b/lib/Utils/makeTraceProcessor/stream.ts deleted file mode 100644 index aa95f85..0000000 --- a/lib/Utils/makeTraceProcessor/stream.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StreamTrace } from '@voiceflow/general-types'; - -export type StreamTraceHandler = ( - src: StreamTrace['payload']['src'], - action: StreamTrace['payload']['action'], - token: StreamTrace['payload']['token'] -) => any; - -export const invokeStreamHandler = (trace: StreamTrace, handler: StreamTraceHandler) => { - const { - payload: { src, action, token }, - } = trace; - return handler(src, action, token); -}; diff --git a/lib/Utils/makeTraceProcessor/traceProcessor.ts b/lib/Utils/makeTraceProcessor/traceProcessor.ts new file mode 100644 index 0000000..ab47525 --- /dev/null +++ b/lib/Utils/makeTraceProcessor/traceProcessor.ts @@ -0,0 +1,62 @@ +import { VFClientError } from '@/lib/Common'; +import { + AudioTraceHandler, + BlockTraceHandler, + ChoiceTraceHandler, + DebugTraceHandler, + EndTraceHandler, + FlowTraceHandler, + GeneralTrace, + SpeakTraceHandler, + TraceType, + VisualTraceHandler, +} from '@/lib/types'; + +import { invokeAudioHandler } from './audio'; +import { invokeBlockHandler } from './block'; +import { invokeChoiceHandler } from './choice'; +import { invokeDebugHandler } from './debug'; +import { invokeEndHandler } from './end'; +import { invokeFlowHandler } from './flow'; +import { invokeSpeakHandler } from './speak'; +import { invokeVisualHandler } from './visual'; + +export type TraceProcessorMap = Partial<{ + [TraceType.BLOCK]: BlockTraceHandler; + [TraceType.CHOICE]: ChoiceTraceHandler; + [TraceType.DEBUG]: DebugTraceHandler; + [TraceType.END]: EndTraceHandler; + [TraceType.FLOW]: FlowTraceHandler; + [TraceType.SPEAK]: SpeakTraceHandler; + [TraceType.AUDIO]: AudioTraceHandler; + [TraceType.VISUAL]: VisualTraceHandler; +}>; + +export type InvokeHandler = (trace: any, handler: any) => any; + +export const invokeHandlerMap = new Map([ + [TraceType.BLOCK, invokeBlockHandler], + [TraceType.CHOICE, invokeChoiceHandler], + [TraceType.DEBUG, invokeDebugHandler], + [TraceType.END, invokeEndHandler], + [TraceType.FLOW, invokeFlowHandler], + [TraceType.SPEAK, invokeSpeakHandler], + [TraceType.AUDIO, invokeAudioHandler], + [TraceType.VISUAL, invokeVisualHandler], +]); + +export const makeTraceProcessor = (handlers: TraceProcessorMap) => (trace: GeneralTrace) => { + const invokeHandler = invokeHandlerMap.get(trace.type); + + if (!invokeHandler) { + throw new VFClientError(`invalid trace type "${trace.type}" was passed into makeTraceProcessor`); + } + + const handler = handlers[trace.type]; + + if (!handler) { + throw new VFClientError(`handler for "${trace.type}" was not implemented`); + } + + return invokeHandler(trace, handlers[trace.type]); +}; diff --git a/lib/Utils/makeTraceProcessor/visual.ts b/lib/Utils/makeTraceProcessor/visual.ts index 6d6323d..27296b9 100644 --- a/lib/Utils/makeTraceProcessor/visual.ts +++ b/lib/Utils/makeTraceProcessor/visual.ts @@ -1,8 +1,4 @@ -import { DeviceType, Dimensions, VisualTrace as CombinedVisualTrace } from '@voiceflow/general-types'; -import { CanvasVisibility, ImageStepData } from '@voiceflow/general-types/build/nodes/visual'; - -export type VisualTrace = CombinedVisualTrace & { payload: ImageStepData }; -export type VisualTraceHandler = (image: string | null, device: DeviceType | null, dimensions: Dimensions | null, visiblity: CanvasVisibility) => any; +import { VisualTrace, VisualTraceHandler } from '@/lib/types'; export const invokeVisualHandler = (trace: VisualTrace, handler: VisualTraceHandler) => { const { @@ -10,3 +6,4 @@ export const invokeVisualHandler = (trace: VisualTrace, handler: VisualTraceHand } = trace; return handler(image, device, dimensions, canvasVisibility); }; +export default invokeVisualHandler; diff --git a/lib/types.ts b/lib/types.ts index f07a383..702f991 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,6 +1,56 @@ -import { ChoiceTrace, Config, GeneralRequest, GeneralTrace } from '@voiceflow/general-types'; +import { + BlockTrace as BaseBlockTrace, + ChoiceTrace as BaseChoiceTrace, + Config, + DebugTrace as BaseDebugTrace, + DeviceType, + Dimensions, + ExitTrace as BaseEndTrace, + FlowTrace as BaseFlowTrace, + GeneralRequest, + GeneralTrace as BaseGeneralTrace, + SpeakTrace as BaseSpeakTrace, + VisualTrace as BaseVisualTrace, +} from '@voiceflow/general-types'; +import { CanvasVisibility, ImageStepData } from '@voiceflow/general-types/build/nodes/visual'; import { State } from '@voiceflow/runtime'; +export enum TraceType { + BLOCK = 'block', + CHOICE = 'choice', + DEBUG = 'debug', + END = 'end', + FLOW = 'flow', + SPEAK = 'speak', + AUDIO = 'audio', + VISUAL = 'visual', +} + +export type AdaptTraceType = Omit & { type: S }; + +export type BlockTrace = AdaptTraceType; + +export type ChoiceTrace = AdaptTraceType; + +export type DebugTrace = AdaptTraceType; + +export type EndTrace = AdaptTraceType; + +export type FlowTrace = AdaptTraceType; + +export type AudioTrace = { + type: TraceType.AUDIO; + payload: Omit; +}; + +export type SpeakTrace = { + type: TraceType.SPEAK; + payload: Omit; +}; +export type VisualTrace = AdaptTraceType & { payload: ImageStepData }; + +export type GeneralTrace = BlockTrace | ChoiceTrace | DebugTrace | EndTrace | FlowTrace | AudioTrace | SpeakTrace | VisualTrace; + export type DataConfig = { tts?: boolean; ssml?: boolean; @@ -20,4 +70,45 @@ export type RequestContext = { config?: Config; }; +export type BlockTraceHandler = (blockID: BlockTrace['payload']['blockID']) => any; + +export type ChoiceTraceHandler = (choices: ChoiceTrace['payload']['choices']) => any; + +export type DebugTraceHandler = (message: DebugTrace['payload']['message']) => any; + +export type EndTraceHandler = () => any; + +export type FlowTraceHandler = (diagramID: FlowTrace['payload']['diagramID']) => any; + +export type SpeakTraceHandler = (message: SpeakTrace['payload']['message'], src: SpeakTrace['payload']['src']) => any; + +export type AudioTraceHandler = (message: SpeakTrace['payload']['message'], src: SpeakTrace['payload']['src']) => any; + +export type VisualTraceHandler = (image: string | null, device: DeviceType | null, dimensions: Dimensions | null, visiblity: CanvasVisibility) => any; + +export type TraceHandlerMap = { + [TraceType.BLOCK]: BlockTraceHandler; + [TraceType.CHOICE]: ChoiceTraceHandler; + [TraceType.DEBUG]: DebugTraceHandler; + [TraceType.END]: EndTraceHandler; + [TraceType.FLOW]: FlowTraceHandler; + [TraceType.SPEAK]: SpeakTraceHandler; + [TraceType.AUDIO]: AudioTraceHandler; + [TraceType.VISUAL]: VisualTraceHandler; +}; + +export type TraceMap = { + [TraceType.BLOCK]: BlockTrace; + [TraceType.CHOICE]: ChoiceTrace; + [TraceType.DEBUG]: DebugTrace; + [TraceType.END]: EndTrace; + [TraceType.FLOW]: FlowTrace; + [TraceType.SPEAK]: SpeakTrace; + [TraceType.AUDIO]: AudioTrace; + [TraceType.VISUAL]: VisualTrace; +}; + +export const TRACE_EVENT = 'trace'; +export type TRACE_EVENT = typeof TRACE_EVENT; + export type Choice = ChoiceTrace['payload']['choices'][number]; diff --git a/tests/lib/Client/adapters/fixtures.ts b/tests/lib/Client/adapters/fixtures.ts new file mode 100644 index 0000000..bf57401 --- /dev/null +++ b/tests/lib/Client/adapters/fixtures.ts @@ -0,0 +1,19 @@ +import { TraceType } from "@voiceflow/general-types"; +import { SpeakType } from "@voiceflow/general-types/build/nodes/speak"; +import { AUDIO_TRACE, SPEAK_TRACE } from "../../fixtures"; + +export const MALFORMED_SPEAK_TRACE = { + ...SPEAK_TRACE, + payload: { + ...SPEAK_TRACE.payload, + type: SpeakType.MESSAGE + } + }; + +export const MALFORMED_AUDIO_TRACE = { + type: TraceType.SPEAK, + payload: { + ...AUDIO_TRACE.payload, + type: SpeakType.AUDIO + } +} \ No newline at end of file diff --git a/tests/lib/Client/adapters/index.unit.ts b/tests/lib/Client/adapters/index.unit.ts index 48a4ca2..52a7d49 100644 --- a/tests/lib/Client/adapters/index.unit.ts +++ b/tests/lib/Client/adapters/index.unit.ts @@ -1,11 +1,13 @@ -import { RequestType, SpeakTrace, TraceType } from '@voiceflow/general-types'; import { SpeakType } from '@voiceflow/general-types/build/nodes/speak'; import { expect } from 'chai'; import sinon from 'sinon'; -import { adaptResponseContext } from '@/lib/Client/adapters'; -import { ResponseContext } from '@/lib/types'; -import { VISUAL_TRACE_IMAGE } from '../../fixtures'; +import { adaptResponseContext, extractAudioStep } from '@/lib/Client/adapters'; +import { RequestType, SpeakTrace, TraceType } from '@voiceflow/general-types'; +import { DBResponseContext } from '@/lib/Client/adapters/types'; +import { DB_VISUAL_TRACE } from '../fixtures'; +import { AUDIO_TRACE, SPEAK_TRACE } from '../../fixtures'; +import { MALFORMED_AUDIO_TRACE, MALFORMED_SPEAK_TRACE } from './fixtures'; describe('adapters', () => { afterEach(() => { @@ -28,7 +30,7 @@ describe('adapters', () => { }, } as SpeakTrace; - const malformedResponse: ResponseContext = { + const malformedResponse: DBResponseContext = { state: { variables: {}, stack: [], @@ -38,7 +40,7 @@ describe('adapters', () => { type: RequestType.TEXT, payload: 'some user input', }, - trace: [malformedTrace1, malformedTrace2, VISUAL_TRACE_IMAGE], + trace: [malformedTrace1, malformedTrace2, DB_VISUAL_TRACE], }; const result = adaptResponseContext(malformedResponse); @@ -62,8 +64,20 @@ describe('adapters', () => { type: SpeakType.MESSAGE, }, }, - VISUAL_TRACE_IMAGE + DB_VISUAL_TRACE ], }); }); + + it('extractAudioStep', () => { + const messyData = { + trace: [MALFORMED_SPEAK_TRACE, MALFORMED_AUDIO_TRACE] + }; + + const result = extractAudioStep(messyData as any); + + expect(result).to.eql({ + trace: [SPEAK_TRACE, AUDIO_TRACE] + }); + }); }); diff --git a/tests/lib/Client/fixtures.ts b/tests/lib/Client/fixtures.ts index b272a73..158cfc5 100644 --- a/tests/lib/Client/fixtures.ts +++ b/tests/lib/Client/fixtures.ts @@ -1,8 +1,24 @@ import { RequestContext } from '@/lib/types'; +import { DeviceType, TraceType, VisualTrace } from '@voiceflow/general-types'; +import { CanvasVisibility, VisualType } from '@voiceflow/general-types/build/nodes/visual'; import { VF_APP_INITIAL_STATE } from '../Context/fixtures'; import { VFAppVariablesSchema } from '../fixtures'; +export const DB_VISUAL_TRACE: VisualTrace = { + type: TraceType.VISUAL, + payload: { + visualType: VisualType.IMAGE, + image: 'the-image.url', + device: DeviceType.DESKTOP, + dimensions: { + height: 100, + width: 200, + }, + canvasVisibility: CanvasVisibility.CROPPED + } +} + export const VF_APP_CUSTOM_INITIAL_VARIABLES: Partial = { age: 337, name: 'Gandalf the White', diff --git a/tests/lib/Context/fixtures.ts b/tests/lib/Context/fixtures.ts index efa6a75..33eeb90 100644 --- a/tests/lib/Context/fixtures.ts +++ b/tests/lib/Context/fixtures.ts @@ -1,11 +1,10 @@ -import { RequestType, TraceType } from '@voiceflow/general-types'; -import { SpeakType } from '@voiceflow/general-types/build/nodes/speak'; +import { RequestType } from '@voiceflow/general-types'; import { State } from '@voiceflow/runtime'; import _ from 'lodash'; -import { RequestContext, ResponseContext } from '@/lib/types'; +import { RequestContext, ResponseContext, TraceType } from '@/lib/types'; -import { BLOCK_TRACE, CHOICE_TRACE, CHOICE_TRACE_WITH_NO_CHOICES, DEBUG_TRACE, END_TRACE, FLOW_TRACE, SPEAK_TRACE, STREAM_TRACE } from '../fixtures'; +import { AUDIO_TRACE, BLOCK_TRACE, CHOICE_TRACE, CHOICE_TRACE_WITH_NO_CHOICES, DEBUG_TRACE, END_TRACE, FLOW_TRACE, SPEAK_TRACE } from '../fixtures'; export { CHOICE_TRACE } from '../fixtures'; @@ -59,7 +58,7 @@ export const VF_APP_NEXT_STATE_1: State = { export const START_RESPONSE_BODY = { state: VF_APP_NEXT_STATE_1, request: null, - trace: [SPEAK_TRACE, BLOCK_TRACE, FLOW_TRACE, STREAM_TRACE, DEBUG_TRACE, CHOICE_TRACE], + trace: [SPEAK_TRACE, BLOCK_TRACE, FLOW_TRACE, AUDIO_TRACE, DEBUG_TRACE, CHOICE_TRACE], }; export const START_RESPONSE_BODY_WITH_NO_CHOICES = { @@ -124,7 +123,6 @@ export const SEND_TEXT_RESPONSE_BODY_WITH_SSML_AND_TTS: ResponseContext = { { ...SPEAK_TRACE, payload: { - type: SpeakType.MESSAGE, message: 'Books ought to have to have good endings.', src: 'data:audio/mpeg;base64,SUQzBAAAAAAA', }, @@ -159,7 +157,6 @@ export const START_RESPONSE_BODY_WITH_MULTIPLE_CHOICES: ResponseContext = { choices: CHOICES_1, }, }, - STREAM_TRACE, DEBUG_TRACE, { type: TraceType.CHOICE, diff --git a/tests/lib/Events/index.unit.ts b/tests/lib/Events/index.unit.ts new file mode 100644 index 0000000..0ec7ae0 --- /dev/null +++ b/tests/lib/Events/index.unit.ts @@ -0,0 +1,60 @@ +import sinon from 'sinon'; +import { expect } from 'chai'; +import EventManager from "@/lib/Events"; +import { TraceType } from '@/lib/types'; +import { SPEAK_TRACE, VISUAL_TRACE } from '../fixtures'; + +const createEventManager = = Record>() => { + const evManager = new EventManager(); + return { evManager }; +} + +describe('EventManager', () => { + afterEach(() => { + sinon.restore(); + }); + + it('on', () => { + const { evManager } = createEventManager() + + const result: any[] = []; + const context1 = { a: 1 }; + const context2 = { b: 2 }; + + evManager.on(TraceType.SPEAK, (object, context) => { + result.push(object); + result.push(context); + }); + + evManager.handle(SPEAK_TRACE, context1 as any); + evManager.handle(VISUAL_TRACE, context2 as any); + + expect(result).to.eql([ + SPEAK_TRACE, + context1 + ]); + }); + + it('onAny', () => { + const { evManager } = createEventManager() + + const result: any[] = []; + const context1 = { a: 1 }; + const context2 = { b: 2 }; + + evManager.onAny((object, context) => { + result.push(object); + result.push(context); + }); + + evManager.handle(SPEAK_TRACE, context1 as any); + evManager.handle(VISUAL_TRACE, context2 as any); + + expect(result).to.eql([ + SPEAK_TRACE, + context1, + VISUAL_TRACE, + context2 + ]); + }); +}); \ No newline at end of file diff --git a/tests/lib/RuntimeClient/index.unit.ts b/tests/lib/RuntimeClient/index.unit.ts index d10169c..32d81f1 100644 --- a/tests/lib/RuntimeClient/index.unit.ts +++ b/tests/lib/RuntimeClient/index.unit.ts @@ -1,12 +1,10 @@ -import { TraceType } from '@voiceflow/general-types'; import chai, { expect } from 'chai'; import chaiAsPromise from 'chai-as-promised'; import _ from 'lodash'; import sinon from 'sinon'; import RuntimeClient from '@/lib/RuntimeClient'; -import { DataConfig } from '@/lib/types'; -import { makeTraceProcessor } from '@/lib/Utils'; +import { DataConfig, TraceType, TRACE_EVENT } from '@/lib/types'; import { CHOICE_TRACE, @@ -24,7 +22,8 @@ import { USER_RESPONSE, VF_APP_INITIAL_STATE, } from '../Context/fixtures'; -import { DEBUG_TRACE, SPEAK_TRACE } from '../fixtures'; +import { AUDIO_TRACE, BLOCK_TRACE, DEBUG_TRACE, END_TRACE, FLOW_TRACE, SPEAK_TRACE } from '../fixtures'; +import { makeTraceProcessor } from '@/lib/Utils/makeTraceProcessor'; chai.use(chaiAsPromise); @@ -266,4 +265,111 @@ describe('RuntimeClient', () => { expect((response[0] as any).payload.src).to.eql('data:audio/mpeg;base64,SUQzBAAAAAAA'); expect(response.length).to.eql(2); }); + + describe('events', () => { + it('on', async () => { + const { agent, client } = createRuntimeClient(); + + const result1: any[] = []; + const result2: any[] = []; + + agent.on(TraceType.SPEAK, (trace, context) => { + result1.push(trace, context); + }); + agent.on(TRACE_EVENT, (trace, context) => { + result2.push(trace, context); + }); + + client.interact.resolves(START_RESPONSE_BODY); + + const context = await agent.start(); + + expect(result1).to.eql([ + SPEAK_TRACE, context, + ]); + expect(result2).to.eql([ + SPEAK_TRACE, context, + BLOCK_TRACE, context, + FLOW_TRACE, context, + AUDIO_TRACE, context, + DEBUG_TRACE, context, + CHOICE_TRACE, context + ]); + }); + + it('on, bad trace type', () => { + const { agent } = createRuntimeClient(); + + const BAD_TRACE_TYPE = 'bad' as TraceType; + + const callback = () => { + agent.on(BAD_TRACE_TYPE, () => {}); + } + + expect(callback).to.throw(); + }); + + it('onEvent', async () => { + const { agent, client } = createRuntimeClient(); + + const results: any = {}; + Object.keys(TraceType) + .map(trace => trace.toLowerCase()) + .forEach((trace) => { + results[trace] = []; + }); + + const insertToResults = (trace: any, context: any) => { + results[trace.type].push(trace, context); + }; + + agent.onAudio(insertToResults); + agent.onBlock(insertToResults); + agent.onDebug(insertToResults); + agent.onEnd(insertToResults); + agent.onChoice(insertToResults); + agent.onFlow(insertToResults); + agent.onSpeak(insertToResults); + agent.onVisual(insertToResults); + + client.interact.resolves(START_RESPONSE_BODY); + + const context1 = await agent.start(); + + client.interact.resolves(SEND_TEXT_RESPONSE_BODY); + + const context2 = await agent.sendText('some nonsense'); + + expect(results[TraceType.SPEAK]).to.eql([ + SPEAK_TRACE, context1, + SPEAK_TRACE, context2 + ]); + + expect(results[TraceType.VISUAL]).to.eql([]); + + expect(results[TraceType.FLOW]).to.eql([ + FLOW_TRACE, context1 + ]); + + expect(results[TraceType.END]).to.eql([ + END_TRACE, context2 + ]); + + expect(results[TraceType.DEBUG]).to.eql([ + DEBUG_TRACE, context1 + ]); + + expect(results[TraceType.CHOICE]).to.eql([ + CHOICE_TRACE, context1 + ]); + + expect(results[TraceType.BLOCK]).to.eql([ + BLOCK_TRACE, context1 + ]); + + expect(results[TraceType.AUDIO]).to.eql([ + AUDIO_TRACE, context1 + ]); + }); + }); }); diff --git a/tests/lib/Utils/makeTraceProcessor/fixtures.ts b/tests/lib/Utils/makeTraceProcessor/fixtures.ts index 924d122..2693157 100644 --- a/tests/lib/Utils/makeTraceProcessor/fixtures.ts +++ b/tests/lib/Utils/makeTraceProcessor/fixtures.ts @@ -1,13 +1,4 @@ -import { TraceType } from '@voiceflow/general-types'; -import { TraceHandlerMap } from '@/lib/Utils/makeTraceProcessor'; -import { BlockTraceHandler } from '@/lib/Utils/makeTraceProcessor/block'; -import { ChoiceTraceHandler } from '@/lib/Utils/makeTraceProcessor/choice'; -import { DebugTraceHandler } from '@/lib/Utils/makeTraceProcessor/debug'; -import { EndTraceHandler } from '@/lib/Utils/makeTraceProcessor/end'; -import { FlowTraceHandler } from '@/lib/Utils/makeTraceProcessor/flow'; -import { SpeakTraceHandlerFunction, SpeakTraceHandlerMap } from '@/lib/Utils/makeTraceProcessor/speak'; -import { StreamTraceHandler } from '@/lib/Utils/makeTraceProcessor/stream'; -import { VisualTraceHandler } from '@/lib/Utils/makeTraceProcessor/visual'; +import { AudioTraceHandler, BlockTraceHandler, ChoiceTraceHandler, DebugTraceHandler, EndTraceHandler, FlowTraceHandler, SpeakTraceHandler, TraceHandlerMap, TraceType, VisualTraceHandler } from "@/lib/types"; export const FAKE_SPEAK_TRACE = { type: TraceType.SPEAK, @@ -42,23 +33,9 @@ export const endHandler: EndTraceHandler = () => { export const flowHandler: FlowTraceHandler = (diagramID: string) => { return diagramID; } - -export const speakHandlerMap: SpeakTraceHandlerMap = { - handleSpeech: (message, src) => { - return [message, src]; - }, - handleAudio: (message, src) => { - return [message, src]; - } -}; - -export const speakHandlerFunc: SpeakTraceHandlerFunction = (message, src, type) => [message, src, type]; - -export const visualHandlerFunc: VisualTraceHandler = (image, device, dimensions, visiblity) => [image, device, dimensions, visiblity]; - -export const streamHandler: StreamTraceHandler = (src, action, token) => { - return [src, action, token]; -} +export const speakHandler: SpeakTraceHandler = (message, src) => [message, src]; +export const audioHandler: AudioTraceHandler = (message, src) => [message, src]; +export const visualHandler: VisualTraceHandler = (image, device, dimensions, visiblity) => [image, device, dimensions, visiblity]; export const TRACE_HANDLER_MAP: TraceHandlerMap = { [TraceType.BLOCK]: blockHandler, @@ -66,6 +43,7 @@ export const TRACE_HANDLER_MAP: TraceHandlerMap = { [TraceType.DEBUG]: debugHandler, [TraceType.END]: endHandler, [TraceType.FLOW]: flowHandler, - [TraceType.SPEAK]: speakHandlerMap, - [TraceType.STREAM]: streamHandler, + [TraceType.SPEAK]: speakHandler, + [TraceType.AUDIO]: audioHandler, + [TraceType.VISUAL]: visualHandler }; diff --git a/tests/lib/Utils/makeTraceProcessor/makeTraceProcess.unit.ts b/tests/lib/Utils/makeTraceProcessor/makeTraceProcess.unit.ts index af79c55..dccf604 100644 --- a/tests/lib/Utils/makeTraceProcessor/makeTraceProcess.unit.ts +++ b/tests/lib/Utils/makeTraceProcessor/makeTraceProcess.unit.ts @@ -1,16 +1,16 @@ -import { GeneralTrace, SpeakTrace, TraceType } from '@voiceflow/general-types'; -import { invokeBlockHandler } from '@/lib/Utils/makeTraceProcessor/block'; -import { invokeChoiceHandler } from '@/lib/Utils/makeTraceProcessor/choice'; -import { invokeDebugHandler } from '@/lib/Utils/makeTraceProcessor/debug'; -import { invokeEndHandler } from '@/lib/Utils/makeTraceProcessor/end'; -import { invokeFlowHandler } from '@/lib/Utils/makeTraceProcessor/flow'; -import { invokeSpeakHandler, SpeakTraceHandler, SpeakTraceHandlerMap } from '@/lib/Utils/makeTraceProcessor/speak'; -import { invokeStreamHandler } from '@/lib/Utils/makeTraceProcessor/stream'; +import invokeBlockHandler from '@/lib/Utils/makeTraceProcessor/block'; +import invokeChoiceHandler from '@/lib/Utils/makeTraceProcessor/choice'; +import invokeDebugHandler from '@/lib/Utils/makeTraceProcessor/debug'; +import invokeEndHandler from '@/lib/Utils/makeTraceProcessor/end'; +import { GeneralTrace, TraceType } from '@/lib/types'; import { expect } from 'chai'; import sinon from 'sinon'; -import { BLOCK_TRACE, CHOICE_TRACE, DEBUG_TRACE, END_TRACE, FLOW_TRACE, SPEAK_TRACE, SPEAK_TRACE_AUDIO, STREAM_TRACE, VISUAL_TRACE_IMAGE } from '../../fixtures'; -import { blockHandler, choiceHandler, debugHandler, endHandler, FAKE_SPEAK_TRACE, flowHandler, RESULT, speakHandlerFunc, speakHandlerMap, streamHandler, TRACE_HANDLER_MAP, UNKNOWN_TRACE_TYPE, visualHandlerFunc } from './fixtures'; -import { invokeVisualHandler } from '@/lib/Utils/makeTraceProcessor/visual'; +import { AUDIO_TRACE, BLOCK_TRACE, CHOICE_TRACE, DEBUG_TRACE, END_TRACE, FLOW_TRACE, SPEAK_TRACE, VISUAL_TRACE } from '../../fixtures'; +import { audioHandler, blockHandler, choiceHandler, debugHandler, endHandler, flowHandler, RESULT, speakHandler, TRACE_HANDLER_MAP, UNKNOWN_TRACE_TYPE, visualHandler } from './fixtures'; +import invokeFlowHandler from '@/lib/Utils/makeTraceProcessor/flow'; +import invokeVisualHandler from '@/lib/Utils/makeTraceProcessor/visual'; +import invokeSpeakHandler from '@/lib/Utils/makeTraceProcessor/speak'; +import invokeAudioHandler from '@/lib/Utils/makeTraceProcessor/audio'; import { makeTraceProcessor } from '@/lib/Utils/makeTraceProcessor'; describe('makeTraceProcessor', () => { @@ -43,78 +43,33 @@ describe('makeTraceProcessor', () => { expect(result).to.eql(FLOW_TRACE.payload.diagramID); }); - it('invokeStreamHandler', () => { - const result = invokeStreamHandler(STREAM_TRACE, streamHandler); + it('invokeVisualHandler', () => { + const result = invokeVisualHandler(VISUAL_TRACE, visualHandler); expect(result).to.eql([ - STREAM_TRACE.payload.src, - STREAM_TRACE.payload.action, - STREAM_TRACE.payload.token, + VISUAL_TRACE.payload.image, + VISUAL_TRACE.payload.device, + VISUAL_TRACE.payload.dimensions, + VISUAL_TRACE.payload.canvasVisibility ]); }); - it('invokeVisualHandler', () => { - const result = invokeVisualHandler(VISUAL_TRACE_IMAGE, visualHandlerFunc); + it('invokeSpeakHandler', () => { + const result = invokeSpeakHandler(SPEAK_TRACE, speakHandler); expect(result).to.eql([ - VISUAL_TRACE_IMAGE.payload.image, - VISUAL_TRACE_IMAGE.payload.device, - VISUAL_TRACE_IMAGE.payload.dimensions, - VISUAL_TRACE_IMAGE.payload.canvasVisibility + SPEAK_TRACE.payload.message, + SPEAK_TRACE.payload.src, ]); }); - describe('invokeSpeakHandler', () => { - it('function handler', () => { - const result = invokeSpeakHandler(SPEAK_TRACE, speakHandlerFunc); - - expect(result).to.eql([ - SPEAK_TRACE.payload.message, - SPEAK_TRACE.payload.src, - SPEAK_TRACE.payload.type - ]); - }); - - it('invokes tts handler', () => { - const handler: SpeakTraceHandlerMap = { - handleSpeech: speakHandlerMap.handleSpeech - } - - const result = invokeSpeakHandler(SPEAK_TRACE, handler); - - expect(result).to.eql([ - SPEAK_TRACE.payload.message, - SPEAK_TRACE.payload.src - ]); - }); - - it('invokes audio handler', () => { - const handler: SpeakTraceHandler = { - handleAudio: speakHandlerMap.handleAudio - } - - const result = invokeSpeakHandler(SPEAK_TRACE_AUDIO, handler); + it('invokeAudioHandler', () => { + const result = invokeAudioHandler(AUDIO_TRACE, audioHandler); - expect(result).to.eql([ - SPEAK_TRACE_AUDIO.payload.message, - SPEAK_TRACE_AUDIO.payload.src - ]); - }); - - it('unimplemented speak handler', () => { - const callback = () => invokeSpeakHandler(SPEAK_TRACE, {}); - expect(callback).to.throw("VFError: missing handler for SpeakTrace's speak subtype"); - }); - - it('unimplemented audio handler', () => { - const callback = () => invokeSpeakHandler(SPEAK_TRACE_AUDIO, {}); - expect(callback).to.throw("VFError: missing handler for SpeakTrace's audio subtype"); - }); - - it('unknown speak subtype', () => { - const callback = () => invokeSpeakHandler(FAKE_SPEAK_TRACE as SpeakTrace, speakHandlerMap) - expect(callback).to.throw("VFError: makeTraceProcessor's returned callback received an unknown SpeakTrace subtype"); - }); + expect(result).to.eql([ + AUDIO_TRACE.payload.message, + AUDIO_TRACE.payload.src, + ]); }); describe('makeTraceProcessor', () => { diff --git a/tests/lib/fixtures.ts b/tests/lib/fixtures.ts index 40323c3..2498688 100644 --- a/tests/lib/fixtures.ts +++ b/tests/lib/fixtures.ts @@ -1,17 +1,15 @@ import { + AudioTrace, BlockTrace, ChoiceTrace, DebugTrace, - DeviceType, - ExitTrace, + EndTrace, FlowTrace, SpeakTrace, - StreamTrace, TraceType, VisualTrace, -} from '@voiceflow/general-types'; -import { SpeakType } from '@voiceflow/general-types/build/nodes/speak'; -import { TraceStreamAction } from '@voiceflow/general-types/build/nodes/stream'; +} from '@/lib/types'; +import { DeviceType } from '@voiceflow/general-types'; import { CanvasVisibility, VisualType } from '@voiceflow/general-types/build/nodes/visual'; export type VFAppVariablesSchema = { @@ -23,7 +21,6 @@ export type VFAppVariablesSchema = { export const SPEAK_TRACE: SpeakTrace = { type: TraceType.SPEAK, payload: { - type: SpeakType.MESSAGE, message: 'Books ought to have to have good endings.', src: 'data:audio/mpeg:some-large-tts-audio-file', }, @@ -37,10 +34,9 @@ export const MAKE_SPEAK_TRACE = (payload: SpeakTrace['payload']): SpeakTrace => }, }); -export const SPEAK_TRACE_AUDIO: SpeakTrace = { - type: TraceType.SPEAK, +export const AUDIO_TRACE: AudioTrace = { + type: TraceType.AUDIO, payload: { - type: SpeakType.AUDIO, message: '