From ba07f39347e3f3609f015f32083f90f5cca5ec77 Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Fri, 29 May 2015 14:58:41 -0700 Subject: [PATCH] refactor(router): convert to typescript Fixes #2001 --- modules/angular2/{router.js => router.ts} | 15 +- modules/angular2/src/dom/dom_adapter.ts | 8 +- modules/angular2/src/facade/browser.dart | 6 +- modules/angular2/src/facade/browser.ts | 4 + .../{location_mock.js => location_mock.ts} | 34 +- .../angular2/src/router/browser_location.js | 37 -- .../angular2/src/router/browser_location.ts | 30 ++ .../router/{instruction.js => instruction.ts} | 64 ++-- .../src/router/{location.js => location.ts} | 35 +- ...{path_recognizer.js => path_recognizer.ts} | 114 +++--- .../src/router/{pipeline.js => pipeline.ts} | 15 +- .../src/router/route_config_annotation.dart | 3 - .../src/router/route_config_annotation.es6 | 1 - .../src/router/route_config_decorator.dart | 2 +- ...ecorator.es6 => route_config_decorator.ts} | 1 - ...te_config_impl.js => route_config_impl.ts} | 8 +- ...oute_recognizer.js => route_recognizer.ts} | 79 ++-- .../{route_registry.js => route_registry.ts} | 71 ++-- .../src/router/{router.js => router.ts} | 114 +++--- .../router/{router_link.js => router_link.ts} | 29 +- .../{router_outlet.js => router_outlet.ts} | 52 +-- .../angular2/src/router/{url.js => url.ts} | 10 +- .../{location_spec.js => location_spec.ts} | 51 +-- modules/angular2/test/router/outlet_spec.js | 348 ------------------ modules/angular2/test/router/outlet_spec.ts | 314 ++++++++++++++++ ...nizer_spec.js => route_recognizer_spec.ts} | 45 ++- ...egistry_spec.js => route_registry_spec.ts} | 30 +- .../test/router/router_integration_spec.js | 78 ---- .../test/router/router_integration_spec.ts | 67 ++++ modules/angular2/test/router/router_spec.js | 103 ------ modules/angular2/test/router/router_spec.ts | 109 ++++++ 31 files changed, 900 insertions(+), 977 deletions(-) rename modules/angular2/{router.js => router.ts} (78%) rename modules/angular2/src/mock/{location_mock.js => location_mock.ts} (64%) delete mode 100644 modules/angular2/src/router/browser_location.js create mode 100644 modules/angular2/src/router/browser_location.ts rename modules/angular2/src/router/{instruction.js => instruction.ts} (63%) rename modules/angular2/src/router/{location.js => location.ts} (70%) rename modules/angular2/src/router/{path_recognizer.js => path_recognizer.ts} (57%) rename modules/angular2/src/router/{pipeline.js => pipeline.ts} (66%) delete mode 100644 modules/angular2/src/router/route_config_annotation.dart delete mode 100644 modules/angular2/src/router/route_config_annotation.es6 rename modules/angular2/src/router/{route_config_decorator.es6 => route_config_decorator.ts} (99%) rename modules/angular2/src/router/{route_config_impl.js => route_config_impl.ts} (78%) rename modules/angular2/src/router/{route_recognizer.js => route_recognizer.ts} (54%) rename modules/angular2/src/router/{route_registry.js => route_registry.ts} (76%) rename modules/angular2/src/router/{router.js => router.ts} (67%) rename modules/angular2/src/router/{router_link.js => router_link.ts} (75%) rename modules/angular2/src/router/{router_outlet.js => router_outlet.ts} (60%) rename modules/angular2/src/router/{url.js => url.ts} (54%) rename modules/angular2/test/router/{location_spec.js => location_spec.ts} (64%) delete mode 100644 modules/angular2/test/router/outlet_spec.js create mode 100644 modules/angular2/test/router/outlet_spec.ts rename modules/angular2/test/router/{route_recognizer_spec.js => route_recognizer_spec.ts} (68%) rename modules/angular2/test/router/{route_registry_spec.js => route_registry_spec.ts} (89%) delete mode 100644 modules/angular2/test/router/router_integration_spec.js create mode 100644 modules/angular2/test/router/router_integration_spec.ts delete mode 100644 modules/angular2/test/router/router_spec.js create mode 100644 modules/angular2/test/router/router_spec.ts diff --git a/modules/angular2/router.js b/modules/angular2/router.ts similarity index 78% rename from modules/angular2/router.js rename to modules/angular2/router.ts index fc42d67166..f8492af625 100644 --- a/modules/angular2/router.js +++ b/modules/angular2/router.ts @@ -14,7 +14,6 @@ export {RouteRegistry} from './src/router/route_registry'; export {BrowserLocation} from './src/router/browser_location'; export {Location} from './src/router/location'; export {Pipeline} from './src/router/pipeline'; -export * from './src/router/route_config_annotation'; export * from './src/router/route_config_decorator'; import {BrowserLocation} from './src/router/browser_location'; @@ -27,18 +26,16 @@ import {Location} from './src/router/location'; import {appComponentTypeToken} from './src/core/application_tokens'; import {bind} from './di'; import {CONST_EXPR} from './src/facade/lang'; +import {List} from './src/facade/collection'; -export const routerDirectives:List = CONST_EXPR([ - RouterOutlet, - RouterLink -]); +export const routerDirectives: List = CONST_EXPR([RouterOutlet, RouterLink]); -export var routerInjectables:List = [ +export var routerInjectables: List = [ RouteRegistry, Pipeline, BrowserLocation, Location, - bind(Router).toFactory((registry, pipeline, location, appRoot) => { - return new RootRouter(registry, pipeline, location, appRoot); - }, [RouteRegistry, Pipeline, Location, appComponentTypeToken]) + bind(Router).toFactory((registry, pipeline, location, appRoot) => + { return new RootRouter(registry, pipeline, location, appRoot);}, + [RouteRegistry, Pipeline, Location, appComponentTypeToken]) ]; diff --git a/modules/angular2/src/dom/dom_adapter.ts b/modules/angular2/src/dom/dom_adapter.ts index e3795039f9..ab2e2e63d6 100644 --- a/modules/angular2/src/dom/dom_adapter.ts +++ b/modules/angular2/src/dom/dom_adapter.ts @@ -110,10 +110,10 @@ export class DomAdapter { cssToRules(css: string): List { throw _abstract(); } supportsDOMEvents(): boolean { throw _abstract(); } supportsNativeShadowDOM(): boolean { throw _abstract(); } - getGlobalEventTarget(target: string) { throw _abstract(); } - getHistory() { throw _abstract(); } - getLocation() { throw _abstract(); } - getBaseHref() { throw _abstract(); } + getGlobalEventTarget(target: string): any { throw _abstract(); } + getHistory(): any { throw _abstract(); } + getLocation(): any { throw _abstract(); } + getBaseHref(): string { throw _abstract(); } getUserAgent(): string { throw _abstract(); } setData(element, name: string, value: string) { throw _abstract(); } getData(element, name: string): string { throw _abstract(); } diff --git a/modules/angular2/src/facade/browser.dart b/modules/angular2/src/facade/browser.dart index 05ff40ef66..71931f9717 100644 --- a/modules/angular2/src/facade/browser.dart +++ b/modules/angular2/src/facade/browser.dart @@ -15,7 +15,11 @@ export 'dart:html' Node, MouseEvent, KeyboardEvent, - Event; + Event, + EventTarget, + History, + Location, + EventListener; final _gc = context['gc']; diff --git a/modules/angular2/src/facade/browser.ts b/modules/angular2/src/facade/browser.ts index 8c69d7f478..05e343676c 100644 --- a/modules/angular2/src/facade/browser.ts +++ b/modules/angular2/src/facade/browser.ts @@ -10,3 +10,7 @@ export var gc = window['gc'] ? () => window['gc']() : () => null; export const Event = Event; export const MouseEvent = MouseEvent; export const KeyboardEvent = KeyboardEvent; +export const EventTarget = EventTarget; +export const History = History; +export const Location = Location; +export const EventListener = EventListener; diff --git a/modules/angular2/src/mock/location_mock.js b/modules/angular2/src/mock/location_mock.ts similarity index 64% rename from modules/angular2/src/mock/location_mock.js rename to modules/angular2/src/mock/location_mock.ts index 95953b96df..8c374fc863 100644 --- a/modules/angular2/src/mock/location_mock.js +++ b/modules/angular2/src/mock/location_mock.ts @@ -9,10 +9,10 @@ import {Location} from 'angular2/src/router/location'; @proxy @IMPLEMENTS(Location) export class SpyLocation extends SpyObject { - urlChanges:List; - _path:string; - _subject:EventEmitter; - _baseHref:string; + urlChanges: List; + _path: string; + _subject: EventEmitter; + _baseHref: string; constructor() { super(); @@ -22,29 +22,17 @@ export class SpyLocation extends SpyObject { this._baseHref = ''; } - setInitialPath(url:string) { - this._path = url; - } + setInitialPath(url: string) { this._path = url; } - setBaseHref(url:string) { - this._baseHref = url; - } + setBaseHref(url: string) { this._baseHref = url; } - path():string { - return this._path; - } + path(): string { return this._path; } - simulateUrlPop(pathname:string) { - ObservableWrapper.callNext(this._subject, { - 'url': pathname - }); - } + simulateUrlPop(pathname: string) { ObservableWrapper.callNext(this._subject, {'url': pathname}); } - normalizeAbsolutely(url) { - return this._baseHref + url; - } + normalizeAbsolutely(url) { return this._baseHref + url; } - go(url:string) { + go(url: string) { url = this.normalizeAbsolutely(url); if (this._path == url) { return; @@ -65,5 +53,5 @@ export class SpyLocation extends SpyObject { ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn); } - noSuchMethod(m){return super.noSuchMethod(m);} + noSuchMethod(m) { return super.noSuchMethod(m); } } diff --git a/modules/angular2/src/router/browser_location.js b/modules/angular2/src/router/browser_location.js deleted file mode 100644 index 8d60fe3e1a..0000000000 --- a/modules/angular2/src/router/browser_location.js +++ /dev/null @@ -1,37 +0,0 @@ -import {DOM} from 'angular2/src/dom/dom_adapter'; - -export class BrowserLocation { - _location; - _history; - _baseHref:string; - - constructor() { - this._location = DOM.getLocation(); - this._history = DOM.getHistory(); - this._baseHref = DOM.getBaseHref(); - } - - onPopState(fn: Function): void { - DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false); - } - - getBaseHref(): string { - return this._baseHref; - } - - path(): string { - return this._location.pathname; - } - - pushState(state:any, title:string, url:string) { - this._history.pushState(state, title, url); - } - - forward(): void { - this._history.forward(); - } - - back(): void { - this._history.back(); - } -} diff --git a/modules/angular2/src/router/browser_location.ts b/modules/angular2/src/router/browser_location.ts new file mode 100644 index 0000000000..67adaf8660 --- /dev/null +++ b/modules/angular2/src/router/browser_location.ts @@ -0,0 +1,30 @@ +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {Injectable} from 'angular2/di'; +import {EventListener, History, Location} from 'angular2/src/facade/browser'; + +@Injectable() +export class BrowserLocation { + private _location: Location; + private _history: History; + private _baseHref: string; + + constructor() { + this._location = DOM.getLocation(); + this._history = DOM.getHistory(); + this._baseHref = DOM.getBaseHref(); + } + + onPopState(fn: EventListener): void { + DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false); + } + + getBaseHref(): string { return this._baseHref; } + + path(): string { return this._location.pathname; } + + pushState(state: any, title: string, url: string) { this._history.pushState(state, title, url); } + + forward(): void { this._history.forward(); } + + back(): void { this._history.back(); } +} diff --git a/modules/angular2/src/router/instruction.js b/modules/angular2/src/router/instruction.ts similarity index 63% rename from modules/angular2/src/router/instruction.js rename to modules/angular2/src/router/instruction.ts index a8243c6aa2..74cfb1703b 100644 --- a/modules/angular2/src/router/instruction.js +++ b/modules/angular2/src/router/instruction.ts @@ -1,37 +1,44 @@ -import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection'; +import { + Map, + MapWrapper, + StringMap, + StringMapWrapper, + List, + ListWrapper +} from 'angular2/src/facade/collection'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {isPresent, normalizeBlank} from 'angular2/src/facade/lang'; export class RouteParams { - params:StringMap; + constructor(public params: StringMap) {} - constructor(params:StringMap) { - this.params = params; - } - - get(param:string): string { - return normalizeBlank(StringMapWrapper.get(this.params, param)); - } + get(param: string): string { return normalizeBlank(StringMapWrapper.get(this.params, param)); } } /** * An `Instruction` represents the component hierarchy of the application based on a given route */ export class Instruction { - component:any; - _children:Map; + component: any; + private _children: StringMap; // the part of the URL captured by this instruction - capturedUrl:string; + capturedUrl: string; // the part of the URL captured by this instruction and all children - accumulatedUrl:string; + accumulatedUrl: string; - params:StringMap; - reuse:boolean; - specificity:number; + params: StringMap; + reuse: boolean; + specificity: number; - constructor({params, component, children, matchedUrl, parentSpecificity}:{params:StringMap, component:any, children:Map, matchedUrl:string, parentSpecificity:number} = {}) { + constructor({params, component, children, matchedUrl, parentSpecificity}: { + params?: StringMap, + component?: any, + children?: StringMap, + matchedUrl?: string, + parentSpecificity?: number + } = {}) { this.reuse = false; this.capturedUrl = matchedUrl; this.accumulatedUrl = matchedUrl; @@ -53,39 +60,38 @@ export class Instruction { this.params = params; } - hasChild(outletName:string):boolean { + hasChild(outletName: string): boolean { return StringMapWrapper.contains(this._children, outletName); } /** * Returns the child instruction with the given outlet name */ - getChild(outletName:string):Instruction { + getChild(outletName: string): Instruction { return StringMapWrapper.get(this._children, outletName); } /** * (child:Instruction, outletName:string) => {} */ - forEachChild(fn:Function): void { - StringMapWrapper.forEach(this._children, fn); - } + forEachChild(fn: Function): void { StringMapWrapper.forEach(this._children, fn); } /** * Does a synchronous, breadth-first traversal of the graph of instructions. * Takes a function with signature: * (child:Instruction, outletName:string) => {} */ - traverseSync(fn:Function): void { + traverseSync(fn: Function): void { this.forEachChild(fn); this.forEachChild((childInstruction, _) => childInstruction.traverseSync(fn)); } /** - * Takes a currently active instruction and sets a reuse flag on each of this instruction's children + * Takes a currently active instruction and sets a reuse flag on each of this instruction's + * children */ - reuseComponentsFrom(oldInstruction:Instruction): void { + reuseComponentsFrom(oldInstruction: Instruction): void { this.traverseSync((childInstruction, outletName) => { var oldInstructionChild = oldInstruction.getChild(outletName); if (shouldReuseComponent(childInstruction, oldInstructionChild)) { @@ -95,16 +101,16 @@ export class Instruction { } } -function shouldReuseComponent(instr1:Instruction, instr2:Instruction): boolean { +function shouldReuseComponent(instr1: Instruction, instr2: Instruction): boolean { return instr1.component == instr2.component && - StringMapWrapper.equals(instr1.params, instr2.params); + StringMapWrapper.equals(instr1.params, instr2.params); } -function mapObjAsync(obj:StringMap, fn): Promise { +function mapObjAsync(obj: StringMap, fn): Promise> { return PromiseWrapper.all(mapObj(obj, fn)); } -function mapObj(obj:StringMap, fn: Function):List { +function mapObj(obj: StringMap, fn: Function): List { var result = ListWrapper.create(); StringMapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key))); return result; diff --git a/modules/angular2/src/router/location.js b/modules/angular2/src/router/location.ts similarity index 70% rename from modules/angular2/src/router/location.js rename to modules/angular2/src/router/location.ts index a899da50b9..1c154d3dd0 100644 --- a/modules/angular2/src/router/location.js +++ b/modules/angular2/src/router/location.ts @@ -1,31 +1,24 @@ import {BrowserLocation} from './browser_location'; import {StringWrapper} from 'angular2/src/facade/lang'; import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; +import {Injectable} from 'angular2/di'; +@Injectable() export class Location { - _subject:EventEmitter; - _browserLocation:BrowserLocation; - _baseHref:string; - constructor(browserLocation:BrowserLocation) { + private _subject: EventEmitter; + private _baseHref: string; + + constructor(public _browserLocation: BrowserLocation) { this._subject = new EventEmitter(); - this._browserLocation = browserLocation; this._baseHref = stripIndexHtml(this._browserLocation.getBaseHref()); this._browserLocation.onPopState((_) => this._onPopState(_)); } - _onPopState(_): void { - ObservableWrapper.callNext(this._subject, { - 'url': this.path() - }); - } + _onPopState(_): void { ObservableWrapper.callNext(this._subject, {'url': this.path()}); } - path(): string { - return this.normalize(this._browserLocation.path()); - } + path(): string { return this.normalize(this._browserLocation.path()); } - normalize(url: string): string { - return this._stripBaseHref(stripIndexHtml(url)); - } + normalize(url: string): string { return this._stripBaseHref(stripIndexHtml(url)); } normalizeAbsolutely(url: string): string { if (url[0] != '/') { @@ -48,18 +41,14 @@ export class Location { return url; } - go(url:string): void { + go(url: string): void { var finalUrl = this.normalizeAbsolutely(url); this._browserLocation.pushState(null, '', finalUrl); } - forward(): void { - this._browserLocation.forward(); - } + forward(): void { this._browserLocation.forward(); } - back(): void { - this._browserLocation.back(); - } + back(): void { this._browserLocation.back(); } subscribe(onNext, onThrow = null, onReturn = null): void { ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn); diff --git a/modules/angular2/src/router/path_recognizer.js b/modules/angular2/src/router/path_recognizer.ts similarity index 57% rename from modules/angular2/src/router/path_recognizer.js rename to modules/angular2/src/router/path_recognizer.ts index cc06405a7c..adc0e7cf8f 100644 --- a/modules/angular2/src/router/path_recognizer.js +++ b/modules/angular2/src/router/path_recognizer.ts @@ -1,35 +1,54 @@ -import {RegExp, RegExpWrapper, RegExpMatcherWrapper, StringWrapper, isPresent, isBlank, BaseException, normalizeBlank} from 'angular2/src/facade/lang'; -import {Map, MapWrapper, StringMap, StringMapWrapper, List, ListWrapper} from 'angular2/src/facade/collection'; +import { + RegExp, + RegExpWrapper, + RegExpMatcherWrapper, + StringWrapper, + isPresent, + isBlank, + BaseException, + normalizeBlank +} from 'angular2/src/facade/lang'; +import { + Map, + MapWrapper, + StringMap, + StringMapWrapper, + List, + ListWrapper +} from 'angular2/src/facade/collection'; +import {IMPLEMENTS} from 'angular2/src/facade/lang'; import {escapeRegex} from './url'; -class StaticSegment { - string:string; - regex:string; - name:string; +// TODO(jeffbcross): implement as interface when ts2dart adds support: +// https://github.com/angular/ts2dart/issues/173 +export class Segment { + name: string; + regex: string; +} - constructor(string:string) { - this.string = string; +class StaticSegment extends Segment { + regex: string; + name: string; + + constructor(public string: string) { + super(); this.name = ''; this.regex = escapeRegex(string); } - generate(params): string { - return this.string; - } + generate(params): string { return this.string; } } +@IMPLEMENTS(Segment) class DynamicSegment { - name:string; - regex:string; - constructor(name:string) { - this.name = name; - this.regex = "([^/]+)"; - } + regex: string; + constructor(public name: string) { this.regex = "([^/]+)"; } - generate(params:StringMap): string { + generate(params: StringMap): string { if (!StringMapWrapper.contains(params, this.name)) { - throw new BaseException(`Route generator for '${this.name}' was not included in parameters passed.`) + throw new BaseException( + `Route generator for '${this.name}' was not included in parameters passed.`) } return normalizeBlank(StringMapWrapper.get(params, this.name)); } @@ -37,14 +56,10 @@ class DynamicSegment { class StarSegment { - name:string; - regex:string; - constructor(name:string) { - this.name = name; - this.regex = "(.+)"; - } + regex: string; + constructor(public name: string) { this.regex = "(.+)"; } - generate(params:StringMap): string { + generate(params: StringMap): string { return normalizeBlank(StringMapWrapper.get(params, this.name)); } } @@ -53,7 +68,7 @@ class StarSegment { var paramMatcher = RegExpWrapper.create("^:([^\/]+)$"); var wildcardMatcher = RegExpWrapper.create("^\\*([^\/]+)$"); -function parsePathString(route:string) { +function parsePathString(route: string) { // normalize route as not starting with a "/". Recognition will // also normalize. if (route[0] === "/") { @@ -64,19 +79,22 @@ function parsePathString(route:string) { var results = ListWrapper.create(); var specificity = 0; - // The "specificity" of a path is used to determine which route is used when multiple routes match a URL. - // Static segments (like "/foo") are the most specific, followed by dynamic segments (like "/:id"). Star segments + // The "specificity" of a path is used to determine which route is used when multiple routes match + // a URL. + // Static segments (like "/foo") are the most specific, followed by dynamic segments (like + // "/:id"). Star segments // add no specificity. Segments at the start of the path are more specific than proceeding ones. - // The code below uses place values to combine the different types of segments into a single integer that we can - // sort later. Each static segment is worth hundreds of points of specificity (10000, 9900, ..., 200), and each + // The code below uses place values to combine the different types of segments into a single + // integer that we can + // sort later. Each static segment is worth hundreds of points of specificity (10000, 9900, ..., + // 200), and each // dynamic segment is worth single points of specificity (100, 99, ... 2). if (segments.length > 98) { throw new BaseException(`'${route}' has more than the maximum supported number of segments.`); } - for (var i=0; i { +function splitBySlash(url: string): List { return url.split('/'); } // represents something like '/foo/:bar' export class PathRecognizer { - segments:List; - regex:RegExp; - handler:any; - specificity:number; - path:string; + segments: List; + regex: RegExp; + specificity: number; - constructor(path:string, handler:any) { - this.path = path; - this.handler = handler; + constructor(public path: string, public handler: any) { this.segments = []; // TODO: use destructuring assignment @@ -117,19 +131,17 @@ export class PathRecognizer { var segments = parsed['segments']; var regexString = '^'; - ListWrapper.forEach(segments, (segment) => { - regexString += '/' + segment.regex; - }); + ListWrapper.forEach(segments, (segment) => { regexString += '/' + segment.regex; }); this.regex = RegExpWrapper.create(regexString); this.segments = segments; this.specificity = specificity; } - parseParams(url:string):StringMap { + parseParams(url: string): StringMap { var params = StringMapWrapper.create(); var urlPart = url; - for(var i=0; i):string { - return ListWrapper.join(ListWrapper.map(this.segments, (segment) => - '/' + segment.generate(params)), ''); + generate(params: StringMap): string { + return ListWrapper.join( + ListWrapper.map(this.segments, (segment) => '/' + segment.generate(params)), ''); } } diff --git a/modules/angular2/src/router/pipeline.js b/modules/angular2/src/router/pipeline.ts similarity index 66% rename from modules/angular2/src/router/pipeline.js rename to modules/angular2/src/router/pipeline.ts index f199044b4d..16298c3e38 100644 --- a/modules/angular2/src/router/pipeline.js +++ b/modules/angular2/src/router/pipeline.ts @@ -7,19 +7,14 @@ import {Instruction} from './instruction'; * "Steps" are conceptually similar to "middleware" */ export class Pipeline { - steps:List; + steps: List; - constructor() { - this.steps = [ - instruction => instruction.router.activateOutlets(instruction) - ]; - } + constructor() { this.steps = [instruction => instruction.router.activateOutlets(instruction)]; } - process(instruction:Instruction):Promise { - var steps = this.steps, - currentStep = 0; + process(instruction: Instruction): Promise { + var steps = this.steps, currentStep = 0; - function processOne(result:any = true):Promise { + function processOne(result: any = true): Promise { if (currentStep >= steps.length) { return PromiseWrapper.resolve(result); } diff --git a/modules/angular2/src/router/route_config_annotation.dart b/modules/angular2/src/router/route_config_annotation.dart deleted file mode 100644 index be520efc56..0000000000 --- a/modules/angular2/src/router/route_config_annotation.dart +++ /dev/null @@ -1,3 +0,0 @@ -library angular2.router.route_config_annotations; - -export './route_config_impl.dart'; diff --git a/modules/angular2/src/router/route_config_annotation.es6 b/modules/angular2/src/router/route_config_annotation.es6 deleted file mode 100644 index 59f27e9869..0000000000 --- a/modules/angular2/src/router/route_config_annotation.es6 +++ /dev/null @@ -1 +0,0 @@ -export {RouteConfig as RouteConfigAnnotation} from './route_config_impl'; diff --git a/modules/angular2/src/router/route_config_decorator.dart b/modules/angular2/src/router/route_config_decorator.dart index 6065b5aadb..6e64fe392d 100644 --- a/modules/angular2/src/router/route_config_decorator.dart +++ b/modules/angular2/src/router/route_config_decorator.dart @@ -1,3 +1,3 @@ library angular2.router.route_config_decorator; -/** This file is intentionally empty, as Dart does not have decorators */ +export './route_config_impl.dart'; diff --git a/modules/angular2/src/router/route_config_decorator.es6 b/modules/angular2/src/router/route_config_decorator.ts similarity index 99% rename from modules/angular2/src/router/route_config_decorator.es6 rename to modules/angular2/src/router/route_config_decorator.ts index 09e8dd3a28..c2b9db5d69 100644 --- a/modules/angular2/src/router/route_config_decorator.es6 +++ b/modules/angular2/src/router/route_config_decorator.ts @@ -2,4 +2,3 @@ import {RouteConfig as RouteConfigAnnotation} from './route_config_impl'; import {makeDecorator} from 'angular2/src/util/decorators'; export var RouteConfig = makeDecorator(RouteConfigAnnotation); - diff --git a/modules/angular2/src/router/route_config_impl.js b/modules/angular2/src/router/route_config_impl.ts similarity index 78% rename from modules/angular2/src/router/route_config_impl.js rename to modules/angular2/src/router/route_config_impl.ts index 625727b5d2..b5779c455d 100644 --- a/modules/angular2/src/router/route_config_impl.js +++ b/modules/angular2/src/router/route_config_impl.ts @@ -9,11 +9,7 @@ import {List, Map} from 'angular2/src/facade/collection'; * - `component`, `components`, `redirectTo` (requires exactly one of these) * - `as` (optional) */ +@CONST() export class RouteConfig { - configs:List; - - @CONST() - constructor(configs:List) { - this.configs = configs; - } + constructor(public configs: List>) {} } diff --git a/modules/angular2/src/router/route_recognizer.js b/modules/angular2/src/router/route_recognizer.ts similarity index 54% rename from modules/angular2/src/router/route_recognizer.js rename to modules/angular2/src/router/route_recognizer.ts index d9b2475ffe..972b705eea 100644 --- a/modules/angular2/src/router/route_recognizer.js +++ b/modules/angular2/src/router/route_recognizer.ts @@ -1,16 +1,30 @@ -import {RegExp, RegExpWrapper, StringWrapper, isPresent, BaseException} from 'angular2/src/facade/lang'; -import {Map, MapWrapper, List, ListWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection'; +import { + RegExp, + RegExpWrapper, + StringWrapper, + isPresent, + BaseException +} from 'angular2/src/facade/lang'; +import { + Map, + MapWrapper, + List, + ListWrapper, + StringMap, + StringMapWrapper +} from 'angular2/src/facade/collection'; import {PathRecognizer} from './path_recognizer'; /** * `RouteRecognizer` is responsible for recognizing routes for a single component. - * It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of components. + * It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of + * components. */ export class RouteRecognizer { - names:Map; - redirects:Map; - matchers:Map; + names: Map; + redirects: Map; + matchers: Map; constructor() { this.names = MapWrapper.create(); @@ -18,15 +32,14 @@ export class RouteRecognizer { this.redirects = MapWrapper.create(); } - addRedirect(path:string, target:string): void { - MapWrapper.set(this.redirects, path, target); - } + addRedirect(path: string, target: string): void { MapWrapper.set(this.redirects, path, target); } - addConfig(path:string, handler:any, alias:string = null): void { + addConfig(path: string, handler: any, alias: string = null): void { var recognizer = new PathRecognizer(path, handler); MapWrapper.forEach(this.matchers, (matcher, _) => { if (recognizer.regex.toString() == matcher.regex.toString()) { - throw new BaseException(`Configuration '${path}' conflicts with existing route '${matcher.path}'`); + throw new BaseException( + `Configuration '${path}' conflicts with existing route '${matcher.path}'`); } }); MapWrapper.set(this.matchers, recognizer.regex, recognizer); @@ -40,11 +53,11 @@ export class RouteRecognizer { * Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route. * */ - recognize(url:string):List { + recognize(url: string): List { var solutions = ListWrapper.create(); MapWrapper.forEach(this.redirects, (target, path) => { - //TODO: "/" redirect case + // TODO: "/" redirect case if (StringWrapper.startsWith(url, path)) { url = target + StringWrapper.substring(url, path.length); } @@ -53,7 +66,7 @@ export class RouteRecognizer { MapWrapper.forEach(this.matchers, (pathRecognizer, regex) => { var match; if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) { - //TODO(btford): determine a good generic way to deal with terminal matches + // TODO(btford): determine a good generic way to deal with terminal matches var matchedUrl = '/'; var unmatchedUrl = ''; if (url != '/') { @@ -61,37 +74,39 @@ export class RouteRecognizer { unmatchedUrl = StringWrapper.substring(url, match[0].length); } ListWrapper.push(solutions, new RouteMatch({ - specificity: pathRecognizer.specificity, - handler: pathRecognizer.handler, - params: pathRecognizer.parseParams(url), - matchedUrl: matchedUrl, - unmatchedUrl: unmatchedUrl - })); + specificity: pathRecognizer.specificity, + handler: pathRecognizer.handler, + params: pathRecognizer.parseParams(url), + matchedUrl: matchedUrl, + unmatchedUrl: unmatchedUrl + })); } }); return solutions; } - hasRoute(name:string): boolean { - return MapWrapper.contains(this.names, name); - } + hasRoute(name: string): boolean { return MapWrapper.contains(this.names, name); } - generate(name:string, params:any): string { + generate(name: string, params: any): string { var pathRecognizer = MapWrapper.get(this.names, name); return isPresent(pathRecognizer) ? pathRecognizer.generate(params) : null; } } export class RouteMatch { - specificity:number; - handler:StringMap; - params:StringMap; - matchedUrl:string; - unmatchedUrl:string; - constructor({specificity, handler, params, matchedUrl, unmatchedUrl}: - {specificity:number, handler:StringMap, params:StringMap, matchedUrl:string, unmatchedUrl:string} = {}) { - + specificity: number; + handler: StringMap; + params: StringMap; + matchedUrl: string; + unmatchedUrl: string; + constructor({specificity, handler, params, matchedUrl, unmatchedUrl}: { + specificity?: number, + handler?: StringMap, + params?: StringMap, + matchedUrl?: string, + unmatchedUrl?: string + } = {}) { this.specificity = specificity; this.handler = handler; this.params = params; diff --git a/modules/angular2/src/router/route_registry.js b/modules/angular2/src/router/route_registry.ts similarity index 76% rename from modules/angular2/src/router/route_registry.js rename to modules/angular2/src/router/route_registry.ts index 1b4548c021..7f19576090 100644 --- a/modules/angular2/src/router/route_registry.js +++ b/modules/angular2/src/router/route_registry.ts @@ -1,25 +1,31 @@ import {RouteRecognizer, RouteMatch} from './route_recognizer'; -import {Instruction, noopInstruction} from './instruction'; -import {List, ListWrapper, Map, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection'; +import {Instruction} from './instruction'; +import { + List, + ListWrapper, + Map, + MapWrapper, + StringMap, + StringMapWrapper +} from 'angular2/src/facade/collection'; import {isPresent, isBlank, isType, StringWrapper, BaseException} from 'angular2/src/facade/lang'; import {RouteConfig} from './route_config_impl'; import {reflector} from 'angular2/src/reflection/reflection'; /** * The RouteRegistry holds route configurations for each component in an Angular app. - * It is responsible for creating Instructions from URLs, and generating URLs based on route and parameters. + * It is responsible for creating Instructions from URLs, and generating URLs based on route and + * parameters. */ export class RouteRegistry { - _rules:Map; + _rules: Map; - constructor() { - this._rules = MapWrapper.create(); - } + constructor() { this._rules = MapWrapper.create(); } /** * Given a component and a configuration object, add the route to this registry */ - config(parentComponent, config:StringMap): void { + config(parentComponent, config: StringMap): void { if (!StringMapWrapper.contains(config, 'path')) { throw new BaseException('Route config does not contain "path"'); } @@ -27,10 +33,11 @@ export class RouteRegistry { if (!StringMapWrapper.contains(config, 'component') && !StringMapWrapper.contains(config, 'components') && !StringMapWrapper.contains(config, 'redirectTo')) { - throw new BaseException('Route config does not contain "component," "components," or "redirectTo"'); + throw new BaseException( + 'Route config does not contain "component," "components," or "redirectTo"'); } - var recognizer:RouteRecognizer = MapWrapper.get(this._rules, parentComponent); + var recognizer: RouteRecognizer = MapWrapper.get(this._rules, parentComponent); if (isBlank(recognizer)) { recognizer = new RouteRecognizer(); @@ -65,7 +72,7 @@ export class RouteRegistry { } var annotations = reflector.annotations(component); if (isPresent(annotations)) { - for (var i=0; i, hostComponent): string { - //TODO: implement for hierarchical routes + generate(name: string, params: StringMap, hostComponent): string { + // TODO: implement for hierarchical routes var componentRecognizer = MapWrapper.get(this._rules, hostComponent); return isPresent(componentRecognizer) ? componentRecognizer.generate(name, params) : null; } } -function routeMatchToInstruction(routeMatch:RouteMatch, parentComponent): Instruction { +function routeMatchToInstruction(routeMatch: RouteMatch, parentComponent): Instruction { var children = StringMapWrapper.create(); var components = StringMapWrapper.get(routeMatch.handler, 'components'); StringMapWrapper.forEach(components, (component, outletName) => { - children[outletName] = new Instruction({ - component: component, - params: routeMatch.params, - parentSpecificity: 0 - }); + children[outletName] = + new Instruction({component: component, params: routeMatch.params, parentSpecificity: 0}); }); return new Instruction({ component: parentComponent, @@ -181,15 +184,11 @@ function routeMatchToInstruction(routeMatch:RouteMatch, parentComponent): Instru * If the config object does not contain a `component` key, the original * config object is returned. */ -function normalizeConfig(config:StringMap): StringMap { +function normalizeConfig(config: StringMap): StringMap { if (!StringMapWrapper.contains(config, 'component')) { return config; } - var newConfig = { - 'components': { - 'default': config['component'] - } - }; + var newConfig = {'components': {'default': config['component']}}; StringMapWrapper.forEach(config, (value, key) => { if (key != 'component' && key != 'components') { diff --git a/modules/angular2/src/router/router.js b/modules/angular2/src/router/router.ts similarity index 67% rename from modules/angular2/src/router/router.js rename to modules/angular2/src/router/router.ts index e8df794e32..d13a83af37 100644 --- a/modules/angular2/src/router/router.js +++ b/modules/angular2/src/router/router.ts @@ -21,51 +21,44 @@ import {Location} from './location'; * The router holds reference to a number of "outlets." An outlet is a placeholder that the * router dynamically fills in depending on the current URL. * - * When the router navigates from a URL, it must first recognizes it and serialize it into an `Instruction`. + * When the router navigates from a URL, it must first recognizes it and serialize it into an + * `Instruction`. * The router uses the `RouteRegistry` to get an `Instruction`. * * @exportedAs angular2/router */ export class Router { - hostComponent:any; - parent:Router; - navigating:boolean; + navigating: boolean; lastNavigationAttempt: string; - previousUrl:string; + previousUrl: string; - _currentInstruction:Instruction; - - _pipeline:Pipeline; - _registry:RouteRegistry; - _outlets:Map; - _subject:EventEmitter; - - - constructor(registry:RouteRegistry, pipeline:Pipeline, parent:Router, hostComponent:any) { - this.hostComponent = hostComponent; + private _currentInstruction: Instruction; + private _outlets: Map; + private _subject: EventEmitter; + // todo(jeffbcross): rename _registry to registry since it is accessed from subclasses + // todo(jeffbcross): rename _pipeline to pipeline since it is accessed from subclasses + constructor(public _registry: RouteRegistry, public _pipeline: Pipeline, public parent: Router, + public hostComponent: any) { this.navigating = false; - this.parent = parent; this.previousUrl = null; this._outlets = MapWrapper.create(); - this._registry = registry; - this._pipeline = pipeline; this._subject = new EventEmitter(); this._currentInstruction = null; } /** - * Constructs a child router. You probably don't need to use this unless you're writing a reusable component. + * Constructs a child router. You probably don't need to use this unless you're writing a reusable + * component. */ - childRouter(hostComponent:any): Router { - return new ChildRouter(this, hostComponent); - } + childRouter(hostComponent: any): Router { return new ChildRouter(this, hostComponent); } /** - * Register an object to notify of route changes. You probably don't need to use this unless you're writing a reusable component. + * Register an object to notify of route changes. You probably don't need to use this unless + * you're writing a reusable component. */ - registerOutlet(outlet:RouterOutlet, name: string = 'default'): Promise { + registerOutlet(outlet: RouterOutlet, name: string = 'default'): Promise { MapWrapper.set(this._outlets, name, outlet); if (isPresent(this._currentInstruction)) { var childInstruction = this._currentInstruction.getChild(name); @@ -94,11 +87,10 @@ export class Router { * ``` * */ - config(config:any): Promise { + config(config: any): Promise { if (config instanceof List) { - config.forEach((configObject) => { - this._registry.config(this.hostComponent, configObject); - }); + config.forEach( + (configObject) => { this._registry.config(this.hostComponent, configObject); }); } else { this._registry.config(this.hostComponent, config); } @@ -112,7 +104,7 @@ export class Router { * If the given URL begins with a `/`, router will navigate absolutely. * If the given URL does not begin with `/`, the router will navigate relative to this component. */ - navigate(url:string):Promise { + navigate(url: string): Promise { if (this.navigating) { return PromiseWrapper.resolve(true); } @@ -132,37 +124,31 @@ export class Router { this._startNavigating(); var result = this.commit(matchedInstruction) - .then((_) => { - ObservableWrapper.callNext(this._subject, matchedInstruction.accumulatedUrl); - this._finishNavigating(); - }); + .then((_) => { + ObservableWrapper.callNext(this._subject, matchedInstruction.accumulatedUrl); + this._finishNavigating(); + }); PromiseWrapper.catchError(result, (_) => this._finishNavigating()); return result; } - _startNavigating(): void { - this.navigating = true; - } + _startNavigating(): void { this.navigating = true; } - _finishNavigating(): void { - this.navigating = false; - } + _finishNavigating(): void { this.navigating = false; } /** * Subscribe to URL updates from the router */ - subscribe(onNext): void { - ObservableWrapper.subscribe(this._subject, onNext); - } + subscribe(onNext): void { ObservableWrapper.subscribe(this._subject, onNext); } /** * */ - commit(instruction:Instruction):Promise { + commit(instruction: Instruction): Promise> { this._currentInstruction = instruction; // collect all outlets that do not have a corresponding child instruction @@ -184,37 +170,32 @@ export class Router { * Recursively remove all components contained by this router's outlets. * Calls deactivate hooks on all descendant components */ - deactivate():Promise { - return this._eachOutletAsync((outlet) => outlet.deactivate); - } + deactivate(): Promise { return this._eachOutletAsync((outlet) => outlet.deactivate); } /** * Recursively activate. * Calls the "activate" hook on descendant components. */ - activate(instruction:Instruction):Promise { + activate(instruction: Instruction): Promise { return this._eachOutletAsync((outlet, name) => outlet.activate(instruction.getChild(name))); } - _eachOutletAsync(fn):Promise { - return mapObjAsync(this._outlets, fn); - } + _eachOutletAsync(fn): Promise { return mapObjAsync(this._outlets, fn); } /** * Given a URL, returns an instruction representing the component graph */ - recognize(url:string): Instruction { - return this._registry.recognize(url, this.hostComponent); - } + recognize(url: string): Instruction { return this._registry.recognize(url, this.hostComponent); } /** - * Navigates to either the last URL successfully navigated to, or the last URL requested if the router has yet to successfully navigate. + * Navigates to either the last URL successfully navigated to, or the last URL requested if the + * router has yet to successfully navigate. */ - renavigate():Promise { + renavigate(): Promise { var destination = isBlank(this.previousUrl) ? this.lastNavigationAttempt : this.previousUrl; if (this.navigating || isBlank(destination)) { return PromiseWrapper.resolve(false); @@ -224,17 +205,19 @@ export class Router { /** - * Generate a URL from a component name and optional map of parameters. The URL is relative to the app's base href. + * Generate a URL from a component name and optional map of parameters. The URL is relative to the + * app's base href. */ - generate(name:string, params:StringMap): string { + generate(name: string, params: StringMap): string { return this._registry.generate(name, params, this.hostComponent); } } export class RootRouter extends Router { - _location:Location; + _location: Location; - constructor(registry:RouteRegistry, pipeline:Pipeline, location:Location, hostComponent:Type) { + constructor(registry: RouteRegistry, pipeline: Pipeline, location: Location, + hostComponent: Type) { super(registry, pipeline, null, hostComponent); this._location = location; this._location.subscribe((change) => this.navigate(change['url'])); @@ -242,25 +225,24 @@ export class RootRouter extends Router { this.navigate(location.path()); } - commit(instruction):Promise { - return super.commit(instruction).then((_) => { - this._location.go(instruction.accumulatedUrl); - }); + commit(instruction): Promise { + return super.commit(instruction) + .then((_) => { this._location.go(instruction.accumulatedUrl); }); } } class ChildRouter extends Router { - constructor(parent:Router, hostComponent) { + constructor(parent: Router, hostComponent) { super(parent._registry, parent._pipeline, parent, hostComponent); this.parent = parent; } } -function mapObjAsync(obj:Map, fn): Promise { +function mapObjAsync(obj: Map, fn: Function): Promise { return PromiseWrapper.all(mapObj(obj, fn)); } -function mapObj(obj:Map, fn):List { +function mapObj(obj: Map, fn: Function): List { var result = ListWrapper.create(); MapWrapper.forEach(obj, (value, key) => ListWrapper.push(result, fn(value, key))); return result; diff --git a/modules/angular2/src/router/router_link.js b/modules/angular2/src/router/router_link.ts similarity index 75% rename from modules/angular2/src/router/router_link.js rename to modules/angular2/src/router/router_link.ts index d16ddbb893..69878ca3be 100644 --- a/modules/angular2/src/router/router_link.js +++ b/modules/angular2/src/router/router_link.ts @@ -1,4 +1,5 @@ -import {Directive, onAllChangesDone} from 'angular2/src/core/annotations_impl/annotations'; +import {onAllChangesDone} from 'angular2/src/core/annotations/annotations'; +import {Directive} from 'angular2/src/core/annotations/decorators'; import {ElementRef} from 'angular2/core'; import {StringMap, StringMapWrapper} from 'angular2/src/facade/collection'; @@ -31,27 +32,21 @@ import {Location} from './location'; */ @Directive({ selector: '[router-link]', - properties: [ - 'route: routerLink', - 'params: routerParams' - ], + properties: ['route: routerLink', 'params: routerParams'], lifecycle: [onAllChangesDone] }) export class RouterLink { - _domEl; - _route:string; - _params:StringMap; - _router:Router; - _location:Location; + private _domEl; + private _route: string; + private _params: StringMap; + // the url displayed on the anchor element. _visibleHref: string; // the url passed to the router navigation. _navigationHref: string; - constructor(elementRef:ElementRef, router:Router, location:Location) { + constructor(elementRef: ElementRef, private _router: Router, private _location: Location) { this._domEl = elementRef.domElement; - this._router = router; - this._location = location; this._params = StringMapWrapper.create(); DOM.on(this._domEl, 'click', (evt) => { DOM.preventDefault(evt); @@ -59,13 +54,9 @@ export class RouterLink { }); } - set route(changes: string) { - this._route = changes; - } + set route(changes: string) { this._route = changes; } - set params(changes: StringMap) { - this._params = changes; - } + set params(changes: StringMap) { this._params = changes; } onAllChangesDone(): void { if (isPresent(this._route) && isPresent(this._params)) { diff --git a/modules/angular2/src/router/router_outlet.js b/modules/angular2/src/router/router_outlet.ts similarity index 60% rename from modules/angular2/src/router/router_outlet.js rename to modules/angular2/src/router/router_outlet.ts index e67851c489..7406f34034 100644 --- a/modules/angular2/src/router/router_outlet.js +++ b/modules/angular2/src/router/router_outlet.ts @@ -1,8 +1,7 @@ import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {isBlank, isPresent} from 'angular2/src/facade/lang'; -import {Directive} from 'angular2/src/core/annotations_impl/annotations'; -import {Attribute} from 'angular2/src/core/annotations_impl/di'; +import {Directive, Attribute} from 'angular2/src/core/annotations/decorators'; import {DynamicComponentLoader, ComponentRef, ElementRef} from 'angular2/core'; import {Injector, bind} from 'angular2/di'; @@ -31,22 +30,19 @@ import {Instruction, RouteParams} from './instruction' selector: 'router-outlet' }) export class RouterOutlet { - _injector:Injector; - _parentRouter:routerMod.Router; - _childRouter:routerMod.Router; - _loader:DynamicComponentLoader; - _componentRef:ComponentRef; - _elementRef:ElementRef; - _currentInstruction:Instruction; + private _childRouter: routerMod.Router; + private _componentRef: ComponentRef; + private _elementRef: ElementRef; + private _currentInstruction: Instruction; - constructor(elementRef:ElementRef, loader:DynamicComponentLoader, router:routerMod.Router, injector:Injector, @Attribute('name') nameAttr:String) { + constructor(elementRef: ElementRef, private _loader: DynamicComponentLoader, + private _parentRouter: routerMod.Router, private _injector: Injector, + @Attribute('name') nameAttr: string) { if (isBlank(nameAttr)) { nameAttr = 'default'; } - this._loader = loader; - this._parentRouter = router; + this._elementRef = elementRef; - this._injector = injector; this._childRouter = null; this._componentRef = null; @@ -57,17 +53,20 @@ export class RouterOutlet { /** * Given an instruction, update the contents of this viewport. */ - activate(instruction:Instruction): Promise { - // if we're able to reuse the component, we just have to pass along the instruction to the component's router + activate(instruction: Instruction): Promise { + // if we're able to reuse the component, we just have to pass along the instruction to the + // component's router // so it can propagate changes to its children - if ((instruction == this._currentInstruction) || instruction.reuse && isPresent(this._childRouter)) { + if ((instruction == this._currentInstruction) || + instruction.reuse && isPresent(this._childRouter)) { return this._childRouter.commit(instruction); } this._currentInstruction = instruction; this._childRouter = this._parentRouter.childRouter(instruction.component); var outletInjector = this._injector.resolveAndCreateChild([ - bind(RouteParams).toValue(new RouteParams(instruction.params)), + bind(RouteParams) + .toValue(new RouteParams(instruction.params)), bind(routerMod.Router).toValue(this._childRouter) ]); @@ -75,18 +74,21 @@ export class RouterOutlet { this._componentRef.dispose(); } - return this._loader.loadNextToExistingLocation(instruction.component, this._elementRef, outletInjector).then((componentRef) => { - this._componentRef = componentRef; - return this._childRouter.commit(instruction); - }); + return this._loader.loadNextToExistingLocation(instruction.component, this._elementRef, + outletInjector) + .then((componentRef) => { + this._componentRef = componentRef; + return this._childRouter.commit(instruction); + }); } - deactivate():Promise { - return (isPresent(this._childRouter) ? this._childRouter.deactivate() : PromiseWrapper.resolve(true)) - .then((_) =>this._componentRef.dispose()); + deactivate(): Promise { + return (isPresent(this._childRouter) ? this._childRouter.deactivate() : + PromiseWrapper.resolve(true)) + .then((_) => this._componentRef.dispose()); } - canDeactivate(instruction:Instruction): Promise { + canDeactivate(instruction: Instruction): Promise { // TODO: how to get ahold of the component instance here? return PromiseWrapper.resolve(true); } diff --git a/modules/angular2/src/router/url.js b/modules/angular2/src/router/url.ts similarity index 54% rename from modules/angular2/src/router/url.js rename to modules/angular2/src/router/url.ts index 0df9751e4a..90136eed2e 100644 --- a/modules/angular2/src/router/url.js +++ b/modules/angular2/src/router/url.ts @@ -1,13 +1,9 @@ import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang'; -var specialCharacters = [ - '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\' -]; +var specialCharacters = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']; var escapeRe = RegExpWrapper.create('(\\' + specialCharacters.join('|\\') + ')', 'g'); -export function escapeRegex(string:string): string { - return StringWrapper.replaceAllMapped(string, escapeRe, (match) => { - return "\\" + match; - }); +export function escapeRegex(string: string): string { + return StringWrapper.replaceAllMapped(string, escapeRe, (match) => { return "\\" + match; }); } diff --git a/modules/angular2/test/router/location_spec.js b/modules/angular2/test/router/location_spec.ts similarity index 64% rename from modules/angular2/test/router/location_spec.js rename to modules/angular2/test/router/location_spec.ts index 4913601a2a..b6fd0a0eed 100644 --- a/modules/angular2/test/router/location_spec.js +++ b/modules/angular2/test/router/location_spec.ts @@ -2,10 +2,15 @@ import { AsyncTestCompleter, describe, proxy, - it, iit, - ddescribe, expect, - inject, beforeEach, beforeEachBindings, - SpyObject} from 'angular2/test_lib'; + it, + iit, + ddescribe, + expect, + inject, + beforeEach, + beforeEachBindings, + SpyObject +} from 'angular2/test_lib'; import {IMPLEMENTS} from 'angular2/src/facade/lang'; import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; @@ -13,7 +18,6 @@ import {BrowserLocation} from 'angular2/src/router/browser_location'; import {Location} from 'angular2/src/router/location'; export function main() { - describe('Location', () => { var browserLocation, location; @@ -27,28 +31,31 @@ export function main() { it('should normalize relative urls on navigate', () => { location.go('user/btford'); - expect(browserLocation.spy('pushState')).toHaveBeenCalledWith(null, '', '/my/app/user/btford'); + expect(browserLocation.spy('pushState')) + .toHaveBeenCalledWith(null, '', '/my/app/user/btford'); }); it('should not append urls with leading slash on navigate', () => { location.go('/my/app/user/btford'); - expect(browserLocation.spy('pushState')).toHaveBeenCalledWith(null, '', '/my/app/user/btford'); + expect(browserLocation.spy('pushState')) + .toHaveBeenCalledWith(null, '', '/my/app/user/btford'); }); it('should remove index.html from base href', () => { browserLocation.baseHref = '/my/app/index.html'; location = new Location(browserLocation); location.go('user/btford'); - expect(browserLocation.spy('pushState')).toHaveBeenCalledWith(null, '', '/my/app/user/btford'); + expect(browserLocation.spy('pushState')) + .toHaveBeenCalledWith(null, '', '/my/app/user/btford'); }); it('should normalize urls on popstate', inject([AsyncTestCompleter], (async) => { - browserLocation.simulatePopState('/my/app/user/btford'); - location.subscribe((ev) => { - expect(ev['url']).toEqual('/user/btford'); - async.done(); - }) - })); + browserLocation.simulatePopState('/my/app/user/btford'); + location.subscribe((ev) => { + expect(ev['url']).toEqual('/user/btford'); + async.done(); + }) + })); it('should normalize location path', () => { browserLocation.internalPath = '/my/app/user/btford'; @@ -62,7 +69,7 @@ export function main() { class DummyBrowserLocation extends SpyObject { baseHref; internalPath; - _subject:EventEmitter; + _subject: EventEmitter; constructor() { super(); this.internalPath = '/'; @@ -74,17 +81,11 @@ class DummyBrowserLocation extends SpyObject { ObservableWrapper.callNext(this._subject, null); } - path() { - return this.internalPath; - } + path() { return this.internalPath; } - onPopState(fn) { - ObservableWrapper.subscribe(this._subject, fn); - } + onPopState(fn) { ObservableWrapper.subscribe(this._subject, fn); } - getBaseHref() { - return this.baseHref; - } + getBaseHref() { return this.baseHref; } - noSuchMethod(m){return super.noSuchMethod(m);} + noSuchMethod(m) { return super.noSuchMethod(m); } } diff --git a/modules/angular2/test/router/outlet_spec.js b/modules/angular2/test/router/outlet_spec.js deleted file mode 100644 index 92e1deebf4..0000000000 --- a/modules/angular2/test/router/outlet_spec.js +++ /dev/null @@ -1,348 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - xdescribe, - describe, - el, - expect, - iit, - inject, - beforeEachBindings, - it, - xit - } from 'angular2/test_lib'; - -import {TestBed} from 'angular2/test'; - -import {Injector, bind} from 'angular2/di'; -import {Component} from 'angular2/src/core/annotations_impl/annotations'; -import {View} from 'angular2/src/core/annotations_impl/view'; - -import {RootRouter} from 'angular2/src/router/router'; -import {Pipeline} from 'angular2/src/router/pipeline'; -import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router'; -import {RouteConfig} from 'angular2/src/router/route_config_impl'; - -import {DOM} from 'angular2/src/dom/dom_adapter'; - -import {SpyLocation} from 'angular2/src/mock/location_mock'; -import {Location} from 'angular2/src/router/location'; -import {RouteRegistry} from 'angular2/src/router/route_registry'; -import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; - -var teamCmpCount; - -export function main() { - describe('Outlet Directive', () => { - - var ctx, tb, view, rtr, location; - - beforeEachBindings(() => [ - Pipeline, - RouteRegistry, - DirectiveResolver, - bind(Location).toClass(SpyLocation), - bind(Router).toFactory((registry, pipeline, location) => { - return new RootRouter(registry, pipeline, location, MyComp); - }, [RouteRegistry, Pipeline, Location]) - ]); - - beforeEach(inject([TestBed, Router, Location], (testBed, router, loc) => { - tb = testBed; - ctx = new MyComp(); - rtr = router; - location = loc; - teamCmpCount = 0; - })); - - function compile(template:string = "") { - tb.overrideView(MyComp, new View({template: ('
' + template + '
'), directives: [RouterOutlet, RouterLink]})); - return tb.createView(MyComp, {context: ctx}).then((v) => { - view = v; - }); - } - - it('should work in a simple case', inject([AsyncTestCompleter], (async) => { - compile() - .then((_) => rtr.config({'path': '/test', 'component': HelloCmp})) - .then((_) => rtr.navigate('/test')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('hello'); - async.done(); - }); - })); - - - it('should navigate between components with different parameters', inject([AsyncTestCompleter], (async) => { - compile() - .then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp})) - .then((_) => rtr.navigate('/user/brian')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('hello brian'); - }) - .then((_) => rtr.navigate('/user/igor')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('hello igor'); - async.done(); - }); - })); - - - it('should work with child routers', inject([AsyncTestCompleter], (async) => { - compile('outer { }') - .then((_) => rtr.config({'path': '/a', 'component': ParentCmp})) - .then((_) => rtr.navigate('/a/b')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('outer { inner { hello } }'); - async.done(); - }); - })); - - - it('should work with sibling routers', inject([AsyncTestCompleter], (async) => { - compile('left { } | right { }') - .then((_) => rtr.config({'path': '/ab', 'components': {'left': A, 'right': B} })) - .then((_) => rtr.config({'path': '/ba', 'components': {'left': B, 'right': A} })) - .then((_) => rtr.navigate('/ab')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('left { A } | right { B }'); - }) - .then((_) => rtr.navigate('/ba')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('left { B } | right { A }'); - async.done(); - }); - })); - - - it('should work with redirects', inject([AsyncTestCompleter, Location], (async, location) => { - compile() - .then((_) => rtr.config({'path': '/original', 'redirectTo': '/redirected' })) - .then((_) => rtr.config({'path': '/redirected', 'component': A })) - .then((_) => rtr.navigate('/original')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('A'); - expect(location.urlChanges).toEqual(['/redirected']); - async.done(); - }); - })); - - function getHref(view) { - return DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href'); - } - - it('should generate absolute hrefs that include the base href', inject([AsyncTestCompleter], (async) => { - location.setBaseHref('/my/base'); - compile('') - .then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'})) - .then((_) => rtr.navigate('/a/b')) - .then((_) => { - view.detectChanges(); - expect(getHref(view)).toEqual('/my/base/user'); - async.done(); - }); - })); - - - it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => { - compile('') - .then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'})) - .then((_) => rtr.navigate('/a/b')) - .then((_) => { - view.detectChanges(); - expect(getHref(view)).toEqual('/user'); - async.done(); - }); - })); - - - it('should reuse common parent components', inject([AsyncTestCompleter], (async) => { - compile() - .then((_) => rtr.config({'path': '/team/:id', 'component': TeamCmp })) - .then((_) => rtr.navigate('/team/angular/user/rado')) - .then((_) => { - view.detectChanges(); - expect(teamCmpCount).toBe(1); - expect(view.rootNodes).toHaveText('team angular { hello rado }'); - }) - .then((_) => rtr.navigate('/team/angular/user/victor')) - .then((_) => { - view.detectChanges(); - expect(teamCmpCount).toBe(1); - expect(view.rootNodes).toHaveText('team angular { hello victor }'); - async.done(); - }); - })); - - - it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => { - ctx.name = 'brian'; - compile('{{name}}') - .then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'})) - .then((_) => rtr.navigate('/a/b')) - .then((_) => { - view.detectChanges(); - expect(view.rootNodes).toHaveText('brian'); - expect(DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href')).toEqual('/user/brian'); - async.done(); - }); - })); - - describe('when clicked', () => { - - var clickOnElement = function(view) { - var anchorEl = view.rootNodes[0].childNodes[0]; - var dispatchedEvent = DOM.createMouseEvent('click'); - DOM.dispatchEvent(anchorEl, dispatchedEvent); - return dispatchedEvent; - }; - - it('test', inject([AsyncTestCompleter], (async) => { - async.done(); - })); - - it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => { - compile('') - .then((_) => rtr.config({ - 'path': '/user', - 'component': UserCmp, - 'as': 'user' - })) - .then((_) => rtr.navigate('/a/b')) - .then((_) => { - view.detectChanges(); - - var dispatchedEvent = clickOnElement(view); - expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue).toBe(true); - - // router navigation is async. - rtr.subscribe((_) => { - expect(location.urlChanges).toEqual(['/user']); - async.done(); - }); - }); - })); - - it('should navigate to link hrefs in presence of base href', inject([AsyncTestCompleter], (async) => { - location.setBaseHref('/base'); - compile('') - .then((_) => rtr.config({ - 'path': '/user', - 'component': UserCmp, - 'as': 'user' - })) - .then((_) => rtr.navigate('/a/b')) - .then((_) => { - view.detectChanges(); - - var dispatchedEvent = clickOnElement(view); - expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue).toBe(true); - - // router navigation is async. - rtr.subscribe((_) => { - expect(location.urlChanges).toEqual(['/base/user']); - async.done(); - }); - }); - })); - }); - }); -} - - -@Component({ - selector: 'hello-cmp' -}) -@View({ - template: "{{greeting}}" -}) -class HelloCmp { - greeting:string; - constructor() { - this.greeting = "hello"; - } -} - - -@Component({ - selector: 'a-cmp' -}) -@View({ - template: "A" -}) -class A {} - - -@Component({ - selector: 'b-cmp' -}) -@View({ - template: "B" -}) -class B {} - - -@Component({ - selector: 'user-cmp' -}) -@View({ - template: "hello {{user}}" -}) -class UserCmp { - user:string; - constructor(params:RouteParams) { - this.user = params.get('name'); - } -} - - -@Component({ - selector: 'parent-cmp' -}) -@View({ - template: "inner { }", - directives: [RouterOutlet] -}) -@RouteConfig([{ - path: '/b', - component: HelloCmp -}]) -class ParentCmp { - constructor() {} -} - - -@Component({ - selector: 'team-cmp' -}) -@View({ - template: "team {{id}} { }", - directives: [RouterOutlet] -}) -@RouteConfig([{ - path: '/user/:name', - component: UserCmp -}]) -class TeamCmp { - id:string; - constructor(params:RouteParams) { - this.id = params.get('id'); - teamCmpCount += 1; - } -} - - -@Component({ - selector: 'my-comp' -}) -class MyComp { - name; -} diff --git a/modules/angular2/test/router/outlet_spec.ts b/modules/angular2/test/router/outlet_spec.ts new file mode 100644 index 0000000000..b322552b58 --- /dev/null +++ b/modules/angular2/test/router/outlet_spec.ts @@ -0,0 +1,314 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + xdescribe, + describe, + el, + expect, + iit, + inject, + beforeEachBindings, + it, + xit +} from 'angular2/test_lib'; + +import {TestBed} from 'angular2/test'; + +import {Injector, bind} from 'angular2/di'; +import {Component, View} from 'angular2/src/core/annotations/decorators'; +import * as annotations from 'angular2/src/core/annotations_impl/view'; +import {CONST} from 'angular2/src/facade/lang'; + +import {RootRouter} from 'angular2/src/router/router'; +import {Pipeline} from 'angular2/src/router/pipeline'; +import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router'; +import {RouteConfig} from 'angular2/src/router/route_config_decorator'; + +import {DOM} from 'angular2/src/dom/dom_adapter'; + +import {SpyLocation} from 'angular2/src/mock/location_mock'; +import {Location} from 'angular2/src/router/location'; +import {RouteRegistry} from 'angular2/src/router/route_registry'; +import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; + +var teamCmpCount; + +export function main() { + describe('Outlet Directive', () => { + + var ctx: MyComp; + var tb: TestBed; + var view, rtr, location; + + beforeEachBindings(() => [ + Pipeline, + RouteRegistry, + DirectiveResolver, + bind(Location).toClass(SpyLocation), + bind(Router).toFactory((registry, pipeline, location) => + { return new RootRouter(registry, pipeline, location, MyComp); }, + [RouteRegistry, Pipeline, Location]) + ]); + + beforeEach(inject([TestBed, Router, Location], (testBed, router, loc) => { + tb = testBed; + ctx = new MyComp(); + rtr = router; + location = loc; + teamCmpCount = 0; + })); + + function compile(template: string = "") { + tb.overrideView(MyComp, new annotations.View({ + template: ('
' + template + '
'), + directives: [RouterOutlet, RouterLink] + })); + return tb.createView(MyComp, {context: ctx}).then((v) => { view = v; }); + } + + it('should work in a simple case', inject([AsyncTestCompleter], (async) => { + compile() + .then((_) => rtr.config({'path': '/test', 'component': HelloCmp})) + .then((_) => rtr.navigate('/test')) + .then((_) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText('hello'); + async.done(); + }); + })); + + + it('should navigate between components with different parameters', + inject([AsyncTestCompleter], (async) => { + compile() + .then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp})) + .then((_) => rtr.navigate('/user/brian')) + .then((_) => + { + view.detectChanges(); + expect(view.rootNodes).toHaveText('hello brian'); + }) + .then((_) => rtr.navigate('/user/igor')) + .then((_) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText('hello igor'); + async.done(); + }); + })); + + + it('should work with child routers', inject([AsyncTestCompleter], (async) => { + compile('outer { }') + .then((_) => rtr.config({'path': '/a', 'component': ParentCmp})) + .then((_) => rtr.navigate('/a/b')) + .then((_) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText('outer { inner { hello } }'); + async.done(); + }); + })); + + + it('should work with sibling routers', inject([AsyncTestCompleter], (async) => { + compile( + 'left { } | right { }') + .then((_) => rtr.config({'path': '/ab', 'components': {'left': A, 'right': B}})) + .then((_) => rtr.config({'path': '/ba', 'components': {'left': B, 'right': A}})) + .then((_) => rtr.navigate('/ab')) + .then((_) => + { + view.detectChanges(); + expect(view.rootNodes).toHaveText('left { A } | right { B }'); + }) + .then((_) => rtr.navigate('/ba')) + .then((_) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText('left { B } | right { A }'); + async.done(); + }); + })); + + + it('should work with redirects', inject([AsyncTestCompleter, Location], (async, location) => { + compile() + .then((_) => rtr.config({'path': '/original', 'redirectTo': '/redirected'})) + .then((_) => rtr.config({'path': '/redirected', 'component': A})) + .then((_) => rtr.navigate('/original')) + .then((_) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText('A'); + expect(location.urlChanges).toEqual(['/redirected']); + async.done(); + }); + })); + + function getHref(view) { return DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href'); } + + it('should generate absolute hrefs that include the base href', + inject([AsyncTestCompleter], (async) => { + location.setBaseHref('/my/base'); + compile('') + .then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'})) + .then((_) => rtr.navigate('/a/b')) + .then((_) => { + view.detectChanges(); + expect(getHref(view)).toEqual('/my/base/user'); + async.done(); + }); + })); + + + it('should generate link hrefs without params', inject([AsyncTestCompleter], (async) => { + compile('') + .then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'})) + .then((_) => rtr.navigate('/a/b')) + .then((_) => { + view.detectChanges(); + expect(getHref(view)).toEqual('/user'); + async.done(); + }); + })); + + + it('should reuse common parent components', inject([AsyncTestCompleter], (async) => { + compile() + .then((_) => rtr.config({'path': '/team/:id', 'component': TeamCmp})) + .then((_) => rtr.navigate('/team/angular/user/rado')) + .then((_) => + { + view.detectChanges(); + expect(teamCmpCount).toBe(1); + expect(view.rootNodes).toHaveText('team angular { hello rado }'); + }) + .then((_) => rtr.navigate('/team/angular/user/victor')) + .then((_) => { + view.detectChanges(); + expect(teamCmpCount).toBe(1); + expect(view.rootNodes).toHaveText('team angular { hello victor }'); + async.done(); + }); + })); + + + it('should generate link hrefs with params', inject([AsyncTestCompleter], (async) => { + ctx.name = 'brian'; + compile('{{name}}') + .then((_) => rtr.config({'path': '/user/:name', 'component': UserCmp, 'as': 'user'})) + .then((_) => rtr.navigate('/a/b')) + .then((_) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText('brian'); + expect(DOM.getAttribute(view.rootNodes[0].childNodes[0], 'href')) + .toEqual('/user/brian'); + async.done(); + }); + })); + + describe('when clicked', () => { + + var clickOnElement = function(view) { + var anchorEl = view.rootNodes[0].childNodes[0]; + var dispatchedEvent = DOM.createMouseEvent('click'); + DOM.dispatchEvent(anchorEl, dispatchedEvent); + return dispatchedEvent; + }; + + it('test', inject([AsyncTestCompleter], (async) => { async.done(); })); + + it('should navigate to link hrefs without params', inject([AsyncTestCompleter], (async) => { + compile('') + .then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'})) + .then((_) => rtr.navigate('/a/b')) + .then((_) => { + view.detectChanges(); + + var dispatchedEvent = clickOnElement(view); + expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue) + .toBe(true); + + // router navigation is async. + rtr.subscribe((_) => { + expect(location.urlChanges).toEqual(['/user']); + async.done(); + }); + }); + })); + + it('should navigate to link hrefs in presence of base href', + inject([AsyncTestCompleter], (async) => { + location.setBaseHref('/base'); + compile('') + .then((_) => rtr.config({'path': '/user', 'component': UserCmp, 'as': 'user'})) + .then((_) => rtr.navigate('/a/b')) + .then((_) => { + view.detectChanges(); + + var dispatchedEvent = clickOnElement(view); + expect(dispatchedEvent.defaultPrevented || !dispatchedEvent.returnValue) + .toBe(true); + + // router navigation is async. + rtr.subscribe((_) => { + expect(location.urlChanges).toEqual(['/base/user']); + async.done(); + }); + }); + })); + }); + }); +} + + +@Component({selector: 'hello-cmp'}) +@View({template: "{{greeting}}"}) +class HelloCmp { + greeting: string; + constructor() { this.greeting = "hello"; } +} + + +@Component({selector: 'a-cmp'}) +@View({template: "A"}) +class A { +} + + +@Component({selector: 'b-cmp'}) +@View({template: "B"}) +class B { +} + + +@Component({selector: 'user-cmp'}) +@View({template: "hello {{user}}"}) +class UserCmp { + user: string; + constructor(params: RouteParams) { this.user = params.get('name'); } +} + + +@Component({selector: 'parent-cmp'}) +@View({template: "inner { }", directives: [RouterOutlet]}) +@RouteConfig([{path: '/b', component: HelloCmp}]) +class ParentCmp { + constructor() {} +} + + +@Component({selector: 'team-cmp'}) +@View({template: "team {{id}} { }", directives: [RouterOutlet]}) +@RouteConfig([{path: '/user/:name', component: UserCmp}]) +class TeamCmp { + id: string; + constructor(params: RouteParams) { + this.id = params.get('id'); + teamCmpCount += 1; + } +} + + +@Component({selector: 'my-comp'}) +class MyComp { + name; +} diff --git a/modules/angular2/test/router/route_recognizer_spec.js b/modules/angular2/test/router/route_recognizer_spec.ts similarity index 68% rename from modules/angular2/test/router/route_recognizer_spec.js rename to modules/angular2/test/router/route_recognizer_spec.ts index f0198aa9d1..933f47fd41 100644 --- a/modules/angular2/test/router/route_recognizer_spec.js +++ b/modules/angular2/test/router/route_recognizer_spec.ts @@ -1,26 +1,24 @@ import { AsyncTestCompleter, describe, - it, iit, - ddescribe, expect, - inject, beforeEach, - SpyObject} from 'angular2/test_lib'; + it, + iit, + ddescribe, + expect, + inject, + beforeEach, + SpyObject +} from 'angular2/test_lib'; import {RouteRecognizer} from 'angular2/src/router/route_recognizer'; export function main() { describe('RouteRecognizer', () => { var recognizer; - var handler = { - 'components': { 'a': 'b' } - }; - var handler2 = { - 'components': { 'b': 'c' } - }; + var handler = {'components': {'a': 'b'}}; + var handler2 = {'components': {'b': 'c'}}; - beforeEach(() => { - recognizer = new RouteRecognizer(); - }); + beforeEach(() => { recognizer = new RouteRecognizer(); }); it('should recognize a static segment', () => { @@ -40,7 +38,7 @@ export function main() { recognizer.addConfig('/user/:name', handler); var solution = recognizer.recognize('/user/brian')[0]; expect(solution.handler).toEqual(handler); - expect(solution.params).toEqual({ 'name': 'brian' }); + expect(solution.params).toEqual({'name': 'brian'}); }); @@ -48,23 +46,22 @@ export function main() { recognizer.addConfig('/first/*rest', handler); var solution = recognizer.recognize('/first/second/third')[0]; expect(solution.handler).toEqual(handler); - expect(solution.params).toEqual({ 'rest': 'second/third' }); + expect(solution.params).toEqual({'rest': 'second/third'}); }); it('should throw when given two routes that start with the same static segment', () => { recognizer.addConfig('/hello', handler); - expect(() => recognizer.addConfig('/hello', handler2)).toThrowError( - 'Configuration \'/hello\' conflicts with existing route \'/hello\'' - ); + expect(() => recognizer.addConfig('/hello', handler2)) + .toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\''); }); it('should throw when given two routes that have dynamic segments in the same order', () => { recognizer.addConfig('/hello/:person/how/:doyoudou', handler); - expect(() => recognizer.addConfig('/hello/:friend/how/:areyou', handler2)).toThrowError( - 'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'' - ); + expect(() => recognizer.addConfig('/hello/:friend/how/:areyou', handler2)) + .toThrowError( + 'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\''); }); @@ -82,14 +79,14 @@ export function main() { it('should generate URLs', () => { recognizer.addConfig('/app/user/:name', handler, 'user'); - expect(recognizer.generate('user', {'name' : 'misko'})).toEqual('/app/user/misko'); + expect(recognizer.generate('user', {'name': 'misko'})).toEqual('/app/user/misko'); }); it('should throw in the absence of required params URLs', () => { recognizer.addConfig('/app/user/:name', handler, 'user'); - expect(() => recognizer.generate('user', {})).toThrowError( - 'Route generator for \'name\' was not included in parameters passed.'); + expect(() => recognizer.generate('user', {})) + .toThrowError('Route generator for \'name\' was not included in parameters passed.'); }); }); } diff --git a/modules/angular2/test/router/route_registry_spec.js b/modules/angular2/test/router/route_registry_spec.ts similarity index 89% rename from modules/angular2/test/router/route_registry_spec.js rename to modules/angular2/test/router/route_registry_spec.ts index 72fe53bc86..2813b9a8c0 100644 --- a/modules/angular2/test/router/route_registry_spec.js +++ b/modules/angular2/test/router/route_registry_spec.ts @@ -1,22 +1,23 @@ import { AsyncTestCompleter, describe, - it, iit, - ddescribe, expect, - inject, beforeEach, - SpyObject} from 'angular2/test_lib'; + it, + iit, + ddescribe, + expect, + inject, + beforeEach, + SpyObject +} from 'angular2/test_lib'; import {RouteRegistry} from 'angular2/src/router/route_registry'; -import {RouteConfig} from 'angular2/src/router/route_config_impl'; +import {RouteConfig} from 'angular2/src/router/route_config_decorator'; export function main() { describe('RouteRegistry', () => { - var registry, - rootHostComponent = new Object(); + var registry, rootHostComponent = new Object(); - beforeEach(() => { - registry = new RouteRegistry(); - }); + beforeEach(() => { registry = new RouteRegistry(); }); it('should match the full URL', () => { registry.config(rootHostComponent, {'path': '/', 'component': DummyCompA}); @@ -87,10 +88,9 @@ export function main() { }); } -@RouteConfig([ - {'path': '/second', 'component': DummyCompB } -]) -class DummyParentComp {} - class DummyCompA {} class DummyCompB {} + +@RouteConfig([{'path': '/second', 'component': DummyCompB}]) +class DummyParentComp { +} diff --git a/modules/angular2/test/router/router_integration_spec.js b/modules/angular2/test/router/router_integration_spec.js deleted file mode 100644 index 53a13b6f36..0000000000 --- a/modules/angular2/test/router/router_integration_spec.js +++ /dev/null @@ -1,78 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - describe, - expect, - iit, - inject, - it, - xdescribe, - xit, - } from 'angular2/test_lib'; - -import {bootstrap} from 'angular2/src/core/application'; -import {Component, Directive} from 'angular2/src/core/annotations_impl/annotations'; -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {bind} from 'angular2/di'; -import {View} from 'angular2/src/core/annotations_impl/view'; -import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; -import {RouteConfig} from 'angular2/src/router/route_config_impl'; -import {routerInjectables, Router, RouteParams, RouterOutlet} from 'angular2/router'; -import {SpyLocation} from 'angular2/src/mock/location_mock'; -import {Location} from 'angular2/src/router/location'; - -export function main() { - describe('router injectables', () => { - var fakeDoc, el, testBindings; - beforeEach(() => { - fakeDoc = DOM.createHtmlDocument(); - el = DOM.createElement('app-cmp', fakeDoc); - DOM.appendChild(fakeDoc.body, el); - testBindings = [ - routerInjectables, - bind(Location).toClass(SpyLocation), - bind(DOCUMENT_TOKEN).toValue(fakeDoc) - ]; - }); - - it('should support bootstrap a simple app', inject([AsyncTestCompleter], (async) => { - bootstrap(AppCmp, testBindings).then((applicationRef) => { - var router = applicationRef.hostComponent.router; - router.subscribe((_) => { - expect(el).toHaveText('outer { hello }'); - async.done(); - }); - }); - })); - - //TODO: add a test in which the child component has bindings - }); -} - - -@Component({ - selector: 'hello-cmp' -}) -@View({ - template: "hello" -}) -class HelloCmp {} - - -@Component({ - selector: 'app-cmp' -}) -@View({ - template: "outer { }", - directives: [RouterOutlet] -}) -@RouteConfig([{ - path: '/', component: HelloCmp -}]) -class AppCmp { - router:Router; - constructor(router:Router) { - this.router = router; - } -} diff --git a/modules/angular2/test/router/router_integration_spec.ts b/modules/angular2/test/router/router_integration_spec.ts new file mode 100644 index 0000000000..ac69906a19 --- /dev/null +++ b/modules/angular2/test/router/router_integration_spec.ts @@ -0,0 +1,67 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + describe, + expect, + iit, + inject, + it, + xdescribe, + xit, +} from 'angular2/test_lib'; + +import {bootstrap} from 'angular2/src/core/application'; +import {Component, Directive, View} from 'angular2/src/core/annotations/decorators'; +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {bind} from 'angular2/di'; +import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; +import {RouteConfig} from 'angular2/src/router/route_config_decorator'; +import {routerInjectables, Router} from 'angular2/router'; +import {RouterOutlet} from 'angular2/src/router/router_outlet'; +import {SpyLocation} from 'angular2/src/mock/location_mock'; +import {Location} from 'angular2/src/router/location'; + +export function main() { + describe('router injectables', () => { + var fakeDoc, el, testBindings; + beforeEach(() => { + fakeDoc = DOM.createHtmlDocument(); + el = DOM.createElement('app-cmp', fakeDoc); + DOM.appendChild(fakeDoc.body, el); + testBindings = [ + routerInjectables, + bind(Location).toClass(SpyLocation), + bind(DOCUMENT_TOKEN).toValue(fakeDoc) + ]; + }); + + it('should support bootstrap a simple app', inject([AsyncTestCompleter], (async) => { + bootstrap(AppCmp, testBindings) + .then((applicationRef) => { + var router = applicationRef.hostComponent.router; + router.subscribe((_) => { + expect(el).toHaveText('outer { hello }'); + async.done(); + }); + }); + })); + + // TODO: add a test in which the child component has bindings + }); +} + + +@Component({selector: 'hello-cmp'}) +@View({template: "hello"}) +class HelloCmp { +} + + +@Component({selector: 'app-cmp'}) +@View({template: "outer { }", directives: [RouterOutlet]}) +@RouteConfig([{path: '/', component: HelloCmp}]) +class AppCmp { + router: Router; + constructor(router: Router) { this.router = router; } +} diff --git a/modules/angular2/test/router/router_spec.js b/modules/angular2/test/router/router_spec.js deleted file mode 100644 index b9707f224c..0000000000 --- a/modules/angular2/test/router/router_spec.js +++ /dev/null @@ -1,103 +0,0 @@ -import { - AsyncTestCompleter, - describe, - proxy, - it, iit, - ddescribe, expect, - inject, beforeEach, beforeEachBindings, - SpyObject} from 'angular2/test_lib'; -import {IMPLEMENTS} from 'angular2/src/facade/lang'; - -import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; -import {Router, RootRouter} from 'angular2/src/router/router'; -import {Pipeline} from 'angular2/src/router/pipeline'; -import {RouterOutlet} from 'angular2/src/router/router_outlet'; -import {SpyLocation} from 'angular2/src/mock/location_mock' -import {Location} from 'angular2/src/router/location'; - -import {RouteRegistry} from 'angular2/src/router/route_registry'; -import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; - -import {bind} from 'angular2/di'; - -export function main() { - describe('Router', () => { - var router, - location; - - beforeEachBindings(() => [ - Pipeline, - RouteRegistry, - DirectiveResolver, - bind(Location).toClass(SpyLocation), - bind(Router).toFactory((registry, pipeline, location) => { - return new RootRouter(registry, pipeline, location, AppCmp); - }, [RouteRegistry, Pipeline, Location]) - ]); - - - beforeEach(inject([Router, Location], (rtr, loc) => { - router = rtr; - location = loc; - })); - - - it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => { - var outlet = makeDummyOutlet(); - - router.config({'path': '/', 'component': 'Index' }) - .then((_) => router.registerOutlet(outlet)) - .then((_) => { - expect(outlet.spy('activate')).toHaveBeenCalled(); - expect(location.urlChanges).toEqual([]); - async.done(); - }); - })); - - - it('should activate viewports and update URL on navigate', inject([AsyncTestCompleter], (async) => { - var outlet = makeDummyOutlet(); - - router.registerOutlet(outlet) - .then((_) => { - return router.config({'path': '/a', 'component': 'A' }); - }) - .then((_) => router.navigate('/a')) - .then((_) => { - expect(outlet.spy('activate')).toHaveBeenCalled(); - expect(location.urlChanges).toEqual(['/a']); - async.done(); - }); - })); - - it('should navigate after being configured', inject([AsyncTestCompleter], (async) => { - var outlet = makeDummyOutlet(); - - router.registerOutlet(outlet) - .then((_) => router.navigate('/a')) - .then((_) => { - expect(outlet.spy('activate')).not.toHaveBeenCalled(); - return router.config({'path': '/a', 'component': 'A' }); - }) - .then((_) => { - expect(outlet.spy('activate')).toHaveBeenCalled(); - async.done(); - }); - })); - }); -} - -@proxy -@IMPLEMENTS(RouterOutlet) -class DummyOutlet extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}} - -function makeDummyOutlet() { - var ref = new DummyOutlet(); - ref.spy('activate').andCallFake((_) => PromiseWrapper.resolve(true)); - ref.spy('canActivate').andCallFake((_) => PromiseWrapper.resolve(true)); - ref.spy('canDeactivate').andCallFake((_) => PromiseWrapper.resolve(true)); - ref.spy('deactivate').andCallFake((_) => PromiseWrapper.resolve(true)); - return ref; -} - -class AppCmp {} diff --git a/modules/angular2/test/router/router_spec.ts b/modules/angular2/test/router/router_spec.ts new file mode 100644 index 0000000000..2e64fe0180 --- /dev/null +++ b/modules/angular2/test/router/router_spec.ts @@ -0,0 +1,109 @@ +import { + AsyncTestCompleter, + describe, + proxy, + it, + iit, + ddescribe, + expect, + inject, + beforeEach, + beforeEachBindings, + SpyObject +} from 'angular2/test_lib'; +import {IMPLEMENTS} from 'angular2/src/facade/lang'; + +import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; +import {Router, RootRouter} from 'angular2/src/router/router'; +import {Pipeline} from 'angular2/src/router/pipeline'; +import {RouterOutlet} from 'angular2/src/router/router_outlet'; +import {SpyLocation} from 'angular2/src/mock/location_mock'; +import {Location} from 'angular2/src/router/location'; + +import {RouteRegistry} from 'angular2/src/router/route_registry'; +import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; + +import {bind} from 'angular2/di'; + +export function main() { + describe('Router', () => { + var router, location; + + beforeEachBindings(() => [ + Pipeline, + RouteRegistry, + DirectiveResolver, + bind(Location).toClass(SpyLocation), + bind(Router).toFactory((registry, pipeline, location) => + { return new RootRouter(registry, pipeline, location, AppCmp); }, + [RouteRegistry, Pipeline, Location]) + ]); + + + beforeEach(inject([Router, Location], (rtr, loc) => { + router = rtr; + location = loc; + })); + + + it('should navigate based on the initial URL state', inject([AsyncTestCompleter], (async) => { + var outlet = makeDummyOutlet(); + + router.config({'path': '/', 'component': 'Index'}) + .then((_) => router.registerOutlet(outlet)) + .then((_) => { + expect(outlet.spy('activate')).toHaveBeenCalled(); + expect(location.urlChanges).toEqual([]); + async.done(); + }); + })); + + + it('should activate viewports and update URL on navigate', + inject([AsyncTestCompleter], (async) => { + var outlet = makeDummyOutlet(); + + router.registerOutlet(outlet) + .then((_) => { return router.config({'path': '/a', 'component': 'A'}); }) + .then((_) => router.navigate('/a')) + .then((_) => { + expect(outlet.spy('activate')).toHaveBeenCalled(); + expect(location.urlChanges).toEqual(['/a']); + async.done(); + }); + })); + + it('should navigate after being configured', inject([AsyncTestCompleter], (async) => { + var outlet = makeDummyOutlet(); + + router.registerOutlet(outlet) + .then((_) => router.navigate('/a')) + .then((_) => + { + expect(outlet.spy('activate')).not.toHaveBeenCalled(); + return router.config({'path': '/a', 'component': 'A'}); + }) + .then((_) => { + expect(outlet.spy('activate')).toHaveBeenCalled(); + async.done(); + }); + })); + }); +} + +@proxy +@IMPLEMENTS(RouterOutlet) +class DummyOutlet extends SpyObject { + noSuchMethod(m) { return super.noSuchMethod(m) } +} + +function makeDummyOutlet() { + var ref = new DummyOutlet(); + ref.spy('activate').andCallFake((_) => PromiseWrapper.resolve(true)); + ref.spy('canActivate').andCallFake((_) => PromiseWrapper.resolve(true)); + ref.spy('canDeactivate').andCallFake((_) => PromiseWrapper.resolve(true)); + ref.spy('deactivate').andCallFake((_) => PromiseWrapper.resolve(true)); + return ref; +} + +class AppCmp {}