Skip to content
This repository has been archived by the owner on Mar 13, 2018. It is now read-only.

Commit

Permalink
Converting some Blink tests over & tightening upgrade logic
Browse files Browse the repository at this point in the history
  • Loading branch information
John Messerly committed Feb 6, 2014
1 parent e28ecfa commit be729c3
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 12 deletions.
42 changes: 34 additions & 8 deletions src/CustomElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var hasNative = Boolean(document.registerElement);
// TODO(sorvell): See https://github.com/Polymer/polymer/issues/399
// we'll address this by defaulting to CE polyfill in the presence of the SD
// polyfill. This will avoid spamming excess attached/detached callbacks.
// If there is a compelling need to run CE native with SD polyfill,
// If there is a compelling need to run CE native with SD polyfill,
// we'll need to fix this issue.
var useNative = !flags.register && hasNative && !window.ShadowDOMPolyfill;

Expand All @@ -41,7 +41,7 @@ if (useNative) {
// exports
scope.registry = {};
scope.upgradeElement = nop;

scope.watchShadow = nop;
scope.upgrade = nop;
scope.upgradeAll = nop;
Expand Down Expand Up @@ -320,6 +320,9 @@ if (useNative) {
}

function registerDefinition(name, definition) {
if (registry[name]) {
throw new Error('a type with that name is already registered.');
}
registry[name] = definition;
}

Expand All @@ -334,16 +337,39 @@ if (useNative) {
// error check it, or perhaps there should only ever be one argument
var definition = getRegisteredDefinition(typeExtension || tag);
if (definition) {
return new definition.ctor();
if (tag == definition.tag && typeExtension == definition.is) {
return new definition.ctor();
}
// Handle empty string for type extension.
if (!typeExtension && !definition.is) {
return new definition.ctor();
}
}

if (typeExtension) {
var element = createElement(tag);
element.setAttribute('is', typeExtension);
return element;
}
var element = domCreateElement(tag);
// Custom tags should be HTMLElements even if not upgraded.
if (tag.indexOf('-') >= 0) {
implement(element, HTMLElement);
}
return domCreateElement(tag);
return element;
}

function upgradeElement(element) {
if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) {
var type = element.getAttribute('is') || element.localName;
var definition = getRegisteredDefinition(type);
return definition && upgrade(element, definition);
var is = element.getAttribute('is');
var definition = getRegisteredDefinition(is || element.localName);
if (definition) {
if (is && definition.tag == element.localName) {
return upgrade(element, definition);
} else if (!is && !definition.extends) {
return upgrade(element, definition);
}
}
}
}

