1 import { Observable } from '../Observable';
2 import { Notification } from '../Notification';
3 import { ColdObservable } from './ColdObservable';
4 import { HotObservable } from './HotObservable';
5 import { SubscriptionLog } from './SubscriptionLog';
6 import { VirtualTimeScheduler, VirtualAction } from '../scheduler/VirtualTimeScheduler';
7 import { AsyncScheduler } from '../scheduler/AsyncScheduler';
8 const defaultMaxFrame = 750;
9 export class TestScheduler extends VirtualTimeScheduler {
10 constructor(assertDeepEqual) {
11 super(VirtualAction, defaultMaxFrame);
12 this.assertDeepEqual = assertDeepEqual;
13 this.hotObservables = [];
14 this.coldObservables = [];
19 const indexOf = marbles.indexOf('|');
21 throw new Error('marble diagram for time should have a completion marker "|"');
23 return indexOf * TestScheduler.frameTimeFactor;
25 createColdObservable(marbles, values, error) {
26 if (marbles.indexOf('^') !== -1) {
27 throw new Error('cold observable cannot have subscription offset "^"');
29 if (marbles.indexOf('!') !== -1) {
30 throw new Error('cold observable cannot have unsubscription marker "!"');
32 const messages = TestScheduler.parseMarbles(marbles, values, error, undefined, this.runMode);
33 const cold = new ColdObservable(messages, this);
34 this.coldObservables.push(cold);
37 createHotObservable(marbles, values, error) {
38 if (marbles.indexOf('!') !== -1) {
39 throw new Error('hot observable cannot have unsubscription marker "!"');
41 const messages = TestScheduler.parseMarbles(marbles, values, error, undefined, this.runMode);
42 const subject = new HotObservable(messages, this);
43 this.hotObservables.push(subject);
46 materializeInnerObservable(observable, outerFrame) {
48 observable.subscribe((value) => {
49 messages.push({ frame: this.frame - outerFrame, notification: Notification.createNext(value) });
51 messages.push({ frame: this.frame - outerFrame, notification: Notification.createError(err) });
53 messages.push({ frame: this.frame - outerFrame, notification: Notification.createComplete() });
57 expectObservable(observable, subscriptionMarbles = null) {
59 const flushTest = { actual, ready: false };
60 const subscriptionParsed = TestScheduler.parseMarblesAsSubscriptions(subscriptionMarbles, this.runMode);
61 const subscriptionFrame = subscriptionParsed.subscribedFrame === Number.POSITIVE_INFINITY ?
62 0 : subscriptionParsed.subscribedFrame;
63 const unsubscriptionFrame = subscriptionParsed.unsubscribedFrame;
66 subscription = observable.subscribe(x => {
68 if (x instanceof Observable) {
69 value = this.materializeInnerObservable(value, this.frame);
71 actual.push({ frame: this.frame, notification: Notification.createNext(value) });
73 actual.push({ frame: this.frame, notification: Notification.createError(err) });
75 actual.push({ frame: this.frame, notification: Notification.createComplete() });
77 }, subscriptionFrame);
78 if (unsubscriptionFrame !== Number.POSITIVE_INFINITY) {
79 this.schedule(() => subscription.unsubscribe(), unsubscriptionFrame);
81 this.flushTests.push(flushTest);
82 const { runMode } = this;
84 toBe(marbles, values, errorValue) {
85 flushTest.ready = true;
86 flushTest.expected = TestScheduler.parseMarbles(marbles, values, errorValue, true, runMode);
90 expectSubscriptions(actualSubscriptionLogs) {
91 const flushTest = { actual: actualSubscriptionLogs, ready: false };
92 this.flushTests.push(flushTest);
93 const { runMode } = this;
96 const marblesArray = (typeof marbles === 'string') ? [marbles] : marbles;
97 flushTest.ready = true;
98 flushTest.expected = marblesArray.map(marbles => TestScheduler.parseMarblesAsSubscriptions(marbles, runMode));
103 const hotObservables = this.hotObservables;
104 while (hotObservables.length > 0) {
105 hotObservables.shift().setup();
108 this.flushTests = this.flushTests.filter(test => {
110 this.assertDeepEqual(test.actual, test.expected);
116 static parseMarblesAsSubscriptions(marbles, runMode = false) {
117 if (typeof marbles !== 'string') {
118 return new SubscriptionLog(Number.POSITIVE_INFINITY);
120 const len = marbles.length;
122 let subscriptionFrame = Number.POSITIVE_INFINITY;
123 let unsubscriptionFrame = Number.POSITIVE_INFINITY;
125 for (let i = 0; i < len; i++) {
126 let nextFrame = frame;
127 const advanceFrameBy = (count) => {
128 nextFrame += count * this.frameTimeFactor;
130 const c = marbles[i];
149 if (subscriptionFrame !== Number.POSITIVE_INFINITY) {
150 throw new Error('found a second subscription point \'^\' in a ' +
151 'subscription marble diagram. There can only be one.');
153 subscriptionFrame = groupStart > -1 ? groupStart : frame;
157 if (unsubscriptionFrame !== Number.POSITIVE_INFINITY) {
158 throw new Error('found a second subscription point \'^\' in a ' +
159 'subscription marble diagram. There can only be one.');
161 unsubscriptionFrame = groupStart > -1 ? groupStart : frame;
164 if (runMode && c.match(/^[0-9]$/)) {
165 if (i === 0 || marbles[i - 1] === ' ') {
166 const buffer = marbles.slice(i);
167 const match = buffer.match(/^([0-9]+(?:\.[0-9]+)?)(ms|s|m) /);
169 i += match[0].length - 1;
170 const duration = parseFloat(match[1]);
171 const unit = match[2];
175 durationInMs = duration;
178 durationInMs = duration * 1000;
181 durationInMs = duration * 1000 * 60;
186 advanceFrameBy(durationInMs / this.frameTimeFactor);
191 throw new Error('there can only be \'^\' and \'!\' markers in a ' +
192 'subscription marble diagram. Found instead \'' + c + '\'.');
196 if (unsubscriptionFrame < 0) {
197 return new SubscriptionLog(subscriptionFrame);
200 return new SubscriptionLog(subscriptionFrame, unsubscriptionFrame);
203 static parseMarbles(marbles, values, errorValue, materializeInnerObservables = false, runMode = false) {
204 if (marbles.indexOf('!') !== -1) {
205 throw new Error('conventional marble diagrams cannot have the ' +
206 'unsubscription marker "!"');
208 const len = marbles.length;
209 const testMessages = [];
210 const subIndex = runMode ? marbles.replace(/^[ ]+/, '').indexOf('^') : marbles.indexOf('^');
211 let frame = subIndex === -1 ? 0 : (subIndex * -this.frameTimeFactor);
212 const getValue = typeof values !== 'object' ?
215 if (materializeInnerObservables && values[x] instanceof ColdObservable) {
216 return values[x].messages;
221 for (let i = 0; i < len; i++) {
222 let nextFrame = frame;
223 const advanceFrameBy = (count) => {
224 nextFrame += count * this.frameTimeFactor;
227 const c = marbles[i];
246 notification = Notification.createComplete();
253 notification = Notification.createError(errorValue || 'error');
257 if (runMode && c.match(/^[0-9]$/)) {
258 if (i === 0 || marbles[i - 1] === ' ') {
259 const buffer = marbles.slice(i);
260 const match = buffer.match(/^([0-9]+(?:\.[0-9]+)?)(ms|s|m) /);
262 i += match[0].length - 1;
263 const duration = parseFloat(match[1]);
264 const unit = match[2];
268 durationInMs = duration;
271 durationInMs = duration * 1000;
274 durationInMs = duration * 1000 * 60;
279 advanceFrameBy(durationInMs / this.frameTimeFactor);
284 notification = Notification.createNext(getValue(c));
289 testMessages.push({ frame: groupStart > -1 ? groupStart : frame, notification });
296 const prevFrameTimeFactor = TestScheduler.frameTimeFactor;
297 const prevMaxFrames = this.maxFrames;
298 TestScheduler.frameTimeFactor = 1;
299 this.maxFrames = Number.POSITIVE_INFINITY;
301 AsyncScheduler.delegate = this;
303 cold: this.createColdObservable.bind(this),
304 hot: this.createHotObservable.bind(this),
305 flush: this.flush.bind(this),
306 expectObservable: this.expectObservable.bind(this),
307 expectSubscriptions: this.expectSubscriptions.bind(this),
310 const ret = callback(helpers);
315 TestScheduler.frameTimeFactor = prevFrameTimeFactor;
316 this.maxFrames = prevMaxFrames;
317 this.runMode = false;
318 AsyncScheduler.delegate = undefined;
322 //# sourceMappingURL=TestScheduler.js.map