From 63e579160ed37b39cb54c400b64a1dee7550e164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Mon, 15 Apr 2019 16:14:39 +0300 Subject: [PATCH 1/6] Improve router outlet tests --- src/tests/integration.spec.ts | 63 +++++++++++++++++++++------- src/tests/test-dummy.component.ts | 7 ++++ src/tests/test-provider.component.ts | 2 +- 3 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 src/tests/test-dummy.component.ts diff --git a/src/tests/integration.spec.ts b/src/tests/integration.spec.ts index 42da800..475fdd7 100644 --- a/src/tests/integration.spec.ts +++ b/src/tests/integration.spec.ts @@ -8,6 +8,7 @@ import { NgxContextModule } from '../lib/context.module'; import { ContextDisposerDirective } from '../lib/disposer.directive'; import { TestConsumerComponent } from './test-consumer.component'; import { TestDisposerComponent } from './test-disposer.component'; +import { TestDummyComponent } from './test-dummy.component'; import { TestFormComponent } from './test-form.component'; import { TestMiddleComponent } from './test-middle.component'; import { TestProviderComponent } from './test-provider.component'; @@ -84,8 +85,10 @@ describe('Context Provider & Consumer', function(this: IContextConsumer) { }).compileComponents(); this.fixture = TestBed.createComponent(TestProviderComponent); + const props = ['target', 'title']; - shouldSyncProvidedProperty.bind(this, 'target'); + setProvidedProperties.call(this, props); + shouldSyncProvidedProperties.call(this, props); })); it('should work through router outlet', fakeAsync(() => { @@ -104,34 +107,60 @@ describe('Context Provider & Consumer', function(this: IContextConsumer) { RouterTestingModule.withRoutes([ { path: '', + pathMatch: 'full', + component: TestDummyComponent, + }, + { + path: 'test', component: TestMiddleComponent, }, ]), NgxContextModule, ], - declarations: [TestProviderComponent, TestMiddleComponent, TestConsumerComponent], + declarations: [ + TestProviderComponent, + TestDummyComponent, + TestMiddleComponent, + TestConsumerComponent, + ], }).compileComponents(); - this.fixture = TestBed.createComponent(TestProviderComponent); + const props = ['target', 'title']; + setProvidedProperties.call(this, props); this.fixture.ngZone.run(() => { // Navigate to base path - this.fixture.debugElement.injector.get(Router).initialNavigation(); + const router = this.fixture.debugElement.injector.get(Router); + router.initialNavigation(); tick(); - shouldSyncProvidedProperty.bind(this, 'target'); + // TestMiddleComponent should not be loaded at first + expect( + this.fixture.debugElement.query(By.directive(TestMiddleComponent)), + ).toBeNull(); + + router.navigate(['test']); + tick(); + + shouldSyncProvidedProperties.call(this, props); }); })); }); type Excluded = 'provided' | 'contextMap' | 'consume'; +type Props = Array< + Exclude +>; -function shouldSyncProvidedProperty( - this: IContextConsumer, - prop: Exclude, -): void { - // Query component instances +function setProvidedProperties(this: IContextConsumer, props: Props): void { + this.fixture = TestBed.createComponent(TestProviderComponent); this.parent = this.fixture.debugElement.componentInstance; + this.parent.provided = props; + this.fixture.detectChanges(); +} + +function shouldSyncProvidedProperties(this: IContextConsumer, props: Props): void { + // Query component instances this.middle = this.fixture.debugElement.query( By.directive(TestMiddleComponent), ).componentInstance; @@ -139,15 +168,21 @@ function shouldSyncProvidedProperty( By.directive(ContextConsumerDirective), ).componentInstance; - expect(this.child[prop]).not.toBe(this.parent[prop]); + // Confirm the starting value is different + props.forEach(prop => { + if (typeof this.parent[prop] !== 'undefined') + expect(this.child[prop]).not.toEqual(this.parent[prop]); + }); // Provide property - this.parent.provided = prop; - this.middle.provided = prop; + this.middle.provided = props; // Detect changes this.fixture.detectChanges(); tick(); - expect(this.child[prop]).toBe(this.parent[prop]); + props.forEach(prop => { + expect(this.child[prop]).not.toBeUndefined(); + expect(this.child[prop]).toEqual(this.parent[prop]); + }); } diff --git a/src/tests/test-dummy.component.ts b/src/tests/test-dummy.component.ts new file mode 100644 index 0000000..679646d --- /dev/null +++ b/src/tests/test-dummy.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'context-test-dummy', + template: ``, +}) +export class TestDummyComponent {} diff --git a/src/tests/test-provider.component.ts b/src/tests/test-provider.component.ts index 327acd3..13a1924 100644 --- a/src/tests/test-provider.component.ts +++ b/src/tests/test-provider.component.ts @@ -9,7 +9,7 @@ import { ContextMap } from '../lib/symbols'; encapsulation: ViewEncapsulation.None, }) export class TestProviderComponent { - private _title: string; + private _title: string = 'Testing'; greeting$ = of('Hello'); target = 'World'; From 8a8a576984c0a8db531dfb8bc1255f5d77cf317c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Mon, 15 Apr 2019 16:15:28 +0300 Subject: [PATCH 2/6] Remove unused imports from specs --- src/tests/disposer.directive.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/tests/disposer.directive.spec.ts b/src/tests/disposer.directive.spec.ts index 9e81499..a98e43e 100644 --- a/src/tests/disposer.directive.spec.ts +++ b/src/tests/disposer.directive.spec.ts @@ -1,11 +1,5 @@ import { ChangeDetectorRef } from '@angular/core'; -import { - ComponentFixture, - fakeAsync, - inject, - TestBed, - tick, -} from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ContextDisposerDirective } from '../lib/disposer.directive'; import { ContextProviderComponent } from '../lib/provider.component'; import { TestDisposerComponent } from './test-disposer.component'; From 0080a798fddd50c00b236e1f1c8ad72ba574c1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Mon, 15 Apr 2019 16:17:09 +0300 Subject: [PATCH 3/6] Sync/dispose all provided properties at start --- src/lib/consumer.abstract.ts | 6 ++++-- src/lib/disposer.directive.ts | 3 ++- src/lib/provider.component.ts | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/lib/consumer.abstract.ts b/src/lib/consumer.abstract.ts index 6e978c5..af9738f 100644 --- a/src/lib/consumer.abstract.ts +++ b/src/lib/consumer.abstract.ts @@ -1,17 +1,18 @@ import { ChangeDetectorRef, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { Subject } from 'rxjs'; -import { filter, takeUntil } from 'rxjs/operators'; +import { filter, startWith, takeUntil } from 'rxjs/operators'; import { parseKeys } from './internals'; import { ContextProviderComponent } from './provider.component'; import { ContextMap } from './symbols'; export abstract class AbstractContextConsumer implements OnChanges, OnDestroy, OnInit { - protected consumed = new Map(); protected destroy$ = new Subject(); protected initialized: boolean; protected _contextMap = {}; protected _consume: string | string[] = ''; + consumed = new Map(); + @Input() set contextMap(map: ContextMap) { this._contextMap = map || {}; @@ -48,6 +49,7 @@ export abstract class AbstractContextConsumer implements OnChanges, OnDestroy this.provider.change$ .pipe( takeUntil(this.destroy$), + startWith(...Array.from(this.provider.provided.keys())), filter(key => !!key), ) .subscribe(providerKey => this.syncProperties(consumed, providerKey)); diff --git a/src/lib/disposer.directive.ts b/src/lib/disposer.directive.ts index 8d822ee..cf8c769 100644 --- a/src/lib/disposer.directive.ts +++ b/src/lib/disposer.directive.ts @@ -8,7 +8,7 @@ import { ViewContainerRef, } from '@angular/core'; import { Subject } from 'rxjs'; -import { filter, takeUntil } from 'rxjs/operators'; +import { filter, startWith, takeUntil } from 'rxjs/operators'; import { parseKeys } from './internals'; import { ContextProviderComponent } from './provider.component'; @@ -49,6 +49,7 @@ export class ContextDisposerDirective { this.provider.change$ .pipe( takeUntil(this.destroy$), + startWith(...Array.from(this.provider.provided.keys())), filter(key => !!key), ) .subscribe(providerKey => this.syncProperties(disposed, providerKey)); diff --git a/src/lib/provider.component.ts b/src/lib/provider.component.ts index f0ad3ee..e07027d 100644 --- a/src/lib/provider.component.ts +++ b/src/lib/provider.component.ts @@ -21,10 +21,11 @@ import { ContextMap } from './symbols'; }) export class ContextProviderComponent implements OnChanges, OnInit { private initialized = false; - private provided = new Map(); private _contextMap: ContextMap = {}; private _provide: string | string[] = ''; + provided = new Map(); + @Input() set contextMap(map: ContextMap) { this._contextMap = map || {}; From fde6c4d2eed5b45dec9d65dade25b6f3dc5661c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Mon, 15 Apr 2019 16:17:22 +0300 Subject: [PATCH 4/6] Update version number --- package.json | 2 +- src/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 88d4841..e52b660 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngx-context", - "version": "1.1.0", + "version": "1.1.1", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/src/package.json b/src/package.json index 7dccc33..dd02133 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "ngx-context", - "version": "1.1.0", + "version": "1.1.1", "repository": { "type": "git", "url": "git+https://github.com/ng-turkey/ngx-context.git" From 3c218e0add2290c70ab62a89c91c882f6361b713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Mon, 15 Apr 2019 16:19:40 +0300 Subject: [PATCH 5/6] Fix lint errors --- src/lib/disposer.directive.ts | 12 +++++++----- src/tests/test-provider.component.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/disposer.directive.ts b/src/lib/disposer.directive.ts index cf8c769..1c977a6 100644 --- a/src/lib/disposer.directive.ts +++ b/src/lib/disposer.directive.ts @@ -2,6 +2,8 @@ import { Directive, EmbeddedViewRef, Input, + OnChanges, + OnDestroy, Optional, SkipSelf, TemplateRef, @@ -12,10 +14,14 @@ import { filter, startWith, takeUntil } from 'rxjs/operators'; import { parseKeys } from './internals'; import { ContextProviderComponent } from './provider.component'; +export class Context { + $implicit: { [key: string]: any } = {}; +} + @Directive({ selector: '[contextDisposer]', }) -export class ContextDisposerDirective { +export class ContextDisposerDirective implements OnChanges, OnDestroy { private destroy$ = new Subject(); private _dispose: string | string[] = ''; private view: EmbeddedViewRef; @@ -84,7 +90,3 @@ export class ContextDisposerDirective { if (this.view) this.vcRef.clear(); } } - -export class Context { - $implicit: { [key: string]: any } = {}; -} diff --git a/src/tests/test-provider.component.ts b/src/tests/test-provider.component.ts index 13a1924..90db32a 100644 --- a/src/tests/test-provider.component.ts +++ b/src/tests/test-provider.component.ts @@ -9,7 +9,7 @@ import { ContextMap } from '../lib/symbols'; encapsulation: ViewEncapsulation.None, }) export class TestProviderComponent { - private _title: string = 'Testing'; + private _title = 'Testing'; greeting$ = of('Hello'); target = 'World'; From 6557f2bde523098cfbc011e5b8dc639b88fbcf50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levent=20Arman=20=C3=96zak?= Date: Mon, 15 Apr 2019 16:28:09 +0300 Subject: [PATCH 6/6] Check if props kept in sync when loaded by router --- src/tests/integration.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tests/integration.spec.ts b/src/tests/integration.spec.ts index 475fdd7..e9663a3 100644 --- a/src/tests/integration.spec.ts +++ b/src/tests/integration.spec.ts @@ -142,7 +142,13 @@ describe('Context Provider & Consumer', function(this: IContextConsumer) { router.navigate(['test']); tick(); + // All properties should be synced at start shouldSyncProvidedProperties.call(this, props); + + // And properties should be kept in sync later + this.parent.title = null; + this.fixture.detectChanges(); + expect(this.child.title).toBeNull(); }); })); });