From 1ea37c5c490de52b538201d3a4fb39291b239771 Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Tue, 5 Apr 2022 19:48:00 +0200 Subject: [PATCH] fix(filtered-list): allow filter nested list-item s (#660) * fix(filtered-list): allow filter nested mwc-...-list-item s * refactor(filtered-list): exclude noninteractive from onFilter --- src/filtered-list.ts | 44 ++++++++++----- test/unit/filtered-list.test.ts | 99 +++++++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 31 deletions(-) diff --git a/src/filtered-list.ts b/src/filtered-list.ts index 49ce708950..6720ed10b4 100644 --- a/src/filtered-list.ts +++ b/src/filtered-list.ts @@ -16,8 +16,29 @@ import '@material/mwc-textfield'; import { CheckListItem } from '@material/mwc-list/mwc-check-list-item'; import { List } from '@material/mwc-list'; import { ListBase } from '@material/mwc-list/mwc-list-base'; +import { ListItemBase } from '@material/mwc-list/mwc-list-item-base'; import { TextField } from '@material/mwc-textfield'; +function slotItem(item: Element): Element { + if (!item.closest('filtered-list') || !item.parentElement) return item; + if (item.parentElement instanceof FilteredList) return item; + return slotItem(item.parentElement); +} + +function hideFiltered(item: ListItemBase, searchText: string): void { + const itemInnerText = item.innerText + '\n'; + const childInnerText = Array.from(item.children) + .map(child => (child).innerText) + .join('\n'); + const filterTarget: string = (itemInnerText + childInnerText).toUpperCase(); + + const terms: string[] = searchText.toUpperCase().split(' '); + + terms.some(term => !filterTarget.includes(term)) + ? slotItem(item).classList.add('hidden') + : slotItem(item).classList.remove('hidden'); +} + /** * A mwc-list with mwc-textfield that filters the list items for given or separated terms */ @@ -61,20 +82,15 @@ export class FilteredList extends ListBase { } onFilterInput(): void { - this.items.forEach(item => { - const text: string = ( - item.innerText + - '\n' + - Array.from(item.children) - .map(child => (child).innerText) - .join('\n') - ).toUpperCase(); - const terms: string[] = this.searchField.value.toUpperCase().split(' '); - - terms.some(term => !text.includes(term)) - ? item.classList.add('hidden') - : item.classList.remove('hidden'); - }); + Array.from( + this.querySelectorAll( + 'mwc-list-item, mwc-check-list-item, mwc-radio-list-item' + ) + ) + .filter(item => !(item as ListItemBase).noninteractive) + .forEach(item => + hideFiltered(item as ListItemBase, this.searchField.value) + ); } protected onListItemConnected(e: CustomEvent): void { diff --git a/test/unit/filtered-list.test.ts b/test/unit/filtered-list.test.ts index e38a3ffda6..74760109ba 100644 --- a/test/unit/filtered-list.test.ts +++ b/test/unit/filtered-list.test.ts @@ -1,6 +1,8 @@ import { expect, fixture, html } from '@open-wc/testing'; import '@material/mwc-list/mwc-check-list-item'; +import '@material/mwc-list/mwc-list-item'; +import '@material/mwc-list/mwc-radio-list-item'; import '../../src/filtered-list.js'; import { FilteredList } from '../../src/filtered-list.js'; @@ -13,6 +15,7 @@ describe('filtered-list', () => { { prim: 'item3', sec: 'item3sec', disabled: false }, { prim: 'item4', sec: 'item4sec', disabled: true }, ]; + beforeEach(async () => { element = await fixture( html` { >${item.prim}${item.sec}` - )}` + )}nestedItem5
+ nestedItem6 +
item7 + ` ); }); @@ -44,6 +53,7 @@ describe('filtered-list', () => { element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') ).to.have.attribute('indeterminate'); }); + it('is selected if all check-list-items are selected', async () => { expect( element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') @@ -59,6 +69,7 @@ describe('filtered-list', () => { element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') ).to.have.attribute('checked'); }); + it('is none of the above if no check-list-item is selected', () => { expect( element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') @@ -67,6 +78,7 @@ describe('filtered-list', () => { element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') ).to.not.have.attribute('indeterminate'); }); + it('can be disabled with disableCheckAll property', async () => { expect(element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox')).to .not.be.null; @@ -75,6 +87,7 @@ describe('filtered-list', () => { expect(element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox')).to .be.null; }); + it('selects all enabled and visable check-list-items on checkAll click', async () => { (( element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') @@ -86,6 +99,7 @@ describe('filtered-list', () => { expect(item).to.have.attribute('selected'); }); }); + it('does not select disabled check-list-items on checkAll click', async () => { (( element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') @@ -93,6 +107,7 @@ describe('filtered-list', () => { await element.updateComplete; expect(element.items[3]).to.not.have.attribute('selected'); }); + it('unselects all check-list-items on checkAll click', async () => { (( element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox') @@ -109,27 +124,32 @@ describe('filtered-list', () => { }); }); - describe('onFilterInput', () => { - it('filteres its items', async () => { + describe('allows to filter on', () => { + it('directly slotted mwc-check-list-item', async () => { element.searchField.value = 'item1'; element.onFilterInput(); element.requestUpdate(); await element.updateComplete; - expect(element.items[0].classList.contains('hidden')).to.be.false; - expect(element.items[1].classList.contains('hidden')).to.be.true; - expect(element.items[2].classList.contains('hidden')).to.be.true; - expect(element.items[3].classList.contains('hidden')).to.be.true; + expect(element.children[0].classList.contains('hidden')).to.be.false; + expect(element.children[1].classList.contains('hidden')).to.be.true; + expect(element.children[2].classList.contains('hidden')).to.be.true; + expect(element.children[3].classList.contains('hidden')).to.be.true; + expect(element.children[4].classList.contains('hidden')).to.be.true; + expect(element.children[5].classList.contains('hidden')).to.be.true; }); - it('filteres within twoline mwc-list-item', async () => { + it('directly slotted twoline mwc-check-list-item', async () => { element.searchField.value = 'item2sec'; element.onFilterInput(); element.requestUpdate(); await element.updateComplete; - expect(element.items[0].classList.contains('hidden')).to.be.true; - expect(element.items[1].classList.contains('hidden')).to.be.false; - expect(element.items[2].classList.contains('hidden')).to.be.true; - expect(element.items[3].classList.contains('hidden')).to.be.true; + expect(element.children[0].classList.contains('hidden')).to.be.true; + expect(element.children[1].classList.contains('hidden')).to.be.false; + expect(element.children[2].classList.contains('hidden')).to.be.true; + expect(element.children[3].classList.contains('hidden')).to.be.true; + expect(element.children[4].classList.contains('hidden')).to.be.true; + expect(element.children[5].classList.contains('hidden')).to.be.true; + expect(element.children[6].classList.contains('hidden')).to.be.false; }); it('uses space as logic AND ', async () => { @@ -137,10 +157,55 @@ describe('filtered-list', () => { element.onFilterInput(); element.requestUpdate(); await element.updateComplete; - expect(element.items[0].classList.contains('hidden')).to.be.true; - expect(element.items[1].classList.contains('hidden')).to.be.true; - expect(element.items[2].classList.contains('hidden')).to.be.false; - expect(element.items[3].classList.contains('hidden')).to.be.true; + expect(element.children[0].classList.contains('hidden')).to.be.true; + expect(element.children[1].classList.contains('hidden')).to.be.true; + expect(element.children[2].classList.contains('hidden')).to.be.false; + expect(element.children[3].classList.contains('hidden')).to.be.true; + expect(element.children[4].classList.contains('hidden')).to.be.true; + expect(element.children[5].classList.contains('hidden')).to.be.true; + expect(element.children[6].classList.contains('hidden')).to.be.false; + }); + + it('nested mwc-list-item elements', async () => { + element.searchField.value = 'nesteditem5'; + element.onFilterInput(); + element.requestUpdate(); + await element.updateComplete; + expect(element.children[0].classList.contains('hidden')).to.be.true; + expect(element.children[1].classList.contains('hidden')).to.be.true; + expect(element.children[2].classList.contains('hidden')).to.be.true; + expect(element.children[3].classList.contains('hidden')).to.be.true; + expect(element.children[4].classList.contains('hidden')).to.be.false; + expect(element.children[5].classList.contains('hidden')).to.be.true; + expect(element.children[6].classList.contains('hidden')).to.be.false; + }); + + it('nested mwc-radio-list-item elements', async () => { + element.searchField.value = 'nesteditem6'; + element.onFilterInput(); + element.requestUpdate(); + await element.updateComplete; + expect(element.children[0].classList.contains('hidden')).to.be.true; + expect(element.children[1].classList.contains('hidden')).to.be.true; + expect(element.children[2].classList.contains('hidden')).to.be.true; + expect(element.children[3].classList.contains('hidden')).to.be.true; + expect(element.children[4].classList.contains('hidden')).to.be.true; + expect(element.children[5].classList.contains('hidden')).to.be.false; + expect(element.children[6].classList.contains('hidden')).to.be.false; + }); + + it('onyl on noninteractive list itmes', async () => { + element.searchField.value = 'item7'; + element.onFilterInput(); + element.requestUpdate(); + await element.updateComplete; + expect(element.children[0].classList.contains('hidden')).to.be.true; + expect(element.children[1].classList.contains('hidden')).to.be.true; + expect(element.children[2].classList.contains('hidden')).to.be.true; + expect(element.children[3].classList.contains('hidden')).to.be.true; + expect(element.children[4].classList.contains('hidden')).to.be.true; + expect(element.children[5].classList.contains('hidden')).to.be.true; + expect(element.children[6].classList.contains('hidden')).to.be.false; }); }); });