Skip to content

Commit

Permalink
add lazy-upgrade tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dfreedm committed Mar 10, 2017
1 parent 9891e48 commit 71b70aa
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 5 deletions.
73 changes: 69 additions & 4 deletions lib/mixins/lazy-upgrade-mixin.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,29 @@
(function(){
'use strict';

/**
* Element class mixin that provides API for lazily upgrading Polymer Elements.
*
* This mixin can be used with the `disable-upgrade` attribute to lazily upgrade elements
* that are descendants of this element. This is useful for elements that need to display
* many elements.
*
* When upgrading elements, the mixin will upgrade as many as it can within the budget
* set by `_lazyUpgradeBudget`, which defaults to 16ms.
*
* When finished processing lazy-upgrade elements, this mixin will set `_lazyUpgrading`
* to false on the instance.
*
* @polymerMixin
* @memberof Polymer
* @summary Element class mixin that provides API for lazily upgrading Polymer elements
*/
Polymer.LazyUpgradeMixin = Polymer.dedupingMixin(function(base) {

const LAZY_UPGRADE = 'lazy-upgrade';
const DISABLE_UPGRADE = 'disable-upgrade';
const LAZY_UPGRADE_QUERY = `[${LAZY_UPGRADE}]`;
const LAZY_UPGRADE_BUDGET = 16;

function sortCandidates(a, b) {
let orderA = parseInt(a.getAttribute(LAZY_UPGRADE), 10) || 0;
Expand All @@ -26,7 +45,7 @@
}

function findCandidates(root) {
let candidates = Array.from(root.querySelectorAll(`[${LAZY_UPGRADE}]`));
let candidates = Array.from(root.querySelectorAll(LAZY_UPGRADE_QUERY));
candidates.sort(sortCandidates);
return candidates;
}
Expand All @@ -39,25 +58,71 @@
node.removeAttribute(LAZY_UPGRADE);
}

function eventInCurrentScope(scope, event) {
return event.target === event.composedPath()[0];
}

/** @polymerMixinClass */
return class LazyUpgrade extends base {
// add a configurable
static get properties() {
return {
/**
* Instance-level property for configuring the frame budget for lazy-upgrading elements.
* Defaults to 16ms
*/
_lazyUpgradeBudget: {
type: Number,
value: LAZY_UPGRADE_BUDGET
},
/**
* Instance-level property that is shows when the element is lazy-upgrading elements
*/
_lazyUpgrading: {
type: Boolean
}
}
}

ready() {
super.ready();
this.__lazyUpgradeDeadline = 16;
// check for dom-repeat and dom-if elements stamping lazy-upgrade nodes
this.addEventListener('dom-change', (ev) => {
if (!eventInCurrentScope(this, ev)) {
return;
}
let root = ev.composedPath()[0].parentNode;
let candidates = findCandidates(root);
for (let i = 0; i < candidates.length; i++) {
let c = candidates[i];
if (this.__lazyUpgradeQueue.indexOf(c) === -1) {
this.__lazyUpgradeQueue.push(c);
}
}
if (candidates.length && !this._lazyUpgrading) {
this._lazyUpgrading = true;
this.__lazyUpgrade();
}
});
this._lazyUpgrading = true;
this.__lazyUpgradeQueue = findCandidates(this.shadowRoot || this);
this.__lazyUpgrade();
}

/**
* @private
*/
__lazyUpgrade() {
if (this.__lazyUpgradeQueue.length) {
Polymer.RenderStatus.afterNextRender(this, () => {
const deadline = performance.now() + this.__lazyUpgradeDeadline;
const deadline = performance.now() + this._lazyUpgradeBudget;
while (this.__lazyUpgradeQueue.length && (performance.now() < deadline)) {
upgradeNode(this.__lazyUpgradeQueue.shift());
}
this.__lazyUpgrade();
});
} else {
this.dispatchEvent(new Event('lazy-upgrade-finished'));
this._lazyUpgrading = false;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion test/runner.html
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
'unit/mixin-utils.html',
'unit/mixin-behaviors.html',
'unit/render-status.html',
'unit/disable-upgrade.html'
'unit/disable-upgrade.html',
'unit/lazy-upgrade.html'
];

// http://eddmann.com/posts/cartesian-product-in-javascript/
Expand Down
268 changes: 268 additions & 0 deletions test/unit/lazy-upgrade.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<meta charset="utf-8">
<script src="../../../webcomponentsjs/webcomponents-loader.js"></script>
<script src="../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../polymer-element.html">
<link rel="import" href="../../lib/elements/dom-if.html">
<link rel="import" href="../../lib/mixins/lazy-upgrade-mixin.html">
<link rel="import" href="../../lib/utils/async.html">
<link rel="import" href="../../lib/utils/debounce.html">

<script>
let Loaded = new Promise((resolve) => {
addEventListener('WebComponentsReady', () => {resolve()});
});
Loaded.then(function() {
let async = Polymer.Async.timeOut.after(150);
window.LazyParentMixin = class LazyParent extends Polymer.LazyUpgradeMixin(Polymer.Element) {
static get observers() {
return ['lazyUpgradingChanged(_lazyUpgrading)']
}
connectedCallback() {
this.upgradedChildren = [];
this.addEventListener('rendered', (e) => {
this.upgradedChildren.push(e.composedPath()[0]);
});
super.connectedCallback();
}
lazyUpgradingChanged() {
this.__lazyDebouncer = Polymer.Debouncer.debounce(this.__lazyDebouncer, async, () => {
this.dispatchEvent(new Event('lazy-upgrade-finished'));
});
}
}
});
</script>

<dom-module id="lazy-child">
<template>
<style>
:host(:not([disable-upgrade])) {
display: block;
border: 2px solid blue;
height: 20px;
}
</style>
</template>
<script>
Loaded.then(function() {
class LazyChild extends Polymer.Element {
static get is() {return 'lazy-child'}
ready() {
super.ready();
this.dispatchEvent(new Event('rendered', {bubbles: true, composed: true}));
}
}
customElements.define('lazy-child', LazyChild);
});
</script>
</dom-module>

<dom-module id="lazy-upgrade-shadow">
<template>
<lazy-child id="one" disable-upgrade lazy-upgrade></lazy-child>
<lazy-child id="two" disable-upgrade lazy-upgrade="1"></lazy-child>
<lazy-child id="three"></lazy-child>
<dom-if lazy-upgrade="1">
<lazy-child id="four" disable-upgrade lazy-upgrade></lazy-child>
</dom-if>
<lazy-child id="five" disable-upgrade></lazy-child>
<dom-if lazy-upgrade>
<lazy-child id="six"></lazy-child>
</dom-if>
</template>
<script>
Loaded.then(function() {
customElements.define('lazy-upgrade-shadow', class extends window.LazyParentMixin {
static get is() { return 'lazy-upgrade-shadow' }
});
});
</script>
</dom-module>

<dom-module id="lazy-parent">
<script>
Loaded.then(function() {
customElements.define('lazy-parent', class extends window.LazyParentMixin {});
});
</script>
</dom-module>

<template id="only-lazy-upgrade">
<dom-if>
<template>
<lazy-child id="one"></lazy-child>
</template>
</dom-if>
<dom-if lazy-upgrade>
<template>
<lazy-child id="two"></lazy-child>
</template>
</dom-if>
<lazy-child id="three" disable-upgrade></lazy-child>
<lazy-child id="four" disable-upgrade lazy-upgrade></lazy-child>
<dom-if lazy-upgrade>
<template>
<lazy-child id="five" disable-upgrade></lazy-child>
<lazy-child id="six" disable-upgrade lazy-upgrade></lazy-child>
</template>
</dom-if>
</template>

<template id="critical">
<lazy-child id="lazy-one" disable-upgrade lazy-upgrade></lazy-child>
<lazy-child id="critical"></lazy-child>
<lazy-child id="lazy-two" disable-upgrade lazy-upgrade></lazy-child>
</template>

<template id="ordered">
<lazy-child id="one" disable-upgrade lazy-upgrade="3"></lazy-child>
<lazy-child id="two" disable-upgrade lazy-upgrade="2"></lazy-child>
<lazy-child id="three" disable-upgrade lazy-upgrade="1"></lazy-child>
<lazy-child id="four" disable-upgrade lazy-upgrade></lazy-child>
<lazy-child id="five" disable-upgrade lazy-upgrade="2"></lazy-child>
</template>

<dom-module id="no-upgrade-shadow">
<template>
<lazy-child id="one" disable-upgrade lazy-upgrade></lazy-child>
<dom-if if>
<template>
<lazy-child id="two" disable-upgrade lazy-upgrade></lazy-child>
</template>
</dom-if>
</template>
</dom-module>
<script>
Loaded.then(() => {
customElements.define('no-upgrade-shadow', class extends Polymer.Element {
static get is() { return 'no-upgrade-shadow' }
});
});
</script>

<template id="scoped">
<no-upgrade-shadow></no-upgrade-shadow>
<lazy-child id="three" disable-upgrade lazy-upgrade></lazy-child>
</template>

<script>
suite('Lazy Upgrade Mixin', () => {
function assertUpgraded(node, expected) {
let disabled = node.hasAttribute('disable-upgrade');
assert.notEqual(disabled, expected, 'upgrade status was not expected');
}

function assertRendered(node, expected) {
let border = getComputedStyle(node).getPropertyValue('border-top-width');
assert.equal(border, expected ? '2px' : '0px', 'render status was not expected');
}

test('fires "lazy-upgrade-finished" event when done', (done) => {
let el = document.createElement('lazy-parent');
el.addEventListener('lazy-upgrade-finished', () => {
document.body.removeChild(el)
done();
});
document.body.appendChild(el);
});

test('Lazy Upgrading shows critical section first, lazy elements later', (done) => {
let template = document.querySelector('template#critical');
let el = document.createElement('lazy-parent');
el.appendChild(document.importNode(template.content, true));
let children = el.querySelectorAll('lazy-child');
el.addEventListener('lazy-upgrade-finished', () => {
for (let i = 0; i < children.length; i++) {
assertUpgraded(children[i], true);
}
el.parentNode.removeChild(el);
done();
})
document.body.appendChild(el);
assertUpgraded(children[0], false);
assertUpgraded(children[1], true);
assertUpgraded(children[2], false);
});

test('Lazy Upgrading activates elements with lazy-upgrade attribute', (done) => {
let template = document.querySelector('template#only-lazy-upgrade');
let el = document.createElement('lazy-parent');
el.appendChild(document.importNode(template.content, true));
el.addEventListener('lazy-upgrade-finished', () => {
let children = el.querySelectorAll('lazy-child');
assert.equal(children.length, 5);
assertRendered(children[0], true);
assertRendered(children[1], false);
assertRendered(children[2], true);
assertRendered(children[3], false);
assertRendered(children[4], true);
document.body.removeChild(el);
done();
});
document.body.appendChild(el);
});

test('Lazy upgrading respects ordering', (done) => {
let template = document.querySelector('template#ordered');
let el = document.createElement('lazy-parent');
el.appendChild(document.importNode(template.content, true));
el.addEventListener('lazy-upgrade-finished', () => {
let expected = [
"four",
"three",
"two",
"five",
"one"
];
document.body.removeChild(el);
assert.deepEqual(el.upgradedChildren.map(n => n.id), expected);
done();
});
document.body.appendChild(el);
});

test('Shadow DOM works the same', (done) => {
let el = document.createElement('lazy-upgrade-shadow');
el.addEventListener('lazy-upgrade-finished', () => {
let children = el.shadowRoot.querySelectorAll('lazy-child');
assert.equal(children.length, 6);
let expected = [
"three",
"six",
"one",
"four",
"two"
];
assert.deepEqual(el.upgradedChildren.map(n => n.id), expected);
document.body.removeChild(el);
done();
});
document.body.appendChild(el);
});

test('Candidates are only found in the same scope', (done) => {
let template = document.querySelector('template#scoped');
let el = document.createElement('lazy-parent');
el.addEventListener('lazy-upgrade-finished', () => {
let expected = [
"three"
];
assert.deepEqual(el.upgradedChildren.map(n => n.id), expected);
document.body.removeChild(el);
done();
});
el.appendChild(document.importNode(template.content, true));
document.body.appendChild(el);
});
})
</script>

0 comments on commit 71b70aa

Please sign in to comment.