| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| library created_callback_test; | |
| import 'dart:html'; | |
| import 'dart:js' as js; | |
| import 'package:async_helper/async_minitest.dart'; | |
| import 'utils.dart'; | |
| class A extends HtmlElement { | |
| static final tag = 'x-a'; | |
| factory A() => new Element.tag(tag); | |
| A.created() : super.created() { | |
| createdInvocations++; | |
| } | |
| static int createdInvocations = 0; | |
| } | |
| class B extends HtmlElement { | |
| static final tag = 'x-b'; | |
| factory B() => new Element.tag(tag); | |
| B.created() : super.created(); | |
| } | |
| class C extends HtmlElement { | |
| static final tag = 'x-c'; | |
| factory C() => new Element.tag(tag); | |
| C.created() : super.created() { | |
| createdInvocations++; | |
| if (this.id != 'u') { | |
| return; | |
| } | |
| var t = div.querySelector('#t'); | |
| var v = div.querySelector('#v'); | |
| var w = div.querySelector('#w'); | |
| expect(querySelector('x-b:not(:unresolved)'), this); | |
| expect(querySelectorAll(':unresolved'), [v, w]); | |
| // As per: | |
| // http://www.w3.org/TR/2013/WD-custom-elements-20130514/#serializing-and-parsing | |
| // creation order is t, u, v, w (postorder). | |
| expect(t is C, isTrue); | |
| // Note, this is different from JavaScript where this would be false. | |
| expect(v is C, isTrue); | |
| } | |
| static int createdInvocations = 0; | |
| static var div; | |
| } | |
| main() async { | |
| // Adapted from Blink's | |
| // fast/dom/custom/created-callback test. | |
| await customElementsReady; | |
| document.registerElement2(B.tag, {'prototype': B}); | |
| document.registerElement2(C.tag, {'prototype': C}); | |
| ErrorConstructorElement.register(); | |
| test('transfer created callback', () { | |
| document.registerElement2(A.tag, {'prototype': A as dynamic}); | |
| var x = new A(); | |
| expect(A.createdInvocations, 1); | |
| }); | |
| test('unresolved and created callback timing', () { | |
| var div = new DivElement(); | |
| C.div = div; | |
| div.setInnerHtml(""" | |
| <x-c id="t"></x-c> | |
| <x-b id="u"></x-b> | |
| <x-c id="v"></x-c> | |
| <x-b id="w"></x-b> | |
| """, treeSanitizer: NodeTreeSanitizer.trusted); | |
| upgradeCustomElements(div); | |
| expect(C.createdInvocations, 2); | |
| expect(div.querySelector('#w') is B, isTrue); | |
| }); | |
| test('nesting of constructors', NestedElement.test); | |
| test('access while upgrading gets unupgraded element', | |
| AccessWhileUpgradingElement.test); | |
| test('cannot call created constructor', () { | |
| expect(() { | |
| new B.created(); | |
| }, throws); | |
| }); | |
| test('cannot register without created', () { | |
| expect(() { | |
| document.registerElement2( | |
| MissingCreatedElement.tag, {'prototype': MissingCreatedElement}); | |
| }, throws); | |
| }); | |
| test('throw on createElement does not upgrade', () { | |
| ErrorConstructorElement.callCount = 0; | |
| var e; | |
| expectGlobalError(() { | |
| e = new Element.tag(ErrorConstructorElement.tag); | |
| }); | |
| expect(ErrorConstructorElement.callCount, 1); | |
| expect(e is HtmlElement, isTrue); | |
| expect(e is ErrorConstructorElement, isFalse); | |
| var dummy = new DivElement(); | |
| dummy.append(e); | |
| e = dummy.firstChild; | |
| expect(ErrorConstructorElement.callCount, 1); | |
| }); | |
| test('throw on innerHtml does not upgrade', () { | |
| ErrorConstructorElement.callCount = 0; | |
| var dummy = new DivElement(); | |
| var tag = ErrorConstructorElement.tag; | |
| expectGlobalError(() { | |
| dummy.setInnerHtml('<$tag></$tag>', | |
| treeSanitizer: NodeTreeSanitizer.trusted); | |
| }); | |
| expect(ErrorConstructorElement.callCount, 1); | |
| var e = dummy.firstChild; | |
| // Accessing should not re-run the constructor. | |
| expect(ErrorConstructorElement.callCount, 1); | |
| expect(e is HtmlElement, isTrue); | |
| expect(e is ErrorConstructorElement, isFalse); | |
| }); | |
| test('cannot register created with params', () { | |
| expect(() { | |
| document.registerElement2( | |
| 'x-created-with-params', {'prototype': CreatedWithParametersElement}); | |
| }, throws); | |
| }); | |
| test('created cannot be called from nested constructor', | |
| NestedCreatedConstructorElement.test); | |
| // TODO(vsm): Port additional test from upstream here: | |
| // http://src.chromium.org/viewvc/blink/trunk/LayoutTests/fast/dom/custom/created-callback.html?r1=156141&r2=156185 | |
| } | |
| class NestedElement extends HtmlElement { | |
| static final tag = 'x-nested'; | |
| final Element b = new B(); | |
| factory NestedElement() => new Element.tag(tag); | |
| NestedElement.created() : super.created(); | |
| static void register() { | |
| document.registerElement2(tag, {'prototype': NestedElement}); | |
| } | |
| static void test() { | |
| register(); | |
| var e = new NestedElement(); | |
| expect(e.b, isNotNull); | |
| expect(e.b is B, isTrue); | |
| expect(e is NestedElement, isTrue); | |
| } | |
| } | |
| class AccessWhileUpgradingElement extends HtmlElement { | |
| static final tag = 'x-access-while-upgrading'; | |
| static Element upgradingContext; | |
| static Element upgradingContextChild; | |
| final foo = runInitializerCode(); | |
| factory AccessWhileUpgradingElement() => new Element.tag(tag); | |
| AccessWhileUpgradingElement.created() : super.created(); | |
| static runInitializerCode() { | |
| upgradingContextChild = upgradingContext.firstChild; | |
| return 666; | |
| } | |
| static void register() { | |
| document.registerElement2(tag, {'prototype': AccessWhileUpgradingElement}); | |
| } | |
| static void test() { | |
| register(); | |
| upgradingContext = new DivElement(); | |
| upgradingContext.setInnerHtml('<$tag></$tag>', | |
| treeSanitizer: new NullTreeSanitizer()); | |
| dynamic child = upgradingContext.firstChild; | |
| expect(child.foo, 666); | |
| expect(upgradingContextChild is HtmlElement, isFalse); | |
| expect(upgradingContextChild is AccessWhileUpgradingElement, isFalse, | |
| reason: 'Elements accessed while upgrading should not be upgraded.'); | |
| } | |
| } | |
| class MissingCreatedElement extends HtmlElement { | |
| static final tag = 'x-missing-created'; | |
| factory MissingCreatedElement() => new Element.tag(tag); | |
| } | |
| class ErrorConstructorElement extends HtmlElement { | |
| static final tag = 'x-throws-in-constructor'; | |
| static int callCount = 0; | |
| factory ErrorConstructorElement() => new Element.tag(tag); | |
| ErrorConstructorElement.created() : super.created() { | |
| ++callCount; | |
| throw new Exception('Just messin with ya'); | |
| } | |
| static void register() { | |
| document.registerElement2(tag, {'prototype': ErrorConstructorElement}); | |
| } | |
| } | |
| class NestedCreatedConstructorElement extends HtmlElement { | |
| static final tag = 'x-nested-created-constructor'; | |
| // Should not be able to call this here. | |
| final B b = constructB(); | |
| static B constructedB; | |
| factory NestedCreatedConstructorElement() => new Element.tag(tag); | |
| NestedCreatedConstructorElement.created() : super.created(); | |
| static void register() { | |
| document | |
| .registerElement2(tag, {'prototype': NestedCreatedConstructorElement}); | |
| } | |
| // Try to run the created constructor, and record the results. | |
| static constructB() { | |
| // This should throw an exception. | |
| constructedB = new B.created(); | |
| return constructedB; | |
| } | |
| static void test() { | |
| register(); | |
| // Exception should have occurred on upgrade. | |
| var e; | |
| expectGlobalError(() { | |
| e = new Element.tag(tag); | |
| }); | |
| expect(e is NestedCreatedConstructorElement, isFalse); | |
| expect(e is HtmlElement, isTrue); | |
| // Should not have been set. | |
| expect(constructedB, isNull); | |
| } | |
| } | |
| class CreatedWithParametersElement extends HtmlElement { | |
| CreatedWithParametersElement.created(ignoredParam) : super.created(); | |
| } | |
| void expectGlobalError(Function test) { | |
| js.context['testExpectsGlobalError'] = true; | |
| try { | |
| test(); | |
| } catch (e) { | |
| rethrow; | |
| } finally { | |
| js.context['testExpectsGlobalError'] = false; | |
| } | |
| var errors = js.context['testSuppressedGlobalErrors']; | |
| expect(errors['length'], 1); | |
| // Clear out the errors; | |
| js.context['testSuppressedGlobalErrors']['length'] = 0; | |
| } |