diff --git a/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.html b/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.html index ecaaa27707..9d9603fac2 100644 --- a/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.html +++ b/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.html @@ -1 +1 @@ -
+
diff --git a/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.spec.ts b/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.spec.ts index c9f0e0b112..ceb5a30f74 100644 --- a/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.spec.ts +++ b/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.spec.ts @@ -33,15 +33,10 @@ describe("ProgressIndicatorComponent", () => { }); }); - describe("display Progress Indicator", () => { - it("should not display the progress indicator if isShown is set to false", () => { - component.isShown = false; - expect(hostFixture.debugElement.query(By.css(".div"))).toBeNull(); - }); - - it("should display the progress indicator if isShown is set to true", () => { - component.isShown = true; - expect(hostFixture.debugElement.query(By.css(".div"))).toBeDefined(); + describe("progress indicator", () => { + it("should be correctly displayed", () => { + const progressIndicator = hostFixture.debugElement.query(By.css("div.stark-loading-icon")); + expect(progressIndicator).toBeTruthy(); }); }); }); diff --git a/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.ts b/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.ts index 32c84e0678..e10a26bc7a 100644 --- a/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.ts +++ b/packages/stark-ui/src/modules/progress-indicator/components/progress-indicator.component.ts @@ -9,7 +9,7 @@ import { AbstractStarkUiComponent } from "../../../common/classes/abstract-compo const componentName = "stark-progress-indicator"; /** - * Component that is dynamically created by the ProgressIndicatorDirective + * Component that is dynamically created by the {@link StarkProgressIndicatorDirective} */ @Component({ selector: "stark-progress-indicator", @@ -21,8 +21,6 @@ const componentName = "stark-progress-indicator"; } }) export class StarkProgressIndicatorComponent extends AbstractStarkUiComponent implements OnInit { - public isShown = false; - public constructor( @Inject(STARK_LOGGING_SERVICE) public logger: StarkLoggingService, protected renderer: Renderer2, diff --git a/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.spec.ts b/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.spec.ts index 6e058fb9dc..59c2f6e11b 100644 --- a/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.spec.ts +++ b/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.spec.ts @@ -1,7 +1,8 @@ // tslint:disable:completed-docs -import { Observable } from "rxjs"; -import { Component } from "@angular/core"; +import { Observable, of, Subject } from "rxjs"; +import { Component, ViewChild } from "@angular/core"; import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; import { BrowserDynamicTestingModule } from "@angular/platform-browser-dynamic/testing"; import { STARK_LOGGING_SERVICE } from "@nationalbankbelgium/stark-core"; import { MockStarkLoggingService } from "@nationalbankbelgium/stark-core/testing"; @@ -17,20 +18,27 @@ import { StarkProgressIndicatorComponent } from "../components/progress-indicato @Component({ selector: "test-component", template: ` -
+
` }) class TestComponent { public starkProgressIndicatorConfig?: StarkProgressIndicatorConfig; + + @ViewChild(StarkProgressIndicatorDirective) + public progressIndicatorDirective!: StarkProgressIndicatorDirective; } describe("StarkProgressIndicator", () => { let hostFixture: ComponentFixture; let hostComponent: TestComponent; + const hostElementSelector = "#host-element"; + const mockConfig: StarkProgressIndicatorConfig = { topic: "some-topic", type: StarkProgressIndicatorType.SPINNER }; - const mockStarkProgressIndicatorService = new MockStarkProgressIndicatorService(); + let mockStarkProgressIndicatorService!: MockStarkProgressIndicatorService; beforeEach(async(() => { + mockStarkProgressIndicatorService = new MockStarkProgressIndicatorService(); + return TestBed.configureTestingModule({ imports: [], declarations: [TestComponent, StarkProgressIndicatorComponent, StarkProgressIndicatorDirective], @@ -54,16 +62,103 @@ describe("StarkProgressIndicator", () => { hostComponent = hostFixture.componentInstance; }); - it("should throw error when StarkProgressIndicatorConfig is not set", () => { - hostComponent.starkProgressIndicatorConfig = undefined; + describe("on initialization", () => { + describe("failure", () => { + it("should throw error when StarkProgressIndicatorConfig is not set", () => { + hostComponent.starkProgressIndicatorConfig = undefined; + + expect(() => hostFixture.detectChanges()).toThrowError( + "StarkProgressIndicatorDirective: a StarkProgressIndicatorConfig is required." + ); + }); + }); + + describe("success", () => { + beforeEach(() => { + mockStarkProgressIndicatorService.isVisible.and.returnValue(of(false)); + hostComponent.starkProgressIndicatorConfig = mockConfig; + + hostFixture.detectChanges(); + }); + + it("should register itself to the service", () => { + expect(mockStarkProgressIndicatorService.register).toHaveBeenCalledTimes(1); + expect(mockStarkProgressIndicatorService.register).toHaveBeenCalledWith( + mockConfig.topic, + StarkProgressIndicatorType[mockConfig.type] + ); + }); + + it("should subscribe to the service to be notified when the indicator for the given topic should be visible/hidden", () => { + expect(mockStarkProgressIndicatorService.isVisible).toHaveBeenCalledTimes(1); + expect(mockStarkProgressIndicatorService.isVisible).toHaveBeenCalledWith(mockConfig.topic); + }); + }); + }); + + describe("show/hide", () => { + let isVisible$: Subject; + + beforeEach(() => { + isVisible$ = new Subject(); + mockStarkProgressIndicatorService.isVisible.and.returnValue(isVisible$); + hostComponent.starkProgressIndicatorConfig = mockConfig; + + hostFixture.detectChanges(); + }); + + it("should show/hide the host element and simultaneously hide/show the progress indicator", () => { + expect(mockStarkProgressIndicatorService.register).toHaveBeenCalledTimes(1); - expect(() => hostFixture.detectChanges()).toThrowError( - "StarkProgressIndicatorDirective: a StarkProgressIndicatorConfig is required." - ); + let progressIndicatorComponent = hostFixture.debugElement.query(By.directive(StarkProgressIndicatorComponent)); + const hostElement = hostFixture.debugElement.query(By.css(hostElementSelector)); + + expect(hostElement).toBeTruthy(); + expect(hostElement.classes).toEqual({}); // host element should be shown + expect(progressIndicatorComponent).toBeFalsy(); // progress indicator should be hidden + + isVisible$.next(true); // show + progressIndicatorComponent = hostFixture.debugElement.query(By.directive(StarkProgressIndicatorComponent)); + + expect(hostElement.classes).toEqual({ "stark-hide": true }); // host element should be hidden + expect(progressIndicatorComponent).toBeTruthy(); // progress indicator should be shown + + isVisible$.next(false); // hide again + progressIndicatorComponent = hostFixture.debugElement.query(By.directive(StarkProgressIndicatorComponent)); + + expect(hostElement.classes).toEqual({ "stark-hide": false }); // host element should be shown + expect(progressIndicatorComponent).toBeFalsy(); // progress indicator should be hidden + }); }); - it("should not throw error when StarkProgressIndicatorConfig is set", () => { - hostComponent.starkProgressIndicatorConfig = { topic: "something", type: StarkProgressIndicatorType.SPINNER }; - expect(() => hostFixture.detectChanges()).not.toThrowError(); + describe("ngOnDestroy", () => { + beforeEach(() => { + mockStarkProgressIndicatorService.isVisible.and.returnValue(of(true)); + hostComponent.starkProgressIndicatorConfig = mockConfig; + + hostFixture.detectChanges(); + }); + + it("should destroy the progress indicator and leave only the host element", () => { + let progressIndicatorComponent = hostFixture.debugElement.query(By.directive(StarkProgressIndicatorComponent)); + let hostElement = hostFixture.debugElement.query(By.css(hostElementSelector)); + + expect(hostElement).toBeTruthy(); + expect(progressIndicatorComponent).toBeTruthy(); // progress indicator should be shown + + hostComponent.progressIndicatorDirective.ngOnDestroy(); + + hostElement = hostFixture.debugElement.query(By.css(hostElementSelector)); + expect(hostElement).toBeTruthy(); + progressIndicatorComponent = hostFixture.debugElement.query(By.directive(StarkProgressIndicatorComponent)); + expect(progressIndicatorComponent).toBeFalsy(); // progress indicator should be removed + }); + + it("should de-register itself from the service", () => { + hostComponent.progressIndicatorDirective.ngOnDestroy(); + + expect(mockStarkProgressIndicatorService.deregister).toHaveBeenCalledTimes(1); + expect(mockStarkProgressIndicatorService.deregister).toHaveBeenCalledWith(mockConfig.topic); + }); }); }); diff --git a/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.ts b/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.ts index 99f460f222..5ccd807f30 100644 --- a/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.ts +++ b/packages/stark-ui/src/modules/progress-indicator/directives/progress-indicator.directive.ts @@ -1,15 +1,15 @@ import { - ComponentFactory, ComponentFactoryResolver, - ComponentRef, Directive, ElementRef, Inject, + Injector, Input, OnDestroy, OnInit, Renderer2, - ViewContainerRef + ViewContainerRef, + ViewRef } from "@angular/core"; import { STARK_PROGRESS_INDICATOR_SERVICE, StarkProgressIndicatorService } from "../services"; import { StarkProgressIndicatorConfig, StarkProgressIndicatorType } from "../entities"; @@ -53,18 +53,20 @@ export class StarkProgressIndicatorDirective implements OnInit, OnDestroy { public topic!: string; public type!: StarkProgressIndicatorType | string; - public progressSubscription!: Subscription; - private _componentFactory: ComponentFactory; - public _componentRef!: ComponentRef; + public progressSubscription?: Subscription; + private readonly componentViewRef!: ViewRef; public constructor( @Inject(STARK_PROGRESS_INDICATOR_SERVICE) public _progressService: StarkProgressIndicatorService, componentFactoryResolver: ComponentFactoryResolver, + injector: Injector, private _viewContainer: ViewContainerRef, protected renderer: Renderer2, protected elementRef: ElementRef ) { - this._componentFactory = componentFactoryResolver.resolveComponentFactory(StarkProgressIndicatorComponent); + const componentFactory = componentFactoryResolver.resolveComponentFactory(StarkProgressIndicatorComponent); + const componentRef = componentFactory.create(injector); + this.componentViewRef = componentRef.hostView; } /** @@ -83,41 +85,40 @@ export class StarkProgressIndicatorDirective implements OnInit, OnDestroy { } /** - * The directive registers itself with the StarkProgressIndicator service. + * The directive registers itself to the {@link StarkProgressIndicatorService}. * The component to add is then created and inserted inside of the container. - * Finally, if the component should be hidden or shown, the stark-hide class is removed/added accordingly + * Finally, if the host component should be hidden or shown, the "stark-hide" class is removed/added accordingly */ public ngOnInit(): void { - this._componentRef = this._viewContainer.createComponent(this._componentFactory); - - // TODO The element is here added as a child, not as a sibling - // this.renderer.appendChild(this.elementRef.nativeElement, this._componentRef.location.nativeElement); - - this._viewContainer.insert(this._componentRef.hostView); + if (!this.starkProgressIndicator) { + throw new Error("StarkProgressIndicatorDirective: a StarkProgressIndicatorConfig is required."); + } + this.registerInstance(this.starkProgressIndicator); this.progressSubscription = this._progressService.isVisible(this.topic).subscribe( (isVisible: boolean = false): void => { - this._componentRef.instance.isShown = isVisible; if (isVisible) { + // TODO The element is here added as a child, not as a sibling + // this.renderer.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement); + + this._viewContainer.insert(this.componentViewRef); // insert the view in the last position this.renderer.addClass(this.elementRef.nativeElement, "stark-hide"); } else { + this._viewContainer.detach(this._viewContainer.indexOf(this.componentViewRef)); this.renderer.removeClass(this.elementRef.nativeElement, "stark-hide"); } } ); - - if (!this.starkProgressIndicator) { - throw new Error("StarkProgressIndicatorDirective: a StarkProgressIndicatorConfig is required."); - } - this.registerInstance(this.starkProgressIndicator); } /** - * The directive de-registers itself from the StarkProgressIndicator service when it is destroyed. + * The directive de-registers itself from the {@link StarkProgressIndicatorService} when it is destroyed. */ public ngOnDestroy(): void { - this._viewContainer.clear(); - this.progressSubscription.unsubscribe(); + this.componentViewRef.destroy(); // destroy the progress indicator + if (this.progressSubscription) { + this.progressSubscription.unsubscribe(); + } this._progressService.deregister(this.topic); } }