From 8dc8330431f6058aea8eb7d7b1b3171f718e3c8b Mon Sep 17 00:00:00 2001 From: Lucas Garcia <58547290+LcsGa@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:13:48 +0200 Subject: [PATCH] feat(debug): add extra subscribe, unsubscribe and finalize extra notifs (#107) (#114) --- .../content/docs/utilities/Operators/debug.md | 23 +++- libs/ngxtension/debug/src/debug.spec.ts | 109 ++++++++++++------ libs/ngxtension/debug/src/debug.ts | 33 ++++-- 3 files changed, 119 insertions(+), 46 deletions(-) diff --git a/docs/src/content/docs/utilities/Operators/debug.md b/docs/src/content/docs/utilities/Operators/debug.md index 870d698d..544392e2 100644 --- a/docs/src/content/docs/utilities/Operators/debug.md +++ b/docs/src/content/docs/utilities/Operators/debug.md @@ -27,7 +27,7 @@ const out$ = in$.pipe( debug('after') ); -out$.subscribe(() => {}); +out$.subscribe(); // LOGS: // [before: Next] 1 // [after: Next] 2 @@ -41,3 +41,24 @@ out$.subscribe(() => {}); // [before: Complete] // [after: Complete] ``` + +In addition to `tag` you can also provide extra notifications like `subscribe`, `unsubscribe` and `finalize` by passing an optional object of type + +```ts +type ExtraNotifications = { + subscribe?: boolean; + unsubscribe?: boolean; + finalize?: boolean; +}; +``` + +```ts +const in$ = of('hello world'); +const out$ = in$.pipe(debug('test', { subscribe: true })); + +out$.subscribe(); +// LOGS: +// [test: Subscribed] +// [test: Next] hello world +// [test: Completed] +``` diff --git a/libs/ngxtension/debug/src/debug.spec.ts b/libs/ngxtension/debug/src/debug.spec.ts index 0406697f..20faffd0 100644 --- a/libs/ngxtension/debug/src/debug.spec.ts +++ b/libs/ngxtension/debug/src/debug.spec.ts @@ -1,9 +1,9 @@ -import { EMPTY, from, map } from 'rxjs'; +import { EMPTY, NEVER, from, map, tap } from 'rxjs'; import { debug } from './debug'; let consoleArgs: any[] = []; const mockConsole = - (type: 'L' | 'W' | 'E') => + (type: 'L' | 'W' | 'E' | 'I') => (...args: any[]) => { if ( typeof args[0] === 'string' && @@ -16,14 +16,27 @@ const mockConsole = }; describe(debug.name, () => { - it('given an observable that complete >| should console.warn completed', () => { + const origConsole = { ...console }; + + beforeEach(() => { //MOCKING CONSOLE - const origConsole = { ...console }; consoleArgs = []; console.log = mockConsole('L'); console.warn = mockConsole('W'); console.error = mockConsole('E'); + console.info = mockConsole('I'); + }); + + afterEach(() => { + //RESTORE CONSOLE + console.log = origConsole.log; + console.warn = origConsole.warn; + console.error = origConsole.error; + console.info = origConsole.info; + consoleArgs = []; + }); + it('given an observable that complete >| should console.warn completed', () => { const in$ = EMPTY; const out$ = in$.pipe(debug('test3')); @@ -42,22 +55,9 @@ describe(debug.name, () => { expect(out).toEqual('>|'); expect(consoleArgs.length).toEqual(1); expect(consoleArgs[0]).toEqual(['W', '[test3: Completed]']); - - //RESTORE CONSOLE - console.log = origConsole.log; - console.warn = origConsole.warn; - console.error = origConsole.error; - consoleArgs = []; }); it('given an observable that throw >1-X should console.log emitted values until console.error', () => { - //MOCKING CONSOLE - const origConsole = { ...console }; - consoleArgs = []; - console.log = mockConsole('L'); - console.warn = mockConsole('W'); - console.error = mockConsole('E'); - const in$ = from([1, 2, 3]); const out$ = in$.pipe( map((n) => { @@ -83,22 +83,9 @@ describe(debug.name, () => { expect(consoleArgs.length).toEqual(2); expect(consoleArgs[0]).toEqual(['L', '[test2: Next]', 1]); expect(consoleArgs[1]).toEqual(['E', '[test2: Error]', '[ERR]']); - - //RESTORE CONSOLE - console.log = origConsole.log; - console.warn = origConsole.warn; - console.error = origConsole.error; - consoleArgs = []; }); it('given an observable >1-2-3| should console.log all emitted values + warn completed', () => { - //MOCKING CONSOLE - const origConsole = { ...console }; - consoleArgs = []; - console.log = mockConsole('L'); - console.warn = mockConsole('W'); - console.error = mockConsole('E'); - const in$ = from([1, 2, 3]); const out$ = in$.pipe(debug('test1')); @@ -120,11 +107,63 @@ describe(debug.name, () => { expect(consoleArgs[1]).toEqual(['L', '[test1: Next]', 2]); expect(consoleArgs[2]).toEqual(['L', '[test1: Next]', 3]); expect(consoleArgs[3]).toEqual(['W', '[test1: Completed]']); + }); - //RESTORE CONSOLE - console.log = origConsole.log; - console.warn = origConsole.warn; - console.error = origConsole.error; - consoleArgs = []; + it('given an observable >1-2-3| should console.info subscribe + log all emitted values + warn completed + info finalized', () => { + const in$ = from([1, 2, 3]); + const out$ = in$.pipe(debug('test4', { subscribe: true, finalize: true })); + + let out = '>'; + out$.subscribe({ + next(s) { + out += s + '-'; + }, + error(e) { + out += e + 'X'; + }, + complete() { + out += '|'; + }, + }); + expect(out).toEqual('>1-2-3-|'); + expect(consoleArgs.length).toEqual(6); + expect(consoleArgs[0]).toEqual(['I', '[test4: Subscribed]']); + expect(consoleArgs[1]).toEqual(['L', '[test4: Next]', 1]); + expect(consoleArgs[2]).toEqual(['L', '[test4: Next]', 2]); + expect(consoleArgs[3]).toEqual(['L', '[test4: Next]', 3]); + expect(consoleArgs[4]).toEqual(['W', '[test4: Completed]']); + expect(consoleArgs[5]).toEqual(['I', '[test4: Finalized]']); + }); + + it('given an observable that unsubscribe >! should console.info unsubscribe + finalize', () => { + const in$ = NEVER; + const out$ = in$.pipe( + debug('test5', { unsubscribe: true, finalize: true }) + ); + + let out = '>'; + out$ + .pipe( + tap({ + next(s) { + out += s + '-'; + }, + error(e) { + out += e + 'X'; + }, + complete() { + out += '|'; + }, + unsubscribe() { + out += '!'; + }, + }) + ) + .subscribe() + .unsubscribe(); + expect(out).toEqual('>!'); + expect(consoleArgs.length).toEqual(2); + expect(consoleArgs[0]).toEqual(['I', '[test5: Unsubscribed]']); + expect(consoleArgs[1]).toEqual(['I', '[test5: Finalized]']); }); }); diff --git a/libs/ngxtension/debug/src/debug.ts b/libs/ngxtension/debug/src/debug.ts index 91ca395c..43f5d3d6 100644 --- a/libs/ngxtension/debug/src/debug.ts +++ b/libs/ngxtension/debug/src/debug.ts @@ -1,16 +1,29 @@ import { tap } from 'rxjs/operators'; +export type ExtraNotifications = { + subscribe?: boolean; + unsubscribe?: boolean; + finalize?: boolean; +}; + //INSPIRED BY @netbasal ARTICLE https://netbasal.com/creating-custom-operators-in-rxjs-32f052d69457 -export function debug(tag: string) { +export function debug(tag: string, extraNotifications?: ExtraNotifications) { + const formatNotif = (notif: string, data?: unknown) => [ + new Date().toISOString(), + `[${tag}: ${notif}]`, + data, + ]; return tap({ - next(value) { - console.log(new Date().toISOString(), `[${tag}: Next]`, value); - }, - error(err) { - console.error(new Date().toISOString(), `[${tag}: Error]`, err); - }, - complete() { - console.warn(new Date().toISOString(), `[${tag}: Completed]`); - }, + next: (value) => console.log(...formatNotif('Next', value)), + error: (err) => console.error(...formatNotif('Error', err)), + complete: () => console.warn(...formatNotif('Completed')), + subscribe: () => + extraNotifications?.subscribe && + console.info(...formatNotif('Subscribed')), + unsubscribe: () => + extraNotifications?.unsubscribe && + console.info(...formatNotif(`Unsubscribed`)), + finalize: () => + extraNotifications?.finalize && console.info(...formatNotif(`Finalized`)), }); }