test(docs-infra): add unit tests for rx-library examples (#38905)

This commit adds missing unit tests for all rx-library examples from the docs.

Closes #28017

PR Close #38905
This commit is contained in:
Sonu Kapoor 2020-09-28 17:56:59 +03:00 committed by Alex Rickabaugh
parent 5ef2c983b9
commit fd7146cc71
20 changed files with 473 additions and 158 deletions

View File

@ -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"]
}
]
}

View File

@ -0,0 +1,46 @@
import { Subject, throwError } from 'rxjs';
import { docRegionDefault } from './error-handling';
describe('error-handling', () => {
let mockConsole;
let ajaxSubject;
let ajax;
beforeEach(() => {
mockConsole = {log: jasmine.createSpy('log')};
ajaxSubject = new Subject();
ajax = jasmine
.createSpy('ajax')
.and.callFake((url: string) => ajaxSubject);
});
afterEach(() => ajaxSubject.unsubscribe());
it('should return the response object', () => {
docRegionDefault(mockConsole, ajax);
ajaxSubject.next({response: {foo: 'bar'}});
expect(mockConsole.log.calls.allArgs()).toEqual([
['data: ', {foo: 'bar'}]
]);
});
it('should return an empty array when using an object without a `response` property', () => {
docRegionDefault(mockConsole, ajax);
ajaxSubject.next({foo: 'bar'});
expect(mockConsole.log.calls.allArgs()).toEqual([
['data: ', []]
]);
});
it('should return an empty array when the ajax observable errors', () => {
ajax.and.returnValue(throwError('Test Error'));
docRegionDefault(mockConsole, ajax);
expect(mockConsole.log.calls.allArgs()).toEqual([
['data: ', []]
]);
});
});

View File

@ -1,25 +1,36 @@
import { of } from 'rxjs';
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:no-shadowed-variable */
/* tslint:disable:align */
// #docregion
import { ajax } from 'rxjs/ajax';
import { map, catchError } from 'rxjs/operators';
// Return "response" from the API. If an error happens,
// return an empty array.
const apiData = ajax('/api/data').pipe(
map(res => {
if (!res.response) {
throw new Error('Value expected!');
}
return res.response;
}),
catchError(err => of([]))
);
apiData.subscribe({
next(x) { console.log('data: ', x); },
error(err) { console.log('errors already caught... will not run'); }
});
import { of } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { map, catchError } from 'rxjs/operators';
// #enddocregion
export function docRegionDefault(console, ajax) {
// #docregion
// Return "response" from the API. If an error happens,
// return an empty array.
const apiData = ajax('/api/data').pipe(
map((res: any) => {
if (!res.response) {
throw new Error('Value expected!');
}
return res.response;
}),
catchError(err => of([]))
);
apiData.subscribe({
next(x) { console.log('data: ', x); },
error(err) { console.log('errors already caught... will not run'); }
});
// #enddocregion
return apiData;
}

View File

@ -0,0 +1,14 @@
import { docRegionDefault } from './operators.1';
describe('squareOdd - operators.1.ts', () => {
it('should return square odds', () => {
const console = {log: jasmine.createSpy('log')};
docRegionDefault(console);
expect(console.log).toHaveBeenCalledTimes(3);
expect(console.log.calls.allArgs()).toEqual([
[1],
[9],
[25],
]);
});
});

View File

@ -1,23 +1,30 @@
import { of, pipe } from 'rxjs';
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:align */
// #docregion
import { filter, map } from 'rxjs/operators';
const nums = of(1, 2, 3, 4, 5);
// Create a function that accepts an Observable.
const squareOddVals = pipe(
filter((n: number) => n % 2 !== 0),
map(n => n * n)
);
// Create an Observable that will run the filter and map functions
const squareOdd = squareOddVals(nums);
// Subscribe to run the combined functions
squareOdd.subscribe(x => console.log(x));
import { of, pipe } from 'rxjs';
import { filter, map } from 'rxjs/operators';
// #enddocregion
export function docRegionDefault(console) {
// #docregion
const nums = of(1, 2, 3, 4, 5);
// Create a function that accepts an Observable.
const squareOddVals = pipe(
filter((n: number) => n % 2 !== 0),
map(n => n * n)
);
// Create an Observable that will run the filter and map functions
const squareOdd = squareOddVals(nums);
// Subscribe to run the combined functions
squareOdd.subscribe(x => console.log(x));
// #enddocregion
}

