// 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(""" """, 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>', 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>', 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; }