go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/components/auto_complete/auto_complete.test.ts (about)

     1  // Copyright 2021 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  import { fixture } from '@open-wc/testing-helpers';
    16  import { html } from 'lit';
    17  
    18  import './auto_complete';
    19  import {
    20    AutoCompleteElement,
    21    Suggestion,
    22    SuggestionEntry,
    23  } from './auto_complete';
    24  
    25  function simulateKeyStroke(target: EventTarget, code: string) {
    26    target.dispatchEvent(
    27      new KeyboardEvent('keydown', {
    28        bubbles: true,
    29        composed: true,
    30        code,
    31      } as KeyboardEventInit),
    32    );
    33    target.dispatchEvent(
    34      new KeyboardEvent('keyup', {
    35        bubbles: true,
    36        composed: true,
    37        code,
    38      } as KeyboardEventInit),
    39    );
    40  }
    41  
    42  const suggestions: Suggestion[] = [
    43    { value: 'suggestion 1', explanation: 'explanation 1' },
    44    { value: 'suggestion 2', explanation: 'explanation 2' },
    45    { value: 'suggestion 3', explanation: 'explanation 3' },
    46    { isHeader: true, display: 'header' },
    47    { value: 'suggestion 4', explanation: 'explanation 4' },
    48    { value: 'suggestion 5', explanation: 'explanation 5' },
    49    { isHeader: true, display: 'header' },
    50  ];
    51  
    52  describe('AuthComplete', () => {
    53    let autoSuggestionEle: AutoCompleteElement;
    54    let inputEle: HTMLInputElement;
    55    let suggestionSpy: jest.SpiedFunction<(_suggestion: SuggestionEntry) => void>;
    56    let completeSpy: jest.SpiedFunction<() => void>;
    57  
    58    beforeAll(async () => {
    59      autoSuggestionEle = await fixture<AutoCompleteElement>(html`
    60        <milo-auto-complete
    61          .value=${'search text'}
    62          .placeHolder=${'Press / to search test results...'}
    63          .suggestions=${suggestions}
    64        >
    65        </milo-auto-complete>
    66      `);
    67      inputEle = autoSuggestionEle.shadowRoot!.querySelector('input')!;
    68      suggestionSpy = jest.spyOn(autoSuggestionEle, 'onSuggestionSelected');
    69      completeSpy = jest.spyOn(autoSuggestionEle, 'onComplete');
    70    });
    71  
    72    test('should be able to select suggestion with key strokes', () => {
    73      simulateKeyStroke(inputEle, 'ArrowDown');
    74      simulateKeyStroke(inputEle, 'ArrowDown');
    75      simulateKeyStroke(inputEle, 'Enter');
    76      expect(suggestionSpy.mock.lastCall?.[0]).toStrictEqual(suggestions[1]);
    77      expect(completeSpy.mock.calls.length).toStrictEqual(0);
    78    });
    79  
    80    test('should reset suggestion selection when suggestions are updated', () => {
    81      const suggestionSpy = jest.spyOn(autoSuggestionEle, 'onSuggestionSelected');
    82      const completeSpy = jest.spyOn(autoSuggestionEle, 'onComplete');
    83      autoSuggestionEle.suggestions = suggestions.slice(0, 2);
    84      simulateKeyStroke(inputEle, 'ArrowDown');
    85      simulateKeyStroke(inputEle, 'ArrowDown');
    86      simulateKeyStroke(inputEle, 'Enter');
    87      expect(suggestionSpy.mock.lastCall?.[0]).toStrictEqual(suggestions[1]);
    88  
    89      autoSuggestionEle.suggestions = suggestions.slice(0);
    90      simulateKeyStroke(inputEle, 'ArrowDown');
    91      simulateKeyStroke(inputEle, 'Enter');
    92      expect(suggestionSpy.mock.lastCall?.[0]).toStrictEqual(suggestions[0]);
    93  
    94      expect(completeSpy.mock.calls.length).toStrictEqual(0);
    95    });
    96  
    97    test('should skip suggestion headers when selecting with key strokes', () => {
    98      autoSuggestionEle.suggestions = suggestions.slice();
    99      simulateKeyStroke(inputEle, 'ArrowDown');
   100      simulateKeyStroke(inputEle, 'ArrowDown');
   101      simulateKeyStroke(inputEle, 'ArrowDown');
   102      simulateKeyStroke(inputEle, 'ArrowDown');
   103      simulateKeyStroke(inputEle, 'Enter');
   104      expect(suggestionSpy.mock.lastCall?.[0]).toStrictEqual(suggestions[4]);
   105  
   106      simulateKeyStroke(inputEle, 'ArrowDown');
   107      simulateKeyStroke(inputEle, 'ArrowDown');
   108      simulateKeyStroke(inputEle, 'ArrowDown');
   109      simulateKeyStroke(inputEle, 'ArrowDown');
   110      simulateKeyStroke(inputEle, 'ArrowUp');
   111      simulateKeyStroke(inputEle, 'Enter');
   112      expect(suggestionSpy.mock.lastCall?.[0]).toStrictEqual(suggestions[2]);
   113  
   114      expect(completeSpy.mock.calls.length).toStrictEqual(0);
   115    });
   116  
   117    test('should not navigate beyond boundary', () => {
   118      autoSuggestionEle.suggestions = suggestions.slice();
   119      for (let i = 0; i < suggestions.length * 2; ++i) {
   120        simulateKeyStroke(inputEle, 'ArrowDown');
   121      }
   122      simulateKeyStroke(inputEle, 'Enter');
   123      const lastSelectableSuggestion = suggestions
   124        .slice()
   125        .reverse()
   126        .find((s) => !s.isHeader);
   127      expect(suggestionSpy.mock.lastCall?.[0]).toStrictEqual(
   128        lastSelectableSuggestion,
   129      );
   130  
   131      const firstSelectableSuggestion = suggestions.find((s) => !s.isHeader);
   132      simulateKeyStroke(inputEle, 'ArrowDown');
   133      for (let i = 0; i < suggestions.length * 2; ++i) {
   134        simulateKeyStroke(inputEle, 'ArrowUp');
   135      }
   136      simulateKeyStroke(inputEle, 'Enter');
   137      expect(suggestionSpy.mock.lastCall?.[0]).toStrictEqual(
   138        firstSelectableSuggestion,
   139      );
   140  
   141      expect(completeSpy.mock.calls.length).toStrictEqual(0);
   142    });
   143  
   144    test('should call onComplete when user hit enter with completed query', () => {
   145      autoSuggestionEle.value = 'search text ';
   146      simulateKeyStroke(inputEle, 'Enter');
   147      expect(completeSpy.mock.calls.length).toStrictEqual(1);
   148    });
   149  });