docs: fix docs and associated code snippets for enabling more macro tasks in `fakeAsync()` (#35778)

In the `testing` guide, there is a section discussing configuring
`fakeAsync()` to handle more macro tasks (e.g.
`HTMLCanvasElement#toBlob()`).

Previously, the corresponding code snippets (some of which were
hard-coded in the guide) were incorrect/incomplete and the associated
tests were broken. This was discovered while enabling docs examples unit
tests in #34374.

This commit fixes the code snippets and associated tests and ensures the
examples used in the guide come from an example app (i.e. are not
hard-coded).

Note: The docs examples unit tests are currently not run on CI. This
will be fixed in #34374.

PR Close #35778
This commit is contained in:
George Kalpakas 2020-02-29 18:10:57 +02:00 committed by atscott
parent ef2721b903
commit 8eb4a9d395
3 changed files with 71 additions and 61 deletions

View File

@ -1,6 +1,21 @@
// #docplaster
// #docregion without-toBlob-macrotask
import { TestBed, async, tick, fakeAsync } from '@angular/core/testing'; import { TestBed, async, tick, fakeAsync } from '@angular/core/testing';
import { CanvasComponent } from './canvas.component'; import { CanvasComponent } from './canvas.component';
describe('CanvasComponent', () => { describe('CanvasComponent', () => {
// #enddocregion without-toBlob-macrotask
// #docregion enable-toBlob-macrotask
beforeEach(() => {
window['__zone_symbol__FakeAsyncTestMacroTask'] = [
{
source: 'HTMLCanvasElement.toBlob',
callbackArgs: [{ size: 200 }],
},
];
});
// #enddocregion enable-toBlob-macrotask
// #docregion without-toBlob-macrotask
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [
@ -8,20 +23,16 @@ describe('CanvasComponent', () => {
], ],
}).compileComponents(); }).compileComponents();
})); }));
beforeEach(() => {
window['__zone_symbol__FakeAsyncTestMacroTask'] = [
{
source: 'HTMLCanvasElement.toBlob',
callbackArgs: [{ size: 200 }]
}
];
});
it('should be able to generate blob data from canvas', fakeAsync(() => { it('should be able to generate blob data from canvas', fakeAsync(() => {
const fixture = TestBed.createComponent(CanvasComponent); const fixture = TestBed.createComponent(CanvasComponent);
const canvasComp = fixture.debugElement.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
expect(canvasComp.blobSize).toBe(0);
tick(); tick();
const app = fixture.debugElement.componentInstance; expect(canvasComp.blobSize).toBeGreaterThan(0);
expect(app.blobSize).toBeGreaterThan(0);
})); }));
}); });
// #enddocregion without-toBlob-macrotask

View File

@ -1,25 +1,32 @@
// #docplaster
// #docregion import-canvas-patch
// Import patch to make async `HTMLCanvasElement` methods (such as `.toBlob()`) Zone.js-aware.
// Either import in `polyfills.ts` (if used in more than one places in the app) or in the component
// file using `HTMLCanvasElement` (if it is only used in a single file).
import 'zone.js/dist/zone-patch-canvas';
// #enddocregion import-canvas-patch
// #docregion main
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core'; import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
@Component({ @Component({
selector: 'sample-canvas', selector: 'sample-canvas',
template: '<canvas #sampleCanvas width="200" height="200"></canvas>' template: '<canvas #sampleCanvas width="200" height="200"></canvas>',
}) })
export class CanvasComponent implements AfterViewInit { export class CanvasComponent implements AfterViewInit {
blobSize: number; blobSize = 0;
@ViewChild('sampleCanvas') sampleCanvas: ElementRef; @ViewChild('sampleCanvas') sampleCanvas: ElementRef;
constructor() { }
ngAfterViewInit() { ngAfterViewInit() {
const canvas = this.sampleCanvas.nativeElement; const canvas: HTMLCanvasElement = this.sampleCanvas.nativeElement;
const context = canvas.getContext('2d'); const context = canvas.getContext('2d');
if (context) {
context.clearRect(0, 0, 200, 200); context.clearRect(0, 0, 200, 200);
context.fillStyle = '#FF1122'; context.fillStyle = '#FF1122';
context.fillRect(0, 0, 200, 200); context.fillRect(0, 0, 200, 200);
canvas.toBlob((blob: any) => {
this.blobSize = blob.size; canvas.toBlob(blob => {
}); this.blobSize = blob.size;
} });
} }
} }
// #enddocregion main

View File

