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) => {
canvas.toBlob(blob => {
this.blobSize = blob.size; 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