From d9af57912cc1f4e68566d2d4d28500780cc03bac Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 26 Sep 2025 14:13:17 +0300 Subject: [PATCH 1/2] test: merge combo-box item renderer and class name generator tests --- .../test/item-class-name-generator.test.js | 58 ------ packages/combo-box/test/item-renderer.test.js | 141 ------------- packages/combo-box/test/items.test.js | 197 ++++++++++++++++++ 3 files changed, 197 insertions(+), 199 deletions(-) delete mode 100644 packages/combo-box/test/item-class-name-generator.test.js delete mode 100644 packages/combo-box/test/item-renderer.test.js create mode 100644 packages/combo-box/test/items.test.js diff --git a/packages/combo-box/test/item-class-name-generator.test.js b/packages/combo-box/test/item-class-name-generator.test.js deleted file mode 100644 index 09d458e51fe..00000000000 --- a/packages/combo-box/test/item-class-name-generator.test.js +++ /dev/null @@ -1,58 +0,0 @@ -import { expect } from '@vaadin/chai-plugins'; -import { fixtureSync, nextRender } from '@vaadin/testing-helpers'; -import '../src/vaadin-combo-box.js'; -import { getAllItems } from './helpers.js'; - -describe('itemClassNameGenerator', () => { - let comboBox; - - beforeEach(async () => { - comboBox = fixtureSync(''); - await nextRender(); - comboBox.items = ['foo', 'bar', 'baz']; - }); - - it('should set class name on dropdown items', async () => { - comboBox.itemClassNameGenerator = (item) => `item-${item}`; - comboBox.open(); - await nextRender(); - const items = getAllItems(comboBox); - expect(items[0].className).to.equal('item-foo'); - expect(items[1].className).to.equal('item-bar'); - expect(items[2].className).to.equal('item-baz'); - }); - - it('should remove class name when return value is empty string', async () => { - comboBox.itemClassNameGenerator = (item) => `item-${item}`; - comboBox.open(); - await nextRender(); - - comboBox.close(); - comboBox.itemClassNameGenerator = () => ''; - - comboBox.open(); - await nextRender(); - - const items = getAllItems(comboBox); - expect(items[0].className).to.equal(''); - expect(items[1].className).to.equal(''); - expect(items[2].className).to.equal(''); - }); - - it('should remove class name when generator is set to null', async () => { - comboBox.itemClassNameGenerator = (item) => `item-${item}`; - comboBox.open(); - await nextRender(); - - comboBox.close(); - comboBox.itemClassNameGenerator = null; - - comboBox.open(); - await nextRender(); - - const items = getAllItems(comboBox); - expect(items[0].className).to.equal(''); - expect(items[1].className).to.equal(''); - expect(items[2].className).to.equal(''); - }); -}); diff --git a/packages/combo-box/test/item-renderer.test.js b/packages/combo-box/test/item-renderer.test.js deleted file mode 100644 index a05f5637dd2..00000000000 --- a/packages/combo-box/test/item-renderer.test.js +++ /dev/null @@ -1,141 +0,0 @@ -import { expect } from '@vaadin/chai-plugins'; -import { fixtureSync, nextRender } from '@vaadin/testing-helpers'; -import sinon from 'sinon'; -import '../src/vaadin-combo-box.js'; -import { getAllItems, getFirstItem, setInputValue } from './helpers.js'; - -describe('item renderer', () => { - let comboBox; - - beforeEach(async () => { - comboBox = fixtureSync(''); - await nextRender(); - comboBox.items = ['foo', 'bar', 'baz']; - }); - - describe('arguments', () => { - beforeEach(() => { - comboBox.renderer = sinon.spy(); - comboBox.opened = true; - }); - - it(`should pass the 'root', 'owner', 'model' arguments to the renderer`, () => { - const [root, owner, model] = comboBox.renderer.args[0]; - - expect(root.localName).to.equal('vaadin-combo-box-item'); - expect(owner).to.eql(comboBox); - expect(model).to.deep.equal({ - item: 'foo', - index: 0, - focused: false, - selected: false, - }); - }); - - it(`should change the 'model.selected' property`, () => { - comboBox.value = 'foo'; - - const model = comboBox.renderer.lastCall.args[2]; - - expect(model.selected).to.be.true; - }); - - it(`should change the 'model.focused' property`, () => { - comboBox._focusedIndex = 0; - - const model = comboBox.renderer.lastCall.args[2]; - - expect(model.focused).to.be.true; - }); - }); - - it('should use renderer when it is defined', () => { - comboBox.renderer = (root, _comboBox, model) => { - const textNode = document.createTextNode(`${model.item} ${model.index}`); - root.appendChild(textNode); - }; - comboBox.opened = true; - - expect(getFirstItem(comboBox).textContent.trim()).to.equal('foo 0'); - }); - - it('should run renderers when requesting content update', () => { - comboBox.renderer = sinon.spy(); - comboBox.opened = true; - - expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length); - - comboBox.requestContentUpdate(); - - expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length * 2); - }); - - it('should not run renderers for invisible items', () => { - // Set up an item renderer that maps item data to DOM elements. - const itemContents = { - foo: document.createElement('div'), - bar: document.createElement('div'), - baz: document.createElement('div'), - }; - comboBox.renderer = (root, _, { item }) => { - root.textContent = ''; - root.appendChild(itemContents[item]); - }; - comboBox.opened = true; - - // Filter the items - // This renders `bar` into the first item now, and hides the other items. - // However, the second item still has `bar` as item data. - setInputValue(comboBox, 'bar'); - - const filteredItem = getAllItems(comboBox)[0]; - expect(filteredItem.children.length).to.equal(1); - expect(filteredItem.children[0]).to.equal(itemContents.bar); - - // Now run requestContentUpdate. This should only render the first item, but - // not the second one. We test this by verifying that the `bar` item content - // was not moved to the second item by its renderer. - comboBox.requestContentUpdate(); - - const allItems = getAllItems(comboBox); - expect(allItems[0].children.length).to.equal(1); - expect(allItems[0].children[0]).to.equal(itemContents.bar); - }); - - it('should not throw if requestContentUpdate() called before opening', () => { - expect(() => comboBox.requestContentUpdate()).not.to.throw(Error); - }); - - it('should render the item label when removing the renderer', () => { - comboBox.renderer = (root) => { - root.textContent = 'bar'; - }; - comboBox.opened = true; - - expect(getFirstItem(comboBox).textContent).to.equal('bar'); - - comboBox.renderer = null; - - expect(getFirstItem(comboBox).textContent).to.equal('foo'); - }); - - it('should clear the old content after assigning a new renderer', () => { - comboBox.opened = true; - comboBox.renderer = () => {}; - expect(getFirstItem(comboBox).textContent).to.equal(''); - }); - - it('should restore filtered item content', () => { - const contentNodes = comboBox.items.map((item) => document.createTextNode(item)); - - comboBox.renderer = (root, _, { item }) => { - root.textContent = ''; - root.append(contentNodes[comboBox.items.indexOf(item)]); - }; - - comboBox.opened = true; - setInputValue(comboBox, 'r'); - setInputValue(comboBox, ''); - expect(getAllItems(comboBox)[1].textContent).to.equal('bar'); - }); -}); diff --git a/packages/combo-box/test/items.test.js b/packages/combo-box/test/items.test.js new file mode 100644 index 00000000000..dadc0fb5334 --- /dev/null +++ b/packages/combo-box/test/items.test.js @@ -0,0 +1,197 @@ +import { expect } from '@vaadin/chai-plugins'; +import { fixtureSync, nextRender } from '@vaadin/testing-helpers'; +import sinon from 'sinon'; +import '../src/vaadin-combo-box.js'; +import { getAllItems, getFirstItem, setInputValue } from './helpers.js'; + +describe('items', () => { + let comboBox; + + describe('renderer', () => { + beforeEach(async () => { + comboBox = fixtureSync(''); + await nextRender(); + comboBox.items = ['foo', 'bar', 'baz']; + }); + + describe('arguments', () => { + beforeEach(() => { + comboBox.renderer = sinon.spy(); + comboBox.opened = true; + }); + + it(`should pass the 'root', 'owner', 'model' arguments to the renderer`, () => { + const [root, owner, model] = comboBox.renderer.args[0]; + + expect(root.localName).to.equal('vaadin-combo-box-item'); + expect(owner).to.equal(comboBox); + expect(model).to.deep.equal({ + item: 'foo', + index: 0, + focused: false, + selected: false, + }); + }); + + it(`should change the 'model.selected' property`, () => { + comboBox.value = 'foo'; + + const model = comboBox.renderer.lastCall.args[2]; + + expect(model.selected).to.be.true; + }); + + it(`should change the 'model.focused' property`, () => { + comboBox._focusedIndex = 0; + + const model = comboBox.renderer.lastCall.args[2]; + + expect(model.focused).to.be.true; + }); + }); + + it('should use renderer when it is defined', () => { + comboBox.renderer = (root, _comboBox, model) => { + const textNode = document.createTextNode(`${model.item} ${model.index}`); + root.appendChild(textNode); + }; + comboBox.opened = true; + + expect(getFirstItem(comboBox).textContent.trim()).to.equal('foo 0'); + }); + + it('should run renderers when requesting content update', () => { + comboBox.renderer = sinon.spy(); + comboBox.opened = true; + + expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length); + + comboBox.requestContentUpdate(); + + expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length * 2); + }); + + it('should not run renderers for invisible items', () => { + // Set up an item renderer that maps item data to DOM elements. + const itemContents = { + foo: document.createElement('div'), + bar: document.createElement('div'), + baz: document.createElement('div'), + }; + comboBox.renderer = (root, _, { item }) => { + root.textContent = ''; + root.appendChild(itemContents[item]); + }; + comboBox.opened = true; + + // Filter the items + // This renders `bar` into the first item now, and hides the other items. + // However, the second item still has `bar` as item data. + setInputValue(comboBox, 'bar'); + + const filteredItem = getAllItems(comboBox)[0]; + expect(filteredItem.children.length).to.equal(1); + expect(filteredItem.children[0]).to.equal(itemContents.bar); + + // Now run requestContentUpdate. This should only render the first item, but + // not the second one. We test this by verifying that the `bar` item content + // was not moved to the second item by its renderer. + comboBox.requestContentUpdate(); + + const allItems = getAllItems(comboBox); + expect(allItems[0].children.length).to.equal(1); + expect(allItems[0].children[0]).to.equal(itemContents.bar); + }); + + it('should not throw if requestContentUpdate() called before opening', () => { + expect(() => comboBox.requestContentUpdate()).not.to.throw(Error); + }); + + it('should render the item label when removing the renderer', () => { + comboBox.renderer = (root) => { + root.textContent = 'bar'; + }; + comboBox.opened = true; + + expect(getFirstItem(comboBox).textContent).to.equal('bar'); + + comboBox.renderer = null; + + expect(getFirstItem(comboBox).textContent).to.equal('foo'); + }); + + it('should clear the old content after assigning a new renderer', () => { + comboBox.opened = true; + comboBox.renderer = () => {}; + expect(getFirstItem(comboBox).textContent).to.equal(''); + }); + + it('should restore filtered item content', () => { + const contentNodes = comboBox.items.map((item) => document.createTextNode(item)); + + comboBox.renderer = (root, _, { item }) => { + root.textContent = ''; + root.append(contentNodes[comboBox.items.indexOf(item)]); + }; + + comboBox.opened = true; + setInputValue(comboBox, 'r'); + setInputValue(comboBox, ''); + expect(getAllItems(comboBox)[1].textContent).to.equal('bar'); + }); + }); + + describe('itemClassNameGenerator', () => { + let comboBox; + + beforeEach(async () => { + comboBox = fixtureSync(''); + await nextRender(); + comboBox.items = ['foo', 'bar', 'baz']; + }); + + it('should set class name on dropdown items', async () => { + comboBox.itemClassNameGenerator = (item) => `item-${item}`; + comboBox.open(); + await nextRender(); + const items = getAllItems(comboBox); + expect(items[0].className).to.equal('item-foo'); + expect(items[1].className).to.equal('item-bar'); + expect(items[2].className).to.equal('item-baz'); + }); + + it('should remove class name when return value is empty string', async () => { + comboBox.itemClassNameGenerator = (item) => `item-${item}`; + comboBox.open(); + await nextRender(); + + comboBox.close(); + comboBox.itemClassNameGenerator = () => ''; + + comboBox.open(); + await nextRender(); + + const items = getAllItems(comboBox); + expect(items[0].className).to.equal(''); + expect(items[1].className).to.equal(''); + expect(items[2].className).to.equal(''); + }); + + it('should remove class name when generator is set to null', async () => { + comboBox.itemClassNameGenerator = (item) => `item-${item}`; + comboBox.open(); + await nextRender(); + + comboBox.close(); + comboBox.itemClassNameGenerator = null; + + comboBox.open(); + await nextRender(); + + const items = getAllItems(comboBox); + expect(items[0].className).to.equal(''); + expect(items[1].className).to.equal(''); + expect(items[2].className).to.equal(''); + }); + }); +}); From b5b36037a5aad59cb684d2532af7019aa01becbc Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 26 Sep 2025 14:32:27 +0300 Subject: [PATCH 2/2] test: merge MSCB item renderer and itemClassNameGenerator tests --- .../multi-select-combo-box/test/basic.test.js | 74 ------- .../multi-select-combo-box/test/chips.test.js | 54 ----- .../multi-select-combo-box/test/items.test.js | 187 ++++++++++++++++++ 3 files changed, 187 insertions(+), 128 deletions(-) create mode 100644 packages/multi-select-combo-box/test/items.test.js diff --git a/packages/multi-select-combo-box/test/basic.test.js b/packages/multi-select-combo-box/test/basic.test.js index fb454469ccf..3732801b1fb 100644 --- a/packages/multi-select-combo-box/test/basic.test.js +++ b/packages/multi-select-combo-box/test/basic.test.js @@ -333,80 +333,6 @@ describe('basic', () => { }); }); - describe('renderer', () => { - it('should pass the "root", "owner", "model" arguments to the renderer', () => { - const spy = sinon.spy(); - comboBox.renderer = spy; - comboBox.opened = true; - - const [root, owner, model] = spy.firstCall.args; - - expect(root.localName).to.equal('vaadin-multi-select-combo-box-item'); - expect(owner).to.eql(comboBox); - expect(model).to.deep.equal({ - item: 'apple', - index: 0, - focused: false, - selected: false, - }); - }); - - it('should use renderer when it is defined', () => { - comboBox.renderer = (root, _, model) => { - const textNode = document.createTextNode(`${model.item} ${model.index}`); - root.appendChild(textNode); - }; - comboBox.opened = true; - - const item = getFirstItem(comboBox); - expect(item.textContent.trim()).to.equal('apple 0'); - }); - - it('should run renderers when requesting content update', () => { - comboBox.renderer = sinon.spy(); - comboBox.opened = true; - - expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length); - - comboBox.renderer.resetHistory(); - comboBox.requestContentUpdate(); - - expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length); - }); - - it('should not throw if requestContentUpdate() called before opening', () => { - expect(() => comboBox.requestContentUpdate()).not.to.throw(Error); - }); - - it('should not throw if requestContentUpdate() called before attached', () => { - const combo = document.createElement('vaadin-multi-select-combo-box'); - expect(() => { - combo.requestContentUpdate(); - }).to.not.throw(Error); - }); - - it('should render the item label when removing the renderer', () => { - comboBox.renderer = (root, _, model) => { - root.textContent = model.item.toUpperCase(); - }; - comboBox.opened = true; - - const item = getFirstItem(comboBox); - expect(item.textContent).to.equal('APPLE'); - - comboBox.renderer = null; - - expect(item.textContent).to.equal('apple'); - }); - - it('should clear the old content after assigning a new renderer', () => { - comboBox.opened = true; - comboBox.renderer = () => {}; - const item = getFirstItem(comboBox); - expect(item.textContent).to.equal(''); - }); - }); - describe('exportparts', () => { let overlay; diff --git a/packages/multi-select-combo-box/test/chips.test.js b/packages/multi-select-combo-box/test/chips.test.js index ba7f0994341..513d98d8ec5 100644 --- a/packages/multi-select-combo-box/test/chips.test.js +++ b/packages/multi-select-combo-box/test/chips.test.js @@ -574,58 +574,4 @@ describe('chips', () => { expect(overlayPart.offsetWidth).to.be.equal(comboBox.clientWidth); }); }); - - describe('itemClassNameGenerator', () => { - beforeEach(() => { - comboBox.autoExpandHorizontally = true; - }); - - it('should set class name on the selected item chips', async () => { - comboBox.itemClassNameGenerator = (item) => item; - comboBox.selectedItems = ['apple', 'lemon']; - await nextRender(); - - const chips = getChips(comboBox); - expect(chips[1].className).to.equal('apple'); - expect(chips[2].className).to.equal('lemon'); - }); - - it('should set class name when generator set after selecting', async () => { - comboBox.selectedItems = ['apple', 'lemon']; - await nextRender(); - - comboBox.itemClassNameGenerator = (item) => item; - await nextRender(); - - const chips = getChips(comboBox); - expect(chips[1].className).to.equal('apple'); - expect(chips[2].className).to.equal('lemon'); - }); - - it('should remove class name when generator returns empty string', async () => { - comboBox.itemClassNameGenerator = (item) => item; - comboBox.selectedItems = ['apple', 'lemon']; - await nextRender(); - - comboBox.itemClassNameGenerator = () => ''; - await nextRender(); - - const chips = getChips(comboBox); - expect(chips[1].className).to.equal(''); - expect(chips[2].className).to.equal(''); - }); - - it('should remove class name when generator is set to null', async () => { - comboBox.itemClassNameGenerator = (item) => item; - comboBox.selectedItems = ['apple', 'lemon']; - await nextRender(); - - comboBox.itemClassNameGenerator = null; - await nextRender(); - - const chips = getChips(comboBox); - expect(chips[1].className).to.equal(''); - expect(chips[2].className).to.equal(''); - }); - }); }); diff --git a/packages/multi-select-combo-box/test/items.test.js b/packages/multi-select-combo-box/test/items.test.js new file mode 100644 index 00000000000..9e2cfc327f7 --- /dev/null +++ b/packages/multi-select-combo-box/test/items.test.js @@ -0,0 +1,187 @@ +import { expect } from '@vaadin/chai-plugins'; +import { fixtureSync, nextRender } from '@vaadin/testing-helpers'; +import sinon from 'sinon'; +import '../src/vaadin-multi-select-combo-box.js'; +import { getAllItems, getFirstItem } from './helpers.js'; + +describe('items', () => { + let comboBox; + + describe('renderer', () => { + beforeEach(async () => { + comboBox = fixtureSync(''); + await nextRender(); + comboBox.items = ['foo', 'bar', 'baz']; + }); + + it('should pass the "root", "owner", "model" arguments to the renderer', () => { + const spy = sinon.spy(); + comboBox.renderer = spy; + comboBox.opened = true; + + const [root, owner, model] = spy.firstCall.args; + + expect(root.localName).to.equal('vaadin-multi-select-combo-box-item'); + expect(owner).to.eql(comboBox); + expect(model).to.deep.equal({ + item: 'foo', + index: 0, + focused: false, + selected: false, + }); + }); + + it('should use renderer when it is defined', () => { + comboBox.renderer = (root, _, model) => { + const textNode = document.createTextNode(`${model.item} ${model.index}`); + root.appendChild(textNode); + }; + comboBox.opened = true; + + expect(getFirstItem(comboBox).textContent.trim()).to.equal('foo 0'); + }); + + it('should run renderers when requesting content update', () => { + comboBox.renderer = sinon.spy(); + comboBox.opened = true; + + expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length); + + comboBox.renderer.resetHistory(); + comboBox.requestContentUpdate(); + + expect(comboBox.renderer.callCount).to.be.equal(comboBox.items.length); + }); + + it('should not throw if requestContentUpdate() called before attached', () => { + const combo = document.createElement('vaadin-multi-select-combo-box'); + expect(() => { + combo.requestContentUpdate(); + }).to.not.throw(Error); + }); + + it('should render the item label when removing the renderer', () => { + comboBox.renderer = (root, _, model) => { + root.textContent = model.item.toUpperCase(); + }; + comboBox.opened = true; + + expect(getFirstItem(comboBox).textContent).to.equal('FOO'); + + comboBox.renderer = null; + + expect(getFirstItem(comboBox).textContent).to.equal('foo'); + }); + + it('should clear the old content after assigning a new renderer', () => { + comboBox.opened = true; + comboBox.renderer = () => {}; + expect(getFirstItem(comboBox).textContent).to.equal(''); + }); + }); + + describe('itemClassNameGenerator', () => { + let comboBox; + + const getChips = (combo) => combo.querySelectorAll('vaadin-multi-select-combo-box-chip'); + + beforeEach(async () => { + comboBox = fixtureSync(''); + await nextRender(); + comboBox.autoExpandHorizontally = true; + comboBox.items = ['foo', 'bar', 'baz']; + }); + + it('should set class name on dropdown items', async () => { + comboBox.itemClassNameGenerator = (item) => `item-${item}`; + comboBox.open(); + await nextRender(); + const items = getAllItems(comboBox); + expect(items[0].className).to.equal('item-foo'); + expect(items[1].className).to.equal('item-bar'); + expect(items[2].className).to.equal('item-baz'); + }); + + it('should remove item class name when return value is empty string', async () => { + comboBox.itemClassNameGenerator = (item) => `item-${item}`; + comboBox.open(); + await nextRender(); + + comboBox.close(); + comboBox.itemClassNameGenerator = () => ''; + + comboBox.open(); + await nextRender(); + + const items = getAllItems(comboBox); + expect(items[0].className).to.equal(''); + expect(items[1].className).to.equal(''); + expect(items[2].className).to.equal(''); + }); + + it('should remove item class name when generator is set to null', async () => { + comboBox.itemClassNameGenerator = (item) => `item-${item}`; + comboBox.open(); + await nextRender(); + + comboBox.close(); + comboBox.itemClassNameGenerator = null; + + comboBox.open(); + await nextRender(); + + const items = getAllItems(comboBox); + expect(items[0].className).to.equal(''); + expect(items[1].className).to.equal(''); + expect(items[2].className).to.equal(''); + }); + + it('should set class name on the selected item chips', async () => { + comboBox.itemClassNameGenerator = (item) => item; + comboBox.selectedItems = ['foo', 'bar']; + await nextRender(); + + const chips = getChips(comboBox); + expect(chips[1].className).to.equal('foo'); + expect(chips[2].className).to.equal('bar'); + }); + + it('should set chip class name when generator set after selecting', async () => { + comboBox.selectedItems = ['foo', 'bar']; + await nextRender(); + + comboBox.itemClassNameGenerator = (item) => item; + await nextRender(); + + const chips = getChips(comboBox); + expect(chips[1].className).to.equal('foo'); + expect(chips[2].className).to.equal('bar'); + }); + + it('should remove chip class name when generator returns empty string', async () => { + comboBox.itemClassNameGenerator = (item) => item; + comboBox.selectedItems = ['foo', 'bar']; + await nextRender(); + + comboBox.itemClassNameGenerator = () => ''; + await nextRender(); + + const chips = getChips(comboBox); + expect(chips[1].className).to.equal(''); + expect(chips[2].className).to.equal(''); + }); + + it('should remove chip class name when generator is set to null', async () => { + comboBox.itemClassNameGenerator = (item) => item; + comboBox.selectedItems = ['foo', 'bar']; + await nextRender(); + + comboBox.itemClassNameGenerator = null; + await nextRender(); + + const chips = getChips(comboBox); + expect(chips[1].className).to.equal(''); + expect(chips[2].className).to.equal(''); + }); + }); +});