<!DOCTYPE html>
<html>

<head>
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <title>angular playground</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.css">
</head>

<body>
  <!-- Polyfills -->
  <script src="https://unpkg.com/core-js/client/shim.min.js"></script>

  <script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/jasmine-html.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.4.1/boot.js"></script>

  <script src="https://unpkg.com/zone.js@0.7.4?main=browser"></script>
  <script src="https://unpkg.com/zone.js/dist/long-stack-trace-zone.js?main=browser"></script>
  <script src="https://unpkg.com/zone.js/dist/proxy.js?main=browser"></script>
  <script src="https://unpkg.com/zone.js/dist/sync-test.js?main=browser"></script>
  <script src="https://unpkg.com/zone.js/dist/jasmine-patch.js?main=browser"></script>
  <script src="https://unpkg.com/zone.js/dist/async-test.js?main=browser"></script>
  <script src="https://unpkg.com/zone.js/dist/fake-async-test.js?main=browser"></script>


  <script>
    var __spec_files__ = [
      'app/app.component.router.spec.ts',
      'app/user.service.spec',
      'app/trade.service.spec',
      'app/log.directive.spec',
      'app/trade-list.component.spec',
      'app/trade-status.pipe.spec',
      'app/trade-view.component.spec'
    ];
  </script>
  <script src="browser-test-shim.js"></script>
</body>

</html>
/* Styles go here */

### Angular Starter Plunker - Typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { Routes, RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { TradeListComponent } from './trade-list.component';
import { TradeViewComponent } from './trade-view.component';
import { TradePipe } from './trade-status.pipe';
import { UserService } from './user.service';
import { TradeService } from './trade.service';

@NgModule({
  declarations: [
    AppComponent,
    TradeListComponent, TradeViewComponent, TradePipe
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    RouterModule.forRoot([
        { path: '', redirectTo: 'home', pathMatch: 'full' },
        { path: 'home', component: TradeListComponent },
        { path: ':id', component: TradeViewComponent }
    ], { useHash: true })
  ],
  providers: [ TradeService, UserService ],
  bootstrap: [AppComponent]
})
export class AppModule { }
[{
        "id": 10000,
        "user_id": 1,
        "user_name": "asdf",
        "sku_id": 10000,
        "title": "商品名称",
        "status": "new"
    }, {
        "id": 10001,
        "user_id": 1,
        "user_name": "asdf",
        "sku_id": 10001,
        "title": "商品名称1",
        "status": "wait_pay"
    }
]
import { Http } from '@angular/http';
import { Injectable } from "@angular/core";

@Injectable()
export class UserService {

    token: string = 'wx';

    get() {
        return {
            "id": 1,
            "name": "asdf"
        };
    }

    type() {
        return ['普通会员', 'VIP会员'];
    }
}
import { Http } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { UserService } from './user.service';

@Injectable()
export class TradeService {
    constructor(private http: Http, private userSrv: UserService) { }

    query(): Observable<any[]> {
        return this.http
            .get('api/trade-list.json?token' + this.userSrv.token)
            .map(response => response.json());
    }

    private getTrade() {
        return {
            "id": 10000,
            "user_id": 1,
            "user_name": "asdf",
            "sku_id": 10000,
            "title": "商品名称"
        }
    }

    get(tid: number): Promise<any> {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(this.getTrade());
            }, 500);
        })
    }

}
import { fakeAsync, tick } from '@angular/core/testing';
import { HttpModule } from '@angular/http';
import { TestBed, inject } from '@angular/core/testing';
import { UserService } from './user.service';

describe('Service: user', () => {
    describe(`without the TestBed`, () => {
        let srv: UserService = new UserService();

        it(`#token should return 'wx'`, () => {
            expect(srv.token).toBe('wx');
        });

        it(`#get should return user data`, () => {
            const res = srv.get();
            expect(res).not.toBeNull();
            expect(res.id).toBe(1);
            expect(res.name).toBe('asdf');
        });

        it(`#type should return [ '普通会员', 'VIP会员' ]`, () => {
            const res = srv.type();
            expect(Array.isArray(res)).toBe(true);
            expect(res.length).toBe(2);
            expect(res[0]).toBe('普通会员');
            expect(res[1]).toBe('VIP会员');
        });
    });

})
import { TestBed, inject, fakeAsync, tick } from '@angular/core/testing';
import { HttpModule } from '@angular/http';
import { TradeService } from './trade.service';
import { UserService } from './user.service';

