/** * @license * Copyright Google Inc. All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {Observable, Subject} from 'rxjs'; import {map} from 'rxjs/operators'; /** * The point of these tests, is to ensure that all callbacks execute in the Zone which was active * when the callback was passed into the Rx. * * The implications are: * - Observable callback passed into `Observable` executes in the same Zone as when the * `new Observable` was invoked. * - The subscription callbacks passed into `subscribe` execute in the same Zone as when the * `subscribe` method was invoked. * - The operator callbacks passe into `map`, etc..., execute in the same Zone as when the * `operator` (`lift`) method was invoked. */ describe('Zone interaction', () => { it('should run methods in the zone of declaration', () => { const log: any[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); let subscriber: any = null; const observable: any = constructorZone.run(() => new Observable((_subscriber: any) => { subscriber = _subscriber; log.push('setup'); expect(Zone.current.name).toEqual(constructorZone.name); return () => { expect(Zone.current.name).toEqual(constructorZone.name); log.push('cleanup'); }; })); subscriptionZone.run( () => observable.subscribe( () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('next'); }, (): any => null, () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('complete'); })); subscriber.next('MyValue'); subscriber.complete(); expect(log).toEqual(['setup', 'next', 'complete', 'cleanup']); log.length = 0; subscriptionZone.run(() => observable.subscribe((): any => null, () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('error'); }, (): any => null)); subscriber.next('MyValue'); subscriber.error('MyError'); expect(log).toEqual(['setup', 'error', 'cleanup']); }); it('should run methods in the zone of declaration when nexting synchronously', () => { const log: any[] = []; const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); const observable: any = constructorZone.run(() => new Observable((subscriber: any) => { // Execute the `next`/`complete` in different zone, and assert that // correct zone // is restored. rootZone.run(() => { subscriber.next('MyValue'); subscriber.complete(); }); return () => { expect(Zone.current.name).toEqual(constructorZone.name); log.push('cleanup'); }; })); subscriptionZone.run( () => observable.subscribe( () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('next'); }, (): any => null, () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('complete'); })); expect(log).toEqual(['next', 'complete', 'cleanup']); }); it('should run operators in the zone of declaration', () => { const log: any[] = []; const rootZone: Zone = Zone.current; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const operatorZone: Zone = Zone.current.fork({name: 'Operator Zone'}); const subscriptionZone: Zone = Zone.current.fork({name: 'Subscription Zone'}); let observable: any = constructorZone.run(() => new Observable((subscriber: any) => { // Execute the `next`/`complete` in different zone, and assert that // correct zone // is restored. rootZone.run(() => { subscriber.next('MyValue'); subscriber.complete(); }); return () => { expect(Zone.current.name).toEqual(constructorZone.name); log.push('cleanup'); }; })); observable = operatorZone.run(() => observable.pipe(map((value: any) => { expect(Zone.current.name).toEqual(operatorZone.name); log.push('map: ' + value); return value; }))); subscriptionZone.run( () => observable.subscribe( () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('next'); }, (e: any) => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('error: ' + e); }, () => { expect(Zone.current.name).toEqual(subscriptionZone.name); log.push('complete'); })); expect(log).toEqual(['map: MyValue', 'next', 'complete', 'cleanup']); }); it('should run subscribe in zone of declaration with Observable.create', () => { const log: any[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); let observable: any = constructorZone.run(() => Observable.create((subscriber: any) => { expect(Zone.current.name).toEqual(constructorZone.name); subscriber.next(1); subscriber.complete(); return () => { expect(Zone.current.name).toEqual(constructorZone.name); log.push('cleanup'); }; })); observable.subscribe(() => { log.push('next'); }); expect(log).toEqual(['next', 'cleanup']); }); it('should run in the zone when subscribe is called to the same Subject', () => { const log: any[] = []; const constructorZone: Zone = Zone.current.fork({name: 'Constructor Zone'}); const subscriptionZone1: Zone = Zone.current.fork({name: 'Subscription Zone 1'}); const subscriptionZone2: Zone = Zone.current.fork({name: 'Subscription Zone 2'}); let subject: any; constructorZone.run(() => { subject = new Subject(); }); let subscription1: any; let subscription2: any; subscriptionZone1.run(() => { subscription1 = subject.subscribe( () => { expect(Zone.current.name).toEqual(subscriptionZone1.name); log.push('next1'); }, () => {}, () => { expect(Zone.current.name).toEqual(subscriptionZone1.name); log.push('complete1'); }); }); subscriptionZone2.run(() => { subscription2 = subject.subscribe( () => { expect(Zone.current.name).toEqual(subscriptionZone2.name); log.push('next2'); }, () => {}, () => { expect(Zone.current.name).toEqual(subscriptionZone2.name); log.push('complete2'); }); }); subject.next(1); subject.complete(); expect(log).toEqual(['next1', 'next2', 'complete1', 'complete2']); }); });