提交 ff08aa62 编写于 作者: J Joe Conway

initial commit

上级 8cf28b49
# Files and directories created by pub
.packages
.pub/
build/
# Remove the following pattern if you wish to check in your lock file
pubspec.lock
# Directory created by dartdoc
doc/api/
*.iml
.idea/
\ No newline at end of file
FROM nginx
COPY ./build/web /usr/share/nginx/html
EXPOSE 80
\ No newline at end of file
## Tour of Heroes: HTTP
Welcome to the example app used in the
[Tour of Heroes: HTTP](https://webdev.dartlang.org/angular/tutorial/toh-pt6) page
of [Dart for the web](https://webdev.dartlang.org).
You can run a [hosted copy](https://webdev.dartlang.org/examples/toh-6) of this
sample. Or run your own copy:
1. Create a local copy of this repo (use the "Clone or download" button above).
2. Get the dependencies: `pub get`
3. Launch a development server: `pub serve`
4. In a browser, open [http://localhost:8080](http://localhost:8080)
In Dartium, you'll see the app right away. In other modern browsers,
you'll have to wait a bit while pub converts the app.
---
*Note:* The content of this repository is generated from the
[Angular docs repository][docs repo] by running the
[dart-doc-syncer](//github.com/dart-lang/dart-doc-syncer) tool.
If you find a problem with this sample's code, please open an [issue][].
[docs repo]: //github.com/dart-lang/site-webdev/tree/master/examples/ng/doc/toh-6
[issue]: //github.com/dart-lang/site-webdev/issues/new?title=examples/ng/doc/toh-6
analyzer:
strong-mode: true
# exclude:
# - path/to/excluded/files/**
# Lint rules and documentation, see http://dart-lang.github.io/linter/lints
linter:
rules:
- cancel_subscriptions
- hash_and_equals
- iterable_contains_unrelated_type
- list_remove_unrelated_type
- test_types_in_equals
- unrelated_type_equality_checks
- valid_regexps
apiVersion: v1
kind: Service
metadata:
name: web-service
namespace: aqueduct-tutorial
spec:
selector:
app: aqueduct-tutorial
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: web-deployment
namespace: aqueduct-tutorial
spec:
replicas: 1
template:
metadata:
labels:
app: aqueduct-tutorial
spec:
containers:
- name: aqueduct-tutorial
imagePullPolicy: Always
image: $IMAGE_NAME
ports:
- containerPort: 80
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: tutorial-ingress
namespace: aqueduct-tutorial
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: aqueduct-tutorial.stablekernel.io
http:
paths:
- path: /
backend:
serviceName: web-service
servicePort: 80
\ No newline at end of file
h1 {
font-size: 1.2em;
color: #999;
margin-bottom: 0;
}
h2 {
font-size: 2em;
margin-top: 0;
padding-top: 0;
}
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.router-link-active {
color: #039be5;
}
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_tour_of_heroes/src/heroes_component.dart';
import 'package:angular_tour_of_heroes/src/hero_service.dart';
import 'package:angular_tour_of_heroes/src/dashboard_component.dart';
import 'package:angular_tour_of_heroes/src/hero_detail_component.dart';
@Component(
selector: 'my-app',
template: '''
<h1>{{title}}</h1>
<nav>
<a [routerLink]="['Dashboard']">Dashboard</a>
<a [routerLink]="['Heroes']">Heroes</a>
</nav>
<router-outlet></router-outlet>''',
styleUrls: const ['app_component.css'],
directives: const [ROUTER_DIRECTIVES],
providers: const [HeroService],
)
@RouteConfig(const [
const Route(
path: '/dashboard',
name: 'Dashboard',
component: DashboardComponent,
useAsDefault: true),
const Route(
path: '/detail/:id', name: 'HeroDetail', component: HeroDetailComponent),
const Route(path: '/heroes', name: 'Heroes', component: HeroesComponent)
])
class AppComponent {
final title = 'Tour of Heroes';
}
// Note: MockClient constructor API forces all InMemoryDataService members to
// be static.
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:angular/angular.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'src/hero.dart';
@Injectable()
class InMemoryDataService extends MockClient {
static final _initialHeroes = [
{'id': 11, 'name': 'Mr. Nice'},
{'id': 12, 'name': 'Narco'},
{'id': 13, 'name': 'Bombasto'},
{'id': 14, 'name': 'Celeritas'},
{'id': 15, 'name': 'Magneta'},
{'id': 16, 'name': 'RubberMan'},
{'id': 17, 'name': 'Dynama'},
{'id': 18, 'name': 'Dr IQ'},
{'id': 19, 'name': 'Magma'},
{'id': 20, 'name': 'Tornado'}
];
static List<Hero> _heroesDb;
static int _nextId;
static Future<Response> _handler(Request request) async {
if (_heroesDb == null) resetDb();
var data;
switch (request.method) {
case 'GET':
final id =
int.parse(request.url.pathSegments.last, onError: (_) => null);
if (id != null) {
data = _heroesDb
.firstWhere((hero) => hero.id == id); // throws if no match
} else {
String prefix = request.url.queryParameters['name'] ?? '';
final regExp = new RegExp(prefix, caseSensitive: false);
data = _heroesDb.where((hero) => hero.name.contains(regExp)).toList();
}
break;
case 'POST':
var name = JSON.decode(request.body)['name'];
var newHero = new Hero(_nextId++, name);
_heroesDb.add(newHero);
data = newHero;
break;
case 'PUT':
var heroChanges = new Hero.fromJson(JSON.decode(request.body));
var targetHero = _heroesDb.firstWhere((h) => h.id == heroChanges.id);
targetHero.name = heroChanges.name;
data = targetHero;
break;
case 'DELETE':
var id = int.parse(request.url.pathSegments.last);
_heroesDb.removeWhere((hero) => hero.id == id);
// No data, so leave it as null.
break;
default:
throw 'Unimplemented HTTP method ${request.method}';
}
return new Response(JSON.encode({'data': data}), 200,
headers: {'content-type': 'application/json'});
}
static resetDb() {
_heroesDb = _initialHeroes.map((json) => new Hero.fromJson(json)).toList();
_nextId = _heroesDb.map((hero) => hero.id).fold(0, max) + 1;
}
static String lookUpName(int id) =>
_heroesDb.firstWhere((hero) => hero.id == id, orElse: null)?.name;
InMemoryDataService() : super(_handler);
}
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center; margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'hero.dart';
import 'hero_service.dart';
import 'hero_search_component.dart';
@Component(
selector: 'my-dashboard',
templateUrl: 'dashboard_component.html',
styleUrls: const ['dashboard_component.css'],
directives: const [CORE_DIRECTIVES, HeroSearchComponent, ROUTER_DIRECTIVES],
)
class DashboardComponent implements OnInit {
List<Hero> heroes;
final HeroService _heroService;
DashboardComponent(this._heroService);
Future<Null> ngOnInit() async {
heroes = (await _heroService.getHeroes()).skip(1).take(4).toList();
}
}
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" [routerLink]="['HeroDetail', {id: hero.id.toString()}]" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
<hero-search></hero-search>
class Hero {
final int id;
String name;
Hero(this.id, this.name);
factory Hero.fromJson(Map<String, dynamic> hero) =>
new Hero(_toInt(hero['id']), hero['name']);
Map toJson() => {'id': id, 'name': name};
}
int _toInt(id) => id is int ? id : int.parse(id);
label {
display: inline-block;
width: 3em;
margin: .5em 0;
color: #607D8B;
font-weight: bold;
}
input {
height: 2em;
font-size: 1em;
padding-left: .4em;
}
button {
margin-top: 20px;
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer; cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #ccc;
cursor: auto;
}
import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'package:angular_router/angular_router.dart';
import 'hero.dart';
import 'hero_service.dart';
@Component(
selector: 'hero-detail',
templateUrl: 'hero_detail_component.html',
styleUrls: const ['hero_detail_component.css'],
directives: const [CORE_DIRECTIVES, formDirectives],
)
class HeroDetailComponent implements OnInit {
Hero hero;
final HeroService _heroService;
final RouteParams _routeParams;
final Location _location;
HeroDetailComponent(this._heroService, this._routeParams, this._location);
Future<Null> ngOnInit() async {
var _id = _routeParams.get('id');
var id = int.parse(_id ?? '', onError: (_) => null);
if (id != null) hero = await (_heroService.getHero(id));
}
Future<Null> save() async {
await _heroService.update(hero);
goBack();
}
void goBack() => _location.back();
}
<div *ngIf="hero != null">
<h2>{{hero.name}} details!</h2>
<div>
<label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name" />
</div>
<button (click)="goBack()">Back</button>
<button (click)="save()">Save</button>
</div>
.search-result {
border-bottom: 1px solid gray;
border-left: 1px solid gray;
border-right: 1px solid gray;
width:195px;
height: 20px;
padding: 5px;
background-color: white;
cursor: pointer;
}
#search-box {
width: 200px;
height: 20px;
}
import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:stream_transform/stream_transform.dart';
import 'hero_search_service.dart';
import 'hero.dart';
@Component(
selector: 'hero-search',
templateUrl: 'hero_search_component.html',
styleUrls: const ['hero_search_component.css'],
directives: const [CORE_DIRECTIVES],
providers: const [HeroSearchService],
pipes: const [COMMON_PIPES],
)
class HeroSearchComponent implements OnInit {
HeroSearchService _heroSearchService;
Router _router;
Stream<List<Hero>> heroes;
StreamController<String> _searchTerms = new StreamController<String>.broadcast();
HeroSearchComponent(this._heroSearchService, this._router) {}
// Push a search term into the stream.
void search(String term) => _searchTerms.add(term);
Future<Null> ngOnInit() async {
heroes = _searchTerms.stream
.transform(debounce(new Duration(milliseconds: 300)))
.distinct()
.transform(switchMap((term) => term.isEmpty
? new Stream<List<Hero>>.fromIterable([<Hero>[]])
: _heroSearchService.search(term).asStream()))
.handleError((e) {
print(e); // for demo purposes only
});
}
void gotoDetail(Hero hero) {
var link = [
'HeroDetail',
{'id': hero.id.toString()}
];
_router.navigate(link);
}
}
<div id="search-component">
<h4>Hero Search</h4>
<input #searchBox id="search-box"
(change)="search(searchBox.value)"
(keyup)="search(searchBox.value)" />
<div>
<div *ngFor="let hero of heroes | async"
(click)="gotoDetail(hero)" class="search-result" >
{{hero.name}}
</div>
</div>
</div>
import 'dart:async';
import 'dart:convert';
import 'package:angular/angular.dart';
import 'package:http/http.dart';
import 'hero.dart';
@Injectable()
class HeroSearchService {
final Client _http;
HeroSearchService(this._http);
Future<List<Hero>> search(String term) async {
try {
final response = await _http.get('http://localhost:8888/heroes?name=$term');
return _extractData(response)
.map((json) => new Hero.fromJson(json))
.toList();
} catch (e) {
throw _handleError(e);
}
}
dynamic _extractData(Response resp) => JSON.decode(resp.body);
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return new Exception('Server error; cause: $e');
}
}
import 'dart:async';
import 'dart:convert';
import 'package:angular/angular.dart';
import 'package:http/http.dart';
import 'hero.dart';
@Injectable()
class HeroService {
static final _headers = {'Content-Type': 'application/json'};
static const _heroesUrl = 'http://localhost:8888/heroes'; // URL to web API
final Client _http;
HeroService(this._http);
Future<List<Hero>> getHeroes() async {
try {
final response = await _http.get(_heroesUrl);
final heroes = _extractData(response)
.map((value) => new Hero.fromJson(value))
.toList();
return heroes;
} catch (e) {
throw _handleError(e);
}
}
dynamic _extractData(Response resp) => JSON.decode(resp.body);
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return new Exception('Server error; cause: $e');
}
Future<Hero> getHero(int id) async {
try {
final response = await _http.get('$_heroesUrl/$id');
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
Future<Hero> create(String name) async {
try {
final response = await _http.post(_heroesUrl,
headers: _headers, body: JSON.encode({'name': name}));
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
Future<Hero> update(Hero hero) async {
try {
final url = '$_heroesUrl/${hero.id}';
final response =
await _http.put(url, headers: _headers, body: JSON.encode(hero));
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
Future<Null> delete(int id) async {
try {
final url = '$_heroesUrl/$id';
await _http.delete(url, headers: _headers);
} catch (e) {
throw _handleError(e);
}
}
}
.selected {
background-color: #CFD8DC !important;
color: white;
}
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes li.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.heroes .text {
position: relative;
top: -3px;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button.delete {
float:right;
margin-top: 2px;
margin-right: .8em;
background-color: gray !important;
color:white;
}
import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'hero.dart';
import 'hero_detail_component.dart';
import 'hero_service.dart';
@Component(
selector: 'my-heroes',
templateUrl: 'heroes_component.html',
styleUrls: const ['heroes_component.css'],
directives: const [CORE_DIRECTIVES, HeroDetailComponent],
pipes: const [COMMON_PIPES],
)
class HeroesComponent implements OnInit {
final HeroService _heroService;
final Router _router;
List<Hero> heroes;
Hero selectedHero;
HeroesComponent(this._heroService, this._router);
Future<Null> getHeroes() async {
heroes = await _heroService.getHeroes();
}
Future<Null> add(String name) async {
name = name.trim();
if (name.isEmpty) return;
heroes.add(await _heroService.create(name));
selectedHero = null;
}
Future<Null> delete(Hero hero) async {
await _heroService.delete(hero.id);
heroes.remove(hero);
if (selectedHero == hero) selectedHero = null;
}
void ngOnInit() => getHeroes();
void onSelect(Hero hero) => selectedHero = hero;
Future<Null> gotoDetail() => _router.navigate([
'HeroDetail',
{'id': selectedHero.id.toString()}
]);
}
<h2>My Heroes</h2>
<div>
<label>Hero name:</label> <input #heroName />
<button (click)="add(heroName.value); heroName.value=''">
Add
</button>
</div>
<ul class="heroes">
<li *ngFor="let hero of heroes" (click)="onSelect(hero)"
[class.selected]="hero === selectedHero">
<span class="badge">{{hero.id}}</span>
<span>{{hero.name}}</span>
<button class="delete"
(click)="delete(hero); $event.stopPropagation()">x</button>
</li>
</ul>
<div *ngIf="selectedHero != null">
<h2>
{{selectedHero.name | uppercase}} is my hero
</h2>
<button (click)="gotoDetail()">View Details</button>
</div>
name: angular_tour_of_heroes
description: Tour of Heroes
version: 0.0.1
environment:
sdk: '>=1.24.0 <2.0.0'
dependencies:
angular: ^4.0.0
angular_forms: ^1.0.0
angular_router: ^1.0.2
http: ^0.11.0
stream_transform: ^0.0.6
dev_dependencies:
angular_test: ^1.0.0
browser: ^0.10.0
dart_to_js_script_rewriter: ^1.0.1
mockito: ^2.0.2
test: ^0.12.21
transformers:
- angular:
entry_points:
- web/main.dart
- test/**_test.dart
- test/pub_serve:
$include: test/**_test.dart
- dart_to_js_script_rewriter
Changes/additions relative to toh-5:
- Test bed: add provider for `Client`.
- Reset (mock) DB during setup.
- Test waiting for element update before proceeding.
- Test interaction with (mock) db backend.
- Use `@WithVisibleText()` PO field annotation.
\ No newline at end of file
@Tags(const ['aot'])
@TestOn('browser')
library heroes_test;
import 'package:angular/angular.dart';
import 'package:test/test.dart';
import 'dashboard.dart' as dashboard;
import 'heroes.dart' as heroes;
import 'hero_detail.dart' as hero_detail;
import 'hero_search.dart' as hero_search;
@AngularEntrypoint()
void main() {
group('dashboard:', dashboard.main);
group('heroes:', heroes.main);
group('hero detail:', hero_detail.main);
group('hero search:', hero_search.main);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tests</title>
<script type="application/dart" src="all_test.dart"></script>
<script src="packages/browser/dart.js"></script>
<script src="packages/test/dart.js"></script>
<link rel="x-dart-test" href="all_test.dart">
</head>
<body>
</body>
</html>
import 'dart:async';
import 'package:pageloader/objects.dart';
import 'utils.dart';
class AppPO {
@ByTagName('h1')
PageLoaderElement _h1;
@ByCss('nav a')
List<PageLoaderElement> _tabLinks;
Future<String> get pageTitle => _h1.visibleText;
Future<List<String>> get tabTitles =>
inIndexOrder(_tabLinks.map((el) => el.visibleText)).toList();
}
@Skip('AppComponent tests need bootstrap equivalent for the Router init')
@Tags(const ['aot'])
@TestOn('browser')
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_test/angular_test.dart';
import 'package:angular_tour_of_heroes/app_component.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'app_po.dart';
NgTestFixture<AppComponent> fixture;
AppPO appPO;
final mockPlatformLocation = new MockPlatformLocation();
class MockPlatformLocation extends Mock implements PlatformLocation {}
@AngularEntrypoint()
void main() {
final providers = [
provide(APP_BASE_HREF, useValue: '/'),
provide(ROUTER_PRIMARY_COMPONENT, useValue: AppComponent),
provide(PlatformLocation, useValue: mockPlatformLocation),
];
final testBed = new NgTestBed<AppComponent>().addProviders(providers);
setUpAll(() async {
// Seems like we'd need to do something equivalent to:
// bootstrap(AppComponent);
});
setUp(() async {
fixture = await testBed.create();
appPO = await fixture.resolvePageObject(AppPO);
});
tearDown(disposeAnyRunningTest);
group('Basics:', basicTests);
}
void basicTests() {
test('page title', () async {
expect(await appPO.pageTitle, 'Tour of Heroes');
});
test('tab titles', () async {
final expectTitles = ['Dashboard', 'Heroes'];
expect(await appPO.tabTitles, expectTitles);
});
}
@Tags(const ['aot'])
@TestOn('browser')
import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_test/angular_test.dart';
import 'package:angular_tour_of_heroes/app_component.dart';
import 'package:angular_tour_of_heroes/in_memory_data_service.dart';
import 'package:angular_tour_of_heroes/src/dashboard_component.dart';
import 'package:angular_tour_of_heroes/src/hero_service.dart';
import 'package:http/http.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'dashboard_po.dart';
NgTestFixture<DashboardComponent> fixture;
DashboardPO po;
final mockPlatformLocation = new MockPlatformLocation();
class MockPlatformLocation extends Mock implements PlatformLocation {}
@AngularEntrypoint()
void main() {
final providers = new List.from(ROUTER_PROVIDERS)
..addAll([
provide(APP_BASE_HREF, useValue: '/'),
provide(Client, useClass: InMemoryDataService),
provide(ROUTER_PRIMARY_COMPONENT, useValue: AppComponent),
provide(PlatformLocation, useValue: mockPlatformLocation),
HeroService,
]);
final testBed = new NgTestBed<DashboardComponent>().addProviders(providers);
setUpAll(() async {
when(mockPlatformLocation.pathname).thenReturn('');
when(mockPlatformLocation.search).thenReturn('');
when(mockPlatformLocation.hash).thenReturn('');
when(mockPlatformLocation.getBaseHrefFromDOM()).thenReturn('');
});
setUp(() async {
fixture = await testBed.create();
po = await fixture.resolvePageObject(DashboardPO);
});
tearDown(disposeAnyRunningTest);
test('title', () async {
expect(await po.title, 'Top Heroes');
});
test('show top heroes', () async {
final expectedNames = ['Narco', 'Bombasto', 'Celeritas', 'Magneta'];
expect(await po.heroNames, expectedNames);
});
test('select hero and navigate to detail', () async {
clearInteractions(mockPlatformLocation);
await po.selectHero(3);
final c = verify(mockPlatformLocation.pushState(any, any, captureAny));
expect(c.captured.single, '/detail/15');
});
test('no search no heroes', () async {
expect(await po.heroesFound, []);
});
group('Search hero:', heroSearchTests);
}
void heroSearchTests() {
final matchedHeroNames = [
'Magneta',
'RubberMan',
'Dynama',
'Magma',
];
setUp(() async {
await po.search.type('ma');
await new Future.delayed(const Duration(seconds: 1));
po = await fixture.resolvePageObject(DashboardPO);
});
test('list matching heroes', () async {
expect(await po.heroesFound, matchedHeroNames);
});
}
import 'dart:async';
import 'package:pageloader/objects.dart';
import 'utils.dart';
class DashboardPO {
@FirstByCss('h3')
PageLoaderElement _title;
@ByTagName('a')
List<PageLoaderElement> _heroes;
@ByTagName('input')
PageLoaderElement search;
@ByCss('div[id="search-component"] div div')
List<PageLoaderElement> _heroesFound;
@ByCss('div[id="search-component"]')
PageLoaderElement heroSearchDiv;
Future<String> get title => _title.visibleText;
Future<List<String>> get heroNames =>
inIndexOrder(_heroes.map((el) => el.visibleText)).toList();
Future selectHero(int index) => _heroes[index].click();
Future<List<String>> get heroesFound =>
inIndexOrder(_heroesFound.map((el) => el.visibleText)).toList();
}
@Tags(const ['aot'])
@TestOn('browser')
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_test/angular_test.dart';
import 'package:angular_tour_of_heroes/in_memory_data_service.dart';
import 'package:angular_tour_of_heroes/src/hero_detail_component.dart';
import 'package:angular_tour_of_heroes/src/hero_service.dart';
import 'package:http/http.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'hero_detail_po.dart';
NgTestFixture<HeroDetailComponent> fixture;
HeroDetailPO po;
class MockPlatformLocation extends Mock implements PlatformLocation {}
final mockPlatformLocation = new MockPlatformLocation();
@AngularEntrypoint()
void main() {
final baseProviders = new List.from(ROUTER_PROVIDERS)
..addAll([
provide(APP_BASE_HREF, useValue: '/'),
provide(Client, useClass: InMemoryDataService),
provide(PlatformLocation, useValue: mockPlatformLocation),
provide(RouteParams, useValue: new RouteParams({})),
HeroService,
]);
final testBed =
new NgTestBed<HeroDetailComponent>().addProviders(baseProviders);
setUp(() {
InMemoryDataService.resetDb();
});
tearDown(disposeAnyRunningTest);
test('No initial hero results in an empty view', () async {
fixture = await testBed.create();
expect(fixture.rootElement.text.trim(), '');
});
const targetHero = const {'id': 15, 'name': 'Magneta'};
group('${targetHero['name']} initial hero:', () {
const nameSuffix = 'X';
final Map updatedHero = {
'id': targetHero['id'],
'name': "${targetHero['name']}$nameSuffix"
};
setUp(() async {
final groupTestBed = testBed.fork().addProviders([
provide(RouteParams,
useValue: new RouteParams({'id': targetHero['id'].toString()}))
]);
fixture = await groupTestBed.create();
po = await fixture.resolvePageObject(HeroDetailPO);
});
test('show hero details', () async {
expect(await po.heroFromDetails, targetHero);
});
test('back button', () async {
await po.back();
verify(mockPlatformLocation.back());
});
group('Update name:', () {
setUp(() async {
await po.type(nameSuffix);
});
test('show updated name', () async {
expect(await po.heroFromDetails, updatedHero);
});
test('discard changes', () async {
await po.back();
verify(mockPlatformLocation.back());
final name = InMemoryDataService.lookUpName(targetHero['id']);
expect(name, targetHero['name']);
});
test('save changes and go back', () async {
await po.save();
verify(mockPlatformLocation.back());
final name = InMemoryDataService.lookUpName(targetHero['id']);
expect(name, updatedHero['name']);
});
});
});
}
import 'dart:async';
import 'package:pageloader/objects.dart';
import 'utils.dart';
class HeroDetailPO {
@FirstByCss('div h2')
PageLoaderElement _title; // e.g. 'Mr Freeze details!'
@FirstByCss('div div')
PageLoaderElement _id;
@ByTagName('input')
PageLoaderElement _input;
@ByTagName('button')
@WithVisibleText('Back')
PageLoaderElement _back;
@ByTagName('button')
@WithVisibleText('Save')
PageLoaderElement _save;
Future<Map> get heroFromDetails async {
if (_id == null) return null;
final idAsString = (await _id.visibleText).split(':')[1];
final text = await _title.visibleText;
final matches = new RegExp((r'^(.*) details!$')).firstMatch(text);
return heroData(idAsString, matches[1]);
}
Future clear() => _input.clear();
Future type(String s) => _input.type(s);
Future back() => _back.click();
Future save() => _save.click();
}
@Tags(const ['aot'])
@TestOn('browser')
import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_test/angular_test.dart';
import 'package:angular_tour_of_heroes/app_component.dart';
import 'package:angular_tour_of_heroes/in_memory_data_service.dart';
import 'package:angular_tour_of_heroes/src/hero_search_component.dart';
import 'package:angular_tour_of_heroes/src/hero_service.dart';
import 'package:http/http.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'hero_search_po.dart';
NgTestFixture<HeroSearchComponent> fixture;
HeroSearchPO po;
final mockPlatformLocation = new MockPlatformLocation();
class MockPlatformLocation extends Mock implements PlatformLocation {}
@AngularEntrypoint()
void main() {
final providers = new List.from(ROUTER_PROVIDERS)
..addAll([
provide(APP_BASE_HREF, useValue: '/'),
provide(Client, useClass: InMemoryDataService),
provide(ROUTER_PRIMARY_COMPONENT, useValue: AppComponent),
provide(PlatformLocation, useValue: mockPlatformLocation),
HeroService,
]);
final testBed = new NgTestBed<HeroSearchComponent>().addProviders(providers);
setUpAll(() async {
when(mockPlatformLocation.pathname).thenReturn('');
when(mockPlatformLocation.search).thenReturn('');
when(mockPlatformLocation.hash).thenReturn('');
when(mockPlatformLocation.getBaseHrefFromDOM()).thenReturn('');
});
setUp(() async {
InMemoryDataService.resetDb();
fixture = await testBed.create();
po = await fixture.resolvePageObject(HeroSearchPO);
});
tearDown(disposeAnyRunningTest);
test('title', () async {
expect(await po.title, 'Hero Search');
});
test('initially no heroes found', () async {
expect(await po.heroNames, []);
});
group('Search hero:', heroSearchTests);
}
void heroSearchTests() {
final searchText = 'ma';
setUp(() async {
await _typeSearchTextAndRefreshPO(searchText);
});
test('list heroes matching ${searchText}', () async {
final matchedHeroNames = [
'Magneta',
'RubberMan',
'Dynama',
'Magma',
];
expect(await po.heroNames, matchedHeroNames);
});
test('list heroes matching ${searchText}g', () async {
await _typeSearchTextAndRefreshPO('g');
expect(await po.heroNames, ['Magneta', 'Magma']);
});
test('select hero and navigate to detail', () async {
clearInteractions(mockPlatformLocation);
await po.selectHero(0);
final c = verify(mockPlatformLocation.pushState(any, any, captureAny));
expect(c.captured.single, '/detail/15');
});
}
Future _typeSearchTextAndRefreshPO(String searchText) async {
Future firstHero;
await fixture.update((c) => firstHero = c.heroes.first);
await po.search.type(searchText);
await firstHero;
po = await fixture.resolvePageObject(HeroSearchPO);
}
import 'dart:async';
import 'package:pageloader/objects.dart';
import 'utils.dart';
class HeroSearchPO {
@FirstByCss('h4')
PageLoaderElement _title;
@ByTagName('input')
PageLoaderElement search;
@ByCss('div[id="search-component"] div div')
List<PageLoaderElement> _heroes; // heroes found
Future<String> get title => _title.visibleText;
Future<List<String>> get heroNames =>
inIndexOrder(_heroes.map((el) => el.visibleText)).toList();
Future selectHero(int index) => _heroes[index].click();
}
@Tags(const ['aot'])
@TestOn('browser')
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_test/angular_test.dart';
import 'package:angular_tour_of_heroes/in_memory_data_service.dart';
import 'package:angular_tour_of_heroes/src/heroes_component.dart';
import 'package:angular_tour_of_heroes/src/hero_service.dart';
import 'package:http/http.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'heroes_po.dart';
import 'utils.dart';
const numHeroes = 10;
const targetHeroIndex = 4; // index in full heroes list
const targetHero = const {'id': 15, 'name': 'Magneta'};
NgTestFixture<HeroesComponent> fixture;
HeroesPO po;
final mockRouter = new MockRouter();
class MockRouter extends Mock implements Router {}
@AngularEntrypoint()
void main() {
final testBed = new NgTestBed<HeroesComponent>().addProviders([
provide(Client, useClass: InMemoryDataService),
provide(Router, useValue: mockRouter),
HeroService,
]);
setUp(() async {
InMemoryDataService.resetDb();
fixture = await testBed.create();
po = await fixture.resolvePageObject(HeroesPO);
});
tearDown(disposeAnyRunningTest);
group('Basics:', basicTests);
group('Selected hero:', selectedHeroTests);
group('Add hero:', addHeroTests);
group('Delete hero:', deleteHeroTests);
}
void basicTests() {
test('title', () async {
expect(await po.title, 'My Heroes');
});
test('hero count', () async {
await fixture.update();
expect(po.heroes.length, numHeroes);
});
test('no selected hero', () async {
expect(await po.selectedHero, null);
});
}
void selectedHeroTests() {
setUp(() async {
await po.selectHero(targetHeroIndex);
po = await fixture.resolvePageObject(HeroesPO);
});
test('is selected', () async {
expect(await po.selectedHero, targetHero);
});
test('show mini-detail', () async {
expect(
await po.myHeroNameInUppercase, equalsIgnoringCase(targetHero['name']));
});
test('go to detail', () async {
await po.gotoDetail();
final c = verify(mockRouter.navigate(captureAny));
final linkParams = [
'HeroDetail',
{'id': '${targetHero['id']}'}
];
expect(c.captured.single, linkParams);
});
test('select another hero', () async {
await po.selectHero(0);
po = await fixture.resolvePageObject(HeroesPO);
final heroData = {'id': 11, 'name': 'Mr. Nice'};
expect(await po.selectedHero, heroData);
});
}
void addHeroTests() {
const newHeroName = 'Carl';
setUp(() async {
await po.addHero(newHeroName);
po = await fixture.resolvePageObject(HeroesPO);
});
test('hero count', () async {
expect(po.heroes.length, numHeroes + 1);
});
test('select new hero', () async {
await po.selectHero(numHeroes);
po = await fixture.resolvePageObject(HeroesPO);
expect(po.heroes.length, numHeroes + 1);
expect((await po.selectedHero)['name'], newHeroName);
expect(await po.myHeroNameInUppercase, equalsIgnoringCase(newHeroName));
});
}
void deleteHeroTests() {
List<Map> heroesWithoutTarget = [];
setUp(() async {
heroesWithoutTarget = await inIndexOrder(po.heroes).toList()
..removeAt(targetHeroIndex);
await po.deleteHero(targetHeroIndex);
po = await fixture.resolvePageObject(HeroesPO);
});
test('hero count', () async {
expect(po.heroes.length, numHeroes - 1);
});
test('heroes left', () async {
expect(await inIndexOrder(po.heroes).toList(), heroesWithoutTarget);
});
}
import 'dart:async';
import 'package:pageloader/objects.dart';
import 'utils.dart';
class HeroesPO {
@FirstByCss('h2')
PageLoaderElement _title;
@ByTagName('li')
List<PageLoaderElement> _heroes;
@ByTagName('li')
@WithClass('selected')
@optional
PageLoaderElement _selectedHero;
@FirstByCss('div h2')
@optional
PageLoaderElement _miniDetailHeading;
@ByTagName('button')
@WithVisibleText('View Details')
@optional
PageLoaderElement _gotoDetail;
@ByCss('button')
@WithVisibleText('Add')
PageLoaderElement _add;
@ByCss('li button')
List<PageLoaderElement> _deleteHeroes;
@ByTagName('input')
PageLoaderElement _input;
Future<String> get title => _title.visibleText;
Iterable<Future<Map>> get heroes =>
_heroes.map((el) async => _heroDataFromLi(await el.visibleText));
Future selectHero(int index) => _heroes[index].click();
Future deleteHero(int index) => _deleteHeroes[index].click();
Future<Map> get selectedHero async => _selectedHero == null
? null
: _heroDataFromLi(await _selectedHero.visibleText);
Future<String> get myHeroNameInUppercase async {
if (_miniDetailHeading == null) return null;
final text = await _miniDetailHeading.visibleText;
final matches = new RegExp((r'^(.*) is my hero\s*$')).firstMatch(text);
return matches[1];
}
Future<Null> addHero(String name) async {
await _input.clear();
await _input.type(name);
return _add.click();
}
Future<Null> gotoDetail() async => _gotoDetail.click();
Map<String, dynamic> _heroDataFromLi(String liText) {
final matches = new RegExp((r'^(\d+) (.*) x$')).firstMatch(liText);
return heroData(matches[1], matches[2]);
}
}
import 'dart:async';
Map<String, dynamic> heroData(String idAsString, String name) =>
{'id': int.parse(idAsString, onError: (_) => -1), 'name': name};
Stream<T> inIndexOrder<T>(Iterable<Future<T>> futures) async* {
for (var x in futures) yield await x;
}
<!DOCTYPE html>
<html>
<head>
<script>
// WARNING: DO NOT set the <base href> like this in production!
// Details: https://webdev.dartlang.org/angular/guide/router
(function () {
var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/);
document.write('<base href="' + (m ? m[0] : '/') + '" />');
}());
</script>
<title>Angular Tour of Heroes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<link rel="icon" type="image/png" href="favicon.png">
<script defer src="main.dart" type="application/dart"></script>
<script defer src="packages/browser/dart.js"></script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>
import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_tour_of_heroes/app_component.dart';
import 'package:angular_tour_of_heroes/in_memory_data_service.dart';
import 'package:http/http.dart';
import 'package:http/browser_client.dart';
void main() {
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
// Remove next line in production
provide(LocationStrategy, useClass: HashLocationStrategy),
// Using a real back end?
// Import browser_client.dart and change the above to:
provide(Client, useFactory: () => new BrowserClient(), deps: [])
]);
}
/*
import 'package:http/browser_client.dart';
void main() {
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
// Remove next line in production
provide(LocationStrategy, useClass: HashLocationStrategy),
provide(BrowserClient, useFactory: () => new BrowserClient(), deps: [])
]);
}
*/
@import url(https://fonts.googleapis.com/css?family=Roboto);
@import url(https://fonts.googleapis.com/css?family=Material+Icons);
/* Master Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
body {
margin: 2em;
}
body, input[text], button {
color: #888;
font-family: Cambria, Georgia;
}
a {
cursor: pointer;
cursor: hand;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
/* Navigation link styles */
nav a {
padding: 5px 10px;
text-decoration: none;
margin-right: 10px;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
/* items class */
.items {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 24em;
}
.items li {
cursor: pointer;
position: relative;
left: 0;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.items li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.items li.selected {
background-color: #CFD8DC;
color: white;
}
.items li.selected:hover {
background-color: #BBD8DC;
}
.items .text {
position: relative;
top: -3px;
}
.items .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册