UNPKG

asclasit

Version:

ASync CLasses + ASync ITerators

507 lines (431 loc) 11.5 kB
const $ = require('./class'); const AsIt = require('../as-it'); test('new $: abstract class', () => { expect(() => new $()).toThrow($.AbstractClassError); }); class Level1 extends $ { static [$] = { wakeInit: true, wakeTimeout: 5000, wakeRetries: 3, wakeRetryDelay: 40, sleepImmediate: true, }; events = []; constructor(arg1) { super(); this.arg1 = arg1; } async *[$](retries) { try { this.l1conn = true; this.events.push('l1conn'); while (this.l1conn) { this.events.push('l1use'); yield; } } finally { this.l1conn = false; this.events.push('l1dc'); } } async $_method(arg) { this.events.push(`l1method ${arg}`); } } class Level2 extends Level1 { static [$] = { ...this[$], wakeInit: false, sleepImmediate: false, sleepIdle: 120000, sleepTimeout: 3000, onSleepError: function (err) { this.lastSleepError = err; }, }; constructor(arg2, arg1) { super(arg1); this.arg2 = arg2; } *[$](retries) { try { this.events.push('l2try'); if (retries < 3) throw new Error('fail'); this.events.push('l2conn'); this.l2conn = true; while (this.l2conn) { this.events.push('l2use'); yield Promise.resolve(1); } } finally { this.events.push('l2dc'); this.l2conn = false; } } async $_method(arg) { this.events.push(`l2method ${arg}`); } #prop = null; get $_prop() { this.events.push('propget'); return this.#prop; } set $_prop(value) { this.events.push('propset'); this.#prop = value; } } test('class: Level1 instance', async () => { const l1 = new Level1('A1'); await $.tick(); expect(l1.events).toEqual(['l1conn', 'l1use', 'l1dc']); l1.events = []; await l1.method('x'); expect(l1.events).toEqual(['l1conn', 'l1use', 'l1method x', 'l1dc']); }); test('class: Level2 instance', async () => { const l2 = new Level2('A2'); const promise = l2.method('y'); expect(!l2[$].awake).toBe(true); expect(l2[$].waking instanceof Promise).toBe(true); await promise; expect(l2[$].awake).toBe(true); expect(l2[$].waking).toBe(null); l2.method('a'); l2.method('b'); await l2.method('c'); expect(l2.events).toEqual([ 'l1conn', 'l1use', 'l2try', 'l2dc', 'l2try', 'l2dc', 'l2try', 'l2conn', 'l2use', 'l2method y', 'l1use', 'l2use', 'l1use', 'l2method a', 'l2use', 'l2method b', 'l2method c', ]); l2.events = []; expect(l2[$].awake).toBe(true); l2.prop = 5; expect(await l2.prop).toBe(5); expect(l2[$].awake).toBe(true); expect(l2[$].sleeping).toBe(null); const sleep = l2[$].sleep(); expect(l2[$].sleeping instanceof Promise).toBe(true); await sleep; expect(l2[$].sleeping).toBe(null); expect(l2[$].awake).toBe(false); expect(l2.events).toEqual([ 'l1use', 'l2use', 'l1use', 'propset', 'l2use', 'propget', 'l2dc', 'l1dc' ]); }); test('class: sleep-wake concurrency', async () => { const l1 = new Level1(); await $.tick(); l1.events = []; l1[$].sleep(); l1[$].wake(); l1[$].sleep(); l1[$].wake(); l1[$].sleep(); l1[$].wake(); l1[$].sleep(); l1[$].wake(); await $.tick(); await l1[$].sleep(); expect(l1.events).toEqual([ 'l1conn', 'l1use', 'l1use', 'l1dc', ]); }); test('class: wake-sleep concurrency', async () => { const l1 = new Level1(); await $.tick(); l1.events = []; await l1[$].wake(); l1[$].sleep(); l1[$].wake(); l1[$].sleep(); l1[$].wake(); l1[$].sleep(); l1[$].wake(); l1[$].sleep(); l1[$].wake(); await $.tick(); await l1[$].sleep(); expect(l1.events).toEqual([ 'l1conn', 'l1use', 'l1dc', 'l1conn', 'l1use', 'l1use', 'l1dc', ]); }); test('class: wake condition', async () => { const l2 = new Level2(); await l2.method('1'); l2.l2conn = false; await l2.method('2'); l2.l1conn = false; await l2.method('3'); await l2[$].sleep(); expect(l2.events).toEqual([ 'l1conn', 'l1use', 'l2try', 'l2dc', 'l2try', 'l2dc', 'l2try', 'l2conn', 'l2use', 'l2method 1', 'l1use', 'l2dc', 'l2try', 'l2dc', 'l2try', 'l2conn', 'l2use', 'l2method 2', 'l1dc', 'l1conn', 'l1use', 'l2try', 'l2dc', 'l2try', 'l2dc', 'l2try', 'l2conn', 'l2use', 'l2method 3', 'l2dc', 'l1dc', ]); }); class Delayed extends $ { static [$] = {wakeRetryDelay: 20}; async *[$]() { try { while (true) { await $.delay(40); yield; } } finally { await $.delay(40); } } async $_method() { await $.delay(40); } async *$_iterMethod() { yield 1; yield 2; } } test('class: waking/running/sleeping', async () => { const a = new Delayed(); a.method(); //await $.tick(); expect(a[$].sleep()).toBe(null); //await a[$].wake(); expect(await AsIt.from(a.iterMethod()).toArray()).toEqual([1, 2]); expect(await a[$].sleep()).toBe(true); a.method(); await a[$].waking; expect(a[$].waking).toBe(null); await $.tick(); expect(a[$].runningCount).toEqual(1); expect(a[$].running.first() instanceof Promise).toEqual(true); await a[$].sleep(); }); class WakeFailed extends $ { static [$] = {wakeRetryDelay: 20}; *[$]() { } } test('class: wake failed', async () => { const wf = new WakeFailed(); try { await wf[$].wake(); throw null; } catch (err) { expect(err.constructor).toBe($.WakeFailedError); } }); class WakeError extends $ { static [$] = {wakeRetryDelay: 20}; *[$]() { throw new Error('bad'); } } test('class: wake error', async () => { const we = new WakeError(); try { await we[$].wake(); throw null; } catch (err) { expect(err.message).toBe('bad'); } }); class WakeTimeout extends $ { static [$] = {wakeTimeout: 40, wakeRetryDelay: 20}; *[$]() { yield $.delay(100); } } test('class: wake timeout', async () => { const wt = new WakeTimeout(); try { await wt[$].wake(); throw null; } catch (err) { expect(err.constructor).toBe($.WakeTimeoutError); } }); class SleepTimeout extends $ { static [$] = class extends $.Inst { sleepTimeout = 50; onSleepError(err) { this.lastSleepError = err; } }; async *[$]() { try { yield; } finally { await $.delay(100); } } } test('class: sleep timeout', async () => { const st = new SleepTimeout(); await st[$].wake(); await st[$].sleep(); expect(st.lastSleepError.constructor).toBe($.SleepTimeoutError); }); class NeverSleep extends $ { static [$] = {sleepIdle: null}; } test('class: never sleep', async () => { const st = new NeverSleep(); await st[$].wake(); expect(st[$].awake).toBe(true); }); class DelayedSleeping extends $ { async *[$]() { try { while (true) yield; } finally { await $.delayMsec(10); } } } test('class: delayed sleeping: wake while sleeping', async () => { const st = new DelayedSleeping(); await st[$].wake(); st[$].sleep(); await st[$].wake(); expect(st[$].awake).toBe(true); await st[$].sleep(); }); /*class MethodBound extends $ { marker = 'native'; async bound$() { return this.marker; } async $_wakeBound$() { return this.marker; } async* iterBound$() { yield this.marker; yield 2; yield 3; } async* $_wakeIterBound$() { yield this.marker; yield 2; yield 3; } } test('class: method bound', async () => { const my = new MethodBound(); const ctx = {marker: 'foreign'}; expect(await my.bound$.call(ctx)).toBe('foreign'); expect(await my.bound.call(ctx)).toBe('native'); expect(await my.wakeBound.call(ctx)).toBe('native'); expect(await AsIt.from(my.iterBound$.call(ctx)).toArray()).toEqual(['foreign', 2, 3]); expect(await AsIt.from(my.iterBound.call(ctx)).toArray()).toEqual(['native', 2, 3]); expect(await AsIt.from(my.wakeIterBound.call(ctx)).toArray()).toEqual(['native', 2, 3]); expect(await my[$].sleep()).toBe(true); expect(await my[$].sleep()).toBe(null); });*/ class MyEvents extends $ { static [$] = class extends $.Inst { throwed = []; throw(err) { this.throwed.push(err); } } } test('class: event support: sync', async () => { const my = new MyEvents(); const ev = []; expect(my[$].on()).toBe(false); expect(my[$].on('a', 1)).toBe(false); const event1 = (...args) => ev.push(['event1', ...args]); expect(my[$].on('event1', event1)).toBe(true); my[$].on('event1', (...args) => ev.push(['event1:2', ...args])); const event2 = (...args) => ev.push(['event2', ...args]); my[$].on('event2', event2); my[$].on('event2', (...args) => ev.push(['event2:2', ...args])); my[$].once('event3', (...args) => (ev.push(['event3', ...args]), true)); my[$].once('event3', (...args) => (ev.push(['pre:event3', ...args]), true), {pre: true}); my[$].once('event3', (...args) => ev.push(['event3:2', ...args])); const preevent1 = (...args) => ev.push(['pre:event1', ...args]); expect(my[$].on('event1', preevent1, {pre: true})).toBe(true); my[$].on('event1', (...args) => ev.push(['pre:event1:2', ...args]), {pre: true}); expect(my[$].on('event1', event1, {pre: true})).toBe(false); expect(my[$].on('event1', preevent1)).toBe(false); await my[$].emit('event1', 1, 2); await my[$].emit('event2', 3, 4); await my[$].emit('event3', 5, 6); await my[$].emit('event2', 7, 8); await my[$].emit('event3', 9, 0); await my[$].emit('event4', 0, 0); my[$].emit('event1', 1, 2); expect(my[$].off('event1', preevent1)).toBe(true); expect(my[$].off('event1', preevent1)).toBe(false); my[$].emit('event2', 1, 2); expect(my[$].off('event2', event2)).toBe(true); expect(my[$].off('event2', event2)).toBe(false); expect(ev).toEqual([ ['pre:event1:2', 1, 2], ['pre:event1', 1, 2], ['event1', 1, 2], ['event1:2', 1, 2], ['event2', 3, 4], ['event2:2', 3, 4], ['pre:event3', 5, 6], ['event2', 7, 8], ['event2:2', 7, 8], ['event3', 9, 0], ['pre:event1:2', 1, 2], ['event2', 1, 2], ]); expect(my[$].off('event1')).toBe(true); expect(my[$].off('event1')).toBe(false); expect(my[$].off()).toBe(true); expect(my[$].off()).toBe(false); }); test('class: event support: throw', async () => { const my = new MyEvents(); const throw1 = () => {throw 1;}; const throw2 = () => {throw 2;}; my[$].on('fail', throw1); my[$].on('fail', throw2, {pre: true}); await my[$].emit('fail', {}); expect(my[$].throwed).toEqual([2, 1]); my[$].off('fail', throw1); my[$].off('fail', throw2); await my[$].emit('fail', {}); expect(my[$].throwed).toEqual([2, 1]); my[$].on('fail', throw1); my[$].off('fail', throw1); await my[$].emit('fail', {}); expect(my[$].throwed).toEqual([2, 1]); my[$].off('fail', throw1); my[$].off('fail'); my[$].off(); }); class MyEmpty extends $ { } test('class: event support: throw default', async () => { const my = new MyEmpty(); const throw1 = () => {throw 1;}; my[$].on('fail', throw1); const orig = $.defaultEmitError; let error, reason; $.defaultEmitError = (err, r) => { error = err; reason = r; } await my[$].emit('fail', {}); $.defaultEmitError = orig; expect(error).toBe(1); expect(reason).toBe('emit'); }); test('class: IoC', () => { const config1 = $(); const config2 = $(); const inst1 = MyEmpty.IoC(config1); const inst2 = MyEmpty.IoC(config2); expect(MyEmpty.IoC(config1)).toBe(inst1); expect(MyEmpty.IoC(config2)).toBe(inst2); });