describe('Service: trade', () => {
    describe(`with the TestBed`, () => {
        let srv: TradeService;
        beforeEach(() => TestBed.configureTestingModule({
            imports: [HttpModule],
            providers: [TradeService, UserService]
        }));
        beforeEach(inject([TradeService], s => { srv = s; }));

        it('#query should return trade array', (done: DoneFn) => {
            srv.query().subscribe(res => {
                expect(Array.isArray(res)).toBe(true);
                expect(res.length).toBe(2);
                done();
            });
        });

        it('#get should return trade', (done: DoneFn) => {
            srv.get(1).then(res => {
                expect(res).not.toBeNull();
                expect(res.id).toBe(10000);
                done();
            });
        });

        it('#get should return trade (fakeAsync)', fakeAsync((done: DoneFn) => {
            srv.get(1).then(res => {
                expect(res).not.toBeNull();
                expect(res.id).toBe(10000);
            });
            tick(600);
        }));
        
    });
});
import { TradeService } from './trade.service';
import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
import { Http } from "@angular/http";
import 'rxjs/add/operator/map';

@Component({
    selector: 'trade-view',
    template: `
    <h1>trade {{id}}</h1>
    <dl *ngIf="item">
        <dt>sku_id</dt><dd class="sku-id">{{item.sku_id}}</dd>
        <dt>title</dt><dd class="ware-title">{{item.title}}</dd>
    </dl>
    <button (click)="_close()">Close</button>
    `,
    host: {
        '[class.trade-view]': 'true'
    },
    styles: [ `.trade-view { display: block; }` ],
    encapsulation: ViewEncapsulation.None
})
export class TradeViewComponent {
    @Input() id: number;

    @Output() close = new EventEmitter();

    constructor(private srv: TradeService) {}

    ngOnInit() {
        this.get();
    }

    item: any;
    get() {
        this.srv.get(this.id).then(res => {
            this.item = res;
        });
    }

    _close() {
        this.close.emit();
    }
}
import { UserService } from './user.service';
import { TradeService } from './trade.service';
import { DebugElement, Component, ViewChild } from "@angular/core";
import { HttpModule, XHRBackend } from '@angular/http';
import { TestBed, ComponentFixture, tick, fakeAsync, inject, async } from '@angular/core/testing';
import { TradeViewComponent } from "./trade-view.component";
import { By } from '@angular/platform-browser';

@Component({
    template: `<trade-view [id]="id" (close)="_close()"></trade-view>`
})
class TestComponent {
    @ViewChild(TradeViewComponent) comp: TradeViewComponent;
    id: number = 0;
    _close() { }
}

