Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(filtered-list): allow filter nested mwc-...-list-item s #660

Merged
merged 2 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});
});
});