Commit bfcf3f14 authored by Harold Boissenin's avatar Harold Boissenin
Browse files

update library

No related merge requests found
Showing with 75 additions and 169 deletions
+75 -169
.gitignore 0 → 100644
......@@ -3,8 +3,11 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
Output,
} from '@angular/core';
// TODO injecter
import { Configuration } from '../configuration';
import { DebugStateService } from '../debug-state.service';
......@@ -26,7 +29,7 @@ interface ResponseInfo {
}
@Component({
selector: 'lib-api-debug',
selector: 'app-api-debug',
templateUrl: './api-debug.component.html',
styleUrls: ['./api-debug.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
......@@ -37,6 +40,9 @@ export class ApiDebugComponent {
private readonly ids: string[] = [];
private readonly apiPrefix: string;
@Output()
public countChanged = new EventEmitter<number>();
constructor(
interceptor: ApiDebugInterceptor,
public readonly debug: DebugStateService,
......@@ -45,7 +51,9 @@ export class ApiDebugComponent {
) {
this.apiPrefix = conf.apiBaseURL;
interceptor.events$.subscribe(ev => this.handle(ev));
interceptor.events$.subscribe(ev => {
this.handle(ev);
});
}
public get unseenCount(): number {
......@@ -82,6 +90,7 @@ export class ApiDebugComponent {
private handle(ev: DebugEvent): void {
if (ev.type === 'request') {
this.addRequest(ev.id, ev.request);
this.countChanged.emit(this.ids.length);
this.cd.markForCheck();
return;
}
......@@ -98,7 +107,10 @@ export class ApiDebugComponent {
this.cd.markForCheck();
}
private addRequest(id: string, { urlWithParams, method }: HttpRequest<any>): void {
private addRequest(
id: string,
{ urlWithParams, method }: HttpRequest<any>
): void {
if (urlWithParams.startsWith(this.apiPrefix)) {
urlWithParams = new URL(urlWithParams).pathname;
}
......@@ -113,7 +125,10 @@ export class ApiDebugComponent {
this.ids.unshift(id);
}
private setResponse(request: RequestInfo, { status, statusText, headers }: HttpResponseBase): void {
private setResponse(
request: RequestInfo,
{ status, statusText, headers }: HttpResponseBase
): void {
request.response = {
status,
statusText,
......
......@@ -9,6 +9,7 @@ import * as _ from 'lodash';
import { Observable, ReplaySubject } from 'rxjs';
import { finalize, tap } from 'rxjs/operators';
// TODO injecter
import { Configuration } from '../configuration';
export interface RequestDebugEvent {
......
import { HttpHandler, HttpRequest } from '@angular/common/http';
import { inject, TestBed } from '@angular/core/testing';
import { AppError } from '@devatscience/ngx-errors';
import { of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ApiInterceptor } from './api.interceptor';
import { Configuration } from '../configuration';
import { MarbleFormatter, MarbleTestScheduler } from '../testing/marbles';
describe('ApiInterceptor', () => {
const API_BASE_URL = 'http://test.com';
const API_PATH = '/foo/bar';
const API_URL = `${API_BASE_URL}${API_PATH}`;
const OTHER_URL = 'http://example.com/foo/bar';
const RESPONSE = 'OK';
const VALUES = {
n: null as null,
r: RESPONSE,
};
let scheduler: MarbleTestScheduler<any>;
let next: jasmine.SpyObj<HttpHandler>;
let conf: Configuration;
beforeEach(() => {
scheduler = new MarbleTestScheduler(new MarbleFormatter(VALUES));
next = jasmine.createSpyObj('HttpHandler', ['handle']);
conf = new Configuration();
conf.apiBaseURL = API_BASE_URL;
TestBed.configureTestingModule({
providers: [ApiInterceptor, { provide: Configuration, useValue: conf }],
});
});
it('should be created', inject(
[ApiInterceptor],
(service: ApiInterceptor) => {
expect(service).toBeTruthy();
}
));
function testUrl(input: string, expected: string): void {
const NEXT_M = /***/ '--r';
const INTER_M = /**/ '--r';
const req = new HttpRequest('GET', input);
scheduler.run(({ expectObservable, cold }) => {
next.handle.and.callFake((r: HttpRequest<any>) => {
expect(r.url).toEqual(expected);
return cold(NEXT_M);
});
inject([ApiInterceptor], (service: ApiInterceptor) =>
expectObservable(service.intercept(req, next)).toBe(INTER_M)
)();
});
}
it('should prepend the base URL to hostless URLs', () => testUrl(API_PATH, API_URL));
it('should not prepend the base URL to full API URLs', () => testUrl(API_URL, API_URL));
it('should not change other URLs', () => testUrl(OTHER_URL, OTHER_URL));
it('should classify errors', () => {
const NEXT_M = /***/ '--#';
const INTER_M = /**/ '--(e|)';
const ERR_IN = new Error('Error');
(VALUES as any).e = new AppError(ERR_IN, ERR_IN.message, ERR_IN.stack, false);
const req = new HttpRequest('GET', '/some/url');
scheduler
.withValues(VALUES)
.withError(ERR_IN)
.run(({ expectObservable, cold }) => {
next.handle.and.callFake(() => cold(NEXT_M));
inject([ApiInterceptor], (service: ApiInterceptor) =>
expectObservable(service.intercept(req, next).pipe(catchError(e => of(e)))).toBe(INTER_M)
)();
});
});
});
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { normalizeError } from '@devatscience/ngx-errors';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Configuration } from '../configuration';
/**
* Préfixe l'URL par BASE_URL si elle commence par "/" (c-à-d que c'est un chemin absolu),
* et ajoute l'authentification si nécessaire.
*/
@Injectable()
export class ApiInterceptor implements HttpInterceptor {
private readonly NON_API_PREFIX = /^(https?:)?\/\//i;
public constructor(private readonly conf: Configuration) {}
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!req.url.match(this.NON_API_PREFIX)) {
req = req.clone({ url: this.conf.apiBaseURL + req.url });
}
return next.handle(req).pipe(catchError((err: any) => throwError(normalizeError(err))));
}
}
......@@ -44,3 +44,11 @@ export class Configuration implements IConfiguration {
}
}
}
export function configurationFactory(): Configuration {
return new Configuration({
apiBaseURL: 'http://baliste-api.dvp',
debug: true,
debugToolbarURL: 'http://baliste-api.dvp/_wdt',
});
}
......@@ -2,8 +2,8 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
// TODO change this
const environment = {
// TODO injecter
export const environment = {
production: false,
apiURL: 'http://baliste-api.dvp',
api: 'http://127.0.0.101:8040',
......
......@@ -3,7 +3,7 @@ import { Component } from '@angular/core';
import { DebugStateService } from './debug-state.service';
@Component({
selector: 'lib-debug-toggle',
selector: 'app-debug-toggle',
template: `
<ng-container *ngIf="state.available">
<p-toggleButton
......
import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { LOCALE_ID, NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import {
......@@ -16,8 +15,7 @@ import { TreeModule } from 'primeng/tree';
import { TreeTableModule } from 'primeng/treetable';
import { ApiDebugComponent } from './api/api-debug.component';
import { ApiDebugInterceptor } from './api/api-debug.interceptor';
import { Configuration } from './configuration';
import { Configuration, configurationFactory } from './configuration';
import { DumpPanelComponent } from './dump-panel.component';
import { DumpValueComponent } from './dump-value.component';
import { SpyDisplayComponent } from './spy/spy-display.component';
......@@ -25,27 +23,16 @@ import { WatchDisplayComponent } from './watch/watch-display.component';
import { WatchComponent } from './watch/watch.component';
import { WatchService } from './watch/watch.service';
export function configurationFactory(): Configuration {
return new Configuration({
apiBaseURL: 'http://baliste-api.dvp',
debug: true,
debugToolbarURL: 'http://baliste-api.dvp/_wdt',
});
}
const EXPORTED_COMPONENTS = [
//
ApiDebugComponent,
WatchComponent,
DumpPanelComponent,
ApiDebugComponent,
];
@NgModule({
imports: [
//
CommonModule,
ReactiveFormsModule,
SidebarModule,
ButtonModule,
TreeModule,
......@@ -58,17 +45,10 @@ const EXPORTED_COMPONENTS = [
TooltipModule,
],
providers: [
ApiDebugInterceptor,
WatchService,
{
provide: HTTP_INTERCEPTORS,
useExisting: ApiDebugInterceptor,
multi: true,
},
{ provide: Configuration, useFactory: configurationFactory },
{ provide: LOCALE_ID, useValue: 'fr' },
],
declarations: [
//
DumpValueComponent,
......
......@@ -18,17 +18,17 @@
<p-tabView>
<p-tabPanel header="Watches ({{ watchCount }})">
<div class="data-panel">
<lib-watch-display (countChanged)="setWatchCount($event)"></lib-watch-display>
<app-watch-display (countChanged)="setWatchCount($event)"></app-watch-display>
</div>
</p-tabPanel>
<p-tabPanel header="Spies ({{ spyCount }})">
<div class="data-panel">
<lib-spy-display (countChanged)="setSpyCount($event)"></lib-spy-display>
<app-spy-display (countChanged)="setSpyCount($event)"></app-spy-display>
</div>
</p-tabPanel>
<p-tabPanel header="API ({{ apiCallCount }})">
<div class="data-panel">
<lib-api-debug></lib-api-debug>
<app-api-debug (countChanged)="setApiCallCount($event)"></app-api-debug>
</div>
</p-tabPanel>
</p-tabView>
......
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
} from '@angular/core';
import { DebugStateService } from './debug-state.service';
@Component({
selector: 'lib-dump-panel',
selector: 'app-dump-panel',
templateUrl: './dump-panel.component.html',
styleUrls: ['./dump-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
......@@ -27,4 +31,9 @@ export class DumpPanelComponent {
this.spyCount = count;
this.cd.markForCheck();
}
public setApiCallCount(count: number): void {
this.apiCallCount = count;
this.cd.markForCheck();
}
}
......@@ -4,7 +4,7 @@ import { TreeNode } from 'primeng/api';
type PropertyPath = Array<string | number>;
@Component({
selector: 'lib-dump-value',
selector: 'app-dump-value',
templateUrl: './dump-value.component.html',
styleUrls: ['./dump-value.component.scss'],
})
......
export * from './rxjs/index'; // TODO make a package ?
export * from './api/api-debug.component';
export * from './api/api-debug.interceptor';
export * from './debug.module';
export * from './debug-state.service';
export * from './debug-toggle.component';
export * from './dump-panel.component';
export * from './dump-value.component';
export * from './rxjs/index';
export * from './spy/spy-display.component';
export * from './watch/watch.component';
export * from './watch/watch-display.component';
export * from './watch/watch.service';
export * from './configuration';
export * from './debug.module';
export * from './debug-state.service';
export * from './debug-toggle.component';
export * from './dump-panel.component';
export * from './dump-value.component';
......@@ -31,6 +31,9 @@ function installHook<T extends Hookable<K, M>, M extends (this: T, ...args: any[
/**
* Modifie une méthode d'un objet pour éxecuter une fonction avant son éxecution normale.
*
* @param {object} target Le prototype à modifier.
* @param {string|symbol} name Le nom de la méthode à surcharger.
* @param {function} hook La fonction à ajouter.
*/
export function hookBefore<
T extends Hookable<K, M>,
......@@ -51,6 +54,9 @@ export function hookBefore<
/**
* Modifie une méthode d'un objet pour éxecuter une fonction après son éxecution normale.
*
* @param {object} target Le prototype à modifier.
* @param {string|symbol} name Le nom de la méthode à surcharger.
* @param {function} hook La fonction à ajouter.
*/
export function hookAfter<
T extends Hookable<K, M>,
......@@ -71,6 +77,9 @@ export function hookAfter<
/**
* Modifie une méthode d'un objet pour éxecuter inconditionnellement une fonction après son éxecution normale.
*
* @param {object} target Le prototype à modifier.
* @param {string|symbol} name Le nom de la méthode à surcharger.
* @param {function} hook La fonction à ajouter.
*/
export function hookFinally<
T extends Hookable<K, M>,
......
......@@ -47,8 +47,8 @@ function ParamFromMap<K extends 'paramMap' | 'queryParamMap'>(
*
* La propriété décorée doit implémenter Observer<string>.
*
* 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|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'.
*/
export function RouteParam(param: string = null, routeProperty: string = 'route') {
return ParamFromMap(param, routeProperty, 'paramMap');
......@@ -59,8 +59,8 @@ export function RouteParam(param: string = null, routeProperty: string = 'route'
*
* La propriété décorée doit implémenter Observer<string>.
*
* 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|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'.
*/
export function QueryParam(param: string = null, routeProperty: string = 'route') {
return ParamFromMap(param, routeProperty, 'queryParamMap');
......
import { Observable } from 'rxjs';
import { MarbleTestScheduler } from '../testing/marbles';
import { safeCombineLatest } from './safe-combine-latest.observable';
import { MarbleTestScheduler } from './testing/marbles';
describe('safe-combine-latest', () => {
let scheduler: MarbleTestScheduler<any>;
......
import { Observable } from 'rxjs';
import { MarbleTestScheduler } from '../testing/marbles';
import { safeForkJoin } from './safe-fork-join.observable';
import { MarbleTestScheduler } from './testing/marbles';
describe('safe-fork-join', () => {
let scheduler: MarbleTestScheduler<any>;
......
import { OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { MarbleTestScheduler } from '../testing/marbles';
import { SubscribeOnInit } from './subscribe-on-init.decorator';
import { MarbleTestScheduler } from './testing/marbles';
describe('@SubscribeOnInit', () => {
const VALUES = { a: 'a' };
......
File moved
// tslint:disable-next-line:no-reference
///<reference path="../../../node_modules/@types/jasmine/index.d.ts"/>
///<reference path="node_modules/@types/jasmine/index.d.ts"/>
/* tslint:disable:rxjs-no-internal */
import * as _ from 'lodash';
......
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