@ -899,8 +899,7 @@ In production, change detection kicks in automatically
when Angular creates a component or the user enters a keystroke or when Angular creates a component or the user enters a keystroke or
an asynchronous activity (e.g., AJAX) completes. an asynchronous activity (e.g., AJAX) completes.
The `TestBed.createComponent` does _not_ trigger change detection. The `TestBed.createComponent` does _not_ trigger change detection; a fact confirmed in the revised test:
a fact confirmed in the revised test:
<code-example <code-example
path="testing/src/app/banner/banner.component.spec.ts" region="test-w-o-detect-changes"></code-example> path="testing/src/app/banner/banner.component.spec.ts" region="test-w-o-detect-changes"></code-example>
@ -1051,8 +1050,7 @@ attempt to reach an authentication server.
These behaviors can be hard to intercept. These behaviors can be hard to intercept.
It is far easier and safer to create and register a test double in place of the real `UserService`. It is far easier and safer to create and register a test double in place of the real `UserService`.
This particular test suite supplies a minimal mock of the `UserService` that satisfies the needs of the `WelcomeComponent` This particular test suite supplies a minimal mock of the `UserService` that satisfies the needs of the `WelcomeComponent` and its tests:
and its tests:
<code-example <code-example
path="testing/src/app/welcome/welcome.component.spec.ts" path="testing/src/app/welcome/welcome.component.spec.ts"
@ -1266,8 +1264,7 @@ You do have to call [tick()](api/core/testing/tick) to advance the (virtual) clo
Calling [tick()](api/core/testing/tick) simulates the passage of time until all pending asynchronous activities finish. Calling [tick()](api/core/testing/tick) simulates the passage of time until all pending asynchronous activities finish.
In this case, it waits for the error handler's `setTimeout()`. In this case, it waits for the error handler's `setTimeout()`.
The [tick()](api/core/testing/tick) function accepts milliseconds and tickOptions as parameters, the millisecond (defaults to 0 if not provided) parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback. The tickOptions is an optional parameter with a property called processNewMacroTasksSynchronously (defaults is true) represents whether to invoke The [tick()](api/core/testing/tick) function accepts milliseconds and tickOptions as parameters, the millisecond (defaults to 0 if not provided) parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback. The tickOptions is an optional parameter with a property called `processNewMacroTasksSynchronously` (defaults to true) represents whether to invoke new generated macro tasks when ticking.
new generated macro tasks when ticking.
<code-example <code-example
path="testing/src/app/demo/async-helper.spec.ts" path="testing/src/app/demo/async-helper.spec.ts"
@ -1331,51 +1328,46 @@ You can also use RxJS scheduler in `fakeAsync()` just like using `setTimeout()`
#### Support more macroTasks #### Support more macroTasks
By default `fakeAsync()` supports the following `macroTasks`. By default, `fakeAsync()` supports the following macro tasks.
- setTimeout - `setTimeout`
- setInterval - `setInterval`
- requestAnimationFrame - `requestAnimationFrame`
- webkitRequestAnimationFrame - `webkitRequestAnimationFrame`
- mozRequestAnimationFrame - `mozRequestAnimationFrame`
If you run other `macroTask` such as `HTMLCanvasElement.toBlob()`, `Unknown macroTask scheduled in fake async test` error will be thrown. If you run other macro tasks such as `HTMLCanvasElement.toBlob()`, an _"Unknown macroTask scheduled in fake async test"_ error will be thrown.
<code-tabs> <code-tabs>
<code-pane <code-pane
header="src/app/shared/canvas.component.spec.ts (failing)"
path="testing/src/app/shared/canvas.component.spec.ts" path="testing/src/app/shared/canvas.component.spec.ts"
header="src/app/shared/canvas.component.spec.ts"> region="without-toBlob-macrotask">
</code-pane> </code-pane>
<code-pane <code-pane
header="src/app/shared/canvas.component.ts"
path="testing/src/app/shared/canvas.component.ts" path="testing/src/app/shared/canvas.component.ts"
header="src/app/shared/canvas.component.ts"> region="main">
</code-pane> </code-pane>
</code-tabs> </code-tabs>
If you want to support such a case, you need to define the `macroTask` you want to support in `beforeEach()`. If you want to support such a case, you need to define the macro task you want to support in `beforeEach()`.
For example: For example:
```javascript <code-example
beforeEach(() => { header="src/app/shared/canvas.component.spec.ts (excerpt)"
window['__zone_symbol__FakeAsyncTestMacroTask'] = [ path="testing/src/app/shared/canvas.component.spec.ts"
{ region="enable-toBlob-macrotask">
source: 'HTMLCanvasElement.toBlob', </code-example>
callbackArgs: [{ size: 200 }]
} Note that in order to make the `<canvas>` element Zone.js-aware in your app, you need to import the `zone-patch-canvas` patch (either in `polyfills.ts` or in the specific file that uses `<canvas>`):
];
}); <code-example
header="src/polyfills.ts or src/app/shared/canvas.component.ts"
path="testing/src/app/shared/canvas.component.ts"
region="import-canvas-patch">
</code-example>
it('toBlob should be able to run in fakeAsync', fakeAsync(() => {
const canvas: HTMLCanvasElement = document.getElementById('canvas') as HTMLCanvasElement;
let blob = null;
canvas.toBlob(function(b) {
blob = b;
});
tick();
expect(blob.size).toBe(200);
})
);
```
#### Async observables #### Async observables
@ -3635,7 +3627,7 @@ next to their corresponding helper files.
#### Keep it simple #### Keep it simple
[Component class testing](#component-class-testing) should be kept very clean and simple. [Component class testing](#component-class-testing) should be kept very clean and simple.
It should test only a single unit. On a first glance, you should be able to understand It should test only a single unit. On a first glance, you should be able to understand
what the test is testing. If it's doing more, then it doesn't belong here. what the test is testing. If it's doing more, then it doesn't belong here.
{@a q-end-to-end} {@a q-end-to-end}