Skip to content

Commit 869f525

Browse files
authored
fix: prevent virtualizer from creating excess elements when idle cb is late (#10606)
1 parent 36d6e2e commit 869f525

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

packages/component-base/src/virtualizer-iron-list-adapter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ export class IronListAdapter {
693693
* @override
694694
*/
695695
_increasePoolIfNeeded(count) {
696-
if (this._physicalCount > 2 && count) {
696+
if (this._physicalCount > 2 && this._physicalAverage > 0 && count > 0) {
697697
// The iron-list logic has already created some physical items and
698698
// has decided to create more. Since each item creation round is
699699
// expensive, let's try to create the remaining items in one go.

packages/component-base/test/virtualizer-item-height.test.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { expect } from '@vaadin/chai-plugins';
22
import { aTimeout, fixtureSync, nextFrame, nextResize, oneEvent } from '@vaadin/testing-helpers';
33
import sinon from 'sinon';
4+
import { idlePeriod } from '../src/async.js';
45
import { Virtualizer } from '../src/virtualizer.js';
56

67
async function contentUpdate() {
@@ -247,6 +248,64 @@ describe('virtualizer - item height - initial render', () => {
247248
});
248249
});
249250

251+
describe('virtualizer - item height - resize with late idle callback', () => {
252+
let virtualizer, scrollTarget;
253+
254+
beforeEach(async () => {
255+
scrollTarget = fixtureSync(`
256+
<div style="height: 300px;">
257+
<div class="container"></div>
258+
</div>
259+
`);
260+
261+
virtualizer = new Virtualizer({
262+
createElements: (count) => {
263+
return Array.from({ length: count }, () => {
264+
const el = document.createElement('div');
265+
el.style.width = '100%';
266+
el.style.height = '100px';
267+
el.classList.add('item');
268+
return el;
269+
});
270+
},
271+
updateElement: (el, index) => {
272+
el.id = `item-${index}`;
273+
},
274+
scrollTarget,
275+
scrollContainer: scrollTarget.firstElementChild,
276+
});
277+
virtualizer.size = 1000;
278+
279+
await nextResize(scrollTarget);
280+
await nextFrame();
281+
});
282+
283+
beforeEach(() => {
284+
// Simulate a late idle callback. Must be longer than (nextResize + nextFrame) * 2
285+
const timeout = 200;
286+
sinon.stub(idlePeriod, 'run').callsFake((callback) => setTimeout(callback, timeout));
287+
sinon.stub(idlePeriod, 'cancel').callsFake((handle) => clearTimeout(handle));
288+
});
289+
290+
afterEach(() => {
291+
idlePeriod.run.restore();
292+
idlePeriod.cancel.restore();
293+
});
294+
295+
it('should keep number of physical elements reasonable when item heights decrease', async () => {
296+
const itemCount = scrollTarget.querySelectorAll('.item').length;
297+
298+
scrollTarget.querySelector('#item-0').style.height = '10px';
299+
await nextResize(scrollTarget);
300+
await nextFrame();
301+
scrollTarget.querySelector('#item-1').style.height = '10px';
302+
await nextResize(scrollTarget);
303+
await nextFrame();
304+
305+
expect(scrollTarget.querySelectorAll('.item').length).to.equal(itemCount + 3);
306+
});
307+
});
308+
250309
describe('virtualizer - item height - lazy rendering', () => {
251310
let virtualizer;
252311
let renderPlaceholders;

0 commit comments

Comments
 (0)