Do not deselect options that are filtered out.
Fixes: 340820865
Test: npm run test:unit:ci
Change-Id: Iccb759c8eed39c76fa63d4564f5da87e3e93ac9a
diff --git a/tools/winscope/src/viewers/components/select_with_filter_component.ts b/tools/winscope/src/viewers/components/select_with_filter_component.ts
index 5cf5c37..8eb116e 100644
--- a/tools/winscope/src/viewers/components/select_with_filter_component.ts
+++ b/tools/winscope/src/viewers/components/select_with_filter_component.ts
@@ -13,13 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {
- Component,
- EventEmitter,
- Input,
- Output,
- SimpleChanges,
-} from '@angular/core';
+import {Component, EventEmitter, Input, Output} from '@angular/core';
import {MatSelectChange} from '@angular/material/select';
@Component({
@@ -34,9 +28,12 @@
multiple>
<mat-form-field class="select-filter" [style]="getInnerFormFieldStyle()">
<mat-label>Filter options</mat-label>
- <input matInput #filter [(ngModel)]="filterString" (input)="onOptionsFilterChange()" />
+ <input matInput #filter [(ngModel)]="filterString" />
</mat-form-field>
- <mat-option *ngFor="let option of filteredOptions" [value]="option">
+ <mat-option
+ *ngFor="let option of options"
+ [value]="option"
+ [class.hidden-option]="!option.includes(filterString)">
{{ option }}
</mat-option>
</mat-select>
@@ -48,6 +45,10 @@
width: 100%;
font-size: 12px;
}
+
+ .hidden-option {
+ display: none;
+ }
`,
],
})
@@ -61,28 +62,11 @@
@Output() readonly selectChange = new EventEmitter<MatSelectChange>();
filterString: string = '';
- filteredOptions: string[] = this.options;
-
- ngOnChanges(changes: SimpleChanges) {
- if (changes['options']) {
- this.updateSelectOptions();
- }
- }
-
- updateSelectOptions() {
- this.filteredOptions = this.options.filter((option) =>
- option.includes(this.filterString),
- );
- }
onSelectChange(event: MatSelectChange) {
this.selectChange.emit(event);
}
- onOptionsFilterChange() {
- this.updateSelectOptions();
- }
-
getOuterFormFieldStyle() {
return {
flex: this.flex,
@@ -102,6 +86,5 @@
onSelectClosed() {
this.filterString = '';
- this.filteredOptions = this.options;
}
}
diff --git a/tools/winscope/src/viewers/components/select_with_filter_component_test.ts b/tools/winscope/src/viewers/components/select_with_filter_component_test.ts
index 067125e..937e896 100644
--- a/tools/winscope/src/viewers/components/select_with_filter_component_test.ts
+++ b/tools/winscope/src/viewers/components/select_with_filter_component_test.ts
@@ -59,27 +59,46 @@
});
it('applies filter correctly', () => {
- const trigger = assertDefined(
- htmlElement.querySelector('.mat-select-trigger'),
- ) as HTMLElement;
- trigger.click();
+ openSelectPanel();
- const selectComponent = assertDefined(component.selectWithFilterComponent);
- expect(selectComponent.filteredOptions).toEqual(['1', '2', '3']);
+ const options = getOptions();
+ checkHiddenOptions(options, []);
- const inputEl = assertDefined(
- document.querySelector('.mat-select-panel .select-filter input'),
+ const inputEl = getFilterInput();
+ dispatchInput(inputEl, '2');
+ checkHiddenOptions(options, [0, 1]);
+
+ dispatchInput(inputEl, '');
+ checkHiddenOptions(options, []);
+ });
+
+ it('maintains selection even if filtered out', () => {
+ const spy = spyOn(
+ assertDefined(component.selectWithFilterComponent).selectChange,
+ 'emit',
);
+ openSelectPanel();
- (inputEl as HTMLInputElement).value = '2';
- (inputEl as HTMLInputElement).dispatchEvent(new Event('input'));
- fixture.detectChanges();
- expect(selectComponent.filteredOptions).toEqual(['2']);
+ const options = getOptions();
+ checkHiddenOptions(options, []);
- (inputEl as HTMLInputElement).value = '';
- (inputEl as HTMLInputElement).dispatchEvent(new Event('input'));
+ (options.item(0) as HTMLElement).click();
fixture.detectChanges();
- expect(selectComponent.filteredOptions).toEqual(['1', '2', '3']);
+ checkSelectValue(spy, ['0']);
+
+ const inputEl = getFilterInput();
+
+ dispatchInput(inputEl, '2');
+ checkHiddenOptions(options, [0, 1]);
+
+ (options.item(2) as HTMLElement).click();
+ checkSelectValue(spy, ['0', '2']);
+
+ dispatchInput(inputEl, '');
+ checkHiddenOptions(options, []);
+
+ (options.item(1) as HTMLElement).click();
+ checkSelectValue(spy, ['0', '1', '2']);
});
it('applies selection correctly', () => {
@@ -87,58 +106,92 @@
assertDefined(component.selectWithFilterComponent).selectChange,
'emit',
);
- const trigger = assertDefined(
- htmlElement.querySelector('.mat-select-trigger'),
- ) as HTMLElement;
- trigger.click();
+ openSelectPanel();
- const option1 = assertDefined(
- document.querySelector('.mat-select-panel .mat-option'),
- ) as HTMLElement;
+ const options = getOptions();
- option1.click();
- expect(spy).toHaveBeenCalled();
- expect(assertDefined(spy.calls.mostRecent().args[0]).value).toEqual(['1']);
+ (options.item(0) as HTMLElement).click();
+ checkSelectValue(spy, ['0']);
- option1.click();
- expect(spy).toHaveBeenCalled();
- expect(assertDefined(spy.calls.mostRecent().args[0]).value).toEqual([]);
+ (options.item(0) as HTMLElement).click();
+ checkSelectValue(spy, []);
});
it('resets filter on close', async () => {
- const trigger = assertDefined(
- htmlElement.querySelector('.mat-select-trigger'),
- ) as HTMLElement;
- trigger.click();
+ openSelectPanel();
- const selectComponent = assertDefined(component.selectWithFilterComponent);
- expect(selectComponent.filteredOptions).toEqual(['1', '2', '3']);
+ const options = getOptions();
+ checkHiddenOptions(options, []);
- const inputEl = assertDefined(
- document.querySelector('.mat-select-panel .select-filter input'),
- );
-
- (inputEl as HTMLInputElement).value = 'A';
- (inputEl as HTMLInputElement).dispatchEvent(new Event('input'));
- fixture.detectChanges();
- expect(selectComponent.filteredOptions).toEqual([]);
+ const inputEl = getFilterInput();
+ dispatchInput(inputEl, 'A');
+ checkHiddenOptions(options, [0, 1, 2]);
(document.querySelector('.cdk-overlay-backdrop') as HTMLElement).click();
fixture.detectChanges();
await fixture.whenStable();
- expect(selectComponent.filterString).toEqual('');
- expect(selectComponent.filteredOptions).toEqual(['1', '2', '3']);
+
+ openSelectPanel();
+ checkHiddenOptions(getOptions(), []);
});
@Component({
selector: 'host-component',
- template: ` <select-with-filter [label]="label" [options]="allOptions"> </select-with-filter> `,
+ template: `
+ <select-with-filter
+ [label]="label"
+ [options]="allOptions"></select-with-filter>
+ `,
})
class TestHostComponent {
label = 'TEST FILTER';
- allOptions = ['1', '2', '3'];
+ allOptions = ['0', '1', '2'];
@ViewChild(SelectWithFilterComponent)
selectWithFilterComponent: SelectWithFilterComponent | undefined;
}
+
+ function openSelectPanel() {
+ const trigger = assertDefined(
+ htmlElement.querySelector('.mat-select-trigger'),
+ ) as HTMLElement;
+ trigger.click();
+ }
+
+ function getOptions(): NodeList {
+ return document.querySelectorAll('.mat-select-panel .mat-option');
+ }
+
+ function checkHiddenOptions(options: NodeList, hidden: number[]) {
+ expect(options.length).toEqual(3);
+ options.forEach((option, index) => {
+ expect(option.textContent).toContain(`${index}`);
+ if (hidden.includes(index)) {
+ expect((option as HTMLElement).className).toContain('hidden-option');
+ } else {
+ expect((option as HTMLElement).className).not.toContain(
+ 'hidden-option',
+ );
+ }
+ });
+ }
+
+ function getFilterInput(): HTMLInputElement {
+ return assertDefined(
+ document.querySelector('.mat-select-panel .select-filter input'),
+ ) as HTMLInputElement;
+ }
+
+ function dispatchInput(inputEl: HTMLInputElement, input: string) {
+ inputEl.value = input;
+ inputEl.dispatchEvent(new Event('input'));
+ fixture.detectChanges();
+ }
+
+ function checkSelectValue(spy: jasmine.Spy, expected: string[]) {
+ expect(spy).toHaveBeenCalled();
+ expect(assertDefined(spy.calls.mostRecent().args[0]).value).toEqual(
+ expected,
+ );
+ }
});