chromium/ui/file_manager/file_manager/foreground/js/ui/file_grid_unittest.ts

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

import {assert} from 'chrome://resources/js/assert.js';
import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
import {assertEquals} from 'chrome://webui-test/chromeos/chai_assert.js';

import {MockVolumeManager} from '../../../background/js/mock_volume_manager.js';
import {FakeEntryImpl} from '../../../common/js/files_app_entry_types.js';
import {RootType} from '../../../common/js/volume_manager_types.js';
import {FileListModel, GROUP_BY_FIELD_DIRECTORY, GROUP_BY_FIELD_MODIFICATION_TIME} from '../file_list_model.js';
import type {MetadataModel} from '../metadata/metadata_model.js';
import {MockMetadataModel} from '../metadata/mock_metadata.js';

import type {A11yAnnounce} from './a11y_announce.js';
import {FileGrid, FileGridSelectionController} from './file_grid.js';
import {ListSelectionModel} from './list_selection_model.js';

let volumeManager: MockVolumeManager;
let metadataModel: MetadataModel;
let element: HTMLElement;
let a11y: A11yAnnounce;

/**
 * Returns the element used to parent the file grid. The element is
 * attached to the body, and styled for visual display.
 */
function setupBody(): HTMLElement {
  document.body.innerHTML = getTrustedHTML`
    <style>
      grid {
        display: block;
        height: 200px;
        width: 800px;
      }
    </style>
  `;

  const element = document.createElement('div');
  document.body.appendChild(element);
  return element;
}

// Set up test components.
export function setUp() {
  // Setup mock components.
  volumeManager = new MockVolumeManager();
  metadataModel = new MockMetadataModel({}) as unknown as MetadataModel;

  const a11Messages = [];
  a11y = {
    speakA11yMessage: (text) => {
      a11Messages.push(text);
    },
  };

  // Create DOM element parent of the file grid under test.
  element = setupBody();
}

// Force round number heights to simplify the math in the test.
const FILE_ITEM_HEIGHT = 50;
const FOLDER_ITEM_HEIGHT = 20;
const GROUP_HEADING_HEIGHT = 30;
const ITEM_WIDTH = 100;
const ITEM_MARGIN_TOP = 10;
const ITEM_MARGIN_LEFT = 10;

function setupFileGrid(): FileGrid {
  FileGrid.decorate(element, metadataModel, volumeManager, a11y);

  // Add 10 fake files.
  const entries = [];
  for (let i = 1; i <= 20; i++) {
    entries.push(new FakeEntryImpl(`${i}.txt`, RootType.RECENT));
  }
  const dataModel = new FileListModel(metadataModel);
  dataModel.splice(0, 0, ...entries);
  const grid = element as FileGrid;
  grid.dataModel = dataModel;
  // Mock item size.
  grid['getFileItemHeight_'] = () => FILE_ITEM_HEIGHT;
  grid['getFolderItemHeight_'] = () => FOLDER_ITEM_HEIGHT;
  grid['getItemWidth_'] = () => ITEM_WIDTH;
  grid['getItemMarginTop_'] = () => ITEM_MARGIN_TOP;
  grid['getItemMarginLeft_'] = () => ITEM_MARGIN_LEFT;
  // 3 columns in each row.
  grid.columns = 3;
  return grid;
}