View File

@ -0,0 +1,14 @@
import { docRegionDefault } from './operators.2';
describe('squareOdd - operators.2.ts', () => {
it('should return square odds', () => {
const console = {log: jasmine.createSpy('log')};
docRegionDefault(console);
expect(console.log).toHaveBeenCalledTimes(3);
expect(console.log.calls.allArgs()).toEqual([
[1],
[9],
[25],
]);
});
});

View File

@ -1,16 +1,25 @@
import { Observable, of } from 'rxjs';
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:align */
// #docregion
import { filter, map } from 'rxjs/operators';
const squareOdd = of(1, 2, 3, 4, 5)
.pipe(
filter(n => n % 2 !== 0),
map(n => n * n)
);
// Subscribe to get values
squareOdd.subscribe(x => console.log(x));
import { of } from 'rxjs';
import { filter, map } from 'rxjs/operators';
// #enddocregion
export function docRegionDefault(console) {
// #docregion
const squareOdd = of(1, 2, 3, 4, 5)
.pipe(
filter(n => n % 2 !== 0),
map(n => n * n)
);
// Subscribe to get values
squareOdd.subscribe(x => console.log(x));
// #enddocregion
}

View File

@ -0,0 +1,14 @@
import { docRegionDefault } from './operators';
describe('squaredNums - operators.ts', () => {
it('should return square odds', () => {
const console = {log: jasmine.createSpy('log')};
docRegionDefault(console);
expect(console.log).toHaveBeenCalledTimes(3);
expect(console.log.calls.allArgs()).toEqual([
[1],
[4],
[9],
]);
});
});

View File

@ -1,20 +1,28 @@
import { Observable, of } from 'rxjs';
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:align */
// #docregion
import { map } from 'rxjs/operators';
const nums = of(1, 2, 3);
const squareValues = map((val: number) => val * val);
const squaredNums = squareValues(nums);
squaredNums.subscribe(x => console.log(x));
// Logs
// 1
// 4
// 9
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
// #enddocregion
export function docRegionDefault(console) {
// #docregion
const nums = of(1, 2, 3);
const squareValues = map((val: number) => val * val);
const squaredNums = squareValues(nums);
squaredNums.subscribe(x => console.log(x));
// Logs
// 1
// 4
// 9
// #enddocregion
}

View File

@ -0,0 +1,45 @@
import { of, throwError } from 'rxjs';
import { tap } from 'rxjs/operators';
import { docRegionDefault } from './retry-on-error';
describe('retry-on-error', () => {
let mockConsole;
beforeEach(() => mockConsole = { log: jasmine.createSpy('log') });
it('should return the response object', () => {
const ajax = () => of({ response: { foo: 'bar' } });
docRegionDefault(mockConsole, ajax);
expect(mockConsole.log.calls.allArgs()).toEqual([
['data: ', { foo: 'bar' }],
]);
});
it('should return an empty array after 3 retries + 1 initial request', () => {
const ajax = () => {
return of({ noresponse: true }).pipe(tap(() => mockConsole.log('Subscribed to AJAX')));
};
docRegionDefault(mockConsole, ajax);
expect(mockConsole.log.calls.allArgs()).toEqual([
['Subscribed to AJAX'],
['Error occured.'],
['Subscribed to AJAX'],
['Error occured.'],
['Subscribed to AJAX'],
['Error occured.'],
['Subscribed to AJAX'],
['Error occured.'],
['data: ', []],
]);
});
it('should return an empty array when the ajax observable throws an error', () => {
const ajax = () => throwError('Test Error');
docRegionDefault(mockConsole, ajax);
expect(mockConsole.log.calls.allArgs()).toEqual([
['data: ', []],
]);
});
});

View File