describe('Component: trade-view', () => {
    let fixture: ComponentFixture<TestComponent>,
        context: TestComponent,
        el: HTMLElement,
        de: DebugElement;

    describe('with spy of service', () => {
        let spy: jasmine.Spy;
        const testTrade = {
            "id": 10000,
            "user_id": 1,
            "user_name": "asdf",
            "sku_id": 10000,
            "title": "商品名称"
        };

        beforeEach(() => {
            TestBed.configureTestingModule({
                imports: [HttpModule],
                declarations: [TradeViewComponent, TestComponent],
                providers: [TradeService, UserService]
            });
            fixture = TestBed.createComponent(TestComponent);
            context = fixture.componentInstance;

            let tradeService = fixture.debugElement.injector.get(TradeService)

            spyOn(context, '_close');
            spy = spyOn(tradeService, 'get').and.returnValue(Promise.resolve(testTrade));

            el = fixture.nativeElement;
            de = fixture.debugElement;
        });

        it('should be component initialized', () => {
            context.id = 1;
            fixture.detectChanges();
            expect(el.querySelector('h1').innerText).toBe('trade 1');
            expect(spy.calls.any()).toBe(true, 'get called');
        });

        it('should be component initialized (done)', (done: DoneFn) => {
            fixture.detectChanges();
            spy.calls.mostRecent().returnValue.then(res => {
                fixture.detectChanges();
                expect(context.comp.item.id).toBe(testTrade.id);
                expect(el.querySelector('dl')).not.toBeNull();
                expect(el.querySelector('.sku-id').textContent).toBe('' + testTrade.sku_id);
                expect(el.querySelector('.ware-title').textContent).toBe(testTrade.title);
                done();
            });
        });

        it('should be component initialized (async)', async(() => {
            fixture.detectChanges();
            fixture.whenStable().then(() => {
                fixture.detectChanges();
                expect(context.comp.item.id).toBe(testTrade.id);
                expect(el.querySelector('dl')).not.toBeNull();
                expect(el.querySelector('.sku-id').textContent).toBe('' + testTrade.sku_id);
                expect(el.querySelector('.ware-title').textContent).toBe(testTrade.title);
            });
        }));

        it('should be component initialized (fakeAsync)', fakeAsync(() => {
            fixture.detectChanges();
            tick();
            fixture.detectChanges();
            expect(context.comp.item.id).toBe(testTrade.id);
            expect(el.querySelector('dl')).not.toBeNull();
            expect(el.querySelector('.sku-id').textContent).toBe('' + testTrade.sku_id);
            expect(el.querySelector('.ware-title').textContent).toBe(testTrade.title);
        }));

        it('should be call `close`', () => {
            el.querySelector('button').click();
            fixture.detectChanges();
            expect(context._close).toHaveBeenCalled();
        });
    });

});
import { DebugElement, Component } from "@angular/core";
import { TestBed, inject, ComponentFixture } from '@angular/core/testing';
import { TradePipe } from './trade-status.pipe';

describe('Pipe: TradePipe', () => {

    describe('without NgModule', () => {
        let pipe = new TradePipe();

        it('should be defined', () => {
            expect(pipe).not.toBeUndefined();
        });

        it(`should be return '新订单' with 'new' string`, () => {
            expect(pipe.transform('new')).toEqual('新订单');
        });

        it(`should be return html code with 'cancel' string`, () => {
            expect(pipe.transform('cancel', 'reason')).toContain('<a title');
        });

        it(`should throw width invalid string`, () => {
            expect(() => pipe.transform(undefined)).toThrow();
            const invalidStatusTest = 'invalid status';
            expect(() => pipe.transform(invalidStatusTest)).toThrowError(`无效状态码${invalidStatusTest}`);
        });
    });

    describe('with NgModule', () => {

        @Component({ template: `<h1>{{ value | trade }}</h1>` })
        class TestComponent {
            value: string = 'new';
        }

        let fixture: ComponentFixture<TestComponent>,
            context: TestComponent,
            el: HTMLElement,
            de: DebugElement;
        beforeEach(() => {
            TestBed.configureTestingModule({
                declarations: [TestComponent, TradePipe]
            });
            fixture = TestBed.createComponent(TestComponent);
            context = fixture.componentInstance;
            el = fixture.nativeElement;
            de = fixture.debugElement;
        });

        it('should display `待支付`', () => {
            context.value = 'wait_pay';
            fixture.detectChanges();
            expect(el.querySelector('h1').textContent).toBe('待支付');
        });

    });
});
import { TradeService } from './trade.service';
import { Component } from '@angular/core';
import 'rxjs/add/operator/map';

@Component({
    selector: 'trade-list',
    templateUrl: './trade-list.component.html',
    styleUrls: [ './trade-list.component.scss' ]
})
export class TradeListComponent {

    constructor(private srv: TradeService) {}

    ngOnInit() {
        this.query();
    }