function groupByModificationTime(fileListModel: FileListModel) {
  const RecentDateBucket = chrome.fileManagerPrivate.RecentDateBucket;

  // Mock group by information.
  fileListModel.shouldShowGroupHeading = () => true;
  fileListModel.groupByField = GROUP_BY_FIELD_MODIFICATION_TIME;
  // Visual illustration of the grid (in total 8 rows with 6 headings):
  // -----------------------------------------------------------------
  // Heading #1/today:
  // (row 0)    Item 0    Item 1
  // Heading #2/yesterday:
  // (row 1)    Item 2    Item 3    Item 4
  // Heading #3/earlier_this_week:
  // (row 2)    Item 5    Item 6    Item 7
  // (row 3)    Item 8    Item 9
  // Heading #4/earlier_this_month:
  // (row 4)    Item 10   Item 11   Item 12
  // (row 5)    Item 13   Item 14   Item 15
  // Heading #5/earlier_this_year:
  // (row 6)    Item 16
  // Heading #6/older:
  // (row 7)    Item 17   Item 18   Item 19
  fileListModel.getGroupBySnapshot = () => {
    return [
      {
        startIndex: 0,
        endIndex: 1,
        label: 'today',
        group: RecentDateBucket.TODAY,
      },
      {
        startIndex: 2,
        endIndex: 4,
        label: 'yesterday',
        group: RecentDateBucket.YESTERDAY,
      },
      {
        startIndex: 5,
        endIndex: 9,
        label: 'earlier_this_week',
        group: RecentDateBucket.EARLIER_THIS_WEEK,
      },
      {
        startIndex: 10,
        endIndex: 15,
        label: 'earlier_this_month',
        group: RecentDateBucket.EARLIER_THIS_MONTH,
      },
      {
        startIndex: 16,
        endIndex: 16,
        label: 'earlier_this_year',
        group: RecentDateBucket.EARLIER_THIS_YEAR,
      },
      {
        startIndex: 17,
        endIndex: 19,
        label: 'older',
        group: RecentDateBucket.OLDER,
      },
    ];
  };
}

function groupByDirectory(fileListModel: FileListModel) {
  // Mock group by information.
  fileListModel.shouldShowGroupHeading = () => true;
  fileListModel.groupByField = GROUP_BY_FIELD_DIRECTORY;
  // Visual illustration of the grid (in total 8 rows with 2 headings):
  // -----------------------------------------------------------------
  // Heading #1/folders:
  // (row 0)    Item 0    Item 1    Item 2
  // (row 1)    Item 3
  // Heading #2/files:
  // (row 2)    Item 4    Item 5    Item 6
  // (row 3)    Item 7    Item 8    Item 9
  // (row 4)    Item 10   Item 11   Item 12
  // (row 5)    Item 13   Item 14   Item 15
  // (row 6)    Item 16   Item 17   Item 18
  // (row 7)    Item 19
  fileListModel.getGroupBySnapshot = () => {
    return [
      {startIndex: 0, endIndex: 3, label: 'folders', group: true},
      {startIndex: 4, endIndex: 19, label: 'files', group: false},
    ];
  };
}

