| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| 'use strict'; |
| |
| (() => { |
| /** |
| * Send Tab keys to Files app until #file-list gets a selected item. |
| * |
| * Raise test failure if #file-list doesn't get anything selected in 20 tabs. |
| * |
| * NOTE: |
| * 1. Sends a maximum of 20 tabs. |
| * 2. The focused element after this function might NOT be #file-list, since |
| * updating the selected item which is async can be detected after an extra |
| * Tab has been sent. |
| */ |
| async function tabUntilFileSelected(appId) { |
| // Check: the file-list should have nothing selected. |
| const selectedRows = await remoteCall.callRemoteTestUtil( |
| 'deepQueryAllElements', appId, ['#file-list li[selected]']); |
| chrome.test.assertEq(0, selectedRows.length); |
| |
| // Press the tab until file-list gets focus and select some file. |
| for (let i = 0; i < 20; i++) { |
| // Send Tab key. |
| const result = await sendTestMessage({name: 'dispatchTabKey'}); |
| chrome.test.assertEq( |
| result, 'tabKeyDispatched', 'Tab key dispatch failed'); |
| |
| // Check if there is a file selected, and return if so. |
| const selectedRows = await remoteCall.callRemoteTestUtil( |
| 'deepQueryAllElements', appId, ['#file-list li[selected]']); |
| if (selectedRows.length > 0) { |
| return; |
| } |
| } |
| |
| chrome.test.assertTrue(false, 'File list has no selection'); |
| } |
| |
| /** |
| * Tests that file list column header have ARIA attributes. |
| */ |
| testcase.fileListAriaAttributes = async () => { |
| const appId = await setupAndWaitUntilReady( |
| RootPath.DOWNLOADS, [ENTRIES.beautiful], []); |
| |
| // Fetch column header. |
| const columnHeadersQuery = |
| ['#detail-table .table-header [aria-describedby]']; |
| const columnHeaders = await remoteCall.callRemoteTestUtil( |
| 'deepQueryAllElements', appId, [columnHeadersQuery, ['display']]); |
| |
| chrome.test.assertTrue(columnHeaders.length > 0); |
| for (const header of columnHeaders) { |
| // aria-describedby is used to tell users that they can click and which |
| // type of sort asc/desc will happen. |
| chrome.test.assertTrue('aria-describedby' in header.attributes); |
| // role button is used so users know that it's clickable. |
| chrome.test.assertEq('button', header.attributes.role); |
| } |
| }; |
| |
| /** |
| * Tests using tab to focus the file list will select the first item, if |
| * nothing is selected. |
| */ |
| testcase.fileListFocusFirstItem = async () => { |
| const appId = await setupAndWaitUntilReady( |
| RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []); |
| |
| // Send Tab keys to make file selected. |
| await tabUntilFileSelected(appId); |
| |
| // Check: The first file only is selected in the file entry rows. |
| const fileRows = await remoteCall.callRemoteTestUtil( |
| 'deepQueryAllElements', appId, ['#file-list li']); |
| chrome.test.assertEq(5, fileRows.length); |
| const selectedRows = fileRows.filter(item => 'selected' in item.attributes); |
| chrome.test.assertEq(1, selectedRows.length); |
| chrome.test.assertEq(0, fileRows.indexOf(selectedRows[0])); |
| }; |
| |
| /** |
| * Tests that after a multiple selection, canceling the selection and using |
| * Tab to focus the files list it selects the item that was last focused. |
| */ |
| testcase.fileListSelectLastFocusedItem = async () => { |
| const appId = await setupAndWaitUntilReady( |
| RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []); |
| |
| // Check: the file-list should have nothing selected. |
| let selectedRows = await remoteCall.callRemoteTestUtil( |
| 'deepQueryAllElements', appId, ['#file-list li[selected]']); |
| chrome.test.assertEq(0, selectedRows.length); |
| |
| // Move to item 2. |
| const downKey = ['#file-list', 'ArrowDown', false, false, false]; |
| for (let i = 0; i < 2; i++) { |
| chrome.test.assertTrue( |
| !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downKey), |
| 'ArrowDown failed'); |
| } |
| |
| // Select item 2 with Ctrl+Space. |
| const ctrlSpace = ['#file-list', ' ', true, false, false]; |
| chrome.test.assertTrue( |
| !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace), |
| 'Ctrl+Space failed'); |
| |
| // Move to item 3 and Cltr+Space to add it to multi-selection. |
| const ctrlDown = ['#file-list', 'ArrowDown', true, false, false]; |
| chrome.test.assertTrue( |
| !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlDown), |
| 'Ctrl+ArrowDown failed'); |
| chrome.test.assertTrue( |
| !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlSpace), |
| 'Ctrl+Space failed'); |
| |
| // Check that items 2 and 3 are selected. |
| selectedRows = await remoteCall.callRemoteTestUtil( |
| 'deepQueryAllElements', appId, ['#file-list li[selected]']); |
| chrome.test.assertEq(2, selectedRows.length); |
| |
| // Cancel the selection. |
| const button = ['#cancel-selection-button']; |
| await remoteCall.waitAndClickElement(appId, '#cancel-selection-button'); |
| |
| // Wait until selection is removed. |
| await remoteCall.waitForElementLost(appId, '#file-list li[selected]'); |
| |
| // Send Tab keys until a file item is selected. |
| await tabUntilFileSelected(appId); |
| |
| // Check: The 3rd item only is selected. |
| const fileRows = await remoteCall.callRemoteTestUtil( |
| 'deepQueryAllElements', appId, ['#file-list li']); |
| chrome.test.assertEq(5, fileRows.length); |
| selectedRows = fileRows.filter(item => 'selected' in item.attributes); |
| chrome.test.assertEq(1, selectedRows.length); |
| chrome.test.assertEq(2, fileRows.indexOf(selectedRows[0])); |
| }; |
| |
| /** |
| * Verifies the total number of a11y messages and asserts the latest message |
| * is the expected one. |
| * |
| * @param {string} appId |
| * @param {number} expectedCount |
| * @param {?string} expectedMessage |
| * @return {string} Latest a11y message. |
| */ |
| async function countAndCheckLatestA11yMessage( |
| appId, expectedCount, expectedMessage) { |
| const a11yMessages = |
| await remoteCall.callRemoteTestUtil('getA11yAnnounces', appId, []); |
| if (expectedMessage === null || expectedMessage === undefined) { |
| return ''; |
| } |
| const latestMessage = a11yMessages[a11yMessages.length - 1]; |
| chrome.test.assertEq( |
| expectedCount, a11yMessages.length, |
| 'Wrong number of a11y messages: latest message: ' + latestMessage); |
| chrome.test.assertEq(expectedMessage, latestMessage); |
| return latestMessage; |
| } |
| |
| /** |
| * Tests that selecting/de-selecting files with keyboard produces a11y |
| * messages. |
| * |
| * NOTE: Test shared with grid_view.js. |
| * @param {boolean=} isGridView if the test is testing the grid view. |
| */ |
| testcase.fileListKeyboardSelectionA11y = async (isGridView) => { |
| const appId = await setupAndWaitUntilReady( |
| RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []); |
| |
| let a11yMsgCount = 0; |
| const viewSelector = isGridView ? 'grid#file-list' : '#file-list'; |
| if (isGridView) { |
| // Click view-button again to switch to detail view. |
| await remoteCall.waitAndClickElement(appId, '#view-button'); |
| |
| // Clicking #view-button adds 1 a11y message. |
| ++a11yMsgCount; |
| } |
| |
| // Keys used for keyboard navigation in the file list. |
| const homeKey = [viewSelector, 'Home', false, false, false]; |
| const ctrlDownKey = [viewSelector, 'ArrowDown', true, false, false]; |
| const ctrlSpaceKey = [viewSelector, ' ', true, false, false]; |
| const shiftEndKey = [viewSelector, 'End', false, true, false]; |
| const ctrlAKey = [viewSelector + ' li', 'a', true, false, false]; |
| const escKey = [viewSelector, 'Escape', false, false, false]; |
| |
| // Select first item with Home key. |
| await remoteCall.fakeKeyDown(appId, ...homeKey); |
| |
| // Navigating with Home key doesn't use aria-live message, it only uses the |
| // native listbox selection state messages. |
| await countAndCheckLatestA11yMessage(appId, a11yMsgCount); |
| |
| // Ctrl+Down & Ctrl+Space to select second item: Beautiful Song.ogg |
| await remoteCall.fakeKeyDown(appId, ...ctrlDownKey); |
| await remoteCall.fakeKeyDown(appId, ...ctrlSpaceKey); |
| |
| // Check: Announced "Beautiful Song.add" added to selection. |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, 'Added Beautiful Song.ogg to selection.'); |
| |
| // Shift+End to select from 2nd item to the last item. |
| await remoteCall.fakeKeyDown(appId, ...shiftEndKey); |
| |
| // Check: Announced range selection from "Beautiful Song.add" to hello.txt. |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, |
| 'Selected a range of 4 entries from Beautiful Song.ogg to hello.txt.'); |
| |
| // Ctrl+Space to de-select currently focused item (last item). |
| await remoteCall.fakeKeyDown(appId, ...ctrlSpaceKey); |
| |
| // Check: Announced de-selecting hello.txt |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, 'Removed hello.txt from selection.'); |
| |
| // Ctrl+A to select all items. |
| await remoteCall.fakeKeyDown(appId, ...ctrlAKey); |
| |
| // Check: Announced selecting all entries. |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, 'Selected all entries.'); |
| |
| // Esc key to deselect all. |
| await remoteCall.fakeKeyDown(appId, ...escKey); |
| |
| // Check: Announced deselecting all entries. |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, 'Removed all entries from selection.'); |
| }; |
| |
| /** |
| * Tests that selecting/de-selecting files with mouse produces a11y messages. |
| * |
| * NOTE: Test shared with grid_view.js. |
| * @param {boolean=} isGridView if the test is testing the grid view. |
| */ |
| testcase.fileListMouseSelectionA11y = async (isGridView) => { |
| const appId = await setupAndWaitUntilReady( |
| RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []); |
| |
| let a11yMsgCount = 0; |
| if (isGridView) { |
| // Click view-button again to switch to detail view. |
| await remoteCall.waitAndClickElement(appId, '#view-button'); |
| |
| // Clicking #view-button adds 1 a11y message. |
| ++a11yMsgCount; |
| } |
| |
| // Click first item. |
| await remoteCall.waitAndClickElement( |
| appId, '#file-list [file-name="photos"]'); |
| |
| // Simple click to select a file doesn't use aria-live message, it only |
| // uses the native listbox selection state messages. |
| await countAndCheckLatestA11yMessage(appId, a11yMsgCount); |
| |
| // Ctrl+Click second item. |
| await remoteCall.waitAndClickElement( |
| appId, '#file-list [file-name="Beautiful Song.ogg"]', {ctrl: true}); |
| |
| // Check: Announced "Beautiful Song.add" added to selection. |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, 'Added Beautiful Song.ogg to selection.'); |
| |
| // Shift+Click last item. |
| await remoteCall.waitAndClickElement( |
| appId, '#file-list [file-name="hello.txt"]', {shift: true}); |
| |
| // Check: Announced range selection from "Beautiful Song.add" to hello.txt. |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, |
| 'Selected a range of 4 entries from Beautiful Song.ogg to hello.txt.'); |
| |
| // Ctrl+Click to de-select the last item. |
| await remoteCall.waitAndClickElement( |
| appId, '#file-list [file-name="hello.txt"]', {ctrl: true}); |
| |
| // Check: Announced de-selecting hello.txt |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, 'Removed hello.txt from selection.'); |
| |
| // Click on "Cancel selection" button. |
| await remoteCall.waitAndClickElement(appId, '#cancel-selection-button'); |
| |
| // Check: Announced deselecting all entries. |
| await countAndCheckLatestA11yMessage( |
| appId, ++a11yMsgCount, 'Removed all entries from selection.'); |
| }; |
| |
| /** |
| * Tests the deletion of one or multiple items. After deletion, one of the |
| * remaining items should have the lead, but shouldn't be in check-select |
| * mode. |
| */ |
| testcase.fileListDeleteMultipleFiles = async () => { |
| const appId = await setupAndWaitUntilReady( |
| RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []); |
| |
| // Select first 2 items. |
| await remoteCall.waitAndClickElement( |
| appId, '#file-list [file-name="world.ogv"]'); |
| await remoteCall.waitAndClickElement( |
| appId, '#file-list [file-name="hello.txt"]', {shift: true}); |
| |
| // Delete item and confirm delete. |
| await remoteCall.waitAndClickElement(appId, '#delete-button'); |
| await remoteCall.waitAndClickElement( |
| appId, '.files-confirm-dialog .cr-dialog-ok'); |
| |
| // Wait for completion of file deletion. |
| await remoteCall.waitForElementLost( |
| appId, '#file-list [file-name="world.ogv"]'); |
| await remoteCall.waitForElementLost( |
| appId, '#file-list [file-name="hello.txt"]'); |
| |
| // Check: no selection state. |
| await remoteCall.waitForElementLost(appId, 'body.check-select'); |
| |
| // Check: lead state of last item. |
| let item = await remoteCall.waitForElement( |
| appId, '#file-list [file-name="My Desktop Background.png"]'); |
| chrome.test.assertTrue('lead' in item.attributes); |
| |
| // Check: selected state of last item. |
| chrome.test.assertTrue('selected' in item.attributes); |
| |
| |
| // Select and delete first item. |
| await remoteCall.waitAndClickElement( |
| appId, '#file-list [file-name="photos"]'); |
| await remoteCall.waitAndClickElement(appId, '#delete-button'); |
| await remoteCall.waitAndClickElement( |
| appId, '.files-confirm-dialog .cr-dialog-ok'); |
| |
| // Wait for file deletion. |
| await remoteCall.waitForElementLost( |
| appId, '#file-list [file-name="photos"]'); |
| |
| // Check: no selection state. |
| await remoteCall.waitForElementLost(appId, 'body.check-select'); |
| |
| // Check: lead state of first item. |
| item = await remoteCall.waitForElement( |
| appId, '#file-list [file-name="Beautiful Song.ogg"]'); |
| chrome.test.assertTrue('lead' in item.attributes); |
| |
| // Check: selected state of first item. |
| chrome.test.assertTrue('selected' in item.attributes); |
| }; |
| })(); |