| // 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. |
| |
| import 'chrome://new-tab-page/lazy_load.js'; |
| import {$$, BrowserProxy} from 'chrome://new-tab-page/new_tab_page.js'; |
| import {isMac} from 'chrome://resources/js/cr.m.js'; |
| import {assertNotStyle, assertStyle, createTestProxy, keydown} from 'chrome://test/new_tab_page/test_support.js'; |
| import {eventToPromise, flushTasks} from 'chrome://test/test_util.m.js'; |
| |
| suite('NewTabPageMostVisitedTest', () => { |
| /** @type {!MostVisitedElement} */ |
| let mostVisited; |
| |
| /** |
| * @implements {BrowserProxy} |
| * @extends {TestBrowserProxy} |
| */ |
| let testProxy; |
| |
| |
| /** @type {!MediaListenerList} */ |
| let mediaListenerWideWidth; |
| |
| /** @type {!MediaListenerList} */ |
| let mediaListenerMediumWidth; |
| |
| /** @type {!Function} */ |
| let mediaListener; |
| |
| /** |
| * @param {string} |
| * @return {!Array<!HTMLElement>} |
| * @private |
| */ |
| function queryAll(q) { |
| return Array.from(mostVisited.shadowRoot.querySelectorAll(q)); |
| } |
| |
| /** |
| * @return {!Array<!HTMLElement>} |
| * @private |
| */ |
| function queryTiles() { |
| return queryAll('.tile'); |
| } |
| |
| /** |
| * @param {number|!Array} n |
| * @param {boolean=} customLinksEnabled |
| * @param {boolean=} visible |
| * @return {!Promise} |
| * @private |
| */ |
| async function addTiles(n, customLinksEnabled = true, visible = true) { |
| const tiles = Array.isArray(n) ? n : Array(n).fill(0).map((x, i) => { |
| const char = String.fromCharCode(i + /* 'a' */ 97); |
| return { |
| title: char, |
| titleDirection: mojoBase.mojom.TextDirection.LEFT_TO_RIGHT, |
| url: {url: `https://${char}/`}, |
| source: i, |
| titleSource: i, |
| isQueryTile: false, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }; |
| }); |
| const tilesRendered = eventToPromise('dom-change', mostVisited.$.tiles); |
| testProxy.callbackRouterRemote.setMostVisitedInfo({ |
| customLinksEnabled: customLinksEnabled, |
| tiles: tiles, |
| visible: visible, |
| }); |
| await testProxy.callbackRouterRemote.$.flushForTesting(); |
| await tilesRendered; |
| } |
| |
| /** @private */ |
| function assertAddShortcutHidden() { |
| assertTrue(mostVisited.$.addShortcut.hidden); |
| } |
| |
| /** @private */ |
| function assertAddShortcutShown() { |
| assertFalse(mostVisited.$.addShortcut.hidden); |
| } |
| |
| /** |
| * @param {boolean} isWide |
| * @param {boolean} isMedium |
| */ |
| function updateScreenWidth(isWide, isMedium) { |
| assertTrue(!!mediaListenerWideWidth); |
| assertTrue(!!mediaListenerMediumWidth); |
| mediaListenerWideWidth.matches = isWide; |
| mediaListenerMediumWidth.matches = isMedium; |
| mediaListener(); |
| } |
| |
| function wide() { |
| updateScreenWidth(true, true); |
| } |
| |
| suiteSetup(() => { |
| loadTimeData.overrideValues({ |
| linkRemovedMsg: '', |
| }); |
| }); |
| |
| setup(() => { |
| PolymerTest.clearBody(); |
| |
| testProxy = createTestProxy(); |
| testProxy.handler.setResultFor('addMostVisitedTile', Promise.resolve({ |
| success: true, |
| })); |
| testProxy.handler.setResultFor('updateMostVisitedTile', Promise.resolve({ |
| success: true, |
| })); |
| testProxy.setResultMapperFor('matchMedia', query => { |
| const mediaListenerList = { |
| matches: false, // Used to determine the screen width. |
| media: query, |
| addListener(listener) { |
| mediaListener = listener; |
| }, |
| removeListener() {}, |
| }; |
| if (query === '(min-width: 672px)') { |
| mediaListenerWideWidth = mediaListenerList; |
| } else if (query === '(min-width: 560px)') { |
| mediaListenerMediumWidth = mediaListenerList; |
| } else { |
| assertTrue(false); |
| } |
| return mediaListenerList; |
| }); |
| BrowserProxy.instance_ = testProxy; |
| mostVisited = document.createElement('ntp-most-visited'); |
| document.body.appendChild(mostVisited); |
| assertEquals(2, testProxy.getCallCount('matchMedia')); |
| wide(); |
| }); |
| |
| test('empty shows add shortcut only', async () => { |
| assertAddShortcutHidden(); |
| await addTiles(0); |
| assertEquals(0, queryTiles().length); |
| assertAddShortcutShown(); |
| }); |
| |
| test('clicking on add shortcut opens dialog', () => { |
| assertFalse(mostVisited.$.dialog.open); |
| mostVisited.$.addShortcut.click(); |
| assertTrue(mostVisited.$.dialog.open); |
| }); |
| |
| test('pressing enter when add shortcut has focus opens dialog', () => { |
| mostVisited.$.addShortcut.focus(); |
| assertFalse(mostVisited.$.dialog.open); |
| keydown(mostVisited.$.addShortcut, 'Enter'); |
| assertTrue(mostVisited.$.dialog.open); |
| }); |
| |
| test('pressing space when add shortcut has focus opens dialog', () => { |
| mostVisited.$.addShortcut.focus(); |
| assertFalse(mostVisited.$.dialog.open); |
| mostVisited.$.addShortcut.dispatchEvent( |
| new KeyboardEvent('keydown', {key: ' '})); |
| mostVisited.$.addShortcut.dispatchEvent( |
| new KeyboardEvent('keyup', {key: ' '})); |
| assertTrue(mostVisited.$.dialog.open); |
| }); |
| |
| test('four tiles fit on one line with addShortcut', async () => { |
| await addTiles(4); |
| assertEquals(4, queryTiles().length); |
| assertAddShortcutShown(); |
| const tops = queryAll('a, #addShortcut').map(({offsetTop}) => offsetTop); |
| assertEquals(5, tops.length); |
| tops.forEach(top => { |
| assertEquals(tops[0], top); |
| }); |
| }); |
| |
| test('five tiles are displayed on two rows with addShortcut', async () => { |
| await addTiles(5); |
| assertEquals(5, queryTiles().length); |
| assertAddShortcutShown(); |
| const tops = queryAll('a, #addShortcut').map(({offsetTop}) => offsetTop); |
| assertEquals(6, tops.length); |
| const firstRowTop = tops[0]; |
| const secondRowTop = tops[3]; |
| assertNotEquals(firstRowTop, secondRowTop); |
| tops.slice(0, 3).forEach(top => { |
| assertEquals(firstRowTop, top); |
| }); |
| tops.slice(3).forEach(top => { |
| assertEquals(secondRowTop, top); |
| }); |
| }); |
| |
| test('nine tiles are displayed on two rows with addShortcut', async () => { |
| await addTiles(9); |
| assertEquals(9, queryTiles().length); |
| assertAddShortcutShown(); |
| const tops = queryAll('a, #addShortcut').map(({offsetTop}) => offsetTop); |
| assertEquals(10, tops.length); |
| const firstRowTop = tops[0]; |
| const secondRowTop = tops[5]; |
| assertNotEquals(firstRowTop, secondRowTop); |
| tops.slice(0, 5).forEach(top => { |
| assertEquals(firstRowTop, top); |
| }); |
| tops.slice(5).forEach(top => { |
| assertEquals(secondRowTop, top); |
| }); |
| }); |
| |
| test('ten tiles are displayed on two rows without addShortcut', async () => { |
| await addTiles(10); |
| assertEquals(10, queryTiles().length); |
| assertAddShortcutHidden(); |
| const tops = queryAll('a:not([hidden])').map(a => a.offsetTop); |
| assertEquals(10, tops.length); |
| const firstRowTop = tops[0]; |
| const secondRowTop = tops[5]; |
| assertNotEquals(firstRowTop, secondRowTop); |
| tops.slice(0, 5).forEach(top => { |
| assertEquals(firstRowTop, top); |
| }); |
| tops.slice(5).forEach(top => { |
| assertEquals(secondRowTop, top); |
| }); |
| }); |
| |
| test('ten tiles is the max tiles displayed', async () => { |
| await addTiles(11); |
| assertEquals(10, queryTiles().length); |
| assertAddShortcutHidden(); |
| }); |
| |
| test('eight tiles is the max (customLinksEnabled=false)', async () => { |
| await addTiles(11, /* customLinksEnabled */ true); |
| assertEquals(10, queryTiles().length); |
| assertEquals(0, queryAll('.tile[hidden]').length); |
| assertAddShortcutHidden(); |
| await addTiles(11, /* customLinksEnabled */ false); |
| assertEquals(8, queryTiles().length); |
| assertEquals(0, queryAll('.tile[hidden]').length); |
| assertAddShortcutHidden(); |
| await addTiles(11, /* customLinksEnabled */ true); |
| assertEquals(10, queryTiles().length); |
| assertEquals(0, queryAll('.tile[hidden]').length); |
| }); |
| |
| test('7 tiles and no add shortcut (customLinksEnabled=false)', async () => { |
| await addTiles(7, /* customLinksEnabled */ true); |
| assertAddShortcutShown(); |
| await addTiles(7, /* customLinksEnabled */ false); |
| assertAddShortcutHidden(); |
| await addTiles(7, /* customLinksEnabled */ true); |
| assertAddShortcutShown(); |
| }); |
| |
| test('no tiles shown when (visible=false)', async () => { |
| await addTiles(1); |
| assertEquals(1, queryTiles().length); |
| assertEquals(0, queryAll('.tile[hidden]').length); |
| assertTrue(mostVisited.visible_); |
| assertFalse(mostVisited.$.container.hidden); |
| await addTiles(1, /* customLinksEnabled */ true, /* visible */ false); |
| assertEquals(1, queryTiles().length); |
| assertEquals(0, queryAll('.tile[hidden]').length); |
| assertFalse(mostVisited.visible_); |
| assertTrue(mostVisited.$.container.hidden); |
| await addTiles(1, /* customLinksEnabled */ true, /* visible */ true); |
| assertEquals(1, queryTiles().length); |
| assertEquals(0, queryAll('.tile[hidden]').length); |
| assertTrue(mostVisited.visible_); |
| assertFalse(mostVisited.$.container.hidden); |
| }); |
| |
| test('dialog opens when add shortcut clicked', () => { |
| const {dialog} = mostVisited.$; |
| assertFalse(dialog.open); |
| mostVisited.$.addShortcut.click(); |
| assertTrue(dialog.open); |
| }); |
| |
| suite('test various widths', () => { |
| function medium() { |
| updateScreenWidth(false, true); |
| } |
| |
| function narrow() { |
| updateScreenWidth(false, false); |
| } |
| |
| test('hide add shortcut if on third row (narrow)', async () => { |
| await addTiles(6); |
| medium(); |
| assertAddShortcutShown(); |
| narrow(); |
| assertAddShortcutHidden(); |
| medium(); |
| assertAddShortcutShown(); |
| }); |
| |
| test('hide add shortcut if on third row (medium)', async () => { |
| await addTiles(8); |
| wide(); |
| assertAddShortcutShown(); |
| medium(); |
| assertAddShortcutHidden(); |
| wide(); |
| assertAddShortcutShown(); |
| }); |
| |
| test('hide add shortcut if on third row (medium)', async () => { |
| await addTiles(9); |
| wide(); |
| assertAddShortcutShown(); |
| await addTiles(10); |
| assertAddShortcutHidden(); |
| }); |
| }); |
| |
| suite('add dialog', () => { |
| /** @private {CrDialogElement} */ |
| let dialog; |
| /** @private {CrInputElement} */ |
| let inputName; |
| /** @private {CrInputElement} */ |
| let inputUrl; |
| /** @private {CrButtonElement} */ |
| let saveButton; |
| /** @private {CrButtonElement} */ |
| let cancelButton; |
| |
| setup(() => { |
| dialog = mostVisited.$.dialog; |
| inputName = mostVisited.$.dialogInputName; |
| inputUrl = mostVisited.$.dialogInputUrl; |
| saveButton = dialog.querySelector('.action-button'); |
| cancelButton = dialog.querySelector('.cancel-button'); |
| mostVisited.$.addShortcut.click(); |
| }); |
| |
| test('inputs are initially empty', () => { |
| assertEquals('', inputName.value); |
| assertEquals('', inputUrl.value); |
| }); |
| |
| test('saveButton is enabled with URL is not empty', () => { |
| assertTrue(saveButton.disabled); |
| |
| inputName.value = 'name'; |
| assertTrue(saveButton.disabled); |
| |
| inputUrl.value = 'url'; |
| assertFalse(saveButton.disabled); |
| |
| inputUrl.value = ''; |
| assertTrue(saveButton.disabled); |
| }); |
| |
| test('cancel closes dialog', () => { |
| assertTrue(dialog.open); |
| cancelButton.click(); |
| assertFalse(dialog.open); |
| }); |
| |
| test('inputs are clear after dialog reuse', () => { |
| inputName.value = 'name'; |
| inputUrl.value = 'url'; |
| cancelButton.click(); |
| mostVisited.$.addShortcut.click(); |
| assertEquals('', inputName.value); |
| assertEquals('', inputUrl.value); |
| }); |
| |
| test('use URL input for title when title empty', async () => { |
| inputUrl.value = 'url'; |
| const addCalled = testProxy.handler.whenCalled('addMostVisitedTile'); |
| saveButton.click(); |
| const [url, title] = await addCalled; |
| assertEquals('url', title); |
| }); |
| |
| test('toast shown on save', async () => { |
| inputUrl.value = 'url'; |
| assertFalse(mostVisited.$.toast.open); |
| const addCalled = testProxy.handler.whenCalled('addMostVisitedTile'); |
| saveButton.click(); |
| await addCalled; |
| assertTrue(mostVisited.$.toast.open); |
| }); |
| |
| test('toast has undo buttons when action successful', async () => { |
| testProxy.handler.setResultFor('addMostVisitedTile', Promise.resolve({ |
| success: true, |
| })); |
| inputUrl.value = 'url'; |
| saveButton.click(); |
| await testProxy.handler.whenCalled('addMostVisitedTile'); |
| await flushTasks(); |
| assertFalse($$(mostVisited, '#undo').hidden); |
| }); |
| |
| test('toast has no undo buttons when action successful', async () => { |
| testProxy.handler.setResultFor('addMostVisitedTile', Promise.resolve({ |
| success: false, |
| })); |
| inputUrl.value = 'url'; |
| saveButton.click(); |
| await testProxy.handler.whenCalled('addMostVisitedTile'); |
| await flushTasks(); |
| assertFalse(!!$$(mostVisited, '#undo')); |
| }); |
| |
| test('save name and URL', async () => { |
| inputName.value = 'name'; |
| inputUrl.value = 'https://url/'; |
| const addCalled = testProxy.handler.whenCalled('addMostVisitedTile'); |
| saveButton.click(); |
| const [{url}, title] = await addCalled; |
| assertEquals('name', title); |
| assertEquals('https://url/', url); |
| }); |
| |
| test('dialog closes on save', () => { |
| inputUrl.value = 'url'; |
| assertTrue(dialog.open); |
| saveButton.click(); |
| assertFalse(dialog.open); |
| }); |
| |
| test('https:// is added if no scheme is used', async () => { |
| inputUrl.value = 'url'; |
| const addCalled = testProxy.handler.whenCalled('addMostVisitedTile'); |
| saveButton.click(); |
| const [{url}, title] = await addCalled; |
| assertEquals('https://url/', url); |
| }); |
| |
| test('http is a valid scheme', async () => { |
| assertTrue(saveButton.disabled); |
| inputUrl.value = 'http://url'; |
| const addCalled = testProxy.handler.whenCalled('addMostVisitedTile'); |
| saveButton.click(); |
| await addCalled; |
| assertFalse(saveButton.disabled); |
| }); |
| |
| test('https is a valid scheme', async () => { |
| inputUrl.value = 'https://url'; |
| const addCalled = testProxy.handler.whenCalled('addMostVisitedTile'); |
| saveButton.click(); |
| await addCalled; |
| }); |
| |
| test('chrome is not a valid scheme', () => { |
| assertTrue(saveButton.disabled); |
| inputUrl.value = 'chrome://url'; |
| assertFalse(inputUrl.invalid); |
| mostVisited.$.dialogInputUrl.dispatchEvent(new Event('blur')); |
| assertTrue(inputUrl.invalid); |
| assertTrue(saveButton.disabled); |
| }); |
| |
| test('invalid cleared when text entered', () => { |
| inputUrl.value = '%'; |
| assertFalse(inputUrl.invalid); |
| mostVisited.$.dialogInputUrl.dispatchEvent(new Event('blur')); |
| assertTrue(inputUrl.invalid); |
| inputUrl.value = ''; |
| assertFalse(inputUrl.invalid); |
| }); |
| }); |
| |
| test('open edit dialog', async () => { |
| await addTiles(2); |
| const {actionMenu, dialog} = mostVisited.$; |
| assertFalse(actionMenu.open); |
| queryTiles()[0].querySelector('#actionMenuButton').click(); |
| assertTrue(actionMenu.open); |
| assertFalse(dialog.open); |
| mostVisited.$.actionMenuEdit.click(); |
| assertFalse(actionMenu.open); |
| assertTrue(dialog.open); |
| }); |
| |
| suite('edit dialog', () => { |
| /** @private {CrActionMenuElement} */ |
| let actionMenu; |
| /** @private {CrIconButtonElement} */ |
| let actionMenuButton; |
| /** @private {CrDialogElement} */ |
| let dialog; |
| /** @private {CrInputElement} */ |
| let inputName; |
| /** @private {CrInputElement} */ |
| let inputUrl; |
| /** @private {CrButtonElement} */ |
| let saveButton; |
| /** @private {CrButtonElement} */ |
| let cancelButton; |
| /** @private {HTMLElement} */ |
| let tile; |
| |
| setup(async () => { |
| actionMenu = mostVisited.$.actionMenu; |
| dialog = mostVisited.$.dialog; |
| inputName = mostVisited.$.dialogInputName; |
| inputUrl = mostVisited.$.dialogInputUrl; |
| saveButton = dialog.querySelector('.action-button'); |
| cancelButton = dialog.querySelector('.cancel-button'); |
| await addTiles(2); |
| tile = queryTiles()[1]; |
| actionMenuButton = tile.querySelector('#actionMenuButton'); |
| actionMenuButton.click(); |
| mostVisited.$.actionMenuEdit.click(); |
| }); |
| |
| test('edit a tile URL', async () => { |
| assertEquals('https://b/', inputUrl.value); |
| const updateCalled = |
| testProxy.handler.whenCalled('updateMostVisitedTile'); |
| inputUrl.value = 'updated-url'; |
| saveButton.click(); |
| const [url, newUrl, newTitle] = await updateCalled; |
| assertEquals('https://updated-url/', newUrl.url); |
| }); |
| |
| test('toast shown when tile editted', async () => { |
| inputUrl.value = 'updated-url'; |
| assertFalse(mostVisited.$.toast.open); |
| saveButton.click(); |
| await testProxy.handler.whenCalled('updateMostVisitedTile'); |
| assertTrue(mostVisited.$.toast.open); |
| }); |
| |
| test('no toast when not editted', async () => { |
| assertFalse(mostVisited.$.toast.open); |
| saveButton.click(); |
| await flushTasks(); |
| assertFalse(mostVisited.$.toast.open); |
| }); |
| |
| test('edit a tile title', async () => { |
| assertEquals('b', inputName.value); |
| const updateCalled = |
| testProxy.handler.whenCalled('updateMostVisitedTile'); |
| inputName.value = 'updated name'; |
| saveButton.click(); |
| const [url, newUrl, newTitle] = await updateCalled; |
| assertEquals('updated name', newTitle); |
| }); |
| |
| test('update not called when name and URL not changed', async () => { |
| // |updateMostVisitedTile| will be called only after either the title or |
| // url has changed. |
| const updateCalled = |
| testProxy.handler.whenCalled('updateMostVisitedTile'); |
| saveButton.click(); |
| |
| // Reopen dialog and edit URL. |
| actionMenuButton.click(); |
| mostVisited.$.actionMenuEdit.click(); |
| inputUrl.value = 'updated-url'; |
| saveButton.click(); |
| |
| const [url, newUrl, newTitle] = await updateCalled; |
| assertEquals('https://updated-url/', newUrl.url); |
| }); |
| }); |
| |
| test('remove with action menu', async () => { |
| const {actionMenu, actionMenuRemove: removeButton} = mostVisited.$; |
| await addTiles(2); |
| const secondTile = queryTiles()[1]; |
| const actionMenuButton = secondTile.querySelector('#actionMenuButton'); |
| |
| assertFalse(actionMenu.open); |
| actionMenuButton.click(); |
| assertTrue(actionMenu.open); |
| const deleteCalled = testProxy.handler.whenCalled('deleteMostVisitedTile'); |
| assertFalse(mostVisited.$.toast.open); |
| removeButton.click(); |
| assertFalse(actionMenu.open); |
| assertEquals('https://b/', (await deleteCalled).url); |
| assertTrue(mostVisited.$.toast.open); |
| // Toast buttons are visible. |
| assertTrue(!!$$(mostVisited, '#undo')); |
| assertTrue(!!$$(mostVisited, '#restore')); |
| }); |
| |
| test('remove query with action menu', async () => { |
| const {actionMenu, actionMenuRemove: removeButton} = mostVisited.$; |
| await addTiles([{ |
| title: 'title', |
| titleDirection: mojoBase.mojom.TextDirection.LEFT_TO_RIGHT, |
| url: {url: 'https://search-url/'}, |
| source: 0, |
| titleSource: 0, |
| isQueryTile: true, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }]); |
| const actionMenuButton = queryTiles()[0].querySelector('#actionMenuButton'); |
| |
| assertFalse(actionMenu.open); |
| actionMenuButton.click(); |
| assertTrue(actionMenu.open); |
| const deleteCalled = testProxy.handler.whenCalled('deleteMostVisitedTile'); |
| assertFalse(mostVisited.$.toast.open); |
| removeButton.click(); |
| assertEquals('https://search-url/', (await deleteCalled).url); |
| assertTrue(mostVisited.$.toast.open); |
| // Toast buttons are visible. |
| assertTrue(!!$$(mostVisited, '#undo')); |
| assertTrue(!!$$(mostVisited, '#restore')); |
| }); |
| |
| test('remove with icon button (customLinksEnabled=false)', async () => { |
| await addTiles(1, /* customLinksEnabled */ false); |
| const removeButton = queryTiles()[0].querySelector('#removeButton'); |
| const deleteCalled = testProxy.handler.whenCalled('deleteMostVisitedTile'); |
| assertFalse(mostVisited.$.toast.open); |
| removeButton.click(); |
| assertEquals('https://a/', (await deleteCalled).url); |
| assertTrue(mostVisited.$.toast.open); |
| // Toast buttons are visible. |
| assertTrue(!!$$(mostVisited, '#undo')); |
| assertTrue(!!$$(mostVisited, '#restore')); |
| }); |
| |
| test('remove query with icon button (customLinksEnabled=false)', async () => { |
| await addTiles( |
| [{ |
| title: 'title', |
| titleDirection: mojoBase.mojom.TextDirection.LEFT_TO_RIGHT, |
| url: {url: 'https://search-url/'}, |
| source: 0, |
| titleSource: 0, |
| isQueryTile: true, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }], |
| /* customLinksEnabled */ false); |
| const removeButton = queryTiles()[0].querySelector('#removeButton'); |
| const deleteCalled = testProxy.handler.whenCalled('deleteMostVisitedTile'); |
| assertFalse(mostVisited.$.toast.open); |
| removeButton.click(); |
| assertEquals('https://search-url/', (await deleteCalled).url); |
| assertTrue(mostVisited.$.toast.open); |
| // Toast buttons are not visible. |
| assertFalse(!!$$(mostVisited, '#undo')); |
| assertFalse(!!$$(mostVisited, '#restore')); |
| }); |
| |
| test('tile url is set to href of <a>', async () => { |
| await addTiles(1); |
| const [tile] = queryTiles(); |
| assertEquals('https://a/', tile.href); |
| }); |
| |
| test('delete first tile', async () => { |
| await addTiles(1); |
| const [tile] = queryTiles(); |
| const deleteCalled = testProxy.handler.whenCalled('deleteMostVisitedTile'); |
| assertFalse(mostVisited.$.toast.open); |
| keydown(tile, 'Delete'); |
| assertEquals('https://a/', (await deleteCalled).url); |
| assertTrue(mostVisited.$.toast.open); |
| }); |
| |
| test('ctrl+z triggers undo and hides toast', async () => { |
| const {toast} = mostVisited.$; |
| assertFalse(toast.open); |
| mostVisited.toast_('linkRemovedMsg', /* showButtons= */ true); |
| await flushTasks(); |
| assertTrue(toast.open); |
| const undoCalled = |
| testProxy.handler.whenCalled('undoMostVisitedTileAction'); |
| mostVisited.dispatchEvent(new KeyboardEvent('keydown', { |
| bubbles: true, |
| ctrlKey: !isMac, |
| key: 'z', |
| metaKey: isMac, |
| })); |
| await undoCalled; |
| assertFalse(toast.open); |
| }); |
| |
| test('ctrl+z does nothing if toast buttons are not showing', async () => { |
| const {toast} = mostVisited.$; |
| assertFalse(toast.open); |
| mostVisited.toast_('linkRemovedMsg', /* showButtons= */ false); |
| await flushTasks(); |
| assertTrue(toast.open); |
| mostVisited.dispatchEvent(new KeyboardEvent('keydown', { |
| bubbles: true, |
| ctrlKey: !isMac, |
| key: 'z', |
| metaKey: isMac, |
| })); |
| assertEquals( |
| 0, testProxy.handler.getCallCount('undoMostVisitedTileAction')); |
| assertTrue(toast.open); |
| }); |
| |
| test('toast restore defaults button', async () => { |
| const wait = testProxy.handler.whenCalled('restoreMostVisitedDefaults'); |
| const {toast} = mostVisited.$; |
| assertFalse(toast.open); |
| mostVisited.toast_('linkRemovedMsg', /* showButtons= */ true); |
| await flushTasks(); |
| assertTrue(toast.open); |
| toast.querySelector('#restore').click(); |
| await wait; |
| assertFalse(toast.open); |
| }); |
| |
| test('toast undo button', async () => { |
| const wait = testProxy.handler.whenCalled('undoMostVisitedTileAction'); |
| const {toast} = mostVisited.$; |
| assertFalse(toast.open); |
| mostVisited.toast_('linkRemovedMsg', /* showButtons= */ true); |
| await flushTasks(); |
| assertTrue(toast.open); |
| toast.querySelector('#undo').click(); |
| await wait; |
| assertFalse(toast.open); |
| }); |
| |
| test('drag first tile to second position', async () => { |
| await addTiles(2); |
| const [first, second] = queryTiles(); |
| assertEquals('https://a/', first.href); |
| assertTrue(first.draggable); |
| assertEquals('https://b/', second.href); |
| assertTrue(second.draggable); |
| const firstRect = first.getBoundingClientRect(); |
| const secondRect = second.getBoundingClientRect(); |
| first.dispatchEvent(new DragEvent('dragstart', { |
| clientX: firstRect.x + firstRect.width / 2, |
| clientY: firstRect.y + firstRect.height / 2, |
| })); |
| await flushTasks(); |
| const reorderCalled = |
| testProxy.handler.whenCalled('reorderMostVisitedTile'); |
| document.dispatchEvent(new DragEvent('dragend', { |
| clientX: secondRect.x + 1, |
| clientY: secondRect.y + 1, |
| })); |
| const [url, newPos] = await reorderCalled; |
| assertEquals('https://a/', url.url); |
| assertEquals(1, newPos); |
| const [newFirst, newSecond] = queryTiles(); |
| assertEquals('https://b/', newFirst.href); |
| assertEquals('https://a/', newSecond.href); |
| }); |
| |
| test('drag second tile to first position', async () => { |
| await addTiles(2); |
| const [first, second] = queryTiles(); |
| assertEquals('https://a/', first.href); |
| assertTrue(first.draggable); |
| assertEquals('https://b/', second.href); |
| assertTrue(second.draggable); |
| const firstRect = first.getBoundingClientRect(); |
| const secondRect = second.getBoundingClientRect(); |
| second.dispatchEvent(new DragEvent('dragstart', { |
| clientX: secondRect.x + secondRect.width / 2, |
| clientY: secondRect.y + secondRect.height / 2, |
| })); |
| await flushTasks(); |
| const reorderCalled = |
| testProxy.handler.whenCalled('reorderMostVisitedTile'); |
| document.dispatchEvent(new DragEvent('dragend', { |
| clientX: firstRect.x + 1, |
| clientY: firstRect.y + 1, |
| })); |
| const [url, newPos] = await reorderCalled; |
| assertEquals('https://b/', url.url); |
| assertEquals(0, newPos); |
| const [newFirst, newSecond] = queryTiles(); |
| assertEquals('https://b/', newFirst.href); |
| assertEquals('https://a/', newSecond.href); |
| }); |
| |
| test('most visited tiles are not draggable', async () => { |
| await addTiles(2, /* customLinksEnabled= */ false); |
| const [first, second] = queryTiles(); |
| assertEquals('https://a/', first.href); |
| assertFalse(first.draggable); |
| assertEquals('https://b/', second.href); |
| assertFalse(second.draggable); |
| |
| const firstRect = first.getBoundingClientRect(); |
| const secondRect = second.getBoundingClientRect(); |
| first.dispatchEvent(new DragEvent('dragstart', { |
| clientX: firstRect.x + firstRect.width / 2, |
| clientY: firstRect.y + firstRect.height / 2, |
| })); |
| document.dispatchEvent(new DragEvent('dragend', { |
| clientX: secondRect.x + 1, |
| clientY: secondRect.y + 1, |
| })); |
| await flushTasks(); |
| assertEquals(0, testProxy.handler.getCallCount('reorderMostVisitedTile')); |
| const [newFirst, newSecond] = queryTiles(); |
| assertEquals('https://a/', newFirst.href); |
| assertEquals('https://b/', newSecond.href); |
| }); |
| |
| test('RIGHT_TO_LEFT tile title text direction', async () => { |
| await addTiles([{ |
| title: 'title', |
| titleDirection: mojoBase.mojom.TextDirection.RIGHT_TO_LEFT, |
| url: {url: 'https://url/'}, |
| source: 0, |
| titleSource: 0, |
| isQueryTile: false, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }]); |
| const [tile] = queryTiles(); |
| const titleElement = tile.querySelector('.tile-title'); |
| assertEquals('rtl', window.getComputedStyle(titleElement).direction); |
| }); |
| |
| test('LEFT_TO_RIGHT tile title text direction', async () => { |
| await addTiles([{ |
| title: 'title', |
| titleDirection: mojoBase.mojom.TextDirection.LEFT_TO_RIGHT, |
| url: {url: 'https://url/'}, |
| source: 0, |
| titleSource: 0, |
| isQueryTile: false, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }]); |
| const [tile] = queryTiles(); |
| const titleElement = tile.querySelector('.tile-title'); |
| assertEquals('ltr', window.getComputedStyle(titleElement).direction); |
| }); |
| |
| test('setting color styles tile color', () => { |
| // Act. |
| mostVisited.style.setProperty('--ntp-theme-text-color', 'blue'); |
| mostVisited.style.setProperty( |
| '--ntp-theme-shortcut-background-color', 'red'); |
| |
| // Assert. |
| queryAll('.tile-title').forEach(tile => { |
| assertStyle(tile, 'color', 'rgb(0, 0, 255)'); |
| }); |
| queryAll('.tile-icon').forEach(tile => { |
| assertStyle(tile, 'background-color', 'rgb(255, 0, 0)'); |
| }); |
| }); |
| |
| test('add shortcut white', () => { |
| assertStyle( |
| mostVisited.$.addShortcutIcon, 'background-color', 'rgb(32, 33, 36)'); |
| mostVisited.toggleAttribute('use-white-add-icon', true); |
| assertStyle( |
| mostVisited.$.addShortcutIcon, 'background-color', |
| 'rgb(255, 255, 255)'); |
| }); |
| |
| test('add title pill', () => { |
| mostVisited.style.setProperty('--ntp-theme-text-shadow', '1px 2px'); |
| queryAll('.tile-title').forEach(tile => { |
| assertStyle(tile, 'background-color', 'rgba(0, 0, 0, 0)'); |
| }); |
| queryAll('.tile-title span').forEach(tile => { |
| assertNotStyle(tile, 'text-shadow', 'none'); |
| }); |
| mostVisited.toggleAttribute('use-title-pill', true); |
| queryAll('.tile-title').forEach(tile => { |
| assertStyle(tile, 'background-color', 'rgb(255, 255, 255)'); |
| }); |
| queryAll('.tile-title span').forEach(tile => { |
| assertStyle(tile, 'text-shadow', 'none'); |
| assertStyle(tile, 'color', 'rgb(60, 64, 67)'); |
| }); |
| }); |
| |
| test('rendering tiles logs event', async () => { |
| // Arrange. |
| testProxy.setResultFor('now', 123); |
| |
| // Act. |
| await addTiles(2); |
| |
| // Assert. |
| const [tiles, time] = |
| await testProxy.handler.whenCalled('onMostVisitedTilesRendered'); |
| assertEquals(time, 123); |
| assertEquals(tiles.length, 2); |
| assertDeepEquals(tiles[0], { |
| title: 'a', |
| titleDirection: mojoBase.mojom.TextDirection.LEFT_TO_RIGHT, |
| url: {url: 'https://a/'}, |
| source: 0, |
| titleSource: 0, |
| isQueryTile: false, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }); |
| assertDeepEquals(tiles[1], { |
| title: 'b', |
| titleDirection: mojoBase.mojom.TextDirection.LEFT_TO_RIGHT, |
| url: {url: 'https://b/'}, |
| source: 1, |
| titleSource: 1, |
| isQueryTile: false, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }); |
| }); |
| |
| test('clicking tile logs event', async () => { |
| // Arrange. |
| await addTiles(1); |
| |
| // Act. |
| const tileLink = queryTiles()[0]; |
| // Prevent triggering a navigation, which would break the test. |
| tileLink.href = '#'; |
| tileLink.click(); |
| |
| // Assert. |
| const [tile, index] = |
| await testProxy.handler.whenCalled('onMostVisitedTileNavigation'); |
| assertEquals(index, 0); |
| assertDeepEquals(tile, { |
| title: 'a', |
| titleDirection: mojoBase.mojom.TextDirection.LEFT_TO_RIGHT, |
| url: {url: 'https://a/'}, |
| source: 0, |
| titleSource: 0, |
| isQueryTile: false, |
| dataGenerationTime: {internalValue: BigInt(0)}, |
| }); |
| }); |
| |
| test('making tab visible refreshes most visited tiles', () => { |
| // Arrange. |
| testProxy.handler.resetResolver('updateMostVisitedInfo'); |
| |
| // Act. |
| document.dispatchEvent(new Event('visibilitychange')); |
| |
| // Assert. |
| assertEquals(1, testProxy.handler.getCallCount('updateMostVisitedInfo')); |
| }); |
| }); |