##// END OF EJS Templates
more tests
cin -
r77:04efbdad7679 default
parent child
Show More
@@ -1,33 +1,33
1 1 HISTORY
2 2 =======
3 3
4 4 1.2.17
5 5 ------
6 6
7 7 Bug fixes
8 8
9 9 1.2.16
10 10 ------
11 11
12 12 Minor fixes and improvements
13 13
14 14 - added `isCancellable` type predicate function to `safe` module
15 15 - `isString, isNumber, isInteger, isPrimitive` are now type predicates
16 16
17 17 1.2.0
18 18 -----
19 19
20 20 Major rafactoring, moving to support both browser (rjs) and server (cjs) environments.
21 21
22 22 - dependency injection container ported to typescript
23 23 - sources are split to several sets to provide the ability for the conditional build of the project.
24 24
25 25 1.0.1
26 26 -----
27 27
28 28 First release, intorduces the following features
29 29
30 30 - `di` - dependency injection container
31 31 - `log` - log4 style logging system
32 32 - `text` - simple and fast text templating and formatting
33 - `Uuid` - uuid generation traits No newline at end of file
33 - `Uuid` - uuid generation traits
@@ -1,456 +1,454
1 1 import { ICancellable, Constructor } from "./interfaces";
2 2 import { Cancellation } from "./Cancellation";
3 3
4 4 let _nextOid = 0;
5 5 const _oid = typeof Symbol === "function" ?
6 6 Symbol("__implab__oid__") :
7 7 "__implab__oid__";
8 8
9 9 export function oid(instance: object): string {
10 10 if (isNull(instance))
11 11 return null;
12 12
13 13 if (_oid in instance)
14 14 return instance[_oid];
15 15 else
16 16 return (instance[_oid] = "oid_" + (++_nextOid));
17 17 }
18 18
19 19 export function argumentNotNull(arg: any, name: string) {
20 20 if (arg === null || arg === undefined)
21 21 throw new Error("The argument " + name + " can't be null or undefined");
22 22 }
23 23
24 24 export function argumentNotEmptyString(arg: any, name: string) {
25 25 if (typeof (arg) !== "string" || !arg.length)
26 26 throw new Error("The argument '" + name + "' must be a not empty string");
27 27 }
28 28
29 29 export function argumentNotEmptyArray(arg: any, name: string) {
30 30 if (!(arg instanceof Array) || !arg.length)
31 31 throw new Error("The argument '" + name + "' must be a not empty array");
32 32 }
33 33
34 34 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
35 35 if (!(arg instanceof type))
36 36 throw new Error("The argument '" + name + "' type doesn't match");
37 37 }
38 38
39 39 export function isNull(val: any) {
40 40 return (val === null || val === undefined);
41 41 }
42 42
43 43 export function isPrimitive(val: any): val is string | number | boolean | undefined | null {
44 44 return (val === null || val === undefined || typeof (val) === "string" ||
45 45 typeof (val) === "number" || typeof (val) === "boolean");
46 46 }
47 47
48 48 export function isInteger(val: any): val is number {
49 49 return parseInt(val, 10) === val;
50 50 }
51 51
52 52 export function isNumber(val: any): val is number {
53 53 return parseFloat(val) === val;
54 54 }
55 55
56 56 export function isString(val: any): val is string {
57 57 return typeof (val) === "string" || val instanceof String;
58 58 }
59 59
60 60 export function isPromise(val: any): val is PromiseLike<any> {
61 61 return val && typeof val.then === "function";
62 62 }
63 63
64 64 export function isCancellable(val: any): val is ICancellable {
65 65 return val && typeof val.cancel === "function";
66 66 }
67 67
68 68 export function isNullOrEmptyString(val: any): val is string | null | undefined {
69 69 if (val === null || val === undefined ||
70 70 ((typeof (val) === "string" || val instanceof String) && val.length === 0))
71 71 return true;
72 72 }
73 73
74 74 export function isNotEmptyArray(arg: any): arg is Array<any> {
75 75 return (arg instanceof Array && arg.length > 0);
76 76 }
77 77
78 78 function _isStrictMode() {
79 79 return !this;
80 80 }
81 81
82 82 function _getNonStrictGlobal() {
83 83 return this;
84 84 }
85 85
86 86 export function getGlobal() {
87 87 // in es3 we can't use indirect call to eval, since it will
88 88 // be executed in the current call context.
89 89 if (!_isStrictMode()) {
90 90 return _getNonStrictGlobal();
91 91 } else {
92 92 // tslint:disable-next-line:no-eval
93 93 return eval.call(null, "this");
94 94 }
95 95 }
96 96
97 97 export function get(member: string, context?: object) {
98 98 argumentNotEmptyString(member, "member");
99 99 let that = context || getGlobal();
100 100 const parts = member.split(".");
101 101 for (const m of parts) {
102 102 if (!m)
103 103 continue;
104 104 if (isNull(that = that[m]))
105 105 break;
106 106 }
107 107 return that;
108 108 }
109 109
110 110 /**
111 111 * ВыполняСт ΠΌΠ΅Ρ‚ΠΎΠ΄ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта массива, останавливаСтся, ΠΊΠΎΠ³Π΄Π°
112 112 * Π»ΠΈΠ±ΠΎ достигнут ΠΊΠΎΠ½Π΅Ρ† массива, Π»ΠΈΠ±ΠΎ функция <c>cb</c> Π²Π΅Ρ€Π½ΡƒΠ»Π°
113 113 * Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅.
114 114 *
115 115 * @param {Array | Object} obj массив элСмСнтов для просмотра
116 116 * @param {Function} cb функция, вызываСмая для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта
117 117 * @param {Object} thisArg Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠ΅Ρ€Π΅Π΄Π°Π½ΠΎ Π² качСствС
118 118 * <c>this</c> Π² <c>cb</c>.
119 119 * @returns Π Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ Π²Ρ‹Π·ΠΎΠ²Π° Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ <c>cb</c>, Π»ΠΈΠ±ΠΎ <c>undefined</c>
120 120 * Ссли достигнут ΠΊΠΎΠ½Π΅Ρ† массива.
121 121 */
122 122 export function each(obj, cb, thisArg?) {
123 123 argumentNotNull(cb, "cb");
124 124 if (obj instanceof Array) {
125 125 for (let i = 0; i < obj.length; i++) {
126 126 const x = cb.call(thisArg, obj[i], i);
127 127 if (x !== undefined)
128 128 return x;
129 129 }
130 130 } else {
131 131 const keys = Object.keys(obj);
132 132 for (const k of keys) {
133 133 const x = cb.call(thisArg, obj[k], k);
134 134 if (x !== undefined)
135 135 return x;
136 136 }
137 137 }
138 138 }
139 139
140 140 /** Copies property values from a source object to the destination and returns
141 141 * the destination onject.
142 142 *
143 143 * @param dest The destination object into which properties from the source
144 144 * object will be copied.
145 145 * @param source The source of values which will be copied to the destination
146 146 * object.
147 147 * @param template An optional parameter specifies which properties should be
148 148 * copied from the source and how to map them to the destination. If the
149 149 * template is an array it contains the list of property names to copy from the
150 150 * source to the destination. In case of object the templates contains the map
151 151 * where keys are property names in the source and the values are property
152 152 * names in the destination object. If the template isn't specified then the
153 153 * own properties of the source are entirely copied to the destination.
154 154 *
155 155 */
156 156 export function mixin<T, S>(dest: T, source: S, template?: string[] | object): T & S {
157 157 argumentNotNull(dest, "to");
158 158 const _res = dest as T & S;
159 159
160 160 if (isPrimitive(source))
161 161 return _res;
162 162
163 163 if (template instanceof Array) {
164 164 for (const p of template) {
165 165 if (p in source)
166 166 _res[p] = source[p];
167 167 }
168 168 } else if (template) {
169 169 const keys = Object.keys(source);
170 170 for (const p of keys) {
171 171 if (p in template)
172 172 _res[template[p]] = source[p];
173 173 }
174 174 } else {
175 175 const keys = Object.keys(source);
176 176 for (const p of keys)
177 177 _res[p] = source[p];
178 178 }
179 179
180 180 return _res;
181 181 }
182 182
183 183 /** Wraps the specified function to emulate an asynchronous execution.
184 184 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
185 185 * @param{Function|String} fn [Required] Function wich will be wrapped.
186 186 */
187 187 export function async(_fn: (...args: any[]) => any, thisArg): (...args: any[]) => PromiseLike<any> {
188 188 let fn = _fn;
189 189
190 190 if (arguments.length === 2 && !(fn instanceof Function))
191 191 fn = thisArg[fn];
192 192
193 193 if (fn == null)
194 194 throw new Error("The function must be specified");
195 195
196 196 function wrapresult(x, e?): PromiseLike<any> {
197 197 if (e) {
198 198 return {
199 199 then(cb, eb) {
200 200 try {
201 201 return eb ? wrapresult(eb(e)) : this;
202 202 } catch (e2) {
203 203 return wrapresult(null, e2);
204 204 }
205 205 }
206 206 };
207 207 } else {
208 208 if (x && x.then)
209 209 return x;
210 210 return {
211 211 then(cb) {
212 212 try {
213 213 return cb ? wrapresult(cb(x)) : this;
214 214 } catch (e2) {
215 215 return wrapresult(e2);
216 216 }
217 217 }
218 218 };
219 219 }
220 220 }
221 221
222 222 return (...args) => {
223 223 try {
224 224 return wrapresult(fn.apply(thisArg, args));
225 225 } catch (e) {
226 226 return wrapresult(null, e);
227 227 }
228 228 };
229 229 }
230 230
231 231 type _AnyFn = (...args) => any;
232 232
233 233 export function delegate<T, K extends keyof T>(target: T, _method: (K | _AnyFn)) {
234 234 let method;
235 235
236 236 if (!(_method instanceof Function)) {
237 237 argumentNotNull(target, "target");
238 238 method = target[_method];
239 239 if (!(method instanceof Function))
240 240 throw new Error("'method' argument must be a Function or a method name");
241 241 } else {
242 242 method = _method;
243 243 }
244 244
245 245 return (...args) => {
246 246 return method.apply(target, args);
247 247 };
248 248 }
249 249
250 250 export function delay(timeMs: number, ct = Cancellation.none) {
251 ct.throwIfRequested();
251 252 return new Promise((resolve, reject) => {
252 if (ct.isRequested()) {
253 ct.register(reject);
254 } else {
255 const h = ct.register(e => {
256 clearTimeout(id);
257 reject(e);
258 // we don't nedd to unregister h, since ct is already disposed
259 });
260 const id = setTimeout(() => {
261 h.destroy();
262 resolve();
263 }, timeMs);
264 }
253 const h = ct.register(e => {
254 clearTimeout(id);
255 reject(e);
256 // we don't nedd to unregister h, since ct is already disposed
257 });
258 const id = setTimeout(() => {
259 h.destroy();
260 resolve();
261 }, timeMs);
262
265 263 });
266 264 }
267 265
268 266 /**
269 267 * Для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ элСмСнта массива Π²Ρ‹Π·Ρ‹Π²Π°Π΅Ρ‚ ΡƒΠΊΠ°Π·Π°Π½Π½ΡƒΡŽ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ ΠΈ сохраняСт
270 268 * Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π΅Π½Π½ΠΎΠ΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ Π² массивС Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ΠΎΠ².
271 269 *
272 270 * @remarks cb ΠΌΠΎΠΆΠ΅Ρ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒΡΡ асинхронно, ΠΏΡ€ΠΈ этом ΠΎΠ΄Π½ΠΎΠ²Ρ€Π΅ΠΌΠ΅Π½Π½ΠΎ Π±ΡƒΠ΄Π΅Ρ‚
273 271 * Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΎΠ΄Π½Π° опСрация.
274 272 *
275 273 * @async
276 274 */
277 275 export function pmap(items, cb) {
278 276 argumentNotNull(cb, "cb");
279 277
280 278 if (isPromise(items))
281 279 return items.then(data => pmap(data, cb));
282 280
283 281 if (isNull(items) || !items.length)
284 282 return items;
285 283
286 284 let i = 0;
287 285 const result = [];
288 286
289 287 function next() {
290 288 let r;
291 289 let ri;
292 290
293 291 function chain(x) {
294 292 result[ri] = x;
295 293 return next();
296 294 }
297 295
298 296 while (i < items.length) {
299 297 r = cb(items[i], i);
300 298 ri = i;
301 299 i++;
302 300 if (isPromise(r)) {
303 301 return r.then(chain);
304 302 } else {
305 303 result[ri] = r;
306 304 }
307 305 }
308 306 return result;
309 307 }
310 308
311 309 return next();
312 310 }
313 311
314 312 export function pfor(items, cb) {
315 313 argumentNotNull(cb, "cb");
316 314
317 315 if (isPromise(items))
318 316 return items.then(data => {
319 317 return pmap(data, cb);
320 318 });
321 319
322 320 if (isNull(items) || !items.length)
323 321 return items;
324 322
325 323 let i = 0;
326 324
327 325 function next() {
328 326 while (i < items.length) {
329 327 const r = cb(items[i], i);
330 328 i++;
331 329 if (isPromise(r))
332 330 return r.then(next);
333 331 }
334 332 }
335 333
336 334 return next();
337 335 }
338 336
339 337 export function first<T>(sequence: ArrayLike<T>): T;
340 338 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
341 339 export function first<T>(
342 340 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
343 341 cb: (x: T) => void,
344 342 err?: (x: Error) => void
345 343 ): void;
346 344 /**
347 345 * Π’Ρ‹Π±ΠΈΡ€Π°Π΅Ρ‚ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ элСмСнт ΠΈΠ· ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ, ΠΈΠ»ΠΈ обСщания, Ссли Π²
348 346 * качСствС ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ ΠΎΠ±Π΅Ρ‰Π°Π½ΠΈΠ΅, ΠΎΠ½ΠΎ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ массив.
349 347 *
350 348 * @param {Function} cb ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°, Π΅ΠΌΡƒ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΠ΅Ρ€Π΅Π΄Π°Π½ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ
351 349 * элСмСнт ΠΏΠΎΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΡΡ‚ΠΈ Π² случаС успСха
352 350 * @param {Function} err ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ ΠΈΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ, Ссли массив пустой, Π»ΠΈΠ±ΠΎ
353 351 * нС массив
354 352 *
355 353 * @remarks Если Π½Π΅ ΡƒΠΊΠ°Π·Π°Π½Ρ‹ Π½ΠΈ cb Π½ΠΈ err, Ρ‚ΠΎΠ³Π΄Π° функция Π²Π΅Ρ€Π½Π΅Ρ‚ Π»ΠΈΠ±ΠΎ
356 354 * ΠΎΠ±Π΅Ρ‰Π°Π½ΠΈΠ΅, Π»ΠΈΠ±ΠΎ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ элСмСнт.
357 355 * @async
358 356 */
359 357 export function first<T>(
360 358 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
361 359 cb?: (x: T) => void,
362 360 err?: (x: Error) => void
363 361 ) {
364 362 if (isPromise(sequence)) {
365 363 return sequence.then(res => first(res, cb, err));
366 364 } else if (sequence && "length" in sequence) {
367 365 if (sequence.length === 0) {
368 366 if (err)
369 367 return err(new Error("The sequence is empty"));
370 368 else
371 369 throw new Error("The sequence is empty");
372 370 } else if (cb) {
373 371 cb(sequence[0]);
374 372 } else {
375 373 return sequence[0];
376 374 }
377 375 } else {
378 376 if (err)
379 377 err(new Error("The sequence is required"));
380 378 else
381 379 throw new Error("The sequence is required");
382 380 }
383 381 }
384 382
385 383 export function firstWhere<T>(
386 384 sequence: ArrayLike<T>,
387 385 predicate: (x: T) => boolean
388 386 ): T;
389 387 export function firstWhere<T>(
390 388 sequence: PromiseLike<ArrayLike<T>>,
391 389 predicate: (x: T) => boolean
392 390 ): PromiseLike<T>;
393 391 export function firstWhere<T>(
394 392 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
395 393 predicate: (x: T) => boolean,
396 394 cb: (x: T) => void,
397 395 err?: (x: Error) => void
398 396 ): void;
399 397
400 398 export function firstWhere<T>(
401 399 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
402 400 predicate?: (x: T) => boolean,
403 401 cb?: (x: T) => any,
404 402 err?: (x: Error) => any
405 403 ) {
406 404 if (isPromise(sequence)) {
407 405 return sequence.then(res => firstWhere(res, predicate, cb, err));
408 406 } else if (sequence && "length" in sequence) {
409 407 if (sequence.length === 0) {
410 408 if (err)
411 409 err(new Error("The sequence is empty"));
412 410 else
413 411 throw new Error("The sequence is empty");
414 412 } else {
415 413 if (!predicate) {
416 414 return cb ? cb(sequence[0]) && void (0) : sequence[0];
417 415 } else {
418 416 for (let i = 0; i < sequence.length; i++) {
419 417 const v = sequence[i];
420 418 if (predicate(v))
421 419 return cb ? cb(v) : v;
422 420 }
423 421 if (err)
424 422 err(new Error("The sequence doesn't contain matching items"));
425 423 else
426 424 throw new Error("The sequence doesn't contain matching items");
427 425 }
428 426 }
429 427 } else {
430 428 if (err)
431 429 err(new Error("The sequence is required"));
432 430 else
433 431 throw new Error("The sequence is required");
434 432 }
435 433 }
436 434
437 435 export function destroy(d: any) {
438 436 if (d && "destroy" in d)
439 437 d.destroy();
440 438 }
441 439
442 440 /**
443 441 * Used to mark that the async operation isn't awaited intentionally.
444 442 * @param p The promise which represents the async operation.
445 443 */
446 444 export function nowait(p: Promise<any>) {
447 445 }
448 446
449 447 /** represents already destroyed object.
450 448 */
451 449 export const destroyed = {
452 450 /** Calling to this method doesn't affect anything, noop.
453 451 */
454 452 destroy() {
455 453 }
456 454 };
@@ -1,97 +1,96
1 1 import * as tape from "tape";
2 2 import { Cancellation } from "@implab/core/Cancellation";
3 import { ICancellation } from "@implab/core/interfaces";
4 import { delay } from "./TestTraits";
3 import { delay } from "@implab/core/safe";
5 4
6 5 tape("standalone cancellation", async t => {
7 6
8 7 let doCancel: (e) => void;
9 8
10 9 const ct = new Cancellation(cancel => {
11 10 doCancel = cancel;
12 11 });
13 12
14 13 let counter = 0;
15 14 const reason = "BILL";
16 15
17 16 t.true(ct.isSupported(), "Cancellation must be supported");
18 17 t.false(ct.isRequested(), "Cancellation shouldn't be requested");
19 18 ct.throwIfRequested();
20 19 t.pass("The exception shouldn't be thrown unless the cancellation is requested");
21 20
22 21 ct.register(() => counter++);
23 22 t.equals(counter, 0, "counter should be zero");
24 23
25 24 ct.register(() => counter++).destroy();
26 25
27 26 doCancel(reason);
28 27
29 28 t.true(ct.isRequested(), "Cancellation should be requested");
30 29 t.equals(counter, 1, "The registered callback should be triggered");
31 30
32 31 ct.register(() => counter++);
33 32 t.equals(counter, 2, "The callback should be triggered immediately");
34 33
35 34 let msg;
36 35 ct.register(e => msg = e);
37 36 t.equals(msg, reason, "The cancellation reason should be passed to callback");
38 37
39 38 try {
40 39 msg = null;
41 40 ct.throwIfRequested();
42 41 t.fail("The exception should be thrown");
43 42 } catch (e) {
44 43 msg = e;
45 44 }
46 45 t.equals(msg, reason, "The cancellation reason should be catched");
47 46
48 47 t.end();
49 48 });
50 49
51 50 tape("async cancellation", async t => {
52 51
53 52 const ct = new Cancellation(cancel => {
54 53 cancel("STOP!");
55 54 });
56 55
57 56 try {
58 57 await delay(0, ct);
59 58 t.fail("Should thow the exception");
60 59 } catch (e) {
61 60 t.equals(e, "STOP!", "Should throw the cancellation reason");
62 61 }
63 62
64 63 t.end();
65 64 });
66 65
67 66 tape("cancel with external event", async t => {
68 67 const ct = new Cancellation(cancel => {
69 68 setTimeout(x => cancel("STOP!"), 0);
70 69 });
71 70
72 71 try {
73 72 await delay(10000, ct);
74 73 t.fail("Should thow the exception");
75 74 } catch (e) {
76 75 t.equals(e, "STOP!", "Should throw the cancellation reason");
77 76 }
78 77
79 78 t.end();
80 79 });
81 80
82 81 tape("operation normal flow", async t => {
83 82
84 83 let htimeout;
85 84 const ct = new Cancellation(cancel => {
86 85 htimeout = setTimeout(() => cancel("STOP!"), 1000);
87 86 });
88 87
89 88 try {
90 89 await delay(0, ct);
91 90 t.pass("Should pass");
92 91 } finally {
93 92 clearTimeout(htimeout);
94 93 }
95 94
96 95 t.end();
97 96 });
@@ -1,73 +1,73
1 1 import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource";
2 2 import * as tape from "tape";
3 import { TapeWriter, delay } from "./TestTraits";
4 3 import { Observable } from "@implab/core/Observable";
5 4 import { IObservable } from "@implab/core/interfaces";
5 import { delay } from "@implab/core/safe";
6 6
7 7 const trace = TraceSource.get("ObservableTests");
8 8
9 9 tape("events sequence example", async t => {
10 10
11 11 let events: IObservable<number>;
12 12
13 13 const done = new Promise<void>(resolve => {
14 14 events = new Observable<number>(async (notify, fail, finish) => {
15 15 for (let i = 0; i < 10; i++) {
16 16 await delay(0);
17 17 notify(i);
18 18 }
19 19 finish();
20 20 resolve();
21 21 });
22 22 });
23 23
24 24 let count = 0;
25 25 let complete = false;
26 26 events.on(x => count = count + x, null, () => complete = true);
27 27
28 28 const first = await events.next();
29 29
30 30 t.equals(first, 0, "the first event");
31 31 t.false(complete, "the sequence is not complete");
32 32
33 33 await done;
34 34
35 35 t.equals(count, 45, "the summ of the evetns");
36 36 t.true(complete, "the sequence is complete");
37 37
38 38 t.end();
39 39 });
40 40
41 41 tape("event sequence termination", async t => {
42 42 let events: IObservable<number>;
43 43
44 44 const done = new Promise<void>(resolve => {
45 45 events = new Observable<number>(async (notify, fail, complete) => {
46 46 await delay(0);
47 47 notify(1);
48 48 complete();
49 49 notify(2);
50 50 complete();
51 51 fail("Sequence terminated");
52 52 resolve();
53 53 });
54 54 });
55 55
56 56 let count = 0;
57 57 events.on(() => {}, e => count++, () => count++);
58 58
59 59 const first = await events.next();
60 60 t.equals(first, 1, "the first message");
61 61 try {
62 62 await events.next();
63 63 t.fail("shoud throw an exception");
64 64 } catch (e) {
65 65 t.pass("the sequence is terminated");
66 66 }
67 67
68 68 await done;
69 69
70 70 t.equals(count, 1, "the sequence must be terminated once");
71 71
72 72 t.end();
73 73 });
@@ -1,86 +1,99
1 1 import tape = require("tape");
2 import { delay } from "./TestTraits";
3 2 import { Cancellation } from "@implab/core/Cancellation";
4 import { first, isPromise } from "@implab/core/safe";
3 import { first, isPromise, firstWhere, delay, nowait } from "@implab/core/safe";
5 4
6 5 tape("await delay test", async t => {
7 6 // schedule delay
8 7 let resolved = false;
9 8 let res = delay(0).then(() => resolved = true);
10 9
11 10 t.false(resolved, "the delay should be async");
12 11
13 12 await res;
14 13 t.pass("await delay");
15 14
16 15 // create cancellation token
17 16 let cancel: (e?: any) => void;
18 17 const ct = new Cancellation(c => cancel = c);
19 18
20 19 // schedule delay
21 20 resolved = false;
22 21 res = delay(0, ct).then(() => resolved = true);
23 22
24 23 t.false(resolved, "created delay with ct");
25 24
26 25 // cancel
27 26 cancel();
28 27
29 28 try {
30 29 await res;
31 30 t.fail("the delay should fail when it is cancelled");
32 31 } catch {
33 32 t.pass("the delay is cancelled");
34 33 }
35 34
36 let died = false;
37
38 // try schedule delay after the cancellation is requested
39 res = delay(0, ct).then(x => true, () => died = true);
40
41 t.false(died, "The delay should be scheduled even if the cancellation is requested");
42
43 await res;
44 t.true(died, "the delay should fail when cancelled");
35 t.throws(() => {
36 // try schedule delay after the cancellation is requested
37 nowait(delay(0, ct));
38 }, "Should throw if cancelled before start");
45 39
46 40 t.end();
47 41 });
48 42
49 43 tape("sequemce test", async t => {
50 44 const sequence = ["a", "b", "c"];
51 45 const empty = [];
52 46
53 47 // synchronous tests
54 48 t.equals(first(sequence), "a", "Should return the first element");
49 t.equals(firstWhere(sequence, x => x === "b"), "b", "Should get the second element");
55 50
56 51 let v: string;
57 52 let e: Error;
58 53 first(sequence, x => v = x);
59 54 t.equal(v, "a", "The callback should be called for the first element");
55 firstWhere(sequence, x => x === "b", x => v = x);
56 t.equal(v, "b", "The callback should be called for the second element");
60 57
61 58 t.throws(() => {
62 59 first(empty);
63 60 }, "Should throw when the sequence is empty");
64 61
65 62 t.throws(() => {
63 firstWhere(empty, x => x === "b");
64 }, "Should throw when the sequence is empty");
65
66 t.throws(() => {
66 67 first(empty, x => v = x);
67 68 }, "Should throw when the sequence is empty");
68 69
70 t.throws(() => {
71 firstWhere(empty, x => x === "b", x => v = x);
72 }, "Should throw when the sequence is empty");
73
74 t.throws(() => {
75 firstWhere(sequence, x => x === "z");
76 }, "Should throw when the element isn't found");
77
78 t.throws(() => {
79 firstWhere(sequence, x => x === "z", x => v = x);
80 }, "Should throw when the element isn't found");
81
69 82 first(empty, null, x => e = x);
70 83 t.true(e, "The errorback should be called for the empty sequence");
71 84
72 85 // async tests
73 86 const asyncSequence = Promise.resolve(sequence);
74 87 const asyncEmptySequence = Promise.resolve(empty);
75 88
76 89 const promise = first(asyncSequence);
77 90 t.true(isPromise(promise), "Should return promise");
78 91
79 92 v = await promise;
80 93 t.equal(v, "a", "Should return the first element");
81 94
82 95 v = await new Promise(resolve => first(asyncSequence, resolve));
83 96 t.equal(v, "a", "The callback should be called for the first element");
84 97
85 98 t.end();
86 99 });
@@ -1,88 +1,65
1 1 import { IObservable, ICancellation, IDestroyable } from "@implab/core/interfaces";
2 2 import { Cancellation } from "@implab/core/Cancellation";
3 3 import { TraceEvent, LogLevel, WarnLevel, DebugLevel, TraceSource } from "@implab/core/log/TraceSource";
4 4 import * as tape from "tape";
5 5 import { argumentNotNull, destroy } from "@implab/core/safe";
6 6
7 7 export class TapeWriter implements IDestroyable {
8 8 readonly _tape: tape.Test;
9 9
10 10 _subscriptions = new Array<IDestroyable>();
11 11
12 12 constructor(t: tape.Test) {
13 13 argumentNotNull(t, "tape");
14 14 this._tape = t;
15 15 }
16 16
17 17 writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) {
18 18 const subscription = source.on(this.writeEvent.bind(this));
19 19 if (ct.isSupported()) {
20 20 ct.register(subscription.destroy.bind(subscription));
21 21 }
22 22 this._subscriptions.push(subscription);
23 23 }
24 24
25 25 writeEvent(next: TraceEvent) {
26 26 if (next.level >= DebugLevel) {
27 27 this._tape.comment(`DEBUG ${next.source.id} ${next.arg}`);
28 28 } else if (next.level >= LogLevel) {
29 29 this._tape.comment(`LOG ${next.source.id} ${next.arg}`);
30 30 } else if (next.level >= WarnLevel) {
31 31 this._tape.comment(`WARN ${next.source.id} ${next.arg}`);
32 32 } else {
33 33 this._tape.comment(`ERROR ${next.source.id} ${next.arg}`);
34 34 }
35 35 }
36 36
37 37 destroy() {
38 38 this._subscriptions.forEach(destroy);
39 39 }
40 40 }
41 41
42 export async function delay(timeout: number, ct: ICancellation = Cancellation.none) {
43 let un: IDestroyable;
44
45 try {
46 await new Promise((resolve, reject) => {
47 if (ct.isRequested()) {
48 un = ct.register(reject);
49 } else {
50 const ht = setTimeout(() => {
51 resolve();
52 }, timeout);
53
54 un = ct.register(e => {
55 clearTimeout(ht);
56 reject(e);
57 });
58 }
59 });
60 } finally {
61 destroy(un);
62 }
63 }
64
65 42 export function test(name: string, cb: (t: tape.Test) => any) {
66 43 tape(name, async t => {
67 44 const writer = new TapeWriter(t);
68 45
69 46 TraceSource.on(ts => {
70 47 ts.level = DebugLevel;
71 48 writer.writeEvents(ts.events);
72 49 });
73 50
74 51 try {
75 52 await cb(t);
76 53 } catch (e) {
77 54
78 55 // verbose error information
79 56 // tslint:disable-next-line
80 57 console.error(e);
81 58 t.fail(e);
82 59
83 60 } finally {
84 61 t.end();
85 62 destroy(writer);
86 63 }
87 64 });
88 65 }
General Comments 0
You need to be logged in to leave comments. Login now