    ls: any[] = [];
    query() {
        this.srv.query().subscribe(res => {
            this.ls = res;
        });
    }
}
import { By } from '@angular/platform-browser';
import { UserService } from './user.service';
import { HttpModule } from '@angular/http';
import { TradeService } from './trade.service';
import { TradePipe } from './trade-status.pipe';
import { DebugElement } from '@angular/core';
import { async, TestBed, ComponentFixture, fakeAsync } from '@angular/core/testing';
import { TradeListComponent } from './trade-list.component';
import { ComponentFixtureAutoDetect, tick } from '@angular/core/testing';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/of';
import { RouterLinkStubDirective } from "../testing";

const tradeData = [{
    "id": 10001,
    "user_id": 1,
    "user_name": "asdf",
    "sku_id": 10000,
    "title": "商品名称1",
    "status": "new"
}, {
    "id": 10002,
    "user_id": 1,
    "user_name": "asdf",
    "sku_id": 10001,
    "title": "商品名称2",
    "status": "wait_pay"
}];
const tradeServiceStub = {
    query(): Observable<any[]> {
        return Observable.of(tradeData);
    }
};

describe('Component: trade-list (async)', () => {

    let fixture: ComponentFixture<TradeListComponent>,
        context: TradeListComponent,
        el: HTMLElement,
        de: DebugElement;

    function createComponent() {
        fixture = TestBed.createComponent(TradeListComponent);
        context = fixture.componentInstance;
        el = fixture.nativeElement;
        de = fixture.debugElement;

        fixture.detectChanges();
    }

    describe('without the Stub', () => {
        beforeEach(async(() => {
            TestBed.configureTestingModule({
                imports: [HttpModule],
                declarations: [TradeListComponent, TradePipe, RouterLinkStubDirective],
                providers: [TradeService, UserService]
            })
            .compileComponents()
            .then(() => {
                createComponent();
            })
        }));

        it('should be inited', () => {
            expect(el.querySelector('h1').innerText).toBe('trade list');
        });

        it('#query should display 2 trade', () => {
            expect(context.ls.length).toBe(2);
            expect(context.ls[0].id).toBe(10000);
            fixture.detectChanges();
            expect(de.queryAll(By.css('li')).length).toBe(2);
        });
    });

    describe('with the Stub', () => {
        beforeEach(async(() => {
            TestBed.configureTestingModule({
                declarations: [TradeListComponent, TradePipe, RouterLinkStubDirective],
                providers: [
                    { provide: TradeService, useValue: tradeServiceStub },
                    { provide: ComponentFixtureAutoDetect, useValue: true }
                ]
            })
            .compileComponents()
            .then(() => {
                createComponent();
            })
        }));

        it('should be inited', () => {
            expect(el.querySelector('h1').innerText).toBe('trade list');
        });

        it('#query should display 2 trade', () => {
            expect(context.ls.length).toBe(2);
            expect(context.ls[0].id).toBe(tradeData[0].id);
            expect(de.queryAll(By.css('li')).length).toBe(2);
        });

        it('can get RouterLinks from template', () => {
            let allLinks = de.queryAll(By.directive(RouterLinkStubDirective));
            let links = allLinks.map(linkDe => linkDe.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
            expect(links.length).toBe(tradeData.length);
            expect(links[0].linkParams.toString()).toBe('/' + tradeData[0].id);
        });
    });

});
.trade-view {
    display: block;
}
<h1>trade list</h1>
<ul>
    <li *ngFor="let i of ls">
        <p><a [routerLink]="[ '/' + i.id ]">{{i.id}}</a></p>
        <p>status: {{i.status | trade}}</p>
    </li>
</ul>
<button (click)="query()">refresh</button>
import { Directive, Output, EventEmitter, HostListener } from "@angular/core";

/**
 * ```
 * <div log (change)="change($event)"></div>
 * ```
 */
@Directive({ selector: '[log]' })
export class LogDirective {
    tick: number = 0;
    
    @Output() change = new EventEmitter();

    @HostListener('click', [ '$event' ])
    click(event: any) {
        ++this.tick;
        this.change.emit(this.tick);
    }
}
import { tick } from '@angular/core/testing';
import { fakeAsync } from '@angular/core/testing';
import { Component, EventEmitter, Output, DebugElement } from '@angular/core';
import { TestBed, ComponentFixture, inject } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { LogDirective } from './log.directive';

@Component({
    template: `<div log (change)="change($event)"></div>`
})
class TestComponent {
    @Output() changeNotify = new EventEmitter();

    change(value) {
        this.changeNotify.emit(value);
    }
}

describe('Directiv: log', () => {

    let fixture: ComponentFixture<TestComponent>,
        context: TestComponent,
        el: HTMLElement,
        directive: LogDirective;

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent, LogDirective]
        });
        fixture = TestBed.createComponent(TestComponent);
        context = fixture.componentInstance;
        el = fixture.nativeElement;

        let directives = fixture.debugElement.queryAll(By.directive(LogDirective));
        directive = directives.map((d: DebugElement) => d.injector.get(LogDirective) as LogDirective)[0];
    });

    it('should be defined on the test component', () => {
        expect(directive).not.toBeUndefined();
    });

    it('should increment tick (fakeAsync)', fakeAsync(() => {
        context.changeNotify.subscribe(val => {
            expect(val).toBe(1);
        });
        el.click();
        tick();
    }));

});


