Skip to content

Commit

Permalink
fix(filtered-list): allow filter nested list-item s (openscd#660)
Browse files Browse the repository at this point in the history
* fix(filtered-list): allow filter nested mwc-...-list-item s

* refactor(filtered-list): exclude noninteractive from onFilter
  • Loading branch information
JakobVogelsang authored Apr 5, 2022
1 parent b9f5555 commit 1ea37c5
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 31 deletions.
44 changes: 30 additions & 14 deletions src/filtered-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => (<HTMLElement>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
*/
Expand Down Expand Up @@ -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 => (<HTMLElement>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 {
Expand Down
99 changes: 82 additions & 17 deletions test/unit/filtered-list.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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`<filtered-list multi
Expand All @@ -22,8 +25,14 @@ describe('filtered-list', () => {
><span>${item.prim}</span
><span slot="secondary">${item.sec}</span></mwc-check-list-item
>`
)}</filtered-list
>`
)}<abbr
><mwc-list-item><span>nestedItem5</span></mwc-list-item></abbr
><abbr
><div>
<mwc-radio-list-item><span>nestedItem6</span></mwc-radio-list-item>
</div></abbr
><mwc-list-item noninteractive><span>item7</span></mwc-list-item>
</filtered-list>`
);
});

Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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;
Expand All @@ -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 () => {
(<HTMLElement>(
element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox')
Expand All @@ -86,13 +99,15 @@ describe('filtered-list', () => {
expect(item).to.have.attribute('selected');
});
});

it('does not select disabled check-list-items on checkAll click', async () => {
(<HTMLElement>(
element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox')
)).click();
await element.updateComplete;
expect(element.items[3]).to.not.have.attribute('selected');
});

it('unselects all check-list-items on checkAll click', async () => {
(<HTMLElement>(
element.shadowRoot!.querySelector('mwc-formfield>mwc-checkbox')
Expand All @@ -109,38 +124,88 @@ 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 () => {
element.searchField.value = 'item item3sec';
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;
});
});
});

0 comments on commit 1ea37c5

Please sign in to comment.