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,
+    );
+  }
 });