From 73bf0d3228b7f2f5f945d8fc597e5b0399b58677 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Fri, 7 Feb 2014 16:22:29 -0500 Subject: [PATCH 1/4] chore(pubspec.lock): upgraded route_hierarchical to 0.4.12 --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index dc3fce02f..89cc6390a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -48,7 +48,7 @@ packages: route_hierarchical: description: route_hierarchical source: hosted - version: "0.4.11" + version: "0.4.12" shadow_dom: description: shadow_dom source: hosted From 37f1c32118383b250ba2db6f21adf1737beb2b0a Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Fri, 7 Feb 2014 16:27:47 -0500 Subject: [PATCH 2/4] fix(MetadataExtractor): ignore typedefs Fixes #524 --- lib/core/registry.dart | 1 + test/core/registry_spec.dart | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/core/registry.dart b/lib/core/registry.dart index f3a92394a..17985f893 100644 --- a/lib/core/registry.dart +++ b/lib/core/registry.dart @@ -70,6 +70,7 @@ abstract class AnnotationsMap { @NgInjectableService() class MetadataExtractor { Iterable call(Type type) { + if (reflectType(type) is TypedefMirror) return []; var metadata = reflectClass(type).metadata; if (metadata == null) return []; return metadata.map((InstanceMirror im) => im.reflectee); diff --git a/test/core/registry_spec.dart b/test/core/registry_spec.dart index acfd43397..532dbf1ae 100644 --- a/test/core/registry_spec.dart +++ b/test/core/registry_spec.dart @@ -30,8 +30,20 @@ main() => describe('RegistryMap', () { expect(keys).toEqual([new MyAnnotation('A'), new MyAnnotation('B')]); expect(types).toEqual([A1, A1]); }); + + it('should safely ignore typedefs', () { + var module = new Module() + ..type(MyMap) + ..type(MetadataExtractor) + ..value(MyTypedef, (String _) => null); + + var injector = new DynamicInjector(modules: [module]); + expect(() => injector.get(MyMap), isNot(throws)); + }); }); +typedef void MyTypedef(String arg); + class MyMap extends AnnotationMap { MyMap(Injector injector, MetadataExtractor metadataExtractor) : super(injector, metadataExtractor); From 95f6503f1390159eeedfe6d14ea60ec0d70b9381 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Fri, 7 Feb 2014 17:01:15 -0500 Subject: [PATCH 3/4] fix(scope): use correct filters when digesting scope tree --- lib/core/scope.dart | 6 +++--- test/core/scope_spec.dart | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 04497a1f7..6d1d004c6 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -559,7 +559,7 @@ class Scope implements Map { scopeCount++; for (_Watch watch = watchers.head; watch != null; watch = watch.next) { var last = watch.last; - var value = watch.get(scope, _filters); + var value = watch.get(scope, scope._filters); if (!_identical(value, last)) { return _digestHandleDirty(scope, watch, last, value, null); } @@ -581,7 +581,7 @@ class Scope implements Map { for (_Watch watch = watchers.head; watch != null; watch = watch.next) { if (identical(stopWatch, watch)) return null; var last = watch.last; - var value = watch.get(scope, _filters); + var value = watch.get(scope, scope._filters); if (!_identical(value, last)) { return _digestHandleDirty(scope, watch, last, value, log); } @@ -610,7 +610,7 @@ class Scope implements Map { watch = scope._watchers.head; } last = watch.last; - value = watch.get(scope, _filters); + value = watch.get(scope, scope._filters); } } diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index 9924581e6..4f284f0c9 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -1268,6 +1268,30 @@ main() { expect(scopeLocal['c']).toEqual('cW'); }); }); + + describe('filters', () { + + it('should use filters from correct scope when digesting scope trees', inject((Scope rootScope, Injector injector) { + var withFilterOne = injector.createChild([new Module()..type(FilterOne)], + forceNewInstances: [FilterMap]).get(FilterMap); + var withFilterTwo = injector.createChild([new Module()..type(FilterTwo)], + forceNewInstances: [FilterMap]).get(FilterMap); + + var childScopeOne = rootScope.$new(filters: withFilterOne); + var childScopeTwo = rootScope.$new(filters: withFilterTwo); + + var valueOne; + var valueTwo; + childScopeOne.$watch('"str" | newFilter', (val) => valueOne = val); + childScopeTwo.$watch('"str" | newFilter', (val) => valueTwo = val); + + rootScope.$digest(); + + expect(valueOne).toEqual('str 1'); + expect(valueTwo).toEqual('str 2'); + })); + + }); }); } @@ -1337,3 +1361,17 @@ class _JsonableIterableWrapper implements Iterable { toJson() => source.toList(); } + +@NgFilter(name:'newFilter') +class FilterOne { + call(String str) { + return '$str 1'; + } +} + +@NgFilter(name:'newFilter') +class FilterTwo { + call(String str) { + return '$str 2'; + } +} From 3db9ddd3d2ab9aa97dfe2d0bdd5631190f6c6a56 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Fri, 7 Feb 2014 17:02:03 -0500 Subject: [PATCH 4/4] feat(routing): new DSL and deferred module loading Introduced new routing DSL that allows adding new modules for views. Modules can be loaded synchronously or asynchronously, for example in case of deferred library loading. --- lib/core_dom/common.dart | 11 ++ lib/routing/module.dart | 1 + lib/routing/ng_view.dart | 20 +- lib/routing/routing.dart | 117 ++++++++++-- test/core_dom/block_spec.dart | 87 +++++++-- test/routing/routing_spec.dart | 334 ++++++++++++++++++++++++++++++++- 6 files changed, 538 insertions(+), 32 deletions(-) diff --git a/lib/core_dom/common.dart b/lib/core_dom/common.dart index f95789020..012023c00 100644 --- a/lib/core_dom/common.dart +++ b/lib/core_dom/common.dart @@ -23,3 +23,14 @@ class DirectiveRef { } } +/** + * Creates a child injector that allows loading new directives, filters and + * services from the provided modules. + */ +Injector forceNewDirectivesAndFilters(Injector injector, List modules) { + modules.add(new Module() + ..factory(Scope, + (i) => i.parent.get(Scope).$new(filters: i.get(FilterMap)))); + return injector.createChild(modules, + forceNewInstances: [DirectiveMap, FilterMap]); +} diff --git a/lib/routing/module.dart b/lib/routing/module.dart index e32c9682a..ef5998f2c 100644 --- a/lib/routing/module.dart +++ b/lib/routing/module.dart @@ -175,6 +175,7 @@ class NgRoutingModule extends Module { type(NgRoutingHelper); value(RouteProvider, null); value(RouteInitializer, null); + value(RouteInitializerFn, null); // directives value(NgViewDirective, null); diff --git a/lib/routing/ng_view.dart b/lib/routing/ng_view.dart index fb8da9988..74a65a81b 100644 --- a/lib/routing/ng_view.dart +++ b/lib/routing/ng_view.dart @@ -64,18 +64,15 @@ part of angular.routing; class NgViewDirective implements NgDetachAware, RouteProvider { final NgRoutingHelper locationService; final BlockCache blockCache; - final Scope scope; final Injector injector; final Element element; - final DirectiveMap directives; RouteHandle _route; Block _previousBlock; Scope _previousScope; Route _viewRoute; - NgViewDirective(this.element, this.blockCache, this.scope, Injector injector, - Router router, this.directives) + NgViewDirective(this.element, this.blockCache, Injector injector, Router router) : injector = injector, locationService = injector.get(NgRoutingHelper) { RouteProvider routeProvider = injector.parent.get(NgViewDirective); if (routeProvider != null) { @@ -98,7 +95,7 @@ class NgViewDirective implements NgDetachAware, RouteProvider { locationService._unregisterPortal(this); } - _show(String templateUrl, Route route) { + _show(String templateUrl, Route route, List modules) { assert(route.isActive); if (_viewRoute != null) return; @@ -112,11 +109,18 @@ class NgViewDirective implements NgDetachAware, RouteProvider { _cleanUp(); }); - blockCache.fromUrl(templateUrl, directives).then((blockFactory) { + var viewInjector = injector; + if (modules != null) { + viewInjector = forceNewDirectivesAndFilters(viewInjector, modules); + } + + var newDirectives = viewInjector.get(DirectiveMap); + blockCache.fromUrl(templateUrl, newDirectives).then((blockFactory) { _cleanUp(); - _previousScope = scope.$new(); + _previousScope = viewInjector.get(Scope).$new(); _previousBlock = blockFactory( - injector.createChild([new Module()..value(Scope, _previousScope)])); + viewInjector.createChild( + [new Module()..value(Scope, _previousScope)])); _previousBlock.elements.forEach((elm) => element.append(elm)); }); diff --git a/lib/routing/routing.dart b/lib/routing/routing.dart index 098cb2e13..4a26bf6f8 100644 --- a/lib/routing/routing.dart +++ b/lib/routing/routing.dart @@ -9,8 +9,76 @@ class ViewFactory { ViewFactory(this.locationService); call(String templateUrl) => - (RouteEvent event) => - locationService._route(event.route, templateUrl, fromEvent: true); + (RouteEnterEvent event) => _enterHandler(event, templateUrl); + + _enterHandler(RouteEnterEvent event, String templateUrl, [List modules]) => + locationService._route(event.route, templateUrl, fromEvent: true, modules: modules); + + configure(Map config) => + _configure(locationService.router.root, config); + + _configure(Route route, Map config) { + config.forEach((name, cfg) { + var moduledCalled = false; + List newModules; + route.addRoute( + name: name, + path: cfg.path, + defaultRoute: cfg.defaultRoute, + enter: (RouteEnterEvent e) { + if (cfg.view != null) { + _enterHandler(e, cfg.view, newModules); + } + if (cfg.enter != null) { + cfg.enter(e); + } + }, + preEnter: (RoutePreEnterEvent e) { + if (cfg.modules != null && !moduledCalled) { + moduledCalled = true; + var modules = cfg.modules(); + if (modules is Future) { + e.allowEnter(modules.then((List m) { + newModules = m; + return true; + })); + } else { + newModules = modules; + } + } + if (cfg.preEnter != null) { + cfg.preEnter(e); + } + }, + leave: cfg.leave, + mount: (Route mountRoute) { + if (cfg.mount != null) { + _configure(mountRoute, cfg.mount); + } + }); + }); + } +} + +NgRouteCfg ngRoute({String path, String view, Map mount, + modules(), bool defaultRoute: false, RoutePreEnterEventHandler preEnter, + RouteEnterEventHandler enter, RouteLeaveEventHandler leave}) => + new NgRouteCfg(path: path, view: view, mount: mount, modules: modules, + defaultRoute: defaultRoute, preEnter: preEnter, enter: enter, + leave: leave); + +class NgRouteCfg { + final String path; + final String view; + final Map mount; + final Function modules; + final bool defaultRoute; + final RouteEnterEventHandler enter; + final RoutePreEnterEventHandler preEnter; + final RouteLeaveEventHandler leave; + + NgRouteCfg({this.view, this.path, this.mount, this.modules, this.defaultRoute, + this.enter, this.preEnter, this.leave}); } /** @@ -19,11 +87,23 @@ class ViewFactory { * * The [init] method will be called by the framework once the router is * instantiated but before [NgBindRouteDirective] and [NgViewDirective]. + * + * Deprecated: use RouteInitializerFn instead. */ +@deprecated abstract class RouteInitializer { void init(Router router, ViewFactory viewFactory); } +/** + * An typedef that must be implemented by the user of routing library and + * should include the route initialization. + * + * The function will be called by the framework once the router is + * instantiated but before [NgBindRouteDirective] and [NgViewDirective]. + */ +typedef void RouteInitializerFn(Router router, ViewFactory viewFactory); + /** * A singleton helper service that handles routing initialization, global * events and view registries. @@ -33,15 +113,22 @@ class NgRoutingHelper { final Router router; final NgApp _ngApp; List portals = []; - Map _templates = new Map(); + Map _templates = new Map(); - NgRoutingHelper(RouteInitializer initializer, this.router, this._ngApp) { - if (initializer == null) { + NgRoutingHelper(RouteInitializer initializer, Injector injector, this.router, this._ngApp) { + // TODO: move this to constructor parameters when di issue is fixed: + // https://github.com/angular/di.dart/issues/40 + RouteInitializerFn initializerFn = injector.get(RouteInitializerFn); + if (initializer == null && initializerFn == null) { window.console.error('No RouteInitializer implementation provided.'); return; }; - initializer.init(router, new ViewFactory(this)); + if (initializerFn != null) { + initializerFn(router, new ViewFactory(this)); + } else { + initializer.init(router, new ViewFactory(this)); + } router.onRouteStart.listen((RouteStartEvent routeEvent) { routeEvent.completed.then((success) { if (success) { @@ -60,23 +147,24 @@ class NgRoutingHelper { activePath = activePath.skip(_routeDepth(startingFrom)); } for (Route route in activePath) { - var templateUrl = _templates[_routePath(route)]; - if (templateUrl == null) continue; + var viewDef = _templates[_routePath(route)]; + if (viewDef == null) continue; + var templateUrl = viewDef.template; NgViewDirective view = portals.lastWhere((NgViewDirective v) { return _routePath(route) != _routePath(v._route) && _routePath(route).startsWith(_routePath(v._route)); }, orElse: () => null); if (view != null && !alreadyActiveViews.contains(view)) { - view._show(templateUrl, route); + view._show(templateUrl, route, viewDef.modules); alreadyActiveViews.add(view); break; } } } - _route(Route route, String template, {bool fromEvent}) { - _templates[_routePath(route)] = template; + _route(Route route, String template, {bool fromEvent, List modules}) { + _templates[_routePath(route)] = new _View(template, modules); } _registerPortal(NgViewDirective ngView) { @@ -88,6 +176,13 @@ class NgRoutingHelper { } } +class _View { + final String template; + final List modules; + + _View(this.template, this.modules); +} + String _routePath(Route route) { var path = []; var p = route; diff --git a/test/core_dom/block_spec.dart b/test/core_dom/block_spec.dart index e5a08bcc7..c94fee0ed 100644 --- a/test/core_dom/block_spec.dart +++ b/test/core_dom/block_spec.dart @@ -2,6 +2,12 @@ library block_spec; import '../_specs.dart'; +class Log { + List log = []; + + add(String msg) => log.add(msg); +} + @NgDirective(children: NgAnnotation.TRANSCLUDE_CHILDREN, selector: 'foo') class LoggerBlockDirective { LoggerBlockDirective(BlockHole hole, BlockFactory blockFactory, @@ -16,24 +22,43 @@ class LoggerBlockDirective { } } -class ReplaceBlockDirective { - ReplaceBlockDirective(BlockHole hole, BoundBlockFactory boundBlockFactory, Node node, Scope scope) { - var block = boundBlockFactory(scope); - block.insertAfter(hole); - node.remove(); +@NgDirective(selector: 'dir-a') +class ADirective { + ADirective(Log log) { + log.add('ADirective'); + } +} + +@NgDirective(selector: 'dir-b') +class BDirective { + BDirective(Log log) { + log.add('BDirective'); + } +} + +@NgFilter(name:'filterA') +class AFilter { + Log log; + + AFilter(this.log) { + log.add('AFilter'); } + + call(value) => value; } -class ShadowBlockDirective { - ShadowBlockDirective(BlockHole hole, BoundBlockFactory boundBlockFactory, Element element, Scope scope) { - var block = boundBlockFactory(scope); - var shadowRoot = element.createShadowRoot(); - for (var i = 0, ii = block.elements.length; i < ii; i++) { - shadowRoot.append(block.elements[i]); - } +@NgFilter(name:'filterB') +class BFilter { + Log log; + + BFilter(this.log) { + log.add('BFilter'); } + + call(value) => value; } + main() { describe('Block', () { var anchor; @@ -201,6 +226,44 @@ main() { }); }); + describe('deferred', () { + + it('should load directives/filters from the child injector', () { + Module rootModule = new Module() + ..type(Probe) + ..type(Log) + ..type(AFilter) + ..type(ADirective); + + Injector rootInjector = + new DynamicInjector(modules: [new AngularModule(), rootModule]); + Log log = rootInjector.get(Log); + Scope rootScope = rootInjector.get(Scope); + + Compiler compiler = rootInjector.get(Compiler); + DirectiveMap directives = rootInjector.get(DirectiveMap); + compiler(es('{{\'a\' | filterA}}'), directives)(rootInjector); + rootScope.$digest(); + + expect(log.log, equals(['ADirective', 'AFilter'])); + + + Module childModule = new Module() + ..type(BFilter) + ..type(BDirective); + + var childInjector = forceNewDirectivesAndFilters(rootInjector, [childModule]); + + DirectiveMap newDirectives = childInjector.get(DirectiveMap); + compiler(es('{{\'a\' | filterA}}' + '{{\'b\' | filterB}}'), newDirectives)(childInjector); + rootScope.$digest(); + + expect(log.log, equals(['ADirective', 'AFilter', 'ADirective', 'BDirective', 'BFilter'])); + }); + + }); + //TODO: tests for attach/detach //TODO: animation/transitions //TODO: tests for re-usability of blocks diff --git a/test/routing/routing_spec.dart b/test/routing/routing_spec.dart index a01833407..be2b6aba9 100644 --- a/test/routing/routing_spec.dart +++ b/test/routing/routing_spec.dart @@ -1,8 +1,8 @@ library routing_spec; import '../_specs.dart'; -import 'package:angular/routing/module.dart'; import 'package:angular/mock/module.dart'; +import 'dart:async'; main() { describe('routing', () { @@ -35,6 +35,322 @@ main() { })); }); + + describe('routing DSL', () { + Router router; + TestBed _; + + afterEach(() { + router = _ = null; + }); + + initRouter(initializer) { + var module = new Module() + ..value(RouteInitializerFn, initializer); + var injector = new DynamicInjector( + modules: [new AngularModule(), new AngularMockModule(), module]); + injector.get(NgRoutingHelper); // force routing initialization + router = injector.get(Router); + _ = injector.get(TestBed); + } + + it('should configure route hierarchy from provided config', async(() { + var counters = { + 'foo': 0, + 'bar': 0, + 'baz': 0, + 'aux': 0, + }; + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + enter: (_) => counters['foo']++, + mount: { + 'bar': ngRoute( + path: '/bar', + enter: (_) => counters['bar']++ + ), + 'baz': ngRoute( + path: '/baz', + enter: (_) => counters['baz']++ + ) + } + ), + 'aux': ngRoute( + path: '/aux', + enter: (_) => counters['aux']++ + ) + }); + }); + + expect(router.root.getRoute('foo').name).toEqual('foo'); + expect(router.root.getRoute('foo.bar').name).toEqual('bar'); + expect(router.root.getRoute('foo.baz').name).toEqual('baz'); + expect(router.root.getRoute('aux').name).toEqual('aux'); + + router.route('/foo'); + microLeap(); + expect(counters, equals({ + 'foo': 1, + 'bar': 0, + 'baz': 0, + 'aux': 0, + })); + + router.route('/foo/bar'); + microLeap(); + expect(counters, equals({ + 'foo': 1, + 'bar': 1, + 'baz': 0, + 'aux': 0, + })); + + router.route('/foo/baz'); + microLeap(); + expect(counters, equals({ + 'foo': 1, + 'bar': 1, + 'baz': 1, + 'aux': 0, + })); + + router.route('/aux'); + microLeap(); + expect(counters, equals({ + 'foo': 1, + 'bar': 1, + 'baz': 1, + 'aux': 1, + })); + })); + + + it('should set the default route', async(() { + int enterCount = 0; + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute(path: '/foo'), + 'bar': ngRoute(path: '/bar', defaultRoute: true), + 'baz': ngRoute(path: '/baz'), + }); + }); + + router.route('/invalidRoute'); + microLeap(); + + expect(router.activePath.length).toBe(1); + expect(router.activePath.first.name).toBe('bar'); + })); + + + it('should call enter callback and show the view when routed', async(() { + int enterCount = 0; + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + enter: (_) => enterCount++, + view: 'foo.html' + ), + }); + }); + _.injector.get(TemplateCache) + .put('foo.html', new HttpResponse(200, '

