Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

TestScheduler.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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 = [];
  15. this.flushTests = [];
  16. this.runMode = false;
  17. }
  18. createTime(marbles) {
  19. const indexOf = marbles.indexOf('|');
  20. if (indexOf === -1) {
  21. throw new Error('marble diagram for time should have a completion marker "|"');
  22. }
  23. return indexOf * TestScheduler.frameTimeFactor;
  24. }
  25. createColdObservable(marbles, values, error) {
  26. if (marbles.indexOf('^') !== -1) {
  27. throw new Error('cold observable cannot have subscription offset "^"');
  28. }
  29. if (marbles.indexOf('!') !== -1) {
  30. throw new Error('cold observable cannot have unsubscription marker "!"');
  31. }
  32. const messages = TestScheduler.parseMarbles(marbles, values, error, undefined, this.runMode);
  33. const cold = new ColdObservable(messages, this);
  34. this.coldObservables.push(cold);
  35. return cold;
  36. }
  37. createHotObservable(marbles, values, error) {
  38. if (marbles.indexOf('!') !== -1) {
  39. throw new Error('hot observable cannot have unsubscription marker "!"');
  40. }
  41. const messages = TestScheduler.parseMarbles(marbles, values, error, undefined, this.runMode);
  42. const subject = new HotObservable(messages, this);
  43. this.hotObservables.push(subject);
  44. return subject;
  45. }
  46. materializeInnerObservable(observable, outerFrame) {
  47. const messages = [];
  48. observable.subscribe((value) => {
  49. messages.push({ frame: this.frame - outerFrame, notification: Notification.createNext(value) });
  50. }, (err) => {
  51. messages.push({ frame: this.frame - outerFrame, notification: Notification.createError(err) });
  52. }, () => {
  53. messages.push({ frame: this.frame - outerFrame, notification: Notification.createComplete() });
  54. });
  55. return messages;
  56. }
  57. expectObservable(observable, subscriptionMarbles = null) {
  58. const actual = [];
  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;
  64. let subscription;
  65. this.schedule(() => {
  66. subscription = observable.subscribe(x => {
  67. let value = x;
  68. if (x instanceof Observable) {
  69. value = this.materializeInnerObservable(value, this.frame);
  70. }
  71. actual.push({ frame: this.frame, notification: Notification.createNext(value) });
  72. }, (err) => {
  73. actual.push({ frame: this.frame, notification: Notification.createError(err) });
  74. }, () => {
  75. actual.push({ frame: this.frame, notification: Notification.createComplete() });
  76. });
  77. }, subscriptionFrame);
  78. if (unsubscriptionFrame !== Number.POSITIVE_INFINITY) {
  79. this.schedule(() => subscription.unsubscribe(), unsubscriptionFrame);
  80. }
  81. this.flushTests.push(flushTest);
  82. const { runMode } = this;
  83. return {
  84. toBe(marbles, values, errorValue) {
  85. flushTest.ready = true;
  86. flushTest.expected = TestScheduler.parseMarbles(marbles, values, errorValue, true, runMode);
  87. }
  88. };
  89. }
  90. expectSubscriptions(actualSubscriptionLogs) {
  91. const flushTest = { actual: actualSubscriptionLogs, ready: false };
  92. this.flushTests.push(flushTest);
  93. const { runMode } = this;
  94. return {
  95. toBe(marbles) {
  96. const marblesArray = (typeof marbles === 'string') ? [marbles] : marbles;
  97. flushTest.ready = true;
  98. flushTest.expected = marblesArray.map(marbles => TestScheduler.parseMarblesAsSubscriptions(marbles, runMode));
  99. }
  100. };
  101. }
  102. flush() {
  103. const hotObservables = this.hotObservables;
  104. while (hotObservables.length > 0) {
  105. hotObservables.shift().setup();
  106. }
  107. super.flush();
  108. this.flushTests = this.flushTests.filter(test => {
  109. if (test.ready) {
  110. this.assertDeepEqual(test.actual, test.expected);
  111. return false;
  112. }
  113. return true;
  114. });
  115. }
  116. static parseMarblesAsSubscriptions(marbles, runMode = false) {
  117. if (typeof marbles !== 'string') {
  118. return new SubscriptionLog(Number.POSITIVE_INFINITY);
  119. }
  120. const len = marbles.length;
  121. let groupStart = -1;
  122. let subscriptionFrame = Number.POSITIVE_INFINITY;
  123. let unsubscriptionFrame = Number.POSITIVE_INFINITY;
  124. let frame = 0;
  125. for (let i = 0; i < len; i++) {
  126. let nextFrame = frame;
  127. const advanceFrameBy = (count) => {
  128. nextFrame += count * this.frameTimeFactor;
  129. };
  130. const c = marbles[i];
  131. switch (c) {
  132. case ' ':
  133. if (!runMode) {
  134. advanceFrameBy(1);
  135. }
  136. break;
  137. case '-':
  138. advanceFrameBy(1);
  139. break;
  140. case '(':
  141. groupStart = frame;
  142. advanceFrameBy(1);
  143. break;
  144. case ')':
  145. groupStart = -1;
  146. advanceFrameBy(1);
  147. break;
  148. case '^':
  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.');
  152. }
  153. subscriptionFrame = groupStart > -1 ? groupStart : frame;
  154. advanceFrameBy(1);
  155. break;
  156. case '!':
  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.');
  160. }
  161. unsubscriptionFrame = groupStart > -1 ? groupStart : frame;
  162. break;
  163. default:
  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) /);
  168. if (match) {
  169. i += match[0].length - 1;
  170. const duration = parseFloat(match[1]);
  171. const unit = match[2];
  172. let durationInMs;
  173. switch (unit) {
  174. case 'ms':
  175. durationInMs = duration;
  176. break;
  177. case 's':
  178. durationInMs = duration * 1000;
  179. break;
  180. case 'm':
  181. durationInMs = duration * 1000 * 60;
  182. break;
  183. default:
  184. break;
  185. }
  186. advanceFrameBy(durationInMs / this.frameTimeFactor);
  187. break;
  188. }
  189. }
  190. }
  191. throw new Error('there can only be \'^\' and \'!\' markers in a ' +
  192. 'subscription marble diagram. Found instead \'' + c + '\'.');
  193. }
  194. frame = nextFrame;
  195. }
  196. if (unsubscriptionFrame < 0) {
  197. return new SubscriptionLog(subscriptionFrame);
  198. }
  199. else {
  200. return new SubscriptionLog(subscriptionFrame, unsubscriptionFrame);
  201. }
  202. }
  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 "!"');
  207. }
  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' ?
  213. (x) => x :
  214. (x) => {
  215. if (materializeInnerObservables && values[x] instanceof ColdObservable) {
  216. return values[x].messages;
  217. }
  218. return values[x];
  219. };
  220. let groupStart = -1;
  221. for (let i = 0; i < len; i++) {
  222. let nextFrame = frame;
  223. const advanceFrameBy = (count) => {
  224. nextFrame += count * this.frameTimeFactor;
  225. };
  226. let notification;
  227. const c = marbles[i];
  228. switch (c) {
  229. case ' ':
  230. if (!runMode) {
  231. advanceFrameBy(1);
  232. }
  233. break;
  234. case '-':
  235. advanceFrameBy(1);
  236. break;
  237. case '(':
  238. groupStart = frame;
  239. advanceFrameBy(1);
  240. break;
  241. case ')':
  242. groupStart = -1;
  243. advanceFrameBy(1);
  244. break;
  245. case '|':
  246. notification = Notification.createComplete();
  247. advanceFrameBy(1);
  248. break;
  249. case '^':
  250. advanceFrameBy(1);
  251. break;
  252. case '#':
  253. notification = Notification.createError(errorValue || 'error');
  254. advanceFrameBy(1);
  255. break;
  256. default:
  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) /);
  261. if (match) {
  262. i += match[0].length - 1;
  263. const duration = parseFloat(match[1]);
  264. const unit = match[2];
  265. let durationInMs;
  266. switch (unit) {
  267. case 'ms':
  268. durationInMs = duration;
  269. break;
  270. case 's':
  271. durationInMs = duration * 1000;
  272. break;
  273. case 'm':
  274. durationInMs = duration * 1000 * 60;
  275. break;
  276. default:
  277. break;
  278. }
  279. advanceFrameBy(durationInMs / this.frameTimeFactor);
  280. break;
  281. }
  282. }
  283. }
  284. notification = Notification.createNext(getValue(c));
  285. advanceFrameBy(1);
  286. break;
  287. }
  288. if (notification) {
  289. testMessages.push({ frame: groupStart > -1 ? groupStart : frame, notification });
  290. }
  291. frame = nextFrame;
  292. }
  293. return testMessages;
  294. }
  295. run(callback) {
  296. const prevFrameTimeFactor = TestScheduler.frameTimeFactor;
  297. const prevMaxFrames = this.maxFrames;
  298. TestScheduler.frameTimeFactor = 1;
  299. this.maxFrames = Number.POSITIVE_INFINITY;
  300. this.runMode = true;
  301. AsyncScheduler.delegate = this;
  302. const helpers = {
  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),
  308. };
  309. try {
  310. const ret = callback(helpers);
  311. this.flush();
  312. return ret;
  313. }
  314. finally {
  315. TestScheduler.frameTimeFactor = prevFrameTimeFactor;
  316. this.maxFrames = prevMaxFrames;
  317. this.runMode = false;
  318. AsyncScheduler.delegate = undefined;
  319. }
  320. }
  321. }
  322. //# sourceMappingURL=TestScheduler.js.map