Commit 52f0f960 authored by Guillaume Perréal's avatar Guillaume Perréal
Browse files

Coding style.

No related merge requests found
Showing with 284 additions and 96 deletions
+284 -96
...@@ -29,7 +29,10 @@ export interface FinalizeDebugEvent { ...@@ -29,7 +29,10 @@ export interface FinalizeDebugEvent {
type: 'finalize'; type: 'finalize';
} }
export type DebugEvent = RequestDebugEvent | ResponseDebugEvent | FinalizeDebugEvent; export type DebugEvent =
| RequestDebugEvent
| ResponseDebugEvent
| FinalizeDebugEvent;
export class ApiDebugInterceptor implements HttpInterceptor { export class ApiDebugInterceptor implements HttpInterceptor {
private readonly sink$ = new ReplaySubject<DebugEvent>(10); private readonly sink$ = new ReplaySubject<DebugEvent>(10);
...@@ -40,7 +43,10 @@ export class ApiDebugInterceptor implements HttpInterceptor { ...@@ -40,7 +43,10 @@ export class ApiDebugInterceptor implements HttpInterceptor {
this.enabled = conf.debug; this.enabled = conf.debug;
} }
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { public intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (!this.enabled) { if (!this.enabled) {
return next.handle(request); return next.handle(request);
} }
......
...@@ -20,7 +20,10 @@ export class DumpPanelComponent { ...@@ -20,7 +20,10 @@ export class DumpPanelComponent {
public spyCount = 0; public spyCount = 0;
public apiCallCount = 0; public apiCallCount = 0;
public constructor(public readonly debugState: DebugStateService, private readonly cd: ChangeDetectorRef) {} public constructor(
public readonly debugState: DebugStateService,
private readonly cd: ChangeDetectorRef
) {}
public setWatchCount(count: number): void { public setWatchCount(count: number): void {
this.watchCount = count; this.watchCount = count;
......
...@@ -36,7 +36,11 @@ export class DumpValueComponent { ...@@ -36,7 +36,11 @@ export class DumpValueComponent {
return this.expanded.has(path.join('§')); return this.expanded.has(path.join('§'));
} }
private createNode(value: any, data: PropertyPath = [], refs = new Map<any, number>()): TreeNode { private createNode(
value: any,
data: PropertyPath = [],
refs = new Map<any, number>()
): TreeNode {
if (value === null) { if (value === null) {
return { label: 'null', data, leaf: true, styleClass: 'null' }; return { label: 'null', data, leaf: true, styleClass: 'null' };
} }
...@@ -51,17 +55,32 @@ export class DumpValueComponent { ...@@ -51,17 +55,32 @@ export class DumpValueComponent {
} }
if (typeof value === 'string') { if (typeof value === 'string') {
return { label: JSON.stringify(value), data, leaf: true, styleClass: 'string' }; return {
label: JSON.stringify(value),
data,
leaf: true,
styleClass: 'string',
};
} }
if (typeof value === 'number') { if (typeof value === 'number') {
return { label: value.toString(), data, leaf: true, styleClass: 'number' }; return {
label: value.toString(),
data,
leaf: true,
styleClass: 'number',
};
} }
const clazz = value.constructor.name; const clazz = value.constructor.name;
let ref = refs.get(value); let ref = refs.get(value);
if (ref) { if (ref) {
return { label: `${clazz} @${ref}`, data, leaf: true, styleClass: 'reference' }; return {
label: `${clazz} @${ref}`,
data,
leaf: true,
styleClass: 'reference',
};
} }
ref = refs.size; ref = refs.size;
...@@ -75,7 +94,9 @@ export class DumpValueComponent { ...@@ -75,7 +94,9 @@ export class DumpValueComponent {
leaf: entries.length === 0, leaf: entries.length === 0,
children: entries children: entries
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([key, item]) => this.createNode(item, [].concat(data, [key]), refs)), .map(([key, item]) =>
this.createNode(item, [].concat(data, [key]), refs)
),
styleClass: 'object', styleClass: 'object',
expanded: this.isExpanded(data), expanded: this.isExpanded(data),
}; };
......
...@@ -2,30 +2,37 @@ ...@@ -2,30 +2,37 @@
* Fonctions pour "patcher" les méthodes d'une classe. * Fonctions pour "patcher" les méthodes d'une classe.
*/ */
type Hookable<K extends string | symbol, M extends (...args: any[]) => any> = { [X in K]: M }; type Hookable<K extends string | symbol, M extends (...args: any[]) => any> = {
[X in K]: M
};
function isHookable<K extends string | symbol, M extends (...args: any[]) => any>( function isHookable<
what: unknown, K extends string | symbol,
key: K M extends (...args: any[]) => any
): what is Hookable<K, M> { >(what: unknown, key: K): what is Hookable<K, M> {
return typeof what === 'object' && what !== null && key in what && typeof (what as any)[key] === 'function'; return (
typeof what === 'object' &&
what !== null &&
key in what &&
typeof (what as any)[key] === 'function'
);
} }
/** /**
* Installe un hook. * Installe un hook.
*/ */
function installHook<T extends Hookable<K, M>, M extends (this: T, ...args: any[]) => void, K extends string | symbol>( function installHook<
target: T, T extends Hookable<K, M>,
name: K, M extends (this: T, ...args: any[]) => void,
makeHook: (old: M) => M K extends string | symbol
): void { >(target: T, name: K, makeHook: (old: M) => M): void {
if (!isHookable<K, M>(target, name)) { if (!isHookable<K, M>(target, name)) {
throw new Error(`cannot hook ${name} on ${target}`); throw new Error(`cannot hook ${name} on ${target}`);
} }
if (name === 'constructor') { if (name === 'constructor') {
throw new Error('cannot hook constructors'); throw new Error('cannot hook constructors');
} }
target[name] = <T[K]> makeHook(target[name]); target[name] = <T[K]>makeHook(target[name]);
} }
/** /**
...@@ -39,7 +46,7 @@ export function hookBefore< ...@@ -39,7 +46,7 @@ export function hookBefore<
T extends Hookable<K, M>, T extends Hookable<K, M>,
M extends (this: T, ...args: any[]) => void, M extends (this: T, ...args: any[]) => void,
K extends string | symbol K extends string | symbol
>(target: Hookable<K, M>, name: K, hook: M): void { >(target: Hookable<K, M>, name: K, hook: M): void {
installHook( installHook(
target, target,
name, name,
...@@ -62,7 +69,7 @@ export function hookAfter< ...@@ -62,7 +69,7 @@ export function hookAfter<
T extends Hookable<K, M>, T extends Hookable<K, M>,
M extends (this: T, ...args: any[]) => void, M extends (this: T, ...args: any[]) => void,
K extends string | symbol K extends string | symbol
>(target: T, name: K, hook: M): void { >(target: T, name: K, hook: M): void {
installHook( installHook(
target, target,
name, name,
...@@ -85,7 +92,7 @@ export function hookFinally< ...@@ -85,7 +92,7 @@ export function hookFinally<
T extends Hookable<K, M>, T extends Hookable<K, M>,
M extends (this: T, ...args: any[]) => void, M extends (this: T, ...args: any[]) => void,
K extends string | symbol K extends string | symbol
>(target: T, name: K, hook: M): void { >(target: T, name: K, hook: M): void {
installHook( installHook(
target, target,
name, name,
......
...@@ -4,7 +4,9 @@ import { defer, Observable, ObservedValueOf } from 'rxjs'; ...@@ -4,7 +4,9 @@ import { defer, Observable, ObservedValueOf } from 'rxjs';
* Un observable qui attend le premier abonnement pour fabriquer l'observable qui va vraiment être * Un observable qui attend le premier abonnement pour fabriquer l'observable qui va vraiment être
* utilisé. * utilisé.
*/ */
export function lazy<O extends Observable<any>>(create: () => O): Observable<ObservedValueOf<O>> { export function lazy<O extends Observable<any>>(
create: () => O
): Observable<ObservedValueOf<O>> {
let obs: O | null = null; let obs: O | null = null;
return defer(() => (obs !== null ? obs : (obs = create()))); return defer(() => (obs !== null ? obs : (obs = create())));
} }
...@@ -6,9 +6,15 @@ import { marbles } from 'rxjs-marbles'; ...@@ -6,9 +6,15 @@ import { marbles } from 'rxjs-marbles';
import { QueryParam, RouteParam } from './route-param.decorator'; import { QueryParam, RouteParam } from './route-param.decorator';
function testOne(decorator: any, mapProperty: string) { function testOne(decorator: any, mapProperty: string) {
function mockActivatedRoute(map$: Observable<ParamMap>, snapshot: any): ActivatedRoute { function mockActivatedRoute(
map$: Observable<ParamMap>,
snapshot: any
): ActivatedRoute {
// tslint:disable-next-line:rxjs-finnish // tslint:disable-next-line:rxjs-finnish
return { [mapProperty]: map$, snapshot: { [mapProperty]: snapshot } } as any; return {
[mapProperty]: map$,
snapshot: { [mapProperty]: snapshot },
} as any;
} }
class FakeComponent implements OnInit, OnDestroy { class FakeComponent implements OnInit, OnDestroy {
...@@ -31,7 +37,9 @@ function testOne(decorator: any, mapProperty: string) { ...@@ -31,7 +37,9 @@ function testOne(decorator: any, mapProperty: string) {
'should subscribe to paramMap on init', 'should subscribe to paramMap on init',
marbles(m => { marbles(m => {
const map$ = m.cold(''); const map$ = m.cold('');
const comp = new FakeComponent(mockActivatedRoute(map$, convertToParamMap({}))); const comp = new FakeComponent(
mockActivatedRoute(map$, convertToParamMap({}))
);
comp.ngOnInit(); comp.ngOnInit();
...@@ -46,7 +54,9 @@ function testOne(decorator: any, mapProperty: string) { ...@@ -46,7 +54,9 @@ function testOne(decorator: any, mapProperty: string) {
'should unsubscribe from paramMap on destroy', 'should unsubscribe from paramMap on destroy',
marbles(m => { marbles(m => {
const map$ = m.cold(''); const map$ = m.cold('');
const comp = new FakeComponent(mockActivatedRoute(map$, convertToParamMap({}))); const comp = new FakeComponent(
mockActivatedRoute(map$, convertToParamMap({}))
);
comp.ngOnInit(); comp.ngOnInit();
comp.ngOnDestroy(); comp.ngOnDestroy();
...@@ -69,7 +79,10 @@ function testOne(decorator: any, mapProperty: string) { ...@@ -69,7 +79,10 @@ function testOne(decorator: any, mapProperty: string) {
const map$ = m.hot('--a--b-^-c--|', VALUES); const map$ = m.hot('--a--b-^-c--|', VALUES);
const comp = new FakeComponent(mockActivatedRoute(map$, VALUES.b)); const comp = new FakeComponent(mockActivatedRoute(map$, VALUES.b));
m.expect(comp.getParam$()).toBeObservable('b-c--|', { b: 'bar', c: 'quz' }); m.expect(comp.getParam$()).toBeObservable('b-c--|', {
b: 'bar',
c: 'quz',
});
comp.ngOnInit(); comp.ngOnInit();
......
...@@ -22,9 +22,15 @@ function ParamFromMap<K extends 'paramMap' | 'queryParamMap'>( ...@@ -22,9 +22,15 @@ function ParamFromMap<K extends 'paramMap' | 'queryParamMap'>(
const observer: Observer<string> = (this as any)[property]; const observer: Observer<string> = (this as any)[property];
if (!route || !(mapName in route)) { if (!route || !(mapName in route)) {
throw new Error(`this.${routeProperty.toString()} must contains an ActivatedRoute`); throw new Error(
`this.${routeProperty.toString()} must contains an ActivatedRoute`
);
} }
if (observer === null || !('next' in observer) || typeof observer.next !== 'function') { if (
observer === null ||
!('next' in observer) ||
typeof observer.next !== 'function'
) {
throw new Error(`this.${property.toString()} must implement Observer`); throw new Error(`this.${property.toString()} must implement Observer`);
} }
...@@ -33,7 +39,9 @@ function ParamFromMap<K extends 'paramMap' | 'queryParamMap'>( ...@@ -33,7 +39,9 @@ function ParamFromMap<K extends 'paramMap' | 'queryParamMap'>(
observer.next(snapshot.get(resolvedParam)); observer.next(snapshot.get(resolvedParam));
} }
(this as any)[subscription] = route[mapName].pipe(map(p => p.get(resolvedParam))).subscribe(observer); (this as any)[subscription] = route[mapName]
.pipe(map(p => p.get(resolvedParam)))
.subscribe(observer);
}); });
hookFinally(prototype, 'ngOnDestroy', function() { hookFinally(prototype, 'ngOnDestroy', function() {
...@@ -50,7 +58,10 @@ function ParamFromMap<K extends 'paramMap' | 'queryParamMap'>( ...@@ -50,7 +58,10 @@ function ParamFromMap<K extends 'paramMap' | 'queryParamMap'>(
* @param {string|null} param Nom du paramètre. Par défaut égal au nom de la propriété minus le '$' final. * @param {string|null} param Nom du paramètre. Par défaut égal au nom de la propriété minus le '$' final.
* @param {string} routeProperty nom de la propriété du Component contenant l'ActivatedRoute. Par défaut 'route'. * @param {string} routeProperty nom de la propriété du Component contenant l'ActivatedRoute. Par défaut 'route'.
*/ */
export function RouteParam(param: string = null, routeProperty: string = 'route') { export function RouteParam(
param: string = null,
routeProperty: string = 'route'
) {
return ParamFromMap(param, routeProperty, 'paramMap'); return ParamFromMap(param, routeProperty, 'paramMap');
} }
...@@ -62,6 +73,9 @@ export function RouteParam(param: string = null, routeProperty: string = 'route' ...@@ -62,6 +73,9 @@ export function RouteParam(param: string = null, routeProperty: string = 'route'
* @param {string|null} param Nom du paramètre. Par défaut égal au nom de la propriété minus le '$' final. * @param {string|null} param Nom du paramètre. Par défaut égal au nom de la propriété minus le '$' final.
* @param {string} routeProperty nom de la propriété du Component contenant l'ActivatedRoute. Par défaut 'route'. * @param {string} routeProperty nom de la propriété du Component contenant l'ActivatedRoute. Par défaut 'route'.
*/ */
export function QueryParam(param: string = null, routeProperty: string = 'route') { export function QueryParam(
param: string = null,
routeProperty: string = 'route'
) {
return ParamFromMap(param, routeProperty, 'queryParamMap'); return ParamFromMap(param, routeProperty, 'queryParamMap');
} }
...@@ -4,12 +4,16 @@ import { combineLatest, Observable, ObservableInput } from 'rxjs'; ...@@ -4,12 +4,16 @@ import { combineLatest, Observable, ObservableInput } from 'rxjs';
* Observable qui envoie un tableau vide et ne complète pas. * Observable qui envoie un tableau vide et ne complète pas.
*/ */
// tslint:disable-next-line:rxjs-finnish // tslint:disable-next-line:rxjs-finnish
const EMPTY_ARRAY_OBSERVABLE: Observable<any[]> = new Observable(subscriber => subscriber.next([])); const EMPTY_ARRAY_OBSERVABLE: Observable<any[]> = new Observable(subscriber =>
subscriber.next([])
);
/** /**
* Variante de combineLatest qui transmets un tableau vide si le tableau des entrées est vide. * Variante de combineLatest qui transmets un tableau vide si le tableau des entrées est vide.
*/ */
export function safeCombineLatest<T>(inputs$: Array<ObservableInput<T>>): Observable<T[]> { export function safeCombineLatest<T>(
inputs$: Array<ObservableInput<T>>
): Observable<T[]> {
if (inputs$.length === 0) { if (inputs$.length === 0) {
return EMPTY_ARRAY_OBSERVABLE; return EMPTY_ARRAY_OBSERVABLE;
} }
......
...@@ -6,7 +6,9 @@ import { take } from 'rxjs/operators'; ...@@ -6,7 +6,9 @@ import { take } from 'rxjs/operators';
* - renvoie un tableau vide si le tableau d'entrée est vide. * - renvoie un tableau vide si le tableau d'entrée est vide.
* - retourne uniquement la première valeure de chaque entrée. * - retourne uniquement la première valeure de chaque entrée.
*/ */
export function safeForkJoin<T>(inputs$: Array<ObservableInput<T>>): Observable<T[]> { export function safeForkJoin<T>(
inputs$: Array<ObservableInput<T>>
): Observable<T[]> {
if (inputs$.length === 0) { if (inputs$.length === 0) {
return of([]); return of([]);
} }
......
...@@ -39,7 +39,10 @@ export function select<T, R, V extends any[]>( ...@@ -39,7 +39,10 @@ export function select<T, R, V extends any[]>(
} }
const values = selectors.map(s => s(input)) as V; const values = selectors.map(s => s(input)) as V;
if (state.status === 'OPEN' && state.values.every((v, i) => isSame(v, values[i]))) { if (
state.status === 'OPEN' &&
state.values.every((v, i) => isSame(v, values[i]))
) {
subscriber.next(state.output); subscriber.next(state.output);
return; return;
} }
......
import { MonoTypeOperatorFunction, Observable, ReplaySubject } from 'rxjs'; import { MonoTypeOperatorFunction, Observable, ReplaySubject } from 'rxjs';
export type NotificationType = 'next' | 'error' | 'complete' | 'subscribed' | 'unsubscribed'; export type NotificationType =
| 'next'
| 'error'
| 'complete'
| 'subscribed'
| 'unsubscribed';
export interface Notification { export interface Notification {
tag: string; tag: string;
...@@ -15,7 +20,12 @@ const timeOrigin = Date.now(); ...@@ -15,7 +20,12 @@ const timeOrigin = Date.now();
export function spy<T>(tag = 'spy'): MonoTypeOperatorFunction<T> { export function spy<T>(tag = 'spy'): MonoTypeOperatorFunction<T> {
function log(type: NotificationType, payload?: any) { function log(type: NotificationType, payload?: any) {
notifications$.next({ tag, type, payload, timestamp: Date.now() - timeOrigin }); notifications$.next({
tag,
type,
payload,
timestamp: Date.now() - timeOrigin,
});
} }
return (source$: Observable<T>): Observable<T> => return (source$: Observable<T>): Observable<T> =>
......
...@@ -7,14 +7,18 @@ ...@@ -7,14 +7,18 @@
*/ */
export function SubjectAccessors() { export function SubjectAccessors() {
return (prototype: object, observablePropertyName: string | symbol): void => { return (prototype: object, observablePropertyName: string | symbol): void => {
Object.defineProperty(prototype, getPlainPropertyName(observablePropertyName), { Object.defineProperty(
get(): any { prototype,
return this[observablePropertyName].getValue(); getPlainPropertyName(observablePropertyName),
}, {
set(value: any): void { get(): any {
this[observablePropertyName].next(value); return this[observablePropertyName].getValue();
}, },
}); set(value: any): void {
this[observablePropertyName].next(value);
},
}
);
}; };
} }
...@@ -25,11 +29,15 @@ export function SubjectAccessors() { ...@@ -25,11 +29,15 @@ export function SubjectAccessors() {
*/ */
export function SubjectSetter() { export function SubjectSetter() {
return (prototype: object, observablePropertyName: string | symbol): void => { return (prototype: object, observablePropertyName: string | symbol): void => {
Object.defineProperty(prototype, getPlainPropertyName(observablePropertyName), { Object.defineProperty(
set(value: any): void { prototype,
this[observablePropertyName].next(value); getPlainPropertyName(observablePropertyName),
}, {
}); set(value: any): void {
this[observablePropertyName].next(value);
},
}
);
}; };
} }
......
...@@ -8,7 +8,8 @@ describe('@SubscribeOnInit', () => { ...@@ -8,7 +8,8 @@ describe('@SubscribeOnInit', () => {
const VALUES = { a: 'a' }; const VALUES = { a: 'a' };
let scheduler: MarbleTestScheduler<string>; let scheduler: MarbleTestScheduler<string>;
class PropertyTestClass<T extends Observable<any>> implements OnInit, OnDestroy { class PropertyTestClass<T extends Observable<any>>
implements OnInit, OnDestroy {
@SubscribeOnInit() @SubscribeOnInit()
public readonly obs$: T; public readonly obs$: T;
...@@ -51,7 +52,10 @@ describe('@SubscribeOnInit', () => { ...@@ -51,7 +52,10 @@ describe('@SubscribeOnInit', () => {
obj1.ngOnInit(); obj1.ngOnInit();
obj2.ngOnInit(); obj2.ngOnInit();
scheduler.schedule(() => obj1.ngOnDestroy(), scheduler.createTime('--|')); scheduler.schedule(() => obj1.ngOnDestroy(), scheduler.createTime('--|'));
scheduler.schedule(() => obj2.ngOnDestroy(), scheduler.createTime('----|')); scheduler.schedule(
() => obj2.ngOnDestroy(),
scheduler.createTime('----|')
);
expectSubscriptions(obj1.obs$.subscriptions).toBe('^--!'); expectSubscriptions(obj1.obs$.subscriptions).toBe('^--!');
expectSubscriptions(obj2.obs$.subscriptions).toBe('^----!'); expectSubscriptions(obj2.obs$.subscriptions).toBe('^----!');
......
...@@ -9,8 +9,10 @@ interface AutoSubscribable extends OnInit, OnDestroy {} ...@@ -9,8 +9,10 @@ interface AutoSubscribable extends OnInit, OnDestroy {}
type ObjectKey = string | number | symbol; type ObjectKey = string | number | symbol;
type AutoSubscriber<Name extends ObjectKey> = AutoSubscribable & { [SUBSCRIPTION_KEY]: Subscription } & { type AutoSubscriber<Name extends ObjectKey> = AutoSubscribable & {
[K in Name]?: Observable<any> [SUBSCRIPTION_KEY]: Subscription;
} & {
[K in Name]?: Observable<any>;
}; };
/** /**
...@@ -19,12 +21,17 @@ type AutoSubscriber<Name extends ObjectKey> = AutoSubscribable & { [SUBSCRIPTION ...@@ -19,12 +21,17 @@ type AutoSubscriber<Name extends ObjectKey> = AutoSubscribable & { [SUBSCRIPTION
* est géré dans ngOnDestroy(). * est géré dans ngOnDestroy().
*/ */
export function SubscribeOnInit() { export function SubscribeOnInit() {
return <Target extends AutoSubscribable, Name extends ObjectKey>(target: Target, name: Name): any => { return <Target extends AutoSubscribable, Name extends ObjectKey>(
target: Target,
name: Name
): any => {
const prototype = (target as any) as AutoSubscriber<Name>; const prototype = (target as any) as AutoSubscriber<Name>;
hookBefore(prototype, 'ngOnInit', function() { hookBefore(prototype, 'ngOnInit', function() {
if (!(this as any)[SUBSCRIPTION_KEY]) { if (!(this as any)[SUBSCRIPTION_KEY]) {
(this as any)[SUBSCRIPTION_KEY] = new Subscription(() => delete (this as any)[SUBSCRIPTION_KEY]); (this as any)[SUBSCRIPTION_KEY] = new Subscription(
() => delete (this as any)[SUBSCRIPTION_KEY]
);
} }
if ((this as any)[name]) { if ((this as any)[name]) {
(this as any)[SUBSCRIPTION_KEY].add((this as any)[name].subscribe()); (this as any)[SUBSCRIPTION_KEY].add((this as any)[name].subscribe());
......
...@@ -22,7 +22,10 @@ describe('MarbleFormatter', () => { ...@@ -22,7 +22,10 @@ describe('MarbleFormatter', () => {
}); });
describe('.formatMarbles()', () => { describe('.formatMarbles()', () => {
function mkNotif<T = any>(frame: number, notification: Notification<T>): Event<T> { function mkNotif<T = any>(
frame: number,
notification: Notification<T>
): Event<T> {
return { frame, notification }; return { frame, notification };
} }
...@@ -31,39 +34,54 @@ describe('MarbleFormatter', () => { ...@@ -31,39 +34,54 @@ describe('MarbleFormatter', () => {
}); });
it('should represented known value with its key', () => { it('should represented known value with its key', () => {
expect(formatter.formatMarbles([mkNotif(0, Notification.createNext({ x: 'A' }))])).toEqual('a'); expect(
formatter.formatMarbles([
mkNotif(0, Notification.createNext({ x: 'A' })),
])
).toEqual('a');
}); });
it('should represent values as "o" when no values are specified', () => { it('should represent values as "o" when no values are specified', () => {
expect( expect(
formatter formatter
.withValues() .withValues()
.formatMarbles([mkNotif(0, Notification.createNext('foo')), mkNotif(10, Notification.createNext('bar'))]) .formatMarbles([
mkNotif(0, Notification.createNext('foo')),
mkNotif(10, Notification.createNext('bar')),
])
).toEqual('oo'); ).toEqual('oo');
}); });
it('should throw on unexpected value', () => { it('should throw on unexpected value', () => {
expect(() => formatter.formatMarbles([mkNotif(10, Notification.createNext('foo'))])).toThrow( expect(() =>
new Error('Unexpected value at 10 ms: "foo"') formatter.formatMarbles([mkNotif(10, Notification.createNext('foo'))])
); ).toThrow(new Error('Unexpected value at 10 ms: "foo"'));
}); });
it('should represent complete as "|"', () => { it('should represent complete as "|"', () => {
expect(formatter.formatMarbles([mkNotif(0, Notification.createComplete())])).toEqual('|'); expect(
formatter.formatMarbles([mkNotif(0, Notification.createComplete())])
).toEqual('|');
}); });
it('should represent errors as "#" when no error is specified', () => { it('should represent errors as "#" when no error is specified', () => {
expect(formatter.withError().formatMarbles([mkNotif(0, Notification.createError('foo'))])).toEqual('#'); expect(
formatter
.withError()
.formatMarbles([mkNotif(0, Notification.createError('foo'))])
).toEqual('#');
}); });
it('should represent expected error as "#"', () => { it('should represent expected error as "#"', () => {
expect(formatter.formatMarbles([mkNotif(0, Notification.createError('error'))])).toEqual('#'); expect(
formatter.formatMarbles([mkNotif(0, Notification.createError('error'))])
).toEqual('#');
}); });
it('should throw on unexpected error', () => { it('should throw on unexpected error', () => {
expect(() => formatter.formatMarbles([mkNotif(10, Notification.createError('foo'))])).toThrow( expect(() =>
new Error('Unexpected error at 10 ms: "foo"') formatter.formatMarbles([mkNotif(10, Notification.createError('foo'))])
); ).toThrow(new Error('Unexpected error at 10 ms: "foo"'));
}); });
it('should represent empty time frames as "-"', () => { it('should represent empty time frames as "-"', () => {
...@@ -104,7 +122,10 @@ describe('MarbleFormatter', () => { ...@@ -104,7 +122,10 @@ describe('MarbleFormatter', () => {
'-a--#', '-a--#',
'--(a|)', '--(a|)',
].forEach(marbles => { ].forEach(marbles => {
TEST_CASES.push([marbles, TestScheduler.parseMarbles(marbles, VALUES, ERROR, false, true)]); TEST_CASES.push([
marbles,
TestScheduler.parseMarbles(marbles, VALUES, ERROR, false, true),
]);
}); });
for (const [marbles, messages] of TEST_CASES) { for (const [marbles, messages] of TEST_CASES) {
...@@ -118,19 +139,29 @@ describe('MarbleFormatter', () => { ...@@ -118,19 +139,29 @@ describe('MarbleFormatter', () => {
describe('formatSubscriptions', () => { describe('formatSubscriptions', () => {
it('should format empty subscriptions', () => { it('should format empty subscriptions', () => {
expect(formatter.formatSubscriptions(new SubscriptionLog(Number.POSITIVE_INFINITY))).toEqual(''); expect(
formatter.formatSubscriptions(
new SubscriptionLog(Number.POSITIVE_INFINITY)
)
).toEqual('');
}); });
it('should format closed subscription', () => { it('should format closed subscription', () => {
expect(formatter.formatSubscriptions(new SubscriptionLog(10, 30))).toEqual(' ^-!'); expect(
formatter.formatSubscriptions(new SubscriptionLog(10, 30))
).toEqual(' ^-!');
}); });
it('should format open subscription', () => { it('should format open subscription', () => {
expect(formatter.formatSubscriptions(new SubscriptionLog(10))).toEqual(' ^'); expect(formatter.formatSubscriptions(new SubscriptionLog(10))).toEqual(
' ^'
);
}); });
it('should format zero-duration subscription', () => { it('should format zero-duration subscription', () => {
expect(formatter.formatSubscriptions(new SubscriptionLog(10, 10))).toEqual(' (^!)'); expect(
formatter.formatSubscriptions(new SubscriptionLog(10, 10))
).toEqual(' (^!)');
}); });
}); });
}); });
...@@ -7,7 +7,10 @@ import { Notification, Observable } from 'rxjs'; ...@@ -7,7 +7,10 @@ import { Notification, Observable } from 'rxjs';
import { ColdObservable } from 'rxjs/internal/testing/ColdObservable'; import { ColdObservable } from 'rxjs/internal/testing/ColdObservable';
import { HotObservable } from 'rxjs/internal/testing/HotObservable'; import { HotObservable } from 'rxjs/internal/testing/HotObservable';
import { SubscriptionLog } from 'rxjs/internal/testing/SubscriptionLog'; import { SubscriptionLog } from 'rxjs/internal/testing/SubscriptionLog';
import { observableToBeFn, RunHelpers } from 'rxjs/internal/testing/TestScheduler'; import {
observableToBeFn,
RunHelpers,
} from 'rxjs/internal/testing/TestScheduler';
import { TestScheduler } from 'rxjs/testing'; import { TestScheduler } from 'rxjs/testing';
export interface Event<T> { export interface Event<T> {
...@@ -52,16 +55,22 @@ export class MarbleFormatter<T = any> { ...@@ -52,16 +55,22 @@ export class MarbleFormatter<T = any> {
event.notification.do( event.notification.do(
value => { value => {
const key = this.values ? _.findKey(this.values, v => _.isEqual(value, v)) : 'o'; const key = this.values
? _.findKey(this.values, v => _.isEqual(value, v))
: 'o';
if (!key) { if (!key) {
throw new Error(`Unexpected value at ${event.frame} ms: ${JSON.stringify(value)}`); throw new Error(
`Unexpected value at ${event.frame} ms: ${JSON.stringify(value)}`
);
} }
group += key; group += key;
}, },
error => { error => {
if (this.error !== undefined && !_.isEqual(error, this.error)) { if (this.error !== undefined && !_.isEqual(error, this.error)) {
const message = MarbleFormatter.errorMessage(error); const message = MarbleFormatter.errorMessage(error);
throw new Error(`Unexpected error at ${event.frame} ms: ${message}`); throw new Error(
`Unexpected error at ${event.frame} ms: ${message}`
);
} }
group += '#'; group += '#';
}, },
...@@ -81,7 +90,10 @@ export class MarbleFormatter<T = any> { ...@@ -81,7 +90,10 @@ export class MarbleFormatter<T = any> {
return marbles; return marbles;
} }
public formatSubscriptions({ subscribedFrame, unsubscribedFrame }: SubscriptionLog): string { public formatSubscriptions({
subscribedFrame,
unsubscribedFrame,
}: SubscriptionLog): string {
if (subscribedFrame === Number.POSITIVE_INFINITY) { if (subscribedFrame === Number.POSITIVE_INFINITY) {
return ''; return '';
} }
...@@ -94,7 +106,9 @@ export class MarbleFormatter<T = any> { ...@@ -94,7 +106,9 @@ export class MarbleFormatter<T = any> {
marbles += '(^!)'; marbles += '(^!)';
break; break;
default: default:
marbles += `^${'-'.repeat((unsubscribedFrame - subscribedFrame) / this.frameDuration - 1)}!`; marbles += `^${'-'.repeat(
(unsubscribedFrame - subscribedFrame) / this.frameDuration - 1
)}!`;
} }
return marbles; return marbles;
} }
...@@ -111,7 +125,11 @@ export class MarbleFormatter<T = any> { ...@@ -111,7 +125,11 @@ export class MarbleFormatter<T = any> {
} }
function isSubscriptionLog(data: any): data is SubscriptionLog { function isSubscriptionLog(data: any): data is SubscriptionLog {
return typeof data === 'object' && 'subscribedFrame' in data && 'unsubscribedFrame' in data; return (
typeof data === 'object' &&
'subscribedFrame' in data &&
'unsubscribedFrame' in data
);
} }
function isEvent<T>(data: any): data is Event<T> { function isEvent<T>(data: any): data is Event<T> {
...@@ -131,7 +149,11 @@ export class MarbleTestScheduler<T> extends TestScheduler { ...@@ -131,7 +149,11 @@ export class MarbleTestScheduler<T> extends TestScheduler {
return new MarbleTestScheduler<T>(this.formatter.withError(error)); return new MarbleTestScheduler<T>(this.formatter.withError(error));
} }
public createColdObservable<X = T>(marbles: string, values?: Values<X>, error?: any): ColdObservable<X> { public createColdObservable<X = T>(
marbles: string,
values?: Values<X>,
error?: any
): ColdObservable<X> {
return super.createColdObservable<X>( return super.createColdObservable<X>(
marbles, marbles,
(values === undefined ? this.formatter.values : values) as Values<X>, (values === undefined ? this.formatter.values : values) as Values<X>,
...@@ -139,7 +161,11 @@ export class MarbleTestScheduler<T> extends TestScheduler { ...@@ -139,7 +161,11 @@ export class MarbleTestScheduler<T> extends TestScheduler {
); );
} }
public createHotObservable<X = T>(marbles: string, values?: Values<X>, error?: any): HotObservable<X> { public createHotObservable<X = T>(
marbles: string,
values?: Values<X>,
error?: any
): HotObservable<X> {
return super.createHotObservable<X>( return super.createHotObservable<X>(
marbles, marbles,
(values === undefined ? this.formatter.values : values) as Values<X>, (values === undefined ? this.formatter.values : values) as Values<X>,
...@@ -166,20 +192,34 @@ export class MarbleTestScheduler<T> extends TestScheduler { ...@@ -166,20 +192,34 @@ export class MarbleTestScheduler<T> extends TestScheduler {
return super.run(callback); return super.run(callback);
} }
private compare<U extends Event<T> | SubscriptionLog>(actual: U[], expected: U[]): void { private compare<U extends Event<T> | SubscriptionLog>(
actual: U[],
expected: U[]
): void {
if ( if (
(expected.length === 0 || isSubscriptionLog(expected[0])) && (expected.length === 0 || isSubscriptionLog(expected[0])) &&
(actual.length === 0 || isSubscriptionLog(actual[0])) (actual.length === 0 || isSubscriptionLog(actual[0]))
) { ) {
const expectedMarbles = expected.map(s => this.formatter.formatSubscriptions(s as SubscriptionLog)); const expectedMarbles = expected.map(s =>
const actualMarbles = actual.map(s => this.formatter.formatSubscriptions(s as SubscriptionLog)); this.formatter.formatSubscriptions(s as SubscriptionLog)
);
const actualMarbles = actual.map(s =>
this.formatter.formatSubscriptions(s as SubscriptionLog)
);
expect(actualMarbles).toEqual(expectedMarbles); expect(actualMarbles).toEqual(expectedMarbles);
return; return;
} }
if ((expected.length === 0 || isEvent(expected[0])) && (actual.length === 0 || isEvent(actual[0]))) { if (
const expectedMarbles = this.formatter.formatMarbles(expected as Array<Event<T>>); (expected.length === 0 || isEvent(expected[0])) &&
const actualMarbles = this.formatter.formatMarbles(actual as Array<Event<T>>); (actual.length === 0 || isEvent(actual[0]))
) {
const expectedMarbles = this.formatter.formatMarbles(expected as Array<
Event<T>
>);
const actualMarbles = this.formatter.formatMarbles(actual as Array<
Event<T>
>);
expect(actualMarbles).toEqual(expectedMarbles); expect(actualMarbles).toEqual(expectedMarbles);
return; return;
} }
...@@ -192,7 +232,9 @@ export class MarbleTestScheduler<T> extends TestScheduler { ...@@ -192,7 +232,9 @@ export class MarbleTestScheduler<T> extends TestScheduler {
error?: any, error?: any,
frameDuration = this.frameTimeFactor frameDuration = this.frameTimeFactor
): MarbleTestScheduler<T> { ): MarbleTestScheduler<T> {
return new MarbleTestScheduler<T>(new MarbleFormatter<T>(values, error, frameDuration)); return new MarbleTestScheduler<T>(
new MarbleFormatter<T>(values, error, frameDuration)
);
} }
} }
......
...@@ -24,7 +24,9 @@ const OPERATOR = Symbol.for('untilDestroyedOperator'); ...@@ -24,7 +24,9 @@ const OPERATOR = Symbol.for('untilDestroyedOperator');
* public ngOnDestroy() {} * public ngOnDestroy() {}
* } * }
*/ */
export function untilDestroyed<T>(target: OnDestroy): MonoTypeOperatorFunction<T> { export function untilDestroyed<T>(
target: OnDestroy
): MonoTypeOperatorFunction<T> {
if (OPERATOR in target) { if (OPERATOR in target) {
return (target as any)[OPERATOR]; return (target as any)[OPERATOR];
} }
......
...@@ -8,7 +8,9 @@ import { WatchedValue, WatchService } from './watch.service'; ...@@ -8,7 +8,9 @@ import { WatchedValue, WatchService } from './watch.service';
templateUrl: './watch-display.component.html', templateUrl: './watch-display.component.html',
}) })
export class WatchDisplayComponent { export class WatchDisplayComponent {
public readonly values$ = this.watchService.values$.pipe(tap(values => this.countChanged.emit(values.length))); public readonly values$ = this.watchService.values$.pipe(
tap(values => this.countChanged.emit(values.length))
);
@Output() @Output()
public countChanged = new EventEmitter<number>(); public countChanged = new EventEmitter<number>();
......
import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { BehaviorSubject, combineLatest, ReplaySubject, Unsubscribable } from 'rxjs'; import {
BehaviorSubject,
combineLatest,
ReplaySubject,
Unsubscribable,
} from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { WatchedValue, WatchService } from './watch.service'; import { WatchedValue, WatchService } from './watch.service';
......
...@@ -14,7 +14,9 @@ export class WatchService { ...@@ -14,7 +14,9 @@ export class WatchService {
private readonly registry = new Set<Observable<WatchedValue>>(); private readonly registry = new Set<Observable<WatchedValue>>();
private readonly registry$ = new BehaviorSubject(this.registry); private readonly registry$ = new BehaviorSubject(this.registry);
public readonly values$ = this.registry$.pipe(switchMap(valueSet => safeCombineLatest(Array.from(valueSet)))); public readonly values$ = this.registry$.pipe(
switchMap(valueSet => safeCombineLatest(Array.from(valueSet)))
);
public register(value$: Observable<WatchedValue>): Unsubscribable { public register(value$: Observable<WatchedValue>): Unsubscribable {
if (!this.registry.has(value$)) { if (!this.registry.has(value$)) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment