-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(component): add ngrxPush pipe and ngrxLet directive to @ngrx/com…
…ponent package (#2046)
- Loading branch information
Showing
30 changed files
with
1,577 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { | ||
ChangeDetectorRef, | ||
EmbeddedViewRef, | ||
Injector, | ||
NgZone, | ||
OnDestroy, | ||
Type, | ||
} from '@angular/core'; | ||
import { CdAware, createCdAware, getGlobalThis } from '../../src/core'; | ||
import { | ||
concat, | ||
EMPTY, | ||
NEVER, | ||
NextObserver, | ||
Observable, | ||
of, | ||
PartialObserver, | ||
Unsubscribable, | ||
} from 'rxjs'; | ||
import { tap } from 'rxjs/operators'; | ||
|
||
class CdAwareImplementation<U> implements OnDestroy { | ||
public renderedValue: any = undefined; | ||
public error: any = undefined; | ||
public completed: boolean = false; | ||
private readonly subscription: Unsubscribable; | ||
public cdAware: CdAware<U | undefined | null>; | ||
resetContextObserver: NextObserver<any> = { | ||
next: _ => (this.renderedValue = undefined), | ||
error: e => (this.error = e), | ||
complete: () => (this.completed = true), | ||
}; | ||
updateViewContextObserver: PartialObserver<U | undefined | null> = { | ||
next: (n: U | undefined | null) => (this.renderedValue = n), | ||
error: e => (this.error = e), | ||
complete: () => (this.completed = true), | ||
}; | ||
configurableBehaviour = <T>( | ||
o$: Observable<Observable<T>> | ||
): Observable<Observable<T>> => o$.pipe(tap()); | ||
|
||
constructor() { | ||
this.cdAware = createCdAware<U>({ | ||
work: () => {}, | ||
resetContextObserver: this.resetContextObserver, | ||
updateViewContextObserver: this.updateViewContextObserver, | ||
configurableBehaviour: this.configurableBehaviour, | ||
}); | ||
this.subscription = this.cdAware.subscribe(); | ||
} | ||
|
||
ngOnDestroy(): void { | ||
this.subscription.unsubscribe(); | ||
} | ||
} | ||
|
||
let cdAwareImplementation: CdAwareImplementation<any>; | ||
const setupCdAwareImplementation = () => { | ||
cdAwareImplementation = new CdAwareImplementation(); | ||
cdAwareImplementation.renderedValue = undefined; | ||
cdAwareImplementation.error = undefined; | ||
cdAwareImplementation.completed = false; | ||
}; | ||
|
||
describe('CdAware', () => { | ||
beforeEach(() => { | ||
setupCdAwareImplementation(); | ||
}); | ||
|
||
it('should be implementable', () => { | ||
expect(cdAwareImplementation).toBeDefined(); | ||
}); | ||
|
||
describe('next value', () => { | ||
it('should do nothing if initialized (as no value ever was emitted)', () => { | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
}); | ||
|
||
it('should render undefined as value when initially undefined was passed (as no value ever was emitted)', () => { | ||
cdAwareImplementation.cdAware.next(undefined); | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
}); | ||
|
||
it('should render null as value when initially null was passed (as no value ever was emitted)', () => { | ||
cdAwareImplementation.cdAware.next(null); | ||
expect(cdAwareImplementation.renderedValue).toBe(null); | ||
}); | ||
|
||
it('should render undefined as value when initially of(undefined) was passed (as undefined was emitted)', () => { | ||
cdAwareImplementation.cdAware.next(of(undefined)); | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
}); | ||
|
||
it('should render null as value when initially of(null) was passed (as null was emitted)', () => { | ||
cdAwareImplementation.cdAware.next(of(null)); | ||
expect(cdAwareImplementation.renderedValue).toBe(null); | ||
}); | ||
|
||
it('should render undefined as value when initially EMPTY was passed (as no value ever was emitted)', () => { | ||
cdAwareImplementation.cdAware.next(EMPTY); | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
}); | ||
|
||
it('should render undefined as value when initially NEVER was passed (as no value ever was emitted)', () => { | ||
cdAwareImplementation.cdAware.next(NEVER); | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
}); | ||
// Also: 'should keep last emitted value in the view until a new observable NEVER was passed (as no value ever was emitted from new observable)' | ||
it('should render emitted value from passed observable without changing it', () => { | ||
cdAwareImplementation.cdAware.next(of(42)); | ||
expect(cdAwareImplementation.renderedValue).toBe(42); | ||
}); | ||
|
||
it('should render undefined as value when a new observable NEVER was passed (as no value ever was emitted from new observable)', () => { | ||
cdAwareImplementation.cdAware.next(of(42)); | ||
expect(cdAwareImplementation.renderedValue).toBe(42); | ||
cdAwareImplementation.cdAware.next(NEVER); | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
}); | ||
}); | ||
|
||
describe('observable context', () => { | ||
it('next handling running observable', () => { | ||
cdAwareImplementation.cdAware.next(concat(of(42), NEVER)); | ||
expect(cdAwareImplementation.renderedValue).toBe(42); | ||
expect(cdAwareImplementation.error).toBe(undefined); | ||
expect(cdAwareImplementation.completed).toBe(false); | ||
}); | ||
|
||
it('next handling completed observable', () => { | ||
cdAwareImplementation.cdAware.next(of(42)); | ||
expect(cdAwareImplementation.renderedValue).toBe(42); | ||
expect(cdAwareImplementation.error).toBe(undefined); | ||
expect(cdAwareImplementation.completed).toBe(true); | ||
}); | ||
|
||
it('error handling', () => { | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
cdAwareImplementation.cdAware.subscribe({ | ||
error: (e: Error) => expect(e).toBeDefined(), | ||
}); | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
// @TODO use this line | ||
// expect(cdAwareImplementation.error).toBe(ArgumentNotObservableError); | ||
expect(cdAwareImplementation.completed).toBe(false); | ||
}); | ||
|
||
it('completion handling', () => { | ||
cdAwareImplementation.cdAware.next(EMPTY); | ||
expect(cdAwareImplementation.renderedValue).toBe(undefined); | ||
expect(cdAwareImplementation.error).toBe(undefined); | ||
expect(cdAwareImplementation.completed).toBe(true); | ||
}); | ||
}); | ||
}); |
37 changes: 37 additions & 0 deletions
37
modules/component/spec/core/projections/toObservableValue.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { EMPTY, isObservable, Observable, of } from 'rxjs'; | ||
import { toObservableValue } from '../../../src/core/projections'; | ||
|
||
describe('toObservableValue', () => { | ||
describe('used as RxJS creation function', () => { | ||
it('should take observables', () => { | ||
const observable: Observable<any> = toObservableValue(EMPTY); | ||
expect(isObservable(observable)).toBe(true); | ||
}); | ||
|
||
it('should take a promise', () => { | ||
const observable: Observable<any> = toObservableValue( | ||
new Promise(() => {}) | ||
); | ||
expect(isObservable(observable)).toBe(true); | ||
}); | ||
|
||
it('should take undefined', () => { | ||
const observable: Observable<any> = toObservableValue(undefined); | ||
expect(isObservable(observable)).toBe(true); | ||
}); | ||
|
||
it('should take a null', () => { | ||
const observable: Observable<any> = toObservableValue(null); | ||
expect(isObservable(observable)).toBe(true); | ||
}); | ||
|
||
it('throw if no observable, promise, undefined or null is passed', () => { | ||
const observable: Observable<any> = toObservableValue(null); | ||
observable.subscribe({ | ||
error(e) { | ||
expect(e).toBeDefined(); | ||
}, | ||
}); | ||
}); | ||
}); | ||
}); |
67 changes: 67 additions & 0 deletions
67
modules/component/spec/core/utils/get-change-detection-handling.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import { | ||
getChangeDetectionHandler, | ||
getGlobalThis, | ||
} from '../../../src/core/utils'; | ||
import { Injector } from '@angular/core'; | ||
|
||
class NgZone {} | ||
class NoopNgZone {} | ||
class ChangeDetectorRef { | ||
public markForCheck(): void {} | ||
public detectChanges(): void {} | ||
} | ||
|
||
let noopNgZone: any; | ||
let ngZone: any; | ||
let changeDetectorRef: any; | ||
|
||
beforeAll(() => { | ||
const injector = Injector.create([ | ||
{ provide: NgZone, useClass: NgZone, deps: [] }, | ||
{ provide: NoopNgZone, useClass: NoopNgZone, deps: [] }, | ||
{ provide: ChangeDetectorRef, useClass: ChangeDetectorRef, deps: [] }, | ||
]); | ||
noopNgZone = injector.get(NoopNgZone) as NgZone; | ||
ngZone = injector.get(NgZone); | ||
changeDetectorRef = injector.get(ChangeDetectorRef); | ||
}); | ||
|
||
describe('getChangeDetectionHandler', () => { | ||
describe('in ViewEngine', () => { | ||
beforeAll(() => { | ||
getGlobalThis().ng = { probe: true }; | ||
}); | ||
|
||
it('should return markForCheck in zone-full mode', () => { | ||
const markForCheckSpy = jasmine.createSpy('markForCheck'); | ||
changeDetectorRef.markForCheck = markForCheckSpy; | ||
getChangeDetectionHandler(ngZone, changeDetectorRef)(); | ||
expect(markForCheckSpy).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should return detectChanges in zone-less mode', () => { | ||
const detectChangesSpy = jasmine.createSpy('detectChanges'); | ||
changeDetectorRef.detectChanges = detectChangesSpy; | ||
getChangeDetectionHandler(noopNgZone, changeDetectorRef)(); | ||
expect(detectChangesSpy).toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
describe('in Ivy', () => { | ||
beforeEach(() => { | ||
getGlobalThis().ng = undefined; | ||
}); | ||
|
||
it('should return markDirty in zone-full mode', () => { | ||
expect(getChangeDetectionHandler(ngZone, changeDetectorRef).name).toBe( | ||
'markDirty' | ||
); | ||
}); | ||
|
||
it('should return detectChanges in zone-less mode', () => { | ||
expect( | ||
getChangeDetectionHandler(noopNgZone, changeDetectorRef).name | ||
).toBe('detectChanges'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { getGlobalThis } from '../../../src/core'; | ||
|
||
describe('getGlobalThis', () => { | ||
it('should return global this', () => { | ||
getGlobalThis().prop = 42; | ||
const globalThis = getGlobalThis(); | ||
|
||
expect(globalThis).toBeDefined(); | ||
expect(globalThis.prop).toBe(42); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { hasZone } from '../../../src/core/utils'; | ||
import { NgZone } from '@angular/core'; | ||
|
||
class NoopNgZone {} | ||
|
||
describe('isZoneLess', () => { | ||
it('should return false if something else than noop zone is passed', () => { | ||
expect(!hasZone({} as NgZone)).toBe(false); | ||
}); | ||
|
||
it('should return true if a noop zone is passed', () => { | ||
expect(!hasZone(new NoopNgZone() as NgZone)).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { getGlobalThis, isIvy } from '../../../src/core'; | ||
|
||
describe('isIvy', () => { | ||
describe('in ViewEngine Angular 8 + 9', () => { | ||
it('should return false if ng is defined with probe', () => { | ||
getGlobalThis().ng = { probe: true }; | ||
expect(isIvy()).toBe(false); | ||
}); | ||
}); | ||
describe('in Ivy Angular 9', () => { | ||
it('should return true if ng is undefined', () => { | ||
getGlobalThis().ng = undefined; | ||
expect(isIvy()).toBe(true); | ||
}); | ||
|
||
it('should return true if ng.probe is set', () => { | ||
getGlobalThis().ng = { probe: undefined }; | ||
expect(isIvy()).toBe(true); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.