UNPKG

rx-sandbox

Version:

Marble diagram DSL based test suite for RxJS 6/7

224 lines (203 loc) 6.89 kB
import { complete, error as e, next, TestMessage } from '../message/TestMessage'; import { ColdObservable } from '../utils/coreInternalImport'; import { ObservableMarbleToken } from './ObservableMarbleToken'; import { SubscriptionMarbleToken } from './SubscriptionMarbleToken'; /** * @internal * Base accumulator interface for parsing marble diagram. */ interface TokenParseAccumulator { /** * Current virtual time passed */ currentTimeFrame: number; /** * Flag indicate values are grouped `()` and emitted simultaneously */ simultaneousGrouped: boolean; /** * Flag indicate timeframe expansion `...` is in progress */ expandingTokenCount: number; /** * Tokens for expanding timeframe, will be joined & parsed into number */ expandingValue: Array<string>; } /** * @internal * Accumulator interface for parsing observable marble diagram. */ interface ObservableTokenParseAccumulator<T> extends TokenParseAccumulator { /** * Meta values emitted by marbles (value, error, complete) */ messages: Array<TestMessage<T | Array<TestMessage<T>>>>; } /** * @internal * Accumulator interface for parsing subscription marble diagram. */ interface SubscriptionTokenParseAccumulator extends TokenParseAccumulator { subscriptionFrame: number; unsubscriptionFrame: number; } /** * Translate single token in marble diagram to correct metadata * @param {any} token Single char in marble diagram * @param {{ [key: string]: T }} [value] Custom value for marble value * @param {boolean} [materializeInnerObservables] Flatten inner observables in cold observable. False by default. */ const getMarbleTokenValue = <T>( token: any, value: { [key: string]: T } | null, materializeInnerObservables: boolean ) => { const customValue = value && token in value ? value[token] : token; return materializeInnerObservables && customValue instanceof ColdObservable ? customValue.messages : customValue; }; const timeFrameExpandTokenHandler = (acc: TokenParseAccumulator, frameTimeFactor: number) => { const ret = { ...acc }; ret.expandingTokenCount += 1; //When token reaches ...xxx..., clean up state if (ret.expandingTokenCount === 6) { ret.expandingValue.splice(0); ret.expandingTokenCount = 0; } //When first ending token arrives ...xxx. , parse values and adjust timeframe if (ret.expandingTokenCount === 4) { if (ret.expandingValue.length === 0) { throw new Error(`There isn't value to expand timeframe`); } const expandedFrame = parseInt(ret.expandingValue.join(''), 10); ret.currentTimeFrame += expandedFrame * frameTimeFactor; } return ret as any; }; const validateTimeFrameToken = (acc: TokenParseAccumulator) => { if (acc.expandingTokenCount > 0 || acc.simultaneousGrouped) { throw new Error('Incorret timeframe specified'); } }; const validateSimultaneousGroupToken = (acc: TokenParseAccumulator) => { if (acc.simultaneousGrouped) { throw new Error('Cannot nest grouped value'); } }; const increaseTimeFrame = (acc: TokenParseAccumulator, frameTimeFactor: number) => { const ret = { ...acc }; ret.currentTimeFrame += 1 * frameTimeFactor; return ret as any; }; /** * @internal * Reducer to traverse Observable marble diagram to generate TestMessage metadata. * @param value Custom values for marble values * @param error Custom error * @param materializeInnerObservables Flatten inner observable if available * @param frameTimeFactor Custom timeframe factor */ const observableTokenParseReducer = <T>( value: { [key: string]: T } | null, error: any, materializeInnerObservables: boolean, frameTimeFactor: number, maxFrame: number ) => (acc: ObservableTokenParseAccumulator<T>, token: any) => { if (acc.currentTimeFrame >= maxFrame) { return acc; } let message: TestMessage<T | Array<TestMessage<T>>> | null = null; switch (token) { case ObservableMarbleToken.TIMEFRAME: validateTimeFrameToken(acc); acc = increaseTimeFrame(acc, frameTimeFactor); break; case ObservableMarbleToken.ERROR: message = e(acc.currentTimeFrame, error || '#'); break; case ObservableMarbleToken.COMPLETE: message = complete(acc.currentTimeFrame); break; case ObservableMarbleToken.TIMEFRAME_EXPAND: acc = timeFrameExpandTokenHandler(acc, frameTimeFactor); break; case ObservableMarbleToken.SIMULTANEOUS_START: validateSimultaneousGroupToken(acc); acc.simultaneousGrouped = true; break; case ObservableMarbleToken.SIMULTANEOUS_END: acc = increaseTimeFrame(acc, frameTimeFactor); acc.simultaneousGrouped = false; break; case SubscriptionMarbleToken.SUBSCRIBE: acc = increaseTimeFrame(acc, frameTimeFactor); break; default: if (acc.expandingTokenCount > 0) { acc.expandingValue.push(token); } else { const tokenValue = getMarbleTokenValue(token, value, materializeInnerObservables); message = next<T | Array<TestMessage<T>>>(acc.currentTimeFrame, tokenValue); } } if (!!message) { acc.messages.push(message); if (!acc.simultaneousGrouped) { acc = increaseTimeFrame(acc, frameTimeFactor); } } return acc; }; /** * @internal * Reducer to traverse subscription marble diagram to generate SubscriptionLog metadata. * @param frameTimeFactor Custom timeframe factor */ const subscriptionTokenParseReducer = (frameTimeFactor: number, maxFrame: number) => ( acc: SubscriptionTokenParseAccumulator, token: string ) => { if (acc.currentTimeFrame >= maxFrame) { return acc; } switch (token) { case SubscriptionMarbleToken.SUBSCRIBE: acc.subscriptionFrame = acc.currentTimeFrame; if (!acc.simultaneousGrouped) { acc = increaseTimeFrame(acc, frameTimeFactor); } break; case SubscriptionMarbleToken.UNSUBSCRIBE: acc.unsubscriptionFrame = acc.currentTimeFrame; break; case ObservableMarbleToken.TIMEFRAME_EXPAND: acc = timeFrameExpandTokenHandler(acc, frameTimeFactor); break; case ObservableMarbleToken.SIMULTANEOUS_START: validateSimultaneousGroupToken(acc); acc.simultaneousGrouped = true; break; case ObservableMarbleToken.SIMULTANEOUS_END: acc.simultaneousGrouped = false; case ObservableMarbleToken.TIMEFRAME: validateTimeFrameToken(acc); case ObservableMarbleToken.ERROR: case ObservableMarbleToken.COMPLETE: default: if (acc.expandingTokenCount > 0) { acc.expandingValue.push(token); } else if (!acc.simultaneousGrouped) { acc = increaseTimeFrame(acc, frameTimeFactor); } break; } return acc; }; export { TokenParseAccumulator, ObservableTokenParseAccumulator, SubscriptionTokenParseAccumulator, subscriptionTokenParseReducer, observableTokenParseReducer, };