@ -1,26 +1,35 @@
import { Observable, of } from 'rxjs';
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:no-shadowed-variable */
/* tslint:disable:align */
// #docregion
import { ajax } from 'rxjs/ajax';
import { map, retry, catchError } from 'rxjs/operators';
const apiData = ajax('/api/data').pipe(
map(res => {
if (!res.response) {
throw new Error('Value expected!');
}
return res.response;
}),
retry(3), // Retry up to 3 times before failing
catchError(err => of([]))
);
apiData.subscribe({
next(x) { console.log('data: ', x); },
error(err) { console.log('errors already caught... will not run'); }
});
import { of } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { map, retry, catchError } from 'rxjs/operators';
// #enddocregion
export function docRegionDefault(console, ajax) {
// #docregion
const apiData = ajax('/api/data').pipe(
map((res: any) => {
if (!res.response) {
console.log('Error occured.');
throw new Error('Value expected!');
}
return res.response;
}),
retry(3), // Retry up to 3 times before failing
catchError(err => of([]))
);
apiData.subscribe({
next(x) { console.log('data: ', x); },
error(err) { console.log('errors already caught... will not run'); }
});
// #enddocregion
}

View File

@ -0,0 +1,14 @@
import { of } from 'rxjs';
import { docRegionPromise } from './simple-creation.1';
describe('simple-creation.1', () => {
it('should create a promise from an observable and return an empty object', () => {
const console = {log: jasmine.createSpy('log')};
const fetch = () => of({foo: 42});
docRegionPromise(console, fetch);
expect(console.log.calls.allArgs()).toEqual([
[{foo: 42}],
['Completed'],
]);
});
});

View File

@ -0,0 +1,24 @@
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:align */
// #docregion promise
import { from } from 'rxjs';
// #enddocregion promise
export function docRegionPromise(console, fetch) {
// #docregion promise
// Create an Observable out of a promise
const data = from(fetch('/api/endpoint'));
// Subscribe to begin listening for async result
data.subscribe({
next(response) { console.log(response); },
error(err) { console.error('Error: ' + err); },
complete() { console.log('Completed'); }
});
// #enddocregion promise
}

View File

@ -0,0 +1,21 @@
import { docRegionInterval } from './simple-creation.2';
describe('simple-creation.2', () => {
beforeEach(() => jasmine.clock().install());
afterEach(() => jasmine.clock().uninstall());
it('should create an Observable that will publish a value on an interval', () => {
const console = {log: jasmine.createSpy('log')};
const subscription = docRegionInterval(console);
jasmine.clock().tick(1000);
expect(console.log).toHaveBeenCalledWith('It\'s been 1 seconds since subscribing!');
console.log.calls.reset();
jasmine.clock().tick(999);
expect(console.log).not.toHaveBeenCalled();
jasmine.clock().tick(1);
expect(console.log).toHaveBeenCalledWith('It\'s been 2 seconds since subscribing!');
subscription.unsubscribe();
});
});

View File

@ -0,0 +1,22 @@
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:align */
// #docregion interval
import { interval } from 'rxjs';
// #enddocregion interval
export function docRegionInterval(console) {
// #docregion interval
// Create an Observable that will publish a value on an interval
const secondsCounter = interval(1000);
// Subscribe to begin publishing values
const subscription = secondsCounter.subscribe(n =>
console.log(`It's been ${n + 1} seconds since subscribing!`));
// #enddocregion interval
return subscription;
}

View File