describe('Directiv: log (simple)', () => {
    let directive: LogDirective;
    beforeEach(() => TestBed.configureTestingModule({
        providers: [ LogDirective ]
    }));

    beforeEach(inject([ LogDirective ], c => {
        directive = c;
    }));

    it('should be defined on the test component', () => {
        expect(directive).not.toBeUndefined();
    });

    it('should increment tick (fakeAsync)', fakeAsync(() => {
        directive.change.subscribe(val => {
          console.log(val);
            expect(val).toBe(1);
        });
        directive.click(null);
    }));

});
import { Pipe, PipeTransform } from "@angular/core";

@Pipe({ name: 'trade' })
export class TradePipe implements PipeTransform {
    transform(value: any, ...args: any[]) {
        switch (value) {
            case 'new':
                return '新订单';
            case 'wait_pay':
                return '待支付';
            case 'cancel':
                return `<a title="${args && args.length > 0 ? args[0] : ''}">已取消</a>`;
            default:
                throw new Error(`无效状态码${value}`);
        }
    }

}
// BROWSER TESTING SHIM
// Keep it in-sync with what karma-test-shim does
/*global jasmine, __karma__, window*/
(function () {

Error.stackTraceLimit = 0; // "No stacktrace"" is usually best for app testing.

// Uncomment to get full stacktrace output. Sometimes helpful, usually not.
// Error.stackTraceLimit = Infinity; //

jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;

var baseURL = document.baseURI;
baseURL = baseURL + baseURL[baseURL.length-1] ? '' : '/';

System.config({
  baseURL: baseURL,
  // Extend usual application package list with test folder
  packages: { 'testing': { main: 'index.js', defaultExtension: 'js' } },

  // Assume npm: is set in `paths` in systemjs.config
  // Map the angular testing umd bundles
  map: {
    '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
    '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
    '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
    '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
    '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
    '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
    '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
    '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',
  },
});

System.import('systemjs.config.js')
  .then(importSystemJsExtras)
  .then(initTestBed)
  .then(initTesting);

/** Optional SystemJS configuration extras. Keep going w/o it */
function importSystemJsExtras(){
  return System.import('systemjs.config.extras.js')
  .catch(function(reason) {
    console.log(
      'Note: System.import could not load "systemjs.config.extras.js" where you might have added more configuration. It is an optional file so we will continue without it.'
    );
    console.log(reason);
  });
}

function initTestBed(){
  return Promise.all([
    System.import('@angular/core/testing'),
    System.import('@angular/platform-browser-dynamic/testing')
  ])

  .then(function (providers) {
    var coreTesting    = providers[0];
    var browserTesting = providers[1];

    coreTesting.TestBed.initTestEnvironment(
      browserTesting.BrowserDynamicTestingModule,
      browserTesting.platformBrowserDynamicTesting());
  })
}

// Import all spec files defined in the html (__spec_files__)
// and start Jasmine testrunner
function initTesting () {
  console.log('loading spec files: '+__spec_files__.join(', '));
  return Promise.all(
    __spec_files__.map(function(spec) {
      return System.import(spec);
    })
  )
  //  After all imports load,  re-execute `window.onload` which
  //  triggers the Jasmine test-runner start or explain what went wrong
  .then(success, console.error.bind(console));

  function success () {
    console.log('Spec files loaded; starting Jasmine testrunner');
    window.onload();
  }
}

})();


