diff --git a/lib/core_dom/content_tag.dart b/lib/core_dom/content_tag.dart new file mode 100644 index 000000000..72a542572 --- /dev/null +++ b/lib/core_dom/content_tag.dart @@ -0,0 +1,139 @@ +part of angular.core.dom_internal; + +abstract class _ContentStrategy { + void attach(); + void detach(); + void insert(Iterable nodes); +} + +/** + * A null implementation of the content tag that is used by Shadow DOM components. + * The distribution is handled by the browser, so Angular does nothing. + */ +class _ShadowDomContent implements _ContentStrategy { + void attach(){} + void detach(){} + void insert(Iterable nodes){} +} + +/** + * An implementation of the content tag that is used by transcluding components. + * It is used when the content tag is not a direct child of another component, + * and thus does not affect redistribution. + */ +class _RenderedTranscludingContent implements _ContentStrategy { + final SourceLightDom _sourceLightDom; + final Content _content; + + static final dom.ScriptElement _beginScriptTemplate = + new dom.ScriptElement()..type = "ng/content"; + + static final dom.ScriptElement _endScriptTemplate = + new dom.ScriptElement()..type = "ng/content"; + + dom.ScriptElement _beginScript; + dom.ScriptElement _endScript; + + _RenderedTranscludingContent(this._content, this._sourceLightDom); + + void attach(){ + _replaceContentElementWithScriptTags(); + _sourceLightDom.redistribute(); + } + + void detach(){ + _removeScriptTags(); + _sourceLightDom.redistribute(); + } + + void insert(Iterable nodes){ + final p = _endScript.parent; + if (p != null) p.insertAllBefore(nodes, _endScript); + } + + void _replaceContentElementWithScriptTags() { + _beginScript = _beginScriptTemplate.clone(true); + _endScript = _endScriptTemplate.clone(true); + + final el = _content.element; + el.parent.insertBefore(_beginScript, el); + el.parent.insertBefore(_endScript, el); + el.remove(); + } + + void _removeScriptTags() { + _removeNodesBetweenScriptTags(); + _beginScript.remove(); + _endScript.remove(); + } + + void _removeNodesBetweenScriptTags() { + final p = _beginScript.parent; + for (var next = _beginScript.nextNode; + next.nodeType != dom.Node.ELEMENT_NODE || next.attributes["ng/content"] != null; + next = _beginScript.nextNode) { + p.nodes.remove(next); + } + } +} + +/** + * An implementation of the content tag that is used by transcluding components. + * It is used when the content tag is a direct child of another component, + * and thus does not get rendered but only affect the distribution of its parent component. + */ +class _IntermediateTranscludingContent implements _ContentStrategy { + final SourceLightDom _sourceLightDom; + final DestinationLightDom _destinationLightDom; + final Content _content; + + _IntermediateTranscludingContent(this._content, this._sourceLightDom, this._destinationLightDom); + + void attach(){ + _sourceLightDom.redistribute(); + } + + void detach(){ + _sourceLightDom.redistribute(); + } + + void insert(Iterable nodes){ + _content.element.nodes = nodes; + _destinationLightDom.redistribute(); + } +} + +@Decorator(selector: 'content') +class Content implements AttachAware, DetachAware { + dom.Element element; + + @NgAttr('select') + String select; + + final SourceLightDom _sourceLightDom; + final DestinationLightDom _destinationLightDom; + var _strategy; + + Content(this.element, this._sourceLightDom, this._destinationLightDom, View view) { + view.addContent(this); + } + + void attach() => strategy.attach(); + void detach() => strategy.detach(); + void insert(Iterable nodes) => strategy.insert(nodes); + + _ContentStrategy get strategy { + if (_strategy == null) _strategy = _createContentStrategy(); + return _strategy; + } + + _ContentStrategy _createContentStrategy() { + if (_sourceLightDom == null) { + return new _ShadowDomContent(); + } else if (_destinationLightDom != null && _destinationLightDom.hasRoot(element)) { + return new _IntermediateTranscludingContent(this, _sourceLightDom, _destinationLightDom); + } else { + return new _RenderedTranscludingContent(this, _sourceLightDom); + } + } +} diff --git a/lib/core_dom/directive_injector.dart b/lib/core_dom/directive_injector.dart index 2bf20a499..9dbaa8823 100644 --- a/lib/core_dom/directive_injector.dart +++ b/lib/core_dom/directive_injector.dart @@ -14,14 +14,15 @@ import 'package:angular/core/module.dart' show Scope, RootScope; import 'package:angular/core/annotation.dart' show Visibility, DirectiveBinder; import 'package:angular/core_dom/module_internal.dart' show Animate, View, ViewFactory, BoundViewFactory, ViewPort, NodeAttrs, ElementProbe, - NgElement, ContentPort, TemplateLoader, ShadowRootEventHandler, EventHandler; + NgElement, DestinationLightDom, SourceLightDom, LightDom, TemplateLoader, ShadowRootEventHandler, EventHandler; var _TAG_GET = new UserTag('DirectiveInjector.get()'); var _TAG_INSTANTIATE = new UserTag('DirectiveInjector.instantiate()'); final DIRECTIVE_INJECTOR_KEY = new Key(DirectiveInjector); final COMPONENT_DIRECTIVE_INJECTOR_KEY = new Key(ComponentDirectiveInjector); -final CONTENT_PORT_KEY = new Key(ContentPort); +final DESTINATION_LIGHT_DOM_KEY = new Key(DestinationLightDom); +final SOURCE_LIGHT_DOM_KEY = new Key(SourceLightDom); final TEMPLATE_LOADER_KEY = new Key(TemplateLoader); final SHADOW_ROOT_KEY = new Key(ShadowRoot); @@ -31,26 +32,27 @@ const int VISIBILITY_LOCAL = -1; const int VISIBILITY_DIRECT_CHILD = -2; const int VISIBILITY_CHILDREN = -3; -const int UNDEFINED_ID = 0; -const int INJECTOR_KEY_ID = 1; -const int DIRECTIVE_INJECTOR_KEY_ID = 2; -const int NODE_KEY_ID = 3; -const int ELEMENT_KEY_ID = 4; -const int NODE_ATTRS_KEY_ID = 5; -const int ANIMATE_KEY_ID = 6; -const int SCOPE_KEY_ID = 7; -const int VIEW_KEY_ID = 8; -const int VIEW_PORT_KEY_ID = 9; -const int VIEW_FACTORY_KEY_ID = 10; -const int NG_ELEMENT_KEY_ID = 11; -const int BOUND_VIEW_FACTORY_KEY_ID = 12; -const int ELEMENT_PROBE_KEY_ID = 13; -const int TEMPLATE_LOADER_KEY_ID = 14; -const int SHADOW_ROOT_KEY_ID = 15; -const int CONTENT_PORT_KEY_ID = 16; -const int EVENT_HANDLER_KEY_ID = 17; -const int COMPONENT_DIRECTIVE_INJECTOR_KEY_ID = 18; -const int KEEP_ME_LAST = 19; +const int UNDEFINED_ID = 0; +const int INJECTOR_KEY_ID = 1; +const int DIRECTIVE_INJECTOR_KEY_ID = 2; +const int NODE_KEY_ID = 3; +const int ELEMENT_KEY_ID = 4; +const int NODE_ATTRS_KEY_ID = 5; +const int ANIMATE_KEY_ID = 6; +const int SCOPE_KEY_ID = 7; +const int VIEW_KEY_ID = 8; +const int VIEW_PORT_KEY_ID = 9; +const int VIEW_FACTORY_KEY_ID = 10; +const int NG_ELEMENT_KEY_ID = 11; +const int BOUND_VIEW_FACTORY_KEY_ID = 12; +const int ELEMENT_PROBE_KEY_ID = 13; +const int TEMPLATE_LOADER_KEY_ID = 14; +const int SHADOW_ROOT_KEY_ID = 15; +const int DESTINATION_LIGHT_DOM_KEY_ID = 16; +const int SOURCE_LIGHT_DOM_KEY_ID = 17; +const int EVENT_HANDLER_KEY_ID = 18; +const int COMPONENT_DIRECTIVE_INJECTOR_KEY_ID = 19; +const int KEEP_ME_LAST = 20; EventHandler eventHandler(DirectiveInjector di) => di._eventHandler; @@ -59,23 +61,24 @@ class DirectiveInjector implements DirectiveBinder { static initUID() { if (_isInit) return; _isInit = true; - INJECTOR_KEY.uid = INJECTOR_KEY_ID; - DIRECTIVE_INJECTOR_KEY.uid = DIRECTIVE_INJECTOR_KEY_ID; - NODE_KEY.uid = NODE_KEY_ID; - ELEMENT_KEY.uid = ELEMENT_KEY_ID; - NODE_ATTRS_KEY.uid = NODE_ATTRS_KEY_ID; - SCOPE_KEY.uid = SCOPE_KEY_ID; - VIEW_KEY.uid = VIEW_KEY_ID; - VIEW_PORT_KEY.uid = VIEW_PORT_KEY_ID; - VIEW_FACTORY_KEY.uid = VIEW_FACTORY_KEY_ID; - NG_ELEMENT_KEY.uid = NG_ELEMENT_KEY_ID; - BOUND_VIEW_FACTORY_KEY.uid = BOUND_VIEW_FACTORY_KEY_ID; - ELEMENT_PROBE_KEY.uid = ELEMENT_PROBE_KEY_ID; - TEMPLATE_LOADER_KEY.uid = TEMPLATE_LOADER_KEY_ID; - SHADOW_ROOT_KEY.uid = SHADOW_ROOT_KEY_ID; - CONTENT_PORT_KEY.uid = CONTENT_PORT_KEY_ID; - EVENT_HANDLER_KEY.uid = EVENT_HANDLER_KEY_ID; - ANIMATE_KEY.uid = ANIMATE_KEY_ID; + INJECTOR_KEY.uid = INJECTOR_KEY_ID; + DIRECTIVE_INJECTOR_KEY.uid = DIRECTIVE_INJECTOR_KEY_ID; + NODE_KEY.uid = NODE_KEY_ID; + ELEMENT_KEY.uid = ELEMENT_KEY_ID; + NODE_ATTRS_KEY.uid = NODE_ATTRS_KEY_ID; + SCOPE_KEY.uid = SCOPE_KEY_ID; + VIEW_KEY.uid = VIEW_KEY_ID; + VIEW_PORT_KEY.uid = VIEW_PORT_KEY_ID; + VIEW_FACTORY_KEY.uid = VIEW_FACTORY_KEY_ID; + NG_ELEMENT_KEY.uid = NG_ELEMENT_KEY_ID; + BOUND_VIEW_FACTORY_KEY.uid = BOUND_VIEW_FACTORY_KEY_ID; + ELEMENT_PROBE_KEY.uid = ELEMENT_PROBE_KEY_ID; + TEMPLATE_LOADER_KEY.uid = TEMPLATE_LOADER_KEY_ID; + SHADOW_ROOT_KEY.uid = SHADOW_ROOT_KEY_ID; + DESTINATION_LIGHT_DOM_KEY.uid = DESTINATION_LIGHT_DOM_KEY_ID; + SOURCE_LIGHT_DOM_KEY.uid = SOURCE_LIGHT_DOM_KEY_ID; + EVENT_HANDLER_KEY.uid = EVENT_HANDLER_KEY_ID; + ANIMATE_KEY.uid = ANIMATE_KEY_ID; COMPONENT_DIRECTIVE_INJECTOR_KEY.uid = COMPONENT_DIRECTIVE_INJECTOR_KEY_ID; for(var i = 1; i < KEEP_ME_LAST; i++) { if (_KEYS[i].uid != i) throw 'MISSORDERED KEYS ARRAY: ${_KEYS} at $i'; @@ -98,7 +101,8 @@ class DirectiveInjector implements DirectiveBinder { , ELEMENT_PROBE_KEY , TEMPLATE_LOADER_KEY , SHADOW_ROOT_KEY - , CONTENT_PORT_KEY + , DESTINATION_LIGHT_DOM_KEY + , SOURCE_LIGHT_DOM_KEY , EVENT_HANDLER_KEY , COMPONENT_DIRECTIVE_INJECTOR_KEY , KEEP_ME_LAST @@ -110,6 +114,7 @@ class DirectiveInjector implements DirectiveBinder { final NodeAttrs _nodeAttrs; final Animate _animate; final EventHandler _eventHandler; + LightDom lightDom; Scope scope; //TODO(misko): this should be final after we get rid of controller final View _view; @@ -282,24 +287,19 @@ class DirectiveInjector implements DirectiveBinder { Object _getById(int keyId) { switch(keyId) { - case INJECTOR_KEY_ID: return _appInjector; - case DIRECTIVE_INJECTOR_KEY_ID: return this; - case NODE_KEY_ID: return _node; - case ELEMENT_KEY_ID: return _node; - case NODE_ATTRS_KEY_ID: return _nodeAttrs; - case ANIMATE_KEY_ID: return _animate; - case SCOPE_KEY_ID: return scope; - case ELEMENT_PROBE_KEY_ID: return elementProbe; - case NG_ELEMENT_KEY_ID: return ngElement; - case EVENT_HANDLER_KEY_ID: return _eventHandler; - case CONTENT_PORT_KEY_ID: - var currentInjector = _parent; - while (currentInjector != null) { - if (currentInjector is ComponentDirectiveInjector) return currentInjector._contentPort; - currentInjector = currentInjector._parent; - } - return null; - case VIEW_KEY_ID: return _view; + case INJECTOR_KEY_ID: return _appInjector; + case DIRECTIVE_INJECTOR_KEY_ID: return this; + case NODE_KEY_ID: return _node; + case ELEMENT_KEY_ID: return _node; + case NODE_ATTRS_KEY_ID: return _nodeAttrs; + case ANIMATE_KEY_ID: return _animate; + case SCOPE_KEY_ID: return scope; + case ELEMENT_PROBE_KEY_ID: return elementProbe; + case NG_ELEMENT_KEY_ID: return ngElement; + case EVENT_HANDLER_KEY_ID: return _eventHandler; + case DESTINATION_LIGHT_DOM_KEY_ID: return _destLightDom; + case SOURCE_LIGHT_DOM_KEY_ID: return _sourceLightDom; + case VIEW_KEY_ID: return _view; default: new NoProviderError(_KEYS[keyId]); } } @@ -367,10 +367,20 @@ class DirectiveInjector implements DirectiveBinder { NgElement get ngElement { if (_ngElement == null) { - _ngElement = new NgElement(_node, scope, _animate); + _ngElement = new NgElement(_node, scope, _animate, _destLightDom); } return _ngElement; } + + SourceLightDom get _sourceLightDom { + var curr = _parent; + while (curr != null && curr is! ComponentDirectiveInjector) { + curr = curr.parent; + } + return curr == null || curr.parent == null ? null : curr.parent.lightDom; + } + + DestinationLightDom get _destLightDom => _parent == null ? null : _parent.lightDom; } class TemplateDirectiveInjector extends DirectiveInjector { @@ -388,29 +398,35 @@ class TemplateDirectiveInjector extends DirectiveInjector { switch(keyId) { case VIEW_FACTORY_KEY_ID: return _viewFactory; case VIEW_PORT_KEY_ID: return ((_viewPort) == null) ? - _viewPort = new ViewPort(this, scope, _node, _animate) : _viewPort; + _viewPort = _createViewPort() : _viewPort; case BOUND_VIEW_FACTORY_KEY_ID: return (_boundViewFactory == null) ? _boundViewFactory = _viewFactory.bind(_parent) : _boundViewFactory; default: return super._getById(keyId); } } + ViewPort _createViewPort() { + final viewPort = new ViewPort(this, scope, _node, _animate, _destLightDom); + if (_destLightDom != null) _destLightDom.addViewPort(viewPort); + return viewPort; + } + } class ComponentDirectiveInjector extends DirectiveInjector { final TemplateLoader _templateLoader; final ShadowRoot _shadowRoot; - final ContentPort _contentPort; ComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector, EventHandler eventHandler, Scope scope, - this._templateLoader, this._shadowRoot, this._contentPort, [View view]) + this._templateLoader, this._shadowRoot, LightDom lightDom, [View view]) : super(parent, appInjector, parent._node, parent._nodeAttrs, eventHandler, scope, parent._animate, view) { // A single component creates a ComponentDirectiveInjector and its DirectiveInjector parent, // so parent should never be null. assert(parent != null); + _parent.lightDom = lightDom; } Object _getById(int keyId) { @@ -435,4 +451,3 @@ class ComponentDirectiveInjector extends DirectiveInjector { // For example, a local directive is visible from its component injector children. num _getDepth(int visType) => super._getDepth(visType) + 1; } - diff --git a/lib/core_dom/shadowless_shadow_root.dart b/lib/core_dom/emulated_shadow_root.dart similarity index 97% rename from lib/core_dom/shadowless_shadow_root.dart rename to lib/core_dom/emulated_shadow_root.dart index ae4a87b62..b81f81f55 100644 --- a/lib/core_dom/shadowless_shadow_root.dart +++ b/lib/core_dom/emulated_shadow_root.dart @@ -1,7 +1,7 @@ part of angular.core.dom_internal; -class ShadowlessShadowRoot implements dom.ShadowRoot { +class EmulatedShadowRoot implements dom.ShadowRoot { dom.Element _element; - ShadowlessShadowRoot(this._element); + EmulatedShadowRoot(this._element); _notSupported() { throw new UnsupportedError("Not supported"); } dom.Element get activeElement => _notSupported(); dom.Element get host => _notSupported(); diff --git a/lib/core_dom/light_dom.dart b/lib/core_dom/light_dom.dart new file mode 100644 index 000000000..8d8c5dfd0 --- /dev/null +++ b/lib/core_dom/light_dom.dart @@ -0,0 +1,108 @@ +part of angular.core.dom_internal; + +@Injectable() +abstract class SourceLightDom { + void redistribute(); +} + +@Injectable() +abstract class DestinationLightDom { + void redistribute(); + void addViewPort(ViewPort viewPort); + bool hasRoot(dom.Element element); +} + +class LightDom implements SourceLightDom, DestinationLightDom { + final dom.Element _componentElement; + + final List _lightDomRootNodes = []; + final Map _ports = {}; + + final Scope _scope; + + View _shadowDomView; + + LightDom(this._componentElement, this._scope); + + void pullNodes() { + _lightDomRootNodes.addAll(_componentElement.nodes); + + // This is needed because _lightDomRootNodes can contains viewports, + // which cannot be detached. + final fakeRoot = new dom.DivElement(); + fakeRoot.nodes.addAll(_lightDomRootNodes); + + _componentElement.nodes = []; + } + + void set shadowDomView(View view) { + _shadowDomView = view; + _componentElement.nodes = view.nodes; + } + + void addViewPort(ViewPort viewPort) { + _ports[viewPort.placeholder] = viewPort; + redistribute(); + } + + //TODO: vsavkin Add dirty flag after implementing view-scoped dom writes. + void redistribute() { + _scope.rootScope.domWrite(() { + redistributeNodes(_sortedContents, _expandedLightDomRootNodes); + }); + } + + bool hasRoot(dom.Element element) => _lightDomRootNodes.contains(element); + + List get _sortedContents { + final res = []; + _collectAllContentTags(_shadowDomView, res); + return res; + } + + void _collectAllContentTags(item, List acc) { + if (item is Content) { + acc.add(item); + + } else if (item is View) { + for (final i in item.insertionPoints) { + _collectAllContentTags(i, acc); + } + + } else if (item is ViewPort) { + for (final i in item.views) { + _collectAllContentTags(i, acc); + } + } + } + + List get _expandedLightDomRootNodes { + final list = []; + for(final root in _lightDomRootNodes) { + if (_ports.containsKey(root)) { + list.addAll(_ports[root].nodes); + } else if (root is dom.ContentElement) { + list.addAll(root.nodes); + } else { + list.add(root); + } + } + return list; + } +} + +void redistributeNodes(Iterable contents, List nodes) { + for (final content in contents) { + final select = content.select; + matchSelector(n) => n.nodeType == dom.Node.ELEMENT_NODE && n.matches(select); + + if (select == null) { + content.insert(nodes); + nodes.clear(); + } else { + final matchingNodes = nodes.where(matchSelector); + content.insert(matchingNodes); + nodes.removeWhere(matchSelector); + } + } +} \ No newline at end of file diff --git a/lib/core_dom/module_internal.dart b/lib/core_dom/module_internal.dart index a6138089a..f0cd7e385 100644 --- a/lib/core_dom/module_internal.dart +++ b/lib/core_dom/module_internal.dart @@ -43,9 +43,11 @@ part 'ng_element.dart'; part 'node_cursor.dart'; part 'selector.dart'; part 'shadow_dom_component_factory.dart'; -part 'shadowless_shadow_root.dart'; +part 'emulated_shadow_root.dart'; part 'template_cache.dart'; part 'transcluding_component_factory.dart'; +part 'light_dom.dart'; +part 'content_tag.dart'; part 'tree_sanitizer.dart'; part 'view.dart'; part 'view_factory.dart'; @@ -73,7 +75,8 @@ class CoreDomModule extends Module { bind(ShadowDomComponentFactory); bind(TranscludingComponentFactory); bind(Content); - bind(ContentPort, toValue: null); + bind(DestinationLightDom, toValue: null); + bind(SourceLightDom, toValue: null); bind(ComponentCssRewriter); bind(WebPlatform); diff --git a/lib/core_dom/ng_element.dart b/lib/core_dom/ng_element.dart index 08de548d2..29f7b9992 100644 --- a/lib/core_dom/ng_element.dart +++ b/lib/core_dom/ng_element.dart @@ -7,13 +7,14 @@ class NgElement { final dom.Element node; final Scope _scope; final Animate _animate; + final DestinationLightDom _lightDom; final _classesToUpdate = new HashMap(); final _attributesToUpdate = new HashMap(); bool _writeScheduled = false; - NgElement(this.node, this._scope, this._animate); + NgElement(this.node, this._scope, this._animate, [this._lightDom]); void addClass(String className) { _scheduleDomWrite(); @@ -41,6 +42,7 @@ class NgElement { _writeScheduled = true; _scope.rootScope.domWrite(() { _writeToDom(); + _notifyLightDom(); _writeScheduled = false; }); } @@ -64,4 +66,8 @@ class NgElement { }); _attributesToUpdate.clear(); } + + void _notifyLightDom() { + if (_lightDom != null) _lightDom.redistribute(); + } } diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart index 11cc2097b..f34e27bd0 100644 --- a/lib/core_dom/shadow_dom_component_factory.dart +++ b/lib/core_dom/shadow_dom_component_factory.dart @@ -62,7 +62,6 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { final DirectiveRef _ref; final DirectiveMap _directives; final Injector _injector; - final DirectiveInjector _directive_injector; Component get _component => _ref.annotation as Component; diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart index fe2029223..0c5c0b1aa 100644 --- a/lib/core_dom/transcluding_component_factory.dart +++ b/lib/core_dom/transcluding_component_factory.dart @@ -1,73 +1,5 @@ part of angular.core.dom_internal; -@Decorator( - selector: 'content') -class Content implements AttachAware, DetachAware { - final ContentPort _port; - final dom.Element _element; - dom.Comment _beginComment; - Content(this._port, this._element); - - void attach() { - if (_port == null) return; - _beginComment = _port.content(_element); - } - - void detach() { - if (_port == null) return; - _port.detachContent(_beginComment); - } -} - -class ContentPort { - dom.Element _element; - var _childNodes = []; - - ContentPort(this._element); - - void pullNodes() { - _childNodes.addAll(_element.nodes); - _element.nodes = []; - } - - content(dom.Element elt) { - var hash = elt.hashCode; - var beginComment = null; - - if (_childNodes.isNotEmpty) { - beginComment = new dom.Comment("content $hash"); - elt.parent.insertBefore(beginComment, elt); - elt.parent.insertAllBefore(_childNodes, elt); - elt.parent.insertBefore(new dom.Comment("end-content $hash"), elt); - _childNodes = []; - } - - elt.remove(); - return beginComment; - } - - void detachContent(dom.Comment _beginComment) { - // Search for endComment and extract everything in between. - // TODO optimize -- there may be a better way of pulling out nodes. - - if (_beginComment == null) { - return; - } - - var endCommentText = "end-${_beginComment.text}"; - - var next; - for (next = _beginComment.nextNode; - next.nodeType != dom.Node.COMMENT_NODE || next.text != endCommentText; - next = _beginComment.nextNode) { - _childNodes.add(next); - next.remove(); - } - assert(next.nodeType == dom.Node.COMMENT_NODE && next.text == endCommentText); - next.remove(); - } -} - @Injectable() class TranscludingComponentFactory implements ComponentFactory { @@ -115,37 +47,36 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { var childInjectorCompleter; // Used if the ViewFuture is available before the childInjector. var component = _component; - var contentPort = new ContentPort(element); + var lightDom = new LightDom(element, scope); // Append the component's template as children var elementFuture; if (_viewFuture != null) { elementFuture = _viewFuture.then((ViewFactory viewFactory) { - contentPort.pullNodes(); + lightDom.pullNodes(); + if (childInjector != null) { - element.nodes.addAll( - viewFactory.call(childInjector.scope, childInjector).nodes); + lightDom.shadowDomView = viewFactory.call(childInjector.scope, childInjector); return element; } else { childInjectorCompleter = new async.Completer(); return childInjectorCompleter.future.then((childInjector) { - element.nodes.addAll( - viewFactory.call(childInjector.scope, childInjector).nodes); + lightDom.shadowDomView = viewFactory.call(childInjector.scope, childInjector); return element; }); } }); } else { - elementFuture = new async.Future.microtask(() => contentPort.pullNodes()); + elementFuture = new async.Future.microtask(() => lightDom.pullNodes()); } TemplateLoader templateLoader = new TemplateLoader(elementFuture); Scope shadowScope = scope.createChild(new HashMap()); childInjector = new ComponentDirectiveInjector(injector, this._injector, - eventHandler, shadowScope, templateLoader, new ShadowlessShadowRoot(element), - contentPort, view); + eventHandler, shadowScope, templateLoader, new EmulatedShadowRoot(element), lightDom, view); + childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility); if (childInjectorCompleter != null) { diff --git a/lib/core_dom/view.dart b/lib/core_dom/view.dart index c034ef458..614488a68 100644 --- a/lib/core_dom/view.dart +++ b/lib/core_dom/view.dart @@ -14,8 +14,17 @@ part of angular.core.dom_internal; class View { final Scope scope; final List nodes; + final List insertionPoints = []; View(this.nodes, this.scope); + + void addViewPort(ViewPort viewPort) { + insertionPoints.add(viewPort); + } + + void addContent(Content content) { + insertionPoints.add(content); + } } /** @@ -27,9 +36,15 @@ class ViewPort { final Scope scope; final dom.Node placeholder; final Animate _animate; - final _views = []; + final DestinationLightDom _lightDom; + final View _parentView; + final views = []; - ViewPort(this.directiveInjector, this.scope, this.placeholder, this._animate); + ViewPort(DirectiveInjector directiveInjector, this.scope, this.placeholder, this._animate, [this._lightDom, View parentView]) + : directiveInjector = directiveInjector, + _parentView = parentView != null ? parentView : directiveInjector.getByKey(VIEW_KEY) { + _parentView.addViewPort(this); + } View insertNew(ViewFactory viewFactory, { View insertAfter, Scope viewScope}) { if (viewScope == null) viewScope = scope.createChild(new PrototypeMap(scope.context)); @@ -42,32 +57,47 @@ class ViewPort { dom.Node previousNode = _lastNode(insertAfter); _viewsInsertAfter(view, insertAfter); _animate.insert(view.nodes, placeholder.parentNode, insertBefore: previousNode.nextNode); + _notifyLightDom(); }); return view; } View remove(View view) { view.scope.destroy(); - _views.remove(view); + views.remove(view); scope.rootScope.domWrite(() { _animate.remove(view.nodes); + _notifyLightDom(); }); return view; } View move(View view, { View moveAfter }) { dom.Node previousNode = _lastNode(moveAfter); - _views.remove(view); + views.remove(view); _viewsInsertAfter(view, moveAfter); scope.rootScope.domWrite(() { _animate.move(view.nodes, placeholder.parentNode, insertBefore: previousNode.nextNode); + _notifyLightDom(); }); return view; } void _viewsInsertAfter(View view, View insertAfter) { - int index = insertAfter == null ? 0 : _views.indexOf(insertAfter) + 1; - _views.insert(index, view); + int index = insertAfter == null ? 0 : views.indexOf(insertAfter) + 1; + views.insert(index, view); + } + + List get nodes { + final r = []; + for(final v in views) { + r.addAll(v.nodes); + } + return r; + } + + void _notifyLightDom() { + if (_lightDom != null) _lightDom.redistribute(); } dom.Node _lastNode(View insertAfter) => diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index 64c88dd50..d26879793 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -57,7 +57,7 @@ if [[ $TESTS == "dart2js" ]]; then cd $NGDART_BASE_DIR/example checkSize build/web/animation.dart.js 208021 checkSize build/web/bouncing_balls.dart.js 202325 - checkSize build/web/hello_world.dart.js 199919 + checkSize build/web/hello_world.dart.js 210000 checkSize build/web/todo.dart.js 203121 if ((SIZE_TOO_BIG_COUNT > 0)); then exit 1 diff --git a/test/_specs.dart b/test/_specs.dart index f6c024973..ead725598 100644 --- a/test/_specs.dart +++ b/test/_specs.dart @@ -5,6 +5,7 @@ import 'dart:html' hide Animation; import 'package:angular/angular.dart'; import 'package:angular/mock/module.dart'; +import 'package:unittest/unittest.dart' as unit; import 'package:guinness/guinness_html.dart' as gns; export 'dart:html' hide Animation; @@ -54,6 +55,8 @@ class Expect extends gns.Expect { void toBePristine() => _expect(actual.pristine && !actual.dirty, true, reason: 'Form is dirty'); + void toHaveText(String text) => _expect(actual, new _TextMatcher(text)); + Function get _expect => gns.guinness.matchers.expect; } @@ -68,6 +71,33 @@ class NotExpect extends gns.NotExpect { } +class _TextMatcher extends unit.Matcher { + final String expected; + + _TextMatcher(this.expected); + + unit.Description describe(unit.Description description) => + description..replace("element matching: ${expected}"); + + unit.Description describeMismatch(actual, unit.Description mismatchDescription, + Map matchState, bool verbose) => + mismatchDescription..add(_elementText(actual)); + + bool matches(actual, Map matchState) => + _elementText(actual) == expected; +} + +String _elementText(n) { + hasShadowRoot(n) => n is Element && n.shadowRoot != null; + if (n is Iterable) return n.map((nn) => _elementText(nn)).join(""); + if (n is Comment) return ''; + if (n is ContentElement) return _elementText(n.getDistributedNodes()); + if (hasShadowRoot(n)) return _elementText(n.shadowRoot.nodes); + if (n.nodes == null || n.nodes.isEmpty) return n.text; + return _elementText(n.nodes); +} + + Function _injectify(Function fn) { // The function does two things: // First: if the it() passed a function, we wrap it in diff --git a/test/core_dom/compiler_spec.dart b/test/core_dom/compiler_spec.dart index 1882a2ea2..5ad29d9ee 100644 --- a/test/core_dom/compiler_spec.dart +++ b/test/core_dom/compiler_spec.dart @@ -35,29 +35,6 @@ forAllCompilersAndComponentFactories(fn) { } void main() { - withElementProbeConfig((compilerType) => - describe('TranscludingComponentFactory', () { - TestBed _; - - beforeEachModule((Module m) { - return m - ..bind(ComponentFactory, toImplementation: TranscludingComponentFactory) - ..bind(SimpleComponent); - }); - - beforeEach((TestBed tb) => _ = tb); - - it('should correctly detach transcluded content when scope destroyed', async(() { - var scope = _.rootScope.createChild({}); - var element = _.compile(r'
trans
', scope: scope); - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('INNER(trans)'); - scope.destroy(); - expect(element).toHaveText('INNER()'); - })); - })); - forAllCompilersAndComponentFactories((compilerType) => describe('dte.compiler', () { TestBed _; @@ -319,70 +296,184 @@ void main() { ..bind(AttachDetachComponent) ..bind(SimpleAttachComponent) ..bind(SimpleComponent) - ..bind(SometimesComponent) + ..bind(MultipleContentTagsComponent) + ..bind(ConditionalContentComponent) ..bind(ExprAttrComponent) ..bind(LogElementComponent) ..bind(SayHelloFormatter) + ..bind(OuterComponent) + ..bind(InnerComponent) + ..bind(InnerInnerComponent) + ..bind(OuterWithDivComponent) ..bind(OneTimeDecorator) ..bind(OnceInside) ..bind(OuterShadowless) ..bind(InnerShadowy); }); - it('should select on element', async(() { - var element = _.compile(r'
'); - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('INNER()'); - })); + describe("distribution", () { + it('should safely remove components that have no content', async(() { + _.rootScope.context['flag'] = true; + _.compile('
'); + microLeap(); _.rootScope.apply(); + _.rootScope.context['flag'] = false; + microLeap(); _.rootScope.apply(); + })); - it('should tranclude correctly', async(() { - var element = _.compile(r'
trans
'); - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('INNER(trans)'); - })); + it('should support multiple content tags', async(() { + var element = _.compile(r'
' + '' + '
B
' + '
C
' + '
A
' + '
' + '
'); - it('should tranclude if content was not present initially', async(() { - var element = _.compile(r'
And jump
'); - document.body.append(element); - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('And '); + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('(A, BC)'); + })); - _.rootScope.context['sometimes'] = true; - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('And jump'); - })); + it('should redistribute only direct children', async(() { + var element = _.compile(r'
' + '' + '
B
A
' + '
C
' + '
' + '
'); + + microLeap(); + _.rootScope.apply(); - it('should redistribute content when the content tag disappears', async(() { - var element = _.compile(r'
And jump
'); - document.body.append(element); + expect(element).toHaveText('(, BAC)'); + })); - _.rootScope.context['sometimes'] = true; - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('And jump'); + it("should redistribute when the light dom changes", async(() { + var element = _.compile(r'
' + '' + '
A
' + '
B
' + '
' + '
'); + document.body.append(element); - _.rootScope.context['sometimes'] = false; - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('And '); + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('(, B)'); - _.rootScope.context['sometimes'] = true; - microLeap(); - _.rootScope.apply(); - expect(element).toHaveText('And jump'); - })); + _.rootScope.context['showLeft'] = true; + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('(A, B)'); + + _.rootScope.context['showLeft'] = false; + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('(, B)'); + })); + + it("should redistribute when a class has been added or removed", async(() { + var element = _.compile(r'
' + '' + '
A
' + '
B
' + '
' + '
'); + document.body.append(element); + + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('(, AB)'); + + _.rootScope.context['showLeft'] = true; + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('(A, B)'); + + _.rootScope.context['showLeft'] = false; + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('(, AB)'); + })); + + it('should redistribute when the shadow dom changes', async(() { + if (compilerType == 'no-elementProbe') return; + + var element = _.compile(r'
' + '' + '
A
' + '
B
' + '
C
' + '
' + '
'); + + final scope = _shadowScope(element.children[0]); + + microLeap(); + scope.apply(); + expect(element).toHaveText('(, ABC)'); + + scope.context['showLeft'] = true; + microLeap(); + scope.apply(); + expect(element).toHaveText('(A, BC)'); + + scope.context['showLeft'] = false; + microLeap(); + scope.apply(); + expect(element).toHaveText('(, ABC)'); + })); + + it("should support nested compoonents", async((){ + var element = _.compile(r'
' + '' + '
A
' + '
B
' + '
C
' + '
' + '
'); + document.body.append(element); + + microLeap(); + _.rootScope.apply(); + + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('OUTER(INNER(BC))'); + + _.rootScope.context["showLeft"] = true; + microLeap(); + _.rootScope.apply(); + + expect(element).toHaveText('OUTER(INNER(ABC))'); + })); + + it("should support nesting with content being direct child of a nested component", async((){ + // platform.js does not emulate this behavior, so the test fails on firefox. + // Remove the if when this is fixed. + if (compilerType != "transcluding") return; + + var element = _.compile(r'
' + '' + '
A
' + '
B
' + '
C
' + '
' + '
'); + document.body.append(element); + + microLeap(); + _.rootScope.apply(); + expect(element).toHaveText('OUTER(INNER(INNERINNER(,ABC)))'); + + _.rootScope.context["showLeft"] = true; + microLeap(); + _.rootScope.apply(); + + expect(element).toHaveText('OUTER(INNER(INNERINNER(A,BC)))'); + })); + }); - it('should safely remove transcluding components that transclude no content', async(() { - _.rootScope.context['flag'] = true; - _.compile('
'); - microLeap(); _.rootScope.apply(); - _.rootScope.context['flag'] = false; - microLeap(); _.rootScope.apply(); - })); it('should store ElementProbe with Elements', async(() { if (compilerType == 'no-elementProbe') return; @@ -792,7 +883,7 @@ void main() { describe('useShadowDom option', () { beforeEachModule((Module m) { m.bind(ShadowyComponent); - m.bind(ShadowlessComponent); + m.bind(TranscludingComponent); }); it('should create shadowy components', async((Logger log) { @@ -801,9 +892,9 @@ void main() { expect(_.rootElement.shadowRoot).toBeNotNull(); })); - it('should create shadowless components', async((Logger log) { - _.compile(''); - expect(log).toEqual(['shadowless']); + it('should create transcluding components', async((Logger log) { + _.compile(''); + expect(log).toEqual(['transcluding']); expect(_.rootElement.shadowRoot).toBeNull(); })); @@ -831,7 +922,7 @@ void main() { beforeEach((Expando _expando) => expando = _expando); - ['shadowy', 'shadowless'].forEach((selector) { + ['shadowy', 'transcluding'].forEach((selector) { it('should release expando when a node is freed ($selector)', async(() { _.rootScope.context['flag'] = true; _.compile('
<$selector>x
'); @@ -1207,7 +1298,7 @@ class PublishModuleAttrDirective implements PublishModuleDirectiveSuperType { @Component( selector: 'simple', - template: r'{{name}}(SHADOW-CONTENT)') + template: r'{{name}}()') class SimpleComponent { Scope scope; SimpleComponent(Scope this.scope) { @@ -1216,13 +1307,11 @@ class SimpleComponent { } @Component( - selector: 'simple2', - template: r'{{name}}(SHADOW-CONTENT)') -class Simple2Component { - Scope scope; - Simple2Component(Scope this.scope) { - scope.context['name'] = 'INNER'; - } + selector: 'multiple-content-tags', + template: r'(, )') +class MultipleContentTagsComponent { + final Scope scope; + MultipleContentTagsComponent(this.scope); } @Component( @@ -1237,23 +1326,22 @@ class ShadowyComponent { } @Component( - selector: 'shadowless', + selector: 'transcluding', template: r'Without shadow DOM', useShadowDom: false ) -class ShadowlessComponent { - ShadowlessComponent(Logger log) { - log('shadowless'); +class TranscludingComponent { + TranscludingComponent(Logger log) { + log('transcluding'); } } @Component( - selector: 'sometimes', - template: r'
', - publishAs: 'ctrl') -class SometimesComponent { - @NgTwoWay('sometimes') - var sometimes; + selector: 'conditional-content', + template: r'(
, )') +class ConditionalContentComponent { + Scope scope; + ConditionalContentComponent(this.scope); } @Component( @@ -1573,3 +1661,47 @@ class InjectorDependentComponent { expect(cdi.parent).toBe(i); } } + + +@Component( + selector: 'outer-with-div', + template: 'OUTER(
)' +) +class OuterWithDivComponent { + final Scope scope; + OuterWithDivComponent(this.scope); +} + +@Component( + selector: 'outer', + template: 'OUTER()' +) +class OuterComponent { + final Scope scope; + OuterComponent(this.scope); +} + +@Component( + selector: 'inner', + template: 'INNER()' +) +class InnerComponent { + final Scope scope; + InnerComponent(this.scope); +} + +@Component( + selector: 'innerinner', + template: 'INNERINNER(,)' +) +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/directive_injector_spec.dart b/test/core_dom/directive_injector_spec.dart index 802fc5d84..39aff71f0 100644 --- a/test/core_dom/directive_injector_spec.dart +++ b/test/core_dom/directive_injector_spec.dart @@ -79,6 +79,34 @@ void main() { expect(() => injector.get(_TypeA)).toThrow('No provider found for _TypeA'); }); + describe("returning SourceLightDom", () { + it('should return the light dom of the closest host element', () { + final lightDom = new LightDom(null, null); + + final componentInjector = new ComponentDirectiveInjector( + injector, null, null, null, null, null, lightDom, null); + final childInjector = new DirectiveInjector(componentInjector, null, null, null, null, null, null, null); + final grandChildInjector = new DirectiveInjector(childInjector, null, null, null, null, null,null, null); + + expect(grandChildInjector.getByKey(SOURCE_LIGHT_DOM_KEY)).toBe(lightDom); + }); + + it('should return null otherwise', () { + expect(injector.getByKey(SOURCE_LIGHT_DOM_KEY)).toBe(null); + }); + }); + + describe("returning DestinationLightDom", () { + it('should return the light dom of the parent injector', () { + final lightDom = new LightDom(null, null); + injector.lightDom = lightDom; + + final childInjector = new DirectiveInjector(injector, null, null, null, null, null, null, null); + + expect(childInjector.getByKey(DESTINATION_LIGHT_DOM_KEY)).toBe(lightDom); + }); + }); + describe('Visibility', () { DirectiveInjector childInjector; DirectiveInjector leafInjector; diff --git a/test/core_dom/light_dom_spec.dart b/test/core_dom/light_dom_spec.dart new file mode 100644 index 000000000..1ff608d62 --- /dev/null +++ b/test/core_dom/light_dom_spec.dart @@ -0,0 +1,61 @@ +library light_dom_spec; + +import 'dart:html' as dom; +import '../_specs.dart'; +import 'package:angular/core/module.dart'; + +class DummyContent extends Mock implements Content { + String select; + List nodes = []; + DummyContent(this.select); + insert(nodes) => this.nodes = new List.from(nodes); +} + +void main() { + describe("redistribute", () { + TestBed _; + final dummyElement = new dom.DivElement(); + + beforeEach((TestBed tb) => _ = tb); + + it("should redistribute nodes between two content tags", () { + var nodes = es( + '
a1
' + '
b
' + '
a2
' + ); + final contentClassA = new DummyContent('.a'); + final contentClassB = new DummyContent('.b'); + + redistributeNodes([contentClassA, contentClassB], nodes); + + expect(contentClassA.nodes).toHaveText('a1a2'); + expect(contentClassB.nodes).toHaveText('b'); + }); + + it("should handle text nodes", () { + var nodes = es('
a
some text'); + final contentClassA = new DummyContent('.a'); + + redistributeNodes([contentClassA], nodes); + + expect(contentClassA.nodes).toHaveText('a'); + }); + + it("should support wildcards", () { + var nodes = es( + '
a1
' + '
b
' + 'text' + '
a2
' + ); + final contentClassA = new DummyContent('.a'); + final contentWildcard = new DummyContent(null); + + redistributeNodes([contentClassA, contentWildcard], nodes); + + expect(contentClassA.nodes).toHaveText('a1a2'); + expect(contentWildcard.nodes).toHaveText('btext'); + }); + }); +} diff --git a/test/core_dom/ng_element_spec.dart b/test/core_dom/ng_element_spec.dart index 40aa76247..b2022c506 100644 --- a/test/core_dom/ng_element_spec.dart +++ b/test/core_dom/ng_element_spec.dart @@ -2,6 +2,8 @@ library ng_element_spec; import '../_specs.dart'; +class _MockLightDom extends Mock implements DestinationLightDom {} + void main() { describe('ngElement', () { @@ -144,4 +146,19 @@ void main() { }); }); + + describe('light dom notification', () { + it('should notify light dom on dom write', + (RootScope scope, Animate animate) { + + var element = e('
'); + var lightDom = new _MockLightDom(); + var ngElement = new NgElement(element, scope, animate, lightDom); + + ngElement.setAttribute('id', 'foo'); + scope.apply(); + + lightDom.getLogs(callsTo('redistribute')).verify(happenedOnce); + }); + }); } diff --git a/test/core_dom/view_spec.dart b/test/core_dom/view_spec.dart index 7cb0498b9..1c2a27f9e 100644 --- a/test/core_dom/view_spec.dart +++ b/test/core_dom/view_spec.dart @@ -60,33 +60,42 @@ class BFormatter { call(value) => value; } +class _MockLightDom extends Mock implements DestinationLightDom {} main() { describe('View', () { - ViewPort viewPort; Element rootElement; + + var expando = new Expando(); + View a, b; var viewCache; - beforeEach(() { + ViewPort createViewPort({Injector injector, DestinationLightDom lightDom}) { + final scope = injector.get(Scope); + final view = new View([], scope); + final di =new DirectiveInjector(null, injector, null, null, null, null, null, view); + return new ViewPort(di, scope, rootElement.childNodes[0], injector.get(Animate), lightDom); + } + + View createView(Injector injector, String html) { + final scope = injector.get(Scope); + final c = injector.get(Compiler); + return c(es(html), injector.get(DirectiveMap))(scope, null); + } + + beforeEach((Injector injector, Profiler perf) { rootElement = e('
'); + rootElement.innerHtml = ''; + + a = createView(injector, "Aa"); + b = createView(injector, "Bb"); }); describe('mutation', () { - var a, b; - - View createView(Injector injector, String html) { - final scope = injector.get(Scope); - final c = injector.get(Compiler); - return c(es(html), injector.get(DirectiveMap))(scope, null); - } - - beforeEach((Injector injector, Profiler perf) { - rootElement.innerHtml = ''; - var scope = injector.get(Scope); - viewPort = new ViewPort(null, scope, rootElement.childNodes[0], - injector.get(Animate)); - a = createView(injector, "Aa"); - b = createView(injector, "Bb"); + ViewPort viewPort; + + beforeEach((Injector injector) { + viewPort = createViewPort(injector: injector); }); @@ -203,8 +212,73 @@ main() { expect(rootElement).toHaveHtml('BbAa'); }); }); + + + describe("light dom notification", () { + ViewPort viewPort; + _MockLightDom lightDom; + Scope scope; + + beforeEach((Injector injector) { + lightDom = new _MockLightDom(); + + viewPort = createViewPort(injector: injector, lightDom: lightDom); + }); + + it('should notify light dom on insert', (RootScope scope) { + viewPort.insert(a); + scope.flush(); + + lightDom.getLogs(callsTo('redistribute')).verify(happenedOnce); + }); + + it('should notify light dom on remove', (RootScope scope) { + viewPort.insert(a); + scope.flush(); + lightDom.clearLogs(); + + viewPort.remove(a); + scope.flush(); + + lightDom.getLogs(callsTo('redistribute')).verify(happenedOnce); + }); + + it('should notify light dom on move', (RootScope scope) { + viewPort.insert(a); + viewPort.insert(b, insertAfter: a); + scope.flush(); + lightDom.clearLogs(); + + viewPort.move(a, moveAfter: b); + scope.flush(); + + lightDom.getLogs(callsTo('redistribute')).verify(happenedOnce); + }); + }); }); + describe("nodes", () { + ViewPort viewPort; + + beforeEach((Injector injector) { + viewPort = createViewPort(injector: injector); + }); + + it("should return all the nodes from all the views", (RootScope scope) { + viewPort.insert(a); + viewPort.insert(b, insertAfter: a); + + scope.flush(); + + expect(viewPort.nodes).toHaveText("AaBb"); + }); + + it("should return an empty list when no views", () { + expect(viewPort.nodes).toEqual([]); + }); + }); + + describe('deferred', () { it('should load directives/formatters from the child injector', (RootScope scope) { diff --git a/test/core_dom/web_platform_spec.dart b/test/core_dom/web_platform_spec.dart index 5984f06a2..17c96f68c 100644 --- a/test/core_dom/web_platform_spec.dart +++ b/test/core_dom/web_platform_spec.dart @@ -9,10 +9,10 @@ main() { beforeEachModule((Module module) { module - ..bind(WebPlatformTestComponent) - ..bind(WebPlatformTestComponentWithAttribute) - ..bind(InnerComponent) - ..bind(OuterComponent) + ..bind(_WebPlatformTestComponent) + ..bind(_WebPlatformTestComponentWithAttribute) + ..bind(_InnerComponent) + ..bind(_OuterComponent) ..bind(WebPlatform, toValue: new WebPlatform()); }); @@ -192,7 +192,7 @@ main() { publishAs: "ctrl", templateUrl: "template.html", cssUrl: "style.css") -class WebPlatformTestComponent { +class _WebPlatformTestComponent { } @Component( @@ -200,7 +200,7 @@ class WebPlatformTestComponent { publishAs: "ctrl", templateUrl: "template.html", cssUrl: "style.css") -class WebPlatformTestComponentWithAttribute { +class _WebPlatformTestComponentWithAttribute { } @Component( @@ -208,7 +208,7 @@ class WebPlatformTestComponentWithAttribute { publishAs: "ctrl", templateUrl: "inner-html.html", cssUrl: "inner-style.css") -class InnerComponent { +class _InnerComponent { } @Component( @@ -216,8 +216,5 @@ class InnerComponent { publishAs: "ctrl", templateUrl: "outer-html.html", cssUrl: "outer-style.css") -class OuterComponent { +class _OuterComponent { } - - -