upstream: Merge remote-tracking branch 'upstream/master' into merge-upstream
# Conflicts: # CHANGELOG.md # aio/content/examples/testing/src/app/app.component.router.spec.ts # aio/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts # aio/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts # aio/content/examples/testing/src/app/hero/hero-detail.component.spec.ts # aio/content/examples/testing/src/app/hero/hero-list.component.spec.ts # aio/content/examples/testing/src/app/twain/twain.component.spec.ts # goldens/public-api/core/testing/testing.d.ts # goldens/size-tracking/aio-payloads.json # package.json # packages/core/test/bundling/forms/bundle.golden_symbols.json # packages/forms/test/form_group_spec.ts
This commit is contained in:
@ -1,31 +1,31 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { TemplateModule } from './template/template.module';
|
||||
import { ReactiveModule } from './reactive/reactive.module';
|
||||
import { TemplateModule } from './template/template.module';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ReactiveModule, TemplateModule],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [ReactiveModule, TemplateModule],
|
||||
declarations: [AppComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
it('should create the app', waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should render title', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
it('should render title', waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Forms Overview');
|
||||
}));
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Forms Overview');
|
||||
}));
|
||||
});
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { FavoriteColorComponent } from './favorite-color.component';
|
||||
import { createNewEvent } from '../../shared/utils';
|
||||
import { FavoriteColorComponent } from './favorite-color.component';
|
||||
|
||||
describe('Favorite Color Component', () => {
|
||||
let component: FavoriteColorComponent;
|
||||
let fixture: ComponentFixture<FavoriteColorComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ ReactiveFormsModule ],
|
||||
declarations: [ FavoriteColorComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule(
|
||||
{imports: [ReactiveFormsModule], declarations: [FavoriteColorComponent]})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -1,19 +1,16 @@
|
||||
import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { FavoriteColorComponent } from './favorite-color.component';
|
||||
import { createNewEvent } from '../../shared/utils';
|
||||
import { FavoriteColorComponent } from './favorite-color.component';
|
||||
|
||||
describe('FavoriteColorComponent', () => {
|
||||
let component: FavoriteColorComponent;
|
||||
let fixture: ComponentFixture<FavoriteColorComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ FormsModule ],
|
||||
declarations: [ FavoriteColorComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({imports: [FormsModule], declarations: [FavoriteColorComponent]})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -28,29 +25,29 @@ describe('FavoriteColorComponent', () => {
|
||||
|
||||
// #docregion model-to-view
|
||||
it('should update the favorite color on the input field', fakeAsync(() => {
|
||||
component.favoriteColor = 'Blue';
|
||||
component.favoriteColor = 'Blue';
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
tick();
|
||||
tick();
|
||||
|
||||
const input = fixture.nativeElement.querySelector('input');
|
||||
const input = fixture.nativeElement.querySelector('input');
|
||||
|
||||
expect(input.value).toBe('Blue');
|
||||
}));
|
||||
expect(input.value).toBe('Blue');
|
||||
}));
|
||||
// #enddocregion model-to-view
|
||||
|
||||
// #docregion view-to-model
|
||||
it('should update the favorite color in the component', fakeAsync(() => {
|
||||
const input = fixture.nativeElement.querySelector('input');
|
||||
const event = createNewEvent('input');
|
||||
const input = fixture.nativeElement.querySelector('input');
|
||||
const event = createNewEvent('input');
|
||||
|
||||
input.value = 'Red';
|
||||
input.dispatchEvent(event);
|
||||
input.value = 'Red';
|
||||
input.dispatchEvent(event);
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.favoriteColor).toEqual('Red');
|
||||
}));
|
||||
expect(component.favoriteColor).toEqual('Red');
|
||||
}));
|
||||
// #enddocregion view-to-model
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { MyLibComponent } from './my-lib.component';
|
||||
|
||||
@ -6,11 +6,8 @@ describe('MyLibComponent', () => {
|
||||
let component: MyLibComponent;
|
||||
let fixture: ComponentFixture<MyLibComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MyLibComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({declarations: [MyLibComponent]}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -1,19 +1,16 @@
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let de: DebugElement;
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AppComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({declarations: [AppComponent]}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -22,12 +19,11 @@ describe('AppComponent', () => {
|
||||
de = fixture.debugElement.query(By.css('h1'));
|
||||
});
|
||||
|
||||
it('should create component', () => expect(comp).toBeDefined() );
|
||||
it('should create component', () => expect(comp).toBeDefined());
|
||||
|
||||
it('should have expected <h1> text', () => {
|
||||
fixture.detectChanges();
|
||||
const h1 = de.nativeElement;
|
||||
expect(h1.textContent).toMatch(/angular/i,
|
||||
'<h1> should say something about "Angular"');
|
||||
expect(h1.textContent).toMatch(/angular/i, '<h1> should say something about "Angular"');
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
// #enddocregion
|
||||
import { AppComponent } from './app-initial.component';
|
||||
/*
|
||||
@ -12,29 +12,29 @@ describe('AppComponent', () => {
|
||||
*/
|
||||
describe('AppComponent (initial CLI version)', () => {
|
||||
// #docregion
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
it('should render title', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
it('should create the app', waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'app'`, waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
it('should render title', waitForAsync(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
}));
|
||||
});
|
||||
// #enddocregion
|
||||
|
||||
@ -43,16 +43,13 @@ import { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture } from '@angular/core/testing';
|
||||
|
||||
describe('AppComponent (initial CLI version - as it should be)', () => {
|
||||
|
||||
let app: AppComponent;
|
||||
let de: DebugElement;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
@ -70,7 +67,6 @@ describe('AppComponent (initial CLI version - as it should be)', () => {
|
||||
|
||||
it('should render title in an h1 tag', () => {
|
||||
fixture.detectChanges();
|
||||
expect(de.nativeElement.querySelector('h1').textContent)
|
||||
.toContain('Welcome to app!');
|
||||
expect(de.nativeElement.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,7 @@
|
||||
// For more examples:
|
||||
// https://github.com/angular/angular/blob/master/modules/@angular/router/test/integration.spec.ts
|
||||
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { waitForAsync, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
|
||||
import { asyncData } from '../testing';
|
||||
|
||||
@ -21,9 +21,9 @@ import { AppModule } from './app.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AboutComponent } from './about/about.component';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
import { TwainService } from './twain/twain.service';
|
||||
|
||||
import { HeroService, TestHeroService } from './model/testing/test-hero.service';
|
||||
import { TwainService } from './twain/twain.service';
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
@ -32,54 +32,51 @@ let router: Router;
|
||||
let location: SpyLocation;
|
||||
|
||||
describe('AppComponent & RouterTestingModule', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
providers: [
|
||||
{ provide: HeroService, useClass: TestHeroService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [
|
||||
AppModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
providers: [{provide: HeroService, useClass: TestHeroService}]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
it('should navigate to "Dashboard" immediately', fakeAsync(() => {
|
||||
createComponent();
|
||||
tick(); // wait for async data to arrive
|
||||
expectPathToBe('/dashboard', 'after initialNavigation()');
|
||||
expectElementOf(DashboardComponent);
|
||||
}));
|
||||
createComponent();
|
||||
tick(); // wait for async data to arrive
|
||||
expectPathToBe('/dashboard', 'after initialNavigation()');
|
||||
expectElementOf(DashboardComponent);
|
||||
}));
|
||||
|
||||
it('should navigate to "About" on click', fakeAsync(() => {
|
||||
createComponent();
|
||||
click(page.aboutLinkDe);
|
||||
// page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom
|
||||
createComponent();
|
||||
click(page.aboutLinkDe);
|
||||
// page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom
|
||||
|
||||
advance();
|
||||
expectPathToBe('/about');
|
||||
expectElementOf(AboutComponent);
|
||||
}));
|
||||
advance();
|
||||
expectPathToBe('/about');
|
||||
expectElementOf(AboutComponent);
|
||||
}));
|
||||
|
||||
it('should navigate to "About" w/ browser location URL change', fakeAsync(() => {
|
||||
createComponent();
|
||||
location.simulateHashChange('/about');
|
||||
// location.go('/about'); // also works ... except, perhaps, in Stackblitz
|
||||
advance();
|
||||
expectPathToBe('/about');
|
||||
expectElementOf(AboutComponent);
|
||||
}));
|
||||
createComponent();
|
||||
location.simulateHashChange('/about');
|
||||
// location.go('/about'); // also works ... except, perhaps, in Stackblitz
|
||||
advance();
|
||||
expectPathToBe('/about');
|
||||
expectElementOf(AboutComponent);
|
||||
}));
|
||||
|
||||
// Can't navigate to lazy loaded modules with this technique
|
||||
xit('should navigate to "Heroes" on click (not working yet)', fakeAsync(() => {
|
||||
createComponent();
|
||||
page.heroesLinkDe.nativeElement.click();
|
||||
advance();
|
||||
expectPathToBe('/heroes');
|
||||
}));
|
||||
|
||||
createComponent();
|
||||
page.heroesLinkDe.nativeElement.click();
|
||||
advance();
|
||||
expectPathToBe('/heroes');
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@ -94,37 +91,37 @@ let loader: SpyNgModuleFactoryLoader;
|
||||
|
||||
///////// Can't get lazy loaded Heroes to work yet
|
||||
xdescribe('AppComponent & Lazy Loading (not working yet)', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
AppModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [
|
||||
AppModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(fakeAsync(() => {
|
||||
createComponent();
|
||||
loader = TestBed.inject(NgModuleFactoryLoader) as SpyNgModuleFactoryLoader;
|
||||
loader.stubbedModules = { expected: HeroModule };
|
||||
loader.stubbedModules = {expected: HeroModule};
|
||||
router.resetConfig([{path: 'heroes', loadChildren: 'expected'}]);
|
||||
}));
|
||||
|
||||
it('should navigate to "Heroes" on click', async(() => {
|
||||
page.heroesLinkDe.nativeElement.click();
|
||||
advance();
|
||||
expectPathToBe('/heroes');
|
||||
expectElementOf(HeroListComponent);
|
||||
}));
|
||||
it('should navigate to "Heroes" on click', waitForAsync(() => {
|
||||
page.heroesLinkDe.nativeElement.click();
|
||||
advance();
|
||||
expectPathToBe('/heroes');
|
||||
expectElementOf(HeroListComponent);
|
||||
}));
|
||||
|
||||
it('can navigate to "Heroes" w/ browser location URL change', fakeAsync(() => {
|
||||
location.go('/heroes');
|
||||
advance();
|
||||
expectPathToBe('/heroes');
|
||||
expectElementOf(HeroListComponent);
|
||||
}));
|
||||
location.go('/heroes');
|
||||
advance();
|
||||
expectPathToBe('/heroes');
|
||||
expectElementOf(HeroListComponent);
|
||||
}));
|
||||
});
|
||||
|
||||
////// Helpers /////////
|
||||
@ -134,9 +131,9 @@ xdescribe('AppComponent & Lazy Loading (not working yet)', () => {
|
||||
* Wait a tick, then detect changes, and tick again
|
||||
*/
|
||||
function advance(): void {
|
||||
tick(); // wait while navigating
|
||||
fixture.detectChanges(); // update view
|
||||
tick(); // wait for async data to arrive
|
||||
tick(); // wait while navigating
|
||||
fixture.detectChanges(); // update view
|
||||
tick(); // wait for async data to arrive
|
||||
}
|
||||
|
||||
function createComponent() {
|
||||
@ -148,8 +145,8 @@ function createComponent() {
|
||||
router = injector.get(Router);
|
||||
router.initialNavigation();
|
||||
spyOn(injector.get(TwainService), 'getQuote')
|
||||
// fake fast async observable
|
||||
.and.returnValue(asyncData('Test Quote'));
|
||||
// fake fast async observable
|
||||
.and.returnValue(asyncData('Test Quote'));
|
||||
advance();
|
||||
|
||||
page = new Page();
|
||||
@ -168,14 +165,14 @@ class Page {
|
||||
|
||||
constructor() {
|
||||
const links = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
|
||||
this.aboutLinkDe = links[2];
|
||||
this.aboutLinkDe = links[2];
|
||||
this.dashboardLinkDe = links[0];
|
||||
this.heroesLinkDe = links[1];
|
||||
this.heroesLinkDe = links[1];
|
||||
|
||||
// for debugging
|
||||
this.comp = comp;
|
||||
this.comp = comp;
|
||||
this.fixture = fixture;
|
||||
this.router = router;
|
||||
this.router = router;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,66 +1,70 @@
|
||||
// #docplaster
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { RouterLinkDirectiveStub } from '../testing';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
// #docregion component-stubs
|
||||
@Component({selector: 'app-banner', template: ''})
|
||||
class BannerStubComponent {}
|
||||
class BannerStubComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'router-outlet', template: ''})
|
||||
class RouterOutletStubComponent { }
|
||||
class RouterOutletStubComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'app-welcome', template: ''})
|
||||
class WelcomeStubComponent {}
|
||||
class WelcomeStubComponent {
|
||||
}
|
||||
// #enddocregion component-stubs
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
|
||||
describe('AppComponent & TestModule', () => {
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
// #docregion testbed-stubs
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
RouterLinkDirectiveStub,
|
||||
BannerStubComponent,
|
||||
RouterOutletStubComponent,
|
||||
WelcomeStubComponent
|
||||
]
|
||||
})
|
||||
// #enddocregion testbed-stubs
|
||||
.compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
comp = fixture.componentInstance;
|
||||
});
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent, RouterLinkDirectiveStub, BannerStubComponent, RouterOutletStubComponent,
|
||||
WelcomeStubComponent
|
||||
]
|
||||
})
|
||||
// #enddocregion testbed-stubs
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
comp = fixture.componentInstance;
|
||||
});
|
||||
}));
|
||||
tests();
|
||||
});
|
||||
|
||||
//////// Testing w/ NO_ERRORS_SCHEMA //////
|
||||
describe('AppComponent & NO_ERRORS_SCHEMA', () => {
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
// #docregion no-errors-schema, mixed-setup
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
// #enddocregion no-errors-schema
|
||||
BannerStubComponent,
|
||||
// #docregion no-errors-schema
|
||||
RouterLinkDirectiveStub
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
})
|
||||
// #enddocregion no-errors-schema, mixed-setup
|
||||
.compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
comp = fixture.componentInstance;
|
||||
});
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
// #enddocregion no-errors-schema
|
||||
BannerStubComponent,
|
||||
// #docregion no-errors-schema
|
||||
RouterLinkDirectiveStub
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
// #enddocregion no-errors-schema, mixed-setup
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
comp = fixture.componentInstance;
|
||||
});
|
||||
}));
|
||||
tests();
|
||||
});
|
||||
@ -72,30 +76,23 @@ import { AppModule } from './app.module';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
describe('AppComponent & AppModule', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({imports: [AppModule]})
|
||||
|
||||
beforeEach(async(() => {
|
||||
// Get rid of app's Router configuration otherwise many failures.
|
||||
// Doing so removes Router declarations; add the Router stubs
|
||||
.overrideModule(AppModule, {
|
||||
remove: {imports: [AppRoutingModule]},
|
||||
add: {declarations: [RouterLinkDirectiveStub, RouterOutletStubComponent]}
|
||||
})
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ AppModule ]
|
||||
})
|
||||
.compileComponents()
|
||||
|
||||
// Get rid of app's Router configuration otherwise many failures.
|
||||
// Doing so removes Router declarations; add the Router stubs
|
||||
.overrideModule(AppModule, {
|
||||
remove: {
|
||||
imports: [ AppRoutingModule ]
|
||||
},
|
||||
add: {
|
||||
declarations: [ RouterLinkDirectiveStub, RouterOutletStubComponent ]
|
||||
}
|
||||
})
|
||||
|
||||
.compileComponents()
|
||||
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
comp = fixture.componentInstance;
|
||||
});
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
comp = fixture.componentInstance;
|
||||
});
|
||||
}));
|
||||
|
||||
tests();
|
||||
@ -107,11 +104,10 @@ function tests() {
|
||||
|
||||
// #docregion test-setup
|
||||
beforeEach(() => {
|
||||
fixture.detectChanges(); // trigger initial data binding
|
||||
fixture.detectChanges(); // trigger initial data binding
|
||||
|
||||
// find DebugElements with an attached RouterLinkStubDirective
|
||||
linkDes = fixture.debugElement
|
||||
.queryAll(By.directive(RouterLinkDirectiveStub));
|
||||
linkDes = fixture.debugElement.queryAll(By.directive(RouterLinkDirectiveStub));
|
||||
|
||||
// get attached link directive instances
|
||||
// using each DebugElement's injector
|
||||
@ -132,8 +128,8 @@ function tests() {
|
||||
});
|
||||
|
||||
it('can click Heroes link in template', () => {
|
||||
const heroesLinkDe = linkDes[1]; // heroes link DebugElement
|
||||
const heroesLink = routerLinks[1]; // heroes link directive
|
||||
const heroesLinkDe = linkDes[1]; // heroes link DebugElement
|
||||
const heroesLink = routerLinks[1]; // heroes link directive
|
||||
|
||||
expect(heroesLink.navigatedTo).toBeNull('should not have navigated yet');
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// #docplaster
|
||||
// #docregion import-async
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
// #enddocregion import-async
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
@ -14,11 +14,12 @@ describe('BannerComponent (external files)', () => {
|
||||
|
||||
describe('Two beforeEach', () => {
|
||||
// #docregion async-before-each
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ BannerComponent ],
|
||||
})
|
||||
.compileComponents(); // compile template and css
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [BannerComponent],
|
||||
})
|
||||
.compileComponents(); // compile template and css
|
||||
}));
|
||||
// #enddocregion async-before-each
|
||||
|
||||
@ -26,7 +27,7 @@ describe('BannerComponent (external files)', () => {
|
||||
// #docregion sync-before-each
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BannerComponent);
|
||||
component = fixture.componentInstance; // BannerComponent test instance
|
||||
component = fixture.componentInstance; // BannerComponent test instance
|
||||
h1 = fixture.nativeElement.querySelector('h1');
|
||||
});
|
||||
// #enddocregion sync-before-each
|
||||
@ -36,16 +37,17 @@ describe('BannerComponent (external files)', () => {
|
||||
|
||||
describe('One beforeEach', () => {
|
||||
// #docregion one-before-each
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ BannerComponent ],
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(BannerComponent);
|
||||
component = fixture.componentInstance;
|
||||
h1 = fixture.nativeElement.querySelector('h1');
|
||||
});
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [BannerComponent],
|
||||
})
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(BannerComponent);
|
||||
component = fixture.componentInstance;
|
||||
h1 = fixture.nativeElement.querySelector('h1');
|
||||
});
|
||||
}));
|
||||
// #enddocregion one-before-each
|
||||
|
||||
@ -69,4 +71,3 @@ describe('BannerComponent (external files)', () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,14 +1,16 @@
|
||||
// #docplaster
|
||||
// #docregion import-by
|
||||
import { By } from '@angular/platform-browser';
|
||||
// #enddocregion import-by
|
||||
// #docregion import-debug-element
|
||||
import { DebugElement } from '@angular/core';
|
||||
// #enddocregion import-debug-element
|
||||
// #docregion v1
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
// #enddocregion v1
|
||||
import { BannerComponent } from './banner-initial.component';
|
||||
|
||||
/*
|
||||
// #docregion v1
|
||||
import { BannerComponent } from './banner.component';
|
||||
@ -17,15 +19,12 @@ describe('BannerComponent', () => {
|
||||
// #enddocregion v1
|
||||
*/
|
||||
describe('BannerComponent (initial CLI generated)', () => {
|
||||
// #docregion v1
|
||||
// #docregion v1
|
||||
let component: BannerComponent;
|
||||
let fixture: ComponentFixture<BannerComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ BannerComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({declarations: [BannerComponent]}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -44,9 +43,7 @@ describe('BannerComponent (initial CLI generated)', () => {
|
||||
describe('BannerComponent (minimal)', () => {
|
||||
it('should create', () => {
|
||||
// #docregion configureTestingModule
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ BannerComponent ]
|
||||
});
|
||||
TestBed.configureTestingModule({declarations: [BannerComponent]});
|
||||
// #enddocregion configureTestingModule
|
||||
// #docregion createComponent
|
||||
const fixture = TestBed.createComponent(BannerComponent);
|
||||
@ -65,9 +62,7 @@ describe('BannerComponent (with beforeEach)', () => {
|
||||
let fixture: ComponentFixture<BannerComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ BannerComponent ]
|
||||
});
|
||||
TestBed.configureTestingModule({declarations: [BannerComponent]});
|
||||
fixture = TestBed.createComponent(BannerComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
@ -1,22 +1,21 @@
|
||||
|
||||
// #docplaster
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { addMatchers, click } from '../../testing';
|
||||
|
||||
import { Hero } from '../model/hero';
|
||||
|
||||
import { DashboardHeroComponent } from './dashboard-hero.component';
|
||||
|
||||
beforeEach( addMatchers );
|
||||
beforeEach(addMatchers);
|
||||
|
||||
describe('DashboardHeroComponent class only', () => {
|
||||
// #docregion class-only
|
||||
it('raises the selected event when clicked', () => {
|
||||
const comp = new DashboardHeroComponent();
|
||||
const hero: Hero = { id: 42, name: 'Test' };
|
||||
const hero: Hero = {id: 42, name: 'Test'};
|
||||
comp.hero = hero;
|
||||
|
||||
comp.selected.subscribe((selectedHero: Hero) => expect(selectedHero).toBe(hero));
|
||||
@ -26,33 +25,31 @@ describe('DashboardHeroComponent class only', () => {
|
||||
});
|
||||
|
||||
describe('DashboardHeroComponent when tested directly', () => {
|
||||
|
||||
let comp: DashboardHeroComponent;
|
||||
let expectedHero: Hero;
|
||||
let fixture: ComponentFixture<DashboardHeroComponent>;
|
||||
let heroDe: DebugElement;
|
||||
let heroEl: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
// #docregion setup, config-testbed
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DashboardHeroComponent ]
|
||||
})
|
||||
// #enddocregion setup, config-testbed
|
||||
.compileComponents();
|
||||
TestBed
|
||||
.configureTestingModule({declarations: [DashboardHeroComponent]})
|
||||
// #enddocregion setup, config-testbed
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
// #docregion setup
|
||||
fixture = TestBed.createComponent(DashboardHeroComponent);
|
||||
comp = fixture.componentInstance;
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
// find the hero's DebugElement and element
|
||||
heroDe = fixture.debugElement.query(By.css('.hero'));
|
||||
heroDe = fixture.debugElement.query(By.css('.hero'));
|
||||
heroEl = heroDe.nativeElement;
|
||||
|
||||
// mock the hero supplied by the parent component
|
||||
expectedHero = { id: 42, name: 'Test Name' };
|
||||
expectedHero = {id: 42, name: 'Test Name'};
|
||||
|
||||
// simulate the parent setting the input property with that hero
|
||||
comp.hero = expectedHero;
|
||||
@ -96,8 +93,8 @@ describe('DashboardHeroComponent when tested directly', () => {
|
||||
let selectedHero: Hero;
|
||||
comp.selected.subscribe((hero: Hero) => selectedHero = hero);
|
||||
|
||||
click(heroDe); // click helper with DebugElement
|
||||
click(heroEl); // click helper with native element
|
||||
click(heroDe); // click helper with DebugElement
|
||||
click(heroEl); // click helper with native element
|
||||
|
||||
expect(selectedHero).toBe(expectedHero);
|
||||
});
|
||||
@ -111,22 +108,21 @@ describe('DashboardHeroComponent when inside a test host', () => {
|
||||
let fixture: ComponentFixture<TestHostComponent>;
|
||||
let heroEl: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
// #docregion test-host-setup
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DashboardHeroComponent, TestHostComponent ]
|
||||
})
|
||||
// #enddocregion test-host-setup
|
||||
.compileComponents();
|
||||
TestBed
|
||||
.configureTestingModule({declarations: [DashboardHeroComponent, TestHostComponent]})
|
||||
// #enddocregion test-host-setup
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
// #docregion test-host-setup
|
||||
// create TestHostComponent instead of DashboardHeroComponent
|
||||
fixture = TestBed.createComponent(TestHostComponent);
|
||||
fixture = TestBed.createComponent(TestHostComponent);
|
||||
testHost = fixture.componentInstance;
|
||||
heroEl = fixture.nativeElement.querySelector('.hero');
|
||||
fixture.detectChanges(); // trigger initial data binding
|
||||
heroEl = fixture.nativeElement.querySelector('.hero');
|
||||
fixture.detectChanges(); // trigger initial data binding
|
||||
// #enddocregion test-host-setup
|
||||
});
|
||||
|
||||
@ -155,8 +151,10 @@ import { Component } from '@angular/core';
|
||||
</dashboard-hero>`
|
||||
})
|
||||
class TestHostComponent {
|
||||
hero: Hero = {id: 42, name: 'Test Name' };
|
||||
hero: Hero = {id: 42, name: 'Test Name'};
|
||||
selectedHero: Hero;
|
||||
onSelected(hero: Hero) { this.selectedHero = hero; }
|
||||
onSelected(hero: Hero) {
|
||||
this.selectedHero = hero;
|
||||
}
|
||||
}
|
||||
// #enddocregion test-host
|
||||
|
@ -1,6 +1,5 @@
|
||||
// #docplaster
|
||||
import { async, inject, ComponentFixture, TestBed
|
||||
} from '@angular/core/testing';
|
||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { addMatchers, asyncData, click } from '../../testing';
|
||||
import { HeroService } from '../model/hero.service';
|
||||
@ -12,7 +11,7 @@ import { Router } from '@angular/router';
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { DashboardModule } from './dashboard.module';
|
||||
|
||||
beforeEach ( addMatchers );
|
||||
beforeEach(addMatchers);
|
||||
|
||||
let comp: DashboardComponent;
|
||||
let fixture: ComponentFixture<DashboardComponent>;
|
||||
@ -21,9 +20,7 @@ let fixture: ComponentFixture<DashboardComponent>;
|
||||
|
||||
describe('DashboardComponent (deep)', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ DashboardModule ]
|
||||
});
|
||||
TestBed.configureTestingModule({imports: [DashboardModule]});
|
||||
});
|
||||
|
||||
compileAndCreate();
|
||||
@ -43,10 +40,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
|
||||
describe('DashboardComponent (shallow)', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DashboardComponent ],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
});
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [DashboardComponent], schemas: [NO_ERRORS_SCHEMA]});
|
||||
});
|
||||
|
||||
compileAndCreate();
|
||||
@ -63,25 +58,26 @@ describe('DashboardComponent (shallow)', () => {
|
||||
/** Add TestBed providers, compile, and create DashboardComponent */
|
||||
function compileAndCreate() {
|
||||
// #docregion compile-and-create-body
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
// #docregion router-spy
|
||||
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
|
||||
const heroServiceSpy = jasmine.createSpyObj('HeroService', ['getHeroes']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: HeroService, useValue: heroServiceSpy },
|
||||
{ provide: Router, useValue: routerSpy }
|
||||
]
|
||||
})
|
||||
// #enddocregion router-spy
|
||||
.compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(DashboardComponent);
|
||||
comp = fixture.componentInstance;
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
providers: [
|
||||
{provide: HeroService, useValue: heroServiceSpy}, {provide: Router, useValue: routerSpy}
|
||||
]
|
||||
})
|
||||
// #enddocregion router-spy
|
||||
.compileComponents()
|
||||
.then(() => {
|
||||
fixture = TestBed.createComponent(DashboardComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
// getHeroes spy returns observable of test heroes
|
||||
heroServiceSpy.getHeroes.and.returnValue(asyncData(getTestHeroes()));
|
||||
});
|
||||
// getHeroes spy returns observable of test heroes
|
||||
heroServiceSpy.getHeroes.and.returnValue(asyncData(getTestHeroes()));
|
||||
});
|
||||
// #enddocregion compile-and-create-body
|
||||
}));
|
||||
}
|
||||
@ -93,23 +89,20 @@ function compileAndCreate() {
|
||||
function tests(heroClick: () => void) {
|
||||
|
||||
it('should NOT have heroes before ngOnInit', () => {
|
||||
expect(comp.heroes.length).toBe(0,
|
||||
'should not have heroes before ngOnInit');
|
||||
expect(comp.heroes.length).toBe(0, 'should not have heroes before ngOnInit');
|
||||
});
|
||||
|
||||
it('should NOT have heroes immediately after ngOnInit', () => {
|
||||
fixture.detectChanges(); // runs initial lifecycle hooks
|
||||
fixture.detectChanges(); // runs initial lifecycle hooks
|
||||
|
||||
expect(comp.heroes.length).toBe(0,
|
||||
'should not have heroes until service promise resolves');
|
||||
expect(comp.heroes.length).toBe(0, 'should not have heroes until service promise resolves');
|
||||
});
|
||||
|
||||
describe('after get dashboard heroes', () => {
|
||||
|
||||
let router: Router;
|
||||
|
||||
// Trigger component so it gets heroes and binds to them
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
router = fixture.debugElement.injector.get(Router);
|
||||
fixture.detectChanges(); // runs ngOnInit -> getHeroes
|
||||
fixture.whenStable() // No need for the `lastPromise` hack!
|
||||
@ -117,8 +110,8 @@ function tests(heroClick: () => void) {
|
||||
}));
|
||||
|
||||
it('should HAVE heroes', () => {
|
||||
expect(comp.heroes.length).toBeGreaterThan(0,
|
||||
'should have heroes after service promise resolves');
|
||||
expect(comp.heroes.length)
|
||||
.toBeGreaterThan(0, 'should have heroes after service promise resolves');
|
||||
});
|
||||
|
||||
it('should DISPLAY heroes', () => {
|
||||
@ -130,8 +123,7 @@ function tests(heroClick: () => void) {
|
||||
|
||||
// #docregion navigate-test
|
||||
it('should tell ROUTER to navigate when hero clicked', () => {
|
||||
|
||||
heroClick(); // trigger click on first inner <div class="hero">
|
||||
heroClick(); // trigger click on first inner <div class="hero">
|
||||
|
||||
// args passed to router.navigateByUrl() spy
|
||||
const spy = router.navigateByUrl as jasmine.Spy;
|
||||
@ -139,10 +131,8 @@ function tests(heroClick: () => void) {
|
||||
|
||||
// expecting to navigate to id of the component's first hero
|
||||
const id = comp.heroes[0].id;
|
||||
expect(navArgs).toBe('/heroes/' + id,
|
||||
'should nav to HeroDetail for first hero');
|
||||
expect(navArgs).toBe('/heroes/' + id, 'should nav to HeroDetail for first hero');
|
||||
});
|
||||
// #enddocregion navigate-test
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,23 @@
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
import { async, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { fakeAsync, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { interval, of } from 'rxjs';
|
||||
import { delay, take } from 'rxjs/operators';
|
||||
|
||||
describe('Angular async helper', () => {
|
||||
|
||||
describe('async', () => {
|
||||
let actuallyDone = false;
|
||||
|
||||
beforeEach(() => { actuallyDone = false; });
|
||||
beforeEach(() => {
|
||||
actuallyDone = false;
|
||||
});
|
||||
|
||||
afterEach(() => { expect(actuallyDone).toBe(true, 'actuallyDone should be true'); });
|
||||
afterEach(() => {
|
||||
expect(actuallyDone).toBe(true, 'actuallyDone should be true');
|
||||
});
|
||||
|
||||
it('should run normal test', () => { actuallyDone = true; });
|
||||
it('should run normal test', () => {
|
||||
actuallyDone = true;
|
||||
});
|
||||
|
||||
it('should run normal async test', (done: DoneFn) => {
|
||||
setTimeout(() => {
|
||||
@ -21,39 +26,50 @@ describe('Angular async helper', () => {
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should run async test with task',
|
||||
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
|
||||
it('should run async test with task', waitForAsync(() => {
|
||||
setTimeout(() => {
|
||||
actuallyDone = true;
|
||||
}, 0);
|
||||
}));
|
||||
|
||||
it('should run async test with task', async(() => {
|
||||
it('should run async test with task', waitForAsync(() => {
|
||||
const id = setInterval(() => {
|
||||
actuallyDone = true;
|
||||
clearInterval(id);
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
it('should run async test with successful promise', async(() => {
|
||||
const p = new Promise(resolve => { setTimeout(resolve, 10); });
|
||||
p.then(() => { actuallyDone = true; });
|
||||
it('should run async test with successful promise', waitForAsync(() => {
|
||||
const p = new Promise(resolve => {
|
||||
setTimeout(resolve, 10);
|
||||
});
|
||||
p.then(() => {
|
||||
actuallyDone = true;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should run async test with failed promise', async(() => {
|
||||
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
|
||||
p.catch(() => { actuallyDone = true; });
|
||||
it('should run async test with failed promise', waitForAsync(() => {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
setTimeout(reject, 10);
|
||||
});
|
||||
p.catch(() => {
|
||||
actuallyDone = true;
|
||||
});
|
||||
}));
|
||||
|
||||
// Use done. Can also use async or fakeAsync.
|
||||
it('should run async test with successful delayed Observable', (done: DoneFn) => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
const source = of(true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err), done);
|
||||
});
|
||||
|
||||
it('should run async test with successful delayed Observable', async(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
it('should run async test with successful delayed Observable', waitForAsync(() => {
|
||||
const source = of(true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
}));
|
||||
|
||||
it('should run async test with successful delayed Observable', fakeAsync(() => {
|
||||
const source = of (true).pipe(delay(10));
|
||||
const source = of(true).pipe(delay(10));
|
||||
source.subscribe(val => actuallyDone = true, err => fail(err));
|
||||
|
||||
tick(10);
|
||||
@ -64,7 +80,9 @@ describe('Angular async helper', () => {
|
||||
// #docregion fake-async-test-tick
|
||||
it('should run timeout callback with delay after call tick with millis', fakeAsync(() => {
|
||||
let called = false;
|
||||
setTimeout(() => { called = true; }, 100);
|
||||
setTimeout(() => {
|
||||
called = true;
|
||||
}, 100);
|
||||
tick(100);
|
||||
expect(called).toBe(true);
|
||||
}));
|
||||
@ -73,7 +91,9 @@ describe('Angular async helper', () => {
|
||||
// #docregion fake-async-test-tick-new-macro-task-sync
|
||||
it('should run new macro task callback with delay after call tick with millis',
|
||||
fakeAsync(() => {
|
||||
function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); }
|
||||
function nestedTimer(cb: () => any): void {
|
||||
setTimeout(() => setTimeout(() => cb()));
|
||||
}
|
||||
const callback = jasmine.createSpy('callback');
|
||||
nestedTimer(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
@ -86,7 +106,9 @@ describe('Angular async helper', () => {
|
||||
// #docregion fake-async-test-tick-new-macro-task-async
|
||||
it('should not run new macro task callback with delay after call tick with millis',
|
||||
fakeAsync(() => {
|
||||
function nestedTimer(cb: () => any): void { setTimeout(() => setTimeout(() => cb())); }
|
||||
function nestedTimer(cb: () => any): void {
|
||||
setTimeout(() => setTimeout(() => cb()));
|
||||
}
|
||||
const callback = jasmine.createSpy('callback');
|
||||
nestedTimer(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
@ -112,7 +134,9 @@ describe('Angular async helper', () => {
|
||||
// need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async'
|
||||
// to patch rxjs scheduler
|
||||
let result = null;
|
||||
of ('hello').pipe(delay(1000)).subscribe(v => { result = v; });
|
||||
of('hello').pipe(delay(1000)).subscribe(v => {
|
||||
result = v;
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
tick(1000);
|
||||
expect(result).toBe('hello');
|
||||
@ -133,12 +157,18 @@ describe('Angular async helper', () => {
|
||||
describe('use jasmine.clock()', () => {
|
||||
// need to config __zone_symbol__fakeAsyncPatchLock flag
|
||||
// before loading zone.js/dist/zone-testing
|
||||
beforeEach(() => { jasmine.clock().install(); });
|
||||
afterEach(() => { jasmine.clock().uninstall(); });
|
||||
beforeEach(() => {
|
||||
jasmine.clock().install();
|
||||
});
|
||||
afterEach(() => {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
it('should auto enter fakeAsync', () => {
|
||||
// is in fakeAsync now, don't need to call fakeAsync(testFn)
|
||||
let called = false;
|
||||
setTimeout(() => { called = true; }, 100);
|
||||
setTimeout(() => {
|
||||
called = true;
|
||||
}, 100);
|
||||
jasmine.clock().tick(100);
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
@ -152,7 +182,7 @@ describe('Angular async helper', () => {
|
||||
}
|
||||
// need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag
|
||||
// before loading zone.js/dist/zone-testing
|
||||
it('should wait until promise.then is called', async(() => {
|
||||
it('should wait until promise.then is called', waitForAsync(() => {
|
||||
let finished = false;
|
||||
new Promise((res, rej) => {
|
||||
jsonp('localhost:8080/jsonp', () => {
|
||||
@ -168,5 +198,4 @@ describe('Angular async helper', () => {
|
||||
}));
|
||||
});
|
||||
// #enddocregion async-test-promise-then
|
||||
|
||||
});
|
||||
|
@ -24,17 +24,18 @@ import { FormsModule } from '@angular/forms';
|
||||
// Forms symbols imported only for a specific test below
|
||||
import { NgModel, NgControl } from '@angular/forms';
|
||||
|
||||
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick
|
||||
import {
|
||||
ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { addMatchers, newEvent, click } from '../../testing';
|
||||
|
||||
export class NotProvided extends ValueService { /* example below */}
|
||||
beforeEach( addMatchers );
|
||||
export class NotProvided extends ValueService { /* example below */ }
|
||||
beforeEach(addMatchers);
|
||||
|
||||
describe('demo (with TestBed):', () => {
|
||||
|
||||
//////// Service Tests /////////////
|
||||
//////// Service Tests /////////////
|
||||
|
||||
// #docregion ValueService
|
||||
describe('ValueService', () => {
|
||||
@ -64,13 +65,13 @@ describe('demo (with TestBed):', () => {
|
||||
// #enddocregion testbed-get-w-null
|
||||
});
|
||||
|
||||
it('test should wait for ValueService.getPromiseValue', async(() => {
|
||||
it('test should wait for ValueService.getPromiseValue', waitForAsync(() => {
|
||||
service.getPromiseValue().then(
|
||||
value => expect(value).toBe('promise value')
|
||||
);
|
||||
}));
|
||||
|
||||
it('test should wait for ValueService.getObservableValue', async(() => {
|
||||
it('test should wait for ValueService.getObservableValue', waitForAsync(() => {
|
||||
service.getObservableValue().subscribe(
|
||||
value => expect(value).toBe('observable value')
|
||||
);
|
||||
@ -150,7 +151,7 @@ describe('demo (with TestBed):', () => {
|
||||
TestBed.configureTestingModule({ providers: [ValueService] });
|
||||
});
|
||||
|
||||
beforeEach(async(inject([ValueService], (service: ValueService) => {
|
||||
beforeEach(waitForAsync(inject([ValueService], (service: ValueService) => {
|
||||
service.getPromiseValue().then(value => serviceValue = value);
|
||||
})));
|
||||
|
||||
@ -159,11 +160,11 @@ describe('demo (with TestBed):', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/////////// Component Tests //////////////////
|
||||
/////////// Component Tests //////////////////
|
||||
|
||||
describe('TestBed component tests', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [DemoModule],
|
||||
@ -235,7 +236,7 @@ describe('demo (with TestBed):', () => {
|
||||
// #docregion ButtonComp
|
||||
it('should support clicking a button', () => {
|
||||
const fixture = TestBed.createComponent(LightswitchComponent);
|
||||
const btn = fixture.debugElement.query(By.css('button'));
|
||||
const btn = fixture.debugElement.query(By.css('button'));
|
||||
const span = fixture.debugElement.query(By.css('span')).nativeElement;
|
||||
|
||||
fixture.detectChanges();
|
||||
@ -248,7 +249,7 @@ describe('demo (with TestBed):', () => {
|
||||
// #enddocregion ButtonComp
|
||||
|
||||
// ngModel is async so we must wait for it with promise-based `whenStable`
|
||||
it('should support entering text in input box (ngModel)', async(() => {
|
||||
it('should support entering text in input box (ngModel)', waitForAsync(() => {
|
||||
const expectedOrigName = 'John';
|
||||
const expectedNewName = 'Sally';
|
||||
|
||||
@ -278,10 +279,10 @@ describe('demo (with TestBed):', () => {
|
||||
input.dispatchEvent(newEvent('input'));
|
||||
return fixture.whenStable();
|
||||
})
|
||||
.then(() => {
|
||||
expect(comp.name).toBe(expectedNewName,
|
||||
`After ngModel updates the model, comp.name should be ${expectedNewName} `);
|
||||
});
|
||||
.then(() => {
|
||||
expect(comp.name).toBe(expectedNewName,
|
||||
`After ngModel updates the model, comp.name should be ${expectedNewName} `);
|
||||
});
|
||||
}));
|
||||
|
||||
// fakeAsync version of ngModel input test enables sync test style
|
||||
@ -327,9 +328,9 @@ describe('demo (with TestBed):', () => {
|
||||
const fixture = TestBed.createComponent(ReversePipeComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const comp = fixture.componentInstance;
|
||||
const comp = fixture.componentInstance;
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement;
|
||||
const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement;
|
||||
const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement;
|
||||
|
||||
// simulate user entering new name in input
|
||||
input.value = inputText;
|
||||
@ -381,12 +382,12 @@ describe('demo (with TestBed):', () => {
|
||||
|
||||
expect(el.styles.color).toBe(comp.color, 'color style');
|
||||
expect(el.styles.width).toBe(comp.width + 'px', 'width style');
|
||||
// #enddocregion dom-attributes
|
||||
// #enddocregion dom-attributes
|
||||
|
||||
// Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?
|
||||
// expect(el.properties['customProperty']).toBe(true, 'customProperty');
|
||||
|
||||
// #docregion dom-attributes
|
||||
// #docregion dom-attributes
|
||||
});
|
||||
// #enddocregion dom-attributes
|
||||
|
||||
@ -400,10 +401,10 @@ describe('demo (with TestBed):', () => {
|
||||
const fixture = TestBed.configureTestingModule({
|
||||
declarations: [Child1Component],
|
||||
})
|
||||
.overrideComponent(Child1Component, {
|
||||
set: { template: '<span>Fake</span>' }
|
||||
})
|
||||
.createComponent(Child1Component);
|
||||
.overrideComponent(Child1Component, {
|
||||
set: { template: '<span>Fake</span>' }
|
||||
})
|
||||
.createComponent(Child1Component);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture).toHaveText('Fake');
|
||||
@ -413,14 +414,14 @@ describe('demo (with TestBed):', () => {
|
||||
const fixture = TestBed.configureTestingModule({
|
||||
declarations: [TestProvidersComponent],
|
||||
})
|
||||
.overrideComponent(TestProvidersComponent, {
|
||||
remove: { providers: [ValueService]},
|
||||
add: { providers: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
.overrideComponent(TestProvidersComponent, {
|
||||
remove: { providers: [ValueService] },
|
||||
add: { providers: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
|
||||
// Or replace them all (this component has only one provider)
|
||||
// set: { providers: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
})
|
||||
.createComponent(TestProvidersComponent);
|
||||
// Or replace them all (this component has only one provider)
|
||||
// set: { providers: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
})
|
||||
.createComponent(TestProvidersComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture).toHaveText('injected value: faked value', 'text');
|
||||
@ -436,14 +437,14 @@ describe('demo (with TestBed):', () => {
|
||||
const fixture = TestBed.configureTestingModule({
|
||||
declarations: [TestViewProvidersComponent],
|
||||
})
|
||||
.overrideComponent(TestViewProvidersComponent, {
|
||||
// remove: { viewProviders: [ValueService]},
|
||||
// add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
.overrideComponent(TestViewProvidersComponent, {
|
||||
// remove: { viewProviders: [ValueService]},
|
||||
// add: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
|
||||
// Or replace them all (this component has only one viewProvider)
|
||||
set: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
})
|
||||
.createComponent(TestViewProvidersComponent);
|
||||
// Or replace them all (this component has only one viewProvider)
|
||||
set: { viewProviders: [{ provide: ValueService, useClass: FakeValueService }] },
|
||||
})
|
||||
.createComponent(TestViewProvidersComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture).toHaveText('injected value: faked value');
|
||||
@ -453,20 +454,20 @@ describe('demo (with TestBed):', () => {
|
||||
|
||||
// TestComponent is parent of TestProvidersComponent
|
||||
@Component({ template: '<my-service-comp></my-service-comp>' })
|
||||
class TestComponent {}
|
||||
class TestComponent { }
|
||||
|
||||
// 3 levels of ValueService provider: module, TestCompomponent, TestProvidersComponent
|
||||
const fixture = TestBed.configureTestingModule({
|
||||
declarations: [TestComponent, TestProvidersComponent],
|
||||
providers: [ValueService]
|
||||
providers: [ValueService]
|
||||
})
|
||||
.overrideComponent(TestComponent, {
|
||||
set: { providers: [{ provide: ValueService, useValue: {} }] }
|
||||
})
|
||||
.overrideComponent(TestProvidersComponent, {
|
||||
set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }
|
||||
})
|
||||
.createComponent(TestComponent);
|
||||
.overrideComponent(TestComponent, {
|
||||
set: { providers: [{ provide: ValueService, useValue: {} }] }
|
||||
})
|
||||
.overrideComponent(TestProvidersComponent, {
|
||||
set: { providers: [{ provide: ValueService, useClass: FakeValueService }] }
|
||||
})
|
||||
.createComponent(TestComponent);
|
||||
|
||||
let testBedProvider: ValueService;
|
||||
let tcProvider: ValueService;
|
||||
@ -489,10 +490,10 @@ describe('demo (with TestBed):', () => {
|
||||
const fixture = TestBed.configureTestingModule({
|
||||
declarations: [ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component],
|
||||
})
|
||||
.overrideComponent(ShellComponent, {
|
||||
set: {
|
||||
selector: 'test-shell',
|
||||
template: `
|
||||
.overrideComponent(ShellComponent, {
|
||||
set: {
|
||||
selector: 'test-shell',
|
||||
template: `
|
||||
<needs-content #nc>
|
||||
<child-1 #content text="My"></child-1>
|
||||
<child-2 #content text="dog"></child-2>
|
||||
@ -501,9 +502,9 @@ describe('demo (with TestBed):', () => {
|
||||
<div #content>!</div>
|
||||
</needs-content>
|
||||
`
|
||||
}
|
||||
})
|
||||
.createComponent(ShellComponent);
|
||||
}
|
||||
})
|
||||
.createComponent(ShellComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
@ -615,7 +616,7 @@ describe('demo (with TestBed):', () => {
|
||||
});
|
||||
|
||||
// must be async test to see child flow to parent
|
||||
it('changed child value flows to parent', async(() => {
|
||||
it('changed child value flows to parent', waitForAsync(() => {
|
||||
fixture.detectChanges();
|
||||
getChild();
|
||||
|
||||
@ -625,14 +626,14 @@ describe('demo (with TestBed):', () => {
|
||||
// Wait one JS engine turn!
|
||||
setTimeout(() => resolve(), 0);
|
||||
})
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(child.ngOnChangesCounter).toBe(2,
|
||||
'expected 2 changes: initial value and changed value');
|
||||
expect(parent.parentValue).toBe('bar',
|
||||
'parentValue should eq changed parent value');
|
||||
});
|
||||
expect(child.ngOnChangesCounter).toBe(2,
|
||||
'expected 2 changes: initial value and changed value');
|
||||
expect(parent.parentValue).toBe('bar',
|
||||
'parentValue should eq changed parent value');
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
// #docplaster
|
||||
import {
|
||||
async, ComponentFixture, fakeAsync, inject, TestBed, tick
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import {
|
||||
@ -36,59 +33,54 @@ describe('HeroDetailComponent', () => {
|
||||
function overrideSetup() {
|
||||
// #docregion hds-spy
|
||||
class HeroDetailServiceSpy {
|
||||
testHero: Hero = {id: 42, name: 'Test Hero' };
|
||||
testHero: Hero = {id: 42, name: 'Test Hero'};
|
||||
|
||||
/* emit cloned test hero */
|
||||
getHero = jasmine.createSpy('getHero').and.callFake(
|
||||
() => asyncData(Object.assign({}, this.testHero))
|
||||
);
|
||||
() => asyncData(Object.assign({}, this.testHero)));
|
||||
|
||||
/* emit clone of test hero, with changes merged in */
|
||||
saveHero = jasmine.createSpy('saveHero').and.callFake(
|
||||
(hero: Hero) => asyncData(Object.assign(this.testHero, hero))
|
||||
);
|
||||
saveHero = jasmine.createSpy('saveHero')
|
||||
.and.callFake((hero: Hero) => asyncData(Object.assign(this.testHero, hero)));
|
||||
}
|
||||
|
||||
// #enddocregion hds-spy
|
||||
|
||||
// the `id` value is irrelevant because ignored by service stub
|
||||
beforeEach(() => activatedRoute.setParamMap({ id: 99999 }));
|
||||
beforeEach(() => activatedRoute.setParamMap({id: 99999}));
|
||||
|
||||
// #docregion setup-override
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
const routerSpy = createRouterSpy();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ HeroModule ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||
{ provide: Router, useValue: routerSpy},
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [HeroModule],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: activatedRoute},
|
||||
{provide: Router, useValue: routerSpy},
|
||||
// #enddocregion setup-override
|
||||
// HeroDetailService at this level is IRRELEVANT!
|
||||
{ provide: HeroDetailService, useValue: {} }
|
||||
// HeroDetailService at this level is IRRELEVANT!
|
||||
{provide: HeroDetailService, useValue: {}}
|
||||
// #docregion setup-override
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
// Override component's own provider
|
||||
// #docregion override-component-method
|
||||
.overrideComponent(HeroDetailComponent, {
|
||||
set: {
|
||||
providers: [
|
||||
{ provide: HeroDetailService, useClass: HeroDetailServiceSpy }
|
||||
]
|
||||
}
|
||||
})
|
||||
// #enddocregion override-component-method
|
||||
// Override component's own provider
|
||||
// #docregion override-component-method
|
||||
.overrideComponent(
|
||||
HeroDetailComponent,
|
||||
{set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]}})
|
||||
// #enddocregion override-component-method
|
||||
|
||||
.compileComponents();
|
||||
.compileComponents();
|
||||
}));
|
||||
// #enddocregion setup-override
|
||||
|
||||
// #docregion override-tests
|
||||
let hdsSpy: HeroDetailServiceSpy;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
createComponent();
|
||||
// get the component's injected HeroDetailServiceSpy
|
||||
hdsSpy = fixture.debugElement.injector.get(HeroDetailService) as any;
|
||||
@ -103,33 +95,32 @@ function overrideSetup() {
|
||||
});
|
||||
|
||||
it('should save stub hero change', fakeAsync(() => {
|
||||
const origName = hdsSpy.testHero.name;
|
||||
const newName = 'New Name';
|
||||
const origName = hdsSpy.testHero.name;
|
||||
const newName = 'New Name';
|
||||
|
||||
page.nameInput.value = newName;
|
||||
page.nameInput.dispatchEvent(newEvent('input')); // tell Angular
|
||||
page.nameInput.value = newName;
|
||||
page.nameInput.dispatchEvent(newEvent('input')); // tell Angular
|
||||
|
||||
expect(component.hero.name).toBe(newName, 'component hero has new name');
|
||||
expect(hdsSpy.testHero.name).toBe(origName, 'service hero unchanged before save');
|
||||
expect(component.hero.name).toBe(newName, 'component hero has new name');
|
||||
expect(hdsSpy.testHero.name).toBe(origName, 'service hero unchanged before save');
|
||||
|
||||
click(page.saveBtn);
|
||||
expect(hdsSpy.saveHero.calls.count()).toBe(1, 'saveHero called once');
|
||||
click(page.saveBtn);
|
||||
expect(hdsSpy.saveHero.calls.count()).toBe(1, 'saveHero called once');
|
||||
|
||||
tick(); // wait for async save to complete
|
||||
expect(hdsSpy.testHero.name).toBe(newName, 'service hero has new name after save');
|
||||
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||
}));
|
||||
tick(); // wait for async save to complete
|
||||
expect(hdsSpy.testHero.name).toBe(newName, 'service hero has new name after save');
|
||||
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||
}));
|
||||
// #enddocregion override-tests
|
||||
|
||||
it('fixture injected service is not the component injected service',
|
||||
// inject gets the service from the fixture
|
||||
inject([HeroDetailService], (fixtureService: HeroDetailService) => {
|
||||
// inject gets the service from the fixture
|
||||
inject([HeroDetailService], (fixtureService: HeroDetailService) => {
|
||||
// use `fixture.debugElement.injector` to get service from component
|
||||
const componentService = fixture.debugElement.injector.get(HeroDetailService);
|
||||
|
||||
// use `fixture.debugElement.injector` to get service from component
|
||||
const componentService = fixture.debugElement.injector.get(HeroDetailService);
|
||||
|
||||
expect(fixtureService).not.toBe(componentService, 'service injected from fixture');
|
||||
}));
|
||||
expect(fixtureService).not.toBe(componentService, 'service injected from fixture');
|
||||
}));
|
||||
}
|
||||
|
||||
////////////////////
|
||||
@ -139,21 +130,22 @@ const firstHero = getTestHeroes()[0];
|
||||
|
||||
function heroModuleSetup() {
|
||||
// #docregion setup-hero-module
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
const routerSpy = createRouterSpy();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ HeroModule ],
|
||||
// #enddocregion setup-hero-module
|
||||
// declarations: [ HeroDetailComponent ], // NO! DOUBLE DECLARATION
|
||||
// #docregion setup-hero-module
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||
{ provide: HeroService, useClass: TestHeroService },
|
||||
{ provide: Router, useValue: routerSpy},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [HeroModule],
|
||||
// #enddocregion setup-hero-module
|
||||
// declarations: [ HeroDetailComponent ], // NO! DOUBLE DECLARATION
|
||||
// #docregion setup-hero-module
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: activatedRoute},
|
||||
{provide: HeroService, useClass: TestHeroService},
|
||||
{provide: Router, useValue: routerSpy},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
// #enddocregion setup-hero-module
|
||||
|
||||
@ -161,17 +153,17 @@ function heroModuleSetup() {
|
||||
describe('when navigate to existing hero', () => {
|
||||
let expectedHero: Hero;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
expectedHero = firstHero;
|
||||
activatedRoute.setParamMap({ id: expectedHero.id });
|
||||
activatedRoute.setParamMap({id: expectedHero.id});
|
||||
createComponent();
|
||||
}));
|
||||
|
||||
// #docregion selected-tests
|
||||
// #docregion selected-tests
|
||||
it('should display that hero\'s name', () => {
|
||||
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
|
||||
});
|
||||
// #enddocregion route-good-id
|
||||
// #enddocregion route-good-id
|
||||
|
||||
it('should navigate when click cancel', () => {
|
||||
click(page.cancelBtn);
|
||||
@ -190,10 +182,10 @@ function heroModuleSetup() {
|
||||
});
|
||||
|
||||
it('should navigate when click save and save resolves', fakeAsync(() => {
|
||||
click(page.saveBtn);
|
||||
tick(); // wait for async save to complete
|
||||
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||
}));
|
||||
click(page.saveBtn);
|
||||
tick(); // wait for async save to complete
|
||||
expect(page.navigateSpy.calls.any()).toBe(true, 'router.navigate called');
|
||||
}));
|
||||
|
||||
// #docregion title-case-pipe
|
||||
it('should convert hero name to Title Case', () => {
|
||||
@ -215,14 +207,14 @@ function heroModuleSetup() {
|
||||
expect(nameDisplay.textContent).toBe('Quick Brown Fox');
|
||||
});
|
||||
// #enddocregion title-case-pipe
|
||||
// #enddocregion selected-tests
|
||||
// #docregion route-good-id
|
||||
// #enddocregion selected-tests
|
||||
// #docregion route-good-id
|
||||
});
|
||||
// #enddocregion route-good-id
|
||||
|
||||
// #docregion route-no-id
|
||||
describe('when navigate with no hero id', () => {
|
||||
beforeEach(async( createComponent ));
|
||||
beforeEach(waitForAsync(createComponent));
|
||||
|
||||
it('should have hero.id === 0', () => {
|
||||
expect(component.hero.id).toBe(0);
|
||||
@ -236,8 +228,8 @@ function heroModuleSetup() {
|
||||
|
||||
// #docregion route-bad-id
|
||||
describe('when navigate to non-existent hero id', () => {
|
||||
beforeEach(async(() => {
|
||||
activatedRoute.setParamMap({ id: 99999 });
|
||||
beforeEach(waitForAsync(() => {
|
||||
activatedRoute.setParamMap({id: 99999});
|
||||
createComponent();
|
||||
}));
|
||||
|
||||
@ -253,11 +245,10 @@ function heroModuleSetup() {
|
||||
let service: HeroDetailService;
|
||||
fixture = TestBed.createComponent(HeroDetailComponent);
|
||||
expect(
|
||||
// Throws because `inject` only has access to TestBed's injector
|
||||
// which is an ancestor of the component's injector
|
||||
inject([HeroDetailService], (hds: HeroDetailService) => service = hds )
|
||||
)
|
||||
.toThrowError(/No provider for HeroDetailService/);
|
||||
// Throws because `inject` only has access to TestBed's injector
|
||||
// which is an ancestor of the component's injector
|
||||
inject([HeroDetailService], (hds: HeroDetailService) => service = hds))
|
||||
.toThrowError(/No provider for HeroDetailService/);
|
||||
|
||||
// get `HeroDetailService` with component's own injector
|
||||
service = fixture.debugElement.injector.get(HeroDetailService);
|
||||
@ -270,30 +261,31 @@ import { FormsModule } from '@angular/forms';
|
||||
import { TitleCasePipe } from '../shared/title-case.pipe';
|
||||
|
||||
function formsModuleSetup() {
|
||||
// #docregion setup-forms-module
|
||||
beforeEach(async(() => {
|
||||
// #docregion setup-forms-module
|
||||
beforeEach(waitForAsync(() => {
|
||||
const routerSpy = createRouterSpy();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ FormsModule ],
|
||||
declarations: [ HeroDetailComponent, TitleCasePipe ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||
{ provide: HeroService, useClass: TestHeroService },
|
||||
{ provide: Router, useValue: routerSpy},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [FormsModule],
|
||||
declarations: [HeroDetailComponent, TitleCasePipe],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: activatedRoute},
|
||||
{provide: HeroService, useClass: TestHeroService},
|
||||
{provide: Router, useValue: routerSpy},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
// #enddocregion setup-forms-module
|
||||
|
||||
it('should display 1st hero\'s name', async(() => {
|
||||
const expectedHero = firstHero;
|
||||
activatedRoute.setParamMap({ id: expectedHero.id });
|
||||
createComponent().then(() => {
|
||||
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
|
||||
});
|
||||
}));
|
||||
it('should display 1st hero\'s name', waitForAsync(() => {
|
||||
const expectedHero = firstHero;
|
||||
activatedRoute.setParamMap({id: expectedHero.id});
|
||||
createComponent().then(() => {
|
||||
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
@ -301,29 +293,30 @@ import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
function sharedModuleSetup() {
|
||||
// #docregion setup-shared-module
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
const routerSpy = createRouterSpy();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ SharedModule ],
|
||||
declarations: [ HeroDetailComponent ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||
{ provide: HeroService, useClass: TestHeroService },
|
||||
{ provide: Router, useValue: routerSpy},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [SharedModule],
|
||||
declarations: [HeroDetailComponent],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: activatedRoute},
|
||||
{provide: HeroService, useClass: TestHeroService},
|
||||
{provide: Router, useValue: routerSpy},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
// #enddocregion setup-shared-module
|
||||
|
||||
it('should display 1st hero\'s name', async(() => {
|
||||
const expectedHero = firstHero;
|
||||
activatedRoute.setParamMap({ id: expectedHero.id });
|
||||
createComponent().then(() => {
|
||||
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
|
||||
});
|
||||
}));
|
||||
it('should display 1st hero\'s name', waitForAsync(() => {
|
||||
const expectedHero = firstHero;
|
||||
activatedRoute.setParamMap({id: expectedHero.id});
|
||||
createComponent().then(() => {
|
||||
expect(page.nameDisplay.textContent).toBe(expectedHero.name);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/////////// Helpers /////
|
||||
@ -347,11 +340,21 @@ function createComponent() {
|
||||
// #docregion page
|
||||
class Page {
|
||||
// getter properties wait to query the DOM until called.
|
||||
get buttons() { return this.queryAll<HTMLButtonElement>('button'); }
|
||||
get saveBtn() { return this.buttons[0]; }
|
||||
get cancelBtn() { return this.buttons[1]; }
|
||||
get nameDisplay() { return this.query<HTMLElement>('span'); }
|
||||
get nameInput() { return this.query<HTMLInputElement>('input'); }
|
||||
get buttons() {
|
||||
return this.queryAll<HTMLButtonElement>('button');
|
||||
}
|
||||
get saveBtn() {
|
||||
return this.buttons[0];
|
||||
}
|
||||
get cancelBtn() {
|
||||
return this.buttons[1];
|
||||
}
|
||||
get nameDisplay() {
|
||||
return this.query<HTMLElement>('span');
|
||||
}
|
||||
get nameInput() {
|
||||
return this.query<HTMLInputElement>('input');
|
||||
}
|
||||
|
||||
gotoListSpy: jasmine.Spy;
|
||||
navigateSpy: jasmine.Spy;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick
|
||||
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { By } from '@angular/platform-browser';
|
||||
@ -7,13 +7,12 @@ import { DebugElement } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { addMatchers, newEvent } from '../../testing';
|
||||
|
||||
import { HeroService } from '../model/hero.service';
|
||||
import { getTestHeroes, TestHeroService } from '../model/testing/test-hero.service';
|
||||
|
||||
import { HeroModule } from './hero.module';
|
||||
import { HeroListComponent } from './hero-list.component';
|
||||
import { HighlightDirective } from '../shared/highlight.directive';
|
||||
import { HeroService } from '../model/hero.service';
|
||||
|
||||
const HEROES = getTestHeroes();
|
||||
|
||||
@ -24,20 +23,20 @@ let page: Page;
|
||||
/////// Tests //////
|
||||
|
||||
describe('HeroListComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
addMatchers();
|
||||
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HeroModule],
|
||||
providers: [
|
||||
{ provide: HeroService, useClass: TestHeroService },
|
||||
{ provide: Router, useValue: routerSpy}
|
||||
]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(createComponent);
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
imports: [HeroModule],
|
||||
providers: [
|
||||
{provide: HeroService, useClass: TestHeroService},
|
||||
{provide: Router, useValue: routerSpy}
|
||||
]
|
||||
})
|
||||
.compileComponents()
|
||||
.then(createComponent);
|
||||
}));
|
||||
|
||||
it('should display heroes', () => {
|
||||
@ -52,36 +51,35 @@ describe('HeroListComponent', () => {
|
||||
});
|
||||
|
||||
it('should select hero on click', fakeAsync(() => {
|
||||
const expectedHero = HEROES[1];
|
||||
const li = page.heroRows[1];
|
||||
li.dispatchEvent(newEvent('click'));
|
||||
tick();
|
||||
// `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService
|
||||
expect(comp.selectedHero).toEqual(expectedHero);
|
||||
}));
|
||||
const expectedHero = HEROES[1];
|
||||
const li = page.heroRows[1];
|
||||
li.dispatchEvent(newEvent('click'));
|
||||
tick();
|
||||
// `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService
|
||||
expect(comp.selectedHero).toEqual(expectedHero);
|
||||
}));
|
||||
|
||||
it('should navigate to selected hero detail on click', fakeAsync(() => {
|
||||
const expectedHero = HEROES[1];
|
||||
const li = page.heroRows[1];
|
||||
li.dispatchEvent(newEvent('click'));
|
||||
tick();
|
||||
const expectedHero = HEROES[1];
|
||||
const li = page.heroRows[1];
|
||||
li.dispatchEvent(newEvent('click'));
|
||||
tick();
|
||||
|
||||
// should have navigated
|
||||
expect(page.navSpy.calls.any()).toBe(true, 'navigate called');
|
||||
// should have navigated
|
||||
expect(page.navSpy.calls.any()).toBe(true, 'navigate called');
|
||||
|
||||
// composed hero detail will be URL like 'heroes/42'
|
||||
// expect link array with the route path and hero id
|
||||
// first argument to router.navigate is link array
|
||||
const navArgs = page.navSpy.calls.first().args[0];
|
||||
expect(navArgs[0]).toContain('heroes', 'nav to heroes detail URL');
|
||||
expect(navArgs[1]).toBe(expectedHero.id, 'expected hero.id');
|
||||
|
||||
}));
|
||||
// composed hero detail will be URL like 'heroes/42'
|
||||
// expect link array with the route path and hero id
|
||||
// first argument to router.navigate is link array
|
||||
const navArgs = page.navSpy.calls.first().args[0];
|
||||
expect(navArgs[0]).toContain('heroes', 'nav to heroes detail URL');
|
||||
expect(navArgs[1]).toBe(expectedHero.id, 'expected hero.id');
|
||||
}));
|
||||
|
||||
it('should find `HighlightDirective` with `By.directive', () => {
|
||||
// #docregion by
|
||||
// Can find DebugElement either by css selector or by directive
|
||||
const h2 = fixture.debugElement.query(By.css('h2'));
|
||||
const h2 = fixture.debugElement.query(By.css('h2'));
|
||||
const directive = fixture.debugElement.query(By.directive(HighlightDirective));
|
||||
// #enddocregion by
|
||||
expect(h2).toBe(directive);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// #docplaster
|
||||
// #docregion without-toBlob-macrotask
|
||||
import { TestBed, async, tick, fakeAsync } from '@angular/core/testing';
|
||||
import { fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { CanvasComponent } from './canvas.component';
|
||||
|
||||
describe('CanvasComponent', () => {
|
||||
@ -10,29 +11,29 @@ describe('CanvasComponent', () => {
|
||||
(window as any).__zone_symbol__FakeAsyncTestMacroTask = [
|
||||
{
|
||||
source: 'HTMLCanvasElement.toBlob',
|
||||
callbackArgs: [{ size: 200 }],
|
||||
callbackArgs: [{size: 200}],
|
||||
},
|
||||
];
|
||||
});
|
||||
// #enddocregion enable-toBlob-macrotask
|
||||
// #docregion without-toBlob-macrotask
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
CanvasComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [CanvasComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
it('should be able to generate blob data from canvas', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(CanvasComponent);
|
||||
const canvasComp = fixture.componentInstance;
|
||||
const fixture = TestBed.createComponent(CanvasComponent);
|
||||
const canvasComp = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(canvasComp.blobSize).toBe(0);
|
||||
fixture.detectChanges();
|
||||
expect(canvasComp.blobSize).toBe(0);
|
||||
|
||||
tick();
|
||||
expect(canvasComp.blobSize).toBeGreaterThan(0);
|
||||
}));
|
||||
tick();
|
||||
expect(canvasComp.blobSize).toBeGreaterThan(0);
|
||||
}));
|
||||
});
|
||||
// #enddocregion without-toBlob-macrotask
|
||||
|
@ -1,14 +1,13 @@
|
||||
// #docplaster
|
||||
import { async, fakeAsync, ComponentFixture, TestBed, tick } from '@angular/core/testing';
|
||||
import { fakeAsync, ComponentFixture, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { asyncData, asyncError } from '../../testing';
|
||||
|
||||
import { of, throwError } from 'rxjs';
|
||||
|
||||
import { last } from 'rxjs/operators';
|
||||
|
||||
import { TwainService } from './twain.service';
|
||||
import { TwainComponent } from './twain.component';
|
||||
import { TwainService } from './twain.service';
|
||||
|
||||
describe('TwainComponent', () => {
|
||||
let component: TwainComponent;
|
||||
@ -32,14 +31,12 @@ describe('TwainComponent', () => {
|
||||
// Create a fake TwainService object with a `getQuote()` spy
|
||||
const twainService = jasmine.createSpyObj('TwainService', ['getQuote']);
|
||||
// Make the spy return a synchronous Observable with the test data
|
||||
getQuoteSpy = twainService.getQuote.and.returnValue( of(testQuote) );
|
||||
getQuoteSpy = twainService.getQuote.and.returnValue(of(testQuote));
|
||||
// #enddocregion spy
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TwainComponent ],
|
||||
providers: [
|
||||
{ provide: TwainService, useValue: twainService }
|
||||
]
|
||||
declarations: [TwainComponent],
|
||||
providers: [{provide: TwainService, useValue: twainService}]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(TwainComponent);
|
||||
@ -58,7 +55,7 @@ describe('TwainComponent', () => {
|
||||
// The quote would not be immediately available if the service were truly async.
|
||||
// #docregion sync-test
|
||||
it('should show quote after component initialized', () => {
|
||||
fixture.detectChanges(); // onInit()
|
||||
fixture.detectChanges(); // onInit()
|
||||
|
||||
// sync spy result shows testQuote immediately after init
|
||||
expect(quoteEl.textContent).toBe(testQuote);
|
||||
@ -71,20 +68,19 @@ describe('TwainComponent', () => {
|
||||
// Use `fakeAsync` because the component error calls `setTimeout`
|
||||
// #docregion error-test
|
||||
it('should display error when TwainService fails', fakeAsync(() => {
|
||||
// tell spy to return an error observable
|
||||
getQuoteSpy.and.returnValue(
|
||||
throwError('TwainService test failure'));
|
||||
// tell spy to return an error observable
|
||||
getQuoteSpy.and.returnValue(throwError('TwainService test failure'));
|
||||
|
||||
fixture.detectChanges(); // onInit()
|
||||
// sync spy errors immediately after init
|
||||
fixture.detectChanges(); // onInit()
|
||||
// sync spy errors immediately after init
|
||||
|
||||
tick(); // flush the component's setTimeout()
|
||||
tick(); // flush the component's setTimeout()
|
||||
|
||||
fixture.detectChanges(); // update errorMessage within setTimeout()
|
||||
fixture.detectChanges(); // update errorMessage within setTimeout()
|
||||
|
||||
expect(errorMessage()).toMatch(/test failure/, 'should display error');
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
}));
|
||||
expect(errorMessage()).toMatch(/test failure/, 'should display error');
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
}));
|
||||
// #enddocregion error-test
|
||||
});
|
||||
|
||||
@ -113,28 +109,28 @@ describe('TwainComponent', () => {
|
||||
|
||||
// #docregion fake-async-test
|
||||
it('should show quote after getQuote (fakeAsync)', fakeAsync(() => {
|
||||
fixture.detectChanges(); // ngOnInit()
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
fixture.detectChanges(); // ngOnInit()
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
|
||||
tick(); // flush the observable to get the quote
|
||||
fixture.detectChanges(); // update view
|
||||
tick(); // flush the observable to get the quote
|
||||
fixture.detectChanges(); // update view
|
||||
|
||||
expect(quoteEl.textContent).toBe(testQuote, 'should show quote');
|
||||
expect(errorMessage()).toBeNull('should not show error');
|
||||
}));
|
||||
expect(quoteEl.textContent).toBe(testQuote, 'should show quote');
|
||||
expect(errorMessage()).toBeNull('should not show error');
|
||||
}));
|
||||
// #enddocregion fake-async-test
|
||||
|
||||
// #docregion async-test
|
||||
it('should show quote after getQuote (async)', async(() => {
|
||||
fixture.detectChanges(); // ngOnInit()
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
it('should show quote after getQuote (async)', waitForAsync(() => {
|
||||
fixture.detectChanges(); // ngOnInit()
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
|
||||
fixture.whenStable().then(() => { // wait for async getQuote
|
||||
fixture.detectChanges(); // update view with quote
|
||||
expect(quoteEl.textContent).toBe(testQuote);
|
||||
expect(errorMessage()).toBeNull('should not show error');
|
||||
});
|
||||
}));
|
||||
fixture.whenStable().then(() => { // wait for async getQuote
|
||||
fixture.detectChanges(); // update view with quote
|
||||
expect(quoteEl.textContent).toBe(testQuote);
|
||||
expect(errorMessage()).toBeNull('should not show error');
|
||||
});
|
||||
}));
|
||||
// #enddocregion async-test
|
||||
|
||||
|
||||
@ -142,8 +138,8 @@ describe('TwainComponent', () => {
|
||||
it('should show last quote (quote done)', (done: DoneFn) => {
|
||||
fixture.detectChanges();
|
||||
|
||||
component.quote.pipe( last() ).subscribe(() => {
|
||||
fixture.detectChanges(); // update view with quote
|
||||
component.quote.pipe(last()).subscribe(() => {
|
||||
fixture.detectChanges(); // update view with quote
|
||||
expect(quoteEl.textContent).toBe(testQuote);
|
||||
expect(errorMessage()).toBeNull('should not show error');
|
||||
done();
|
||||
@ -157,7 +153,7 @@ describe('TwainComponent', () => {
|
||||
|
||||
// the spy's most recent call returns the observable with the test quote
|
||||
getQuoteSpy.calls.mostRecent().returnValue.subscribe(() => {
|
||||
fixture.detectChanges(); // update view with quote
|
||||
fixture.detectChanges(); // update view with quote
|
||||
expect(quoteEl.textContent).toBe(testQuote);
|
||||
expect(errorMessage()).toBeNull('should not show error');
|
||||
done();
|
||||
@ -167,16 +163,16 @@ describe('TwainComponent', () => {
|
||||
|
||||
// #docregion async-error-test
|
||||
it('should display error when TwainService fails', fakeAsync(() => {
|
||||
// tell spy to return an async error observable
|
||||
getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure'));
|
||||
// tell spy to return an async error observable
|
||||
getQuoteSpy.and.returnValue(asyncError<string>('TwainService test failure'));
|
||||
|
||||
fixture.detectChanges();
|
||||
tick(); // component shows error after a setTimeout()
|
||||
fixture.detectChanges(); // update error message
|
||||
fixture.detectChanges();
|
||||
tick(); // component shows error after a setTimeout()
|
||||
fixture.detectChanges(); // update error message
|
||||
|
||||
expect(errorMessage()).toMatch(/test failure/, 'should display error');
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
}));
|
||||
expect(errorMessage()).toMatch(/test failure/, 'should display error');
|
||||
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
|
||||
}));
|
||||
// #enddocregion async-error-test
|
||||
});
|
||||
});
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { HeroSearchComponent } from '../hero-search/hero-search.component';
|
||||
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { of } from 'rxjs';
|
||||
import { HEROES } from '../mock-heroes';
|
||||
|
||||
import { HeroSearchComponent } from '../hero-search/hero-search.component';
|
||||
import { HeroService } from '../hero.service';
|
||||
import { HEROES } from '../mock-heroes';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
|
||||
describe('DashboardComponent', () => {
|
||||
let component: DashboardComponent;
|
||||
@ -14,23 +14,16 @@ describe('DashboardComponent', () => {
|
||||
let heroService;
|
||||
let getHeroesSpy;
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
heroService = jasmine.createSpyObj('HeroService', ['getHeroes']);
|
||||
getHeroesSpy = heroService.getHeroes.and.returnValue( of(HEROES) );
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
DashboardComponent,
|
||||
HeroSearchComponent
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
providers: [
|
||||
{ provide: HeroService, useValue: heroService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
getHeroesSpy = heroService.getHeroes.and.returnValue(of(HEROES));
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [DashboardComponent, HeroSearchComponent],
|
||||
imports: [RouterTestingModule.withRoutes([])],
|
||||
providers: [{provide: HeroService, useValue: heroService}]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -47,12 +40,11 @@ describe('DashboardComponent', () => {
|
||||
expect(fixture.nativeElement.querySelector('h3').textContent).toEqual('Top Heroes');
|
||||
});
|
||||
|
||||
it('should call heroService', async(() => {
|
||||
expect(getHeroesSpy.calls.any()).toBe(true);
|
||||
}));
|
||||
|
||||
it('should display 4 links', async(() => {
|
||||
expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4);
|
||||
}));
|
||||
it('should call heroService', waitForAsync(() => {
|
||||
expect(getHeroesSpy.calls.any()).toBe(true);
|
||||
}));
|
||||
|
||||
it('should display 4 links', waitForAsync(() => {
|
||||
expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4);
|
||||
}));
|
||||
});
|
||||
|
@ -1,22 +1,16 @@
|
||||
// #docregion
|
||||
// #docregion activatedroute
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
// #enddocregion activatedroute
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PhoneDetailComponent } from './phone-detail.component';
|
||||
import { Phone, PhoneData } from '../core/phone/phone.service';
|
||||
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
|
||||
|
||||
function xyzPhoneData(): PhoneData {
|
||||
return {
|
||||
name: 'phone xyz',
|
||||
snippet: '',
|
||||
images: ['image/url1.png', 'image/url2.png']
|
||||
};
|
||||
return {name: 'phone xyz', snippet: '', images: ['image/url1.png', 'image/url2.png']};
|
||||
}
|
||||
|
||||
class MockPhone {
|
||||
@ -34,10 +28,9 @@ class ActivatedRouteMock {
|
||||
// #enddocregion activatedroute
|
||||
|
||||
describe('PhoneDetailComponent', () => {
|
||||
|
||||
// #docregion activatedroute
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CheckmarkPipe, PhoneDetailComponent ],
|
||||
providers: [
|
||||
@ -55,5 +48,4 @@ describe('PhoneDetailComponent', () => {
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,13 +1,14 @@
|
||||
/* tslint:disable */
|
||||
// #docregion
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SpyLocation } from '@angular/common/testing';
|
||||
import {SpyLocation} from '@angular/common/testing';
|
||||
import {NO_ERRORS_SCHEMA} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Observable, of} from 'rxjs';
|
||||
|
||||
import { PhoneListComponent } from './phone-list.component';
|
||||
import { Phone, PhoneData } from '../core/phone/phone.service';
|
||||
import {Phone, PhoneData} from '../core/phone/phone.service';
|
||||
|
||||
import {PhoneListComponent} from './phone-list.component';
|
||||
|
||||
class ActivatedRouteMock {
|
||||
constructor(public snapshot: any) {}
|
||||
@ -16,8 +17,7 @@ class ActivatedRouteMock {
|
||||
class MockPhone {
|
||||
query(): Observable<PhoneData[]> {
|
||||
return of([
|
||||
{name: 'Nexus S', snippet: '', images: []},
|
||||
{name: 'Motorola DROID', snippet: '', images: []}
|
||||
{name: 'Nexus S', snippet: '', images: []}, {name: 'Motorola DROID', snippet: '', images: []}
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -25,18 +25,18 @@ class MockPhone {
|
||||
let fixture: ComponentFixture<PhoneListComponent>;
|
||||
|
||||
describe('PhoneList', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PhoneListComponent ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) },
|
||||
{ provide: Location, useClass: SpyLocation },
|
||||
{ provide: Phone, useClass: MockPhone },
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [PhoneListComponent],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: new ActivatedRouteMock({params: {'phoneId': 1}})},
|
||||
{provide: Location, useClass: SpyLocation},
|
||||
{provide: Phone, useClass: MockPhone},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -47,20 +47,15 @@ describe('PhoneList', () => {
|
||||
fixture.detectChanges();
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2);
|
||||
expect(
|
||||
compiled.querySelector('.phone-list-item:nth-child(1)').textContent
|
||||
).toContain('Motorola DROID');
|
||||
expect(
|
||||
compiled.querySelector('.phone-list-item:nth-child(2)').textContent
|
||||
).toContain('Nexus S');
|
||||
expect(compiled.querySelector('.phone-list-item:nth-child(1)').textContent)
|
||||
.toContain('Motorola DROID');
|
||||
expect(compiled.querySelector('.phone-list-item:nth-child(2)').textContent)
|
||||
.toContain('Nexus S');
|
||||
});
|
||||
|
||||
xit('should set the default value of orderProp model', () => {
|
||||
fixture.detectChanges();
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
expect(
|
||||
compiled.querySelector('select option:last-child').selected
|
||||
).toBe(true);
|
||||
expect(compiled.querySelector('select option:last-child').selected).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,22 +1,16 @@
|
||||
// #docregion
|
||||
// #docregion activatedroute
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
// #enddocregion activatedroute
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PhoneDetailComponent } from './phone-detail.component';
|
||||
import { Phone, PhoneData } from '../core/phone/phone.service';
|
||||
import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe';
|
||||
|
||||
function xyzPhoneData(): PhoneData {
|
||||
return {
|
||||
name: 'phone xyz',
|
||||
snippet: '',
|
||||
images: ['image/url1.png', 'image/url2.png']
|
||||
};
|
||||
return {name: 'phone xyz', snippet: '', images: ['image/url1.png', 'image/url2.png']};
|
||||
}
|
||||
|
||||
class MockPhone {
|
||||
@ -34,10 +28,9 @@ class ActivatedRouteMock {
|
||||
// #enddocregion activatedroute
|
||||
|
||||
describe('PhoneDetailComponent', () => {
|
||||
|
||||
// #docregion activatedroute
|
||||
|
||||
beforeEach(async(() => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CheckmarkPipe, PhoneDetailComponent ],
|
||||
providers: [
|
||||
@ -55,5 +48,4 @@ describe('PhoneDetailComponent', () => {
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,13 +1,14 @@
|
||||
/* tslint:disable */
|
||||
// #docregion routestuff
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SpyLocation } from '@angular/common/testing';
|
||||
import {SpyLocation} from '@angular/common/testing';
|
||||
import {NO_ERRORS_SCHEMA} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {Observable, of} from 'rxjs';
|
||||
|
||||
import { PhoneListComponent } from './phone-list.component';
|
||||
import { Phone, PhoneData } from '../core/phone/phone.service';
|
||||
import {Phone, PhoneData} from '../core/phone/phone.service';
|
||||
|
||||
import {PhoneListComponent} from './phone-list.component';
|
||||
|
||||
// #enddocregion routestuff
|
||||
|
||||
@ -18,8 +19,7 @@ class ActivatedRouteMock {
|
||||
class MockPhone {
|
||||
query(): Observable<PhoneData[]> {
|
||||
return of([
|
||||
{name: 'Nexus S', snippet: '', images: []},
|
||||
{name: 'Motorola DROID', snippet: '', images: []}
|
||||
{name: 'Nexus S', snippet: '', images: []}, {name: 'Motorola DROID', snippet: '', images: []}
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -27,20 +27,20 @@ class MockPhone {
|
||||
let fixture: ComponentFixture<PhoneListComponent>;
|
||||
|
||||
describe('PhoneList', () => {
|
||||
|
||||
// #docregion routestuff
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PhoneListComponent ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) },
|
||||
{ provide: Location, useClass: SpyLocation },
|
||||
{ provide: Phone, useClass: MockPhone },
|
||||
],
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
})
|
||||
.compileComponents();
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed
|
||||
.configureTestingModule({
|
||||
declarations: [PhoneListComponent],
|
||||
providers: [
|
||||
{provide: ActivatedRoute, useValue: new ActivatedRouteMock({params: {'phoneId': 1}})},
|
||||
{provide: Location, useClass: SpyLocation},
|
||||
{provide: Phone, useClass: MockPhone},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -52,20 +52,15 @@ describe('PhoneList', () => {
|
||||
fixture.detectChanges();
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2);
|
||||
expect(
|
||||
compiled.querySelector('.phone-list-item:nth-child(1)').textContent
|
||||
).toContain('Motorola DROID');
|
||||
expect(
|
||||
compiled.querySelector('.phone-list-item:nth-child(2)').textContent
|
||||
).toContain('Nexus S');
|
||||
expect(compiled.querySelector('.phone-list-item:nth-child(1)').textContent)
|
||||
.toContain('Motorola DROID');
|
||||
expect(compiled.querySelector('.phone-list-item:nth-child(2)').textContent)
|
||||
.toContain('Nexus S');
|
||||
});
|
||||
|
||||
xit('should set the default value of orderProp model', () => {
|
||||
fixture.detectChanges();
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
expect(
|
||||
compiled.querySelector('select option:last-child').selected
|
||||
).toBe(true);
|
||||
expect(compiled.querySelector('select option:last-child').selected).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -124,9 +124,9 @@ Data binding plays an important role in communication between a template and its
|
||||
|
||||
Angular pipes let you declare display-value transformations in your template HTML. A class with the `@Pipe` decorator defines a function that transforms input values to output values for display in a view.
|
||||
|
||||
Angular defines various pipes, such as the [date](https://angular.io/api/common/DatePipe) pipe and [currency](https://angular.io/api/common/CurrencyPipe) pipe; for a complete list, see the [Pipes API list](https://angular.io/api?type=pipe). You can also define new pipes.
|
||||
Angular defines various pipes, such as the [date](api/common/DatePipe) pipe and [currency](api/common/CurrencyPipe) pipe; for a complete list, see the [Pipes API list](api?type=pipe). You can also define new pipes.
|
||||
|
||||
To specify a value transformation in an HTML template, use the [pipe operator (|)](https://angular.io/guide/template-expression-operators#pipe).
|
||||
To specify a value transformation in an HTML template, use the [pipe operator (|)](guide/template-expression-operators#pipe).
|
||||
|
||||
`{{interpolated_value | pipe_name}}`
|
||||
|
||||
|
@ -140,7 +140,7 @@ Angular provides *value accessors* for all of the basic HTML form elements and t
|
||||
|
||||
You can't apply `[(ngModel)]` to a non-form native element or a
|
||||
third-party custom component until you write a suitable value accessor. For more information, see
|
||||
the API documentation on [DefaultValueAccessor](https://angular.io/api/forms/DefaultValueAccessor).
|
||||
the API documentation on [DefaultValueAccessor](api/forms/DefaultValueAccessor).
|
||||
|
||||
You don't need a value accessor for an Angular component that
|
||||
you write because you can name the value and event properties
|
||||
|
@ -42,11 +42,11 @@ For example, your `myBuilder` folder could contain the following files.
|
||||
| `src/my-builder.ts` | Main source file for the builder definition. |
|
||||
| `src/my-builder.spec.ts` | Source file for tests. |
|
||||
| `src/schema.json` | Definition of builder input options. |
|
||||
| `builders.json` | Testing configuration. |
|
||||
| `builders.json` | Builders definition. |
|
||||
| `package.json` | Dependencies. See https://docs.npmjs.com/files/package.json. |
|
||||
| `tsconfig.json` | [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). |
|
||||
|
||||
You can publish the builder to `npm` (see [Publishing your Library](https://angular.io/guide/creating-libraries#publishing-your-library)). If you publish it as `@example/my-builder`, you can install it using the following command.
|
||||
You can publish the builder to `npm` (see [Publishing your Library](guide/creating-libraries#publishing-your-library)). If you publish it as `@example/my-builder`, you can install it using the following command.
|
||||
|
||||
<code-example language="sh">
|
||||
|
||||
|
@ -58,6 +58,7 @@ v9 - v12
|
||||
| `@angular/core` | [`ANALYZE_FOR_ENTRY_COMPONENTS`](api/core/ANALYZE_FOR_ENTRY_COMPONENTS) | <!--v9--> v11 |
|
||||
| `@angular/router` | [`loadChildren` string syntax](#loadChildren) | <!--v9--> v11 |
|
||||
| `@angular/core/testing` | [`TestBed.get`](#testing) | <!--v9--> v12 |
|
||||
| `@angular/core/testing` | [`async`](#testing) | <!--v9--> v12 |
|
||||
| `@angular/router` | [`ActivatedRoute` params and `queryParams` properties](#activatedroute-props) | unspecified |
|
||||
| template syntax | [`/deep/`, `>>>`, and `::ng-deep`](#deep-component-style-selector) | <!--v7--> unspecified |
|
||||
| browser support | [`IE 9 and 10, IE mobile`](#ie-9-10-and-mobile) | <!--v10--> v11 |
|
||||
@ -108,6 +109,7 @@ Tip: In the [API reference section](api) of this doc site, deprecated APIs are i
|
||||
| API | Replacement | Deprecation announced | Notes |
|
||||
| --- | ----------- | --------------------- | ----- |
|
||||
| [`TestBed.get`](api/core/testing/TestBed#get) | [`TestBed.inject`](api/core/testing/TestBed#inject) | v9 | Same behavior, but type safe. |
|
||||
| [`async`](api/core/testing/async) | [`waitForAsync`](api/core/testing/waitForAsync) | v10 | Same behavior, but rename to avoid confusion. |
|
||||
|
||||
|
||||
{@a forms}
|
||||
@ -477,7 +479,7 @@ The final decision was made on three key points:
|
||||
|
||||
|
||||
{@a wrapped-value}
|
||||
### `WrappedValue`
|
||||
### `WrappedValue`
|
||||
|
||||
The purpose of `WrappedValue` is to allow the same object instance to be treated as different for the purposes of change detection.
|
||||
It is commonly used with the `async` pipe in the case where the `Observable` produces the same instance of the value.
|
||||
@ -487,7 +489,7 @@ No replacement is planned for this deprecation.
|
||||
|
||||
If you rely on the behavior that the same object instance should cause change detection, you have two options:
|
||||
- Clone the resulting value so that it has a new identity.
|
||||
- Explicitly call [`ChangeDetectorRef.detectChanges()`](api/core/ChangeDetectorRef#detectchanges) to force the update.
|
||||
- Explicitly call [`ChangeDetectorRef.detectChanges()`](api/core/ChangeDetectorRef#detectchanges) to force the update.
|
||||
|
||||
{@a deprecated-cli-flags}
|
||||
## Deprecated CLI APIs and Options
|
||||
|
@ -867,7 +867,7 @@ To learn more, see [Introduction to Services and Dependency Injection](guide/arc
|
||||
|
||||
## structural directives
|
||||
|
||||
A category of [directive](#directive) that is responsible for shaping HTML layout by modifying the DOM&mdashthat is, adding, removing, or manipulating elements and their children.
|
||||
A category of [directive](#directive) that is responsible for shaping HTML layout by modifying the DOM—that is, adding, removing, or manipulating elements and their children.
|
||||
|
||||
To learn more, see [Structural Directives](guide/structural-directives).
|
||||
|
||||
|
@ -58,7 +58,7 @@ While following these steps, you can [explore the translated example app](#app-p
|
||||
|
||||
The following are optional practices that may be required in special cases:
|
||||
|
||||
* [Set the source locale manually](#set-source-manually) if you need to set the [LOCALE_ID](https://angular.io/api/core/LOCALE_ID "API reference for LOCALE_ID") token.
|
||||
* [Set the source locale manually](#set-source-manually) if you need to set the [LOCALE_ID](api/core/LOCALE_ID "API reference for LOCALE_ID") token.
|
||||
* [Import global variants of the locale data](#import-locale) for extra locale data.
|
||||
* [Manage marked text with custom IDs](#custom-id) if you require more control over matching translations.
|
||||
|
||||
@ -77,7 +77,7 @@ This command updates your project's `package.json` and `polyfills.ts` files to i
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For more information about `package.json` and polyfill packages, see [Workspace npm dependencies](https://angular.io/guide/npm-packages).
|
||||
For more information about `package.json` and polyfill packages, see [Workspace npm dependencies](guide/npm-packages).
|
||||
|
||||
</div>
|
||||
|
||||
@ -804,7 +804,7 @@ The following tabs show the example app and its translation files:
|
||||
|
||||
The following are optional practices that may be required in special cases:
|
||||
|
||||
* [Set the source locale manually](#set-source-manually) by setting the [LOCALE_ID](https://angular.io/api/core/LOCALE_ID "API reference for LOCALE_ID") token.
|
||||
* [Set the source locale manually](#set-source-manually) by setting the [LOCALE_ID](api/core/LOCALE_ID "API reference for LOCALE_ID") token.
|
||||
* [Import global variants of the locale data](#import-locale) for extra locale data.
|
||||
* [Manage marked text with custom IDs](#custom-id) if you require more control over matching translations.
|
||||
|
||||
|
93
aio/content/guide/roadmap.md
Normal file
93
aio/content/guide/roadmap.md
Normal file
@ -0,0 +1,93 @@
|
||||
# Angular Roadmap
|
||||
|
||||
Angular receives a large number of feature requests, both from inside Google and from the broader open-source community. At the same time, our list of projects contains plenty of maintenance tasks, code refactorings, potential performance improvements, and so on. We bring together representatives from developer relations, product management, and engineering to prioritize this list. As new projects come into the queue, we regularly position them based on relative priority to other projects. As work gets done, projects will move up in the queue.
|
||||
|
||||
The projects below are not associated with a particular Angular version. We'll release them on completion, and they will be part of a specific version based on our release schedule, following semantic versioning. For example, features are released in the next minor after they are complete, or the next major if they include breaking changes.
|
||||
|
||||
## In Progress
|
||||
|
||||
### Operation Bye Bye Backlog (aka Operation Byelog)
|
||||
|
||||
We are actively investing up to 50% of our engineering capacity on triaging issues and PRs until we have a clear understanding of broader community needs. After that, we'll commit up to 20% of our engineering capacity to keep up with new submissions promptly.
|
||||
|
||||
### Support TypeScript 4.0
|
||||
|
||||
We're working on adding support for TypeScript 4.0 ahead of its stable release. We always want Angular to stay up-to-date with the latest version of TypeScript so that developers get the best the language has to offer.
|
||||
|
||||
### Update our e2e testing strategy
|
||||
|
||||
To ensure we provide a future-proof e2e testing strategy, we want to evaluate the state of Protractor, community innovations, e2e best practices, and explore novel opportunities.
|
||||
|
||||
### Angular libraries use Ivy
|
||||
|
||||
We are investing in the design and development of Ivy library distribution plan, which will include an update of the library package format to use Ivy compilation, unblock the deprecation of the View Engine library format, and [ngcc](guide/glossary#ngcc).
|
||||
|
||||
### Evaluate future RxJS changes (v7 and beyond)
|
||||
|
||||
We want to ensure Angular developers are taking advantage of the latest capabilities of RxJS and have a smooth transition to the next major releases of the framework. For this purpose, we will explore and document the scope of the changes in v7 and beyond of RxJS and plan an update strategy.
|
||||
|
||||
### Angular language service uses Ivy
|
||||
|
||||
Today the language service still uses the View Engine compiler and type checking, even for Ivy applications. We want to use the Ivy template parser and improved type checking for the Angular Language service to match application behavior. This migration will also be a step towards unblocking the removal of View Engine, which will simplify Angular, reduce the npm package size, and improve the framework's maintainability.
|
||||
|
||||
### Expand component harnesses best practices
|
||||
|
||||
Angular CDK introduced the concept of [component test harnesses](https://material.angular.io/cdk/test-harnesses) to Angular in version 9. Test harnesses allow component authors to create supported APIs for testing component interactions. We're continuing to improve this harness infrastructure and clarifying the best practices around using harnesses. We're also working to drive more harness adoption inside of Google.
|
||||
|
||||
### Support native [Trusted Types](https://web.dev/trusted-types/) in Angular
|
||||
|
||||
In collaboration with Google's security team, we're adding support for the new Trusted Types API. This web platform API will help developers build more secure web applications.
|
||||
|
||||
### Integrate [MDC Web](https://material.io/develop/web/) into Angular Material
|
||||
|
||||
MDC Web is a library created by Google's Material Design team that provides reusable primitives for building Material Design components. The Angular team is incorporating these primitives into Angular Material. Using MDC Web will align Angular Material more closely with the Material Design specification, expand accessibility, overall improve component quality, and improve our team's velocity.
|
||||
|
||||
### Offer Google engineers better integration with Angular and Google's internal server stack
|
||||
|
||||
This is an internal project to add support for Angular front-ends to Google's internal integrated server stack.
|
||||
|
||||
### Angular versioning & branching
|
||||
|
||||
We want to consolidate release management tooling between Angular's multiple GitHub repositories ([angular/angular](https://github.com/angular/angular), [angular/angular-cli](https://github.com/angular/angular-cli), and [angular/components](https://github.com/angular/components)). This effort will allow us to reuse infrastructure, unify and simplify processes, and improve our release process's reliability.
|
||||
|
||||
## Future
|
||||
|
||||
### Refresh introductory documentation
|
||||
|
||||
We will redefine the user learning journeys and refresh the introductory documentation. We will clearly state the benefits of Angular, how to explore its capabilities, and provide guidance so developers can become proficient with the framework in as little time as possible.
|
||||
|
||||
### Strict typing for `@angular/forms`
|
||||
|
||||
We will work on implementing stricter type checking for reactive forms. This way, we will allow developers to catch more issues during development time, enable better text editor and IDE support, and improve the type checking for reactive forms.
|
||||
|
||||
### webpack 5 in the Angular CLI
|
||||
|
||||
Webpack 5 brings a lot of build speed and bundle size improvements. To make them available for Angular developers, we will invest in migrating Angular CLI from using deprecated and removed webpack APIs.
|
||||
|
||||
### Commit message standardization
|
||||
|
||||
We want to unify commit message requirements and conformance across Angular repositories ([angular/angular](https://github.com/angular/angular), [angular/components](https://github.com/angular/components), [angular/angular-cli](https://github.com/angular/angular-cli)) to bring consistency to our development process and reuse infrastructure tooling.
|
||||
|
||||
### Optional Zone.js
|
||||
|
||||
We are going to design and implement a plan to make Zone.js optional from Angular applications. This way, we will simplify the framework, improve debugging, and reduce application bundle size. Additionally, this will allow us to take advantage of native async/await syntax, which currently Zone.js does not support.
|
||||
|
||||
### Remove legacy [View Engine](guide/ivy)
|
||||
|
||||
After the transition of all our internal tooling to Ivy has completed, we want to remove the legacy View Engine for smaller Angular conceptual overhead, smaller package size, lower maintenance cost, and lower complexity of the codebase.
|
||||
|
||||
### Angular DevTools
|
||||
|
||||
We’ll be working on development tooling for Angular that will provide utilities for debugging and performance profiling. This project aims to help developers understand the component structure and the change detection in an Angular application.
|
||||
|
||||
### Optional NgModules
|
||||
|
||||
To simplify the Angular mental model and learning journey, we’ll be working on making NgModules optional. This work will allow developers to develop standalone components and implement an alternative API for declaring the component’s compilation scope.
|
||||
|
||||
### Ergonomic component level code-splitting APIs
|
||||
|
||||
A common problem of web applications is their slow initial load time. A way to improve it is to apply more granular code-splitting on a component level. To encourage this practice, we’ll be working on more ergonomic code-splitting APIs.
|
||||
|
||||
### Migration to ESLint
|
||||
|
||||
With the deprecation of TSLint we will be moving to ESLint. As part of the process, we will work on ensuring backward compatibility with our current recommended TSLint configuration, implement a migration strategy for existing Angular applications and introduce new tooling to the Angular CLI toolchain.
|
@ -53,14 +53,27 @@ This field contains an array of asset groups, each of which defines a set of ass
|
||||
|
||||
```json
|
||||
{
|
||||
"assetGroups": [{
|
||||
...
|
||||
}, {
|
||||
...
|
||||
}]
|
||||
"assetGroups": [
|
||||
{
|
||||
...
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
When the ServiceWorker handles a request, it checks asset groups in the order in which they appear in `ngsw-config.json`.
|
||||
The first asset group that matches the requested resource handles the request.
|
||||
|
||||
It is recommended that you put the more specific asset groups higher in the list.
|
||||
For example, an asset group that matches `/foo.js` should appear before one that matches `*.js`.
|
||||
|
||||
</div>
|
||||
|
||||
Each asset group specifies both a group of resources and a policy that governs them. This policy determines when the resources are fetched and what happens when changes are detected.
|
||||
|
||||
Asset groups follow the Typescript interface shown here:
|
||||
@ -123,6 +136,31 @@ These options are used to modify the matching behavior of requests. They are pas
|
||||
|
||||
Unlike asset resources, data requests are not versioned along with the app. They're cached according to manually-configured policies that are more useful for situations such as API requests and other data dependencies.
|
||||
|
||||
This field contains an array of data groups, each of which defines a set of data resources and the policy by which they are cached.
|
||||
|
||||
```json
|
||||
{
|
||||
"dataGroups": [
|
||||
{
|
||||
...
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
When the ServiceWorker handles a request, it checks data groups in the order in which they appear in `ngsw-config.json`.
|
||||
The first data group that matches the requested resource handles the request.
|
||||
|
||||
It is recommended that you put the more specific data groups higher in the list.
|
||||
For example, a data group that matches `/api/foo.json` should appear before one that matches `/api/*.json`.
|
||||
|
||||
</div>
|
||||
|
||||
Data groups follow this Typescript interface:
|
||||
|
||||
```typescript
|
||||
|
@ -159,10 +159,10 @@ It also generates an initial test file for the component, `banner-external.compo
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Because `compileComponents` is asynchronous, it uses
|
||||
the [`async`](api/core/testing/async) utility
|
||||
the [`waitForAsync`](api/core/testing/waitForAsync) utility
|
||||
function imported from `@angular/core/testing`.
|
||||
|
||||
Please refer to the [async](guide/testing-components-scenarios#async) section for more details.
|
||||
Please refer to the [waitForAsync](guide/testing-components-scenarios#waitForAsync) section for more details.
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -402,7 +402,7 @@ There is no nested syntax (like a `Promise.then()`) to disrupt the flow of contr
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Limitation: The `fakeAsync()` function won't work if the test body makes an `XMLHttpRequest` (XHR) call.
|
||||
XHR calls within a test are rare, but if you need to call XHR, see [`async()`](#async), below.
|
||||
XHR calls within a test are rare, but if you need to call XHR, see [`waitForAsync()`](#waitForAsync), below.
|
||||
|
||||
</div>
|
||||
|
||||
@ -587,41 +587,41 @@ Then call `detectChanges()` to tell Angular to update the screen.
|
||||
|
||||
Then you can assert that the quote element displays the expected text.
|
||||
|
||||
{@a async}
|
||||
{@a waitForAsync}
|
||||
|
||||
#### Async test with _async()_
|
||||
#### Async test with _waitForAsync()_
|
||||
|
||||
To use `async()` functionality, you must import `zone.js/dist/zone-testing` in your test setup file.
|
||||
To use `waitForAsync()` functionality, you must import `zone.js/dist/zone-testing` in your test setup file.
|
||||
If you created your project with the Angular CLI, `zone-testing` is already imported in `src/test.ts`.
|
||||
|
||||
The `fakeAsync()` utility function has a few limitations.
|
||||
In particular, it won't work if the test body makes an `XMLHttpRequest` (XHR) call.
|
||||
XHR calls within a test are rare so you can generally stick with [`fakeAsync()`](#fake-async).
|
||||
But if you ever do need to call `XMLHttpRequest`, you'll want to know about `async()`.
|
||||
But if you ever do need to call `XMLHttpRequest`, you'll want to know about `waitForAsync()`.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `TestBed.compileComponents()` method (see [below](#compile-components)) calls `XHR`
|
||||
to read external template and css files during "just-in-time" compilation.
|
||||
Write tests that call `compileComponents()` with the `async()` utility.
|
||||
Write tests that call `compileComponents()` with the `waitForAsync()` utility.
|
||||
|
||||
</div>
|
||||
|
||||
Here's the previous `fakeAsync()` test, re-written with the `async()` utility.
|
||||
Here's the previous `fakeAsync()` test, re-written with the `waitForAsync()` utility.
|
||||
|
||||
<code-example
|
||||
path="testing/src/app/twain/twain.component.spec.ts"
|
||||
region="async-test">
|
||||
</code-example>
|
||||
|
||||
The `async()` utility hides some asynchronous boilerplate by arranging for the tester's code
|
||||
The `waitForAsync()` utility hides some asynchronous boilerplate by arranging for the tester's code
|
||||
to run in a special _async test zone_.
|
||||
You don't need to pass Jasmine's `done()` into the test and call `done()` because it is `undefined` in promise or observable callbacks.
|
||||
|
||||
But the test's asynchronous nature is revealed by the call to `fixture.whenStable()`,
|
||||
which breaks the linear flow of control.
|
||||
|
||||
When using an `intervalTimer()` such as `setInterval()` in `async()`, remember to cancel the timer with `clearInterval()` after the test, otherwise the `async()` never ends.
|
||||
When using an `intervalTimer()` such as `setInterval()` in `waitForAsync()`, remember to cancel the timer with `clearInterval()` after the test, otherwise the `waitForAsync()` never ends.
|
||||
|
||||
{@a when-stable}
|
||||
|
||||
@ -641,18 +641,18 @@ update the quote element with the expected text.
|
||||
|
||||
#### Jasmine _done()_
|
||||
|
||||
While the `async()` and `fakeAsync()` functions greatly
|
||||
While the `waitForAsync()` and `fakeAsync()` functions greatly
|
||||
simplify Angular asynchronous testing,
|
||||
you can still fall back to the traditional technique
|
||||
and pass `it` a function that takes a
|
||||
[`done` callback](https://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support).
|
||||
|
||||
You can't call `done()` in `async()` or `fakeAsync()` functions, because the `done parameter`
|
||||
You can't call `done()` in `waitForAsync()` or `fakeAsync()` functions, because the `done parameter`
|
||||
is `undefined`.
|
||||
|
||||
Now you are responsible for chaining promises, handling errors, and calling `done()` at the appropriate moments.
|
||||
|
||||
Writing test functions with `done()`, is more cumbersome than `async()`and `fakeAsync()`, but it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`.
|
||||
Writing test functions with `done()`, is more cumbersome than `waitForAsync()`and `fakeAsync()`, but it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`.
|
||||
|
||||
Here are two more versions of the previous test, written with `done()`.
|
||||
The first one subscribes to the `Observable` exposed to the template by the component's `quote` property.
|
||||
@ -738,7 +738,7 @@ you tell the `TestScheduler` to _flush_ its queue of prepared tasks like this.
|
||||
region="test-scheduler-flush"></code-example>
|
||||
|
||||
This step serves a purpose analogous to [tick()](api/core/testing/tick) and `whenStable()` in the
|
||||
earlier `fakeAsync()` and `async()` examples.
|
||||
earlier `fakeAsync()` and `waitForAsync()` examples.
|
||||
The balance of the test is the same as those examples.
|
||||
|
||||
#### Marble error testing
|
||||
@ -1535,7 +1535,7 @@ You must call `compileComponents()` within an asynchronous test function.
|
||||
<div class="alert is-critical">
|
||||
|
||||
If you neglect to make the test function async
|
||||
(e.g., forget to use `async()` as described below),
|
||||
(e.g., forget to use `waitForAsync()` as described below),
|
||||
you'll see this error message
|
||||
|
||||
<code-example language="sh" class="code-shell" hideCopy>
|
||||
@ -1549,7 +1549,7 @@ A typical approach is to divide the setup logic into two separate `beforeEach()`
|
||||
1. An async `beforeEach()` that compiles the components
|
||||
1. A synchronous `beforeEach()` that performs the remaining setup.
|
||||
|
||||
To follow this pattern, import the `async()` helper with the other testing symbols.
|
||||
To follow this pattern, import the `waitForAsync()` helper with the other testing symbols.
|
||||
|
||||
<code-example
|
||||
path="testing/src/app/banner/banner-external.component.spec.ts"
|
||||
@ -1565,7 +1565,7 @@ Write the first async `beforeEach` like this.
|
||||
region="async-before-each"
|
||||
header="app/banner/banner-external.component.spec.ts (async beforeEach)"></code-example>
|
||||
|
||||
The `async()` helper function takes a parameterless function with the body of the setup.
|
||||
The `waitForAsync()` helper function takes a parameterless function with the body of the setup.
|
||||
|
||||
The `TestBed.configureTestingModule()` method returns the `TestBed` class so you can chain
|
||||
calls to other `TestBed` static methods such as `compileComponents()`.
|
||||
|
@ -25,7 +25,7 @@ Here's a summary of the stand-alone functions, in order of likely utility:
|
||||
<td>
|
||||
|
||||
Runs the body of a test (`it`) or setup (`beforeEach`) function within a special _async test zone_.
|
||||
See [discussion above](guide/testing-components-scenarios#async).
|
||||
See [discussion above](guide/testing-components-scenarios#waitForAsync).
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -306,9 +306,9 @@ If you develop angular locally with `ng serve`, a `websocket` connection is set
|
||||
In Windows, by default, one application can only have 6 websocket connections, <a href="https://msdn.microsoft.com/library/ee330736%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#websocket_maxconn" title="MSDN WebSocket settings">MSDN WebSocket Settings</a>.
|
||||
So when IE is refreshed (manually or automatically by `ng serve`), sometimes the websocket does not close properly. When websocket connections exceed the limitations, a `SecurityError` will be thrown. This error will not affect the angular application, you can just restart IE to clear this error, or modify the windows registry to update the limitations.
|
||||
|
||||
## Appendix: Test using `fakeAsync()/async()`
|
||||
## Appendix: Test using `fakeAsync()/waitForAsync()`
|
||||
|
||||
If you use the `fakeAsync()/async()` helper function to run unit tests (for details, read the [Testing guide](guide/testing-components-scenarios#fake-async)), you need to import `zone.js/dist/zone-testing` in your test setup file.
|
||||
If you use the `fakeAsync()/waitForAsync()` helper function to run unit tests (for details, read the [Testing guide](guide/testing-components-scenarios#fake-async)), you need to import `zone.js/dist/zone-testing` in your test setup file.
|
||||
|
||||
<div class="alert is-important">
|
||||
If you create project with `Angular/CLI`, it is already imported in `src/test.ts`.
|
||||
|
@ -820,6 +820,11 @@
|
||||
"title": "Release Practices",
|
||||
"tooltip": "Angular versioning, release, support, and deprecation policies and practices."
|
||||
},
|
||||
{
|
||||
"url": "guide/roadmap",
|
||||
"title": "Roadmap",
|
||||
"tooltip": "Roadmap of the Angular team."
|
||||
},
|
||||
{
|
||||
"title": "Updating to Version 10",
|
||||
"tooltip": "Support for updating your application from version 9 to 10.",
|
||||
|
Reference in New Issue
Block a user