/*
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;

module.exports.translate = function(load){
  if (load.source.indexOf('moduleId') != -1) return load;

  var url = document.createElement('a');
  url.href = load.address;

  var basePathParts = url.pathname.split('/');

  basePathParts.pop();
  var basePath = basePathParts.join('/');

  var baseHref = document.createElement('a');
  baseHref.href = this.baseURL;
  baseHref = baseHref.pathname;

  if (!baseHref.startsWith('/base/')) { // it is not karma
    basePath = basePath.replace(baseHref, '');
  }

  load.source = load.source
    .replace(templateUrlRegex, function(match, quote, url){
      var resolvedUrl = url;

      if (url.startsWith('.')) {
        resolvedUrl = basePath + url.substr(1);
      }

      return 'templateUrl: "' + resolvedUrl + '"';
    })
    .replace(stylesRegex, function(match, relativeUrls) {
      var urls = [];

      while ((match = stringRegex.exec(relativeUrls)) !== null) {
        if (match[2].startsWith('.')) {
          urls.push('"' + basePath + match[2].substr(1) + '"');
        } else {
          urls.push('"' + match[2] + '"');
        }
      }

      return "styleUrls: [" + urls.join(', ') + "]";
    });

  return load;
};
/** App specific SystemJS configuration */
System.config({
  packages: {
    // barrels
    'src': {main: "index.ts", defaultExtension: "ts"}
  }
});


/*
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
/**
 * WEB ANGULAR VERSION
 * (based on systemjs.config.js in angular.io)
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
    transpiler: 'ts',
    typescriptOptions: {
      // Copy of compiler options in standard tsconfig.json
      "target": "es5",
      "module": "commonjs",
      "moduleResolution": "node",
      "sourceMap": true,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "lib": ["es2015", "dom"],
      "noImplicitAny": true,
      "suppressImplicitAnyIndexErrors": true
    },
    meta: {
      'typescript': {
        "exports": "ts"
      }
    },
    paths: {
      // paths serve as alias
      'npm:': 'https://unpkg.com/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      'app': 'app',

      // angular bundles
      '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
      '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',

      // other libraries
      'rxjs':                      'npm:rxjs@5.0.1',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js',
      'ts':                        'npm:plugin-typescript@5.2.7/lib/plugin.js',
      'typescript':                'npm:typescript@2.3.2/lib/typescript.js',

    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.ts',
        defaultExtension: 'ts',
        meta: {
          './*.ts': {
            loader: 'systemjs-angular-loader.js'
          }
        }
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });

})(this);

/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
  <router-outlet></router-outlet>
  `
})
export class AppComponent {
}
import { DebugElement } from '@angular/core';
import { tick, ComponentFixture } from '@angular/core/testing';

export * from './router-stubs';

///// Short utilities /////

/** Wait a tick, then detect changes */
export function advance(f: ComponentFixture<any>): void {
    tick();
    f.detectChanges();
}

/**
 * Create custom DOM event the old fashioned way
 *
 * https://developer.mozilla.org/en-US/docs/Web/API/Event/initEvent
 * Although officially deprecated, some browsers (phantom) don't accept the preferred "new Event(eventName)"
 */
export function newEvent(eventName: string, bubbles = false, cancelable = false) {
    let evt = document.createEvent('CustomEvent');  // MUST be 'CustomEvent'
    evt.initCustomEvent(eventName, bubbles, cancelable, null);
    return evt;
}

// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
export const ButtonClickEvents = {
    left: { button: 0 },
    right: { button: 2 }
};
/** Simulate element click. Defaults to mouse left-button click event. */
export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void {
    if (el instanceof HTMLElement) {
        el.click();
    } else {
        el.triggerEventHandler('click', eventObj);
    }
}
export { ActivatedRoute, Router, RouterLink, RouterOutlet} from '@angular/router';

