Skip to content

Commit

Permalink
fix: dispose of Lit parts after changing a renderer (#2325)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomivirkki authored Aug 12, 2021
1 parent 76fab2f commit 54042b7
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/vaadin-combo-box/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@vaadin/testing-helpers": "^0.2.1",
"@vaadin/vaadin-dialog": "^22.0.0-alpha1",
"@vaadin/vaadin-template-renderer": "^22.0.0-alpha1",
"lit": "^2.0.0-rc.1",
"sinon": "^9.2.0"
},
"publishConfig": {
Expand Down
4 changes: 4 additions & 0 deletions packages/vaadin-combo-box/src/vaadin-combo-box-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ class ComboBoxItemElement extends ThemableMixin(DirMixin(PolymerElement)) {

if (this._oldRenderer !== renderer) {
this.$.content.innerHTML = '';
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
// When clearing the rendered content, this part needs to be manually disposed of.
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
delete this.$.content._$litPart$;
}

if (renderer) {
Expand Down
6 changes: 6 additions & 0 deletions packages/vaadin-combo-box/test/item-renderer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,10 @@ describe('item renderer', () => {

expect(getFirstItem().$.content.textContent).to.equal('foo');
});

it('should clear the old content after assigning a new renderer', () => {
comboBox.opened = true;
comboBox.renderer = () => {};
expect(getFirstItem().$.content.textContent).to.equal('');
});
});
41 changes: 41 additions & 0 deletions packages/vaadin-combo-box/test/lit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync } from '@vaadin/testing-helpers';
import { html, render } from 'lit';
import '../vaadin-combo-box.js';

describe('lit', () => {
describe('renderer', () => {
let comboBox;

function getFirstItem() {
return comboBox.$.overlay._selector.querySelector('vaadin-combo-box-item');
}

beforeEach(() => {
comboBox = fixtureSync(`<vaadin-combo-box></vaadin-combo-box>`);

const size = 100;

comboBox.items = new Array(size).fill().map((e, i) => {
return { value: `value-${i}` };
});

comboBox.renderer = (root, _, { index }) => {
render(html`value-${index}`, root);
};
});

it('should render the content', () => {
comboBox.opened = true;
expect(getFirstItem().$.content.textContent).to.equal('value-0');
});

it('should render new content after assigning a new renderer', () => {
comboBox.opened = true;
comboBox.renderer = (root, _, { index }) => {
render(html`new-${index}`, root);
};
expect(getFirstItem().$.content.textContent).to.equal('new-0');
});
});
});
1 change: 1 addition & 0 deletions packages/vaadin-virtual-list/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"devDependencies": {
"@esm-bundle/chai": "^4.1.5",
"@vaadin/testing-helpers": "^0.2.1",
"lit": "^2.0.0-rc.1",
"sinon": "^9.2.4"
},
"publishConfig": {
Expand Down
24 changes: 23 additions & 1 deletion packages/vaadin-virtual-list/src/vaadin-virtual-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,36 @@ class VirtualListElement extends ElementMixin(ThemableMixin(PolymerElement)) {

/** @private */
__updateElement(el, index) {
if (el.__renderer !== this.renderer) {
el.__renderer = this.renderer;
this.__clearRenderTargetContent(el);
}

if (this.renderer) {
this.renderer(el, this, { item: this.items[index], index });
}
}

/**
* Clears the content of a render target.
* @private
*/
__clearRenderTargetContent(element) {
element.innerHTML = '';
// Whenever a Lit-based renderer is used, it assigns a Lit part to the node it was rendered into.
// When clearing the rendered content, this part needs to be manually disposed of.
// Otherwise, using a Lit-based renderer on the same node will throw an exception or render nothing afterward.
delete element._$litPart$;
}

/** @private */
__itemsOrRendererChanged(items = [], renderer, virtualizer) {
if (renderer && virtualizer) {
// If the renderer is removed but there are elements created by
// a previous renderer, we need to request an update from the virtualizer
// to get the already existing elements properly cleared.
const hasRenderedItems = this.childElementCount > 0;

if ((renderer || hasRenderedItems) && virtualizer) {
if (items.length === virtualizer.size) {
virtualizer.update();
} else {
Expand Down
36 changes: 36 additions & 0 deletions packages/vaadin-virtual-list/test/lit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync } from '@vaadin/testing-helpers';
import { html, render } from 'lit';
import '../vaadin-virtual-list.js';

describe('lit', () => {
describe('renderer', () => {
let list;

beforeEach(() => {
list = fixtureSync(`<vaadin-virtual-list></vaadin-virtual-list>`);

const size = 100;

list.items = new Array(size).fill().map((e, i) => {
return { value: `value-${i}` };
});

list.renderer = (root, _, { index }) => {
render(html`value-${index}`, root);
};
});

it('should render the content', () => {
expect(list.children[0].textContent.trim()).to.equal('value-0');
});

it('should render new content after assigning a new renderer', () => {
list.renderer = (root, _, { index }) => {
render(html`new-${index}`, root);
};

expect(list.children[0].textContent.trim()).to.equal('new-0');
});
});
});
10 changes: 10 additions & 0 deletions packages/vaadin-virtual-list/test/virtual-list.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,15 @@ describe('virtual-list', () => {
const itemRect = item.getBoundingClientRect();
expect(list.getBoundingClientRect().bottom).to.be.within(itemRect.top, itemRect.bottom);
});

it('should clear the old content after assigning a new renderer', () => {
list.renderer = () => {};
expect(list.children[0].textContent.trim()).to.equal('');
});

it('should clear the old content after removing the renderer', () => {
list.renderer = null;
expect(list.children[0].textContent.trim()).to.equal('');
});
});
});

0 comments on commit 54042b7

Please sign in to comment.