Foo

')); + + Element root = _.compile(''); + expect(root.text).toEqual(''); + + router.route('/foo'); + microLeap(); + + expect(enterCount).toBe(1); + expect(root.text).toEqual('Foo'); + })); + + + it('should call preEnter callback and load modules', async(() { + int preEnterCount = 0; + int modulesCount = 0; + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + preEnter: (_) => preEnterCount++, + modules: () { + modulesCount++; + return new Future.value(); + } + ), + 'bar': ngRoute( + path: '/bar' + ) + }); + }); + + router.route('/foo'); + microLeap(); + + expect(preEnterCount).toBe(1); + expect(modulesCount).toBe(1); + + router.route('/foo'); + microLeap(); + + expect(preEnterCount).toBe(1); + expect(modulesCount).toBe(1); + + router.route('/bar'); + microLeap(); + + expect(preEnterCount).toBe(1); + expect(modulesCount).toBe(1); + + router.route('/foo'); + microLeap(); + + expect(preEnterCount).toBe(2); + expect(modulesCount).toBe(1); + })); + + + it('should clear view on leave an call leave callback', async(() { + int leaveCount = 0; + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + leave: (_) => leaveCount++, + view: 'foo.html' + ), + 'bar': ngRoute( + path: '/bar' + ), + }); + }); + _.injector.get(TemplateCache) + .put('foo.html', new HttpResponse(200, '