import { Component, Directive, Injectable, Input } from '@angular/core';
import { NavigationExtras } from '@angular/router';

@Directive({
  selector: '[routerLink]',
  host: {
    '(click)': 'onClick()'
  }
})
export class RouterLinkStubDirective {
  @Input('routerLink') linkParams: any;
  navigatedTo: any = null;

  onClick() {
    this.navigatedTo = this.linkParams;
  }
}

@Component({selector: 'router-outlet', template: ''})
export class RouterOutletStubComponent { }

@Injectable()
export class RouterStub {
  navigate(commands: any[], extras?: NavigationExtras) { }
}

// Only implements params and part of snapshot.params
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class ActivatedRouteStub {

  // ActivatedRoute.params is Observable
  private subject = new BehaviorSubject(this.testParams);
  params = this.subject.asObservable();

  // Test parameters
  private _testParams: {};
  get testParams() { return this._testParams; }
  set testParams(params: {}) {
    this._testParams = params;
    this.subject.next(params);
  }

  // ActivatedRoute.snapshot.params
  get snapshot() {
    return { params: this.testParams };
  }
}
import { By } from '@angular/platform-browser';
import { async, TestBed, fakeAsync, ComponentFixture, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { SpyLocation } from '@angular/common/testing';
import { Router, RouterLinkWithHref } from '@angular/router';
import { Location } from '@angular/common';
import { NO_ERRORS_SCHEMA, Type } from "@angular/core";
import { Observable } from "rxjs/Observable";

import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { TradeListComponent } from './trade-list.component';
import { TradeViewComponent } from './trade-view.component';
import { TradeService } from './trade.service';
import { click } from '../testing';

let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let router: Router;
let location: SpyLocation;

const testTrade = {
    "id": 10000,
    "user_id": 1,
    "user_name": "asdf",
    "sku_id": 10000,
    "title": "商品名称"
};
const tradeData = [{
    "id": 10001,
    "user_id": 1,
    "user_name": "asdf",
    "sku_id": 10000,
    "title": "商品名称1",
    "status": "new"
}, {
    "id": 10002,
    "user_id": 1,
    "user_name": "asdf",
    "sku_id": 10001,
    "title": "商品名称2",
    "status": "wait_pay"
}];
const tradeServiceStub = {
    query(): Observable<any[]> {
        return Observable.of(tradeData);
    },
    get(tid: number): Promise<any> {
        return Promise.resolve(testTrade);
    }
};
describe('AppComponent & RouterTestingModule', () => {
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [AppModule, RouterTestingModule],
            providers: [
                { provide: TradeService, useValue: tradeServiceStub }
            ],
            schemas: [NO_ERRORS_SCHEMA]
        })
            .compileComponents()
    }));

    it('should navigate to "home" immediately', fakeAsync(() => {
        createComponent();
        expectPathToBe('/home');
        expectElementOf(TradeListComponent);
    }));

    it('should navigate to "trade-view" on click', fakeAsync(() => {
        createComponent();

        let allLinks = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
        click(allLinks[0]);
        tick();
        fixture.detectChanges();
        expectPathToBe('/' + tradeData[0].id);
        expectElementOf(TradeViewComponent);
    }));
});

////// Helpers /////////

function createComponent() {
    fixture = TestBed.createComponent(AppComponent);
    comp = fixture.componentInstance;

    const injector = fixture.debugElement.injector;
    location = injector.get(Location) as SpyLocation;
    router = injector.get(Router);
    router.initialNavigation();

    tick();
    fixture.detectChanges();
}

function expectPathToBe(path: string, expectationFailOutput?: any) {
    expect(location.path()).toEqual(path, expectationFailOutput || 'location.path()');
}

function expectElementOf(type: Type<any>): any {
    const el = fixture.debugElement.query(By.directive(type));
    expect(el).toBeTruthy('expected an element for ' + type.name);
    return el;
}