chromium/chrome/test/data/webui/tab_search/search_test.ts

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import type {SearchOptions} from 'chrome://tab-search.top-chrome/tab_search.js';
import {search, getHostname, getTitle, TabData, TabItemType} from 'chrome://tab-search.top-chrome/tab_search.js';
import {assertDeepEquals, assertEquals} from 'chrome://webui-test/chai_assert.js';

import {createTab} from './tab_search_test_data.js';

/**
 * Assert search results return in specific order.
 */
function assertSearchOrders(
    input: string, items: TabData[], options: SearchOptions,
    expectedIndices: number[]) {
  const results = search(input, items, options);
  assertEquals(results.length, expectedIndices.length);
  for (let i = 0; i < results.length; ++i) {
    const expectedItem = items[expectedIndices[i]!]!;
    const actualItem = results[i]!;
    assertEquals(expectedItem.tab.title, actualItem.tab.title);
    assertEquals(expectedItem.hostname, actualItem.hostname);
  }
}

function assertResults(expectedRecords: any[], actualRecords: TabData[]) {
  assertEquals(expectedRecords.length, actualRecords.length);
  expectedRecords.forEach((expected, i) => {
    const actual = actualRecords[i]!;
    assertEquals(expected.tab.title, actual.tab.title);
    if (expected.tabGroup !== undefined) {
      assertEquals(expected.tabGroup.title, actual.tabGroup!.title);
    }
    assertEquals(expected.hostname, actual.hostname);
    assertDeepEquals(expected.highlightRanges, actual.highlightRanges);
  });
}

