| // Copyright 2017 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 {BookmarksFolderNodeElement, BookmarksItemElement, BookmarksListElement, SelectFolderAction, SelectItemsAction} from 'chrome://bookmarks/bookmarks.js'; |
| import {BookmarkManagerApiProxyImpl, BookmarksApiProxyImpl, BookmarksCommandManagerElement, Command, createBookmark, DialogFocusManager, getDisplayedList, MenuSource, selectFolder, setDebouncerForTesting} from 'chrome://bookmarks/bookmarks.js'; |
| import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; |
| import {isMac} from 'chrome://resources/js/platform.js'; |
| import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; |
| import {pressAndReleaseKeyOn} from 'chrome://webui-test/keyboard_mock_interactions.js'; |
| import type {ModifiersParam} from 'chrome://webui-test/keyboard_mock_interactions.js'; |
| import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js'; |
| |
| import {TestBookmarkManagerApiProxy} from './test_bookmark_manager_api_proxy.js'; |
| import {TestBookmarksApiProxy} from './test_bookmarks_api_proxy.js'; |
| import {TestCommandManager} from './test_command_manager.js'; |
| import {TestStore} from './test_store.js'; |
| import {createFolder, createItem, customClick, findFolderNode, normalizeIterable, replaceBody, testTree} from './test_util.js'; |
| |
| suite('<bookmarks-command-manager>', function() { |
| let commandManager: BookmarksCommandManagerElement; |
| let testCommandManager: TestCommandManager; |
| let store: TestStore; |
| let bookmarkManagerProxy: TestBookmarkManagerApiProxy; |
| |
| setup(function() { |
| loadTimeData.overrideValues({ |
| splitViewEnabled: true, |
| }); |
| |
| const bulkChildren = []; |
| for (let i = 1; i <= 20; i++) { |
| const id = '3' + i; |
| bulkChildren.push(createItem(id, {url: `http://${id}/`})); |
| } |
| |
| store = new TestStore({ |
| nodes: testTree( |
| createFolder( |
| '1', |
| [ |
| createFolder( |
| '11', |
| [ |
| createItem('111', {url: 'http://111/'}), |
| ]), |
| createFolder( |
| '12', |
| [ |
| createItem('121', {url: 'http://121/'}), |
| createFolder( |
| '122', |
| [ |
| createItem('1221'), |
| ]), |
| ]), |
| createItem('13', {url: 'http://13/'}), |
| createFolder( |
| '14', |
| [ |
| createItem('141'), |
| createItem('142'), |
| ]), |
| ]), |
| createFolder( |
| '2', |
| [ |
| createFolder('21', []), |
| ]), |
| createFolder('3', bulkChildren), createFolder('4', [], { |
| unmodifiable: chrome.bookmarks.BookmarkTreeNodeUnmodifiable.MANAGED, |
| })), |
| selectedFolder: '1', |
| }); |
| store.replaceSingleton(); |
| |
| const bookmarksProxy = new TestBookmarksApiProxy(); |
| BookmarksApiProxyImpl.setInstance(bookmarksProxy); |
| |
| bookmarkManagerProxy = new TestBookmarkManagerApiProxy(); |
| BookmarkManagerApiProxyImpl.setInstance(bookmarkManagerProxy); |
| |
| testCommandManager = new TestCommandManager(); |
| commandManager = testCommandManager.getCommandManager(); |
| replaceBody(commandManager); |
| document.body.appendChild(document.createElement('cr-toast-manager')); |
| DialogFocusManager.setInstance(null); |
| }); |
| |
| test('context menu hides invalid commands', async () => { |
| store.data.selection.items = new Set(['11', '13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.ITEM); |
| await microtasksFinished(); |
| |
| const commandHidden: {[key: string]: boolean} = {}; |
| commandManager.shadowRoot.querySelectorAll<HTMLElement>('.dropdown-item') |
| .forEach(element => { |
| commandHidden[element.dataset['command']!] = element.hidden; |
| }); |
| |
| // With a folder and an item selected, the only available context menu item |
| // is 'Delete'. |
| assertTrue(commandHidden[Command.EDIT] !== undefined); |
| assertTrue(commandHidden[Command.EDIT]); |
| |
| assertTrue(commandHidden[Command.DELETE] !== undefined); |
| assertFalse(commandHidden[Command.DELETE]); |
| }); |
| |
| test('edit shortcut triggers when valid', async () => { |
| const key = isMac ? 'Enter' : 'F2'; |
| |
| store.data.selection.items = new Set(['13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| pressAndReleaseKeyOn(document.body, 0, [], key); |
| testCommandManager.assertLastCommand(Command.EDIT, ['13']); |
| |
| // Doesn't trigger when multiple items are selected. |
| store.data.selection.items = new Set(['11', '13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| pressAndReleaseKeyOn(document.body, 0, [], key); |
| testCommandManager.assertLastCommand(null); |
| |
| // Doesn't trigger when nothing is selected. |
| store.data.selection.items = new Set(); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| pressAndReleaseKeyOn(document.body, 0, [], key); |
| testCommandManager.assertLastCommand(null); |
| }); |
| |
| test('delete command triggers', async () => { |
| store.data.selection.items = new Set(['12', '13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| pressAndReleaseKeyOn(document.body, 46, [], 'Delete'); |
| testCommandManager.assertLastCommand(Command.DELETE, ['12', '13']); |
| }); |
| |
| test('copy command triggers', async () => { |
| store.data.selection.items = new Set(['11', '13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| document.dispatchEvent(new Event('copy')); |
| testCommandManager.assertLastCommand(Command.COPY, ['11', '13']); |
| }); |
| |
| test('cut/paste commands trigger', async () => { |
| store.data.selection.items = new Set(['11', '13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| document.dispatchEvent(new Event('cut')); |
| const lastCut = (await bookmarkManagerProxy.whenCalled('cut')).sort(); |
| assertDeepEquals(['11', '13'], lastCut); |
| |
| setDebouncerForTesting(); |
| document.dispatchEvent(new Event('paste')); |
| const lastPaste = await bookmarkManagerProxy.whenCalled('paste'); |
| assertEquals('1', lastPaste); |
| }); |
| |
| test('undo and redo commands trigger', function() { |
| const undoModifier = isMac ? 'meta' : 'ctrl'; |
| const undoKey = 'z'; |
| const redoModifier: ModifiersParam = isMac ? ['meta', 'shift'] : 'ctrl'; |
| const redoKey = isMac ? 'Z' : 'y'; |
| |
| pressAndReleaseKeyOn(document.body, 0, undoModifier, undoKey); |
| testCommandManager.assertLastCommand(Command.UNDO); |
| |
| pressAndReleaseKeyOn(document.body, 0, redoModifier, redoKey); |
| testCommandManager.assertLastCommand(Command.REDO); |
| }); |
| |
| test('undo triggered when bookmarks-toolbar element has focus', function() { |
| const element = document.createElement('bookmarks-toolbar'); |
| document.body.appendChild(element); |
| pressAndReleaseKeyOn(element, 0, isMac ? 'meta' : 'ctrl', 'z'); |
| testCommandManager.assertLastCommand(Command.UNDO); |
| }); |
| |
| test('undo not triggered when most other elements have focus', function() { |
| const element = document.createElement('div'); |
| document.body.appendChild(element); |
| pressAndReleaseKeyOn(element, 0, isMac ? 'meta' : 'ctrl', 'z'); |
| testCommandManager.assertLastCommand(null); |
| }); |
| |
| test('undo not triggered when toolbar input has focus', function() { |
| const toolbar = document.createElement('bookmarks-toolbar'); |
| const input = document.createElement('input'); |
| toolbar.appendChild(input); |
| document.body.appendChild(toolbar); |
| pressAndReleaseKeyOn(input, 0, isMac ? 'meta' : 'ctrl', 'z'); |
| testCommandManager.assertLastCommand(null); |
| }); |
| |
| test('Show In Folder is only available during search', async () => { |
| store.data.selection.items = new Set(['12']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.ITEM); |
| await microtasksFinished(); |
| const showInFolderItem = |
| commandManager.shadowRoot.querySelector<HTMLElement>( |
| `[data-command='${Command.SHOW_IN_FOLDER}']`); |
| |
| // Show in folder hidden when search is inactive. |
| assertTrue(!!showInFolderItem); |
| assertTrue(showInFolderItem.hidden); |
| |
| // Show in Folder visible when search is active. |
| store.data.search.term = 'test'; |
| store.data.search.results = ['12', '13']; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| commandManager.closeCommandMenu(); |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.ITEM); |
| await microtasksFinished(); |
| assertFalse(showInFolderItem.hidden); |
| |
| // Show in Folder hidden when menu is opened from the sidebar. |
| commandManager.closeCommandMenu(); |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.TREE); |
| await microtasksFinished(); |
| assertTrue(showInFolderItem.hidden); |
| |
| // Show in Folder hidden when multiple items are selected. |
| store.data.selection.items = new Set(['12', '13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| commandManager.closeCommandMenu(); |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.ITEM); |
| await microtasksFinished(); |
| assertTrue(showInFolderItem.hidden); |
| |
| // Executing the command selects the parent folder. |
| commandManager.handle(Command.SHOW_IN_FOLDER, new Set(['12'])); |
| assertTrue(!!store.lastAction); |
| assertEquals('select-folder', store.lastAction.name); |
| assertEquals('1', (store.lastAction as SelectFolderAction).id); |
| }); |
| |
| test('does not delete children at same time as ancestor', async function() { |
| const parentAndChildren = new Set(['11', '12', '111', '1221']); |
| assertTrue(commandManager.canExecute(Command.DELETE, parentAndChildren)); |
| commandManager.handle(Command.DELETE, parentAndChildren); |
| |
| const lastDelete = await bookmarkManagerProxy.whenCalled('removeTrees'); |
| |
| assertDeepEquals(['11', '12'], lastDelete); |
| }); |
| |
| test('shift-enter opens URLs in new window', async function() { |
| store.data.selection.items = new Set(['12', '13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| pressAndReleaseKeyOn(document.body, 13, 'shift', 'Enter'); |
| const [ids, incognito] = |
| await bookmarkManagerProxy.whenCalled('openInNewWindow'); |
| testCommandManager.assertLastCommand(Command.OPEN_NEW_WINDOW, ['12', '13']); |
| assertDeepEquals(['121', '13'], ids); |
| assertFalse(incognito); |
| }); |
| |
| test('shift-enter does not trigger enter commands', async () => { |
| // Enter by itself performs an edit (Mac) or open (non-Mac). Ensure that |
| // shift-enter doesn't trigger those commands. |
| store.data.selection.items = new Set(['13']); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| pressAndReleaseKeyOn(document.body, 13, 'shift', 'Enter'); |
| testCommandManager.assertLastCommand(Command.OPEN_NEW_WINDOW); |
| }); |
| |
| test('opening many items causes a confirmation dialog', async function() { |
| const items = new Set(['3']); |
| assertTrue(commandManager.canExecute(Command.OPEN_NEW_WINDOW, items)); |
| |
| commandManager.handle(Command.OPEN_NEW_WINDOW, items); |
| await microtasksFinished(); |
| |
| const dialog = commandManager.shadowRoot.querySelector('cr-dialog'); |
| assertTrue(!!dialog); |
| assertTrue(dialog.open); |
| |
| // Pressing 'cancel' should not open the window. |
| dialog.querySelector<HTMLElement>('.cancel-button')!.click(); |
| await microtasksFinished(); |
| assertFalse(dialog.open); |
| |
| commandManager.handle(Command.OPEN_NEW_WINDOW, items); |
| await microtasksFinished(); |
| assertTrue(dialog.open); |
| |
| // Pressing 'yes' will open all the URLs. |
| dialog.querySelector<HTMLElement>('.action-button')!.click(); |
| const [ids] = await bookmarkManagerProxy.whenCalled('openInNewWindow'); |
| assertFalse(dialog.open); |
| assertEquals(20, ids.length); |
| }); |
| |
| test('"Open in Split View" passes correct args', async function() { |
| const items = new Set(['141']); |
| assertTrue(commandManager.canExecute(Command.OPEN_SPLIT_VIEW, items)); |
| |
| commandManager.handle(Command.OPEN_SPLIT_VIEW, items); |
| await microtasksFinished(); |
| |
| const [id, {active, split}] = |
| await bookmarkManagerProxy.whenCalled('openInNewTab'); |
| |
| assertEquals('141', id); |
| assertFalse(active); |
| assertTrue(split); |
| }); |
| |
| test('"Open in New Tab Group" does not expand nodes', async function() { |
| const items = new Set(['1']); |
| assertTrue(commandManager.canExecute(Command.OPEN_NEW_GROUP, items)); |
| commandManager.handle(Command.OPEN_NEW_GROUP, items); |
| await microtasksFinished(); |
| |
| const [ids] = await bookmarkManagerProxy.whenCalled('openInNewTabGroup'); |
| assertDeepEquals(['1'], ids); |
| }); |
| |
| test( |
| 'cannot execute "Open in New Tab" on folders with no items', async () => { |
| const items = new Set(['2']); |
| assertFalse(commandManager.canExecute(Command.OPEN_NEW_TAB, items)); |
| |
| store.data.selection.items = items; |
| |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.ITEM); |
| await microtasksFinished(); |
| |
| const commandItem: {[key: string]: HTMLButtonElement} = {}; |
| commandManager.shadowRoot |
| .querySelectorAll<HTMLButtonElement>('.dropdown-item') |
| .forEach(element => { |
| commandItem[element.dataset['command']!] = element; |
| }); |
| |
| assertTrue(!!commandItem[Command.OPEN_NEW_TAB]); |
| assertTrue(commandItem[Command.OPEN_NEW_TAB].disabled); |
| assertFalse(commandItem[Command.OPEN_NEW_TAB].hidden); |
| |
| assertTrue(!!commandItem[Command.OPEN_NEW_WINDOW]); |
| assertTrue(commandItem[Command.OPEN_NEW_WINDOW].disabled); |
| assertFalse(commandItem[Command.OPEN_NEW_WINDOW].hidden); |
| |
| assertTrue(!!commandItem[Command.OPEN_INCOGNITO]); |
| assertTrue(commandItem[Command.OPEN_INCOGNITO].disabled); |
| assertFalse(commandItem[Command.OPEN_INCOGNITO].hidden); |
| |
| assertTrue(!!commandItem[Command.OPEN_SPLIT_VIEW]); |
| assertTrue(commandItem[Command.OPEN_SPLIT_VIEW].disabled); |
| assertFalse(commandItem[Command.OPEN_SPLIT_VIEW].hidden); |
| |
| assertTrue(!!commandItem[Command.OPEN_NEW_GROUP]); |
| assertTrue(commandItem[Command.OPEN_NEW_GROUP].disabled); |
| assertFalse(commandItem[Command.OPEN_NEW_GROUP].hidden); |
| }); |
| |
| test('cannot execute editing commands when editing is disabled', async () => { |
| const items = new Set(['12']); |
| |
| store.data.prefs.canEdit = false; |
| store.data.selection.items = items; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| assertFalse(commandManager.canExecute(Command.EDIT, items)); |
| assertFalse(commandManager.canExecute(Command.DELETE, items)); |
| assertFalse(commandManager.canExecute(Command.UNDO, items)); |
| assertFalse(commandManager.canExecute(Command.REDO, items)); |
| |
| // No divider line should be visible when only 'Open' commands are enabled. |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.ITEM); |
| await microtasksFinished(); |
| commandManager.shadowRoot.querySelectorAll('hr').forEach(element => { |
| assertTrue(element.hidden); |
| }); |
| }); |
| |
| test('cannot edit unmodifiable nodes', async () => { |
| // Cannot edit root folders. |
| let items = new Set(['1']); |
| store.data.selection.items = items; |
| assertFalse(commandManager.canExecute(Command.EDIT, items)); |
| assertFalse(commandManager.canExecute(Command.DELETE, items)); |
| assertFalse(commandManager.canExecute(Command.CUT, items)); |
| |
| items = new Set(['4']); |
| assertFalse(commandManager.canExecute(Command.EDIT, items)); |
| assertFalse(commandManager.canExecute(Command.DELETE, items)); |
| assertFalse(commandManager.canExecute(Command.CUT, items)); |
| |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.ITEM); |
| await microtasksFinished(); |
| const commandItem: {[key: string]: HTMLElement} = {}; |
| commandManager.shadowRoot.querySelectorAll<HTMLElement>('.dropdown-item') |
| .forEach(element => { |
| commandItem[element.dataset['command']!] = element; |
| }); |
| assertTrue(!!commandItem[Command.EDIT]); |
| commandItem[Command.EDIT].click(); |
| testCommandManager.assertLastCommand(null); |
| }); |
| |
| test('keyboard shortcuts are disabled while a dialog is open', async () => { |
| assertFalse(DialogFocusManager.getInstance().hasOpenDialog()); |
| const items = new Set(['12']); |
| store.data.selection.items = items; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| const editKey = isMac ? 'Enter' : 'F2'; |
| pressAndReleaseKeyOn(document.body, 0, [], editKey); |
| testCommandManager.assertLastCommand(Command.EDIT); |
| await microtasksFinished(); |
| assertTrue(DialogFocusManager.getInstance().hasOpenDialog()); |
| |
| pressAndReleaseKeyOn(document.body, 0, [], 'Delete'); |
| testCommandManager.assertLastCommand(null); |
| }); |
| |
| test('toolbar menu options are disabled when appropriate', async () => { |
| store.data.selectedFolder = '1'; |
| store.data.prefs.canEdit = true; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.TOOLBAR); |
| await microtasksFinished(); |
| assertTrue(commandManager.canExecute(Command.SORT, new Set())); |
| assertTrue(commandManager.canExecute(Command.ADD_BOOKMARK, new Set())); |
| assertTrue(commandManager.canExecute(Command.ADD_FOLDER, new Set())); |
| |
| store.data.selectedFolder = '4'; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| assertFalse(commandManager.canExecute(Command.SORT, new Set())); |
| assertFalse(commandManager.canExecute(Command.ADD_BOOKMARK, new Set())); |
| assertFalse(commandManager.canExecute(Command.ADD_FOLDER, new Set())); |
| assertTrue(commandManager.canExecute(Command.IMPORT, new Set())); |
| |
| store.data.selectedFolder = '1'; |
| store.data.prefs.canEdit = false; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| assertFalse(commandManager.canExecute(Command.SORT, new Set())); |
| assertFalse(commandManager.canExecute(Command.IMPORT, new Set())); |
| assertFalse(commandManager.canExecute(Command.ADD_BOOKMARK, new Set())); |
| assertFalse(commandManager.canExecute(Command.ADD_FOLDER, new Set())); |
| }); |
| |
| test('sort button is disabled when folder is empty', async () => { |
| store.data.selectedFolder = '3'; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| commandManager.openCommandMenuAtPosition(0, 0, MenuSource.TOOLBAR); |
| await microtasksFinished(); |
| assertTrue(commandManager.canExecute(Command.SORT, new Set())); |
| |
| store.data.selectedFolder = '21'; |
| store.notifyObservers(); |
| await microtasksFinished(); |
| |
| assertFalse(commandManager.canExecute(Command.SORT, new Set())); |
| |
| // Adding 2 bookmarks should enable sorting. |
| store.setReducersEnabled(true); |
| const item1 = { |
| id: '211', |
| parentId: '21', |
| index: 0, |
| url: 'https://www.example.com', |
| title: 'example', |
| }; |
| store.dispatch(createBookmark(item1.id, item1)); |
| assertFalse(commandManager.canExecute(Command.SORT, new Set())); |
| |
| const item2 = { |
| id: '212', |
| parentId: '21', |
| index: 1, |
| url: 'https://www.example.com', |
| title: 'example', |
| }; |
| store.dispatch(createBookmark(item2.id, item2)); |
| assertTrue(commandManager.canExecute(Command.SORT, new Set())); |
| }); |
| }); |
| |
| suite('<bookmarks-item> CommandManager integration', function() { |
| let list: BookmarksListElement; |
| let items: NodeListOf<BookmarksItemElement>; |
| let commandManager: BookmarksCommandManagerElement; |
| let rootNode: BookmarksFolderNodeElement; |
| let store: TestStore; |
| let bookmarkManagerProxy: TestBookmarkManagerApiProxy; |
| |
| setup(async function() { |
| store = new TestStore({ |
| nodes: testTree(createFolder( |
| '1', |
| [ |
| createFolder( |
| '11', |
| [ |
| createItem('111', {url: 'http://111/'}), |
| ]), |
| createItem('12', {url: 'http://12/'}), |
| createItem('13', {url: 'http://13/'}), |
| ])), |
| selectedFolder: '1', |
| }); |
| store.setReducersEnabled(true); |
| store.replaceSingleton(); |
| bookmarkManagerProxy = new TestBookmarkManagerApiProxy(); |
| BookmarkManagerApiProxyImpl.setInstance(bookmarkManagerProxy); |
| |
| commandManager = document.createElement('bookmarks-command-manager'); |
| |
| list = document.createElement('bookmarks-list'); |
| replaceBody(list); |
| document.body.appendChild(commandManager); |
| |
| rootNode = document.createElement('bookmarks-folder-node'); |
| rootNode.itemId = '1'; |
| rootNode.depth = 0; |
| document.body.appendChild(rootNode); |
| document.body.appendChild(document.createElement('cr-toast-manager')); |
| await eventToPromise('viewport-filled', list.$.list); |
| |
| items = list.shadowRoot.querySelectorAll<BookmarksItemElement>( |
| 'bookmarks-item'); |
| // Wait for the flushed properties to propagate to the item elements' DOMs. |
| await microtasksFinished(); |
| }); |
| |
| function simulateDoubleClick(element: HTMLElement, config?: MouseEventInit) { |
| config = config || {}; |
| customClick(element, config); |
| config.detail = 2; |
| customClick(element, config); |
| } |
| |
| function simulateMiddleClick(element: HTMLElement, config?: MouseEventInit) { |
| config = config || {}; |
| config.button = 1; |
| customClick(element, config, 'auxclick'); |
| } |
| |
| test('double click opens folders in bookmark manager', function() { |
| simulateDoubleClick(items[0]!); |
| assertEquals(store.data.selectedFolder, '11'); |
| }); |
| |
| test('double click opens items in foreground tab', async function() { |
| simulateDoubleClick(items[1]!); |
| |
| const [id, {active, split}] = |
| await bookmarkManagerProxy.whenCalled('openInNewTab'); |
| |
| assertEquals('12', id); |
| assertTrue(active); |
| assertFalse(split); |
| }); |
| |
| test('shift-double click opens full selection', function() { |
| // Shift-double click works because the first click event selects the range |
| // of items, then the second doubleclick event opens that whole selection. |
| const item1 = items[0]; |
| const item2 = items[1]; |
| |
| assertTrue(!!item1); |
| assertTrue(!!item2); |
| |
| customClick(item1); |
| simulateDoubleClick(item2, {shiftKey: true}); |
| |
| const [id1] = bookmarkManagerProxy.getArgs('openInNewTab')[0]; |
| const [id2] = bookmarkManagerProxy.getArgs('openInNewTab')[1]; |
| |
| assertDeepEquals(['11', '12'], [id1, id2]); |
| }); |
| |
| test('control-double click opens full selection', function() { |
| const item1 = items[0]; |
| const item2 = items[2]; |
| |
| assertTrue(!!item1); |
| assertTrue(!!item2); |
| |
| customClick(item1); |
| simulateDoubleClick(item2, {ctrlKey: true}); |
| |
| const [id1] = bookmarkManagerProxy.getArgs('openInNewTab')[0]; |
| const [id2] = bookmarkManagerProxy.getArgs('openInNewTab')[1]; |
| |
| assertDeepEquals(['11', '13'], [id1, id2]); |
| }); |
| |
| test('middle-click opens clicked item in new tab', async function() { |
| const item1 = items[1]; |
| const item2 = items[2]; |
| |
| assertTrue(!!item1); |
| assertTrue(!!item2); |
| |
| // Select multiple items. |
| customClick(item1); |
| customClick(item2, {shiftKey: true}); |
| |
| // Only the middle-clicked item is opened. |
| simulateMiddleClick(item2); |
| |
| const [id, {active, split}] = |
| await bookmarkManagerProxy.whenCalled('openInNewTab'); |
| |
| assertEquals('13', id); |
| assertFalse(active); |
| assertFalse(split); |
| }); |
| |
| test('middle-click does not open folders', function() { |
| const item = items[0]; |
| assertTrue(!!item); |
| |
| simulateMiddleClick(item); |
| |
| assertDeepEquals(['11'], normalizeIterable(store.data.selection.items)); |
| assertEquals(0, bookmarkManagerProxy.getCallCount('openInNewTab')); |
| }); |
| |
| test('shift-middle click opens in foreground tab', async function() { |
| const item = items[1]; |
| assertTrue(!!item); |
| |
| simulateMiddleClick(item, {shiftKey: true}); |
| const [id, {active, split}] = |
| await bookmarkManagerProxy.whenCalled('openInNewTab'); |
| |
| assertEquals('12', id); |
| assertTrue(active); |
| assertFalse(split); |
| }); |
| |
| test( |
| 'copy/cut/paste for folder nodes independent of selection', |
| async function() { |
| const modifier = isMac ? 'meta' : 'ctrl'; |
| |
| store.data.selection.items = new Set(['12', '13']); |
| store.data.folderOpenState.set('1', true); |
| store.notifyObservers(); |
| await microtasksFinished(); |
| const targetNode = findFolderNode(rootNode, '11'); |
| assertTrue(!!targetNode); |
| |
| pressAndReleaseKeyOn(targetNode, 0, modifier, 'c'); |
| const lastCopy = await bookmarkManagerProxy.whenCalled('copy'); |
| assertDeepEquals(['11'], lastCopy); |
| |
| pressAndReleaseKeyOn(targetNode, 0, modifier, 'x'); |
| const lastCut = await bookmarkManagerProxy.whenCalled('cut'); |
| assertDeepEquals(['11'], lastCut); |
| }); |
| |
| test('context menu disappears immediately on right click', async function() { |
| bookmarkManagerProxy.setCanPaste(true); |
| await microtasksFinished(); |
| |
| customClick(items[0]!, {button: 1}, 'contextmenu'); |
| assertDeepEquals(['11'], normalizeIterable(store.data.selection.items)); |
| |
| await microtasksFinished(); |
| |
| const dropdown = commandManager.$.dropdown.getIfExists(); |
| assertTrue(!!dropdown); |
| |
| const dialog = dropdown.getDialog(); |
| assertTrue(dropdown.open); |
| |
| const x = dialog.offsetLeft + dialog.offsetWidth + 5; |
| const y = dialog.offsetHeight; |
| |
| // Ensure the dialog is the target even when clicking outside it, and send |
| // a context menu event which should immediately dismiss the dialog, |
| // allowing subsequent events to bubble through to elements below. |
| assertEquals(dropdown, commandManager.shadowRoot.elementFromPoint(x, y)); |
| assertEquals(dialog, dropdown.shadowRoot.elementFromPoint(x, y)); |
| customClick(dialog, {clientX: x, clientY: y, button: 1}, 'contextmenu'); |
| assertFalse(dropdown.open); |
| }); |
| }); |
| |
| suite('<bookmarks-command-manager> whole page integration', function() { |
| let store: TestStore; |
| let commandManager: BookmarksCommandManagerElement; |
| let testFolderId: string; |
| |
| function create(details: chrome.bookmarks.CreateDetails) { |
| return chrome.bookmarks.create(details); |
| } |
| |
| suiteSetup(async function() { |
| const testFolder = { |
| parentId: '1', |
| title: 'Test', |
| }; |
| const testFolderNode = await create(testFolder); |
| testFolderId = testFolderNode.id; |
| const testItem = { |
| parentId: testFolderId, |
| title: 'Test bookmark', |
| url: 'https://www.example.com/', |
| }; |
| |
| await create(testItem); |
| await create(testItem); |
| }); |
| |
| setup(async function() { |
| const bookmarksProxy = new BookmarksApiProxyImpl(); |
| BookmarksApiProxyImpl.setInstance(bookmarksProxy); |
| const bookmarkManagerProxy = new BookmarkManagerApiProxyImpl(); |
| BookmarkManagerApiProxyImpl.setInstance(bookmarkManagerProxy); |
| store = new TestStore({}); |
| store.replaceSingleton(); |
| store.setReducersEnabled(true); |
| const promise = store.acceptInitOnce(); |
| const app = document.createElement('bookmarks-app'); |
| replaceBody(app); |
| |
| commandManager = BookmarksCommandManagerElement.getInstance(); |
| |
| await promise; |
| |
| store.dispatch(selectFolder(testFolderId)); |
| }); |
| |
| test('paste selects newly created items', async function() { |
| const displayedIdsBefore = getDisplayedList(store.data); |
| |
| commandManager.handle(Command.SELECT_ALL, new Set()); |
| commandManager.handle(Command.COPY, new Set(displayedIdsBefore)); |
| |
| store.expectAction('select-items'); |
| commandManager.handle(Command.PASTE, new Set()); |
| const action = |
| await store.waitForAction('select-items') as SelectItemsAction; |
| |
| const displayedIdsAfter = getDisplayedList(store.data); |
| assertEquals(4, displayedIdsAfter.length); |
| |
| // The start of the list shouldn't change. |
| assertEquals(displayedIdsBefore[0], displayedIdsAfter[0]); |
| assertEquals(displayedIdsBefore[1], displayedIdsAfter[1]); |
| |
| // The two pasted items should be selected at the end of the list. |
| assertEquals(action.items[0], displayedIdsAfter[2]); |
| assertEquals(action.items[1], displayedIdsAfter[3]); |
| assertEquals(2, action.items.length); |
| assertEquals(action.anchor, displayedIdsAfter[2]); |
| }); |
| |
| suiteTeardown(function() { |
| return chrome.bookmarks.removeTree(testFolderId); |
| }); |
| }); |