go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/queries/tr_search_query.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 {
    16    TestResult,
    17    TestStatus,
    18    TestVariant,
    19    TestVariantStatus,
    20  } from '@/common/services/resultdb';
    21  
    22  import {
    23    parseTestResultSearchQuery,
    24    suggestTestResultSearchQuery,
    25  } from './tr_search_query';
    26  
    27  const variant1: TestVariant = {
    28    testId: 'invocation-a/test-suite-a/test-1',
    29    sourcesId: '1',
    30    variant: { def: { key1: 'val1' } },
    31    variantHash: 'key1:val1',
    32    testMetadata: {
    33      name: 'test-name-1',
    34    },
    35    status: TestVariantStatus.UNEXPECTED,
    36    results: [
    37      {
    38        result: {
    39          status: TestStatus.Fail,
    40          tags: [{ key: 'tag-key-1', value: 'tag-val-1' }],
    41          duration: '10s',
    42        } as TestResult,
    43      },
    44      {
    45        result: {
    46          status: TestStatus.Fail,
    47          tags: [{ key: 'tag-key-1', value: 'tag-val-1=1' }],
    48          duration: '15s',
    49        } as TestResult,
    50      },
    51      {
    52        result: {
    53          status: TestStatus.Skip,
    54          tags: [{ key: 'tag-key-2', value: 'tag-val-2' }],
    55          duration: '20s',
    56        } as TestResult,
    57      },
    58    ],
    59  };
    60  
    61  const variant2: TestVariant = {
    62    testId: 'invocation-a/test-suite-a/test-2',
    63    sourcesId: '1',
    64    variant: { def: { key1: 'val2' } },
    65    variantHash: 'key1:val2',
    66    testMetadata: {
    67      name: 'test-name-2',
    68    },
    69    status: TestVariantStatus.UNEXPECTED,
    70    results: [
    71      {
    72        result: {
    73          tags: [{ key: 'tag-key-1', value: 'unknown-val' }],
    74          status: TestStatus.Fail,
    75          duration: '30s',
    76        } as TestResult,
    77      },
    78      {
    79        result: {
    80          status: TestStatus.Fail,
    81          tags: [
    82            { key: 'duplicated-tag-key', value: 'first-tag-val' },
    83            { key: 'duplicated-tag-key', value: 'second-tag-val' },
    84          ],
    85        } as TestResult,
    86      },
    87    ],
    88  };
    89  
    90  const variant3: TestVariant = {
    91    testId: 'invocation-a/test-suite-b/test-3',
    92    sourcesId: '1',
    93    variant: { def: { key1: 'val3' } },
    94    variantHash: 'key1:val3',
    95    testMetadata: {
    96      name: 'test',
    97    },
    98    status: TestVariantStatus.FLAKY,
    99    results: [
   100      {
   101        result: {
   102          status: TestStatus.Pass,
   103        } as TestResult,
   104      },
   105      {
   106        result: {
   107          status: TestStatus.Fail,
   108        } as TestResult,
   109      },
   110    ],
   111  };
   112  
   113  const variant4: TestVariant = {
   114    testId: 'invocation-a/test-suite-B/test-4',
   115    sourcesId: '1',
   116    variant: { def: { key1: 'val2' } },
   117    variantHash: 'key1:val2',
   118    status: TestVariantStatus.EXONERATED,
   119  };
   120  
   121  const variant5: TestVariant = {
   122    testId: 'invocation-a/test-suite-B/test-5',
   123    sourcesId: '1',
   124    variant: { def: { key1: 'val2', key2: 'val1' } },
   125    variantHash: 'key1:val2|key2:val1',
   126    status: TestVariantStatus.EXPECTED,
   127    results: [
   128      {
   129        result: {
   130          status: TestStatus.Pass,
   131        } as TestResult,
   132      },
   133    ],
   134  };
   135  
   136  const variant6: TestVariant = {
   137    testId: 'invocation-a/test-suite-b/test-5',
   138    sourcesId: '1',
   139    variant: { def: { key1: 'val2', key2: 'val3' } },
   140    variantHash: 'key1:val2|key2:val3',
   141    testMetadata: {
   142      name: 'sub',
   143    },
   144    status: TestVariantStatus.EXPECTED,
   145    results: [
   146      {
   147        result: {
   148          status: TestStatus.Skip,
   149        } as TestResult,
   150      },
   151    ],
   152  };
   153  
   154  const variant7: TestVariant = {
   155    testId: 'invocation-a/test-suite-b/test-5/sub',
   156    sourcesId: '1',
   157    variant: { def: { key1: 'val2', key2: 'val3=val' } },
   158    variantHash: 'key1:val2|key2:val3=val',
   159    status: TestVariantStatus.EXPECTED,
   160    results: [
   161      {
   162        result: {
   163          status: TestStatus.Skip,
   164        } as TestResult,
   165      },
   166    ],
   167  };
   168  
   169  const variants = [
   170    variant1,
   171    variant2,
   172    variant3,
   173    variant4,
   174    variant5,
   175    variant6,
   176    variant7,
   177  ];
   178  
   179  describe('parseTestResultSearchQuery', () => {
   180    describe('query with no type', () => {
   181      test('should match either test ID or test name', () => {
   182        const filter = parseTestResultSearchQuery('sub');
   183        const filtered = variants.filter(filter);
   184        expect(filtered).toEqual([variant6, variant7]);
   185      });
   186  
   187      test('should be case insensitive', () => {
   188        const filter = parseTestResultSearchQuery('SuB');
   189        const filtered = variants.filter(filter);
   190        expect(filtered).toEqual([variant6, variant7]);
   191      });
   192    });
   193  
   194    describe('ID query', () => {
   195      test("should filter out variants whose test ID doesn't match the search text", () => {
   196        const filter = parseTestResultSearchQuery('ID:test-suite-a');
   197        const filtered = variants.filter(filter);
   198        expect(filtered).toEqual([variant1, variant2]);
   199      });
   200  
   201      test('should be case insensitive', () => {
   202        const filter = parseTestResultSearchQuery('id:test-suite-b');
   203        const filtered = variants.filter(filter);
   204        expect(filtered).toEqual([
   205          variant3,
   206          variant4,
   207          variant5,
   208          variant6,
   209          variant7,
   210        ]);
   211      });
   212  
   213      test('should work with negation', () => {
   214        const filter = parseTestResultSearchQuery('-id:test-5');
   215        const filtered = variants.filter(filter);
   216        expect(filtered).toEqual([variant1, variant2, variant3, variant4]);
   217      });
   218    });
   219  
   220    describe('RSTATUS query', () => {
   221      test('should filter out variants with no matching status', () => {
   222        const filter = parseTestResultSearchQuery('rstatus:pass');
   223        const filtered = variants.filter(filter);
   224        expect(filtered).toEqual([variant3, variant5]);
   225      });
   226  
   227      test('supports multiple statuses', () => {
   228        const filter = parseTestResultSearchQuery('rstatus:pass,fail');
   229        const filtered = variants.filter(filter);
   230        expect(filtered).toEqual([variant1, variant2, variant3, variant5]);
   231      });
   232  
   233      test('should work with negation', () => {
   234        const filter = parseTestResultSearchQuery('-rstatus:pass');
   235        const filtered = variants.filter(filter);
   236        expect(filtered).toEqual([
   237          variant1,
   238          variant2,
   239          variant4,
   240          variant6,
   241          variant7,
   242        ]);
   243      });
   244    });
   245  
   246    describe('ExactID query', () => {
   247      test('should only keep tests with the same ID', () => {
   248        const filter = parseTestResultSearchQuery(
   249          'ExactID:invocation-a/test-suite-b/test-5',
   250        );
   251        const filtered = variants.filter(filter);
   252        expect(filtered).toEqual([variant6]);
   253      });
   254  
   255      test('should be case sensitive', () => {
   256        const filter = parseTestResultSearchQuery(
   257          'ExactID:invocation-a/test-suite-B/test-5',
   258        );
   259        const filtered = variants.filter(filter);
   260        expect(filtered).toEqual([variant5]);
   261      });
   262  
   263      test('should work with negation', () => {
   264        const filter = parseTestResultSearchQuery(
   265          '-ExactID:invocation-a/test-suite-b/test-5',
   266        );
   267        const filtered = variants.filter(filter);
   268        expect(filtered).toEqual([
   269          variant1,
   270          variant2,
   271          variant3,
   272          variant4,
   273          variant5,
   274          variant7,
   275        ]);
   276      });
   277    });
   278  
   279    describe('V query', () => {
   280      test('should filter out variants with no matching variant key-value pair', () => {
   281        const filter = parseTestResultSearchQuery('v:key1=val1');
   282        const filtered = variants.filter(filter);
   283        expect(filtered).toEqual([variant1]);
   284      });
   285  
   286      test("should support variant value with '=' in it", () => {
   287        const filter = parseTestResultSearchQuery('v:key2=val3%3Dval');
   288        const filtered = variants.filter(filter);
   289        expect(filtered).toEqual([variant7]);
   290      });
   291  
   292      test('should support filter with only variant key', () => {
   293        const filter = parseTestResultSearchQuery('v:key2');
   294        const filtered = variants.filter(filter);
   295        expect(filtered).toEqual([variant5, variant6, variant7]);
   296      });
   297  
   298      test('should work with negation', () => {
   299        const filter = parseTestResultSearchQuery('-v:key1=val1');
   300        const filtered = variants.filter(filter);
   301        expect(filtered).toEqual([
   302          variant2,
   303          variant3,
   304          variant4,
   305          variant5,
   306          variant6,
   307          variant7,
   308        ]);
   309      });
   310    });
   311  
   312    describe('VHash query', () => {
   313      test('should filter out variants with no matching variant hash', () => {
   314        const filter = parseTestResultSearchQuery('vhash:key1:val1');
   315        const filtered = variants.filter(filter);
   316        expect(filtered).toEqual([variant1]);
   317      });
   318  
   319      test('should work with negation', () => {
   320        const filter = parseTestResultSearchQuery('-vhash:key1:val1');
   321        const filtered = variants.filter(filter);
   322        expect(filtered).toEqual([
   323          variant2,
   324          variant3,
   325          variant4,
   326          variant5,
   327          variant6,
   328          variant7,
   329        ]);
   330      });
   331    });
   332  
   333    describe('Tag query', () => {
   334      test('should filter out variants with no matching tag key-value pair', () => {
   335        const filter = parseTestResultSearchQuery('tag:tag-key-1=tag-val-1');
   336        const filtered = variants.filter(filter);
   337        expect(filtered).toEqual([variant1]);
   338      });
   339  
   340      test("should support tag value with '=' in it", () => {
   341        const filter = parseTestResultSearchQuery('tag:tag-key-1=tag-val-1=1');
   342        const filtered = variants.filter(filter);
   343        expect(filtered).toEqual([variant1]);
   344      });
   345  
   346      test('should support filter with only tag key', () => {
   347        const filter = parseTestResultSearchQuery('tag:tag-key-1');
   348        const filtered = variants.filter(filter);
   349        expect(filtered).toEqual([variant1, variant2]);
   350      });
   351  
   352      test('should work with negation', () => {
   353        const filter = parseTestResultSearchQuery('-tag:tag-key-1=tag-val-1');
   354        const filtered = variants.filter(filter);
   355        expect(filtered).toEqual([
   356          variant2,
   357          variant3,
   358          variant4,
   359          variant5,
   360          variant6,
   361          variant7,
   362        ]);
   363      });
   364  
   365      test('should support duplicated tag key', () => {
   366        const filter = parseTestResultSearchQuery(
   367          '-tag:duplicated-tag-key=second-tag-val',
   368        );
   369        const filtered = variants.filter(filter);
   370        expect(filtered).toEqual([
   371          variant1,
   372          variant3,
   373          variant4,
   374          variant5,
   375          variant6,
   376          variant7,
   377        ]);
   378      });
   379    });
   380  
   381    describe('Name query', () => {
   382      test("should filter out variants whose test name doesn't match the search text", () => {
   383        const filter = parseTestResultSearchQuery('Name:test-name');
   384        const filtered = variants.filter(filter);
   385        expect(filtered).toEqual([variant1, variant2]);
   386      });
   387  
   388      test('should be case insensitive', () => {
   389        const filter = parseTestResultSearchQuery('Name:test-NAME');
   390        const filtered = variants.filter(filter);
   391        expect(filtered).toEqual([variant1, variant2]);
   392      });
   393  
   394      test('should work with negation', () => {
   395        const filter = parseTestResultSearchQuery('-Name:test-name-1');
   396        const filtered = variants.filter(filter);
   397        expect(filtered).toEqual([
   398          variant2,
   399          variant3,
   400          variant4,
   401          variant5,
   402          variant6,
   403          variant7,
   404        ]);
   405      });
   406    });
   407  
   408    describe('Duration query', () => {
   409      test('should filter out variants with no run that has the specified duration', () => {
   410        const filter = parseTestResultSearchQuery('Duration:5-10');
   411        const filtered = variants.filter(filter);
   412        expect(filtered).toEqual([variant1]);
   413      });
   414  
   415      test('should support decimals', () => {
   416        const filter = parseTestResultSearchQuery('Duration:5.5-10.5');
   417        const filtered = variants.filter(filter);
   418        expect(filtered).toEqual([variant1]);
   419      });
   420  
   421      test('should support omitting max duration', () => {
   422        const filter = parseTestResultSearchQuery('Duration:5-');
   423        const filtered = variants.filter(filter);
   424        expect(filtered).toEqual([variant1, variant2]);
   425      });
   426  
   427      test('should work with negation', () => {
   428        const filter = parseTestResultSearchQuery('-Duration:5-10');
   429        const filtered = variants.filter(filter);
   430        expect(filtered).toEqual([
   431          variant2,
   432          variant3,
   433          variant4,
   434          variant5,
   435          variant6,
   436          variant7,
   437        ]);
   438      });
   439    });
   440  
   441    describe('ExactName query', () => {
   442      test('should only keep tests with the same name', () => {
   443        const filter = parseTestResultSearchQuery('ExactName:test');
   444        const filtered = variants.filter(filter);
   445        expect(filtered).toEqual([variant3]);
   446      });
   447  
   448      test('should be case sensitive', () => {
   449        const filter = parseTestResultSearchQuery('ExactName:tesT');
   450        const filtered = variants.filter(filter);
   451        expect(filtered).toEqual([]);
   452      });
   453  
   454      test('should work with negation', () => {
   455        const filter = parseTestResultSearchQuery('-ExactName:test');
   456        const filtered = variants.filter(filter);
   457        expect(filtered).toEqual([
   458          variant1,
   459          variant2,
   460          variant4,
   461          variant5,
   462          variant6,
   463          variant7,
   464        ]);
   465      });
   466    });
   467  
   468    describe('multiple queries', () => {
   469      test('should be able to combine different types of query', () => {
   470        const filter = parseTestResultSearchQuery('rstatus:pass id:test-3');
   471        const filtered = variants.filter(filter);
   472        expect(filtered).toEqual([variant3]);
   473      });
   474  
   475      test('should be able to combine normal and negative queries', () => {
   476        const filter = parseTestResultSearchQuery('rstatus:pass -rstatus:fail');
   477        const filtered = variants.filter(filter);
   478        expect(filtered).toEqual([variant5]);
   479      });
   480    });
   481  });
   482  
   483  describe('suggestTestResultSearchQuery', () => {
   484    test('should give user some suggestions when the query is empty', () => {
   485      const suggestions1 = suggestTestResultSearchQuery('');
   486      expect(suggestions1.length).not.toStrictEqual(0);
   487    });
   488  
   489    test('should not give suggestions when the sub-query is empty', () => {
   490      const suggestions1 = suggestTestResultSearchQuery('Status:UNEXPECTED ');
   491      expect(suggestions1.length).toStrictEqual(0);
   492    });
   493  
   494    test('should give user suggestions based on the last sub-query', () => {
   495      const suggestions1 = suggestTestResultSearchQuery('unexpected Pass');
   496      expect(suggestions1.find((s) => s.value === 'RStatus:Pass')).toBeDefined();
   497      expect(suggestions1.find((s) => s.value === '-RStatus:Pass')).toBeDefined();
   498      expect(
   499        suggestions1.find((s) => s.value === 'Status:UNEXPECTED'),
   500      ).toBeUndefined();
   501      expect(
   502        suggestions1.find((s) => s.value === '-Status:UNEXPECTED'),
   503      ).toBeUndefined();
   504    });
   505  
   506    test('should suggest run status query with matching status', () => {
   507      const suggestions1 = suggestTestResultSearchQuery('Pass');
   508      expect(suggestions1.find((s) => s.value === 'RStatus:Pass')).toBeDefined();
   509      expect(suggestions1.find((s) => s.value === '-RStatus:Pass')).toBeDefined();
   510  
   511      const suggestions2 = suggestTestResultSearchQuery('Fail');
   512      expect(suggestions2.find((s) => s.value === 'RStatus:Fail')).toBeDefined();
   513      expect(suggestions2.find((s) => s.value === '-RStatus:Fail')).toBeDefined();
   514  
   515      const suggestions3 = suggestTestResultSearchQuery('Crash');
   516      expect(suggestions3.find((s) => s.value === 'RStatus:Crash')).toBeDefined();
   517      expect(
   518        suggestions3.find((s) => s.value === '-RStatus:Crash'),
   519      ).toBeDefined();
   520  
   521      const suggestions4 = suggestTestResultSearchQuery('Abort');
   522      expect(suggestions4.find((s) => s.value === 'RStatus:Abort')).toBeDefined();
   523      expect(
   524        suggestions4.find((s) => s.value === '-RStatus:Abort'),
   525      ).toBeDefined();
   526  
   527      const suggestions5 = suggestTestResultSearchQuery('Skip');
   528      expect(suggestions5.find((s) => s.value === 'RStatus:Skip')).toBeDefined();
   529      expect(suggestions5.find((s) => s.value === '-RStatus:Skip')).toBeDefined();
   530    });
   531  
   532    test('should not suggest run status query with a different status', () => {
   533      const suggestions1 = suggestTestResultSearchQuery('Pass');
   534      expect(
   535        suggestions1.find((s) => s.value === 'RStatus:Fail'),
   536      ).toBeUndefined();
   537      expect(
   538        suggestions1.find((s) => s.value === '-RStatus:Fail'),
   539      ).toBeUndefined();
   540      expect(
   541        suggestions1.find((s) => s.value === 'RStatus:Crash'),
   542      ).toBeUndefined();
   543      expect(
   544        suggestions1.find((s) => s.value === '-RStatus:Crash'),
   545      ).toBeUndefined();
   546      expect(
   547        suggestions1.find((s) => s.value === 'RStatus:Abort'),
   548      ).toBeUndefined();
   549      expect(
   550        suggestions1.find((s) => s.value === '-RStatus:Abort'),
   551      ).toBeUndefined();
   552      expect(
   553        suggestions1.find((s) => s.value === 'RStatus:Skip'),
   554      ).toBeUndefined();
   555      expect(
   556        suggestions1.find((s) => s.value === '-RStatus:Skip'),
   557      ).toBeUndefined();
   558    });
   559  
   560    test('should suggest variant status query with matching status', () => {
   561      const suggestions1 = suggestTestResultSearchQuery('unexpected');
   562      expect(
   563        suggestions1.find((s) => s.value === 'Status:UNEXPECTED'),
   564      ).toBeDefined();
   565      expect(
   566        suggestions1.find((s) => s.value === '-Status:UNEXPECTED'),
   567      ).toBeDefined();
   568  
   569      const suggestions2 = suggestTestResultSearchQuery('flaky');
   570      expect(suggestions2.find((s) => s.value === 'Status:FLAKY')).toBeDefined();
   571      expect(suggestions2.find((s) => s.value === '-Status:FLAKY')).toBeDefined();
   572  
   573      const suggestions3 = suggestTestResultSearchQuery('exonerated');
   574      expect(
   575        suggestions3.find((s) => s.value === 'Status:EXONERATED'),
   576      ).toBeDefined();
   577      expect(
   578        suggestions3.find((s) => s.value === '-Status:EXONERATED'),
   579      ).toBeDefined();
   580  
   581      const suggestions4 = suggestTestResultSearchQuery('expected');
   582      expect(
   583        suggestions4.find((s) => s.value === 'Status:EXPECTED'),
   584      ).toBeDefined();
   585      expect(
   586        suggestions4.find((s) => s.value === '-Status:EXPECTED'),
   587      ).toBeDefined();
   588    });
   589  
   590    test('should not suggest variant status query with a different status', () => {
   591      const suggestions1 = suggestTestResultSearchQuery('UNEXPECTED');
   592      expect(
   593        suggestions1.find((s) => s.value === 'Status:FLAKY'),
   594      ).toBeUndefined();
   595      expect(
   596        suggestions1.find((s) => s.value === '-Status:FLAKY'),
   597      ).toBeUndefined();
   598      expect(
   599        suggestions1.find((s) => s.value === 'Status:EXONERATED'),
   600      ).toBeUndefined();
   601      expect(
   602        suggestions1.find((s) => s.value === '-Status:EXONERATED'),
   603      ).toBeUndefined();
   604      expect(
   605        suggestions1.find((s) => s.value === 'Status:EXPECTED'),
   606      ).toBeUndefined();
   607      expect(
   608        suggestions1.find((s) => s.value === '-Status:EXPECTED'),
   609      ).toBeUndefined();
   610    });
   611  
   612    test('suggestion should be case insensitive', () => {
   613      const suggestions1 = suggestTestResultSearchQuery('PASS');
   614      expect(suggestions1.find((s) => s.value === 'RStatus:Pass')).toBeDefined();
   615      expect(suggestions1.find((s) => s.value === '-RStatus:Pass')).toBeDefined();
   616  
   617      const suggestions2 = suggestTestResultSearchQuery('fail');
   618      expect(suggestions2.find((s) => s.value === 'RStatus:Fail')).toBeDefined();
   619      expect(suggestions2.find((s) => s.value === '-RStatus:Fail')).toBeDefined();
   620  
   621      const suggestions3 = suggestTestResultSearchQuery('CrAsH');
   622      expect(suggestions3.find((s) => s.value === 'RStatus:Crash')).toBeDefined();
   623      expect(
   624        suggestions3.find((s) => s.value === '-RStatus:Crash'),
   625      ).toBeDefined();
   626  
   627      const suggestions4 = suggestTestResultSearchQuery('Abort');
   628      expect(suggestions4.find((s) => s.value === 'RStatus:Abort')).toBeDefined();
   629      expect(
   630        suggestions4.find((s) => s.value === '-RStatus:Abort'),
   631      ).toBeDefined();
   632  
   633      const suggestions5 = suggestTestResultSearchQuery('sKIP');
   634      expect(suggestions5.find((s) => s.value === 'RStatus:Skip')).toBeDefined();
   635      expect(suggestions5.find((s) => s.value === '-RStatus:Skip')).toBeDefined();
   636    });
   637  
   638    test('should suggest ID query', () => {
   639      const suggestions1 = suggestTestResultSearchQuery('ranDom');
   640      expect(suggestions1.find((s) => s.value === 'ID:ranDom')).toBeDefined();
   641      expect(suggestions1.find((s) => s.value === '-ID:ranDom')).toBeDefined();
   642    });
   643  
   644    test('should suggest ID query when the query prefix is ID:', () => {
   645      const suggestions1 = suggestTestResultSearchQuery('ID:pass');
   646      expect(suggestions1.find((s) => s.value === 'ID:pass')).toBeDefined();
   647      expect(suggestions1.find((s) => s.value === '-ID:pass')).toBeDefined();
   648  
   649      const suggestions2 = suggestTestResultSearchQuery('-ID:pass');
   650      // When user explicitly typed negative query, don't suggest positive query.
   651      expect(suggestions2.find((s) => s.value === 'ID:pass')).toBeUndefined();
   652      expect(suggestions2.find((s) => s.value === '-ID:pass')).toBeDefined();
   653    });
   654  
   655    test('should suggest ID query when the query type is a substring of ID:', () => {
   656      const suggestions1 = suggestTestResultSearchQuery('i');
   657      expect(suggestions1.find((s) => s.value === 'ID:')).toBeDefined();
   658      expect(suggestions1.find((s) => s.value === '-ID:')).toBeDefined();
   659    });
   660  
   661    test('should suggest ID query even when there are other matching queries', () => {
   662      const suggestions1 = suggestTestResultSearchQuery('fail');
   663      expect(suggestions1.find((s) => s.value === 'RStatus:Fail')).toBeDefined();
   664      expect(suggestions1.find((s) => s.value === '-RStatus:Fail')).toBeDefined();
   665      expect(suggestions1.find((s) => s.value === 'ID:fail')).toBeDefined();
   666      expect(suggestions1.find((s) => s.value === '-ID:fail')).toBeDefined();
   667    });
   668  
   669    test('should suggest ExactID query when the query prefix is ExactID:', () => {
   670      const suggestions1 = suggestTestResultSearchQuery('ExactID:pass');
   671      expect(suggestions1.find((s) => s.value === 'ExactID:pass')).toBeDefined();
   672      expect(suggestions1.find((s) => s.value === '-ExactID:pass')).toBeDefined();
   673  
   674      const suggestions2 = suggestTestResultSearchQuery('-ExactID:pass');
   675      // When user explicitly typed negative query, don't suggest positive query.
   676      expect(
   677        suggestions2.find((s) => s.value === 'ExactID:pass'),
   678      ).toBeUndefined();
   679      expect(suggestions2.find((s) => s.value === '-ExactID:pass')).toBeDefined();
   680    });
   681  
   682    test('should suggest ExactID query when the query type is a substring of ExactID:', () => {
   683      const suggestions1 = suggestTestResultSearchQuery('xact');
   684      expect(suggestions1.find((s) => s.value === 'ExactID:')).toBeDefined();
   685      expect(suggestions1.find((s) => s.value === '-ExactID:')).toBeDefined();
   686    });
   687  
   688    test('should suggest V query when the query prefix is V:', () => {
   689      const suggestions1 = suggestTestResultSearchQuery('V:test_suite');
   690      expect(suggestions1.find((s) => s.value === 'V:test_suite')).toBeDefined();
   691      expect(suggestions1.find((s) => s.value === '-V:test_suite')).toBeDefined();
   692  
   693      const suggestions2 = suggestTestResultSearchQuery('-V:test_suite');
   694      // When user explicitly typed negative query, don't suggest positive query.
   695      expect(
   696        suggestions2.find((s) => s.value === 'V:test_suite'),
   697      ).toBeUndefined();
   698      expect(suggestions2.find((s) => s.value === '-V:test_suite')).toBeDefined();
   699    });
   700  
   701    test('should suggest Tag query when the query prefix is Tag:', () => {
   702      const suggestions1 = suggestTestResultSearchQuery('Tag:tag_key');
   703      expect(suggestions1.find((s) => s.value === 'Tag:tag_key')).toBeDefined();
   704      expect(suggestions1.find((s) => s.value === '-Tag:tag_key')).toBeDefined();
   705  
   706      const suggestions2 = suggestTestResultSearchQuery('-Tag:tag_key');
   707      // When user explicitly typed negative query, don't suggest positive query.
   708      expect(suggestions2.find((s) => s.value === 'Tag:tag_key')).toBeUndefined();
   709      expect(suggestions2.find((s) => s.value === '-Tag:tag_key')).toBeDefined();
   710    });
   711  
   712    test('should suggest Duration query when the query prefix is Duration:', () => {
   713      const suggestions1 = suggestTestResultSearchQuery('Duration:10');
   714      expect(suggestions1.find((s) => s.value === 'Duration:10')).toBeDefined();
   715      expect(suggestions1.find((s) => s.value === '-Duration:10')).toBeDefined();
   716  
   717      const suggestions2 = suggestTestResultSearchQuery('-Duration:10');
   718      // When user explicitly typed negative query, don't suggest positive query.
   719      expect(suggestions2.find((s) => s.value === 'Duration:10')).toBeUndefined();
   720      expect(suggestions2.find((s) => s.value === '-Duration:10')).toBeDefined();
   721    });
   722  
   723    test('should suggest VHash query when the query prefix is VHash:', () => {
   724      const suggestions1 = suggestTestResultSearchQuery('VHash:pass');
   725      expect(suggestions1.find((s) => s.value === 'VHash:pass')).toBeDefined();
   726      expect(suggestions1.find((s) => s.value === '-VHash:pass')).toBeDefined();
   727  
   728      const suggestions2 = suggestTestResultSearchQuery('-VHash:pass');
   729      // When user explicitly typed negative query, don't suggest positive query.
   730      expect(suggestions2.find((s) => s.value === 'VHash:pass')).toBeUndefined();
   731      expect(suggestions2.find((s) => s.value === '-VHash:pass')).toBeDefined();
   732    });
   733  
   734    test('should suggest VHash query when the query type is a substring of VHash:', () => {
   735      const suggestions1 = suggestTestResultSearchQuery('hash');
   736      expect(suggestions1.find((s) => s.value === 'VHash:')).toBeDefined();
   737      expect(suggestions1.find((s) => s.value === '-VHash:')).toBeDefined();
   738    });
   739  });