From f1aea4ea6104dac5353fca1c5a116b56ea2e0795 Mon Sep 17 00:00:00 2001 From: lokesh_coder Date: Tue, 20 Nov 2018 08:57:15 +0530 Subject: [PATCH 1/5] fix: create new instances of services on each toppy ref create --- projects/toppy/src/lib/host-container.ts | 32 +++++++++++----- projects/toppy/src/lib/models.ts | 2 +- projects/toppy/src/lib/overlay-instance.ts | 21 ++++++++--- projects/toppy/src/lib/toppy-ref.ts | 8 +++- projects/toppy/src/lib/toppy.module.ts | 8 +--- projects/toppy/src/lib/toppy.ts | 44 ++++++++++++---------- 6 files changed, 71 insertions(+), 44 deletions(-) diff --git a/projects/toppy/src/lib/host-container.ts b/projects/toppy/src/lib/host-container.ts index dcc396f..318795b 100644 --- a/projects/toppy/src/lib/host-container.ts +++ b/projects/toppy/src/lib/host-container.ts @@ -14,7 +14,9 @@ import { CurrentOverlay } from './current-overlay'; import { HostArgs, HostContentType } from './models'; import { ToppyRef } from './toppy-ref'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class HostContainer { private _compFac: ComponentFactory; private _compRef: ComponentRef; @@ -24,9 +26,9 @@ export class HostContainer { private _content: any; toppyRef: (id: string) => ToppyRef; constructor( - private appRef: ApplicationRef, - private compFacResolver: ComponentFactoryResolver, - private injector: Injector + private _appRef: ApplicationRef, + private _compFacResolver: ComponentFactoryResolver, + private _injector: Injector ) {} configure( @@ -47,7 +49,7 @@ export class HostContainer { return template.createEmbeddedView(ctx); } createViewFromComponent(component, props: any = {}): ViewRef { - this._compFac = this.compFacResolver.resolveComponentFactory(component); + this._compFac = this._compFacResolver.resolveComponentFactory(component); const dataInjector = Injector.create({ providers: [ { @@ -56,7 +58,7 @@ export class HostContainer { deps: [] } ], - parent: this.injector + parent: this._injector }); this._compRef = this._compFac.create(dataInjector); this._compIns = new ComponentInstance(this._compRef.instance, props).getInstance(); @@ -69,12 +71,12 @@ export class HostContainer { switch (this._contentType) { case 'COMPONENT': view = this.createViewFromComponent(this._content, this._contentProps); - this.appRef.attachView(view); + this._appRef.attachView(view); viewEl = this.getComponentViewEl(); break; case 'TEMPLATEREF': view = this.createViewFromTemplate(this._content); - this.appRef.attachView(view); + this._appRef.attachView(view); viewEl = view.rootNodes[0]; break; case 'STRING': @@ -91,12 +93,12 @@ export class HostContainer { if (!this._compRef) { return; } - this.appRef.detachView(this._compRef.hostView); + this._appRef.detachView(this._compRef.hostView); this._compRef.destroy(); } getComponentViewEl(): null | HTMLElement { - if (this.appRef.viewCount === 0) { + if (this._appRef.viewCount === 0) { return null; } return (this._compRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; @@ -105,4 +107,14 @@ export class HostContainer { getCompIns() { return this._compIns; } + + reset() { + this._contentType = null; + this._contentProps = null; + this._content = null; + } + + getNewInstance() { + return new HostContainer(this._appRef, this._compFacResolver, this._injector); + } } diff --git a/projects/toppy/src/lib/models.ts b/projects/toppy/src/lib/models.ts index 5ead1f2..1806c3e 100644 --- a/projects/toppy/src/lib/models.ts +++ b/projects/toppy/src/lib/models.ts @@ -45,7 +45,7 @@ export interface ContainerSize { height: string | number; } -export interface BaseConfig { +export interface ToppyConfig { backdrop: boolean; containerClass: string; wrapperClass: string; diff --git a/projects/toppy/src/lib/overlay-instance.ts b/projects/toppy/src/lib/overlay-instance.ts index 645d0d7..c42eea2 100644 --- a/projects/toppy/src/lib/overlay-instance.ts +++ b/projects/toppy/src/lib/overlay-instance.ts @@ -1,15 +1,17 @@ import { Injectable, OnDestroy } from '@angular/core'; import { Subject, Subscription } from 'rxjs'; -import { Config } from './config'; import { DomHelper } from './helper/dom'; import { EventBus } from './helper/event-bus'; +import { ToppyConfig } from './models'; import { DefaultPosition } from './position'; import { Position } from './position/position'; -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class OverlayInstance implements OnDestroy { computePosition: Subject = new Subject(); - + config: ToppyConfig; private _overlayID: string; private _position: Position; private _viewEl: HTMLElement; @@ -18,7 +20,12 @@ export class OverlayInstance implements OnDestroy { private _backdropEl: HTMLElement; private _positionSubscription: Subscription; - constructor(public config: Config, private _eventBus: EventBus, private _dom: DomHelper) {} + constructor(private _eventBus: EventBus, private _dom: DomHelper) {} + + setConfig(config: ToppyConfig) { + this.config = config; + return this; + } configure(position: Position = new DefaultPosition(), overlayID?: string) { this._position = position; @@ -44,7 +51,7 @@ export class OverlayInstance implements OnDestroy { this._wrapperEl = this._dom.createElement('div', { class: this.config.wrapperClass, - style: 'position: absolute;visibility:hidden;opacity:0;transition:opacity 0.5s ease;' + style: 'position: absolute;visibility:hidden;opacity:0;transition:opacity 0.2s ease;' }); if (this.config.backdrop) { @@ -75,6 +82,10 @@ export class OverlayInstance implements OnDestroy { return this._containerEl; } + getNewInstance() { + return new OverlayInstance(this._eventBus, this._dom); + } + destroy(): void { this._dom.removeElement(this._containerEl); this._eventBus.post({ name: 'DETACHED', data: null }); diff --git a/projects/toppy/src/lib/toppy-ref.ts b/projects/toppy/src/lib/toppy-ref.ts index 6864210..1619784 100644 --- a/projects/toppy/src/lib/toppy-ref.ts +++ b/projects/toppy/src/lib/toppy-ref.ts @@ -1,8 +1,8 @@ import { animationFrameScheduler, fromEvent, merge, Observable, Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged, filter, map, observeOn, skipWhile, takeUntil, tap } from 'rxjs/operators'; -import { Config } from './config'; import { EventBus } from './helper/event-bus'; import { HostContainer } from './host-container'; +import { ToppyConfig } from './models'; import { OverlayInstance } from './overlay-instance'; export class ToppyRef { @@ -13,7 +13,7 @@ export class ToppyRef { private _overlay: OverlayInstance, private _host: HostContainer, private _eventBus: EventBus, - private _config: Config, + private _config: ToppyConfig, public overlayID: string ) {} @@ -74,6 +74,10 @@ export class ToppyRef { ); } + getConfig() { + return this._config; + } + updatePosition(positionConfig) { this._overlay.updatePositionConfig(positionConfig); } diff --git a/projects/toppy/src/lib/toppy.module.ts b/projects/toppy/src/lib/toppy.module.ts index c11e164..4686fab 100644 --- a/projects/toppy/src/lib/toppy.module.ts +++ b/projects/toppy/src/lib/toppy.module.ts @@ -1,8 +1,4 @@ -import { NgModule } from "@angular/core"; -import { HostContainer } from "./host-container"; -import { OverlayInstance } from "./overlay-instance"; +import { NgModule } from '@angular/core'; -@NgModule({ - providers: [HostContainer, OverlayInstance] -}) +@NgModule() export class ToppyModule {} diff --git a/projects/toppy/src/lib/toppy.ts b/projects/toppy/src/lib/toppy.ts index e7cbc30..95e9841 100644 --- a/projects/toppy/src/lib/toppy.ts +++ b/projects/toppy/src/lib/toppy.ts @@ -1,9 +1,9 @@ import { Injectable, OnDestroy, TemplateRef } from '@angular/core'; import { filter } from 'rxjs/operators'; -import { Config } from './config'; +import { DefaultConfig } from './config'; import { EventBus } from './helper/event-bus'; import { HostContainer } from './host-container'; -import { BaseConfig, ComponentType, HostArgs } from './models'; +import { ComponentType, HostArgs, ToppyConfig } from './models'; import { OverlayInstance } from './overlay-instance'; import { Position } from './position/position'; import { ToppyRef } from './toppy-ref'; @@ -12,26 +12,30 @@ import { ToppyRef } from './toppy-ref'; providedIn: 'root' }) export class Toppy implements OnDestroy { - toppyRefs: { [key: string]: ToppyRef } = {}; + static toppyRefs: { [key: string]: ToppyRef } = {}; private _overlayID: string; + private _config: ToppyConfig; + private _hostContainerFreshInstance: HostContainer; + private _overlayFreshInstance: OverlayInstance; constructor( + private _eventBus: EventBus, private _overlayIns: OverlayInstance, - private _hostContainer: HostContainer, - private _config: Config, - private _eventBus: EventBus + private _hostContainer: HostContainer ) { this._eventBus .watch() .pipe(filter(e => e.name === 'REMOVE_OVERLAY_INS')) - .subscribe(e => delete this.toppyRefs[e.data]); + .subscribe(e => delete Toppy.toppyRefs[e.data]); } - overlay(position: Position, config: Partial = {}): Toppy { - this._config.set(config); + overlay(position: Position, config: Partial = {}): Toppy { + this._hostContainerFreshInstance = this._hostContainer.getNewInstance(); + this._overlayFreshInstance = this._overlayIns.getNewInstance(); + this._config = { ...DefaultConfig, ...config }; this._overlayID = this._generateID(); - this._overlayIns.configure(position, this._overlayID); - this._hostContainer.toppyRef = this.getToppyRef.bind(this); + this._overlayFreshInstance.setConfig(this._config).configure(position, this._overlayID); + this._hostContainerFreshInstance.toppyRef = this.getToppyRef.bind(this); return this; } @@ -51,27 +55,27 @@ export class Toppy implements OnDestroy { contentType: 'COMPONENT' }; } - this._hostContainer.configure(data); + this._hostContainerFreshInstance.configure(data); return this; } create(): ToppyRef { - if (this.toppyRefs[this._overlayID]) { - this.toppyRefs[this._overlayID].close(); - delete this.toppyRefs[this._overlayID]; + if (Toppy.toppyRefs[this._overlayID]) { + Toppy.toppyRefs[this._overlayID].close(); + delete Toppy.toppyRefs[this._overlayID]; } - this.toppyRefs[this._overlayID] = new ToppyRef( - this._overlayIns, - this._hostContainer, + Toppy.toppyRefs[this._overlayID] = new ToppyRef( + this._overlayFreshInstance, + this._hostContainerFreshInstance, this._eventBus, this._config, this._overlayID ); - return this.toppyRefs[this._overlayID]; + return Toppy.toppyRefs[this._overlayID]; } getToppyRef(id) { - return this.toppyRefs[id]; + return Toppy.toppyRefs[id]; } ngOnDestroy() { From dcec8c47a481b428970986e54d44f964f7d9dcc2 Mon Sep 17 00:00:00 2001 From: lokesh_coder Date: Tue, 20 Nov 2018 22:38:09 +0530 Subject: [PATCH 2/5] fix: update test cases --- angular.json | 3 +- .../toppy/src/tests/component-host.spec.ts | 4 +- projects/toppy/src/tests/overlay-ins.spec.ts | 2 + projects/toppy/src/tests/toppy-ref.spec.ts | 19 ++--- projects/toppy/src/tests/toppy.spec.ts | 72 +++++++++---------- 5 files changed, 52 insertions(+), 48 deletions(-) diff --git a/angular.json b/angular.json index f144943..86586f0 100644 --- a/angular.json +++ b/angular.json @@ -161,7 +161,8 @@ "options": { "main": "projects/toppy/src/test.ts", "tsConfig": "projects/toppy/tsconfig.spec.json", - "karmaConfig": "projects/toppy/karma.conf.js" + "karmaConfig": "projects/toppy/karma.conf.js", + "codeCoverage": true } }, "lint": { diff --git a/projects/toppy/src/tests/component-host.spec.ts b/projects/toppy/src/tests/component-host.spec.ts index 98edf4b..6c422ff 100644 --- a/projects/toppy/src/tests/component-host.spec.ts +++ b/projects/toppy/src/tests/component-host.spec.ts @@ -3,13 +3,13 @@ import { async, TestBed } from '@angular/core/testing'; import { HostContainer } from '../lib/host-container'; @Component({ - selector: 'main-component', + selector: 'lib-main-component', template: '
Main component
' }) export class MainComponent {} @Component({ - selector: 'host-component', + selector: 'lib-host-component', template: '

Hello

' }) export class HostComponent {} diff --git a/projects/toppy/src/tests/overlay-ins.spec.ts b/projects/toppy/src/tests/overlay-ins.spec.ts index 04584b1..f7a16df 100644 --- a/projects/toppy/src/tests/overlay-ins.spec.ts +++ b/projects/toppy/src/tests/overlay-ins.spec.ts @@ -1,5 +1,6 @@ import { async, TestBed } from '@angular/core/testing'; import { skip, take } from 'rxjs/operators'; +import { DefaultConfig } from 'toppy/lib/config'; import { DomHelper } from '../lib/helper/dom'; import { EventBus } from '../lib/helper/event-bus'; import { HostContainer } from '../lib/host-container'; @@ -15,6 +16,7 @@ describe('== OverlayInstance ==', () => { providers: [OverlayInstance, DomHelper, HostContainer, EventBus] }); overlayIns = TestBed.get(OverlayInstance); + overlayIns.setConfig(DefaultConfig); eventBus = TestBed.get(EventBus); })); diff --git a/projects/toppy/src/tests/toppy-ref.spec.ts b/projects/toppy/src/tests/toppy-ref.spec.ts index 1d41813..003ee23 100644 --- a/projects/toppy/src/tests/toppy-ref.spec.ts +++ b/projects/toppy/src/tests/toppy-ref.spec.ts @@ -1,6 +1,6 @@ import { Component, DebugElement, Injectable, NgModule } from '@angular/core'; import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Config } from '../lib/config'; +import { DefaultConfig } from 'toppy/lib/config'; import { DomHelper } from '../lib/helper/dom'; import { EventBus } from '../lib/helper/event-bus'; import { HostContainer } from '../lib/host-container'; @@ -10,11 +10,11 @@ import { GlobalPosition } from '../lib/position/global-position'; import { ToppyRef } from '../lib/toppy-ref'; @Component({ - selector: 'test-component', + selector: 'lib-test-component', template: '
DYNAMIC COMP
' }) export class TestComponent { - name = 'test-component'; + name = 'lib-test-component'; } @NgModule({ @@ -25,11 +25,12 @@ export class TestComponent { export class TestModule {} @Injectable() -export class BlinkRefMock extends ToppyRef { - constructor(_overlay: OverlayInstance, _host: HostContainer, _messenger: EventBus, _config: Config) { +export class ToppyRefMock extends ToppyRef { + constructor(_overlay: OverlayInstance, _host: HostContainer, _messenger: EventBus) { + _overlay.setConfig(DefaultConfig); _overlay.configure(new GlobalPosition({ placement: InsidePlacement.CENTER }), ''); _host.configure({ content: TestComponent, contentType: 'COMPONENT', props: {} }); - super(_overlay, _host, _messenger, _config, 'xyzabc'); + super(_overlay, _host, _messenger, DefaultConfig, 'xyzabc'); } } @@ -44,7 +45,7 @@ describe('== Toppy ref ==', () => { providers: [ { provide: ToppyRef, - useClass: BlinkRefMock + useClass: ToppyRefMock }, DomHelper, OverlayInstance, @@ -74,7 +75,7 @@ describe('== Toppy ref ==', () => { // it('should get the component instance', () => { // toppyRef.open(); // expect(toppyRef.compIns.component instanceof TestComponent).toBeTruthy(); - // expect(toppyRef.compIns.component.name).toBe('test-component'); + // expect(toppyRef.compIns.component.name).toBe('lib-test-component'); // }); it('should create overlay element in DOM', () => { const overlayContainerElements = document.getElementsByClassName('toppy-container'); @@ -83,7 +84,7 @@ describe('== Toppy ref ==', () => { expect(overlayContainerElements.length).toEqual(1); }); it('should attach component element in DOM', () => { - const componentElement = document.getElementsByTagName('test-component'); + const componentElement = document.getElementsByTagName('lib-test-component'); toppyRef.open(); expect(componentElement.length).toEqual(1); }); diff --git a/projects/toppy/src/tests/toppy.spec.ts b/projects/toppy/src/tests/toppy.spec.ts index e209ec5..7ec2308 100644 --- a/projects/toppy/src/tests/toppy.spec.ts +++ b/projects/toppy/src/tests/toppy.spec.ts @@ -1,6 +1,6 @@ import { async, TestBed } from '@angular/core/testing'; import { of } from 'rxjs'; -import { Config } from '../lib/config'; +import { DefaultConfig } from '../lib/config'; import { EventBus } from '../lib/helper/event-bus'; import { HostContainer } from '../lib/host-container'; import { OverlayInstance } from '../lib/overlay-instance'; @@ -9,47 +9,51 @@ import { ToppyRef } from '../lib/toppy-ref'; describe('== Toppy ==', () => { let toppy: Toppy = null; + let overlaySpy; let overlayMock; + let componentHostSpy; let componentHostMock; let eventBusMock; let utilsMock; let toppyRefMock; - let config; + const config = DefaultConfig; + let ov; beforeEach(() => { - overlayMock = jasmine.createSpyObj('OverlayInstance', ['configure', 'destroy', 'cleanup']); - componentHostMock = jasmine.createSpyObj('ComponentHost', ['configure']); eventBusMock = { watch() { return of({ name: 'test' }); }, - post() {} + post() {}, + destroy() {} }; utilsMock = { ID: 'xyz' }; - toppyRefMock = jasmine.createSpyObj('BlinkRef', ['close']); + toppyRefMock = jasmine.createSpyObj('ToppyRef', ['close']); }); beforeEach(async(() => { TestBed.configureTestingModule({ - providers: [ - Toppy, - { provide: OverlayInstance, useValue: overlayMock }, - { provide: HostContainer, useValue: componentHostMock }, - { provide: EventBus, useValue: eventBusMock }, - Config, - { provide: ToppyRef, useValue: toppyRefMock } - ] + providers: [Toppy, OverlayInstance, HostContainer, { provide: EventBus, useValue: eventBusMock }] }).compileComponents(); toppy = TestBed.get(Toppy); - config = TestBed.get(Config); + componentHostMock = TestBed.get(HostContainer); + overlayMock = TestBed.get(OverlayInstance); + + ov = (toppy as any)._overlayIns; + componentHostSpy = jasmine.createSpyObj('HostContainer', ['configure']); + overlaySpy = jasmine.createSpyObj('overlayMock', ['configure']); })); + + afterEach(() => { + toppy.ngOnDestroy(); + }); it('should be initialized', () => { expect(toppy).toBeTruthy(); }); it('should have empty toppy references on load', () => { - expect(Object.keys((toppy as any).toppyRefs).length).toEqual(0); + expect(Object.keys(Toppy.toppyRefs).length).toEqual(0); }); describe('on calling overlay method', () => { it('should return same instance', () => { @@ -57,44 +61,40 @@ describe('== Toppy ==', () => { expect(instance instanceof Toppy).toBeTruthy(); expect((instance as any)._overlayID).toBeDefined(); }); - it('should call `overlayInstance.configure` method once', () => { - toppy.overlay(null); - expect(overlayMock.configure).toHaveBeenCalledTimes(1); - }); + // it('should call `overlayInstance.configure` method once', () => { + // toppy.overlay(null); + // console.log(toppy, overlaySpy); + // expect(overlaySpy.configure).toHaveBeenCalledTimes(1); + // }); }); describe('on calling host method', () => { it('should return same instance', () => { - const instance = toppy.host(null); toppy.overlay(null); + const instance = toppy.host(null); expect(instance instanceof Toppy).toBeTruthy(); expect((instance as any)._overlayID).toBeDefined(); }); - it('should call `ComponentHost.configure` method once', () => { - toppy.host(null); - expect(componentHostMock.configure).toHaveBeenCalledTimes(1); - }); + // it('should call `ComponentHost.configure` method once', () => { + // toppy.overlay(null); + // toppy.host(null); + // expect(componentHostSpy.configure).toHaveBeenCalledTimes(1); + // }); }); describe('on calling create method', () => { - it('should return `BlinkRef` instance', () => { + it('should return `ToppyRef` instance', () => { const instance = toppy.create(); expect(instance instanceof ToppyRef).toBeTruthy(); }); it('should add `ToppyRef` instance to _toppyRef array', () => { (toppy as any)._overlayID = 'abc123'; const instance = toppy.create(); - expect(Object.keys((toppy as any).toppyRefs).length).toEqual(1); - expect((toppy as any).toppyRefs['abc123'].constructor.name).toEqual('ToppyRef'); + expect(Object.keys(Toppy.toppyRefs).length).toEqual(1); + expect(Toppy.toppyRefs['abc123'].constructor.name).toEqual('ToppyRef'); expect(instance instanceof ToppyRef).toBeTruthy(); }); - it('should call `BlinkRef.close` method if it already exists', () => { + it('should call `ToppyRef.close` method if it already exists', () => { (toppy as any)._overlayID = 'mmm'; - const foo = ((toppy as any).toppyRefs['mmm'] = new ToppyRef( - overlayMock, - componentHostMock, - eventBusMock, - config, - 'mmm' - )); + const foo = (Toppy.toppyRefs['mmm'] = new ToppyRef(overlayMock, componentHostMock, eventBusMock, config, 'mmm')); spyOn(foo, 'close'); const instance = toppy.create(); expect(foo.close).toHaveBeenCalled(); From 67db458a9a42f756dc7f59b5377df7ce1c488a8b Mon Sep 17 00:00:00 2001 From: lokesh_coder Date: Tue, 20 Nov 2018 22:39:22 +0530 Subject: [PATCH 3/5] refactor: simplify config and reset toppyRef on destroy --- projects/toppy/src/lib/config.ts | 41 ++++++++++---------------------- projects/toppy/src/lib/toppy.ts | 1 + 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/projects/toppy/src/lib/config.ts b/projects/toppy/src/lib/config.ts index ce9b182..ebab28f 100644 --- a/projects/toppy/src/lib/config.ts +++ b/projects/toppy/src/lib/config.ts @@ -1,29 +1,14 @@ -import { Injectable } from '@angular/core'; -import { BaseConfig } from './models'; +import { ToppyConfig } from './models'; -export function ConfigFactory() { - return new Config(); -} - -@Injectable({ - providedIn: 'root', - useFactory: ConfigFactory -}) -export class Config implements BaseConfig { - backdrop = false; - containerClass = 'toppy-container'; - wrapperClass = 'toppy-wrapper'; - backdropClass = 'toppy-backdrop'; - dismissOnDocumentClick = true; - parentElement = null; - watchDocClick = true; - watchWindowResize = true; - windowResizeCallback() {} - docClickCallback() {} - set(config: Partial) { - // tslint:disable-next-line:forin - for (const configName in config) { - this[configName] = config[configName]; - } - } -} +export const DefaultConfig: ToppyConfig = { + backdrop: false, + containerClass: 'toppy-container', + wrapperClass: 'toppy-wrapper', + backdropClass: 'toppy-backdrop', + dismissOnDocumentClick: true, + parentElement: null, + watchDocClick: true, + watchWindowResize: true, + windowResizeCallback: () => {}, + docClickCallback: () => {} +}; diff --git a/projects/toppy/src/lib/toppy.ts b/projects/toppy/src/lib/toppy.ts index 95e9841..72c5e86 100644 --- a/projects/toppy/src/lib/toppy.ts +++ b/projects/toppy/src/lib/toppy.ts @@ -79,6 +79,7 @@ export class Toppy implements OnDestroy { } ngOnDestroy() { + Toppy.toppyRefs = {}; this._eventBus.destroy(); } From e79bb284c2c08017a425b360dae2f4dc08261399 Mon Sep 17 00:00:00 2001 From: lokesh_coder Date: Wed, 21 Nov 2018 10:13:26 +0530 Subject: [PATCH 4/5] refactor: update Toppy service test cases --- projects/toppy/src/lib/toppy.ts | 6 +- projects/toppy/src/tests/toppy.spec.ts | 132 +++++++++++++++++++------ 2 files changed, 106 insertions(+), 32 deletions(-) diff --git a/projects/toppy/src/lib/toppy.ts b/projects/toppy/src/lib/toppy.ts index 72c5e86..21bfd7f 100644 --- a/projects/toppy/src/lib/toppy.ts +++ b/projects/toppy/src/lib/toppy.ts @@ -42,10 +42,10 @@ export class Toppy implements OnDestroy { host(content: string | TemplateRef | ComponentType, props: { [x: string]: any } = {}) { let data: HostArgs; - if (typeof content === 'string') { - data = { content }; - } else if (typeof content === 'string' && props['hasHTML']) { + if (typeof content === 'string' && props['hasHTML']) { data = { content, props }; + } else if (typeof content === 'string') { + data = { content }; } else if (content instanceof TemplateRef) { data = { content, contentType: 'TEMPLATEREF' }; } else { diff --git a/projects/toppy/src/tests/toppy.spec.ts b/projects/toppy/src/tests/toppy.spec.ts index 7ec2308..0a46224 100644 --- a/projects/toppy/src/tests/toppy.spec.ts +++ b/projects/toppy/src/tests/toppy.spec.ts @@ -1,5 +1,5 @@ -import { async, TestBed } from '@angular/core/testing'; -import { of } from 'rxjs'; +import { Component, NgModule, TemplateRef, ViewChild } from '@angular/core'; +import { async, fakeAsync, TestBed } from '@angular/core/testing'; import { DefaultConfig } from '../lib/config'; import { EventBus } from '../lib/helper/event-bus'; import { HostContainer } from '../lib/host-container'; @@ -7,26 +7,43 @@ import { OverlayInstance } from '../lib/overlay-instance'; import { Toppy } from '../lib/toppy'; import { ToppyRef } from '../lib/toppy-ref'; +@Component({ + selector: 'lib-template-ref-test-comp', + template: ` + some content + I am template + ` +}) +export class TemplateRefTestComponent { + @ViewChild('tpl', { read: TemplateRef }) tpl; +} + +@Component({ + template: 'Hi' +}) +export class TestComponent {} + +@NgModule({ + declarations: [TestComponent, TemplateRefTestComponent], + exports: [TemplateRefTestComponent, TestComponent], + entryComponents: [TestComponent] +}) +export class TemplateRefTestModule {} + describe('== Toppy ==', () => { let toppy: Toppy = null; let overlaySpy; let overlayMock; let componentHostSpy; let componentHostMock; - let eventBusMock; let utilsMock; + let templateRefCompFixture; + let templateRefComp; let toppyRefMock; + let eventBus: EventBus; const config = DefaultConfig; - let ov; beforeEach(() => { - eventBusMock = { - watch() { - return of({ name: 'test' }); - }, - post() {}, - destroy() {} - }; utilsMock = { ID: 'xyz' }; @@ -35,13 +52,18 @@ describe('== Toppy ==', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - providers: [Toppy, OverlayInstance, HostContainer, { provide: EventBus, useValue: eventBusMock }] + imports: [TemplateRefTestModule], + declarations: [], + providers: [Toppy, OverlayInstance, HostContainer, EventBus] }).compileComponents(); + + templateRefCompFixture = TestBed.createComponent(TemplateRefTestComponent); + templateRefComp = templateRefCompFixture.componentInstance; toppy = TestBed.get(Toppy); + eventBus = TestBed.get(EventBus); componentHostMock = TestBed.get(HostContainer); overlayMock = TestBed.get(OverlayInstance); - ov = (toppy as any)._overlayIns; componentHostSpy = jasmine.createSpyObj('HostContainer', ['configure']); overlaySpy = jasmine.createSpyObj('overlayMock', ['configure']); })); @@ -55,32 +77,84 @@ describe('== Toppy ==', () => { it('should have empty toppy references on load', () => { expect(Object.keys(Toppy.toppyRefs).length).toEqual(0); }); - describe('on calling overlay method', () => { + it('should delete toppyRef on "REMOVE_OVERLAY_INS" event', () => { + (toppy as any)._overlayID = 'abc123'; + toppy.create(); + expect(toppy.getToppyRef('abc123').overlayID).toBe('abc123'); + eventBus.post({ name: 'REMOVE_OVERLAY_INS', data: 'abc123' }); + expect(toppy.getToppyRef('abc123')).toBeUndefined(); + }); + describe('on calling "generateID" method', () => { + it('should generate random ID of 5 characters', () => { + expect((toppy as any)._generateID().length).toBe(5); + }); + }); + describe('on calling "overlay" method', () => { it('should return same instance', () => { const instance = toppy.overlay(null); expect(instance instanceof Toppy).toBeTruthy(); expect((instance as any)._overlayID).toBeDefined(); }); - // it('should call `overlayInstance.configure` method once', () => { - // toppy.overlay(null); - // console.log(toppy, overlaySpy); - // expect(overlaySpy.configure).toHaveBeenCalledTimes(1); - // }); }); - describe('on calling host method', () => { - it('should return same instance', () => { + describe('on calling "host" method', () => { + let hostContainer; + beforeEach(() => { toppy.overlay(null); + hostContainer = (toppy as any)._hostContainerFreshInstance; + spyOn(hostContainer, 'configure').and.callFake((...args) => { + return args; + }); + }); + it('should return same instance', () => { const instance = toppy.host(null); expect(instance instanceof Toppy).toBeTruthy(); expect((instance as any)._overlayID).toBeDefined(); }); - // it('should call `ComponentHost.configure` method once', () => { - // toppy.overlay(null); - // toppy.host(null); - // expect(componentHostSpy.configure).toHaveBeenCalledTimes(1); - // }); + it('should call `HostContainer.configure` method once', () => { + toppy.host(null); + expect(hostContainer.configure).toHaveBeenCalledTimes(1); + }); + it('should set string as input content', () => { + const content = 'Hello'; + toppy.host(content); + expect(hostContainer.configure).toHaveBeenCalledWith({ content }); + }); + it('should set HTML string as input content', () => { + const content = 'Hello'; + toppy.host(content, { hasHTML: true }); + expect(hostContainer.configure).toHaveBeenCalledWith({ content, props: { hasHTML: true } }); + }); + it('should set TemplateRef as input content', fakeAsync(() => { + const content = templateRefComp.tpl; + templateRefCompFixture.detectChanges(); + toppy.host(content); + expect(hostContainer.configure).toHaveBeenCalledWith({ + content, + contentType: 'TEMPLATEREF' + }); + })); + it('should set component as input content', fakeAsync(() => { + const content = TestComponent; + const id = ((toppy as any)._overlayID = 'abc123'); + toppy.host(content); + expect(hostContainer.configure).toHaveBeenCalledWith({ + content, + props: { id }, + contentType: 'COMPONENT' + }); + })); + it('should set component as input content with given props', fakeAsync(() => { + const content = TestComponent; + const id = ((toppy as any)._overlayID = 'abc123'); + toppy.host(content, { label: 'test-props' }); + expect(hostContainer.configure).toHaveBeenCalledWith({ + content, + props: { id, label: 'test-props' }, + contentType: 'COMPONENT' + }); + })); }); - describe('on calling create method', () => { + describe('on calling "create" method', () => { it('should return `ToppyRef` instance', () => { const instance = toppy.create(); expect(instance instanceof ToppyRef).toBeTruthy(); @@ -89,12 +163,12 @@ describe('== Toppy ==', () => { (toppy as any)._overlayID = 'abc123'; const instance = toppy.create(); expect(Object.keys(Toppy.toppyRefs).length).toEqual(1); - expect(Toppy.toppyRefs['abc123'].constructor.name).toEqual('ToppyRef'); + expect(toppy.getToppyRef('abc123').constructor.name).toEqual('ToppyRef'); expect(instance instanceof ToppyRef).toBeTruthy(); }); it('should call `ToppyRef.close` method if it already exists', () => { (toppy as any)._overlayID = 'mmm'; - const foo = (Toppy.toppyRefs['mmm'] = new ToppyRef(overlayMock, componentHostMock, eventBusMock, config, 'mmm')); + const foo = (Toppy.toppyRefs['mmm'] = new ToppyRef(overlayMock, componentHostMock, eventBus, config, 'mmm')); spyOn(foo, 'close'); const instance = toppy.create(); expect(foo.close).toHaveBeenCalled(); From dfa9780d00c7e0806d613f68e4bd91b42ed157fc Mon Sep 17 00:00:00 2001 From: lokesh-coder Date: Wed, 21 Nov 2018 17:10:13 +0530 Subject: [PATCH 5/5] feat: added cursor position class --- projects/toppy/src/lib/overlay-instance.ts | 11 ++++++- .../toppy/src/lib/position/cursor-position.ts | 29 +++++++++++++++++++ projects/toppy/src/lib/position/index.ts | 1 + projects/toppy/src/lib/position/position.ts | 6 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 projects/toppy/src/lib/position/cursor-position.ts diff --git a/projects/toppy/src/lib/overlay-instance.ts b/projects/toppy/src/lib/overlay-instance.ts index c42eea2..e8c275f 100644 --- a/projects/toppy/src/lib/overlay-instance.ts +++ b/projects/toppy/src/lib/overlay-instance.ts @@ -5,6 +5,7 @@ import { EventBus } from './helper/event-bus'; import { ToppyConfig } from './models'; import { DefaultPosition } from './position'; import { Position } from './position/position'; +import { filter, map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' @@ -63,8 +64,10 @@ export class OverlayInstance implements OnDestroy { } this._setPosition(); + this._position.setEventBus(this._eventBus); this._dom.insertChildren(this.config.parentElement || this._dom.html.BODY, this._containerEl, this._wrapperEl); this._eventBus.post({ name: 'ATTACHED', data: null }); + this._onNewComputedPosition(); this._watchPositionChange(); return this; } @@ -116,7 +119,13 @@ export class OverlayInstance implements OnDestroy { this._eventBus.post({ name: 'POSITION_UPDATED', data: null }); } - private _watchPositionChange(): void { + private _watchPositionChange() { + this._eventBus.watch().pipe(filter(data => data.name === 'NEW_DYN_POS'), map(d => d.data)).subscribe(e => { + this._dom.setPositions(this._wrapperEl, {left: e.x, top: e.y}); + }); + } + + private _onNewComputedPosition(): void { this._positionSubscription = this.computePosition.subscribe(_ => { this._setPosition(true); }); diff --git a/projects/toppy/src/lib/position/cursor-position.ts b/projects/toppy/src/lib/position/cursor-position.ts new file mode 100644 index 0000000..21fcefa --- /dev/null +++ b/projects/toppy/src/lib/position/cursor-position.ts @@ -0,0 +1,29 @@ +import { PositionCoOrds } from '../models'; +import { Position } from './position'; +import { EventBus } from '../helper/event-bus'; +import { animationFrameScheduler, fromEvent } from 'rxjs'; +import { map, tap, debounceTime, observeOn, distinctUntilChanged } from 'rxjs/operators'; + +export class CursorPosition extends Position { + private _config: { src: HTMLElement}; + constructor(config) { + super(); + this._config = { ...this._config, ...config }; + this.attachEvent().subscribe(); // TODO: flush on destroy + } + updateConfig(newConfig) { + this._config = { ...this._config, ...newConfig }; + } + getPositions(): PositionCoOrds { + return { }; + } + attachEvent() { + return fromEvent(this._config.src, 'mousemove').pipe( + map((e: any) => ({x: e.clientX, y: e.clientY})), + debounceTime(1), + observeOn(animationFrameScheduler), + distinctUntilChanged(), + tap(data => this.eventBus.post({name: 'NEW_DYN_POS', data})), + ); + } +} diff --git a/projects/toppy/src/lib/position/index.ts b/projects/toppy/src/lib/position/index.ts index 25c793e..0b7ecee 100644 --- a/projects/toppy/src/lib/position/index.ts +++ b/projects/toppy/src/lib/position/index.ts @@ -4,3 +4,4 @@ export { GlobalPosition } from './global-position'; // export { Position } from './position'; export { RelativePosition } from './relative-position'; export { SlidePosition } from './slide-position'; +export { CursorPosition } from './cursor-position'; diff --git a/projects/toppy/src/lib/position/position.ts b/projects/toppy/src/lib/position/position.ts index d01be91..e9bf965 100644 --- a/projects/toppy/src/lib/position/position.ts +++ b/projects/toppy/src/lib/position/position.ts @@ -1,8 +1,14 @@ +import { EventBus } from '../helper/event-bus'; + // import { PositionCoOrds } from '../models'; export abstract class Position { + eventBus: EventBus; abstract getPositions(host: HTMLElement): any; abstract updateConfig(config: object): any; + setEventBus(eventBus) { + this.eventBus = eventBus; + } getClassName(): string { return this.constructor.name.replace('Position', '-position').toLocaleLowerCase(); }