suite('FuzzySearchTest', () => {
  test('Test the exact match ranking order.', () => {
    const options = {
      keys: [
        {
          name: 'tab.title',
          getter: getTitle,
          weight: 1,
        },
        {
          name: 'hostname',
          getter: getHostname,
          weight: 1,
        },
      ],
    };

    // Initial pre-search item list.
    const records = [
      new TabData(
          createTab({title: 'Code Search'}), TabItemType.OPEN_TAB,
          'search.chromium.search'),
      new TabData(
          createTab({title: 'Marching band'}), TabItemType.OPEN_TAB,
          'en.marching.band.com'),
      new TabData(
          createTab({title: 'Chrome Desktop Architecture'}),
          TabItemType.OPEN_TAB, 'drive.google.com'),
      new TabData(
          createTab({title: 'Arch Linux'}), TabItemType.OPEN_TAB,
          'www.archlinux.org'),
      new TabData(
          createTab({title: 'Arches National Park'}), TabItemType.OPEN_TAB,
          'www.nps.gov'),
      new TabData(
          createTab({title: 'Search Engine Land - Search Engines'}),
          TabItemType.OPEN_TAB, 'searchengineland.com'),
    ];

    // Results for 'arch'.
    const archMatchedRecords = [
      {
        tab: {title: 'Arch Linux'},
        hostname: 'www.archlinux.org',
        highlightRanges: {
          'tab.title': [{start: 0, length: 4}],
          hostname: [{start: 4, length: 4}],
        },
      },
      {
        tab: {title: 'Arches National Park'},
        hostname: 'www.nps.gov',
        highlightRanges: {
          'tab.title': [{start: 0, length: 4}],
        },
      },
      {
        tab: {title: 'Chrome Desktop Architecture'},
        hostname: 'drive.google.com',
        highlightRanges: {
          'tab.title': [{start: 15, length: 4}],
        },
      },
      {
        tab: {title: 'Code Search'},
        hostname: 'search.chromium.search',
        highlightRanges: {
          'tab.title': [{start: 7, length: 4}],
          hostname: [{start: 2, length: 4}, {start: 18, length: 4}],
        },
      },
      {
        tab: {title: 'Search Engine Land - Search Engines'},
        hostname: 'searchengineland.com',
        highlightRanges: {
          'tab.title': [{start: 2, length: 4}, {start: 23, length: 4}],
          hostname: [{start: 2, length: 4}],
        },
      },
      {
        tab: {title: 'Marching band'},
        hostname: 'en.marching.band.com',
        highlightRanges: {
          'tab.title': [{start: 1, length: 4}],
          hostname: [{start: 4, length: 4}],
        },
      },
    ];

    // Results for 'search'.
    const searchMatchedRecords = [
      {
        tab: {title: 'Code Search'},
        hostname: 'search.chromium.search',
        highlightRanges: {
          'tab.title': [{start: 5, length: 6}],
          hostname: [{start: 0, length: 6}, {start: 16, length: 6}],
        },
      },
      {
        tab: {title: 'Search Engine Land - Search Engines'},
        hostname: 'searchengineland.com',
        highlightRanges: {
          'tab.title': [{start: 0, length: 6}, {start: 21, length: 6}],
          hostname: [{start: 0, length: 6}],
        },
      },
    ];

    // Empty search should return the full list.
    assertResults(records, search('', records, options));
    assertResults(archMatchedRecords, search('arch', records, options));
    assertResults(
        searchMatchedRecords, search('search', records, options));

    // No matches should return an empty list.
    assertResults([], search('archh', records, options));
  });

  test('Test exact search with escaped characters.', () => {
    const options = {
      keys: [
        {
          name: 'tab.title',
          getter: getTitle,
          weight: 1,
        },
        {
          name: 'hostname',
          getter: getHostname,
          weight: 1,
        },
      ],
    };

    // Initial pre-search item list.
    const records = [
      new TabData(
          createTab({title: '\'beginning\\test\\end'}), TabItemType.OPEN_TAB,
          'beginning\\test\"end'),
    ];

    // Expected results for '\test'.
    const backslashMatchedRecords = [
      {
        tab: {title: '\'beginning\\test\\end'},
        hostname: 'beginning\\test\"end',
        highlightRanges: {
          'tab.title': [{start: 10, length: 5}],
          hostname: [{start: 9, length: 5}],
        },
      },
    ];

    // Expected results for '"end'.
    const quoteMatchedRecords = [
      {
        tab: {title: '\'beginning\\test\\end'},
        hostname: 'beginning\\test\"end',
        highlightRanges: {
          hostname: [{start: 14, length: 4}],
        },
      },
    ];

    assertResults(
        backslashMatchedRecords, search('\\test', records, options));
    assertResults(quoteMatchedRecords, search('\"end', records, options));
  });

  test('Test exact match result scoring accounts for match position.', () => {
    const options = {
      keys: [
        {
          name: 'tab.title',
          getter: getTitle,
          weight: 1,
        },
        {
          name: 'hostname',
          getter: getHostname,
          weight: 1,
        },
      ],
    };

    assertSearchOrders(
        'two',
        [
          new TabData(
              createTab({title: 'three one two'}), TabItemType.OPEN_TAB,
              'three one two'),
          new TabData(
              createTab({title: 'three two one'}), TabItemType.OPEN_TAB,
              'three two one'),
          new TabData(
              createTab({title: 'one two three'}), TabItemType.OPEN_TAB,
              'one two three'),
        ],
        options, [2, 1, 0]);
  });

  test(
      'Test exact match result scoring takes into account the number of matches per item.',
      () => {
        const options = {
          keys: [
            {
              name: 'tab.title',
              getter: getTitle,
              weight: 1,
            },
            {
              name: 'hostname',
              getter: getHostname,
              weight: 1,
            },
          ],
        };

        assertSearchOrders(
            'one',
            [
              new TabData(
                  createTab({title: 'one two three'}), TabItemType.OPEN_TAB,
                  'one two three'),
              new TabData(
                  createTab({title: 'one one three'}), TabItemType.OPEN_TAB,
                  'one one three'),
              new TabData(
                  createTab({title: 'one one one'}), TabItemType.OPEN_TAB,
                  'one one one'),
            ],
            options, [2, 1, 0]);
      });

  test('Test exact match result scoring abides by the key weights.', () => {
    const options = {
      keys: [
        {
          name: 'tab.title',
          getter: getTitle,
          weight: 2,
        },
        {
          name: 'hostname',
          getter: getHostname,
          weight: 1,
        },
      ],
    };

    assertSearchOrders(
        'search',
        [
          new TabData(
              createTab({title: 'New tab'}), TabItemType.OPEN_TAB,
              'chrome://tab-search'),
          new TabData(
              createTab({title: 'chrome://tab-search'}), TabItemType.OPEN_TAB,
              'chrome://tab-search'),
          new TabData(
              createTab({title: 'chrome://tab-search'}), TabItemType.OPEN_TAB,
              'chrome://tab-search'),
        ],
        options, [2, 1, 0]);
  });
});