diff --git a/benchmark/web/tree.dart b/benchmark/web/tree.dart index 332f94141..bdca3cc2c 100644 --- a/benchmark/web/tree.dart +++ b/benchmark/web/tree.dart @@ -5,17 +5,14 @@ import 'package:angular/application_factory.dart'; import 'package:angular/change_detection/ast_parser.dart'; import 'dart:html'; -import 'dart:math'; import 'dart:js' as js; @Component( selector: 'tree', template: ' {{ctrl.data.value}}' - '' - '' - '', - publishAs: 'ctrl', - useShadowDom: true) + '' + '' + '') class TreeComponent { @NgOneWay('data') var data; @@ -34,9 +31,7 @@ class TranscludingTreeComponent extends TreeComponent {} @Component( selector: 'tree-url', - templateUrl: 'tree-tmpl.html', - publishAs: 'ctrl', - useShadowDom: true) + templateUrl: 'tree-tmpl.html') class TreeUrlComponent { @NgOneWay('data') var data; @@ -209,19 +204,19 @@ class FreeTreeClass { s.text = " $v"; } }); - + scope.watchAST(treeRightNotNullAST, (v, _) { if (v != true) return; s.append(new SpanElement() ..append(new FreeTreeClass(scope, treeRightAST).element())); }); - + scope.watchAST(treeLeftNotNullAST, (v, _) { if (v != true) return; s.append(new SpanElement() ..append(new FreeTreeClass(scope, treeLeftAST).element())); }); - + return elt; } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 8379b928e..fdb78573e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -18,7 +18,7 @@ packages: barback: description: barback source: hosted - version: "0.14.0+3" + version: "0.14.2" browser: description: browser source: hosted @@ -59,10 +59,10 @@ packages: description: perf_api source: hosted version: "0.0.9" - quiver: - description: quiver + pool: + description: pool source: hosted - version: "0.18.2" + version: "1.0.1" route_hierarchical: description: route_hierarchical source: hosted @@ -78,7 +78,7 @@ packages: stack_trace: description: stack_trace source: hosted - version: "0.9.3+2" + version: "1.0.2" typed_mock: description: typed_mock source: hosted diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 992598a6b..bcd0043ea 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,6 @@ dependencies: path: ../ browser: any unittest: any - quiver: any web_components: any transformers: diff --git a/example/web/animation.dart b/example/web/animation.dart index 15c4fc3a6..01a2fa1f6 100644 --- a/example/web/animation.dart +++ b/example/web/animation.dart @@ -3,7 +3,6 @@ library animation; import 'package:angular/angular.dart'; import 'package:angular/application_factory.dart'; import 'package:angular/animate/module.dart'; -import 'package:quiver/collection.dart'; part 'animation/repeat_demo.dart'; part 'animation/visibility_demo.dart'; @@ -16,16 +15,6 @@ class AnimationDemo { var currentPage = "About"; } -// Temporary workaround, because context needs to extend Map. -@Injectable() -class AnimationDemoHashMap extends DelegatingMap { - final Map _delegate; - AnimationDemoHashMap(AnimationDemo demo) : _delegate = new Map() { - _delegate['demo'] = demo; - } - Map get delegate => _delegate; -} - class AnimationDemoModule extends Module { AnimationDemoModule() { install(new AnimationModule()); @@ -33,12 +22,11 @@ class AnimationDemoModule extends Module { bind(VisibilityDemo); bind(StressDemo); bind(CssDemo); - bind(AnimationDemo); } } main() { applicationFactory() .addModule(new AnimationDemoModule()) - .rootContextType(AnimationDemoHashMap) + .rootContextType(AnimationDemo) .run(); } diff --git a/example/web/animation.html b/example/web/animation.html index 6328ae2c1..5373d98c4 100644 --- a/example/web/animation.html +++ b/example/web/animation.html @@ -6,13 +6,13 @@ -
+

About