Expand Down Expand Up @@ -373,7 +399,7 @@ if (useNative) {

/**
* Upgrade an element to a custom element. Upgrading an element
* causes the custom prototype to be applied, an `is` attribute
* causes the custom prototype to be applied, an `is` attribute
* to be attached (as needed), and invocation of the `readyCallback`.
* `upgrade` does nothing if the element is already upgraded, or
* if it matches no registered custom tag name.
Expand Down
7 changes: 3 additions & 4 deletions src/Observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ function insertedNode(node) {
}


// TODO(sorvell): on platforms without MutationObserver, mutations may not be
// TODO(sorvell): on platforms without MutationObserver, mutations may not be
// reliable and therefore attached/detached are not reliable.
// To make these callbacks less likely to fail, we defer all inserts and removes
// to give a chance for elements to be inserted into dom.
// This ensures attachedCallback fires for elements that are created and
// to give a chance for elements to be inserted into dom.
// This ensures attachedCallback fires for elements that are created and
// immediately added to dom.
var hasPolyfillMutations = (!window.MutationObserver ||
(window.MutationObserver === window.JsMutationObserver));
Expand Down Expand Up @@ -180,7 +180,6 @@ function removedNode(node) {
});
}


function removed(element) {
if (hasPolyfillMutations) {
deferMutation(function() {
Expand Down
211 changes: 211 additions & 0 deletions test/js/documentRegister.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/


// Adapted from:
// https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/LayoutTests/fast/dom/custom/document-register-type-extensions.html
var testForm = document.createElement('form');

function isFormControl(element)
{
testForm.appendChild(element);
return element.form == testForm;
}

suite('register-type-extensions', function() {
var assert = chai.assert;

var fooConstructor = document.register('x-foo-x', {
prototype: Object.create(HTMLElement.prototype) });
var fooOuterHTML = '<x-foo-x></x-foo-x>';
var barConstructor = document.register('x-bar-x', {
prototype: Object.create(HTMLInputElement.prototype),
extends:'input'});
var barOuterHTML = '<input is="x-bar-x">';
var bazConstructor = document.register('x-baz', {
prototype: Object.create(fooConstructor.prototype) });
var quxConstructor = document.register('x-qux', {
prototype: Object.create(barConstructor.prototype),
extends:'x-bar-x'});

test('cannot register twice', function() {
assert.throws(function() {
document.register('x-foo-x', {
prototype: Object.create(HTMLDivElement.prototype) });
});
});

suite('generated constructors', function() {
test('custom tag', function() {
var fooNewed = new fooConstructor();
assert.equal(fooNewed.outerHTML, fooOuterHTML);
assert.instanceOf(fooNewed, fooConstructor);
assert.instanceOf(fooNewed, HTMLElement);
// This is part of the Blink tests, but not supported in Firefox with
// polyfill. Similar assertions are also commented out below.
// assert.notInstanceOf(fooNewed, HTMLUnknownElement);

test('custom tag constructor', function() {
assert.equal('a', 'b');
});
});

test('type extension', function() {
var barNewed = new barConstructor();
assert.equal(barNewed.outerHTML, barOuterHTML);
assert.instanceOf(barNewed, barConstructor);
assert.instanceOf(barNewed, HTMLInputElement);
assert.ok(isFormControl(barNewed));
});

test('custom tag deriving from custom tag', function() {
var bazNewed = new bazConstructor();
var bazOuterHTML = '<x-baz></x-baz>';
assert.equal(bazNewed.outerHTML, bazOuterHTML);
assert.instanceOf(bazNewed, bazConstructor);
assert.instanceOf(bazNewed, HTMLElement);
// assert.notInstanceOf(bazNewed, HTMLUnknownElement);
});

test('type extension deriving from custom tag', function() {
var quxNewed = new quxConstructor();
var quxOuterHTML = '<input is="x-qux">';
assert.instanceOf(quxNewed, quxConstructor);
assert.instanceOf(quxNewed, barConstructor);
assert.instanceOf(quxNewed, HTMLInputElement);
assert.equal(quxNewed.outerHTML, quxOuterHTML);
assert.ok(isFormControl(quxNewed));
});
});

suite('single-parameter createElement', function() {
test('custom tag', function() {
var fooCreated = document.createElement('x-foo-x');
assert.equal(fooCreated.outerHTML, fooOuterHTML);
assert.instanceOf(fooCreated, fooConstructor);
});

test('type extension', function() {
var barCreated = document.createElement('x-bar-x');
assert.equal(barCreated.outerHTML, '<x-bar-x></x-bar-x>');
assert.notInstanceOf(barCreated, barConstructor);
// assert.notInstanceOf(barCreated, HTMLUnknownElement);
assert.instanceOf(barCreated, HTMLElement);
});

test('custom tag deriving from custom tag', function() {
bazCreated = document.createElement('x-baz');
assert.equal(bazCreated.outerHTML, '<x-baz></x-baz>');
assert.instanceOf(bazCreated, bazConstructor);
// assert.notInstanceOf(bazCreated, HTMLUnknownElement);
});

test('type extension deriving from custom tag', function() {
quxCreated = document.createElement('x-qux');
assert.equal(quxCreated.outerHTML, '<x-qux></x-qux>');
assert.notInstanceOf(quxCreated, quxConstructor);
// assert.notInstanceOf(quxCreated, HTMLUnknownElement);
assert.instanceOf(quxCreated, HTMLElement);
});
});

suite('createElement with type extensions', function() {
test('extension is custom tag', function() {
var divFooCreated = document.createElement('div', 'x-foo-x');
assert.equal(divFooCreated.outerHTML, '<div is="x-foo-x"></div>');
assert.notInstanceOf(divFooCreated, fooConstructor);
assert.instanceOf(divFooCreated, HTMLDivElement);
});

test('valid extension', function() {
var inputBarCreated = document.createElement('input', 'x-bar-x');
assert.equal(inputBarCreated.outerHTML, barOuterHTML);
assert.instanceOf(inputBarCreated, barConstructor);
assert.notInstanceOf(inputBarCreated, HTMLUnknownElement);
assert.ok(isFormControl(inputBarCreated));
});

test('type extension of incorrect tag', function() {
var divBarCreated = document.createElement('div', 'x-bar-x');
assert.equal(divBarCreated.outerHTML, '<div is="x-bar-x"></div>');
assert.notInstanceOf(divBarCreated, barConstructor);
assert.instanceOf(divBarCreated, HTMLDivElement);
});

test('incorrect extension of custom tag', function() {
var fooBarCreated = document.createElement('x-foo-x', 'x-bar-x');
assert.equal(fooBarCreated.outerHTML, '<x-foo-x is="x-bar-x"></x-foo-x>');
assert.instanceOf(fooBarCreated, fooConstructor);
});

test('incorrect extension of type extension', function() {
var barFooCreated = document.createElement('x-bar-x', 'x-foo-x');
assert.equal(barFooCreated.outerHTML, '<x-bar-x is="x-foo-x"></x-bar-x>');
// assert.notInstanceOf(barFooCreated, HTMLUnknownElement);
assert.instanceOf(barFooCreated, HTMLElement);
});

test('null type extension', function() {
var fooCreatedNull = document.createElement('x-foo-x', null);
assert.equal(fooCreatedNull.outerHTML, fooOuterHTML);
assert.instanceOf(fooCreatedNull, fooConstructor);
});

test('empty type extension', function() {
fooCreatedEmpty = document.createElement('x-foo-x', '');
assert.equal(fooCreatedEmpty.outerHTML, fooOuterHTML);
assert.instanceOf(fooCreatedEmpty, fooConstructor);
});

test('invalid tag name', function() {
assert.throws(function() {
document.createElement('@invalid', 'x-bar-x');
});
});
});

suite('parser', function() {
function createElementFromHTML(html) {
var container = document.createElement('div');
container.innerHTML = html;
if (window.CustomElements) {
window.CustomElements.upgradeAll(container);
}
return container.firstChild;
}

test('custom tag', function() {
var fooParsed = createElementFromHTML('<x-foo-x>');
assert.instanceOf(fooParsed, fooConstructor);
});

test('type extension', function() {
var barParsed = createElementFromHTML('<input is="x-bar-x">')
assert.instanceOf(barParsed, barConstructor);
assert.ok(isFormControl(barParsed));
});

test('custom tag as type extension', function() {
var divFooParsed = createElementFromHTML('<div is="x-foo-x">')
assert.notInstanceOf(divFooParsed, fooConstructor);
assert.instanceOf(divFooParsed, HTMLDivElement);
});

// Should we upgrade invalid tags to HTMLElement?
/*test('type extension as custom tag', function() {
var namedBarParsed = createElementFromHTML('<x-bar-x>')
assert.notInstanceOf(namedBarParsed, barConstructor);
assert.notInstanceOf(namedBarParsed, HTMLUnknownElement);
assert.instanceOf(namedBarParsed, HTMLElement);
});*/

test('type extension of incorrect tag', function() {
var divBarParsed = createElementFromHTML('<div is="x-bar-x">')
assert.notInstanceOf(divBarParsed, barConstructor);
assert.instanceOf(divBarParsed, HTMLDivElement);
});
});
});
1 change: 1 addition & 0 deletions test/runner.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<script src="js/customElements.js"></script>
<script src="js/upgrade.js"></script>
<script src="js/observe.js"></script>
<script src="js/documentRegister.js"></script>
<!-- -->
<script>
document.addEventListener('WebComponentsReady', function() {
Expand Down

0 comments on commit be729c3

Please sign in to comment.