UNPKG

@traxjs/trax

Version:

Reactive state management

1,082 lines (930 loc) 36.8 kB
import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createEventStream } from '../eventstream'; import { EventStream, StreamEvent, traxEvents } from '../types'; import { mockGlobalConsole, pause, printEvents, resetGlobalConsole } from './utils'; describe('Event Stream', () => { let log: EventStream; let count = 0; const internalSrcKey = {}; function printLogs(minCycleId = 0, ignoreCycleEvents = true): string[] { return printEvents(log, ignoreCycleEvents, minCycleId); } function getLogArray() { const arr: StreamEvent[] = []; log.scan((itm: StreamEvent) => { arr.push(itm); }); return arr; } beforeEach(() => { count = 0; log = createEventStream(internalSrcKey); }); describe('LogFormatter', () => { it('should generate errors for invalid types', async () => { log.event("", { foo: "bar" }); expect(printLogs()).toMatchObject([ "0:1 !ERR - Event type cannot be empty", ]); }); it('should not accept unauthorized events', async () => { log.event(traxEvents.New, { foo: "bar" }); expect(printLogs()).toMatchObject([ "0:1 !ERR - Event type cannot start with reserved prefix: !NEW", ]); }); it('should accept authorized events', async () => { log.event(traxEvents.New, { foo: "bar" }, internalSrcKey); expect(printLogs()).toMatchObject([ '0:1 !NEW - {"foo":"bar"}', ]); }); it('should accept Info, warning and errors without authorization', async () => { log.event(traxEvents.Info, { foo: "bar" }); log.event(traxEvents.Warning, { foo: "bar" }); log.event(traxEvents.Error, { foo: "bar" }); expect(printLogs()).toMatchObject([ '0:1 !LOG - {foo:bar}', '0:2 !WRN - {foo:bar}', '0:3 !ERR - {foo:bar}', ]); }); it('should accept Info, warning and errors with authorization', async () => { log.event(traxEvents.Info, { foo: "bar" }, internalSrcKey); log.event(traxEvents.Warning, { foo: "bar" }, internalSrcKey); log.event(traxEvents.Error, { foo: "bar" }, internalSrcKey); expect(printLogs()).toMatchObject([ '0:1 !LOG - {foo:bar}', '0:2 !WRN - {foo:bar}', '0:3 !ERR - {foo:bar}', ]); }); it('should generate error in case of stringification problem', async () => { log.event(traxEvents.New, { foo: "bar", fn: BigInt(42) } as any, internalSrcKey); expect(printLogs()).toMatchObject([ '0:1 !ERR - Event strinfication error: TypeError: Do not know how to serialize a BigInt', ]); }); }); describe('Events', () => { it('should log internal and custom events', async () => { expect(log.size).toBe(0); log.event("foo.bar"); expect(log.size).toBe(2); // because of cycle start log.event("blah", { v: "value" }); expect(log.size).toBe(3); log.event(traxEvents.New, {}, internalSrcKey); expect(log.size).toBe(4); expect(printLogs(0, false)).toMatchObject([ "0:0 !CS - 0", "0:1 foo.bar - NO-DATA", '0:2 blah - {"v":"value"}', "0:3 !NEW - {}", ]); }); it('should log errors for non authorized events', async () => { log.event(traxEvents.New, {}, internalSrcKey); log.event(traxEvents.New, {}); // key not provided expect(log.size).toBe(3); expect(printLogs()).toMatchObject([ "0:1 !NEW - {}", '0:2 !ERR - Event type cannot start with reserved prefix: !NEW', ]); }); }); describe('Scan', () => { it('should scan until processor return false', async () => { log.event("foo.bar"); log.event("foo.bar"); log.event("foo.bar"); log.event("foo.bar"); expect(log.size).toBe(4 + 1); expect(printLogs2()).toMatchObject([ "0:0 !CS - {\"elapsedTime\":0}", "0:1 foo.bar - NO-DATA", "0:2 foo.bar - NO-DATA" ]); function printLogs2(): string[] { const arr: string[] = []; let count = 0; log.scan((itm) => { arr.push(`${itm.id} ${itm.type} - ${itm.data || "NO-DATA"}`); count++; return (count !== 3); }); return arr; } }); }); describe('App logs', () => { it('should log info messages', async () => { log.info("Hello", "World"); log.info("Hello", "Trax", "!"); expect(printLogs()).toMatchObject([ '0:1 !LOG - Hello World', '0:2 !LOG - Hello Trax !', ]); }); it('should log info messages', async () => { log.warn("A", 42, "x"); log.warn("B", false, null, 321); expect(printLogs()).toMatchObject([ '0:1 !WRN - A 42 x', '0:2 !WRN - B false null 321', ]); }); it('should log error messages', async () => { log.error("Some error", { description: "!!!" }); log.error({ desc: "Some Error" }); log.error(); log.error("Unexpected error"); expect(printLogs()).toMatchObject([ '0:1 !ERR - [Some error,{description:!!!}]', '0:2 !ERR - {desc:Some Error}', '0:3 !ERR - NO-DATA', '0:4 !ERR - Unexpected error', ]); }); }); describe('Stream Maxsize', () => { it('should have a default maxSize of 1000', async () => { expect(log.maxSize).toBe(1000); }); it('should consider negative values as no limits', async () => { log.maxSize = 4; expect(log.maxSize).toBe(4); log.maxSize = -4; expect(log.maxSize).toBe(-1); }); it('should not accept values 0 and 1', async () => { log.maxSize = 1; expect(log.maxSize).toBe(2); log.maxSize = 9; expect(log.maxSize).toBe(9); log.maxSize = 0; expect(log.maxSize).toBe(2); }); it('should start rotating entries when max size is reached', async () => { log.maxSize = 4; log.info("A"); log.info("B"); log.info("C"); expect(log.size).toBe(3 + 1); expect(printLogs(0, false)).toMatchObject([ '0:0 !CS - 0', '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !LOG - C', ]); log.info("D"); expect(log.size).toBe(3 + 1); expect(printLogs(0, false)).toMatchObject([ '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !LOG - C', '0:4 !LOG - D', ]); }); it('should accept max size increase', async () => { log.maxSize = 3; log.info("A"); log.info("B"); log.info("C"); log.info("D"); expect(log.size).toBe(3); log.maxSize = 5; log.info("E"); log.info("F"); log.info("G"); expect(log.size).toBe(5); expect(printLogs(0, false)).toMatchObject([ '0:3 !LOG - C', '0:4 !LOG - D', '0:5 !LOG - E', '0:6 !LOG - F', '0:7 !LOG - G', ]); }); it('should manage size decrease (max reached)', async () => { log.maxSize = 4; log.info("A"); log.info("B"); log.info("C"); expect(log.size).toBe(4); expect(printLogs(0, false)).toMatchObject([ '0:0 !CS - 0', '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !LOG - C' ]); log.maxSize = 2; expect(log.size).toBe(2); expect(printLogs(0, false)).toMatchObject([ '0:2 !LOG - B', '0:3 !LOG - C' ]); }); it('should manage size decrease (max not reached)', async () => { log.maxSize = 42; log.info("A"); log.info("B"); log.info("C"); log.info("D"); log.info("E"); log.info("F"); log.info("G"); expect(log.size).toBe(7 + 1); log.maxSize = 3; expect(log.size).toBe(3); expect(printLogs(0, false)).toMatchObject([ '0:5 !LOG - E', '0:6 !LOG - F', '0:7 !LOG - G' ]); }); it('should support no limits', async () => { log.maxSize = -1; expect(log.maxSize).toBe(-1); log.info("A"); log.info("B"); log.info("C"); log.info("D"); log.info("E"); log.info("F"); log.info("G"); expect(log.size).toBe(7 + 1); await Promise.resolve(); expect(log.size).toBe(7 + 2); expect(printLogs(0, false)).toMatchObject([ '0:0 !CS - 0', '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !LOG - C', '0:4 !LOG - D', '0:5 !LOG - E', '0:6 !LOG - F', '0:7 !LOG - G', '0:8 !CC - 0', ]); }); }); describe('Cycle ids', () => { it('should incremented when a new cycle starts', async () => { log.info("A"); log.info("B"); await Promise.resolve(); log.info("C"); await Promise.resolve(); // nothing logged here, so no new cycle in the logger await Promise.resolve(); log.warn("D"); log.info("E"); expect(printLogs(0, false)).toMatchObject([ '0:0 !CS - 0', '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !CC - 0', '1:0 !CS - 0', '1:1 !LOG - C', '1:2 !CC - 0', '2:0 !CS - 0', '2:1 !WRN - D', '2:2 !LOG - E', ]); }); }); describe('Cycle events', () => { beforeEach(() => { count = 0; log = createEventStream(internalSrcKey, undefined, () => { log.info("BEFORE CC"); }); }); it('should give elapsed time on cycle start / end', async () => { log.info("A"); log.info("B"); await pause(10); log.info("C"); await Promise.resolve(); // nothing logged here, so no new cycle in the logger await pause(20); log.warn("D"); log.info("E"); expect(printLogs(0, false)).toMatchObject([ '0:0 !CS - 0', '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !LOG - BEFORE CC', '0:4 !CC - 0', '1:0 !CS - 0', // >= 10 '1:1 !LOG - C', '1:2 !LOG - BEFORE CC', '1:3 !CC - 0', '2:0 !CS - 0', // >= 20 '2:1 !WRN - D', '2:2 !LOG - E', ]); const logs = getLogArray(); expect(elapsed(0)).toBe(0); // first is always 0 expect(elapsed(4)).toBeLessThan(10); // probably 0 or 1 expect(elapsed(5)).toBeGreaterThan(10 - 2) expect(elapsed(8)).toBeLessThan(10); // probably 0 or 1 expect(elapsed(9)).toBeGreaterThan(20 - 2) function elapsed(idx: number) { return JSON.parse("" + logs[idx]!.data!).elapsedTime; } }); it('should return the last stream event', async () => { expect(log.lastEvent()).toBe(undefined); log.info("A"); expect(log.lastEvent()!.type).toBe(traxEvents.Info); await log.awaitEvent(traxEvents.CycleComplete); expect(log.lastEvent()!.type).toBe(traxEvents.CycleComplete); }); }); describe('Subscription', () => { it('should allow to await a specific event', async () => { let count = 0, lastEvent: StreamEvent | null = null; log.awaitEvent(traxEvents.Warning).then((e) => { count++; lastEvent = e; }); log.info("A"); log.info("B"); expect(count).toBe(0); await Promise.resolve(); // cycle ended expect(count).toBe(0); log.warn("C"); expect(count).toBe(0); log.info("D"); expect(count).toBe(0); log.warn("E"); // 2nd warning but won't trigger any callback expect(count).toBe(0); await pause(1); // flushes all pending promises expect(count).toBe(1); expect(printLogs(0, false)).toMatchObject([ '0:0 !CS - 0', '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !CC - 0', '1:0 !CS - 0', '1:1 !WRN - C', '1:2 !LOG - D', '1:3 !WRN - E', '1:4 !CC - 0', ]); expect(lastEvent).toMatchObject({ id: '1:1', type: '!WRN', data: '"C"' // JSON stringified }); }); it('should allow to await cycle end', async () => { log.info("A"); log.info("B"); await log.awaitEvent(traxEvents.CycleComplete); expect(printLogs(0, false)).toMatchObject([ '0:0 !CS - 0', '0:1 !LOG - A', '0:2 !LOG - B', '0:3 !CC - 0' ]); }); it('should allow 2 listeners to await the same event', async () => { let count1 = 0, count2 = 0, lastEvent1: StreamEvent | null = null, lastEvent2: StreamEvent | null = null; log.awaitEvent(traxEvents.Warning).then((e) => { count1++; lastEvent1 = e; }); log.awaitEvent(traxEvents.Warning).then((e) => { count2++; lastEvent2 = e; }); log.info("A"); log.info("B"); expect(count1).toBe(0); await Promise.resolve(); // cycle ended log.warn("C"); log.info("D"); log.warn("E"); // 2nd warning but won't trigger any callback expect(count1).toBe(0); await pause(1); // flushes all pending promises expect(count1).toBe(1); expect(count2).toBe(1); expect(lastEvent1).toMatchObject(lastEvent2!); }); it('should allow to await an event matching certain data properties (data object)', async () => { let count = 0, lastEvent: StreamEvent | null = null log.awaitEvent(traxEvents.ProcessingEnd, { name: "processX" }).then((e) => { count++; lastEvent = e; }); log.info("A"); let c = log.startProcessingContext({ name: "processA" }); log.info("B"); c.end(); expect(count).toBe(0); await pause(1); expect(count).toBe(0); // would be 1 if name filter didn't work expect(printLogs()).toMatchObject([ "0:1 !LOG - A", "0:2 !PCS - processA", "0:3 !LOG - B", "0:4 !PCE - 0:2", ]); log.info("C"); let pc = log.startProcessingContext({ name: "processX" }) pc.end() expect(count).toBe(0); log.info("D"); await pause(1); expect(count).toBe(1); // was called expect(printLogs(1)).toMatchObject([ "1:1 !LOG - C", "1:2 !PCS - processX", "1:3 !PCE - 1:2", "1:4 !LOG - D", ]); expect(JSON.parse((lastEvent as any).data).processId).toBe("1:2") log.info("E"); pc = log.startProcessingContext({ name: "processX" }) pc.end() expect(count).toBe(1); log.info("F"); await pause(1); expect(count).toBe(1); // no more calls (awaitEvent is only called once) }); it('should allow to await an event matching certain data properties (object + regexp)', async () => { let count = 0, lastEvent: StreamEvent | null = null; log.awaitEvent(traxEvents.ProcessingEnd, { name: /process/, src: "abc" }).then((e) => { count++; lastEvent = e; }); log.info("A"); let c = log.startProcessingContext({ name: "processA" }); log.info("B"); c.end(); expect(count).toBe(0); await pause(1); expect(count).toBe(0); // src doesn't match expect(printLogs()).toMatchObject([ "0:1 !LOG - A", "0:2 !PCS - processA", "0:3 !LOG - B", "0:4 !PCE - 0:2", ]); log.info("C"); let pc = log.startProcessingContext({ name: "XYZ/processX", src: "abc" }); pc.end() expect(count).toBe(0); log.info("D"); await pause(1); expect(count).toBe(1); // was called expect(printLogs(1)).toMatchObject([ "1:1 !LOG - C", "1:2 !PCS - XYZ/processX", "1:3 !PCE - 1:2", "1:4 !LOG - D", ]); expect(JSON.parse((lastEvent as any).data).processId).toBe("1:2") log.info("E"); pc = log.startProcessingContext({ name: "XYZ/processX", src: "abc" }) pc.end() expect(count).toBe(1); log.info("F"); await pause(1); expect(count).toBe(1); // no more calls (awaitEvent is only called once) }); it('should allow to await an event matching certain data properties (primitive type data)', async () => { let count = 0, lastEvent: StreamEvent | null = null log.awaitEvent("MyEvent", "MyData").then((e) => { count++; lastEvent = e; }); log.info("A"); log.event("MyEvent", "SomeData"); log.info("B"); expect(count).toBe(0); await pause(1); expect(count).toBe(0); // would be 1 if name filter didn't work expect(printLogs()).toMatchObject([ "0:1 !LOG - A", "0:2 MyEvent - \"SomeData\"", "0:3 !LOG - B", ]); log.info("C"); log.event("MyEvent", "MyData"); expect(count).toBe(0); log.info("D"); await pause(1); expect(count).toBe(1); // was called expect(printLogs(1)).toMatchObject([ "1:1 !LOG - C", "1:2 MyEvent - \"MyData\"", "1:3 !LOG - D", ]); expect((lastEvent as any).data).toBe('"MyData"'); // JSON stringified log.info("E"); log.event("MyEvent", "MyData"); expect(count).toBe(1); log.info("F"); await pause(1); expect(count).toBe(1); // no more calls (awaitEvent is only called once) }); it('should ignore await for unmatching types', async () => { let count = 0, lastEvent: StreamEvent | null = null log.awaitEvent("MyEvent", { value: "MyData" }).then((e) => { count++; lastEvent = e; }); log.info("A"); log.event("MyEvent", "MyData"); log.info("B"); expect(count).toBe(0); await pause(1); expect(count).toBe(0); // would be 1 if name filter didn't work expect(printLogs()).toMatchObject([ "0:1 !LOG - A", "0:2 MyEvent - \"MyData\"", "0:3 !LOG - B", ]); }); it('should accept multiple subscriptions with the same callback', async () => { let traces = ""; function cb(e: StreamEvent) { traces += e.id + "/" + e.type + ";" } const s1 = log.subscribe("*", cb); log.info("A"); expect(traces).toBe("0:0/!CS;0:1/!LOG;"); traces = ""; const s2 = log.subscribe("*", cb); expect(s2).not.toBe(s1); log.info("B"); log.info("C"); expect(traces).toBe("0:2/!LOG;0:2/!LOG;0:3/!LOG;0:3/!LOG;"); // double logging traces = ""; let r2 = log.unsubscribe(s2); expect(r2).toBe(true); r2 = log.unsubscribe(s2); expect(r2).toBe(false); // already unsubscribed const r3 = log.unsubscribe(cb); expect(r3).toBe(false); log.info("D"); expect(traces).toBe("0:4/!LOG;"); traces = ""; const r1 = log.unsubscribe(s1); expect(r1).toBe(true); log.info("E"); expect(traces).toBe(""); }); it('should accept mixing * and specific event subscriptions', async () => { let traces = "", warnings = ""; const s1 = log.subscribe("*", (e: StreamEvent) => { traces += e.id + "/" + e.type + ";" }); const s2 = log.subscribe(traxEvents.Warning, (e: StreamEvent) => { warnings += e.id + "/" + e.type + ";" }); log.info("A"); expect(traces).toBe("0:0/!CS;0:1/!LOG;"); expect(warnings).toBe(""); traces = warnings = ""; log.warn("B"); expect(traces).toBe("0:2/!WRN;"); expect(warnings).toBe("0:2/!WRN;"); traces = warnings = ""; log.info("C"); expect(traces).toBe("0:3/!LOG;"); expect(warnings).toBe(""); traces = warnings = ""; log.warn("D"); expect(traces).toBe("0:4/!WRN;"); expect(warnings).toBe("0:4/!WRN;"); const r1 = log.unsubscribe(s1); expect(r1).toBe(true); traces = warnings = ""; log.warn("E"); expect(traces).toBe(""); expect(warnings).toBe("0:5/!WRN;"); await log.awaitEvent(traxEvents.CycleComplete); traces = warnings = ""; log.warn("F"); expect(traces).toBe(""); expect(warnings).toBe("1:1/!WRN;"); const r2 = log.unsubscribe(s2); expect(r2).toBe(true); traces = warnings = ""; log.warn("G"); expect(traces).toBe(""); expect(warnings).toBe(""); }); it('should log an error in case of invalid subscription event', async () => { log.info("A"); await log.awaitEvent("*"); await log.awaitEvent(""); expect(printLogs(0, false)).toMatchObject([ "0:0 !CS - 0", "0:1 !LOG - A", "0:2 !ERR - [trax/eventStream.await] Invalid event type: '*'", "0:3 !CC - 0", "1:0 !CS - 0", "1:1 !ERR - [trax/eventStream.await] Invalid event type: ''", "1:2 !CC - 0", ]); }); }); describe('Processing Context', () => { it('should support synchronous contexts', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); log.warn("C"); c.end(); log.info("D"); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !WRN - C', '0:5 !PCE - 0:2', '0:6 !LOG - D', ]); }); it('should have a valid name', async () => { log.info("A"); const c = log.startProcessingContext({ name: '!MyAction' }); log.info("B"); c.end(); log.info("D"); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', "0:2 !ERR - Processing Context name cannot start with reserved prefix: !MyAction", "0:3 !PCS - MyAction", "0:4 !LOG - B", "0:5 !PCE - 0:3", "0:6 !LOG - D", ]); }); it('should support async contexts', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAsyncAction' }); log.info("B"); log.info("C"); c.pause(); log.info("D"); await log.awaitEvent(traxEvents.CycleComplete); c.resume(); log.info("E"); c.pause(); await log.awaitEvent(traxEvents.CycleComplete); c.resume(); log.info("F"); c.end(); log.info("G"); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAsyncAction', '0:3 !LOG - B', '0:4 !LOG - C', '0:5 !PCP - 0:2', '0:6 !LOG - D', '1:1 !PCR - 0:2', '1:2 !LOG - E', '1:3 !PCP - 0:2', '2:1 !PCR - 0:2', '2:2 !LOG - F', '2:3 !PCE - 0:2', '2:4 !LOG - G', ]); }); it('should support contexts into contexts (sync)', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); const sc = log.startProcessingContext({ name: 'SubAction' }); log.info("C"); sc.end() c.end(); log.info("D"); expect(sc) expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !PCS - SubAction - parentId=0:2', '0:5 !LOG - C', '0:6 !PCE - 0:4', '0:7 !PCE - 0:2', '0:8 !LOG - D', ]); }); it('should support contexts into contexts (sync/async)', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); const sc = log.startProcessingContext({ name: 'SubAction' }); log.info("C"); sc.pause(); log.info("D"); c.end(); log.info("E"); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !PCS - SubAction - parentId=0:2', '0:5 !LOG - C', '0:6 !PCP - 0:4', '0:7 !LOG - D', '0:8 !PCE - 0:2', '0:9 !LOG - E', ]); }); it('should support contexts into contexts (async/async)', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); const sc = log.startProcessingContext({ name: 'SubAction' }); log.info("C"); sc.pause(); log.info("D"); c.pause(); log.info("E"); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !PCS - SubAction - parentId=0:2', '0:5 !LOG - C', '0:6 !PCP - 0:4', '0:7 !LOG - D', '0:8 !PCP - 0:2', '0:9 !LOG - E', ]); }); describe('Console output', () => { afterEach(() => { resetGlobalConsole(); }); it('should support console output', async () => { expect(log.consoleOutput).toBe(""); const logs = mockGlobalConsole(); log.consoleOutput = "All"; expect(log.consoleOutput).toBe("All"); log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.warn("B"); const sc = log.startProcessingContext({ name: 'SubAction' }); log.error("C"); sc.pause(); log.info("D"); c.end(); log.info("E"); log.event("MyEvent", null); expect(log.consoleOutput).toBe("All"); log.consoleOutput = ""; expect(log.consoleOutput).toBe(""); expect(logs).toMatchObject([ "%cTRX %c0:1 %c!LOG%c - %cA", "%cTRX %c0:2 %c!PCS%c - %cMyAction", "%cTRX %c0:3 %c!WRN%c - %cB", "%cTRX %c0:4 %c!PCS%c - %cSubAction - parent:%c0:2", "%cTRX %c0:5 %c!ERR%c - %cC", "%cTRX %c0:6 %c!PCP%c - 0:4", "%cTRX %c0:7 %c!LOG%c - %cD", "%cTRX %c0:8 %c!PCE%c - 0:2", "%cTRX %c0:9 %c!LOG%c - %cE", "%cTRX %c0:10 %cMyEvent%c - null", ]); resetGlobalConsole(); }); }); describe('Errors', () => { it('should be raised if pause() is done after end()', async () => { const c = log.startProcessingContext({ name: 'MyAction' }); log.info("A"); c.end(); log.info("B"); c.pause(); expect(printLogs()).toMatchObject([ '0:1 !PCS - MyAction', '0:2 !LOG - A', '0:3 !PCE - 0:1', '0:4 !LOG - B', '0:5 !ERR - [trax/processing context] Only started or resumed contexts can be paused: 0:1', ]); }); it('should tell when an invalid consoleOutput is set', async () => { const logs = mockGlobalConsole(); log.consoleOutput = "All"; // Valid expect(logs).toMatchObject([]); log.consoleOutput = "XXX" as any; // Invalid expect(logs).toMatchObject([ '%cTRX %cInvalid consoleOutput value - should be either %c"" or "Main" or "AllButGet" or "All"' ]); resetGlobalConsole(); }); it('should be raised if end() is done after end()', async () => { const c = log.startProcessingContext({ name: 'MyAction' }); log.info("A"); c.end(); log.info("B"); c.end(); expect(printLogs()).toMatchObject([ '0:1 !PCS - MyAction', '0:2 !LOG - A', '0:3 !PCE - 0:1', '0:4 !LOG - B', '0:5 !ERR - [trax/processing context] Contexts cannot be ended twice: 0:1', ]); }); it('should be raised if resume() is improperly called', async () => { const c = log.startProcessingContext({ name: 'MyAction' }); log.info("A"); c.resume(); log.info("B"); c.end(); c.resume(); expect(printLogs()).toMatchObject([ '0:1 !PCS - MyAction', '0:2 !LOG - A', '0:3 !ERR - [trax/processing context] Only paused contexts can be resumed: 0:1', '0:4 !LOG - B', '0:5 !PCE - 0:1', '0:6 !ERR - [trax/processing context] Only paused contexts can be resumed: 0:1', ]); }); it('should be raised is end() or pause() is not called at cycle end', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); log.warn("C"); await log.awaitEvent(traxEvents.CycleComplete); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !WRN - C', '0:5 !ERR - [trax/processing context] Contexts must be ended or paused before cycle ends: 0:2', ]); }); it('should be raised is end() or pause() is not called at cycle end (sub-processing context)', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); const cs = log.startProcessingContext({ name: 'SubAction' }); log.warn("C"); await log.awaitEvent(traxEvents.CycleComplete); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !PCS - SubAction - parentId=0:2', '0:5 !WRN - C', '0:6 !ERR - [trax/processing context] Contexts must be ended or paused before cycle ends: 0:4', '0:7 !ERR - [trax/processing context] Contexts must be ended or paused before cycle ends: 0:2', ]); }); it('should be raised if sub-context is not closed before parent context (sync)', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); const cs = log.startProcessingContext({ name: 'SubAction' }); log.warn("C"); c.end(); await log.awaitEvent(traxEvents.CycleComplete); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !PCS - SubAction - parentId=0:2', '0:5 !WRN - C', '0:6 !ERR - [trax/processing context] Contexts must be ended or paused before parent: 0:4', '0:7 !PCE - 0:2' ]); }); it('should be raised if sub-context is not closed before parent context (async)', async () => { log.info("A"); const c = log.startProcessingContext({ name: 'MyAction' }); log.info("B"); const cs = log.startProcessingContext({ name: 'SubAction' }); log.warn("C"); c.pause(); await log.awaitEvent(traxEvents.CycleComplete); expect(printLogs()).toMatchObject([ '0:1 !LOG - A', '0:2 !PCS - MyAction', '0:3 !LOG - B', '0:4 !PCS - SubAction - parentId=0:2', '0:5 !WRN - C', '0:6 !ERR - [trax/processing context] Contexts must be ended or paused before parent: 0:4', '0:7 !PCP - 0:2' ]); }); }); }); });