export function testGetItemTop() {
  const grid = setupFileGrid();
  grid['getGroupHeadingHeight_'] = () => GROUP_HEADING_HEIGHT;
  const ROW_HEIGHT = FILE_ITEM_HEIGHT;
  assert(grid.dataModel);
  // Enable group by modification time.
  groupByModificationTime(grid.dataModel);
  // Item 0,1 is in group #1/today, nothing is above it.
  assertEquals(grid.getItemTop(0), 0);
  assertEquals(grid.getItemTop(1), 0);
  // Item 2,3,4 is in group #2/yesterday, 1 row above + 1 header.
  assertEquals(grid.getItemTop(2), 1 * ROW_HEIGHT + GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(3), 1 * ROW_HEIGHT + GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(4), 1 * ROW_HEIGHT + GROUP_HEADING_HEIGHT);
  // Item 5,6,7 is in group #3/earlier_this_week, 2 rows above + 2 headers.
  assertEquals(grid.getItemTop(5), 2 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(6), 2 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(7), 2 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  // Item 8,9 is in group #3/earlier_this_week, 3 rows above + 3 headers.
  assertEquals(grid.getItemTop(8), 3 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(9), 3 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  // Item 10,11,12 is in group #4/earlier_this_month, 4 rows above + 3 headers.
  assertEquals(grid.getItemTop(10), 4 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(11), 4 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(12), 4 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  // Item 13,14,15 is in group #4/earlier_this_month, 5 rows above + 4 headers.
  assertEquals(grid.getItemTop(13), 5 * ROW_HEIGHT + 4 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(14), 5 * ROW_HEIGHT + 4 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(15), 5 * ROW_HEIGHT + 4 * GROUP_HEADING_HEIGHT);
  // Item 16 is in group #5/earlier_this_year, 6 rows above + 4 headers.
  assertEquals(grid.getFirstItemInRow(6), 16);
  assertEquals(grid.getItemTop(16), 6 * ROW_HEIGHT + 4 * GROUP_HEADING_HEIGHT);
  // Item 17,18,19 is in group #6/older, 7 rows above + 5 header.
  assertEquals(grid.getFirstItemInRow(7), 17);
  assertEquals(grid.getItemTop(17), 7 * ROW_HEIGHT + 5 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(18), 7 * ROW_HEIGHT + 5 * GROUP_HEADING_HEIGHT);
  assertEquals(grid.getItemTop(19), 7 * ROW_HEIGHT + 5 * GROUP_HEADING_HEIGHT);
}

// Test functions related to item position: getItemRow(), getItemColumn(),
// getItemIndex(), getFirstItemInRow().
export function testGetItemPosition() {
  const grid = setupFileGrid();
  assert(grid.dataModel);
  // Enable group by modification time.
  groupByModificationTime(grid.dataModel);
  // Check the comment in groupByModificationTime() for a visual illustration.
  // Item 0,1 is in group #1/today, row 0.
  assertEquals(grid.getFirstItemInRow(0), 0);
  assertEquals(grid.getItemRow(0), 0);
  assertEquals(grid.getItemColumn(0), 0);
  assertEquals(grid.getItemIndex(0, 0), 0);
  assertEquals(grid.getItemRow(1), 0);
  assertEquals(grid.getItemColumn(1), 1);
  assertEquals(grid.getItemIndex(0, 1), 1);
  // Item 2,3,4 is in group #2/yesterday, row 1.
  assertEquals(grid.getFirstItemInRow(1), 2);
  assertEquals(grid.getItemRow(2), 1);
  assertEquals(grid.getItemColumn(2), 0);
  assertEquals(grid.getItemIndex(1, 0), 2);
  assertEquals(grid.getItemRow(3), 1);
  assertEquals(grid.getItemColumn(3), 1);
  assertEquals(grid.getItemIndex(1, 1), 3);
  assertEquals(grid.getItemRow(4), 1);
  assertEquals(grid.getItemColumn(4), 2);
  assertEquals(grid.getItemIndex(1, 2), 4);
  // Item 5,6,7 is in group #3/earlier_this_week, row 2.
  assertEquals(grid.getFirstItemInRow(2), 5);
  assertEquals(grid.getItemRow(5), 2);
  assertEquals(grid.getItemColumn(5), 0);
  assertEquals(grid.getItemIndex(2, 0), 5);
  assertEquals(grid.getItemRow(6), 2);
  assertEquals(grid.getItemColumn(6), 1);
  assertEquals(grid.getItemIndex(2, 1), 6);
  assertEquals(grid.getItemRow(7), 2);
  assertEquals(grid.getItemColumn(7), 2);
  assertEquals(grid.getItemIndex(2, 2), 7);
  // Item 8,9 is in group #3/earlier_this_week, row 3.
  assertEquals(grid.getFirstItemInRow(3), 8);
  assertEquals(grid.getItemRow(8), 3);
  assertEquals(grid.getItemColumn(8), 0);
  assertEquals(grid.getItemIndex(3, 0), 8);
  assertEquals(grid.getItemRow(9), 3);
  assertEquals(grid.getItemColumn(9), 1);
  assertEquals(grid.getItemIndex(3, 1), 9);
  // Item 10,11,12 is in group #4/earlier_this_month, row 4.
  assertEquals(grid.getFirstItemInRow(4), 10);
  assertEquals(grid.getItemRow(10), 4);
  assertEquals(grid.getItemColumn(10), 0);
  assertEquals(grid.getItemIndex(4, 0), 10);
  assertEquals(grid.getItemRow(11), 4);
  assertEquals(grid.getItemColumn(11), 1);
  assertEquals(grid.getItemIndex(4, 1), 11);
  assertEquals(grid.getItemRow(12), 4);
  assertEquals(grid.getItemColumn(12), 2);
  assertEquals(grid.getItemIndex(4, 2), 12);
  // Item 13,14,15 is in group #4/earlier_this_month, row 5.
  assertEquals(grid.getFirstItemInRow(5), 13);
  assertEquals(grid.getItemRow(13), 5);
  assertEquals(grid.getItemColumn(13), 0);
  assertEquals(grid.getItemIndex(5, 0), 13);
  assertEquals(grid.getItemRow(14), 5);
  assertEquals(grid.getItemColumn(14), 1);
  assertEquals(grid.getItemIndex(5, 1), 14);
  assertEquals(grid.getItemRow(15), 5);
  assertEquals(grid.getItemColumn(15), 2);
  assertEquals(grid.getItemIndex(5, 2), 15);
  // Item 16 is in group #5/earlier_this_year, row 6.
  assertEquals(grid.getFirstItemInRow(6), 16);
  assertEquals(grid.getItemRow(16), 6);
  assertEquals(grid.getItemColumn(16), 0);
  assertEquals(grid.getItemIndex(6, 0), 16);
  // Item 17,18,19 is in group #6/older, row 7.
  assertEquals(grid.getFirstItemInRow(7), 17);
  assertEquals(grid.getItemRow(17), 7);
  assertEquals(grid.getItemColumn(17), 0);
  assertEquals(grid.getItemIndex(7, 0), 17);
  assertEquals(grid.getItemRow(18), 7);
  assertEquals(grid.getItemColumn(18), 1);
  assertEquals(grid.getItemIndex(7, 1), 18);
  assertEquals(grid.getItemRow(19), 7);
  assertEquals(grid.getItemColumn(19), 2);
  assertEquals(grid.getItemIndex(7, 2), 19);

  // Invalid inputs:
  // row and column is negative.
  assertEquals(grid.getItemIndex(-1, -1), -1);
  assertEquals(grid.getFirstItemInRow(-1), 0);
  // column is too big.
  assertEquals(grid.getItemIndex(0, 10), -1);
  // row is too big.
  assertEquals(grid.getItemIndex(10, 0), -1);
  assertEquals(grid.getFirstItemInRow(10), grid.dataModel.length);
  // column on a specific row is too big for that row.
  assertEquals(grid.getItemIndex(0, 2), -1);
  assertEquals(grid.getItemIndex(6, 2), -1);
  assertEquals(grid.getItemIndex(6, 3), -1);
}

export function testGetAfterFillerHeight() {
  const grid = setupFileGrid();
  grid['getGroupHeadingHeight_'] = () => GROUP_HEADING_HEIGHT;
  const ROW_HEIGHT = FILE_ITEM_HEIGHT;
  assert(grid.dataModel);
  // Enable group by modification time.
  groupByModificationTime(grid.dataModel);
  // Check the comment in groupByModificationTime() for a visual illustration.
  // Note: the index itself is being excluded.
  // Item 0,1 is in group #1/today, 8 rows below + 5 headers.
  assertEquals(
      grid.getAfterFillerHeight(0), 8 * ROW_HEIGHT + 5 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(1), 8 * ROW_HEIGHT + 5 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(2), 8 * ROW_HEIGHT + 5 * GROUP_HEADING_HEIGHT);
  // Item 2,3,4 is in group #2/yesterday, 7 rows below + 4 header.
  assertEquals(
      grid.getAfterFillerHeight(3), 7 * ROW_HEIGHT + 4 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(4), 7 * ROW_HEIGHT + 4 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(5), 7 * ROW_HEIGHT + 4 * GROUP_HEADING_HEIGHT);
  // Item 5,6,7 is in group #3/earlier_this_week, 6 rows below + 3 headers.
  assertEquals(
      grid.getAfterFillerHeight(6), 6 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(7), 6 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(8), 6 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  // Item 8,9 is in group #3/earlier_this_week, 5 rows below + 3 headers.
  assertEquals(
      grid.getAfterFillerHeight(9), 5 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(10), 5 * ROW_HEIGHT + 3 * GROUP_HEADING_HEIGHT);
  // Item 10,11,12 is in group #4/earlier_this_month, 4 rows below + 2 headers.
  assertEquals(
      grid.getAfterFillerHeight(11), 4 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(12), 4 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(13), 4 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  // Item 13,14,15 is in group #4/earlier_this_month, 3 rows below + 2 headers.
  assertEquals(
      grid.getAfterFillerHeight(14), 3 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(15), 3 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  assertEquals(
      grid.getAfterFillerHeight(16), 3 * ROW_HEIGHT + 2 * GROUP_HEADING_HEIGHT);
  // Item 16 is in group #5/earlier_this_year, 2 rows below + 1 header.
  assertEquals(
      grid.getAfterFillerHeight(17), 2 * ROW_HEIGHT + 1 * GROUP_HEADING_HEIGHT);
  // Item 17,18,19 is in group #6/older, 1 row below.
  assertEquals(grid.getAfterFillerHeight(18), 1 * ROW_HEIGHT);
  assertEquals(grid.getAfterFillerHeight(19), 1 * ROW_HEIGHT);
  assertEquals(grid.getAfterFillerHeight(20), 1 * ROW_HEIGHT);
}

export function testGetRowForListOffset() {
  const grid = setupFileGrid();
  grid['getGroupHeadingHeight_'] = () => GROUP_HEADING_HEIGHT;
  grid['paddingTop_'] = 0;  // To ease calculation.
  assert(grid.dataModel);
  // Enable group by modification time.
  groupByModificationTime(grid.dataModel);
  // index                                       height      total height
  // --------------------------------------------------------------------
  // Heading #1/today:                             30              30
  // (row 0)   Item 0    Item 1                    50              80
  // Heading #2/yesterday:                         30              110
  // (row 1)   Item 2    Item 3    Item 4          50              160
  // Heading #3/earlier_this_week:                 30              190
  // (row 2)   Item 5    Item 6    Item 7          50              240
  // (row 3)   Item 8    Item 9                    50              290
  // Heading #4/earlier_this_month:                30              320
  // (row 4)   Item 10   Item 11   Item 12         50              370
  // (row 5)   Item 13   Item 14   Item 15         50              420
  // Heading #5/earlier_this_year:                 30              450
  // (row 6)   Item 16                             50              500
  // Heading #6/older:                             30              530
  // (row 7)   Item 17   Item 18   Item 19         50              580
  assertEquals(grid['getRowForListOffset_'](0), 0);
  assertEquals(grid['getRowForListOffset_'](30), 0);
  assertEquals(grid['getRowForListOffset_'](100), 1);
  assertEquals(grid['getRowForListOffset_'](200), 2);
  assertEquals(grid['getRowForListOffset_'](240), 3);
  assertEquals(grid['getRowForListOffset_'](300), 4);
  assertEquals(grid['getRowForListOffset_'](400), 5);
  assertEquals(grid['getRowForListOffset_'](450), 6);
  assertEquals(grid['getRowForListOffset_'](500), 7);
  assertEquals(grid['getRowForListOffset_'](600), 7);
}

export function testItemHeightForGroupByModificationTime() {
  const grid = setupFileGrid();
  assert(grid.dataModel);
  // Enable group by modification time.
  groupByModificationTime(grid.dataModel);

  // We are testing the logic in getGroupHeadingHeight_(), so we should use
  // the real MODIFICATION_TIME_GROUP_HEADING_HEIGHT(57) and
  // GROUP_MARGIN_TOP(16) from file_grid.
  assertEquals(grid['getGroupHeadingHeight_'](0), 57);
  assertEquals(grid['getGroupHeadingHeight_'](1), 57 + 16);
  for (let i = 0; i < 20; i++) {
    assertEquals(grid['getItemHeightByIndex_'](i), FILE_ITEM_HEIGHT);
  }
}

export function testItemHeightForGroupByDirectory() {
  const grid = setupFileGrid();
  assert(grid.dataModel);
  // Enable group by directory.
  groupByDirectory(grid.dataModel);

  // We are testing the logic in getGroupHeadingHeight_(), so we should use
  // the real DIRECTORY_GROUP_HEADING_HEIGHT(40) and GROUP_MARGIN_TOP(16)
  // from file_grid.
  assertEquals(grid['getGroupHeadingHeight_'](0), 40);
  assertEquals(grid['getGroupHeadingHeight_'](1), 40 + 16);
  for (let i = 0; i < 20; i++) {
    // index 3 is the last item for folders
    assertEquals(
        grid['getItemHeightByIndex_'](i),
        i <= 3 ? FOLDER_ITEM_HEIGHT : FILE_ITEM_HEIGHT);
  }
}

export function testGetHitRowIndex() {
  const grid = setupFileGrid();
  assert(grid.dataModel);
  // Enable group by directory.
  groupByDirectory(grid.dataModel);
  grid['getGroupHeadingHeight_'] = () => GROUP_HEADING_HEIGHT;

  // index                                       height      total height
  // --------------------------------------------------------------------
  // Heading #1/folders:                          30             30
  // (row 0)    Item 0    Item 1    Item 2        20             50
  // (row 1)    Item 3                            20             70
  // Heading #2/files:                            30             100
  // (row 2)    Item 4    Item 5    Item 6        50             150
  // (row 3)    Item 7    Item 8    Item 9        50             200
  // (row 4)    Item 10   Item 11   Item 12       50             250
  // (row 5)    Item 13   Item 14   Item 15       50             300
  // (row 6)    Item 16   Item 17   Item 18       50             350
  // (row 7)    Item 19                           50             400
  assertEquals(grid['getHitRowIndex_'](0, true), 0);
  assertEquals(grid['getHitRowIndex_'](0, false), -1);
  assertEquals(grid['getHitRowIndex_'](30, true), 0);
  assertEquals(grid['getHitRowIndex_'](30, false), -1);
  assertEquals(grid['getHitRowIndex_'](70, true), 2);
  assertEquals(grid['getHitRowIndex_'](70, false), 1);
  assertEquals(grid['getHitRowIndex_'](70 + ITEM_MARGIN_TOP, true), 2);
  assertEquals(grid['getHitRowIndex_'](70 + ITEM_MARGIN_TOP, false), 1);
  assertEquals(grid['getHitRowIndex_'](100, true), 2);
  assertEquals(grid['getHitRowIndex_'](100, false), 1);
  assertEquals(grid['getHitRowIndex_'](180, true), 3);
  assertEquals(grid['getHitRowIndex_'](180, false), 3);
  assertEquals(grid['getHitRowIndex_'](250 + ITEM_MARGIN_TOP - 1, true), 5);
  assertEquals(grid['getHitRowIndex_'](250 + ITEM_MARGIN_TOP - 1, false), 4);
  // For larger y, the out of bound row index will be returned (e.g. max
  // index = 7), which is expected.
  assertEquals(grid['getHitRowIndex_'](450, true), 8);
  assertEquals(grid['getHitRowIndex_'](450, false), 8);
}

export function testGetHitColumnIndex() {
  const grid = setupFileGrid();
  assert(grid.dataModel);
  // Enable group by directory.
  groupByDirectory(grid.dataModel);
  grid['getGroupHeadingHeight_'] = () => GROUP_HEADING_HEIGHT;

  //           (col 0)  (col 1)   (col 2)
  // -----------------------------------------
  //         |   100 | |  100  | |   100 |
  // -----------------------------------------
  // Heading #1/folders:
  // (row 0)    Item 0    Item 1    Item 2
  // (row 1)    Item 3
  // Heading #2/files:
  // (row 2)    Item 4    Item 5    Item 6
  // (row 3)    Item 7    Item 8    Item 9
  // (row 4)    Item 10   Item 11   Item 12
  // (row 5)    Item 13   Item 14   Item 15
  // (row 6)    Item 16   Item 17   Item 18
  // (row 7)    Item 19
  assertEquals(grid['getHitColumnIndex_'](0, true), 0);
  assertEquals(grid['getHitColumnIndex_'](0, false), -1);
  assertEquals(grid['getHitColumnIndex_'](60, true), 0);
  assertEquals(grid['getHitColumnIndex_'](60, false), 0);
  assertEquals(grid['getHitColumnIndex_'](100 + ITEM_MARGIN_LEFT - 1, true), 1);
  assertEquals(
      grid['getHitColumnIndex_'](100 + ITEM_MARGIN_LEFT - 1, false), 0);
  // For larger x, the out of bound column index will be returned (e.g. max
  // index = 2), which is expected.
  assertEquals(grid['getHitColumnIndex_'](400, true), 4);
  assertEquals(grid['getHitColumnIndex_'](400, false), 3);
}

// Test FileGridSelectionController's getIndexAbove() and getIndexBelow().
export function testSelectionModelIndexMovement() {
  const grid = setupFileGrid();
  assert(grid.dataModel);
  groupByDirectory(grid.dataModel);
  const sm = new FileGridSelectionController(
      new ListSelectionModel(grid.dataModel.length), grid);
  // Heading #1/folders:
  // (row 0)    Item 0    Item 1    Item 2
  // (row 1)    Item 3
  // Heading #2/files:
  // (row 2)    Item 4    Item 5    Item 6
  // (row 3)    Item 7    Item 8    Item 9
  // (row 4)    Item 10   Item 11   Item 12
  // (row 5)    Item 13   Item 14   Item 15
  // (row 6)    Item 16   Item 17   Item 18
  // (row 7)    Item 19

  // getIndexAbove()
  assertEquals(sm.getIndexAbove(0), -1);
  assertEquals(sm.getIndexAbove(1), 0);
  assertEquals(sm.getIndexAbove(2), 0);
  assertEquals(sm.getIndexAbove(3), 0);
  assertEquals(sm.getIndexAbove(4), 3);
  // The col above item 5/6 doesn't have items, so fall back to 3.
  assertEquals(sm.getIndexAbove(5), 3);
  assertEquals(sm.getIndexAbove(6), 3);
  assertEquals(sm.getIndexAbove(11), 8);
  assertEquals(sm.getIndexAbove(15), 12);
  assertEquals(sm.getIndexAbove(19), 16);
  // getIndexBelow()
  assertEquals(sm.getIndexBelow(0), 3);
  // The col below item 1/2 doesn't have items, so fall back to 3.
  assertEquals(sm.getIndexBelow(1), 3);
  assertEquals(sm.getIndexBelow(2), 3);
  assertEquals(sm.getIndexBelow(3), 4);
  assertEquals(sm.getIndexBelow(4), 7);
  assertEquals(sm.getIndexBelow(8), 11);
  assertEquals(sm.getIndexBelow(12), 15);
  // The col below item 17/18 doesn't have items, so fall back to 19.
  assertEquals(sm.getIndexBelow(17), 19);
  assertEquals(sm.getIndexBelow(18), 19);
  assertEquals(sm.getIndexBelow(19), -1);
}