@ -0,0 +1,53 @@
import { docRegionEvent } from './simple-creation.3';
describe('simple-creation.3', () => {
let triggerMousemove;
let mockConsole;
let input;
let mockDocument;
beforeEach(() => {
mockConsole = {log: jasmine.createSpy('log')};
input = {
addEventListener: jasmine
.createSpy('addEventListener')
.and.callFake((eventName, cb) => {
if (eventName === 'mousemove') {
triggerMousemove = cb;
}
}),
removeEventListener: jasmine.createSpy('removeEventListener'),
};
mockDocument = { getElementById: () => input };
});
it('should log coords when subscribing', () => {
docRegionEvent(mockConsole, mockDocument);
expect(mockConsole.log).not.toHaveBeenCalled();
triggerMousemove({ clientX: 50, clientY: 50 });
triggerMousemove({ clientX: 30, clientY: 50 });
triggerMousemove({ clientX: 50, clientY: 30 });
expect(mockConsole.log).toHaveBeenCalledTimes(3);
expect(mockConsole.log.calls.allArgs()).toEqual([
['Coords: 50 X 50'],
['Coords: 30 X 50'],
['Coords: 50 X 30']
]);
});
it('should call unsubscribe when clientX and clientY are below < 40 ', () => {
docRegionEvent(mockConsole, mockDocument);
expect(mockConsole.log).not.toHaveBeenCalled();
// Ensure that we have unsubscribed.
triggerMousemove({ clientX: 30, clientY: 30 });
expect(input.removeEventListener).toHaveBeenCalledWith('mousemove', triggerMousemove, undefined);
mockConsole.log.calls.reset();
triggerMousemove({ clientX: 50, clientY: 50 });
expect(mockConsole.log).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,32 @@
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:align */
// #docregion event
import { fromEvent } from 'rxjs';
// #enddocregion event
export function docRegionEvent(console, document) {
// #docregion event
const el = document.getElementById('my-element');
// Create an Observable that will publish mouse movements
const mouseMoves = fromEvent(el, 'mousemove');
// Subscribe to start listening for mouse-move events
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
// Log coords of mouse movements
console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
// When the mouse is over the upper-left of the screen,
// unsubscribe to stop listening for mouse movements
if (evt.clientX < 40 && evt.clientY < 40) {
subscription.unsubscribe();
}
});
// #enddocregion event
}

View File

@ -0,0 +1,14 @@
import { of } from 'rxjs';
import { docRegionAjax } from './simple-creation';
describe('ajax', () => {
it('should make a request and console log the status and response', () => {
const console = {log: jasmine.createSpy('log')};
const ajax = jasmine.createSpy('ajax').and.callFake((url: string) => {
return of({status: 200, response: 'foo bar'});
});
docRegionAjax(console, ajax);
expect(console.log).toHaveBeenCalledWith(200, 'foo bar');
});
});

View File

@ -1,65 +1,19 @@
// #docregion promise
import { from } from 'rxjs';
// Create an Observable out of a promise
const data = from(fetch('/api/endpoint'));
// Subscribe to begin listening for async result
data.subscribe({
next(response) { console.log(response); },
error(err) { console.error('Error: ' + err); },
complete() { console.log('Completed'); }
});
// #enddocregion promise
// #docregion interval
import { interval } from 'rxjs';
// Create an Observable that will publish a value on an interval
const secondsCounter = interval(1000);
// Subscribe to begin publishing values
secondsCounter.subscribe(n =>
console.log(`It's been ${n} seconds since subscribing!`));
// #enddocregion interval
// #docregion event
import { fromEvent } from 'rxjs';
const el = document.getElementById('my-element');
// Create an Observable that will publish mouse movements
const mouseMoves = fromEvent(el, 'mousemove');
// Subscribe to start listening for mouse-move events
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
// Log coords of mouse movements
console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
// When the mouse is over the upper-left of the screen,
// unsubscribe to stop listening for mouse movements
if (evt.clientX < 40 && evt.clientY < 40) {
subscription.unsubscribe();
}
});
// #enddocregion event
// #docplaster
/*
Because of how the code is merged together using the doc regions,
we need to indent the imports with the function below.
*/
/* tslint:disable:no-shadowed-variable */
/* tslint:disable:align */
// #docregion ajax
import { ajax } from 'rxjs/ajax';
import { ajax } from 'rxjs/ajax';
// Create an Observable that will create an AJAX request
const apiData = ajax('/api/data');
// Subscribe to create the request
apiData.subscribe(res => console.log(res.status, res.response));
// #enddocregion ajax
export function docRegionAjax(console, ajax) {
// #docregion ajax
const apiData = ajax('/api/data');
// Subscribe to create the request
apiData.subscribe(res => console.log(res.status, res.response));
// #enddocregion ajax
}

View File

@ -15,11 +15,11 @@ RxJS provides an implementation of the `Observable` type, which is needed until
RxJS offers a number of functions that can be used to create new observables. These functions can simplify the process of creating observables from things such as events, timers, promises, and so on. For example:
<code-example path="rx-library/src/simple-creation.ts" region="promise" header="Create an observable from a promise"></code-example>
<code-example path="rx-library/src/simple-creation.1.ts" region="promise" header="Create an observable from a promise"></code-example>
<code-example path="rx-library/src/simple-creation.ts" region="interval" header="Create an observable from a counter"></code-example>
<code-example path="rx-library/src/simple-creation.2.ts" region="interval" header="Create an observable from a counter"></code-example>
<code-example path="rx-library/src/simple-creation.ts" region="event" header="Create an observable from an event"></code-example>
<code-example path="rx-library/src/simple-creation.3.ts" region="event" header="Create an observable from an event"></code-example>
<code-example path="rx-library/src/simple-creation.ts" region="ajax" header="Create an observable that creates an AJAX request"></code-example>