Foo

')); + + Element root = _.compile(''); + expect(root.text).toEqual(''); + + router.route('/foo'); + microLeap(); + + expect(root.text).toEqual('Foo'); + expect(leaveCount).toBe(0); + + router.route('/bar'); + microLeap(); + + expect(root.text).toEqual(''); + expect(leaveCount).toBe(1); + })); + + + it('should synchronously load new directives from modules ', async(() { + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + modules: () => [ + new Module()..type(NewDirective) + ], + view: 'foo.html' + ), + }); + }); + _.injector.get(TemplateCache) + .put('foo.html', new HttpResponse(200, '
Old!
')); + + Element root = _.compile(''); + expect(root.text).toEqual(''); + + router.route('/foo'); + microLeap(); + + expect(root.text).toEqual('New!'); + })); + + + it('should asynchronously load new directives from modules ', async(() { + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + modules: () => new Future.value([ + new Module()..type(NewDirective) + ]), + view: 'foo.html' + ), + }); + }); + _.injector.get(TemplateCache) + .put('foo.html', new HttpResponse(200, '
Old!
')); + + Element root = _.compile(''); + expect(root.text).toEqual(''); + + router.route('/foo'); + microLeap(); + + expect(root.text).toEqual('New!'); + })); + + + it('should synchronously load new filters from modules ', async(() { + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + modules: () => [ + new Module()..type(HelloFilter) + ], + view: 'foo.html' + ), + }); + }); + _.injector.get(TemplateCache) + .put('foo.html', new HttpResponse(200, '
{{\'World\' | hello}}
')); + + Element root = _.compile(''); + expect(root.text).toEqual(''); + + router.route('/foo'); + microLeap(); + _.rootScope.$digest(); + + expect(root.text).toEqual('Hello, World!'); + })); + + + it('should asynchronously load new filters from modules ', async(() { + initRouter((Router router, ViewFactory views) { + views.configure({ + 'foo': ngRoute( + path: '/foo', + modules: () => new Future.value([ + new Module()..type(HelloFilter) + ]), + view: 'foo.html' + ), + }); + }); + _.injector.get(TemplateCache) + .put('foo.html', new HttpResponse(200, '
{{\'World\' | hello}}
')); + + Element root = _.compile(''); + expect(root.text).toEqual(''); + + router.route('/foo'); + microLeap(); + _.rootScope.$digest(); + + expect(root.text).toEqual('Hello, World!'); + })); + + }); } class TestRouteInitializer implements RouteInitializer { @@ -46,3 +362,19 @@ class TestRouteInitializer implements RouteInitializer { this.router = router; } } + + +@NgDirective(selector: '[make-it-new]') +class NewDirective { + NewDirective(Element element) { + element.innerHtml = 'New!'; + } +} + +@NgFilter(name:'hello') +class HelloFilter { + call(String str) { + return 'Hello, $str!'; + } +} +