diff --git a/lib/elements/dom-repeat.js b/lib/elements/dom-repeat.js
index 0a88c81d0f..ef920fad84 100644
--- a/lib/elements/dom-repeat.js
+++ b/lib/elements/dom-repeat.js
@@ -245,15 +245,16 @@ export class DomRepeat extends domRepeatBase {
},
/**
- * Defines an initial count of template instances to render after setting
- * the `items` array, before the next paint, and puts the `dom-repeat`
- * into "chunking mode". The remaining items (and any future items as a
- * result of pushing onto the array) will be created and rendered
- * incrementally at each animation frame thereof until all instances have
- * been rendered.
+ * When greater than zero, defines an initial count of template instances
+ * to render after setting the `items` array, before the next paint, and
+ * puts the `dom-repeat` into "chunking mode". The remaining items (and
+ * any future items as a result of pushing onto the array) will be created
+ * and rendered incrementally at each animation frame thereof until all
+ * instances have been rendered.
*/
initialCount: {
- type: Number
+ type: Number,
+ observer: '__initialCountChanged'
},
/**
@@ -471,6 +472,17 @@ export class DomRepeat extends domRepeatBase {
this.observe.replace('.*', '.').split(' ');
}
+ __initialCountChanged(value) {
+ // When not chunking, ensure the list is unlimited
+ if (!value) {
+ this.__limit = Infinity;
+ }
+ // When chunking, `__limit` is managed in `__updateLimit` called during
+ // `__render`, which ensures the limit takes into account initialCount, the
+ // filtered items length, and any increases based on the throttled
+ // __chunkCount
+ }
+
__handleObservedPaths(path) {
// Handle cases where path changes should cause a re-sort/filter
if (this.__sortFn || this.__filterFn) {
diff --git a/test/unit/dom-repeat.html b/test/unit/dom-repeat.html
index 9dce86e49d..18390dea76 100644
--- a/test/unit/dom-repeat.html
+++ b/test/unit/dom-repeat.html
@@ -3945,28 +3945,38 @@
x-repeat-limit
});
-suite('chunked rendering', function() {
+suite.only('chunked rendering', function() {
let chunked;
- let verifyAfterChange;
- const verify = () => verifyAfterChange && verifyAfterChange();
+ let repeat;
+ let resolve;
+ let targetCount;
+ const handleChange = () => {
+ if (!targetCount || chunked.$.repeater.renderedItemCount >= targetCount) {
+ resolve(Array.from(chunked.root.querySelectorAll('*:not(template):not(dom-repeat)')));
+ }
+ };
+ const waitUntilRendered = async (count) => {
+ targetCount = count;
+ return await new Promise(r => resolve = r);
+ };
setup(() => {
chunked = document.createElement('x-repeat-chunked');
- chunked.addEventListener('dom-change', verify);
+ chunked.addEventListener('dom-change', handleChange);
document.body.appendChild(chunked);
+ repeat = chunked.$.repeat;
});
teardown(() => {
- chunked.removeEventListener('dom-change', verify);
+ chunked.removeEventListener('dom-change', handleChange);
document.body.removeChild(chunked);
chunked = null;
- verifyAfterChange = null;
});
// Framerate=25, element cost = 4ms: should never make more than
// (1000/25) / 4 = 10 elements per frame
const MAX_PER_FRAME = (1000 / 25) / 4;
- test('basic chunked rendering', function(done) {
+ test('basic chunked rendering', async () => {
var checkItemOrder = function(stamped) {
for (var i=0; ix-repeat-limit
}
};
+ // Set items to chunk
+ chunked.items = chunked.preppedItems.slice();
+
+ let stamped = [];
let lastStamped;
let frameCount = 0;
- verifyAfterChange = function() {
- var stamped = Array.from(chunked.root.querySelectorAll('*:not(template):not(dom-repeat)'));
+ // Loop until chunking is complete
+ while (stamped.length < chunked.items.length) {
+ frameCount++;
+ stamped = await waitUntilRendered();
checkItemOrder(stamped);
if (!lastStamped) {
// Initial rendering of initial count
@@ -3991,21 +4007,14 @@ x-repeat-limit
assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
`list should not render more than ${MAX_PER_FRAME} per frame`);
}
- if (stamped.length < chunked.items.length) {
- frameCount++;
- lastStamped = stamped;
- } else {
- // Final rendering at exact item count
- assert.equal(stamped.length, 100, 'final count wrong');
- assert.isAtLeast(frameCount, 10, 'should have taken at least 10 frames to render');
- done();
- }
- };
- chunked.items = chunked.preppedItems.slice();
-
+ lastStamped = stamped;
+ }
+ // Final rendering at exact item count
+ assert.equal(stamped.length, 100, 'final count wrong');
+ assert.isAtLeast(frameCount, 10, 'should have taken at least 10 frames to render');
});
- test('mutations during chunked rendering', function(done) {
+ test('mutations during chunked rendering', async () => {
var checkItemOrder = function(stamped) {
var last = -1;
@@ -4038,10 +4047,15 @@ x-repeat-limit
}
};
+ // Set items to chunk
+ chunked.items = chunked.preppedItems.slice();
+
+ let stamped = [];
let lastStamped;
- var frameCount = 0;
- verifyAfterChange = function() {
- var stamped = Array.from(chunked.root.querySelectorAll('*:not(template):not(dom-repeat)'));
+ let frameCount = 0;
+ // Loop until chunking is complete
+ while (stamped.length < chunked.items.length) {
+ stamped = await waitUntilRendered();
checkItemOrder(stamped);
if (!lastStamped) {
// Initial rendering of initial count
@@ -4055,24 +4069,20 @@ x-repeat-limit
assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
`list should not render more than ${MAX_PER_FRAME} per frame`);
}
- if (stamped.length < chunked.items.length) {
- if (frameCount++ < 5) {
- mutateArray(chunked, stamped);
- }
- lastStamped = stamped;
- } else {
- // Final rendering at exact item count
- assert.equal(stamped.length, chunked.items.length, 'final count wrong');
- assert.isAtLeast(frameCount, 10, 'should have taken at least 10 frames to render');
- done();
+ if (stamped.length < chunked.items.length && frameCount < 5) {
+ mutateArray(chunked, stamped);
}
- };
- chunked.items = chunked.preppedItems.slice();
+ lastStamped = stamped;
+ frameCount++;
+ }
+ // Final rendering at exact item count
+ assert.equal(stamped.length, chunked.items.length, 'final count wrong');
+ assert.isAtLeast(frameCount, 10, 'should have taken at least 10 frames to render');
});
- test('mutations during chunked rendering, sort & filtered', function(done) {
+ test('mutations during chunked rendering, sort & filtered', async () => {
var checkItemOrder = function(stamped) {
var last = Infinity;
@@ -4094,12 +4104,23 @@ x-repeat-limit
chunked.splice('items', Math.round(stamped.length/2), 3);
};
- var lastStamped;
- var frameCount = 0;
- verifyAfterChange = function() {
- var stamped = Array.from(chunked.root.querySelectorAll('*:not(template):not(dom-repeat)'));
+ // Set items to chunk
+ chunked.$.repeater.sort = function(a, b) {
+ return b.prop - a.prop;
+ };
+ chunked.$.repeater.filter = function(a) {
+ return (a.prop % 2) === 0;
+ };
+ chunked.items = chunked.preppedItems.slice();
+
+ let stamped = [];
+ let lastStamped;
+ let frameCount = 0;
+ let filteredLength = chunked.items.filter(chunked.$.repeater.filter).length;
+ // Loop until chunking is complete
+ while (stamped.length < filteredLength) {
+ stamped = await waitUntilRendered();
checkItemOrder(stamped);
- var filteredLength = chunked.items.filter(chunked.$.repeater.filter).length;
if (!lastStamped) {
// Initial rendering of initial count
assert.equal(stamped.length, 10);
@@ -4112,37 +4133,35 @@ x-repeat-limit
assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
`list should not render more than ${MAX_PER_FRAME} per frame`);
}
- if (stamped.length < filteredLength) {
- if (frameCount++ < 4) {
- mutateArray(chunked, stamped);
- }
- lastStamped = stamped;
- } else {
- assert.equal(stamped.length, filteredLength, 'final count wrong');
- assert.isAtLeast(frameCount, 5, 'should have taken at least 5 frames to render');
- done();
+ if (stamped.length < chunked.items.length && frameCount < 4) {
+ mutateArray(chunked, stamped);
+ filteredLength = chunked.items.filter(chunked.$.repeater.filter).length;
}
- };
- chunked.$.repeater.sort = function(a, b) {
- return b.prop - a.prop;
- };
- chunked.$.repeater.filter = function(a) {
- return (a.prop % 2) === 0;
- };
- chunked.items = chunked.preppedItems.slice();
-
+ lastStamped = stamped;
+ frameCount++;
+ }
+ // Final rendering at exact item count
+ assert.equal(stamped.length, filteredLength, 'final count wrong');
+ assert.isAtLeast(frameCount, 5, 'should have taken at least 5 frames to render');
});
suite('resetting items array', () => {
[false, true].forEach(reuse => {
- test(`reuseChunkedInstances=${reuse}`, (done) => {
+ test(`reuseChunkedInstances=${reuse}`, async () => {
- let i = 3;
+ // Set items to chunk
+ chunked.$.repeater.reuseChunkedInstances = reuse;
+ chunked.items = chunked.preppedItems.slice();
+
+ let resetCount = 3;
+ let stamped = [];
let lastStamped;
- verifyAfterChange = function() {
- var stamped = Array.from(chunked.root.querySelectorAll('*:not(template):not(dom-repeat)'));
+ let frameCount = 0;
+ // Loop until chunking is complete
+ while (stamped.length < chunked.items.length) {
+ stamped = await waitUntilRendered();
if (!lastStamped) {
// Initial rendering of initial count
assert.equal(stamped.length, 10);
@@ -4155,30 +4174,61 @@ x-repeat-limit
assert.isAtMost((stamped.length - lastStamped.length), MAX_PER_FRAME,
`list should not render more than ${MAX_PER_FRAME} per frame`);
}
- if (stamped.length < chunked.items.length) {
- lastStamped = stamped;
- } else {
- assert.equal(stamped.length, chunked.items.length, 'final count wrong');
- if (--i > 0) {
- if (!reuse) {
- lastStamped = null;
- }
- chunked.items = chunked.preppedItems.slice();
- } else {
- done();
+ lastStamped = stamped;
+ frameCount++;
+ if (--resetCount > 0) {
+ if (!reuse) {
+ lastStamped = null;
}
+ chunked.items = chunked.preppedItems.slice();
}
- };
-
- chunked.$.repeater.reuseChunkedInstances = reuse;
- chunked.items = chunked.preppedItems.slice();
-
+ }
+ // Final rendering at exact item count
+ assert.equal(stamped.length, chunked.items.length, 'final count wrong');
+ assert.isAtLeast(frameCount, 5, 'should have taken at least 5 frames to render');
+
});
});
});
+ test('changing to/from initialCount=0', async () => {
+ // Render all
+ chunked.items = chunked.preppedItems.slice();
+ let stamped = await waitUntilRendered(chunked.preppedItems.length);
+ assert.equal(stamped.length, chunked.preppedItems.length);
+ // Clear the list
+ chunked.items = [];
+ stamped = await waitUntilRendered(0);
+ assert.equal(stamped.length, 0);
+ // Disable chunking
+ chunked.$.repeater.initialCount = 0;
+ // Render all
+ chunked.items = chunked.preppedItems.slice();
+ stamped = await waitUntilRendered(chunked.preppedItems.length);
+ assert.equal(stamped.length, chunked.preppedItems.length);
+ // Clear the list
+ chunked.items = [];
+ stamped = await waitUntilRendered(0);
+ assert.equal(stamped.length, 0);
+ // Re-enable chunking
+ chunked.$.repeater.initialCount = 10;
+ // Render all; the initial render should have the initial count, and then
+ // chunk until the end of the list
+ let frameCount = 0;
+ chunked.items = chunked.preppedItems.slice();
+ while (stamped.length < chunked.items.length) {
+ stamped = await waitUntilRendered(10);
+ if (frameCount === 0) {
+ assert.equal(stamped.length, 10);
+ }
+ frameCount++;
+ }
+ assert.equal(stamped.length, chunked.preppedItems.length);
+ assert.isAtLeast(frameCount, 2);
+ });
+
});
suite('misc', function() {
@@ -4233,7 +4283,6 @@ x-repeat-limit
test('paths update on observed properties', function() {
let simple = fixture('simple');
flush();
- //debugger;
var stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
assert.equal(stamped[0].itemaProp, 'prop-1');
simple.$.repeat.observe = 'prop';