All files / src/app/core/utils/dev operators.ts

24.13% Statements 7/29
18.51% Branches 5/27
25% Functions 3/12
25% Lines 7/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104  1x   1x                                                                                     1x 5x 5x     10x         1x                                                                                              
import { OperatorFunction } from 'rxjs';
import { delay, map, pairwise, startWith, tap } from 'rxjs/operators';
 
export function randomDelay<T>(min = 1000, max = 10000): OperatorFunction<T, T> {
  return source$ => source$.pipe(delay(Math.floor(Math.random() * (max - min + 1)) + min));
}
 
/**
 * Operator logging the current stream content to the console.
 *
 * If used without argument just prints the content:
 *
 * ```ts
 * from(1, 'foo' ,false).pipe(log()).subscribe(...)
 * // 1
 * // 'foo'
 * // true
 * ```
 * If used with a message will prefix the message:
 *
 * ```ts
 * from(1, 'foo' ,false).pipe(log('message')).subscribe(...)
 * // "message" 1
 * // "message" "foo"
 * // "message" true
 * ```
 *
 * The message argument can be anything, also a function.
 * The function will be evaluated when the stream element is emitted.
 * This can be useful when the message value is not constant during the stream lifetime.
 *
 * ```ts
 * from(1, 'foo').pipe(log(() => this.value)).subscribe(...)
 * ```
 *
 * As a second argument the `stringify` function can be activated (default false).
 * With this the stream element is passed through `JSON.stringify`.
 * Of course the element must be serializable for this, otherwise an Error is thrown.
 *
 * ```ts
 * from(1, 'foo' , { element: { test: true } }).pipe(log('message', true)).subscribe(...)
 * // "message" "1"
 * // "message" "foo"
 * // "message" "{ "element": { "test": true } }"
 * ```
 */
export function log<T>(message?: unknown, stringify?: boolean): OperatorFunction<T, T> {
  return source$ =>
    source$.pipe(
      tap(e =>
        // eslint-disable-next-line no-console
        console.log(typeof message === 'function' ? message() : message || '', stringify ? JSON.stringify(e) : e)
      )
    );
}
 
export function logDiff<T>(message?: unknown): OperatorFunction<T, T> {
  return source$ =>
    source$.pipe(
      map(el => el || ({} as T)),
      startWith({} as T),
      pairwise(),
      // eslint-disable-next-line no-console
      tap(([a, b]) => console.log(message || '', keyChanges(a, b), { emit: b })),
      map(([, b]) => b)
    );
}
 
// https://gist.github.com/Yimiprod/7ee176597fef230d1451
/* eslint-disable @typescript-eslint/no-explicit-any -- utility function */
function keyChanges(a: any, b: any) {
  const changes: any = {};
 
  // eslint-disable-next-line complexity
  function walkObject(base: any, object: any, path = '') {
    for (const key of Object.keys(base)) {
      const currentPath = path === '' ? key : `${path}.${key}`;
 
      Iif (object[key] === undefined) {
        changes[currentPath] = '-';
      }
    }
 
    for (const [key, value] of Object.entries(object)) {
      const currentPath = Array.isArray(object) ? `${path}[${key}]` : path === '' ? key : `${path}.${key}`;
 
      if (base[key] === undefined) {
        changes[currentPath] = '+';
      } else Iif (value !== base[key]) {
        if (typeof value === 'object' && typeof base[key] === 'object') {
          walkObject(base[key], value, currentPath);
        } else {
          changes[currentPath] = object[key];
        }
      }
    }
  }
 
  walkObject(a, b);
 
  return changes;
}
/* eslint-enable @typescript-eslint/no-explicit-any */