From ba54671993cf86923f1e87f58a5900cd6bf64064 Mon Sep 17 00:00:00 2001 From: Sonu Kapoor Date: Mon, 2 Dec 2019 13:15:58 -0500 Subject: [PATCH] test(docs-infra): add unit tests for rxjs examples (#34190) This commit adds missing unit tests for all rxjs examples from the docs. Closes #28017 PR Close #34190 --- .../observables-in-angular/src/main.ts | 1 + .../examples/observables/example-config.json | 6 +- .../examples/observables/src/creating.spec.ts | 55 ++++ .../examples/observables/src/creating.ts | 71 ++--- .../examples/observables/src/geolocation.ts | 2 +- .../observables/src/multicasting.spec.ts | 48 +++ .../examples/observables/src/multicasting.ts | 277 +++++++++--------- .../observables/src/subscribing.spec.ts | 19 ++ .../examples/observables/src/subscribing.ts | 53 ++-- .../example-config.json | 6 +- .../practical-observable-usage/src/backoff.ts | 2 +- 11 files changed, 341 insertions(+), 199 deletions(-) create mode 100644 aio/content/examples/observables/src/creating.spec.ts create mode 100644 aio/content/examples/observables/src/multicasting.spec.ts create mode 100644 aio/content/examples/observables/src/subscribing.spec.ts diff --git a/aio/content/examples/observables-in-angular/src/main.ts b/aio/content/examples/observables-in-angular/src/main.ts index 0eaca6bc62..3c846a598f 100644 --- a/aio/content/examples/observables-in-angular/src/main.ts +++ b/aio/content/examples/observables-in-angular/src/main.ts @@ -1,3 +1,4 @@ +// TODO: Add unit tests for this file. // tslint:disable: no-output-native // #docregion import { Component, Output, OnInit, EventEmitter, NgModule } from '@angular/core'; diff --git a/aio/content/examples/observables/example-config.json b/aio/content/examples/observables/example-config.json index c07fa9794c..3aa2059b4d 100644 --- a/aio/content/examples/observables/example-config.json +++ b/aio/content/examples/observables/example-config.json @@ -2,7 +2,11 @@ "tests": [ { "cmd": "yarn", - "args": [ "tsc", "--project", "./tsconfig.app.json" ] + "args": ["tsc", "--project", "tsconfig.spec.json", "--module", "commonjs"] + }, + { + "cmd": "yarn", + "args": ["jasmine", "out-tsc/**/*.spec.js"] } ] } diff --git a/aio/content/examples/observables/src/creating.spec.ts b/aio/content/examples/observables/src/creating.spec.ts new file mode 100644 index 0000000000..e1718a9585 --- /dev/null +++ b/aio/content/examples/observables/src/creating.spec.ts @@ -0,0 +1,55 @@ +import { docRegionFromEvent, docRegionSubscriber } from './creating'; + +describe('observables', () => { + it('should create an observable using the constructor', () => { + const console = {log: jasmine.createSpy('log')}; + docRegionSubscriber(console); + expect(console.log).toHaveBeenCalledTimes(4); + expect(console.log.calls.allArgs()).toEqual([ + [1], + [2], + [3], + ['Finished sequence'], + ]); + }); + + it('should listen to input changes', () => { + let triggerInputChange; + const input = { + value: 'Test', + addEventListener: jasmine + .createSpy('addEvent') + .and.callFake((eventName: string, cb: (e) => void) => { + if (eventName === 'keydown') { + triggerInputChange = cb; + } + }), + removeEventListener: jasmine.createSpy('removeEventListener'), + }; + + const document = { getElementById: () => input }; + docRegionFromEvent(document); + triggerInputChange({keyCode: 65}); + expect(input.value).toBe('Test'); + + triggerInputChange({keyCode: 27}); + expect(input.value).toBe(''); + }); + + it('should call removeEventListener when unsubscribing', (doneFn: DoneFn) => { + const input = { + addEventListener: jasmine.createSpy('addEvent'), + removeEventListener: jasmine + .createSpy('removeEvent') + .and.callFake((eventName: string, cb: (e) => void) => { + if (eventName === 'keydown') { + doneFn(); + } + }) + }; + + const document = { getElementById: () => input }; + const subscription = docRegionFromEvent(document); + subscription.unsubscribe(); + }); +}); diff --git a/aio/content/examples/observables/src/creating.ts b/aio/content/examples/observables/src/creating.ts index 83003b99bf..7a673228cc 100644 --- a/aio/content/examples/observables/src/creating.ts +++ b/aio/content/examples/observables/src/creating.ts @@ -1,38 +1,39 @@ +// #docplaster import { Observable } from 'rxjs'; -// #docregion subscriber +export function docRegionSubscriber(console) { + // #docregion subscriber + // This function runs when subscribe() is called + function sequenceSubscriber(observer) { + // synchronously deliver 1, 2, and 3, then complete + observer.next(1); + observer.next(2); + observer.next(3); + observer.complete(); -// This function runs when subscribe() is called -function sequenceSubscriber(observer) { - // synchronously deliver 1, 2, and 3, then complete - observer.next(1); - observer.next(2); - observer.next(3); - observer.complete(); + // unsubscribe function doesn't need to do anything in this + // because values are delivered synchronously + return {unsubscribe() {}}; + } - // unsubscribe function doesn't need to do anything in this - // because values are delivered synchronously - return {unsubscribe() {}}; + // Create a new Observable that will deliver the above sequence + const sequence = new Observable(sequenceSubscriber); + + // execute the Observable and print the result of each notification + sequence.subscribe({ + next(num) { console.log(num); }, + complete() { console.log('Finished sequence'); } + }); + + // Logs: + // 1 + // 2 + // 3 + // Finished sequence + // #enddocregion subscriber } -// Create a new Observable that will deliver the above sequence -const sequence = new Observable(sequenceSubscriber); - -// execute the Observable and print the result of each notification -sequence.subscribe({ - next(num) { console.log(num); }, - complete() { console.log('Finished sequence'); } -}); - -// Logs: -// 1 -// 2 -// 3 -// Finished sequence - -// #enddocregion subscriber - // #docregion fromevent function fromEvent(target, eventName) { @@ -51,16 +52,18 @@ function fromEvent(target, eventName) { // #enddocregion fromevent -// #docregion fromevent_use +export function docRegionFromEvent(document) { + // #docregion fromevent_use -const ESC_KEY = 27; -const nameInput = document.getElementById('name') as HTMLInputElement; + const ESC_KEY = 27; + const nameInput = document.getElementById('name') as HTMLInputElement; -const subscription = fromEvent(nameInput, 'keydown') - .subscribe((e: KeyboardEvent) => { + const subscription = fromEvent(nameInput, 'keydown').subscribe((e: KeyboardEvent) => { if (e.keyCode === ESC_KEY) { nameInput.value = ''; } }); + // #enddocregion fromevent_use + return subscription; +} -// #enddocregion fromevent_use diff --git a/aio/content/examples/observables/src/geolocation.ts b/aio/content/examples/observables/src/geolocation.ts index 841fea556e..e1f0a2e40f 100644 --- a/aio/content/examples/observables/src/geolocation.ts +++ b/aio/content/examples/observables/src/geolocation.ts @@ -1,5 +1,5 @@ +// TODO: Add unit tests for this file. import { Observable } from 'rxjs'; - // #docregion // Create an Observable that will start listening to geolocation updates diff --git a/aio/content/examples/observables/src/multicasting.spec.ts b/aio/content/examples/observables/src/multicasting.spec.ts new file mode 100644 index 0000000000..90bab96151 --- /dev/null +++ b/aio/content/examples/observables/src/multicasting.spec.ts @@ -0,0 +1,48 @@ +import { docRegionDelaySequence, docRegionMulticastSequence } from './multicasting'; + +describe('multicasting', () => { + let console; + beforeEach(() => { + jasmine.clock().install(); + console = {log: jasmine.createSpy('log')}; + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('should create an observable and emit in sequence', () => { + docRegionDelaySequence(console); + jasmine.clock().tick(10000); + expect(console.log).toHaveBeenCalledTimes(12); + expect(console.log.calls.allArgs()).toEqual([ + [1], + ['1st subscribe: 1'], + ['2nd subscribe: 1'], + [2], + ['1st subscribe: 2'], + ['2nd subscribe: 2'], + [3], + ['Finished sequence'], + ['1st subscribe: 3'], + ['1st sequence finished.'], + ['2nd subscribe: 3'], + ['2nd sequence finished.'] + ]); + }); + + it('should create an observable and multicast the emissions', () => { + docRegionMulticastSequence(console); + jasmine.clock().tick(10000); + expect(console.log).toHaveBeenCalledTimes(7); + expect(console.log.calls.allArgs()).toEqual([ + ['1st subscribe: 1'], + ['1st subscribe: 2'], + ['2nd subscribe: 2'], + ['1st subscribe: 3'], + ['2nd subscribe: 3'], + ['1st sequence finished.'], + ['2nd sequence finished.'] + ]); + }); +}); diff --git a/aio/content/examples/observables/src/multicasting.ts b/aio/content/examples/observables/src/multicasting.ts index 2d622e41ff..fe821edd2a 100644 --- a/aio/content/examples/observables/src/multicasting.ts +++ b/aio/content/examples/observables/src/multicasting.ts @@ -1,155 +1,160 @@ +// #docplaster import { Observable } from 'rxjs'; -// #docregion delay_sequence +export function docRegionDelaySequence(console) { + // #docregion delay_sequence + function sequenceSubscriber(observer) { + const seq = [1, 2, 3]; + let timeoutId; -function sequenceSubscriber(observer) { - const seq = [1, 2, 3]; - let timeoutId; + // Will run through an array of numbers, emitting one value + // per second until it gets to the end of the array. + function doInSequence(arr, idx) { + timeoutId = setTimeout(() => { + observer.next(arr[idx]); + if (idx === arr.length - 1) { + observer.complete(); + } else { + doInSequence(arr, ++idx); + } + }, 1000); + } - // Will run through an array of numbers, emitting one value + doInSequence(seq, 0); + + // Unsubscribe should clear the timeout to stop execution + return { + unsubscribe() { + clearTimeout(timeoutId); + } + }; + } + + // Create a new Observable that will deliver the above sequence + const sequence = new Observable(sequenceSubscriber); + + sequence.subscribe({ + next(num) { console.log(num); }, + complete() { console.log('Finished sequence'); } + }); + + // Logs: + // (at 1 second): 1 + // (at 2 seconds): 2 + // (at 3 seconds): 3 + // (at 3 seconds): Finished sequence + + // #enddocregion delay_sequence + + // #docregion subscribe_twice + + // Subscribe starts the clock, and will emit after 1 second + sequence.subscribe({ + next(num) { console.log('1st subscribe: ' + num); }, + complete() { console.log('1st sequence finished.'); } + }); + + // After 1/2 second, subscribe again. + setTimeout(() => { + sequence.subscribe({ + next(num) { console.log('2nd subscribe: ' + num); }, + complete() { console.log('2nd sequence finished.'); } + }); + }, 500); + + // Logs: + // (at 1 second): 1st subscribe: 1 + // (at 1.5 seconds): 2nd subscribe: 1 + // (at 2 seconds): 1st subscribe: 2 + // (at 2.5 seconds): 2nd subscribe: 2 + // (at 3 seconds): 1st subscribe: 3 + // (at 3 seconds): 1st sequence finished + // (at 3.5 seconds): 2nd subscribe: 3 + // (at 3.5 seconds): 2nd sequence finished + + // #enddocregion subscribe_twice +} + +export function docRegionMulticastSequence(console) { + // #docregion multicast_sequence + function multicastSequenceSubscriber() { + const seq = [1, 2, 3]; + // Keep track of each observer (one for every active subscription) + const observers = []; + // Still a single timeoutId because there will only ever be one + // set of values being generated, multicasted to each subscriber + let timeoutId; + + // Return the subscriber function (runs when subscribe() + // function is invoked) + return observer => { + observers.push(observer); + // When this is the first subscription, start the sequence + if (observers.length === 1) { + timeoutId = doSequence({ + next(val) { + // Iterate through observers and notify all subscriptions + observers.forEach(obs => obs.next(val)); + }, + complete() { + // Notify all complete callbacks + observers.slice(0).forEach(obs => obs.complete()); + } + }, seq, 0); + } + + return { + unsubscribe() { + // Remove from the observers array so it's no longer notified + observers.splice(observers.indexOf(observer), 1); + // If there's no more listeners, do cleanup + if (observers.length === 0) { + clearTimeout(timeoutId); + } + } + }; + }; + } + + // Run through an array of numbers, emitting one value // per second until it gets to the end of the array. - function doInSequence(arr, idx) { - timeoutId = setTimeout(() => { + function doSequence(observer, arr, idx) { + return setTimeout(() => { observer.next(arr[idx]); if (idx === arr.length - 1) { observer.complete(); } else { - doInSequence(arr, ++idx); + doSequence(observer, arr, ++idx); } }, 1000); } - doInSequence(seq, 0); + // Create a new Observable that will deliver the above sequence + const multicastSequence = new Observable(multicastSequenceSubscriber()); - // Unsubscribe should clear the timeout to stop execution - return {unsubscribe() { - clearTimeout(timeoutId); - }}; -} - -// Create a new Observable that will deliver the above sequence -const sequence = new Observable(sequenceSubscriber); - -sequence.subscribe({ - next(num) { console.log(num); }, - complete() { console.log('Finished sequence'); } -}); - -// Logs: -// (at 1 second): 1 -// (at 2 seconds): 2 -// (at 3 seconds): 3 -// (at 3 seconds): Finished sequence - -// #enddocregion delay_sequence - -// #docregion subscribe_twice - -// Subscribe starts the clock, and will emit after 1 second -sequence.subscribe({ - next(num) { console.log('1st subscribe: ' + num); }, - complete() { console.log('1st sequence finished.'); } -}); - -// After 1/2 second, subscribe again. -setTimeout(() => { - sequence.subscribe({ - next(num) { console.log('2nd subscribe: ' + num); }, - complete() { console.log('2nd sequence finished.'); } - }); -}, 500); - -// Logs: -// (at 1 second): 1st subscribe: 1 -// (at 1.5 seconds): 2nd subscribe: 1 -// (at 2 seconds): 1st subscribe: 2 -// (at 2.5 seconds): 2nd subscribe: 2 -// (at 3 seconds): 1st subscribe: 3 -// (at 3 seconds): 1st sequence finished -// (at 3.5 seconds): 2nd subscribe: 3 -// (at 3.5 seconds): 2nd sequence finished - -// #enddocregion subscribe_twice - -// #docregion multicast_sequence - -function multicastSequenceSubscriber() { - const seq = [1, 2, 3]; - // Keep track of each observer (one for every active subscription) - const observers = []; - // Still a single timeoutId because there will only ever be one - // set of values being generated, multicasted to each subscriber - let timeoutId; - - // Return the subscriber function (runs when subscribe() - // function is invoked) - return (observer) => { - observers.push(observer); - // When this is the first subscription, start the sequence - if (observers.length === 1) { - timeoutId = doSequence({ - next(val) { - // Iterate through observers and notify all subscriptions - observers.forEach(obs => obs.next(val)); - }, - complete() { - // Notify all complete callbacks - observers.slice(0).forEach(obs => obs.complete()); - } - }, seq, 0); - } - - return { - unsubscribe() { - // Remove from the observers array so it's no longer notified - observers.splice(observers.indexOf(observer), 1); - // If there's no more listeners, do cleanup - if (observers.length === 0) { - clearTimeout(timeoutId); - } - } - }; - }; -} - -// Run through an array of numbers, emitting one value -// per second until it gets to the end of the array. -function doSequence(observer, arr, idx) { - return setTimeout(() => { - observer.next(arr[idx]); - if (idx === arr.length - 1) { - observer.complete(); - } else { - doSequence(observer, arr, ++idx); - } - }, 1000); -} - -// Create a new Observable that will deliver the above sequence -const multicastSequence = new Observable(multicastSequenceSubscriber()); - -// Subscribe starts the clock, and begins to emit after 1 second -multicastSequence.subscribe({ - next(num) { console.log('1st subscribe: ' + num); }, - complete() { console.log('1st sequence finished.'); } -}); - -// After 1 1/2 seconds, subscribe again (should "miss" the first value). -setTimeout(() => { + // Subscribe starts the clock, and begins to emit after 1 second multicastSequence.subscribe({ - next(num) { console.log('2nd subscribe: ' + num); }, - complete() { console.log('2nd sequence finished.'); } + next(num) { console.log('1st subscribe: ' + num); }, + complete() { console.log('1st sequence finished.'); } }); -}, 1500); -// Logs: -// (at 1 second): 1st subscribe: 1 -// (at 2 seconds): 1st subscribe: 2 -// (at 2 seconds): 2nd subscribe: 2 -// (at 3 seconds): 1st subscribe: 3 -// (at 3 seconds): 1st sequence finished -// (at 3 seconds): 2nd subscribe: 3 -// (at 3 seconds): 2nd sequence finished + // After 1 1/2 seconds, subscribe again (should "miss" the first value). + setTimeout(() => { + multicastSequence.subscribe({ + next(num) { console.log('2nd subscribe: ' + num); }, + complete() { console.log('2nd sequence finished.'); } + }); + }, 1500); -// #enddocregion multicast_sequence + // Logs: + // (at 1 second): 1st subscribe: 1 + // (at 2 seconds): 1st subscribe: 2 + // (at 2 seconds): 2nd subscribe: 2 + // (at 3 seconds): 1st subscribe: 3 + // (at 3 seconds): 1st sequence finished + // (at 3 seconds): 2nd subscribe: 3 + // (at 3 seconds): 2nd sequence finished + + // #enddocregion multicast_sequence +} diff --git a/aio/content/examples/observables/src/subscribing.spec.ts b/aio/content/examples/observables/src/subscribing.spec.ts new file mode 100644 index 0000000000..43d0307e5d --- /dev/null +++ b/aio/content/examples/observables/src/subscribing.spec.ts @@ -0,0 +1,19 @@ +import { docRegionObserver } from './subscribing'; + +describe('subscribing', () => { + it('should subscribe and emit', () => { + const console = {log: jasmine.createSpy('log')}; + docRegionObserver(console); + expect(console.log).toHaveBeenCalledTimes(8); + expect(console.log.calls.allArgs()).toEqual([ + ['Observer got a next value: 1'], + ['Observer got a next value: 2'], + ['Observer got a next value: 3'], + ['Observer got a complete notification'], + ['Observer got a next value: 1'], + ['Observer got a next value: 2'], + ['Observer got a next value: 3'], + ['Observer got a complete notification'], + ]); + }); +}); diff --git a/aio/content/examples/observables/src/subscribing.ts b/aio/content/examples/observables/src/subscribing.ts index 06a21575d1..47677b5d9e 100644 --- a/aio/content/examples/observables/src/subscribing.ts +++ b/aio/content/examples/observables/src/subscribing.ts @@ -1,32 +1,35 @@ +// #docplaster +import { of } from 'rxjs'; -import { Observable, of } from 'rxjs'; +export function docRegionObserver(console) { + // #docregion observer -// #docregion observer + // Create simple observable that emits three values + const myObservable = of(1, 2, 3); -// Create simple observable that emits three values -const myObservable = of(1, 2, 3); + // Create observer object + const myObserver = { + next: x => console.log('Observer got a next value: ' + x), + error: err => console.error('Observer got an error: ' + err), + complete: () => console.log('Observer got a complete notification'), + }; -// Create observer object -const myObserver = { - next: x => console.log('Observer got a next value: ' + x), - error: err => console.error('Observer got an error: ' + err), - complete: () => console.log('Observer got a complete notification'), -}; + // Execute with the observer object + myObservable.subscribe(myObserver); -// Execute with the observer object -myObservable.subscribe(myObserver); -// Logs: -// Observer got a next value: 1 -// Observer got a next value: 2 -// Observer got a next value: 3 -// Observer got a complete notification + // Logs: + // Observer got a next value: 1 + // Observer got a next value: 2 + // Observer got a next value: 3 + // Observer got a complete notification -// #enddocregion observer + // #enddocregion observer -// #docregion sub_fn -myObservable.subscribe( - x => console.log('Observer got a next value: ' + x), - err => console.error('Observer got an error: ' + err), - () => console.log('Observer got a complete notification') -); -// #enddocregion sub_fn + // #docregion sub_fn + myObservable.subscribe( + x => console.log('Observer got a next value: ' + x), + err => console.error('Observer got an error: ' + err), + () => console.log('Observer got a complete notification') + ); + // #enddocregion sub_fn +} diff --git a/aio/content/examples/practical-observable-usage/example-config.json b/aio/content/examples/practical-observable-usage/example-config.json index c07fa9794c..3aa2059b4d 100644 --- a/aio/content/examples/practical-observable-usage/example-config.json +++ b/aio/content/examples/practical-observable-usage/example-config.json @@ -2,7 +2,11 @@ "tests": [ { "cmd": "yarn", - "args": [ "tsc", "--project", "./tsconfig.app.json" ] + "args": ["tsc", "--project", "tsconfig.spec.json", "--module", "commonjs"] + }, + { + "cmd": "yarn", + "args": ["jasmine", "out-tsc/**/*.spec.js"] } ] } diff --git a/aio/content/examples/practical-observable-usage/src/backoff.ts b/aio/content/examples/practical-observable-usage/src/backoff.ts index e9e88e7cb2..70490b917d 100644 --- a/aio/content/examples/practical-observable-usage/src/backoff.ts +++ b/aio/content/examples/practical-observable-usage/src/backoff.ts @@ -1,4 +1,4 @@ - +// TODO: Add unit tests for this file. import { pipe, range, timer, zip } from 'rxjs'; import { ajax } from 'rxjs/ajax'; import { retryWhen, map, mergeMap } from 'rxjs/operators';