Skip to content

Commit

Permalink
Ensure limit is reset when initialCount is disabled.
Browse files Browse the repository at this point in the history
Note that any falsey value for initialCount (including `0`) is interpreted as "chunking disabled". This is consistent with 1.x logic, and follows from the logic of "starting chunking by rendering zero items" doesn't really make sense.
  • Loading branch information
kevinpschaaf committed Mar 3, 2020
1 parent 5421b5b commit 60f6ccf
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 89 deletions.
26 changes: 19 additions & 7 deletions lib/elements/dom-repeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
},

/**
Expand Down Expand Up @@ -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) {
Expand Down
213 changes: 131 additions & 82 deletions test/unit/dom-repeat.html
Original file line number Diff line number Diff line change
Expand Up @@ -3945,39 +3945,55 @@ <h4>x-repeat-limit</h4>

});

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; i<stamped.length; i++) {
assert.equal(stamped[i].textContent, i);
}
};

// 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
Expand All @@ -3991,21 +4007,14 @@ <h4>x-repeat-limit</h4>
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;
Expand Down Expand Up @@ -4038,10 +4047,15 @@ <h4>x-repeat-limit</h4>
}
};

// 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
Expand All @@ -4055,24 +4069,20 @@ <h4>x-repeat-limit</h4>
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;
Expand All @@ -4094,12 +4104,23 @@ <h4>x-repeat-limit</h4>
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);
Expand All @@ -4112,37 +4133,35 @@ <h4>x-repeat-limit</h4>
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);
Expand All @@ -4155,30 +4174,61 @@ <h4>x-repeat-limit</h4>
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() {
Expand Down Expand Up @@ -4233,7 +4283,6 @@ <h4>x-repeat-limit</h4>
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';
Expand Down

0 comments on commit 60f6ccf

Please sign in to comment.