├── .analysis_options
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── lib
├── app.dart
├── app.html
├── components
│ ├── hn_item
│ │ ├── hn_item.dart
│ │ ├── hn_item.html
│ │ └── hn_item.less
│ ├── home
│ │ ├── home.dart
│ │ └── home.html
│ ├── item
│ │ ├── item.dart
│ │ ├── item.html
│ │ └── item_page.less
│ ├── spinner.less
│ └── user_page
│ │ ├── user_page.dart
│ │ ├── user_page.html
│ │ └── user_page.less
├── directives
│ └── parse_html
│ │ └── parse_html.dart
├── ng2_hackernews.dart
├── pipes
│ ├── domain_pipe.dart
│ └── timeago_pipe.dart
└── services
│ └── hn_api.dart
├── pubspec.yaml
├── test
├── domain_pipe_spec.dart
├── hn_item_spec.dart
├── parse_html_directive_spec.dart
└── timeago_pipe_spec.dart
└── web
├── index.html
├── main.dart
└── main.less
/.analysis_options:
--------------------------------------------------------------------------------
1 | analyzer:
2 | strong-mode: true
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.html linguist-language=Dart
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .buildlog
2 | .DS_Store
3 | .idea
4 | .pub/
5 | build/
6 | packages
7 | pubspec.lock
8 | .packages
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.0.1
4 |
5 | - Initial version, created by Stagehand
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Andres Araujo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A HackerNews application made with Angular2 Dart.
2 |
3 | Pretty much inspired from the work by https://github.com/hswolff/hn-ng2 with a few changes and additions
4 |
5 | The goal of this project is to show a sample application to get you started with Angular2 Dart.
6 | It will be updated to work with the most recent Angular 2 version.
7 |
8 | ## Usage
9 |
10 | #### Install dependencies
11 | `pub get`
12 | #### Run local web server
13 | `pub serve`
14 | #### open http://localhost:8080/
15 |
16 | ## [Live Demo](https://andresaraujo.github.io/ng2_hackernews)
17 |
--------------------------------------------------------------------------------
/lib/app.dart:
--------------------------------------------------------------------------------
1 | library ng2_hackernews.app;
2 |
3 | import 'package:angular2/angular2.dart' show Component, NgFor, NgIf, View;
4 | import 'package:angular2/router.dart'
5 | show Route, RouteConfig, Router, RouterOutlet;
6 |
7 | import 'package:ng2_hackernews/components/home/home.dart' show Home;
8 | import 'package:ng2_hackernews/components/item/item.dart' show ItemPage;
9 | import 'package:ng2_hackernews/components/user_page/user_page.dart'
10 | show UserPage;
11 |
12 | @Component(selector: 'app')
13 | @View(
14 | templateUrl: 'package:ng2_hackernews/app.html',
15 | directives: const [NgIf, NgFor, RouterOutlet])
16 | @RouteConfig(const [
17 | const Route(path: '/user/:id', component: UserPage, name: 'User'),
18 | const Route(path: '/item/:id', component: ItemPage, name: 'Item'),
19 | const Route(path: '/home', component: Home, name: 'Home'),
20 | const Route(path: '/', component: Home)
21 | ])
22 | class App {
23 | Router _router;
24 | App(this._router);
25 | goHome() {
26 | _router.navigateByUrl("/home");
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/app.html:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lib/components/hn_item/hn_item.dart:
--------------------------------------------------------------------------------
1 | library hacker_news.components.hnitem;
2 |
3 | import 'package:angular2/angular2.dart';
4 | import 'package:angular2/router.dart';
5 | import 'package:ng2_hackernews/directives/parse_html/parse_html.dart'
6 | show ParseHtml;
7 | import 'package:ng2_hackernews/pipes/domain_pipe.dart' show DomainPipe;
8 | import 'package:ng2_hackernews/services/hn_api.dart' show HNApi;
9 | import 'package:timeago/timeago.dart' show TimeAgo;
10 |
11 | const itemMap = const {'comment': 1, 'job': 2, 'poll': 3, 'story': 4};
12 | final fuzzyTime = new TimeAgo();
13 |
14 | @Component(selector: 'hn-item')
15 | @View(
16 | templateUrl: 'package:ng2_hackernews/components/hn_item/hn_item.html',
17 | directives: const [CORE_DIRECTIVES, HNItem, ParseHtml, RouterLink],
18 | pipes: const [DomainPipe])
19 | class HNItem implements OnInit {
20 | HNApi _hnApi;
21 | String _itemId;
22 |
23 | @Input() bool loadChildren = true;
24 | @Input() bool topLevel = false;
25 |
26 | Map data;
27 | bool collapsed = false;
28 | int type = 0;
29 | String timeAgo;
30 |
31 | HNItem(this._hnApi);
32 |
33 | @Input('itemId')
34 | set newItemId(itemId) => _itemId = itemId.toString();
35 |
36 | ngOnInit() async {
37 | await _fetchData();
38 | }
39 |
40 | _fetchData() async {
41 | data = await _hnApi.fetchItem(_itemId);
42 | if (data != null) {
43 | type = itemMap[data['type']];
44 | timeAgo = fuzzyTime.timeAgo(data['time'] * 1000);
45 | data['type'] = data['type'] != null ? data['type'] : '';
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/components/hn_item/hn_item.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
30 |
31 |
32 |
41 |
42 |
43 |
55 |
56 |
57 |
58 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/lib/components/hn_item/hn_item.less:
--------------------------------------------------------------------------------
1 | hn-item {
2 | display: block;
3 | margin: 10px 0;
4 | }
5 |
6 | .hnItem-title {
7 | font-size: 10pt;
8 | color:#828282;
9 | }
10 |
11 | .hnItem > section {
12 | font-size: 7pt;
13 |
14 | &,
15 | a:link,
16 | a:visited {
17 | color: #828282;
18 | }
19 |
20 | a:hover {
21 | text-decoration: underline;
22 | }
23 | }
24 |
25 | .hnItem--comment {
26 | margin: 15px 0;
27 | }
28 |
29 | .hnItem--coment-content {
30 | margin-top: 5px;
31 | }
32 |
33 | .hnItem--comment-children {
34 | margin-left: 50px;
35 | }
--------------------------------------------------------------------------------
/lib/components/home/home.dart:
--------------------------------------------------------------------------------
1 | library hacker_news.components.home;
2 |
3 | import 'package:angular2/angular2.dart' show Component, NgFor, View;
4 | import 'package:ng2_hackernews/components/hn_item/hn_item.dart' show HNItem;
5 | import 'package:ng2_hackernews/services/hn_api.dart' show HNApi;
6 |
7 | @Component(selector: 'home-page', viewProviders: const [HNApi])
8 | @View(
9 | templateUrl: "package:ng2_hackernews/components/home/home.html",
10 | directives: const [NgFor, HNItem])
11 | class Home {
12 | HNApi api;
13 | Iterable> topStories = [];
14 | Home(this.api) {
15 | api.fetchTopStories().then((Iterable> value) => this.topStories = value);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/components/home/home.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/components/item/item.dart:
--------------------------------------------------------------------------------
1 | library hacker_news.components.item;
2 |
3 | import 'package:angular2/angular2.dart' show Component, NgFor, View;
4 | import 'package:angular2/router.dart' show RouteParams;
5 | import 'package:ng2_hackernews/components/hn_item/hn_item.dart' show HNItem;
6 | import 'package:ng2_hackernews/services/hn_api.dart' show HNApi;
7 |
8 | @Component(selector: 'page-item', viewProviders: const [HNApi])
9 | @View(
10 | templateUrl: 'package:ng2_hackernews/components/item/item.html',
11 | directives: const [NgFor, HNItem])
12 | class ItemPage {
13 | HNApi api;
14 | List childrenIds = [];
15 | String itemId;
16 |
17 | ItemPage(this.api, RouteParams routeParams) {
18 | itemId = routeParams.get("id");
19 | _fetchItem();
20 | }
21 | _fetchItem() async {
22 | Map data = await api.fetchItem("$itemId");
23 | if (data != null) {
24 | childrenIds = data['kids'] as List;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/components/item/item.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/components/item/item_page.less:
--------------------------------------------------------------------------------
1 | .itemDetail {
2 | margin: 10px 20px;
3 | }
4 |
5 | .itemDetail-item {
6 |
7 | &:first-of-type {
8 | margin-top: 30px;
9 | }
10 | }
--------------------------------------------------------------------------------
/lib/components/spinner.less:
--------------------------------------------------------------------------------
1 | // source: https://github.com/tobiasahlin/SpinKit/blob/master/3-wave.html
2 |
3 | .spinner {
4 | width: 50px;
5 | height: 30px;
6 | text-align: center;
7 | font-size: 10px;
8 | }
9 |
10 | .spinner > div {
11 | background-color: #333;
12 | height: 100%;
13 | width: 6px;
14 | display: inline-block;
15 |
16 | -webkit-animation: stretchdelay 1.2s infinite ease-in-out;
17 | animation: stretchdelay 1.2s infinite ease-in-out;
18 | }
19 |
20 | .spinner .rect2 {
21 | -webkit-animation-delay: -1.1s;
22 | animation-delay: -1.1s;
23 | }
24 |
25 | .spinner .rect3 {
26 | -webkit-animation-delay: -1.0s;
27 | animation-delay: -1.0s;
28 | }
29 |
30 | .spinner .rect4 {
31 | -webkit-animation-delay: -0.9s;
32 | animation-delay: -0.9s;
33 | }
34 |
35 | .spinner .rect5 {
36 | -webkit-animation-delay: -0.8s;
37 | animation-delay: -0.8s;
38 | }
39 |
40 | @-webkit-keyframes stretchdelay {
41 | 0%, 40%, 100% { -webkit-transform: scaleY(0.4) }
42 | 20% { -webkit-transform: scaleY(1.0) }
43 | }
44 |
45 | @keyframes stretchdelay {
46 | 0%, 40%, 100% {
47 | transform: scaleY(0.4);
48 | -webkit-transform: scaleY(0.4);
49 | } 20% {
50 | transform: scaleY(1.0);
51 | -webkit-transform: scaleY(1.0);
52 | }
53 | }
--------------------------------------------------------------------------------
/lib/components/user_page/user_page.dart:
--------------------------------------------------------------------------------
1 | library hacker_news.components.user_page;
2 |
3 | import 'package:angular2/angular2.dart' show Component, NgFor, NgIf, View;
4 | import 'package:angular2/router.dart' show RouteParams;
5 | import 'package:ng2_hackernews/components/hn_item/hn_item.dart' show HNItem;
6 | import 'package:ng2_hackernews/directives/parse_html/parse_html.dart'
7 | show ParseHtml;
8 | import 'package:ng2_hackernews/services/hn_api.dart' show HNApi;
9 | import 'package:timeago/timeago.dart' show TimeAgo;
10 |
11 | final fuzzyTime = new TimeAgo();
12 |
13 | @Component(selector: 'page-user', viewProviders: const [HNApi])
14 | @View(
15 | templateUrl: 'package:ng2_hackernews/components/user_page/user_page.html',
16 | directives: const [NgFor, NgIf, HNItem, ParseHtml])
17 | class UserPage {
18 | HNApi api;
19 | bool showSubmissions;
20 | Map data = {};
21 | String timeAgo;
22 |
23 | UserPage(this.api, RouteParams routeParams) {
24 | _fetchUser(routeParams.get("id"));
25 | this.showSubmissions = false;
26 | }
27 | _fetchUser(String userId) async {
28 | data = await api.fetchUser(userId);
29 | data['submitted'] = (data['submitted'] as List).take(30);
30 | timeAgo = fuzzyTime.timeAgo(data['created'] * 1000);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/components/user_page/user_page.html:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/lib/components/user_page/user_page.less:
--------------------------------------------------------------------------------
1 | .userDetail {
2 | margin: 10px 20px;
3 | }
4 |
5 | .userDetail-item {
6 |
7 | &:first-of-type {
8 | margin-top: 30px;
9 | }
10 | }
--------------------------------------------------------------------------------
/lib/directives/parse_html/parse_html.dart:
--------------------------------------------------------------------------------
1 | library hacker_news.decorators.parse_html;
2 |
3 | import 'dart:html' as dom;
4 |
5 | import 'package:angular2/angular2.dart'
6 | show Directive, ElementRef, AfterContentInit;
7 | import 'package:html/parser.dart' show parse;
8 |
9 | @Directive(
10 | selector: '[parsehtml]')
11 | class ParseHtml implements AfterContentInit {
12 | ElementRef ref;
13 |
14 | ParseHtml(this.ref);
15 |
16 | ngAfterContentInit() {
17 | dom.Element element = ref.nativeElement;
18 | String raw = element.text;
19 | element.text = "";
20 | element.setInnerHtml(parse(raw).outerHtml);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/ng2_hackernews.dart:
--------------------------------------------------------------------------------
1 | library hacker_news;
2 |
3 | export 'app.dart';
4 |
--------------------------------------------------------------------------------
/lib/pipes/domain_pipe.dart:
--------------------------------------------------------------------------------
1 | import 'package:angular2/angular2.dart'
2 | show Pipe, PipeTransform, ChangeDetectorRef, Injectable;
3 |
4 | /**
5 | * Transforms a string that represents the url to show only the domain.
6 | *
7 | * # Example
8 | *
9 | * ```
10 | * @Component(
11 | * selector: "domain-example"
12 | * )
13 | * @View(
14 | * template: "domain: {{ 'http://www.google.com/accounts/recovery' | domain }}" // will be transformed to 'google.com',
15 | * pipes: const [DomainPipe]
16 | * )
17 | * class DomainExample {
18 | * }
19 | *
20 | * ```
21 | */
22 | @Pipe(name: 'domain')
23 | @Injectable()
24 | class DomainPipe implements PipeTransform {
25 | bool supports(dynamic str) {
26 | return str == null || str is String || str is List;
27 | }
28 |
29 | void onDestroy() {}
30 | String transform(String value, [List args = null]) {
31 | if (value == null || value.isEmpty) {
32 | return value;
33 | }
34 | return _parseDomain(value);
35 | }
36 |
37 | create(ChangeDetectorRef cdRef) {
38 | return this;
39 | }
40 |
41 | DomainPipe();
42 | }
43 |
44 | _parseDomain(String url) {
45 | url = url.replaceFirst(new RegExp(r'https?:\/\/(www.)?|www.'), '');
46 | if(url.contains('/')){
47 | var parts = url.split('/');
48 | url = parts[0];
49 | } else if (url.contains('?')) {
50 | var parts = url.split('?');
51 | url = parts[0];
52 | }
53 | return url;
54 | }
55 |
--------------------------------------------------------------------------------
/lib/pipes/timeago_pipe.dart:
--------------------------------------------------------------------------------
1 | import 'package:angular2/angular2.dart'
2 | show Pipe, PipeTransform, ChangeDetectorRef, Injectable;
3 | import 'package:timeago/timeago.dart' show TimeAgo;
4 |
5 | final fuzzyTime = new TimeAgo();
6 |
7 | /**
8 | * Transforms a string date in milliseconds to a fuzzy time. ie. "15 mins ago"
9 | *
10 | * # Example
11 | *
12 | * ```
13 | * @Component(
14 | * selector: "timeago-example"
15 | * )
16 | * @View(
17 | * template: "posted: {{ time | timeago }}",
18 | * pipes: const [TimeAgoPipe]
19 | * )
20 | * class TimeAgoExample {
21 | * String millisStr = "${new DateTime.now().millisecondsSinceEpoch - (15 * 60 * 1000)}";
22 | * }
23 | *
24 | * ```
25 | */
26 | @Pipe(name: 'timeago')
27 | @Injectable()
28 | class TimeAgoPipe implements PipeTransform {
29 | String _latestValue = null;
30 | String _latestResult = null;
31 | bool supports(dynamic str) {
32 | return str is String;
33 | }
34 |
35 | void onDestroy() {
36 | _latestValue = null;
37 | _latestResult = null;
38 | }
39 |
40 | String transform(String value, [List args = null]) {
41 | if (this._latestValue != value) {
42 | _latestValue = value;
43 | _latestResult = fuzzyTime.timeAgo(num.parse(value));
44 | return _latestResult;
45 | } else {
46 | return _latestResult;
47 | }
48 | }
49 |
50 | create(ChangeDetectorRef cdRef) {
51 | return this;
52 | }
53 |
54 | TimeAgoPipe();
55 | }
56 |
--------------------------------------------------------------------------------
/lib/services/hn_api.dart:
--------------------------------------------------------------------------------
1 | library hacker_news.services.hn_api;
2 |
3 | import 'dart:async';
4 | import 'package:firebase/firebase.dart';
5 | import 'package:angular2/angular2.dart';
6 |
7 | @Injectable()
8 | class HNApi {
9 | final fb = new Firebase('https://hacker-news.firebaseio.com/v0/');
10 |
11 | Map userStore = {};
12 | Map itemStore = {};
13 |
14 | Future>> fetchTopStories() {
15 | return topStoriesRef().once('value')
16 | .then((DataSnapshot value) => (value.val() as Iterable>).take(10));
17 | }
18 |
19 | Future fetchItems(List items) {
20 | List promises = [];
21 | items.forEach((String id) {
22 | promises.add(itemRef(id).onValue.first.then((value) {
23 | itemStore[id] = value.snapshot.val();
24 | return new Future.value(itemStore[id]);
25 | }));
26 | });
27 |
28 | return Future.wait(promises);
29 | }
30 |
31 | Future> fetchItem(String item) {
32 | if (item == null) {
33 | return new Future.error("item should not be null");
34 | }
35 | return fetchItems([item]).then((List data) => data[0]) as Future>;
36 | }
37 |
38 | Future> fetchUser(String userId) {
39 | if (userId == null || userId.isEmpty) {
40 | return new Future.error("user id should not be null");
41 | }
42 |
43 | return userRef(userId).onValue.first.then((Event value) {
44 | return new Future.value(value.snapshot.val());
45 | }) as Future>;
46 | }
47 |
48 | Firebase topStoriesRef() {
49 | return fb.child('topstories/');
50 | }
51 |
52 | Firebase itemRef(itemId) {
53 | return fb.child('item/' + itemId);
54 | }
55 |
56 | Firebase userRef(userId) {
57 | return fb.child('user/' + userId);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: 'ng2_hackernews'
2 | version: 0.0.1
3 | description: >
4 | A HackerNews application made with Angular2 Dart.
5 | author: Andres Araujo
6 | homepage: https://www.example.com
7 | environment:
8 | sdk: '>=1.0.0 <2.0.0'
9 | dependencies:
10 | firebase: ">=0.6.0 <0.7.0"
11 | angular2: "2.0.0-beta.0"
12 | timeago: any
13 | less_dart: any
14 | browser: any
15 | html: any
16 | dart_to_js_script_rewriter: '^0.1.0'
17 |
18 | dev_dependencies:
19 | test: '^0.12.6'
20 | angular2_testing: any
21 |
22 | transformers:
23 | - less_dart:
24 | include_path: ../lib/components/
25 | - angular2:
26 | entry_points: web/main.dart
27 | - dart_to_js_script_rewriter
28 |
--------------------------------------------------------------------------------
/test/domain_pipe_spec.dart:
--------------------------------------------------------------------------------
1 | import 'package:test/test.dart';
2 | import 'package:ng2_hackernews/pipes/domain_pipe.dart';
3 |
4 | main () {
5 | DomainPipe pipe;
6 | setUp(() async {
7 | pipe = new DomainPipe();
8 | });
9 |
10 | group('DomainPipe', () {
11 | test('Should return correct domain for url', () {
12 | expect(pipe.transform('www.google.com/asd'), equals('google.com'));
13 | expect(pipe.transform('http://www.google.com/asd'), equals('google.com'));
14 | expect(pipe.transform('https://www.google.com/asd'), equals('google.com'));
15 | expect(pipe.transform('http://www.google.com?asd=qwe'), equals('google.com'));
16 | });
17 | });
18 | }
--------------------------------------------------------------------------------
/test/hn_item_spec.dart:
--------------------------------------------------------------------------------
1 | // Because Angular is using dart:html, we need these tests to run on an actual
2 | // browser. This means that it should be run with `-p dartium` or `-p chrome`.
3 | @TestOn('browser')
4 | import 'package:angular2/angular2.dart'
5 | show
6 | Component,
7 | View,
8 | NgFor,
9 | provide,
10 | Inject,
11 | Injectable,
12 | Optional,
13 | Provider,
14 | DebugElement,
15 | Directive;
16 |
17 | import 'package:angular2/src/mock/mock_application_ref.dart';
18 |
19 | import 'package:angular2/router.dart' show RouterLink;
20 |
21 | import 'package:test/test.dart';
22 | import 'package:angular2_testing/angular2_testing.dart';
23 | import 'package:ng2_hackernews/components/hn_item/hn_item.dart';
24 | import 'package:ng2_hackernews/services/hn_api.dart';
25 | import 'dart:html' as dom;
26 |
27 | @Injectable()
28 | class MockHNApi implements HNApi {
29 | fetchItem(itemId) async {
30 | int time =
31 | (new DateTime.now().millisecondsSinceEpoch / 1000 - (15 * 60)).round();
32 | return {'type': 'comment', 'time': time, 'text': 'My dummy comment'};
33 | }
34 |
35 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
36 | const MockHNApi();
37 | }
38 |
39 | void main() {
40 | initAngularTests();
41 |
42 | setUpProviders(() => [provide(HNApi, useClass: MockHNApi)]);
43 |
44 | group('HNItem Component', () {
45 | ngTest('should use mock hacker news api', (HNApi api) async {
46 | expect(api, new isInstanceOf());
47 | });
48 |
49 | group('When component is initialized', () {
50 | HNItem hnItem;
51 | dom.Element hnItemElement;
52 |
53 | ngSetUp((TestComponentBuilder tcb) async {
54 | Uri uri = new Uri.file(
55 | 'packages/ng2_hackernews/components/hn_item/hn_item.html');
56 | String template = await dom.HttpRequest.getString(uri.toString());
57 | var rootTC = await tcb
58 | .overrideTemplate(HNItem, template)
59 | .overrideDirective(HNItem, RouterLink, Dummy)
60 | .createAsync(HNItem);
61 |
62 | hnItem = rootTC.debugElement.componentInstance;
63 | hnItemElement = rootTC.debugElement.nativeElement;
64 | await hnItem.ngOnInit();
65 | rootTC.detectChanges();
66 | });
67 |
68 | ngTest('should set data/timeago/type', () async {
69 | expect(hnItem.topLevel, equals(false));
70 | expect(hnItem.collapsed, equals(false));
71 | expect(hnItem.loadChildren, equals(true));
72 | expect(hnItem.timeAgo, equals('15 minutes ago'));
73 | expect(hnItem.type, equals(itemMap['comment']));
74 |
75 | expect(hnItemElement.querySelector('.hnItem--coment-content').text.trim(), equals('My dummy comment'));
76 | });
77 |
78 |
79 | });
80 | });
81 | }
82 |
83 | @Directive(selector: '[routerLink]')
84 | class Dummy {}
85 |
--------------------------------------------------------------------------------
/test/parse_html_directive_spec.dart:
--------------------------------------------------------------------------------
1 | // Because Angular is using dart:html, we need these tests to run on an actual
2 | // browser. This means that it should be run with `-p dartium` or `-p chrome`.
3 | @TestOn('browser')
4 | import 'package:angular2/angular2.dart'
5 | show
6 | Component,
7 | View,
8 | NgFor,
9 | provide,
10 | Inject,
11 | Injectable,
12 | Optional,
13 | Provider;
14 |
15 | import 'package:test/test.dart';
16 | import 'package:angular2_testing/angular2_testing.dart';
17 | import 'package:ng2_hackernews/directives/parse_html/parse_html.dart';
18 |
19 | // This is the component we will be testing.
20 | @Component(selector: 'test-cmp')
21 | @View(template: '{{raw}}
', directives: const [ParseHtml])
22 | class TestComponent {
23 | String raw;
24 | TestComponent() {
25 | this.raw = "Dummy Dummy2 ";
26 | }
27 | }
28 |
29 | void main() {
30 | initAngularTests();
31 |
32 | ngTest('should parse a raw html string', (TestComponentBuilder tcb) async {
33 | var rootTC = await tcb
34 | .createAsync(TestComponent);
35 |
36 | rootTC.detectChanges();
37 | expect(rootTC.debugElement.nativeElement.text, equals('DummyDummy2'));
38 | });
39 | }
40 |
--------------------------------------------------------------------------------
/test/timeago_pipe_spec.dart:
--------------------------------------------------------------------------------
1 | import 'package:test/test.dart';
2 | import 'package:ng2_hackernews/pipes/timeago_pipe.dart';
3 |
4 | main () {
5 | TimeAgoPipe pipe;
6 | setUp(() async {
7 | pipe = new TimeAgoPipe();
8 | });
9 |
10 | group('DomainPipe', () {
11 | test('Should return correct domain for url', () {
12 | int time = new DateTime.now().millisecondsSinceEpoch - (15 * 60 * 1000);
13 | expect(pipe.transform('$time'), equals('15 minutes ago'));
14 | });
15 | });
16 | }
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Angular2 Hackernews Dart
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/web/main.dart:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, . All rights reserved. Use of this source code
2 | // is governed by a BSD-style license that can be found in the LICENSE file.
3 |
4 | import 'package:angular2/angular2.dart' show provide;
5 | import 'package:angular2/bootstrap.dart' show bootstrap;
6 | import 'package:angular2/router.dart' show APP_BASE_HREF, HashLocationStrategy, LocationStrategy, ROUTER_PROVIDERS;
7 |
8 | import 'package:ng2_hackernews/ng2_hackernews.dart' show App;
9 | import 'package:ng2_hackernews/services/hn_api.dart';
10 |
11 | main() {
12 |
13 | bootstrap(App, [
14 | ROUTER_PROVIDERS,
15 | provide(APP_BASE_HREF, useValue: '/'),
16 | provide(LocationStrategy, useClass: HashLocationStrategy),
17 | provide(HNApi)
18 | ]);
19 | }
--------------------------------------------------------------------------------
/web/main.less:
--------------------------------------------------------------------------------
1 |
2 | input { font-family:Courier; font-size:10pt; color:#000000; }
3 | input[type=submit] { font-family:Verdana, Geneva, sans-serif; }
4 | textarea { font-family:Courier; font-size:10pt; color:#000000; }
5 |
6 | a:link { color:#000000; text-decoration:none; }
7 | a:visited { color:#828282; text-decoration:none; }
8 |
9 | .default { font-family:Verdana, Geneva, sans-serif; font-size: 10pt; color:#828282; }
10 | .admin { font-family:Verdana, Geneva, sans-serif; font-size:8.5pt; color:#000000; }
11 |
12 | .adtitle { font-family:Verdana, Geneva, sans-serif; font-size: 9pt; color:#828282; }
13 | .yclinks { font-family:Verdana, Geneva, sans-serif; font-size: 8pt; color:#828282; }
14 | .pagetop { font-family:Verdana, Geneva, sans-serif; font-size: 10pt; color:#222222; }
15 | .comhead { font-family:Verdana, Geneva, sans-serif; font-size: 8pt; color:#828282; }
16 | .comment { font-family:Verdana, Geneva, sans-serif; font-size: 9pt; }
17 | .dead { font-family:Verdana, Geneva, sans-serif; font-size: 9pt; color:#dddddd; }
18 |
19 | .comment a:link, .comment a:visited { text-decoration:underline;}
20 | .dead a:link, .dead a:visited { color:#dddddd; }
21 | .pagetop a:visited { color:#000000;}
22 | .topsel a:link, .topsel a:visited { color:#ffffff; }
23 |
24 | .comhead a:link, .subtext a:visited { color:#828282; }
25 | .comhead a:hover { text-decoration:underline; }
26 |
27 | .default p { margin-top: 8px; margin-bottom: 0px; }
28 |
29 | .pagebreak {page-break-before:always}
30 |
31 | pre { overflow: auto; padding: 2px; max-width:600px; }
32 | pre:hover {overflow:auto}
33 |
34 |
35 | /**
36 | * Real Styles
37 | */
38 |
39 | .u-pointer {
40 | cursor: pointer;
41 | }
42 |
43 | body {
44 | font-family: Verdana, Geneva, sans-serif;
45 | font-size: 10pt;
46 | color: #828282;
47 | }
48 |
49 | .bodyContainer {
50 | margin: 0 auto;
51 | width: 85%;
52 | max-width: 1280px;
53 | background: #F6F6EF;
54 | }
55 |
56 | .headerBar {
57 | background: #FF6600;
58 | }
59 |
60 | @import "packages/ng2_hackernews/components/spinner.less";
61 | @import "packages/ng2_hackernews/components/hn_item/hn_item.less";
62 | @import "packages/ng2_hackernews/components/item/item_page.less";
63 | @import "packages/ng2_hackernews/components/user_page/user_page.less";
--------------------------------------------------------------------------------