This repository was archived by the owner on Dec 4, 2017. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 877
docs(hierarchical-di): Correct avoidance example (#3086) and more #3091
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
127 changes: 87 additions & 40 deletions
127
public/docs/_examples/hierarchical-dependency-injection/e2e-spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,103 @@ | ||
import { browser, element, by } from 'protractor'; | ||
'use strict'; // necessary for es6 output in node | ||
|
||
describe('Hierarchical dependency injection', function () { | ||
import { browser, by, element } from 'protractor'; | ||
|
||
beforeEach(function () { | ||
describe('Hierarchical dependency injection', () => { | ||
|
||
beforeAll(() => { | ||
browser.get(''); | ||
}); | ||
|
||
it('should open with a card view', function () { | ||
expect(element.all(by.cssContainingText('button', 'edit')).get(0).isDisplayed()).toBe(true, | ||
'edit button should be displayed'); | ||
}); | ||
describe('Heroes Scenario', () => { | ||
let page = { | ||
heroName: '', | ||
income: '', | ||
|
||
it('should have multiple heroes listed', function () { | ||
expect(element.all(by.css('heroes-list li')).count()).toBeGreaterThan(1); | ||
}); | ||
// queries | ||
heroEl: element.all(by.css('heroes-list li')).get(0), // first hero | ||
heroCardEl: element(by.css('heroes-list hero-tax-return')), // first hero tax-return | ||
taxReturnNameEl: element.all(by.css('heroes-list hero-tax-return #name')).get(0), | ||
incomeInputEl: element.all(by.css('heroes-list hero-tax-return input')).get(0), | ||
cancelButtonEl: element(by.cssContainingText('heroes-list hero-tax-return button', 'Cancel')), | ||
closeButtonEl: element(by.cssContainingText('heroes-list hero-tax-return button', 'Close')), | ||
saveButtonEl: element(by.cssContainingText('heroes-list hero-tax-return button', 'Save')) | ||
}; | ||
|
||
it('should change to editor view after selection', function () { | ||
let editButtonEle = element.all(by.cssContainingText('button', 'edit')).get(0); | ||
editButtonEle.click().then(function() { | ||
expect(editButtonEle.isDisplayed()).toBe(false, 'edit button should be hidden after selection'); | ||
it('should list multiple heroes', () => { | ||
expect(element.all(by.css('heroes-list li')).count()).toBeGreaterThan(1); | ||
}); | ||
|
||
it('should show no hero tax-returns at the start', () => { | ||
expect(element.all(by.css('heroes-list li hero-tax-return')).count()).toBe(0); | ||
}); | ||
|
||
it('should open first hero in hero-tax-return view after click', () => { | ||
page.heroEl.getText() | ||
.then(val => { | ||
page.heroName = val; | ||
}) | ||
.then(() => page.heroEl.click()) | ||
.then(() => { | ||
expect(page.heroCardEl.isDisplayed()).toBe(true); | ||
}); | ||
}); | ||
|
||
it('hero tax-return should have first hero\'s name', () => { | ||
// Not `page.tax-returnNameInputEl.getAttribute('value')` although later that is essential | ||
expect(page.taxReturnNameEl.getText()).toEqual(page.heroName); | ||
}); | ||
|
||
it('should be able to cancel change', () => { | ||
page.incomeInputEl.clear() | ||
.then(() => page.incomeInputEl.sendKeys('777')) | ||
.then(() => { | ||
expect(page.incomeInputEl.getAttribute('value')).toBe('777', 'income should be 777'); | ||
return page.cancelButtonEl.click(); | ||
}) | ||
.then(() => { | ||
expect(page.incomeInputEl.getAttribute('value')).not.toBe('777', 'income should not be 777'); | ||
}); | ||
}); | ||
|
||
it('should be able to save change', () => { | ||
page.incomeInputEl.clear() | ||
.then(() => page.incomeInputEl.sendKeys('999')) | ||
.then(() => { | ||
expect(page.incomeInputEl.getAttribute('value')).toBe('999', 'income should be 999'); | ||
return page.saveButtonEl.click(); | ||
}) | ||
.then(() => { | ||
expect(page.incomeInputEl.getAttribute('value')).toBe('999', 'income should still be 999'); | ||
}); | ||
}); | ||
|
||
it('should be able to close tax-return', () => { | ||
page.saveButtonEl.click() | ||
.then(() => { | ||
expect(element.all(by.css('heroes-list li hero-tax-return')).count()).toBe(0); | ||
}); | ||
}); | ||
}); | ||
|
||
it('should be able to save editor change', function () { | ||
testEdit(true); | ||
}); | ||
|
||
it('should be able to cancel editor change', function () { | ||
testEdit(false); | ||
describe('Villains Scenario', () => { | ||
it('should list multiple villains', () => { | ||
expect(element.all(by.css('villains-list li')).count()).toBeGreaterThan(1); | ||
}); | ||
}); | ||
|
||
function testEdit(shouldSave: boolean) { | ||
// select 2nd ele | ||
let heroEle = element.all(by.css('heroes-list li')).get(1); | ||
// get the 2nd span which is the name of the hero | ||
let heroNameEle = heroEle.all(by.css('hero-card span')).get(1); | ||
let editButtonEle = heroEle.element(by.cssContainingText('button', 'edit')); | ||
editButtonEle.click().then(function() { | ||
let inputEle = heroEle.element(by.css('hero-editor input')); | ||
return inputEle.sendKeys('foo'); | ||
}).then(function() { | ||
let buttonName = shouldSave ? 'save' : 'cancel'; | ||
let buttonEle = heroEle.element(by.cssContainingText('button', buttonName)); | ||
return buttonEle.click(); | ||
}).then(function() { | ||
if (shouldSave) { | ||
expect(heroNameEle.getText()).toContain('foo'); | ||
} else { | ||
expect(heroNameEle.getText()).not.toContain('foo'); | ||
} | ||
}); | ||
} | ||
describe('Cars Scenario', () => { | ||
|
||
it('A-component should use expected services', () => { | ||
expect(element(by.css('a-car')).getText()).toContain('C1-E1-T1'); | ||
}); | ||
|
||
it('B-component should use expected services', () => { | ||
expect(element(by.css('b-car')).getText()).toContain('C2-E2-T1'); | ||
}); | ||
|
||
it('C-component should use expected services', () => { | ||
expect(element(by.css('c-car')).getText()).toContain('C3-E2-T1'); | ||
}); | ||
}); | ||
}); |
21 changes: 21 additions & 0 deletions
21
public/docs/_examples/hierarchical-dependency-injection/ts/app/app.component.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { Component } from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'my-app', | ||
template: ` | ||
<label><input type="checkbox" [checked]="showHeroes" (change)="showHeroes=!showHeroes">Heroes</label> | ||
<label><input type="checkbox" [checked]="showVillains" (change)="showVillains=!showVillains">Villains</label> | ||
<label><input type="checkbox" [checked]="showCars" (change)="showCars=!showCars">Cars</label> | ||
|
||
<h1>Hierarchical Dependency Injection</h1> | ||
|
||
<heroes-list *ngIf="showHeroes"></heroes-list> | ||
<villains-list *ngIf="showVillains"></villains-list> | ||
<my-cars *ngIf="showCars"></my-cars> | ||
` | ||
}) | ||
export class AppComponent { | ||
showCars = true; | ||
showHeroes = true; | ||
showVillains = true; | ||
} |
46 changes: 18 additions & 28 deletions
46
public/docs/_examples/hierarchical-dependency-injection/ts/app/app.module.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,33 @@ | ||
// #docregion | ||
import { NgModule } from '@angular/core'; | ||
import { NgModule } from '@angular/core'; | ||
import { BrowserModule } from '@angular/platform-browser'; | ||
import { FormsModule } from '@angular/forms'; | ||
import { FormsModule } from '@angular/forms'; | ||
|
||
import { HeroesListComponent } from './heroes-list.component'; | ||
import { HeroEditorComponent } from './hero-editor.component'; | ||
import { HeroCardComponent } from './hero-card.component'; | ||
import { HeroesService } from './heroes.service'; | ||
import { AppComponent } from './app.component'; | ||
import { HeroTaxReturnComponent } from './hero-tax-return.component'; | ||
import { HeroesListComponent } from './heroes-list.component'; | ||
import { HeroesService } from './heroes.service'; | ||
import { VillainsListComponent } from './villains-list.component'; | ||
|
||
import { carComponents, carServices } from './car.components'; | ||
|
||
@NgModule({ | ||
imports: [ | ||
BrowserModule, | ||
FormsModule | ||
], | ||
providers: [ HeroesService ], | ||
providers: [ | ||
carServices, | ||
HeroesService | ||
], | ||
declarations: [ | ||
AppComponent, | ||
carComponents, | ||
HeroesListComponent, | ||
HeroCardComponent, | ||
HeroEditorComponent | ||
HeroTaxReturnComponent, | ||
VillainsListComponent | ||
], | ||
bootstrap: [ HeroesListComponent ] | ||
bootstrap: [ AppComponent ] | ||
}) | ||
export class AppModule { } | ||
|
||
/* Documentation artifact below | ||
// #docregion bad-alternative | ||
// Don't do this! | ||
@NgModule({ | ||
imports: [ | ||
BrowserModule, | ||
FormsModule | ||
], | ||
providers: [ HeroesService, RestoreService ], | ||
declarations: [ HeroesListComponent ], | ||
bootstrap: [ | ||
HeroesListComponent, | ||
HeroCardComponent, | ||
HeroEditorComponent | ||
] | ||
}) | ||
// #enddocregion bad-alternative | ||
*/ |
74 changes: 74 additions & 0 deletions
74
public/docs/_examples/hierarchical-dependency-injection/ts/app/car.components.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { Component } from '@angular/core'; | ||
|
||
import { | ||
CarService, CarService2, CarService3, | ||
EngineService, EngineService2, TiresService | ||
} from './car.services'; | ||
|
||
////////// CCarComponent //////////// | ||
@Component({ | ||
selector: 'c-car', | ||
template: `<div>C: {{description}}</div>`, | ||
providers: [ | ||
{ provide: CarService, useClass: CarService3 } | ||
] | ||
}) | ||
export class CCarComponent { | ||
description: string; | ||
constructor(carService: CarService) { | ||
this.description = `${carService.getCar().description} (${carService.name})`; | ||
} | ||
} | ||
|
||
////////// BCarComponent //////////// | ||
@Component({ | ||
selector: 'b-car', | ||
template: ` | ||
<div>B: {{description}}</div> | ||
<c-car></c-car> | ||
`, | ||
providers: [ | ||
{ provide: CarService, useClass: CarService2 }, | ||
{ provide: EngineService, useClass: EngineService2 } | ||
] | ||
}) | ||
export class BCarComponent { | ||
description: string; | ||
constructor(carService: CarService) { | ||
this.description = `${carService.getCar().description} (${carService.name})`; | ||
} | ||
} | ||
|
||
////////// ACarComponent //////////// | ||
@Component({ | ||
selector: 'a-car', | ||
template: ` | ||
<div>A: {{description}}</div> | ||
<b-car></b-car>` | ||
}) | ||
export class ACarComponent { | ||
description: string; | ||
constructor(carService: CarService) { | ||
this.description = `${carService.getCar().description} (${carService.name})`; | ||
} | ||
} | ||
////////// CarsComponent //////////// | ||
@Component({ | ||
selector: 'my-cars', | ||
template: ` | ||
<h3>Cars</h3> | ||
<a-car></a-car>` | ||
}) | ||
export class CarsComponent { } | ||
|
||
//////////////// | ||
|
||
export const carComponents = [ | ||
CarsComponent, | ||
ACarComponent, BCarComponent, CCarComponent | ||
]; | ||
|
||
// generic car-related services | ||
export const carServices = [ | ||
CarService, EngineService, TiresService | ||
]; |
95 changes: 95 additions & 0 deletions
95
public/docs/_examples/hierarchical-dependency-injection/ts/app/car.services.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { Injectable } from '@angular/core'; | ||
|
||
/// Model /// | ||
export class Car { | ||
name = 'Avocado Motors'; | ||
constructor(public engine: Engine, public tires: Tires) { } | ||
|
||
get description() { | ||
return `${this.name} car with ` + | ||
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`; | ||
} | ||
} | ||
|
||
export class Engine { | ||
cylinders = 4; | ||
} | ||
|
||
export class Tires { | ||
make = 'Flintstone'; | ||
model = 'Square'; | ||
} | ||
|
||
//// Engine services /// | ||
@Injectable() | ||
export class EngineService { | ||
id = 'E1'; | ||
getEngine() { return new Engine(); } | ||
} | ||
|
||
@Injectable() | ||
export class EngineService2 { | ||
id = 'E2'; | ||
getEngine() { | ||
const eng = new Engine(); | ||
eng.cylinders = 8; | ||
return eng; | ||
} | ||
} | ||
|
||
//// Tire services /// | ||
@Injectable() | ||
export class TiresService { | ||
id = 'T1'; | ||
getTires() { return new Tires(); } | ||
} | ||
|
||
/// Car Services /// | ||
@Injectable() | ||
export class CarService { | ||
id = 'C1'; | ||
constructor( | ||
protected engineService: EngineService, | ||
protected tiresService: TiresService) { } | ||
|
||
getCar() { | ||
return new Car( | ||
this.engineService.getEngine(), | ||
this.tiresService.getTires()); | ||
} | ||
|
||
get name() { | ||
return `${this.id}-${this.engineService.id}-${this.tiresService.id}`; | ||
} | ||
} | ||
|
||
@Injectable() | ||
export class CarService2 extends CarService { | ||
id = 'C2'; | ||
constructor( | ||
protected engineService: EngineService, | ||
protected tiresService: TiresService) { | ||
super(engineService, tiresService); | ||
} | ||
getCar() { | ||
const car = super.getCar(); | ||
car.name = 'BamBam Motors, BroVan 2000'; | ||
return car; | ||
} | ||
} | ||
|
||
@Injectable() | ||
export class CarService3 extends CarService2 { | ||
id = 'C3'; | ||
constructor( | ||
protected engineService: EngineService, | ||
protected tiresService: TiresService) { | ||
super(engineService, tiresService); | ||
} | ||
getCar() { | ||
const car = super.getCar(); | ||
car.name = 'Chizzamm Motors, Calico UltraMax Supreme'; | ||
return car; | ||
} | ||
} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting usage of the Page Objects pattern. There really is no reason to have it separately in smaller tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My reason was that I was gathering together the queries I wanted to do in tests. Wasn't sure how many tests I would write but I was pretty sure the affordances should be involved.
Did it make the tests hard to read?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not at all, I think we should have more page objects like this in our e2e tests overall.