diff --git a/example/web/animation/css_demo.dart b/example/web/animation/css_demo.dart index a09018595..5312556f3 100644 --- a/example/web/animation/css_demo.dart +++ b/example/web/animation/css_demo.dart @@ -4,25 +4,24 @@ part of animation; selector: 'css-demo', template: '''
- - -
BOX
+ 'a': stateA, + 'b': stateB, + 'c': stateC}">BOX
''', - publishAs: 'ctrl', applyAuthorStyles: true) class CssDemo { bool stateA = false; diff --git a/example/web/animation/repeat_demo.dart b/example/web/animation/repeat_demo.dart index 6c0f9b446..8fb989377 100644 --- a/example/web/animation/repeat_demo.dart +++ b/example/web/animation/repeat_demo.dart @@ -5,19 +5,17 @@ part of animation; useShadowDom: false, template: '''
- - + +
''', - publishAs: 'ctrl', applyAuthorStyles: true) class RepeatDemo { var thing = 0; diff --git a/example/web/animation/stress_demo.dart b/example/web/animation/stress_demo.dart index 2f9b9a488..19938d5ff 100644 --- a/example/web/animation/stress_demo.dart +++ b/example/web/animation/stress_demo.dart @@ -4,14 +4,13 @@ part of animation; selector: 'stress-demo', template: '''
-
-
+
''', - publishAs: 'ctrl', applyAuthorStyles: true) class StressDemo { bool _visible = true; diff --git a/example/web/animation/visibility_demo.dart b/example/web/animation/visibility_demo.dart index 2d1a7c688..1197cf0e2 100644 --- a/example/web/animation/visibility_demo.dart +++ b/example/web/animation/visibility_demo.dart @@ -4,19 +4,18 @@ part of animation; selector: 'visibility-demo', template: '''
- -
+ +

Hello World. ng-if will create and destroy dom elements each time you toggle me.

-
+

Hello World. ng-hide will add and remove the .ng-hide class from me to show and hide this view of text.

''', - publishAs: 'ctrl', useShadowDom: false) class VisibilityDemo { bool visible = false; diff --git a/example/web/bouncing_balls.dart b/example/web/bouncing_balls.dart index fc1057462..8316b8869 100644 --- a/example/web/bouncing_balls.dart +++ b/example/web/bouncing_balls.dart @@ -26,15 +26,9 @@ class BallModel { } } - -@Component( - selector: 'bounce-controller', - publishAs: 'ctrl', - templateUrl: 'bouncing_controller.html', - cssUrl: 'bouncing_controller.css' -) -class BounceController { - RootScope rootScope; +@Injectable() +class BounceController implements ScopeAware { + Scope scope; var lastTime = window.performance.now(); var run = false; var fps = 0; @@ -43,9 +37,8 @@ class BounceController { var balls = []; var ballClassName = 'ball'; - BounceController(this.rootScope) { + BounceController() { changeCount(100); - if (run) tick(); } void toggleCSS() { @@ -76,7 +69,7 @@ class BounceController { void timeDigest() { var start = window.performance.now(); digestTime = currentDigestTime; - rootScope.domRead(() { + scope.rootScope.domRead(() { currentDigestTime = window.performance.now() - start; }); } @@ -135,6 +128,7 @@ class MyModule extends Module { main() { applicationFactory() + .rootContextType(BounceController) .addModule(new MyModule()) .run(); } diff --git a/example/web/bouncing_balls.html b/example/web/bouncing_balls.html index def40285a..f90e398fb 100644 --- a/example/web/bouncing_balls.html +++ b/example/web/bouncing_balls.html @@ -2,9 +2,68 @@ Bouncing balls + - +
+
+
+
+ +
+
+
+
+
+ + {{ fps }} fps. ({{ balls.length }} balls) [{{ 1000 / fps }} ms]
+ Digest: {{ digestTime }} ms
+ +1 + +10 + +100 +
+ -1 + -10 + -100 +
+ ▶❙❙
+ Toggle CSS
+ noop
+
diff --git a/example/web/form.dart b/example/web/form.dart index 4d96eb035..d290c1bb0 100644 --- a/example/web/form.dart +++ b/example/web/form.dart @@ -25,15 +25,20 @@ class FormCtrl { '2560x1440', '2560x1600']; - final Scope scope; - final NgForm form; + Scope scope; final List colors = []; final List formattedColors = []; + NgForm myForm; + NgForm colorForm; + NgForm colorsForm; + Map info; + PreviewCtrl preview; - FormCtrl(this.scope, this.form) { + FormCtrl() { newColor(_COLOR_HEX, '#222'); newColor(_COLOR_HEX, '#444'); newColor(_COLOR_HEX, '#000'); + info = new Map(); } List get colorTypes => _COLOR_TYPES; @@ -41,7 +46,7 @@ class FormCtrl { List get resolutions => _RESOLUTIONS; void submit() { - form.reset(); + myForm.reset(); } int getTotalSquares(inputValue) { @@ -110,8 +115,8 @@ class FormCtrl { selector: '[preview-controller]' ) class PreviewCtrl { - PreviewCtrl(Scope scope) { - scope.context['preview'] = this; + PreviewCtrl(FormCtrl form) { + form.preview = this; } List _collection = []; diff --git a/example/web/form_controller.dart b/example/web/form_controller.dart deleted file mode 100644 index 6d747aa9f..000000000 --- a/example/web/form_controller.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:angular/angular.dart'; - -@Component( - selector: '[form-controller]', - templateUrl: 'form_controller.html', - publishAs: 'form_ctrl', - useShadowDom: false) -class FormCtrl { - static const String _COLOR_HEX = "hex"; - static const String _COLOR_HSL = "hsl"; - static const String _COLOR_RGB = "rgb"; - static const String _COLOR_NAME = "name"; - - static const _COLOR_TYPES = const [_COLOR_RGB, _COLOR_HSL, _COLOR_HEX, _COLOR_NAME]; - - static const _RESOLUTIONS = const ['1024x600', - '1280x800', - '1366x768', - '1440x900', - '1600x900', - '1680x1050', - '1920x1080', - '1920x1200', - '2560x1440', - '2560x1600']; - - final Scope scope; - final NgForm form; - final List colors = []; - final List formattedColors = []; - - FormCtrl(this.scope, this.form) { - newColor(_COLOR_HEX, '#222'); - newColor(_COLOR_HEX, '#444'); - newColor(_COLOR_HEX, '#000'); - } - - List get colorTypes => _COLOR_TYPES; - - List get resolutions => _RESOLUTIONS; - - void submit() { - form.reset(); - } - - int getTotalSquaresFromInput() => getTotalSquares(scope.context['squares']); - - int getTotalSquares(inputValue) { - var value = 4; - if(inputValue != null) { - try { - value = double.parse(inputValue.toString()); - } catch(e) { - } - } - return (value * value).toInt(); - } - - List formatColors() { - formattedColors.clear(); - colors.forEach((color) { - var value = null; - switch(color['type']) { - case _COLOR_HEX: - value = color['hex']; - break; - case _COLOR_HSL: - var hue = color['hue']; - var saturation = color['saturation']; - var luminance = color['luminance']; - if(hue != null && saturation != null && luminance != null) { - value = "hsl($hue, $saturation%, $luminance%)"; - } - break; - case _COLOR_RGB: - var red = color['red']; - var blue = color['blue']; - var green = color['green']; - if(red != null && green != null && blue != null) { - value = "rgb($red, $green, $blue)"; - } - break; - default: //COLOR_NAME - value = color['name']; - break; - } - if(value != null) { - formattedColors.add(value); - } - }); - return formattedColors; - } - - void newColor([String type = _COLOR_HEX, String color]) { - colors.add({ - 'id' : colors.length, - 'type' : type, - 'hex' : type == _COLOR_HEX ? color : '', - 'hue' : '', - 'saturation' : '', - 'luminance' : '', - 'red' : '', - 'green' : '', - 'blue': '', - 'name': '' - }); - } -} - -@Decorator( - selector: '[preview-controller]' -) -class PreviewCtrl { - PreviewCtrl(Scope scope) { - scope.context['preview'] = this; - } - - List _collection = []; - - List expandList(items, limit) { - _collection.clear(); - if(items != null && items.length > 0) { - for (var i = 0; i < limit; i++) { - var x = i % items.length; - _collection.add(items[x]); - } - } - return _collection; - } -} - diff --git a/example/web/form_controller.html b/example/web/form_controller.html index ce5eb0fbd..5f99d60d4 100644 --- a/example/web/form_controller.html +++ b/example/web/form_controller.html @@ -1,8 +1,8 @@
+ ng-submit="submit()" + ng-class="{submitted:myForm.submitted}">
@@ -10,7 +10,7 @@

Wallpaper Resolution Size

@@ -46,13 +46,13 @@

Account Details

Colors

- New Color + New Color -
+
@@ -89,10 +89,10 @@

Colors

-
+
+ ng-repeat="color in preview.expandList(formatColors(), getTotalSquares(info.squares))">
diff --git a/example/web/shadow_dom_components.dart b/example/web/shadow_dom_components.dart index 0f840f87a..16b016f16 100644 --- a/example/web/shadow_dom_components.dart +++ b/example/web/shadow_dom_components.dart @@ -12,16 +12,15 @@ main() { @Component( selector: "my-component", - publishAs: "ctrl", template: """ -
+
Shadow [ ] - + Toggle - off - on + off + on
""", cssUrl: "/css/shadow_dom_components.css") diff --git a/example/web/todo.dart b/example/web/todo.dart index 4135dfa51..379372356 100644 --- a/example/web/todo.dart +++ b/example/web/todo.dart @@ -6,8 +6,6 @@ import 'package:angular/angular.dart'; import 'package:angular/application_factory.dart'; import 'package:angular/playback/playback_http.dart'; -import 'package:quiver/collection.dart'; - class Item { String text; bool done; @@ -92,19 +90,9 @@ class Todo { } -// Temporary workaround, because context needs to extend Map. -@Injectable() -class TodoHashMap extends DelegatingMap { - final Map _delegate; - TodoHashMap(Todo todo) : _delegate = new Map() { - _delegate['todo'] = todo; - } - Map get delegate => _delegate; -} - main() { print(window.location.search); - var module = new Module()..bind(PlaybackHttpBackendConfig)..bind(Todo); + var module = new Module()..bind(PlaybackHttpBackendConfig); // If these is a query in the URL, use the server-backed // TodoController. Otherwise, use the stored-data controller. @@ -124,7 +112,7 @@ main() { } applicationFactory() - .rootContextType(TodoHashMap) .addModule(module) + .rootContextType(Todo) .run(); } diff --git a/example/web/todo.html b/example/web/todo.html index abb0f7474..2dcb69f4a 100644 --- a/example/web/todo.html +++ b/example/web/todo.html @@ -15,15 +15,15 @@

Things To Do ;-)

- - + +
-

Remaining {{todo.remaining()}} of {{todo.items.length}} items.

+

Remaining {{ remaining() }} of {{ items.length }} items.

    -
  • +
  • @@ -31,9 +31,9 @@

    Things To Do ;-)

- - - + + +
diff --git a/lib/change_detection/context_locals.dart b/lib/change_detection/context_locals.dart new file mode 100644 index 000000000..05098d68a --- /dev/null +++ b/lib/change_detection/context_locals.dart @@ -0,0 +1,24 @@ +part of angular.watch_group; + +class ContextLocals { + final Map _locals = {}; + + final Object _parentContext; + Object get parentContext => _parentContext; + + ContextLocals(this._parentContext, [Map locals = null]) { + assert(_parentContext != null); + if (locals != null) _locals.addAll(locals); + } + + static ContextLocals wrapper(context, Map locals) => + new ContextLocals(context, locals); + + bool hasProperty(String prop) => _locals.containsKey(prop); + + void operator[]=(String prop, value) { + _locals[prop] = value; + } + + dynamic operator[](String prop) => _locals[prop]; +} diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart index 0fec28340..c48871ee0 100644 --- a/lib/change_detection/dirty_checking_change_detector.dart +++ b/lib/change_detection/dirty_checking_change_detector.dart @@ -2,6 +2,7 @@ library dirty_checking_change_detector; import 'dart:collection'; import 'package:angular/change_detection/change_detection.dart'; +import 'package:angular/change_detection/watch_group.dart'; /** * [DirtyCheckingChangeDetector] determines which object properties have changed @@ -369,7 +370,7 @@ class _ChangeIterator implements Iterator>{ * removing efficient. [DirtyCheckingRecord] also has a [nextChange] field which * creates a single linked list of all of the changes for efficient traversal. */ -class DirtyCheckingRecord implements Record, WatchRecord { +class DirtyCheckingRecord implements WatchRecord { static const List _MODE_NAMES = const [ 'MARKER', 'NOOP', @@ -423,9 +424,10 @@ class DirtyCheckingRecord implements Record, WatchRecord { * [DirtyCheckingRecord] into different access modes. If Object it sets up * reflection. If [Map] then it sets up map accessor. */ - void set object(obj) { - _object = obj; - if (obj == null) { + void set object(Object object) { + _object = object; + + if (object == null) { _mode = _MODE_IDENTITY_; _getter = null; return; @@ -433,7 +435,8 @@ class DirtyCheckingRecord implements Record, WatchRecord { if (field == null) { _getter = null; - if (obj is Map) { + + if (object is Map) { if (_mode != _MODE_MAP_) { _mode = _MODE_MAP_; currentValue = new _MapChangeRecord(); @@ -445,8 +448,7 @@ class DirtyCheckingRecord implements Record, WatchRecord { // new reference. currentValue._revertToPreviousState(); } - - } else if (obj is Iterable) { + } else if (object is Iterable) { if (_mode != _MODE_ITERABLE_) { _mode = _MODE_ITERABLE_; currentValue = new _CollectionChangeRecord(); @@ -465,12 +467,21 @@ class DirtyCheckingRecord implements Record, WatchRecord { return; } - if (obj is Map) { + if (object is Map) { _mode = _MODE_MAP_FIELD_; _getter = null; } else { + while (object is ContextLocals) { + var ctx = object as ContextLocals; + if (ctx.hasProperty(field)) { + _mode = _MODE_MAP_FIELD_; + _getter = null; + return; + } + _object = object = ctx.parentContext; + } _mode = _MODE_GETTER_OR_METHOD_CLOSURE_; - _getter = _fieldGetterFactory.getter(obj, field); + _getter = _fieldGetterFactory.getter(object, field); } } diff --git a/lib/change_detection/watch_group.dart b/lib/change_detection/watch_group.dart index 456541432..2a2f85237 100644 --- a/lib/change_detection/watch_group.dart +++ b/lib/change_detection/watch_group.dart @@ -6,7 +6,7 @@ import 'package:angular/ng_tracing.dart'; part 'linked_list.dart'; part 'ast.dart'; -part 'prototype_map.dart'; +part 'context_locals.dart'; /** * A function that is notified of changes to the model. @@ -784,24 +784,31 @@ class _EvalWatchRecord implements WatchRecord<_Handler> { get object => _object; - set object(value) { + void set object(object) { assert(mode != _MODE_DELETED_); assert(mode != _MODE_MARKER_); assert(mode != _MODE_FUNCTION_); assert(mode != _MODE_PURE_FUNCTION_); assert(mode != _MODE_PURE_FUNCTION_APPLY_); - _object = value; + _object = object; - if (value == null) { + if (object == null) { mode = _MODE_NULL_; + } else if (object is Map) { + mode = _MODE_MAP_CLOSURE_; } else { - if (value is Map) { - mode = _MODE_MAP_CLOSURE_; - } else { - mode = _MODE_FIELD_OR_METHOD_CLOSURE_; - fn = _fieldGetterFactory.getter(value, name); + while (object is ContextLocals) { + var ctx = object as ContextLocals; + if (ctx.hasProperty(name)) { + mode = _MODE_MAP_CLOSURE_; + return; + } + _object = object = ctx.parentContext; } + mode = _MODE_FIELD_OR_METHOD_CLOSURE_; + fn = _fieldGetterFactory.getter(object, name); } + } bool check() { diff --git a/lib/core/annotation_src.dart b/lib/core/annotation_src.dart index bbddf568a..7741784a6 100644 --- a/lib/core/annotation_src.dart +++ b/lib/core/annotation_src.dart @@ -238,6 +238,30 @@ bool _resetStyleInheritanceDeprecationWarningPrinted = false; * [ShadowRoot](https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart-dom-html.ShadowRoot) is loaded. */ class Component extends Directive { + /** + * This property is left here for backward compatibility + * + * Before: + * + * @Component(publishAs: 'ctrl', ...) + * class MyComponent { + * // ... + * } + * + * After: + * + * @Component(publishAs: 'ctrl', ...) + * class MyComponent { + * // You must add a getter named after the publishAs configuration + * MyComponent get ctrl => this; + * + * // ... + * } + */ + @Deprecated('next release. This property is left for backward compatibility but setting it has no' + 'effect') + final String publishAs; + /** * Inlined HTML template for the component. */ @@ -282,13 +306,6 @@ class Component extends Directive { } final bool _resetStyleInheritance; - /** - * An expression under which the component's controller instance will be - * published into. This allows the expressions in the template to be referring - * to controller instance and its properties. - */ - final String publishAs; - /** * If set to true, this component will always use shadow DOM. * If set to false, this component will never use shadow DOM. @@ -307,7 +324,6 @@ class Component extends Directive { cssUrl, applyAuthorStyles, resetStyleInheritance, - this.publishAs, DirectiveBinderFn module, map, selector, @@ -315,7 +331,8 @@ class Component extends Directive { exportExpressions, exportExpressionAttrs, this.useShadowDom, - this.useNgBaseCss: true}) + this.useNgBaseCss: true, + this.publishAs}) : _cssUrls = cssUrl, _applyAuthorStyles = applyAuthorStyles, _resetStyleInheritance = resetStyleInheritance, @@ -338,7 +355,6 @@ class Component extends Directive { cssUrl: cssUrls, applyAuthorStyles: applyAuthorStyles, resetStyleInheritance: resetStyleInheritance, - publishAs: publishAs, map: newMap, module: module, selector: selector, @@ -346,7 +362,8 @@ class Component extends Directive { exportExpressions: exportExpressions, exportExpressionAttrs: exportExpressionAttrs, useShadowDom: useShadowDom, - useNgBaseCss: useNgBaseCss); + useNgBaseCss: useNgBaseCss, + publishAs: publishAs); } /** diff --git a/lib/core/module.dart b/lib/core/module.dart index 329696d28..3f8345bcd 100644 --- a/lib/core/module.dart +++ b/lib/core/module.dart @@ -70,8 +70,8 @@ export "package:angular/core/module_internal.dart" show Interpolate, VmTurnZone, WebPlatform, - PrototypeMap, RootScope, + ContextLocals, Scope, ScopeAware, ScopeDigestTTL, diff --git a/lib/core/parser/eval_access.dart b/lib/core/parser/eval_access.dart index 566a69834..966fcf3e1 100644 --- a/lib/core/parser/eval_access.dart +++ b/lib/core/parser/eval_access.dart @@ -38,22 +38,26 @@ class AccessKeyed extends syntax.AccessKeyed { * where we have a pair of pre-compiled getter and setter functions that we * use to do the access the field. */ +// todo(vicb) - parser should not depend on ContextLocals +// todo(vicb) - Map should not be a special case so that we can access the props abstract class AccessFast { String get name; Getter get getter; Setter get setter; - _eval(holder) { + dynamic _eval(holder) { if (holder == null) return null; - return (holder is Map) ? holder[name] : getter(holder); + if (holder is Map) return holder[name]; + return getter(holder); } - _assign(scope, holder, value) { + dynamic _assign(scope, holder, value) { if (holder == null) { _assignToNonExisting(scope, value); return value; } else { - return (holder is Map) ? (holder[name] = value) : setter(holder, value); + if (holder is Map) return holder[name] = value; + return setter(holder, value); } } diff --git a/lib/core/parser/parser.dart b/lib/core/parser/parser.dart index 3b32b3dbe..e86a503cd 100644 --- a/lib/core/parser/parser.dart +++ b/lib/core/parser/parser.dart @@ -7,7 +7,11 @@ import 'package:angular/core/parser/eval.dart'; import 'package:angular/core/parser/utils.dart' show EvalError; import 'package:angular/cache/module.dart'; import 'package:angular/core/annotation_src.dart' hide Formatter; -import 'package:angular/core/module_internal.dart' show FormatterMap; +import 'package:angular/core/module_internal.dart' show + FormatterMap, + ContextLocals; + +import 'package:angular/core/parser/parser.dart'; import 'package:angular/core/parser/lexer.dart'; import 'package:angular/core/parser/parse_expression.dart'; import 'package:angular/utils.dart'; @@ -128,7 +132,7 @@ class _UnwrapExceptionDecorator extends Expression { @Injectable() class RuntimeParserBackend extends ParserBackend { final ClosureMap _closures; - RuntimeParserBackend(this._closures); + RuntimeParserBackend(ClosureMap _closures): _closures = new ClosureMapLocalsAware(_closures); bool isAssignable(Expression expression) => expression.isAssignable; @@ -197,3 +201,61 @@ class RuntimeParserBackend extends ParserBackend { } } +// todo(vicb) Would probably be better to remove this from the parser +class ClosureMapLocalsAware implements ClosureMap { + final ClosureMap wrappedClsMap; + + ClosureMapLocalsAware(this.wrappedClsMap); + + Getter lookupGetter(String name) { + return (o) { + while (o is ContextLocals) { + var ctx = o as ContextLocals; + if (ctx.hasProperty(name)) return ctx[name]; + o = ctx.parentContext; + } + var getter = wrappedClsMap.lookupGetter(name); + return getter(o); + }; + } + + Setter lookupSetter(String name) { + return (o, value) { + while (o is ContextLocals) { + var ctx = o as ContextLocals; + if (ctx.hasProperty(name)) return ctx[name] = value; + o = ctx.parentContext; + } + var setter = wrappedClsMap.lookupSetter(name); + return setter(o, value); + }; + } + + MethodClosure lookupFunction(String name, CallArguments arguments) { + return (o, pArgs, nArgs) { + while (o is ContextLocals) { + var ctx = o as ContextLocals; + if (ctx.hasProperty(name)) { + var fn = ctx[name]; + if (fn is Function) { + var snArgs = {}; + nArgs.forEach((name, value) { + var symbol = wrappedClsMap.lookupGetter(name); + snArgs[symbol] = value; + }); + return Function.apply(fn, pArgs, snArgs); + } else { + throw "Property '$name' is not of type function."; + } + } + o = ctx.parentContext; + } + var fn = wrappedClsMap.lookupFunction(name, arguments); + return fn(o, pArgs, nArgs); + }; + } + + Symbol lookupSymbol(String name) => wrappedClsMap.lookupSymbol(name); +} + + diff --git a/lib/core/parser/utils.dart b/lib/core/parser/utils.dart index 0da2df0e1..cf3c9a42d 100644 --- a/lib/core/parser/utils.dart +++ b/lib/core/parser/utils.dart @@ -2,6 +2,7 @@ library angular.core.parser.utils; import 'package:angular/core/parser/syntax.dart' show Expression; import 'package:angular/core/formatter.dart' show FormatterMap; +import 'package:angular/core/module_internal.dart' show ContextLocals; export 'package:angular/utils.dart' show relaxFnApply, relaxFnArgs, toBool; /// Marker for an uninitialized value. @@ -80,6 +81,11 @@ getKeyed(object, key) { } else if (object == null) { throw new EvalError('Accessing null object'); } else { + while (object is ContextLocals) { + var ctx = object as ContextLocals; + if (ctx.hasProperty(key)) break; + object = ctx.parentContext; + } return object[key]; } } @@ -93,6 +99,11 @@ setKeyed(object, key, value) { } else if (object is Map) { object["$key"] = value; // toString dangerous? } else { + while (object is ContextLocals) { + var ctx = object as ContextLocals; + if (ctx.hasProperty(key)) break; + object = ctx.parentContext; + } object[key] = value; } return value; diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 8a58fadab..29e086ba6 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -69,46 +69,6 @@ class ScopeDigestTTL { ScopeDigestTTL.value(this.ttl); } -//TODO(misko): I don't think this should be in scope. -class ScopeLocals implements Map { - static wrapper(scope, Map locals) => - new ScopeLocals(scope, locals); - - Map _scope; - Map _locals; - - ScopeLocals(this._scope, this._locals); - - void operator []=(String name, value) { - _scope[name] = value; - } - dynamic operator [](String name) { - // Map needed to clear Dart2js warning - Map map = _locals.containsKey(name) ? _locals : _scope; - return map[name]; - } - - bool get isEmpty => _scope.isEmpty && _locals.isEmpty; - bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty; - List get keys => _scope.keys; - List get values => _scope.values; - int get length => _scope.length; - - void forEach(fn) { - _scope.forEach(fn); - } - dynamic remove(key) => _scope.remove(key); - void clear() { - _scope.clear; - } - bool containsKey(key) => _scope.containsKey(key); - bool containsValue(key) => _scope.containsValue(key); - void addAll(map) { - _scope.addAll(map); - } - dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn); -} - /** * When a [Component] or the root context class implements [ScopeAware] the scope setter will be * called to set the [Scope] on this component. @@ -150,7 +110,7 @@ class Scope { int _childScopeNextId = 0; /// The default execution context for [watch]es [observe]ers, and [eval]uation. - final context; + final dynamic context; /// The [RootScope] of the application. final RootScope rootScope; @@ -183,8 +143,8 @@ class Scope { // TODO(misko): WatchGroup should be private. // Instead we should expose performance stats about the watches // such as # of watches, checks/1ms, field checks, function checks, etc - final WatchGroup _readWriteGroup; - final WatchGroup _readOnlyGroup; + WatchGroup _readWriteGroup; + WatchGroup _readOnlyGroup; Scope _childHead, _childTail, _next, _prev; _Streams _streams; @@ -192,14 +152,16 @@ class Scope { /// Do not use. Exposes internal state for testing. bool get hasOwnStreams => _streams != null && _streams._scope == this; - Scope(Object this.context, this.rootScope, this._parentScope, + Scope(this.context, this.rootScope, this._parentScope, this._readWriteGroup, this._readOnlyGroup, this.id, - this._stats); + this._stats) { + if (context is ScopeAware) context.scope = this; + } /** * Use [watch] to set up change detection on an expression. * - * * [expression]: The expression to watch for changes. + * * [expression]: The expression to watch for changes. * Expressions may use a special notation in addition to what is supported by the parser. * In particular: * - If an expression begins with '::', it is unwatched as soon as it evaluates to a non-null @@ -296,8 +258,8 @@ class Scope { expression is String || expression is Function); if (expression is String && expression.isNotEmpty) { - var obj = locals == null ? context : new ScopeLocals(context, locals); - return rootScope._parser(expression).eval(obj); + var ctx = locals == null ? context : new ContextLocals(context, locals); + return rootScope._parser(expression).eval(ctx); } assert(locals == null); @@ -368,8 +330,8 @@ class Scope { var child = new Scope(childContext, rootScope, this, _readWriteGroup.newGroup(childContext), _readOnlyGroup.newGroup(childContext), - '$id:${_childScopeNextId++}', - _stats); + '$id:${_childScopeNextId++}', + _stats); var prev = _childTail; child._prev = prev; diff --git a/lib/core_dom/directive_injector.dart b/lib/core_dom/directive_injector.dart index 7637b2300..ab73b8983 100644 --- a/lib/core_dom/directive_injector.dart +++ b/lib/core_dom/directive_injector.dart @@ -442,10 +442,13 @@ class ComponentDirectiveInjector extends DirectiveInjector { final TemplateLoader _templateLoader; final ShadowRoot _shadowRoot; + // The key for the directive that triggered the creation of this injector. + final Key _typeKey; ComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector, EventHandler eventHandler, Scope scope, - this._templateLoader, this._shadowRoot, LightDom lightDom, [View view]) + this._templateLoader, this._shadowRoot, LightDom lightDom, this._typeKey, + [View view]) : super(parent, appInjector, parent._node, parent._nodeAttrs, eventHandler, scope, parent._animate, view) { // A single component creates a ComponentDirectiveInjector and its DirectiveInjector parent, @@ -460,6 +463,13 @@ class ComponentDirectiveInjector extends DirectiveInjector { case SHADOW_ROOT_KEY_ID: return _shadowRoot; case DIRECTIVE_INJECTOR_KEY_ID: return _parent; case COMPONENT_DIRECTIVE_INJECTOR_KEY_ID: return this; + // Currently, this is guaranteed to be called after controller creation. + case SCOPE_KEY_ID: + if (scope == null) { + Scope parentScope = _parent.scope; + scope = parentScope.createChild(getByKey(_typeKey)); + } + return scope; default: return super._getById(keyId); } } diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index fbec8a1b4..3ee67c4ff 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -106,7 +106,7 @@ class ElementBinder { } void _bindCallback(dstPathFn, controller, expression, scope) { - dstPathFn.assign(controller, _parser(expression).bind(scope.context, ScopeLocals.wrapper)); + dstPathFn.assign(controller, _parser(expression).bind(scope.context, ContextLocals.wrapper)); } @@ -273,6 +273,7 @@ class ElementBinder { for(var i = 0; i < directiveRefs.length; i++) { DirectiveRef ref = directiveRefs[i]; Directive annotation = ref.annotation; + _createDirectiveFactories(ref, nodeInjector, node, nodeAttrs); if (ref.annotation.module != null) { DirectiveBinderFn config = ref.annotation.module; diff --git a/lib/core_dom/event_handler.dart b/lib/core_dom/event_handler.dart index b47fb59b7..8ae4aa494 100644 --- a/lib/core_dom/event_handler.dart +++ b/lib/core_dom/event_handler.dart @@ -4,7 +4,7 @@ typedef void EventFunction(event); /** * [EventHandler] is responsible for handling events bound using on-* syntax - * (i.e. `on-click="ctrl.doSomething();"`). The root of the application has an + * (i.e. `on-click="doSomething();"`). The root of the application has an * EventHandler attached as does every [Component]. * * Events bound within [Component] are handled by EventHandler attached to @@ -16,12 +16,14 @@ typedef void EventFunction(event); * Example: * *
- * ; + * ; *
* - * @Component(selector: '[foo]', publishAs: ctrl) - * class FooController { - * say(String something) => print(something); + * @Component(selector: '[foo]') + * class FooComponent { + * void say(String something) { + * print(something); + * } * } * * When button is clicked, "Hello" will be printed in the console. diff --git a/lib/core_dom/module_internal.dart b/lib/core_dom/module_internal.dart index f0cd7e385..5db66b4ca 100644 --- a/lib/core_dom/module_internal.dart +++ b/lib/core_dom/module_internal.dart @@ -19,7 +19,7 @@ import 'package:angular/core_dom/static_keys.dart'; import 'package:angular/core_dom/directive_injector.dart'; export 'package:angular/core_dom/directive_injector.dart' show DirectiveInjector; -import 'package:angular/change_detection/watch_group.dart' show Watch, PrototypeMap; +import 'package:angular/change_detection/watch_group.dart' show Watch, ContextLocals; import 'package:angular/change_detection/ast_parser.dart'; import 'package:angular/core/registry.dart'; import 'package:angular/ng_tracing.dart'; @@ -79,7 +79,6 @@ class CoreDomModule extends Module { bind(SourceLightDom, toValue: null); bind(ComponentCssRewriter); bind(WebPlatform); - bind(Http); bind(UrlRewriter); bind(HttpBackend); diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart index 38f2d2bfa..c8cd5911f 100644 --- a/lib/core_dom/shadow_dom_component_factory.dart +++ b/lib/core_dom/shadow_dom_component_factory.dart @@ -11,8 +11,8 @@ abstract class BoundComponentFactory { List get callArgs; Function call(dom.Element element); - static async.Future _viewFuture( - Component component, ViewCache viewCache, DirectiveMap directives) { + static async.Future _viewFuture(Component component, ViewCache viewCache, + DirectiveMap directives) { if (component.template != null) { return new async.Future.value(viewCache.fromHtml(component.template, directives)); } @@ -89,33 +89,31 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { return _componentFactory.styleElementCache.putIfAbsent( new _ComponentAssetKey(_tag, cssUrl), () => http.get(cssUrl, cache: templateCache) - .then((resp) => resp.responseText, - onError: (e) => '/*\n$e\n*/\n') - .then((String css) { + .then((resp) => resp.responseText, onError: (e) => '/*\n$e\n*/\n') + .then((String css) { - // Shim CSS if required - if (platform.cssShimRequired) { - css = platform.shimCss(css, selector: _tag, cssUrl: cssUrl); - } + // Shim CSS if required + if (platform.cssShimRequired) { + css = platform.shimCss(css, selector: _tag, cssUrl: cssUrl); + } - // If a css rewriter is installed, run the css through a rewriter - var styleElement = new dom.StyleElement() - ..appendText(componentCssRewriter(css, selector: _tag, - cssUrl: cssUrl)); + // If a css rewriter is installed, run the css through a rewriter + var styleElement = new dom.StyleElement() + ..appendText(componentCssRewriter(css, selector: _tag, cssUrl: cssUrl)); - // ensure there are no invalid tags or modifications - treeSanitizer.sanitizeTree(styleElement); + // ensure there are no invalid tags or modifications + treeSanitizer.sanitizeTree(styleElement); - // If the css shim is required, it means that scoping does not - // work, and adding the style to the head of the document is - // preferrable. - if (platform.cssShimRequired) { - dom.document.head.append(styleElement); - return null; - } + // If the css shim is required, it means that scoping does not + // work, and adding the style to the head of the document is + // preferable. + if (platform.cssShimRequired) { + dom.document.head.append(styleElement); + return null; + } - return styleElement; - }) + return styleElement; + }) ); } @@ -131,7 +129,7 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { ..applyAuthorStyles = _component.applyAuthorStyles ..resetStyleInheritance = _component.resetStyleInheritance; - var shadowScope = scope.createChild(new HashMap()); // Isolate + Scope shadowScope; async.Future> cssFuture; if (_component.useNgBaseCss == true) { @@ -165,18 +163,18 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { var eventHandler = new ShadowRootEventHandler( shadowDom, injector.getByKey(EXPANDO_KEY), injector.getByKey(EXCEPTION_HANDLER_KEY)); shadowInjector = new ComponentDirectiveInjector(injector, _injector, eventHandler, shadowScope, - templateLoader, shadowDom, null, view); + templateLoader, shadowDom, null, _ref.typeKey, view); shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility); + var controller = shadowInjector.getByKey(_ref.typeKey); + shadowScope = shadowInjector.getByKey(SCOPE_KEY); + BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); + if (_componentFactory.config.elementProbeEnabled) { probe = _componentFactory.expando[shadowDom] = shadowInjector.elementProbe; shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowDom] = null); } - var controller = shadowInjector.getByKey(_ref.typeKey); - if (controller is ScopeAware) controller.scope = shadowScope; - BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); - shadowScope.context[_component.publishAs] = controller; return controller; } finally { @@ -211,7 +209,5 @@ class _ComponentAssetKey { @Injectable() class ComponentCssRewriter { - String call(String css, { String selector, String cssUrl} ) { - return css; - } + String call(String css, {String selector, String cssUrl}) => css; } diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart index 0c5c0b1aa..8aa593263 100644 --- a/lib/core_dom/transcluding_component_factory.dart +++ b/lib/core_dom/transcluding_component_factory.dart @@ -35,8 +35,7 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { DIRECTIVE_MAP_KEY, NG_BASE_CSS_KEY, EVENT_HANDLER_KEY]; Function call(dom.Node node) { // CSS is not supported. - assert(_component.cssUrls == null || - _component.cssUrls.isEmpty); + assert(_component.cssUrls == null || _component.cssUrls.isEmpty); var element = node as dom.Element; return (DirectiveInjector injector, Scope scope, View view, @@ -72,20 +71,16 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { } TemplateLoader templateLoader = new TemplateLoader(elementFuture); - Scope shadowScope = scope.createChild(new HashMap()); - childInjector = new ComponentDirectiveInjector(injector, this._injector, - eventHandler, shadowScope, templateLoader, new EmulatedShadowRoot(element), lightDom, view); + eventHandler, null, templateLoader, new EmulatedShadowRoot(element), lightDom, _ref.typeKey, + view); childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility); - if (childInjectorCompleter != null) { - childInjectorCompleter.complete(childInjector); - } + if (childInjectorCompleter != null) childInjectorCompleter.complete(childInjector); var controller = childInjector.getByKey(_ref.typeKey); - shadowScope.context[component.publishAs] = controller; - if (controller is ScopeAware) controller.scope = shadowScope; + Scope shadowScope = childInjector.getByKey(SCOPE_KEY); BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); return controller; }; diff --git a/lib/core_dom/view.dart b/lib/core_dom/view.dart index c05e0c247..c9cd683a3 100644 --- a/lib/core_dom/view.dart +++ b/lib/core_dom/view.dart @@ -55,7 +55,7 @@ class ViewPort { } View insertNew(ViewFactory viewFactory, {View insertAfter, Scope viewScope}) { - if (viewScope == null) viewScope = scope.createChild(new PrototypeMap(scope.context)); + if (viewScope == null) viewScope = scope.createChild(scope.context); View view = viewFactory.call(viewScope, directiveInjector); return insert(view, insertAfter: insertAfter); } diff --git a/lib/directive/module.dart b/lib/directive/module.dart index 8b903d3cf..69a29d7a9 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -12,7 +12,8 @@ * * For example: * - * this text is conditionally visible + * this text is conditionally visible + * */ library angular.directive; diff --git a/lib/directive/ng_class.dart b/lib/directive/ng_class.dart index 4f607d0e2..9a6c4216b 100644 --- a/lib/directive/ng_class.dart +++ b/lib/directive/ng_class.dart @@ -173,7 +173,8 @@ abstract class _NgClassBase { nodeAttrs.observe('class', (String cls) { if (prevCls != cls) { prevCls = cls; - _applyChanges(_scope.context[r'$index']); + var index = _hasLocal(_scope, r'$index') ? _getLocal(_scope, r'$index') : null; + _applyChanges(index); } }); } @@ -182,7 +183,8 @@ abstract class _NgClassBase { if (_watchExpression != null) _watchExpression.remove(); _watchExpression = _scope.watch(expression, (v, _) { _computeChanges(v); - _applyChanges(_scope.context[r'$index']); + var index = _hasLocal(_scope, r'$index') ? _getLocal(_scope, r'$index') : null; + _applyChanges(index); }, canChangeModel: false, collection: true); @@ -278,3 +280,21 @@ abstract class _NgClassBase { _previousSet = _currentSet.toSet(); } } + +bool _hasLocal(context, name) { + var ctx = context; + while (ctx is ContextLocals) { + if (ctx.hasProperty(name)) return true; + ctx = ctx.parentScope; + } + return false; +} + +dynamic _getLocal(context, name) { + var ctx = context; + while (ctx is ContextLocals) { + if (ctx.hasProperty(name)) return ctx[name]; + ctx = ctx.parentScope; + } + return null; +} diff --git a/lib/directive/ng_form.dart b/lib/directive/ng_form.dart index 595d547a2..57c693d49 100644 --- a/lib/directive/ng_form.dart +++ b/lib/directive/ng_form.dart @@ -17,7 +17,7 @@ part of angular.directive; @Decorator( selector: '[ng-form]', module: NgForm.module, - map: const { 'ng-form': '@name' }) + map: const { 'ng-form': '&name' }) class NgForm extends NgControl { static module(DirectiveBinder binder) => binder.bind(NgControl, toInstanceOf: NG_FORM_KEY, visibility: Visibility.CHILDREN); @@ -52,19 +52,26 @@ class NgForm extends NgControl { * The name of the control. This is usually fetched via the name attribute that is * present on the element that the control is bound to. */ - @NgAttr('name') - get name => _name; - set name(String value) { - if (value != null) { - super.name = value; - _scope.context[name] = this; - } - } + @NgCallback('name') + String get name => super.name; + void set name(exp) { + // The type could not be added on the parameter as the base setter takes a String + assert(exp is BoundExpression); + var name = exp.expression.toString(); + if (name != null && name.isNotEmpty) { + super.name = name; + try { + exp.assign(this); + } catch (e) { + throw 'There must be a "$name" field on your component to store the form instance.'; + } + } + } /** * The list of associated child controls. */ - get controls => _controlByName; + Map> get controls => _controlByName; /** * Returns the child control that is associated with the given name. If multiple diff --git a/lib/directive/ng_include.dart b/lib/directive/ng_include.dart index d01473d1a..6ab58b321 100644 --- a/lib/directive/ng_include.dart +++ b/lib/directive/ng_include.dart @@ -43,9 +43,8 @@ class NgInclude { _updateContent(ViewFactory viewFactory) { // create a new scope - _childScope = scope.createChild(new PrototypeMap(scope.context)); + _childScope = scope.createChild(scope.context); _view = viewFactory(_childScope, directiveInjector); - _view.nodes.forEach((node) => element.append(node)); } diff --git a/lib/directive/ng_repeat.dart b/lib/directive/ng_repeat.dart index 29aa4006b..f01dd90de 100644 --- a/lib/directive/ng_repeat.dart +++ b/lib/directive/ng_repeat.dart @@ -111,8 +111,7 @@ class NgRepeat { ..[r'$index'] = index ..[r'$id'] = (obj) => obj; if (_keyIdentifier != null) context[_keyIdentifier] = key; - return relaxFnArgs(trackBy.eval)(new ScopeLocals(_scope.context, - context)); + return relaxFnArgs(trackBy.eval)(new ContextLocals(_scope.context, context)); }); } @@ -171,9 +170,11 @@ class NgRepeat { }); addFn((CollectionChangeItem addition) { + var value = addition.item; changeFunctions[addition.currentIndex] = (index, previousView) { - var childContext = _updateContext(new PrototypeMap(_scope.context), index,length) - ..[_valueIdentifier] = addition.item; + var childContext = new ContextLocals(_scope.context); + childContext = _updateContext(childContext, index, length); + childContext[_valueIdentifier] = value; var childScope = _scope.createChild(childContext); var view = views[index] = _boundViewFactory(childScope); _viewPort.insert(view, insertAfter: previousView); @@ -222,9 +223,10 @@ class NgRepeat { _views = views; } - PrototypeMap _updateContext(PrototypeMap context, int index, int length) { - var first = (index == 0); - var last = (index == length - 1); + ContextLocals _updateContext(ContextLocals context, int index, int len) { + var first = index == 0; + var last = index == len - 1; + return context ..[r'$index'] = index ..[r'$first'] = first diff --git a/lib/directive/ng_switch.dart b/lib/directive/ng_switch.dart index 94028231d..dbae4cc04 100644 --- a/lib/directive/ng_switch.dart +++ b/lib/directive/ng_switch.dart @@ -57,55 +57,30 @@ part of angular.directive; }, visibility: Directive.DIRECT_CHILDREN_VISIBILITY) class NgSwitch { - Map> cases = new Map>(); - List<_ViewScopePair> currentViews = <_ViewScopePair>[]; + final _cases = >{'?': <_Case>[]}; + final _currentViews = <_ViewRef>[]; Function onChange; - final Scope scope; + final Scope _scope; - NgSwitch(this.scope) { - cases['?'] = <_Case>[]; - } + NgSwitch(this._scope); - addCase(String value, ViewPort anchor, BoundViewFactory viewFactory) { - cases.putIfAbsent(value, () => <_Case>[]); - cases[value].add(new _Case(anchor, viewFactory)); + void addCase(String value, ViewPort anchor, BoundViewFactory viewFactory) { + _cases.putIfAbsent(value, () => <_Case>[]).add(new _Case(anchor, viewFactory)); } - set value(val) { - currentViews - ..forEach((_ViewScopePair pair) { - pair.port.remove(pair.view); - }) - ..clear(); + void set value(val) { + _currentViews..forEach((_ViewRef view) => view.remove()) + ..clear(); val = '!$val'; - (cases.containsKey(val) ? cases[val] : cases['?']) - .forEach((_Case caze) { - Scope childScope = scope.createChild(new PrototypeMap(scope.context)); - var view = caze.viewFactory(childScope); - caze.anchor.insert(view); - currentViews.add(new _ViewScopePair(view, caze.anchor, - childScope)); - }); - if (onChange != null) { - onChange(); - } - } -} - -class _ViewScopePair { - final View view; - final ViewPort port; - final Scope scope; - - _ViewScopePair(this.view, this.port, this.scope); -} + var cases = _cases.containsKey(val) ? _cases[val] : _cases['?']; + cases.forEach((_Case caze) { + var childScope = _scope.createChild(_scope.context); + _currentViews.add(caze.createView(childScope)); + }); -class _Case { - final ViewPort anchor; - final BoundViewFactory viewFactory; - - _Case(this.anchor, this.viewFactory); + if (onChange != null) onChange(); + } } @Decorator( @@ -113,23 +88,45 @@ class _Case { children: Directive.TRANSCLUDE_CHILDREN, map: const {'.': '@value'}) class NgSwitchWhen { - final NgSwitch ngSwitch; - final ViewPort port; - final BoundViewFactory viewFactory; - final Scope scope; + final NgSwitch _ngSwitch; + final ViewPort _port; + final BoundViewFactory _viewFactory; - NgSwitchWhen(this.ngSwitch, this.port, this.viewFactory, this.scope); + NgSwitchWhen(this._ngSwitch, this._port, this._viewFactory); - set value(String value) => ngSwitch.addCase('!$value', port, viewFactory); + void set value(String value) => _ngSwitch.addCase('!$value', _port, _viewFactory); } @Decorator( children: Directive.TRANSCLUDE_CHILDREN, selector: '[ng-switch-default]') class NgSwitchDefault { - - NgSwitchDefault(NgSwitch ngSwitch, ViewPort port, - BoundViewFactory viewFactory, Scope scope) { + NgSwitchDefault(NgSwitch ngSwitch, ViewPort port, BoundViewFactory viewFactory) { ngSwitch.addCase('?', port, viewFactory); } } + +class _ViewRef { + final View _view; + final ViewPort _port; + final Scope _scope; + + _ViewRef(this._view, this._port, this._scope); + + void remove() { + _port.remove(_view); + } +} + +class _Case { + final ViewPort port; + final BoundViewFactory viewFactory; + + _Case(this.port, this.viewFactory); + + _ViewRef createView(Scope scope) { + var view = viewFactory(scope); + port.insert(view); + return new _ViewRef(view, port, scope); + } +} diff --git a/lib/mock/test_bed.dart b/lib/mock/test_bed.dart index 616bd235b..0abae07f0 100644 --- a/lib/mock/test_bed.dart +++ b/lib/mock/test_bed.dart @@ -89,7 +89,7 @@ class TestBed { rootScope.apply(); } - getProbe(Node node) { + ElementProbe getProbe(Node node) { while (node != null) { ElementProbe probe = expando[node]; if (probe != null) return probe; @@ -98,5 +98,5 @@ class TestBed { throw 'Probe not found.'; } - getScope(Node node) => getProbe(node).scope; + Scope getScope(Node node) => getProbe(node).scope; } diff --git a/lib/routing/ng_view.dart b/lib/routing/ng_view.dart index 5ebb4c4d2..f3cbfaa95 100644 --- a/lib/routing/ng_view.dart +++ b/lib/routing/ng_view.dart @@ -1,13 +1,13 @@ part of angular.routing; /** - * A directive that works with the [Router] and loads the template associated - * with the current route. + * A directive that works with the [Router] and loads the template associated with the current + * route. * * * - * [NgViewDirective] can work with [NgViewDirective] to define nested views - * for hierarchical routes. For example: + * [NgViewDirective] can work with [NgViewDirective] to define nested views for hierarchical routes. + * For example: * * void initRoutes(Router router, RouteViewFactory views) { * views.configure({ @@ -120,9 +120,9 @@ class NgView implements DetachAware, RouteProvider { _viewCache.fromUrl(viewDef.template, newDirectives); viewFuture.then((ViewFactory viewFactory) { _cleanUp(); - _childScope = _scope.createChild(new PrototypeMap(_scope.context)); + _childScope = _scope.createChild(_scope.context); _view = viewFactory(_childScope, _dirInjector); - _view.nodes.forEach((elm) => _element.append(elm)); + _view.nodes.forEach((el) => _element.append(el)); }); } @@ -131,8 +131,8 @@ class NgView implements DetachAware, RouteProvider { _view.nodes.forEach((node) => node.remove()); _childScope.destroy(); - _view = null; _childScope = null; + _view = null; } Route get route => _viewRoute; @@ -140,13 +140,13 @@ class NgView implements DetachAware, RouteProvider { String get routeName => _viewRoute.name; Map get parameters { - var res = new HashMap(); - var p = _viewRoute; - while (p != null) { - res.addAll(p.parameters); - p = p.parent; + var params = new HashMap(); + var route = _viewRoute; + while (route != null) { + params.addAll(route.parameters); + route = route.parent; } - return res; + return params; } /** * Creates a child injector that allows loading new directives, formatters and @@ -197,7 +197,6 @@ class NgView implements DetachAware, RouteProvider { * injected [RouteProvider] will be null. */ abstract class RouteProvider { - /** * Returns [Route] for current view. */ diff --git a/pubspec.lock b/pubspec.lock index 1b6409e97..862d01955 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -85,6 +85,10 @@ packages: description: source_maps source: hosted version: "0.9.3" + source_span: + description: source_span + source: hosted + version: "1.0.0" stack_trace: description: stack_trace source: hosted diff --git a/test/angular_spec.dart b/test/angular_spec.dart index 577f69d12..f9a9fa937 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -257,7 +257,7 @@ main() { "angular.tracing.traceEnter1", "angular.tracing.traceLeave", "angular.tracing.traceLeaveVal", - "angular.watch_group.PrototypeMap", + "angular.watch_group.ContextLocals", "angular.watch_group.ReactionFn", "angular.watch_group.Watch", "change_detection.AvgStopwatch", diff --git a/test/change_detection/context_locals_spec.dart b/test/change_detection/context_locals_spec.dart new file mode 100644 index 000000000..84780907a --- /dev/null +++ b/test/change_detection/context_locals_spec.dart @@ -0,0 +1,50 @@ +library context_locals_spec; + +import '../_specs.dart'; +import 'package:angular/change_detection/watch_group.dart'; + +class RootContext { + String a, b, c; + RootContext(this.a, this.b, this.c); +} + +void main() { + describe('Context Locals', () { + RootContext rootCtx; + beforeEach(() { + rootCtx = new RootContext('#a', '#b', '#c'); + }); + + it('should allow retrieving the parent context', () { + var localCtx = new ContextLocals(rootCtx); + expect(localCtx.parentContext).toBe(rootCtx); + }); + + it('should allow testing for supported locals', () { + var localCtx = new ContextLocals(rootCtx, {'foo': 'bar'}); + expect(localCtx.hasProperty('foo')).toBeTruthy(); + expect(localCtx.hasProperty('far')).toBeFalsy(); + expect(localCtx['foo']).toBe('bar'); + }); + + it('should not allow modifying the root context', () { + var localCtx = new ContextLocals(rootCtx, {'a': '@a'}); + expect(localCtx['a']).toBe('@a'); + localCtx['a'] = '@foo'; + expect(localCtx['a']).toBe('@foo'); + expect(rootCtx.a).toBe('#a'); + }); + + it('should write to the local context', () { + var localCtx = new ContextLocals(rootCtx, {'a': 0}); + var childCtx = new ContextLocals(localCtx); + expect(childCtx.hasProperty('a')).toBeFalsy(); + childCtx['a'] = '@a'; + childCtx['b'] = '@b'; + expect(localCtx['a']).toBe(0); + expect(childCtx['a']).toBe('@a'); + expect(childCtx['b']).toBe('@b'); + expect(localCtx.hasProperty('b')).toBeFalsy(); + }); + }); +} \ No newline at end of file diff --git a/test/change_detection/dirty_checking_change_detector_spec.dart b/test/change_detection/dirty_checking_change_detector_spec.dart index 3fcabaa7d..acec44102 100644 --- a/test/change_detection/dirty_checking_change_detector_spec.dart +++ b/test/change_detection/dirty_checking_change_detector_spec.dart @@ -789,6 +789,28 @@ void testWithGetterFactory(FieldGetterFactory getterFactory) { expect(map.get(k1, 0)).toEqual(null); }); }); + + describe('ContextLocals', () { + it('locals should be watchable', () { + _User parentObject = new _User('R', 'K', 42); + ContextLocals loc = new ContextLocals(parentObject, {'bar': 'buz'}); + detector.watch(loc, 'bar', null); + detector.collectChanges(); + expect(detector.collectChanges().moveNext()).toEqual(false); + loc['bar'] = 'buzz'; + expect(detector.collectChanges().moveNext()).not.toEqual(false); + }); + + it('parentObject should be watchable', () { + _User parentObject = new _User('R', 'K', 42); + ContextLocals loc = new ContextLocals(parentObject, {'bar': 'buz'}); + detector.watch(loc, 'age', null); + detector.collectChanges(); + expect(detector.collectChanges().moveNext()).toEqual(false); + parentObject.age += 1; + expect(detector.collectChanges().moveNext()).not.toEqual(false); + }); + }); }); } @@ -803,6 +825,7 @@ void main() { "toString": (o) => o.toString, "isUnderAge": (o) => o.isUnderAge, "isUnderAgeAsVariable": (o) => o.isUnderAgeAsVariable, + "foo": (o) => o.foo, })); } diff --git a/test/change_detection/watch_group_spec.dart b/test/change_detection/watch_group_spec.dart index 43dd95c9b..efce011af 100644 --- a/test/change_detection/watch_group_spec.dart +++ b/test/change_detection/watch_group_spec.dart @@ -65,7 +65,7 @@ void main() { } beforeEach((Logger _logger) { - context = {}; + context = new ContextLocals({}); var getterFactory = new DynamicFieldGetterFactory(); changeDetector = new DirtyCheckingChangeDetector(getterFactory); watchGrp = new RootWatchGroup(getterFactory, changeDetector, context); @@ -73,6 +73,7 @@ void main() { }); it('should have a toString for debugging', () { + context['a'] = 0; watchGrp.watch(parse('a'), (v, p) {}); watchGrp.newGroup({}); expect("$watchGrp").toEqual( @@ -848,11 +849,13 @@ void main() { }); describe('child group', () { - it('should remove all field watches in group and group\'s children', () { + // todo (vicb) + xit('should remove all field watches in group and group\'s children', () { + context = {'a': null}; watchGrp.watch(parse('a'), (v, p) => logger('0a')); - var child1a = watchGrp.newGroup(new PrototypeMap(context)); - var child1b = watchGrp.newGroup(new PrototypeMap(context)); - var child2 = child1a.newGroup(new PrototypeMap(context)); + var child1a = watchGrp.newGroup(new ContextLocals(context)); + var child1b = watchGrp.newGroup(new ContextLocals(context)); + var child2 = child1a.newGroup(new ContextLocals(context)); child1a.watch(parse('a'), (v, p) => logger('1a')); child1b.watch(parse('a'), (v, p) => logger('1b')); watchGrp.watch(parse('a'), (v, p) => logger('0A')); @@ -881,15 +884,16 @@ void main() { }); it('should remove all method watches in group and group\'s children', () { - context['my'] = new MyClass(logger); + var myClass = new MyClass(logger); + context['my'] = myClass; AST countMethod = new MethodAST(parse('my'), 'count', []); watchGrp.watch(countMethod, (v, p) => logger('0a')); expectOrder(['0a']); - var child1a = watchGrp.newGroup(new PrototypeMap(context)); - var child1b = watchGrp.newGroup(new PrototypeMap(context)); - var child2 = child1a.newGroup(new PrototypeMap(context)); - var child3 = child2.newGroup(new PrototypeMap(context)); + var child1a = watchGrp.newGroup({'my': myClass}); + var child1b = watchGrp.newGroup({'my': myClass}); + var child2 = child1a.newGroup({'my': myClass}); + var child3 = child2.newGroup({'my': myClass}); child1a.watch(countMethod, (v, p) => logger('1a')); expectOrder(['0a', '1a']); child1b.watch(countMethod, (v, p) => logger('1b')); @@ -913,10 +917,11 @@ void main() { }); it('should add watches within its own group', () { - context['my'] = new MyClass(logger); + var myClass = new MyClass(logger); + context['my'] = myClass; AST countMethod = new MethodAST(parse('my'), 'count', []); var ra = watchGrp.watch(countMethod, (v, p) => logger('a')); - var child = watchGrp.newGroup(new PrototypeMap(context)); + var child = watchGrp.newGroup({'my': myClass}); var cb = child.watch(countMethod, (v, p) => logger('b')); expectOrder(['a', 'b']); @@ -958,7 +963,7 @@ void main() { it('should watch children', () { - var childContext = new PrototypeMap(context); + var childContext = new ContextLocals(context); context['a'] = 'OK'; context['b'] = 'BAD'; childContext['b'] = 'OK'; diff --git a/test/core/annotation_src_spec.dart b/test/core/annotation_src_spec.dart index 2c47fb49e..310bd8dde 100644 --- a/test/core/annotation_src_spec.dart +++ b/test/core/annotation_src_spec.dart @@ -39,14 +39,14 @@ void main() => describe('annotations', () { cssUrl: [''], applyAuthorStyles: true, resetStyleInheritance: true, - publishAs: '', - module: (i){}, + module: (i) {}, map: {}, selector: '', visibility: Directive.LOCAL_VISIBILITY, exportExpressions: [], exportExpressionAttrs: [], - useShadowDom: true + useShadowDom: true, + publishAs: '' ); // Check that no fields are null diff --git a/test/core/core_directive_spec.dart b/test/core/core_directive_spec.dart index 12632dc14..aa9e5ef80 100644 --- a/test/core/core_directive_spec.dart +++ b/test/core/core_directive_spec.dart @@ -23,7 +23,6 @@ void main() { expect(annotation.template).toEqual('template'); expect(annotation.templateUrl).toEqual('templateUrl'); expect(annotation.cssUrls).toEqual(['cssUrls']); - expect(annotation.publishAs).toEqual('ctrl'); expect(annotation.map).toEqual({ 'foo': '=>foo', 'attr': '@attr', @@ -104,7 +103,6 @@ class NullParser implements Parser { template: 'template', templateUrl: 'templateUrl', cssUrl: const ['cssUrls'], - publishAs: 'ctrl', module: AnnotatedIoComponent.module, visibility: Directive.LOCAL_VISIBILITY, exportExpressions: const ['exportExpressions'], diff --git a/test/core/parser/parser_spec.dart b/test/core/parser/parser_spec.dart index 9b3a7ed69..b71b5bcfb 100644 --- a/test/core/parser/parser_spec.dart +++ b/test/core/parser/parser_spec.dart @@ -711,7 +711,7 @@ main() { context['a'] = {'b': 1}; context['this'] = context; var locals = {'b': 2}; - var fn = parser("this['a'].b").bind(context, ScopeLocals.wrapper); + var fn = parser("this['a'].b").bind(context, ContextLocals.wrapper); expect(fn(locals)).toEqual(1); }); @@ -958,25 +958,26 @@ main() { }); }); - describe('locals', () { + // todo (vicb) it('should expose local variables', () { - expect(parser('a').bind({'a': 6}, ScopeLocals.wrapper)({'a': 1})).toEqual(1); - expect(parser('add(a,b)'). - bind({'b': 1, 'add': (a, b) { return a + b; }}, ScopeLocals.wrapper)({'a': 2})).toEqual(3); + expect(parser('a').bind({'a': 6}, ContextLocals.wrapper)({'a': 1})).toEqual(1); + + expect(parser('add(a,b)') + .bind(new Context(), ContextLocals.wrapper)({'a': 2})).toEqual(3); }); it('should expose traverse locals', () { - expect(parser('a.b').bind({'a': {'b': 6}}, ScopeLocals.wrapper)({'a': {'b':1}})).toEqual(1); - expect(parser('a.b').bind({'a': null}, ScopeLocals.wrapper)({'a': {'b':1}})).toEqual(1); - expect(parser('a.b').bind({'a': {'b': 5}}, ScopeLocals.wrapper)({'a': null})).toEqual(null); + expect(parser('a.b').bind({'a': {'b': 6}}, ContextLocals.wrapper)({'a': {'b':1}})).toEqual(1); + expect(parser('a.b').bind({'a': null}, ContextLocals.wrapper)({'a': {'b':1}})).toEqual(1); + expect(parser('a.b').bind({'a': {'b': 5}}, ContextLocals.wrapper)({'a': null})).toEqual(null); }); it('should work with scopes', (Scope scope) { scope.context['a'] = {'b': 6}; - expect(parser('a.b').bind(scope.context, ScopeLocals.wrapper)({'a': {'b':1}})).toEqual(1); + expect(parser('a.b').bind(scope.context, ContextLocals.wrapper)({'a': {'b':1}})).toEqual(1); }); it('should expose assignment function', () { @@ -984,7 +985,7 @@ main() { expect(fn.assign).toBeNotNull(); var scope = {}; var locals = {"a": {}}; - fn.bind(scope, ScopeLocals.wrapper).assign(123, locals); + fn.bind(scope, ContextLocals.wrapper).assign(123, locals); expect(scope).toEqual({}); expect(locals["a"]).toEqual({'b':123}); }); @@ -1216,3 +1217,8 @@ class HelloFormatter { return 'Hello, $str!'; } } + +class Context { + var b = 1; + add(a, b) => a + b; +} diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index 4767113db..8bd572345 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -238,9 +238,9 @@ void main() { }); it('children should point to root', (RootScope rootScope) { - var child = rootScope.createChild(new PrototypeMap(rootScope.context)); + var child = rootScope.createChild(rootScope.context); expect(child.rootScope).toEqual(rootScope); - expect(child.createChild(new PrototypeMap(rootScope.context)).rootScope).toEqual(rootScope); + expect(child.createChild(rootScope.context).rootScope).toEqual(rootScope); }); }); @@ -253,11 +253,11 @@ void main() { it('should point to parent', (RootScope rootScope) { - var child = rootScope.createChild(new PrototypeMap(rootScope.context)); + var child = rootScope.createChild(rootScope.context); expect(child.id).toEqual(':0'); expect(rootScope.parentScope).toEqual(null); expect(child.parentScope).toEqual(rootScope); - expect(child.createChild(new PrototypeMap(rootScope.context)).parentScope).toEqual(child); + expect(child.createChild(rootScope.context).parentScope).toEqual(child); }); }); }); @@ -274,7 +274,7 @@ void main() { it(r'should add listener for both emit and broadcast events', (RootScope rootScope) { var log = '', - child = rootScope.createChild(new PrototypeMap(rootScope.context)); + child = rootScope.createChild(rootScope.context); eventFn(event) { expect(event).not.toEqual(null); @@ -294,7 +294,7 @@ void main() { it(r'should return a function that deregisters the listener', (RootScope rootScope) { var log = ''; - var child = rootScope.createChild(new PrototypeMap(rootScope.context)); + var child = rootScope.createChild(rootScope.context); var subscription; eventFn(e) { @@ -411,7 +411,7 @@ void main() { var random = new Random(); for (var i = 0; i < 1000; i++) { if (i % 10 == 0) { - scopes = [root.createChild(null)]; + scopes = [root.createChild({})]; listeners = []; steps = []; } @@ -420,9 +420,9 @@ void main() { if (scopes.length > 10) break; var index = random.nextInt(scopes.length); Scope scope = scopes[index]; - var child = scope.createChild(null); + var child = scope.createChild({}); scopes.add(child); - steps.add('scopes[$index].createChild(null)'); + steps.add('scopes[$index].createChild({})'); break; case 1: var index = random.nextInt(scopes.length); @@ -971,37 +971,6 @@ void main() { }); - - describe('ScopeLocals', () { - it('should read from locals', (RootScope scope) { - scope.context['a'] = 'XXX'; - scope.context['c'] = 'C'; - var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'}); - expect(scopeLocal['a']).toEqual('A'); - expect(scopeLocal['b']).toEqual('B'); - expect(scopeLocal['c']).toEqual('C'); - }); - - it('should write to Scope', (RootScope scope) { - scope.context['a'] = 'XXX'; - scope.context['c'] = 'C'; - var scopeLocal = new ScopeLocals(scope.context, {'a': 'A', 'b': 'B'}); - - scopeLocal['a'] = 'aW'; - scopeLocal['b'] = 'bW'; - scopeLocal['c'] = 'cW'; - - expect(scope.context['a']).toEqual('aW'); - expect(scope.context['b']).toEqual('bW'); - expect(scope.context['c']).toEqual('cW'); - - expect(scopeLocal['a']).toEqual('A'); - expect(scopeLocal['b']).toEqual('B'); - expect(scopeLocal['c']).toEqual('cW'); - }); - }); - - describe(r'watch/digest', () { it(r'should watch and fire on simple property change', (RootScope rootScope) { var log; diff --git a/test/core_dom/compiler_spec.dart b/test/core_dom/compiler_spec.dart index 8f92103d3..f690f56f1 100644 --- a/test/core_dom/compiler_spec.dart +++ b/test/core_dom/compiler_spec.dart @@ -222,7 +222,7 @@ void main() { _.rootScope.apply(); var component = _.rootScope.context['ioComponent']; - expect(component.scope.context['attr']).toEqual('A'); + expect(component.attr).toEqual('A'); })); it('should work with one-way bindings', async(() { @@ -231,9 +231,9 @@ void main() { microLeap(); _.rootScope.apply(); var component = _.rootScope.context['ioComponent']; - expect(component.scope.context['oneway']).toEqual('misko'); + expect(component.oneway).toEqual('misko'); - component.scope.context['oneway'] = 'angular'; + component.oneway = 'angular'; _.rootScope.apply(); // Not two-way, did not change. expect(_.rootScope.context['name']).toEqual('misko'); @@ -246,8 +246,9 @@ void main() { microLeap(); _.rootScope.apply(); var component = _.rootScope.context['ioComponent']; - expect(component.scope.context['expr']).toEqual('misko'); - component.scope.context['expr'] = 'angular'; + expect(component.attr).toBeNull(); + expect(component.expr).toEqual('misko'); + component.expr = 'angular'; _.rootScope.apply(); expect(_.rootScope.context['name']).toEqual('angular'); })); @@ -402,20 +403,20 @@ void main() { '' '
'); - final scope = _shadowScope(element.children[0]); + final component = ngProbe(element.children[0]).directive(ConditionalContentComponent); microLeap(); - scope.apply(); + _.rootScope.apply(); expect(element).toHaveText('(, ABC)'); - scope.context['showLeft'] = true; + component.showLeft = true; microLeap(); - scope.apply(); + _.rootScope.apply(); expect(element).toHaveText('(A, BC)'); - scope.context['showLeft'] = false; + component.showLeft = false; microLeap(); - scope.apply(); + _.rootScope.apply(); expect(element).toHaveText('(, ABC)'); })); @@ -480,7 +481,7 @@ void main() { expect(simpleElement).toHaveText('INNER(innerText)'); var simpleProbe = ngProbe(simpleElement); var simpleComponent = simpleProbe.injector.getByKey(new Key(SimpleComponent)); - expect(simpleComponent.scope.context['name']).toEqual('INNER'); + expect(simpleComponent.name).toEqual('INNER'); var shadowRoot = simpleElement.shadowRoot; // If there is no shadow root, skip this. @@ -576,14 +577,13 @@ void main() { _.rootScope.context['name'] = 'misko'; _.rootScope.apply(); var component = _.rootScope.context['ioComponent']; - expect(component.scope.context['name']).toEqual(null); - expect(component.scope.context['attr']).toEqual('A'); - expect(component.scope.context['expr']).toEqual('misko'); - component.scope.context['expr'] = 'angular'; + expect(component.attr).toEqual('A'); + expect(component.expr).toEqual('misko'); + component.expr = 'angular'; _.rootScope.apply(); expect(_.rootScope.context['name']).toEqual('angular'); expect(_.rootScope.context['done']).toEqual(null); - component.scope.context['ondone'](); + component.ondone(); expect(_.rootScope.context['done']).toEqual(true); })); @@ -607,8 +607,8 @@ void main() { var component = _.rootScope.context['ioComponent']; _.rootScope.apply(); - expect(component.scope.context['expr']).toEqual('misko'); - component.scope.context['expr'] = 'angular'; + expect(component.expr).toEqual('misko'); + component.expr = 'angular'; _.rootScope.apply(); expect(_.rootScope.context['name']).toEqual('angular'); })); @@ -711,8 +711,8 @@ void main() { _.compile(''); microLeap(); _.rootScope.apply(); - var componentScope = _.rootScope.context['camelCase']; - expect(componentScope.context['camelCase']).toEqual('G'); + var componentContext = _.rootScope.context['camelCase']; + expect(componentContext.camelCase).toEqual('G'); })); // TODO: This is a terrible test @@ -727,6 +727,13 @@ void main() { } })); + it('should publish component controller into the scope', async(() { + var element = _.compile(r'
'); + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('WORKED'); + })); + it('should "publish" controller to injector under provided module', () { _.compile(r'
'); expect(PublishModuleAttrDirective._injector.get(PublishModuleAttrDirective)). @@ -981,7 +988,6 @@ void main() { }); }); - describe('controller scoping', () { it('should make controllers available to sibling and child controllers', async((Logger log) { _.compile(''); @@ -1010,7 +1016,7 @@ void main() { })); /* - This test is dissabled becouse I (misko) thinks it has no real use case. It is easier + This test is dissabled because I (misko) thinks it has no real use case. It is easier to understand in terms of ng-repeat @@ -1055,23 +1061,6 @@ void main() { expect(decorator.valueDecorator).toEqual('worked'); }); }); - - describe('Injection accross application injection boundaries', () { - it('should create directive injectors for elements only', - async((TestBed _, Logger logger, CompilerConfig config) { - if (!config.elementProbeEnabled) return; - _.compile(''); - var directiveInjector = ngInjector(_.rootElement); - var lazyInjector = NgView.createChildInjectorWithReload( - _.injector, - [new Module()..bind(LazyPane)..bind(LazyPaneHelper)]); - var dirMap = lazyInjector.get(DirectiveMap); - ViewFactory viewFactory = _.compiler([new Element.tag('lazy-pane')], dirMap); - var childScope = _.rootScope.createChild({}); - viewFactory(childScope, directiveInjector); - expect(logger).toContain('LazyPane-0'); - })); - }); })); } @@ -1080,9 +1069,7 @@ void main() { visibility: Directive.DIRECT_CHILDREN_VISIBILITY) class TabComponent { int id = 0; - Logger log; - LocalAttrDirective local; - TabComponent(Logger this.log, LocalAttrDirective this.local, Scope scope) { + TabComponent(Logger log, LocalAttrDirective local) { log('TabComponent-${id++}'); local.ping(); } @@ -1103,10 +1090,7 @@ class LazyPaneHelper {} @Component(selector: 'pane') class PaneComponent { - TabComponent tabComponent; - LocalAttrDirective localDirective; - Logger log; - PaneComponent(TabComponent this.tabComponent, LocalAttrDirective this.localDirective, Logger this.log, Scope scope) { + PaneComponent(TabComponent tabComponent, LocalAttrDirective localDirective, Logger log) { log('PaneComponent-${tabComponent.id++}'); localDirective.ping(); } @@ -1216,18 +1200,15 @@ class PublishModuleAttrDirective implements PublishModuleDirectiveSuperType { selector: 'simple', template: r'{{name}}()') class SimpleComponent { - Scope scope; - SimpleComponent(Scope this.scope) { - scope.context['name'] = 'INNER'; - } + var name = 'INNER'; } @Component( selector: 'multiple-content-tags', template: r'(, )') -class MultipleContentTagsComponent { - final Scope scope; - MultipleContentTagsComponent(this.scope); +class MultipleContentTagsComponent implements ScopeAware { + Scope scope; + MultipleContentTagsComponent(); } @Component( @@ -1253,35 +1234,46 @@ class TranscludingComponent { } @Component( - selector: 'conditional-content', - template: r'(
, )') -class ConditionalContentComponent { + selector: 'conditional-content', + template: r'(
, )') +class ConditionalContentComponent implements ScopeAware { Scope scope; - ConditionalContentComponent(this.scope); + var showLeft; + ConditionalContentComponent(); +} + +@Component( + selector: 'sometimes', + template: r'
') +class SometimesComponent { + @NgTwoWay('sometimes') + var sometimes; } @Component( selector: 'io', template: r'', map: const { - 'attr': '@scope.context.attr', - 'expr': '<=>scope.context.expr', - 'oneway': '=>scope.context.oneway', - 'ondone': '&scope.context.ondone', + 'attr': '@attr', + 'expr': '<=>expr', + 'oneway': '=>oneway', + 'ondone': '&ondone', }) -class IoComponent { - Scope scope; - IoComponent(Scope scope) { - this.scope = scope; +class IoComponent implements ScopeAware { + var attr; + var oneway; + var expr = 'initialExpr'; + Function ondone; + var done; + + void set scope(Scope scope) { scope.rootScope.context['ioComponent'] = this; - scope.context['expr'] = 'initialExpr'; } } @Component( selector: 'io-controller', template: r'', - publishAs: 'ctrl', map: const { 'attr': '@attr', 'expr': '<=>expr', @@ -1289,15 +1281,14 @@ class IoComponent { 'ondone': '&onDone', 'on-optional': '&onOptional' }) -class IoControllerComponent { - Scope scope; +class IoControllerComponent implements ScopeAware { var attr; var expr; var exprOnce; var onDone; var onOptional; - IoControllerComponent(Scope scope) { - this.scope = scope; + + void set scope(Scope scope) { scope.rootScope.context['ioComponent'] = this; } } @@ -1311,15 +1302,14 @@ class IoControllerComponent { 'ondone': '&onDone', 'onOptional': '&onOptional' }) -class UnpublishedIoControllerComponent { - Scope scope; +class UnpublishedIoControllerComponent implements ScopeAware { var attr; var expr; var exprOnce; var onDone; var onOptional; - UnpublishedIoControllerComponent(Scope scope) { - this.scope = scope; + + void set scope(Scope scope) { scope.rootScope.context['ioComponent'] = this; } } @@ -1339,12 +1329,13 @@ class NonAssignableMappingComponent { } @Component( selector: 'camel-case-map', map: const { - 'camel-case': '@scope.context.camelCase', + 'camel-case': '@camelCase', }) -class CamelCaseMapComponent { - Scope scope; - CamelCaseMapComponent(Scope this.scope) { - scope.rootScope.context['camelCase'] = scope; +class CamelCaseMapComponent implements ScopeAware { + var camelCase; + + void set scope(Scope scope) { + scope.rootScope.context['camelCase'] = this; } } @@ -1352,28 +1343,26 @@ class CamelCaseMapComponent { selector: 'parent-expression', template: '
inside {{fromParent()}}
', map: const { - 'from-parent': '&scope.context.fromParent', + 'from-parent': '&fromParent', }) class ParentExpressionComponent { Scope scope; - ParentExpressionComponent(Scope this.scope); + var fromParent; } @Component( selector: 'publish-me', - template: r'{{ctrlName.value}}', - publishAs: 'ctrlName') + template: r'{{value}}') class PublishMeComponent { String value = 'WORKED'; } @Component( selector: 'log', - template: r'', - publishAs: 'ctrlName') + template: r'') class LogComponent { - LogComponent(Scope scope, Logger logger) { - logger(scope); + LogComponent(Logger logger) { + logger("LogComponent"); } } @@ -1388,7 +1377,7 @@ class LogComponent { 'optional-two': '<=>optional', 'optional-once': '=>!optional', }) -class AttachDetachComponent implements AttachAware, DetachAware, ShadowRootAware { +class AttachDetachComponent implements AttachAware, DetachAware, ShadowRootAware, ScopeAware { Logger logger; Scope scope; String attrValue = 'too early'; @@ -1396,14 +1385,16 @@ class AttachDetachComponent implements AttachAware, DetachAware, ShadowRootAware String onceValue = 'too early'; String optional; - AttachDetachComponent(Logger this.logger, TemplateLoader templateLoader, Scope this.scope) { + AttachDetachComponent(this.logger, TemplateLoader templateLoader) { logger('new'); templateLoader.template.then((_) => logger('templateLoaded')); } - attach() => logger('attach:@$attrValue; =>$exprValue; =>!$onceValue'); - detach() => logger('detach'); - onShadowRoot(shadowRoot) { + void attach() => logger('attach:@$attrValue; =>$exprValue; =>!$onceValue'); + + void detach() => logger('detach'); + + void onShadowRoot(shadowRoot) { scope.rootScope.context['shadowRoot'] = shadowRoot; logger(shadowRoot); } @@ -1425,18 +1416,17 @@ class SayHelloFormatter { @Component( selector: 'expr-attr-component', template: r'', - publishAs: 'ctrl', map: const { 'expr': '<=>expr', 'one-way': '=>oneWay', 'once': '=>!exprOnce' }) -class ExprAttrComponent { +class ExprAttrComponent implements ScopeAware { var expr; var oneWay; var exprOnce; - ExprAttrComponent(Scope scope) { + void set scope(Scope scope) { scope.rootScope.context['exprAttrComponent'] = this; } } @@ -1446,11 +1436,18 @@ class ExprAttrComponent { templateUrl: 'foo.html') class SimpleAttachComponent implements AttachAware, ShadowRootAware { Logger logger; + SimpleAttachComponent(this.logger) { logger('SimpleAttachComponent'); } - attach() => logger('attach'); - onShadowRoot(_) => logger('onShadowRoot'); + + void attach() { + logger('attach'); + } + + void onShadowRoot(_) { + logger('onShadowRoot'); + } } @Decorator( @@ -1471,7 +1468,7 @@ class AttachWithAttr implements AttachAware { templateUrl: 'foo.html') class LogElementComponent{ LogElementComponent(Logger logger, Element element, Node node, - ShadowRoot shadowRoot) { + ShadowRoot shadowRoot) { logger(element); logger(node); logger(shadowRoot); @@ -1485,8 +1482,10 @@ class LogElementComponent{ }) class OneTimeDecorator { Logger log; + OneTimeDecorator(this.log); - set value(v) => log(v); + + void set value(v) => log(v); } @Decorator( @@ -1537,8 +1536,7 @@ class InnerShadowy {} @Component( selector: 'once-inside', - template: '
', - publishAs: 'ctrl' + template: '
' ) class OnceInside { var ot; @@ -1566,27 +1564,27 @@ class InjectorDependentComponent { selector: 'outer-with-div', template: 'OUTER(
)' ) -class OuterWithDivComponent { - final Scope scope; - OuterWithDivComponent(this.scope); +class OuterWithDivComponent implements ScopeAware { + Scope scope; + OuterWithDivComponent(); } @Component( selector: 'outer', template: 'OUTER()' ) -class OuterComponent { - final Scope scope; - OuterComponent(this.scope); +class OuterComponent implements ScopeAware { + Scope scope; + OuterComponent(); } @Component( selector: 'inner', template: 'INNER()' ) -class InnerComponent { - final Scope scope; - InnerComponent(this.scope); +class InnerComponent implements ScopeAware { + Scope scope; + InnerComponent(); } @Component( @@ -1597,10 +1595,3 @@ class InnerInnerComponent { InnerInnerComponent() {} } -_shadowScope(element){ - if (element.shadowRoot != null) { - return ngProbe(element.shadowRoot).scope; - } else { - return ngProbe(element).directives[0].scope; - } -} \ No newline at end of file diff --git a/test/core_dom/event_handler_spec.dart b/test/core_dom/event_handler_spec.dart index 036e19986..601f06f8a 100644 --- a/test/core_dom/event_handler_spec.dart +++ b/test/core_dom/event_handler_spec.dart @@ -5,11 +5,10 @@ import '../_specs.dart'; @Component(selector: 'bar', template: '''
- +
- ''', - publishAs: 'ctrl') + ''') class BarComponent { var invoked = false; BarComponent(RootScope scope) { @@ -40,26 +39,33 @@ main() { it('should register and handle event', (TestBed _) { var e = compile(_, - '''
'''); + '''
+
+
'''); - _.triggerEvent(e, 'abc'); + _.triggerEvent(e.querySelector('[on-abc]'), 'abc'); expect(_.rootScope.context['invoked']).toEqual(true); }); it('shoud register and handle event with long name', (TestBed _) { var e = compile(_, - '''
'''); + '''
+
+
'''); - _.triggerEvent(e, 'myNewEvent'); + _.triggerEvent(e.querySelector('[on-my-new-event]'), 'myNewEvent'); expect(_.rootScope.context['invoked']).toEqual(true); }); it('shoud have model updates applied correctly', (TestBed _) { var e = compile(_, - '''
{{description}}
'''); - e.dispatchEvent(new Event('abc')); + '''
+
{{description}}
+
'''); + var el = document.querySelector('[on-abc]'); + el.dispatchEvent(new Event('abc')); _.rootScope.apply(); - expect(e.text).toEqual("new description"); + expect(e.text.trim()).toEqual("new description"); }); it('shoud register event when shadow dom is used', async((TestBed _) { @@ -76,8 +82,9 @@ main() { it('shoud handle event within content only once', async((TestBed _) { var e = compile(_, - ''' -
+ '''
+ +
'''); @@ -86,10 +93,9 @@ main() { document.querySelector('[on-abc]').dispatchEvent(new Event('abc')); var shadowRoot = document.querySelector('bar').shadowRoot; var shadowRootScope = _.getScope(shadowRoot); - BarComponent ctrl = shadowRootScope.context['ctrl']; - expect(ctrl.invoked).toEqual(false); + expect(shadowRootScope.context.invoked).toEqual(false); - expect(_.rootScope.context['ctrl']['invoked']).toEqual(true); + expect(_.rootScope.context['invoked']).toEqual(true); })); }); } diff --git a/test/core_dom/web_platform_spec.dart b/test/core_dom/web_platform_spec.dart index 17c96f68c..10964b5dd 100644 --- a/test/core_dom/web_platform_spec.dart +++ b/test/core_dom/web_platform_spec.dart @@ -189,7 +189,6 @@ main() { @Component( selector: "test-wptc", - publishAs: "ctrl", templateUrl: "template.html", cssUrl: "style.css") class _WebPlatformTestComponent { @@ -197,7 +196,6 @@ class _WebPlatformTestComponent { @Component( selector: "test-wptca[a]", - publishAs: "ctrl", templateUrl: "template.html", cssUrl: "style.css") class _WebPlatformTestComponentWithAttribute { @@ -205,7 +203,6 @@ class _WebPlatformTestComponentWithAttribute { @Component( selector: "my-inner", - publishAs: "ctrl", templateUrl: "inner-html.html", cssUrl: "inner-style.css") class _InnerComponent { @@ -213,7 +210,6 @@ class _InnerComponent { @Component( selector: "my-outer", - publishAs: "ctrl", templateUrl: "outer-html.html", cssUrl: "outer-style.css") class _OuterComponent { diff --git a/test/directive/ng_if_spec.dart b/test/directive/ng_if_spec.dart index 2ad47f030..b23f8f1d9 100644 --- a/test/directive/ng_if_spec.dart +++ b/test/directive/ng_if_spec.dart @@ -70,36 +70,57 @@ main() { } ); - they('should create a child scope', + they('should create and destroy a child scope', [ // ng-if '
' + '
'.trim() + - ' inside {{setBy}};'.trim() + + ' inside {{ctx}};'.trim() + '
'.trim() + - ' outside {{setBy}}'.trim() + + ' outside {{ctx}}'.trim() + '
', // ng-unless '
' + '
'.trim() + - ' inside {{setBy}};'.trim() + + ' inside {{ctx}};'.trim() + '
'.trim() + - ' outside {{setBy}}'.trim() + + ' outside {{ctx}}'.trim() + '
'], (html) { - rootScope.context['setBy'] = 'topLevel'; + rootScope.context['ctx'] = 'parent'; + + var getChildScope = () => rootScope.context['probe'] == null ? + null : rootScope.context['probe'].scope; + compile(html); - expect(element).toHaveText('outside topLevel'); + expect(element).toHaveText('outside parent'); + expect(getChildScope()).toBeNull(); + + rootScope.apply(() { + rootScope.context['isVisible'] = true; + }); + // The nested scope uses the parent context + expect(element).toHaveText('inside parent;outside parent'); + expect(element.querySelector('#outside')).toHaveHtml('outside parent'); + expect(element.querySelector('#inside')).toHaveHtml('inside parent;'); + + var childScope1 = getChildScope(); + expect(childScope1).toBeNotNull(); + var destroyListener = guinness.createSpy('destroy child scope'); + var watcher = childScope1.on(ScopeEvent.DESTROY).listen(destroyListener); + + rootScope.apply(() { + rootScope.context['isVisible'] = false; + }); + expect(getChildScope()).toBeNull(); + expect(destroyListener).toHaveBeenCalledOnce(); rootScope.apply(() { rootScope.context['isVisible'] = true; }); - expect(element).toHaveText('inside childController;outside topLevel'); - // The value on the parent scope.context['should'] be unchanged. - expect(rootScope.context['setBy']).toEqual('topLevel'); - expect(element.querySelector('#outside')).toHaveHtml('outside topLevel'); - // A child scope.context['must'] have been created and hold a different value. - expect(element.querySelector('#inside')).toHaveHtml('inside childController;'); + var childScope2 = getChildScope(); + expect(childScope2).toBeNotNull(); + expect(childScope2).not.toBe(childScope1); } ); diff --git a/test/directive/ng_include_spec.dart b/test/directive/ng_include_spec.dart index b1b793b70..7ff719f3c 100644 --- a/test/directive/ng_include_spec.dart +++ b/test/directive/ng_include_spec.dart @@ -13,12 +13,12 @@ main() { var element = _.compile('
'); - expect(element.innerHtml).toEqual(''); + expect(element).toHaveText(''); microLeap(); // load the template from cache. scope.context['name'] = 'Vojta'; scope.apply(); - expect(element.text).toEqual('my name is Vojta'); + expect(element).toHaveText('my name is Vojta'); })); it('should fetch template from url using interpolation', async((Scope scope, TemplateCache cache) { @@ -27,7 +27,7 @@ main() { var element = _.compile('
'); - expect(element.innerHtml).toEqual(''); + expect(element).toHaveText(''); scope.context['name'] = 'Vojta'; scope.context['template'] = 'tpl1.html'; @@ -35,15 +35,54 @@ main() { scope.apply(); microLeap(); scope.apply(); - expect(element.text).toEqual('My name is Vojta'); + expect(element).toHaveText('My name is Vojta'); scope.context['template'] = 'tpl2.html'; microLeap(); scope.apply(); microLeap(); scope.apply(); - expect(element.text).toEqual('I am Vojta'); + expect(element).toHaveText('I am Vojta'); })); + it('should create and destroy a child scope', async((Scope scope, TemplateCache cache) { + cache.put('tpl.html', new HttpResponse(200, '

include

')); + + var getChildScope = () => scope.context['probe'] == null ? + null : scope.context['probe'].scope; + + var element = _.compile('
'); + + expect(element).toHaveText(''); + expect(getChildScope()).toBeNull(); + + scope.context['template'] = 'tpl.html'; + microLeap(); + scope.apply(); + microLeap(); + scope.apply(); + expect(element).toHaveText('include'); + var childScope1 = getChildScope(); + expect(childScope1).toBeNotNull(); + var destroyListener = guinness.createSpy('destroy child scope'); + var watcher = childScope1.on(ScopeEvent.DESTROY).listen(destroyListener); + + scope.context['template'] = null; + microLeap(); + scope.apply(); + expect(element).toHaveText(''); + expect(getChildScope()).toBeNull(); + expect(destroyListener).toHaveBeenCalledOnce(); + + scope.context['template'] = 'tpl.html'; + microLeap(); + scope.apply(); + microLeap(); + scope.apply(); + expect(element).toHaveText('include'); + var childScope2 = getChildScope(); + expect(childScope2).toBeNotNull(); + expect(childScope2).not.toBe(childScope1); + })); }); } diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index 1c2a3ae30..2f3da0db4 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -1668,8 +1668,7 @@ void main() { @Component( selector: 'no-love', - template: '', - publishAs: 'ctrl') + template: '') class ComponentWithNoLove { } diff --git a/test/directive/ng_repeat_spec.dart b/test/directive/ng_repeat_spec.dart index a0a61f5ee..78931a9f8 100644 --- a/test/directive/ng_repeat_spec.dart +++ b/test/directive/ng_repeat_spec.dart @@ -404,9 +404,7 @@ main() { it('should not error when the first watched item is removed', () { element = compile( '
    ' - '
  • ' - r' ' - '
  • ' + '
  • {{ i }}
  • ' '
'); scope.context['items'] = ['misko', 'shyam', 'frodo']; scope.apply(); @@ -419,9 +417,7 @@ main() { it('should not error when the last watched item is removed', () { element = compile( '
    ' - '
  • ' - r' ' - '
  • ' + '
  • {{ i }}
  • ' '
'); scope.context['items'] = ['misko', 'shyam', 'frodo']; scope.apply(); @@ -434,15 +430,12 @@ main() { it('should not error when multiple watched items are removed at the same time', () { element = compile( '
    ' - '
  • ' - r' ' - '
  • ' + '
  • {{ i }}
  • ' '
'); scope.context['items'] = ['misko', 'shyam', 'frodo', 'igor']; scope.apply(); expect(element.children.length).toEqual(4); - scope.context['items'].remove('shyam'); - scope.context['items'].remove('frodo'); + scope.context['items']..remove('shyam')..remove('frodo'); scope.apply(); expect(element.children.length).toEqual(2); }); @@ -529,13 +522,13 @@ main() { it('should correctly handle detached state', () { scope.context['items'] = [1]; - var parentScope = scope.createChild(new PrototypeMap(scope.context)); + var childScope = scope.createChild(scope.context); element = compile( '
    ' '
  • {{item}}
  • ' - '
', parentScope); + '', childScope); - parentScope.destroy(); + childScope.destroy(); expect(scope.apply).not.toThrow(); }); diff --git a/test/directive/ng_switch_spec.dart b/test/directive/ng_switch_spec.dart index 2c26666ca..2ab47c728 100644 --- a/test/directive/ng_switch_spec.dart +++ b/test/directive/ng_switch_spec.dart @@ -174,7 +174,7 @@ void main() { _.rootScope.apply(); var getChildScope = () => _.rootScope.context['probe'] == null ? - null : _.rootScope.context['probe'].scope; + null : _.rootScope.context['probe'].scope; expect(getChildScope()).toBeNull(); diff --git a/test/io/expression_extractor_spec.dart b/test/io/expression_extractor_spec.dart index 6a2711270..9b1e12cc2 100644 --- a/test/io/expression_extractor_spec.dart +++ b/test/io/expression_extractor_spec.dart @@ -33,19 +33,15 @@ void main() { var expressions = _extractExpressions('test/io/test_files/main.dart'); expect(expressions, unorderedEquals([ - 'ctrl.expr', - 'ctrl.anotherExpression', - 'ctrl.callback', - 'ctrl.twoWayStuff', 'attr', 'expr', 'anotherExpression', 'callback', 'twoWayStuff', 'exported + expression', - 'ctrl.inline.template.expression', + 'inline.template.expression', 'ngIfCondition', - 'ctrl.if' + 'if' ])); }); diff --git a/test/io/test_files/main.dart b/test/io/test_files/main.dart index cd263cdfd..094684265 100644 --- a/test/io/test_files/main.dart +++ b/test/io/test_files/main.dart @@ -16,7 +16,7 @@ class NgIfDirective { 'attr': '@attr', 'expr': '=>expr' }, - template: '
{{ctrl.inline.template.expression}}
', + template: '
{{inline.template.expression}}
', exportExpressionAttrs: const ['exported-attr'], exportExpressions: const ['exported + expression']) class MyComponent { diff --git a/test/io/test_files/main.html b/test/io/test_files/main.html index 95ecc8ad0..41b1ff188 100644 --- a/test/io/test_files/main.html +++ b/test/io/test_files/main.html @@ -1,16 +1,15 @@
- + + attr="attr2" expr="expr2" + another-expression="anotherExpression2" + callback="callback2" + two-way-stuff="twoWayStuff2"> -
-
+
\ No newline at end of file diff --git a/test/routing/ng_view_spec.dart b/test/routing/ng_view_spec.dart index 34bffa8f8..c0fd88623 100644 --- a/test/routing/ng_view_spec.dart +++ b/test/routing/ng_view_spec.dart @@ -20,28 +20,26 @@ main() => describe('ngView', () { _ = tb; router = _router; - templates.put('foo.html', new HttpResponse(200, - '

Foo

')); - templates.put('bar.html', new HttpResponse(200, - '

Bar

')); + templates.put('foo.html', new HttpResponse(200, '

Foo

')); + templates.put('bar.html', new HttpResponse(200, '

Bar

')); }); it('should switch template', async(() { Element root = _.compile(''); - expect(root.text).toEqual(''); + expect(root).toHaveText(''); router.route('/foo'); microLeap(); - expect(root.text).toEqual('Foo'); + expect(root).toHaveText('Foo'); router.route('/bar'); microLeap(); - expect(root.text).toEqual('Bar'); + expect(root).toHaveText('Bar'); router.route('/foo'); microLeap(); - expect(root.text).toEqual('Foo'); + expect(root).toHaveText('Foo'); })); it('should expose NgView as RouteProvider', async(() { @@ -62,25 +60,50 @@ main() => describe('ngView', () { router.route('/foo'); microLeap(); Element root = _.compile(''); - expect(root.text).toEqual(''); + expect(root).toHaveText(''); _.rootScope.apply(); microLeap(); - expect(root.text).toEqual('Foo'); + expect(root).toHaveText('Foo'); })); it('should clear template when route is deactivated', async(() { Element root = _.compile(''); - expect(root.text).toEqual(''); + expect(root).toHaveText(''); router.route('/foo'); microLeap(); - expect(root.text).toEqual('Foo'); + expect(root).toHaveText('Foo'); router.route('/baz'); // route without a template microLeap(); - expect(root.text).toEqual(''); + expect(root).toHaveText(''); + })); + + it('should create and destroy a child scope', async((RootScope scope) { + Element root = _.compile(''); + + var getChildScope = () => scope.context['p'] == null ? + null : scope.context['p'].scope; + + expect(root).toHaveText(''); + expect(getChildScope()).toBeNull(); + + router.route('/foo'); + microLeap(); + expect(root).toHaveText('Foo'); + var childScope1 = getChildScope(); + expect(childScope1).toBeNotNull(); + var destroyListener = guinness.createSpy('destroy child scope'); + var watcher = childScope1.on(ScopeEvent.DESTROY).listen(destroyListener); + + router.route('/baz'); + microLeap(); + expect(root).toHaveText(''); + expect(destroyListener).toHaveBeenCalledOnce(); + var childScope2 = getChildScope(); + expect(childScope2).toBeNull(); })); }); @@ -116,25 +139,25 @@ main() => describe('ngView', () { it('should switch nested templates', async(() { Element root = _.compile(''); microLeap(); _.rootScope.apply(); microLeap(); - expect(root.text).toEqual(''); + expect(root).toHaveText(''); router.route('/library/all'); microLeap(); _.rootScope.apply(); microLeap(); - expect(root.text).toEqual('LibraryBooks'); + expect(root).toHaveText('LibraryBooks'); router.route('/library/1234'); microLeap(); _.rootScope.apply(); microLeap(); - expect(root.text).toEqual('LibraryBook 1234'); + expect(root).toHaveText('LibraryBook 1234'); // nothing should change here router.route('/library/1234/overview'); microLeap(); _.rootScope.apply(); microLeap(); - expect(root.text).toEqual('LibraryBook 1234'); + expect(root).toHaveText('LibraryBook 1234'); // nothing should change here router.route('/library/1234/read'); microLeap(); _.rootScope.apply(); microLeap(); - expect(root.text).toEqual('LibraryRead Book 1234'); + expect(root).toHaveText('LibraryRead Book 1234'); })); it('should not attempt to destroy and already destroyed childscope', async(() { @@ -182,11 +205,11 @@ main() => describe('ngView', () { it('should switch inline templates', async(() { Element root = _.compile(''); - expect(root.text).toEqual(''); + expect(root).toHaveText(''); router.route('/foo'); microLeap(); - expect(root.text).toEqual('Hello'); + expect(root).toHaveText('Hello'); })); }); }); diff --git a/test/tools/html_extractor_spec.dart b/test/tools/html_extractor_spec.dart index 253059d11..012eb1529 100644 --- a/test/tools/html_extractor_spec.dart +++ b/test/tools/html_extractor_spec.dart @@ -13,40 +13,40 @@ void main() { it('should extract text mustache expressions', () { var ioService = new MockIoService({ 'foo.html': r''' -
foo {{ctrl.bar}} baz {{aux}}
+
foo {{bar}} baz {{aux}}
''' }); var extractor = new HtmlExpressionExtractor([]); extractor.crawl('/', ioService); expect(extractor.expressions.toList()..sort(), - equals(['aux', 'ctrl.bar'])); + equals(['aux', 'bar'])); }); it('should extract attribute mustache expressions', () { var ioService = new MockIoService({ 'foo.html': r''' -
+
''' }); var extractor = new HtmlExpressionExtractor([]); extractor.crawl('/', ioService); expect(extractor.expressions.toList()..sort(), - equals(['aux', 'ctrl.bar'])); + equals(['aux', 'bar'])); }); it('should extract ng-repeat expressions', () { var ioService = new MockIoService({ 'foo.html': r''' -
+
''' }); var extractor = new HtmlExpressionExtractor([]); extractor.crawl('/', ioService); expect(extractor.expressions.toList()..sort(), - equals(['ctrl.bar'])); + equals(['bar'])); }); it('should extract expressions provided in the directive info', () { @@ -61,30 +61,30 @@ void main() { }); it('should extract expressions from expression attributes', () { - var ioService = new MockIoService({'foo.html': r''}); + var ioService = new MockIoService({ + 'foo.html': r'' + }); var extractor = new HtmlExpressionExtractor([ new DirectiveInfo('foo', ['bar']) ]); extractor.crawl('/', ioService); - expect(extractor.expressions.toList()).toEqual(['ctrl.baz']); + expect(extractor.expressions.toList()).toEqual(['baz']); }); it('should extract expressions from expression attributes for camelCased attributes', () { - var ioService = new MockIoService({'foo.html': r''}); + var ioService = new MockIoService({'foo.html': r''}); var extractor = new HtmlExpressionExtractor([ new DirectiveInfo('foo', ['fooBar']) ]); extractor.crawl('/', ioService); - expect(extractor.expressions.toList()).toEqual(['ctrl.baz']); + expect(extractor.expressions.toList()).toEqual(['baz']); }); it('should ignore ng-repeat while extracting attribute expressions', () { var ioService = new MockIoService({ - 'foo.html': r''' -
- ''' + 'foo.html': r'
' }); var extractor = new HtmlExpressionExtractor([ @@ -93,7 +93,7 @@ void main() { extractor.crawl('/', ioService); // Basically we don't want to extract "foo in ctrl.bar". expect(extractor.expressions.toList()..sort(), - equals(['ctrl.bar'])); + equals(['bar'])); }); }); } diff --git a/test_e2e/animation_ng_repeat_spec.dart b/test_e2e/animation_ng_repeat_spec.dart index 51064160b..709ea1af7 100644 --- a/test_e2e/animation_ng_repeat_spec.dart +++ b/test_e2e/animation_ng_repeat_spec.dart @@ -3,7 +3,7 @@ part of angular.example.animation_spec; class NgRepeatAppState extends AppState { var addBtn = element(by.buttonText("Add Thing")); var removeBtn = element(by.buttonText("Remove Thing")); - var rows = element.all(by.repeater("outer in ctrl.items")); + var rows = element.all(by.repeater("outer in items")); var thingId = 0; // monotonically increasing. var things = []; diff --git a/test_e2e/todo_spec.dart b/test_e2e/todo_spec.dart index bc60086e9..67c7b9f88 100644 --- a/test_e2e/todo_spec.dart +++ b/test_e2e/todo_spec.dart @@ -4,16 +4,16 @@ import 'package:protractor/protractor_api.dart'; class AppState { - var items = element.all(by.repeater('item in todo.items')); - var remaining = element(by.binding('todo.remaining')); - var total = element(by.binding('todo.items.length')); + var items = element.all(by.repeater('item in items')); + var remaining = element(by.binding('remaining')); + var total = element(by.binding('items.length')); var markAllDoneBtn = element(by.buttonText("mark all done")); var archiveDoneBtn = element(by.buttonText("archive done")); var addBtn = element(by.buttonText("add")); var clearBtn = element(by.buttonText("clear")); - var newItemInput = element(by.model("todo.newItem.text")); + var newItemInput = element(by.model("newItem.text")); get newItemText => newItemInput.getAttribute('value'); todo(i) => items.get(i).getText(); @@ -44,7 +44,7 @@ class AppState { expect(clearBtn.isEnabled()).toEqual(text.length > 0); // input field and model value should contain the typed text. expect(newItemText).toEqual(text); - expect(newItemInput.evaluate('todo.newItem.text')).toEqual(text); + expect(newItemInput.evaluate('newItem.text')).toEqual(text); } }