| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Platform from '../../../core/platform/platform.js'; |
| import { |
| assertScreenshot, |
| dispatchClickEvent, |
| dispatchKeyDownEvent, |
| raf, |
| renderElementIntoDOM |
| } from '../../../testing/DOMHelpers.js'; |
| import {setupLocaleHooks} from '../../../testing/LocaleHelpers.js'; |
| import * as RenderCoordinator from '../render_coordinator/render_coordinator.js'; |
| |
| import * as Dialogs from './dialogs.js'; |
| |
| describe('Dialog', () => { |
| describe('positioning', () => { |
| let dialog: Dialogs.Dialog.Dialog; |
| let container: HTMLDivElement; |
| let host: HTMLDivElement; |
| beforeEach(() => { |
| container = document.createElement('div'); |
| container.style.width = '500px'; |
| container.style.height = '500px'; |
| container.style.display = 'flex'; |
| container.style.alignItems = 'center'; |
| container.style.justifyContent = 'center'; |
| |
| dialog = new Dialogs.Dialog.Dialog(); |
| |
| host = document.createElement('div'); |
| host.textContent = 'Hover me'; |
| host.style.width = '100px'; |
| host.style.height = '100px'; |
| |
| dialog.position = Dialogs.Dialog.DialogVerticalPosition.TOP; |
| dialog.origin = host; |
| }); |
| it('places the Dialog hit area correctly', async () => { |
| host.addEventListener('click', () => dialog.setDialogVisible(true)); |
| dialog.addEventListener('clickoutsidedialog', () => dialog.setDialogVisible(false)); |
| |
| container.appendChild(host); |
| container.appendChild(dialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| |
| const hostBounds = host.getBoundingClientRect(); |
| const hitAreaBounds = dialog.getHitArea(); |
| |
| // Make sure the hit area contains the host fully. |
| assert.isAtMost(hitAreaBounds.top, hostBounds.top); |
| assert.isAtLeast(hitAreaBounds.bottom, hostBounds.top + hostBounds.height); |
| |
| assert.isAtMost(hitAreaBounds.left, hostBounds.left); |
| assert.isAtLeast(hitAreaBounds.right, hostBounds.left + hostBounds.width); |
| }); |
| it('sets the automatic horizontal alignment correctly', async () => { |
| // Create the container for the dialog and its origin (host). |
| // This container will be set as the dialog's "window". |
| // With the host in the left border of the window, the Dialog |
| // should deploy from left to right (left horizontal alignment). |
| container.style.display = 'block'; |
| host.style.position = 'relative'; |
| host.style.left = '0'; |
| |
| const content = document.createElement('div'); |
| content.classList.add('dialog-content'); |
| content.style.padding = '0 1em'; |
| content.innerHTML = 'Hi'; |
| |
| dialog.horizontalAlignment = Dialogs.Dialog.DialogHorizontalAlignment.AUTO; |
| dialog.origin = host; |
| |
| dialog.setBoundingElementForTesting(container); |
| |
| host.addEventListener('click', () => dialog.setDialogVisible(true)); |
| dialog.addEventListener('clickoutsidedialog', () => dialog.setDialogVisible(false)); |
| |
| dialog.appendChild(content); |
| container.appendChild(host); |
| container.appendChild(dialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| |
| // Test the dialog is deployed left to right, since this way there is more space. |
| assert.strictEqual(dialog.bestHorizontalAlignment, Dialogs.Dialog.DialogHorizontalAlignment.LEFT); |
| |
| // Close the dialog |
| dispatchKeyDownEvent(dialog, {key: Platform.KeyboardUtilities.ESCAPE_KEY, bubbles: true, composed: true}); |
| await RenderCoordinator.done(); |
| |
| // With the host in the right border of the window, the Dialog |
| // should deploy from right to left (left horizontal alignment). |
| host.style.position = 'relative'; |
| host.style.left = '450px'; |
| await RenderCoordinator.done(); |
| |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| |
| // Test the dialog is deployed right to left. |
| assert.strictEqual(dialog.bestHorizontalAlignment, Dialogs.Dialog.DialogHorizontalAlignment.RIGHT); |
| }); |
| |
| it('sets the automatic vertical position correctly when it fits on top', async () => { |
| // Create the container for the dialog and its origin (host), aligning |
| // items to the bottom of the container. This container will be set as the |
| // dialog's "window". |
| // By default the dialog is placed at the bottom of its origin, but doing |
| // so means it wouldn't fit in its window. Because if shown on top would |
| // fit in its window it should be automatically positioned there. |
| container.style.width = '150px'; |
| container.style.height = '300px'; |
| container.style.display = 'flex'; |
| container.style.alignItems = 'end'; |
| container.style.justifyContent = 'center'; |
| |
| // The dialogs content dimensions exceed the viewport's |
| const content = document.createElement('div'); |
| content.classList.add('dialog-content'); |
| content.style.padding = '0 1em'; |
| content.innerHTML = 'Hello, World<br/> I am <br/> a Dialog!'; |
| |
| dialog.position = Dialogs.Dialog.DialogVerticalPosition.AUTO; |
| dialog.origin = host; |
| dialog.setBoundingElementForTesting(container); |
| |
| host.addEventListener('click', () => dialog.setDialogVisible(true)); |
| dialog.addEventListener('clickoutsidedialog', () => dialog.setDialogVisible(false)); |
| |
| dialog.appendChild(content); |
| container.appendChild(host); |
| container.appendChild(dialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| |
| // Test the capped dimensions |
| assert.strictEqual(dialog.bestVerticalPosition, Dialogs.Dialog.DialogVerticalPosition.TOP); |
| }); |
| |
| it('sets the automatic vertical position correctly when it does not fit on top', async () => { |
| // Create the container for the dialog and its origin (host), aligning |
| // items to the bottom of the container. This container will be set as the |
| // dialog's "window". |
| // Because the dialog's full height cannot be fully fit at the |
| // bottom or at the top it is positioned at the bottom and the |
| // overflow made visible by scrolling. |
| container.style.width = '150px'; |
| container.style.height = '80px'; |
| container.style.display = 'flex'; |
| container.style.alignItems = 'end'; |
| container.style.justifyContent = 'center'; |
| |
| // The dialogs content dimensions exceed the viewport's |
| const content = document.createElement('div'); |
| content.classList.add('dialog-content'); |
| content.style.padding = '0 1em'; |
| content.innerHTML = 'Hello, World<br/> I am <br/> a Dialog!'; |
| |
| dialog.position = Dialogs.Dialog.DialogVerticalPosition.AUTO; |
| dialog.origin = host; |
| dialog.setBoundingElementForTesting(container); |
| |
| host.addEventListener('click', () => dialog.setDialogVisible(true)); |
| dialog.addEventListener('clickoutsidedialog', () => dialog.setDialogVisible(false)); |
| |
| dialog.appendChild(content); |
| container.appendChild(host); |
| container.appendChild(dialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| |
| // Test the capped dimensions |
| assert.strictEqual(dialog.bestVerticalPosition, Dialogs.Dialog.DialogVerticalPosition.BOTTOM); |
| }); |
| it('sets the max width and height correctly when the dialog\'s content dimensions exceed the viewport and the dialog is displayed as a modal', |
| async () => { |
| const devtoolsDialog = new Dialogs.Dialog.Dialog(); |
| devtoolsDialog.setBoundingElementForTesting(container); |
| const WINDOW_WIDTH = 300; |
| // This container will be set as the dialog's "window", or the representation |
| // of DevTools bounding element. |
| container.style.width = `${WINDOW_WIDTH}px`; |
| container.style.height = `${WINDOW_WIDTH}px`; |
| const host = document.createElement('div'); |
| host.textContent = 'Hover me'; |
| host.style.width = '100px'; |
| |
| const content = document.createElement('div'); |
| content.classList.add('dialog-content'); |
| content.style.width = '400px'; |
| content.style.height = '400px'; |
| content.innerHTML = 'Hello, World<br/> I am <br/> a Dialog!'; |
| |
| devtoolsDialog.origin = Dialogs.Dialog.MODAL; |
| |
| host.addEventListener('click', () => devtoolsDialog.setDialogVisible(true)); |
| devtoolsDialog.addEventListener('clickoutsidedialog', () => devtoolsDialog.setDialogVisible(false)); |
| |
| container.appendChild(host); |
| container.appendChild(devtoolsDialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| devtoolsDialog.appendChild(content); |
| |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| const dialog = devtoolsDialog.shadowRoot?.querySelector('dialog'); |
| assert.exists(dialog, 'Dialog not found'); |
| assert.strictEqual( |
| dialog.clientWidth, |
| WINDOW_WIDTH - Dialogs.Dialog.DIALOG_PADDING_FROM_WINDOW + 2 * Dialogs.Dialog.DIALOG_SIDE_PADDING); |
| assert.strictEqual( |
| dialog.clientHeight, |
| WINDOW_WIDTH - Dialogs.Dialog.DIALOG_PADDING_FROM_WINDOW + 2 * Dialogs.Dialog.DIALOG_VERTICAL_PADDING); |
| }); |
| describe('with an anchor and possible overflow', () => { |
| const CONTAINER_WIDTH = 500; |
| const CONTAINER_HEIGHT = 500; |
| const HOST_OFFSET = 100; |
| const HOST_HEIGHT = 100; |
| const devtoolsDialog = new Dialogs.Dialog.Dialog(); |
| let host: HTMLElement; |
| let container: HTMLElement; |
| beforeEach(async () => { |
| // This container will be set as the dialog's "window", or the representation |
| // of DevTools bounding element. |
| container = document.createElement('div'); |
| container.style.width = `${CONTAINER_WIDTH}px`; |
| container.style.height = `${CONTAINER_HEIGHT}px`; |
| container.style.position = 'fixed'; |
| container.style.top = '0'; |
| container.style.left = '0'; |
| |
| host = document.createElement('div'); |
| host.textContent = 'Click me'; |
| host.style.width = `${HOST_HEIGHT}px`; |
| host.style.height = `${HOST_HEIGHT}px`; |
| host.style.position = 'absolute'; |
| host.style.top = `${HOST_OFFSET}px`; |
| host.style.left = `${HOST_OFFSET}px`; |
| |
| // The dialogs content dimensions exceed the container's |
| const content = document.createElement('div'); |
| content.classList.add('dialog-content'); |
| content.style.width = '600px'; |
| content.style.height = '600px'; |
| content.innerHTML = 'Hello, World<br/> I am <br/> a Dialog!'; |
| |
| devtoolsDialog.origin = host; |
| devtoolsDialog.horizontalAlignment = Dialogs.Dialog.DialogHorizontalAlignment.CENTER; |
| |
| host.addEventListener('click', () => devtoolsDialog.setDialogVisible(true)); |
| devtoolsDialog.addEventListener('clickoutsidedialog', () => devtoolsDialog.setDialogVisible(false)); |
| |
| devtoolsDialog.setBoundingElementForTesting(container); |
| dialog.setBoundingElementForTesting(container); |
| |
| container.appendChild(host); |
| container.appendChild(devtoolsDialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| devtoolsDialog.appendChild(content); |
| }); |
| it('sets the max width and height correctly when the dialog\'s content dimensions exceed the viewport and the dialog is anchored to the left', |
| async () => { |
| devtoolsDialog.horizontalAlignment = Dialogs.Dialog.DialogHorizontalAlignment.LEFT; |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| const dialog = devtoolsDialog.shadowRoot?.querySelector('dialog'); |
| assert.exists(dialog, 'Dialog not found'); |
| |
| // Test the capped dimensions |
| const {left: dialogLeft, width: dialogWidth} = dialog.getBoundingClientRect(); |
| const dialogLeftBorderLimitPosition = dialogWidth + dialogLeft + Dialogs.Dialog.DIALOG_PADDING_FROM_WINDOW - |
| 2 * Dialogs.Dialog.DIALOG_SIDE_PADDING; |
| assert.strictEqual(dialogLeftBorderLimitPosition, CONTAINER_WIDTH); |
| assert.strictEqual( |
| dialog.clientHeight, |
| CONTAINER_HEIGHT - Dialogs.Dialog.DIALOG_PADDING_FROM_WINDOW - HOST_HEIGHT - HOST_OFFSET + |
| 2 * Dialogs.Dialog.DIALOG_VERTICAL_PADDING); |
| }); |
| it('sets the max width and height correctly when the dialog\'s content dimensions exceed the viewport and the dialog is anchored to the right', |
| async () => { |
| devtoolsDialog.horizontalAlignment = Dialogs.Dialog.DialogHorizontalAlignment.RIGHT; |
| await RenderCoordinator.done(); |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| const dialog = devtoolsDialog.shadowRoot?.querySelector('dialog'); |
| assert.exists(dialog, 'Dialog not found'); |
| |
| // Test the capped dimensions |
| const dialogRight = host.getBoundingClientRect().right; |
| const containerLeft = container.getBoundingClientRect().left; |
| assert.strictEqual( |
| dialog.clientWidth, |
| (dialogRight - containerLeft) - Dialogs.Dialog.DIALOG_PADDING_FROM_WINDOW + |
| 2 * Dialogs.Dialog.DIALOG_SIDE_PADDING); |
| }); |
| it('sets the dialog\'s horizontal position correctly to prevent overlap with DevTools when alinged to the left.', |
| async () => { |
| devtoolsDialog.horizontalAlignment = Dialogs.Dialog.DialogHorizontalAlignment.LEFT; |
| host.style.left = '-10px'; |
| |
| await RenderCoordinator.done(); |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| const dialog = devtoolsDialog.shadowRoot?.querySelector('dialog'); |
| assert.exists(dialog, 'Dialog not found'); |
| // Test the capped dimensions |
| const dialogLeft = dialog.getBoundingClientRect().left; |
| const containerLeft = container.getBoundingClientRect().left; |
| assert.isAtLeast(dialogLeft, containerLeft); |
| }); |
| it('sets the dialog\'s horizontal position correctly to prevent overlap with DevTools when alinged to the right.', |
| async () => { |
| devtoolsDialog.horizontalAlignment = Dialogs.Dialog.DialogHorizontalAlignment.RIGHT; |
| const containerWidth = container.clientWidth; |
| host.style.left = `${containerWidth + 10}px`; |
| |
| await RenderCoordinator.done(); |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| const dialog = devtoolsDialog.shadowRoot?.querySelector('dialog'); |
| assert.exists(dialog, 'Dialog not found'); |
| // Test the capped dimensions |
| const dialogRight = dialog.getBoundingClientRect().right; |
| const dialogRightEdgePosition = |
| dialogRight - 2 * Dialogs.Dialog.DIALOG_SIDE_PADDING - Dialogs.Dialog.DIALOG_PADDING_FROM_WINDOW / 2; |
| assert.isAtMost(dialogRightEdgePosition, containerWidth); |
| }); |
| it('sets the dialog\'s horizontal position correctly to prevent overlapping with DevTools on the right when alinged to the center.', |
| async () => { |
| const containerWidth = container.clientWidth; |
| host.style.left = `${containerWidth + 260}px`; |
| |
| await RenderCoordinator.done(); |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| const dialog = devtoolsDialog.shadowRoot?.querySelector('dialog'); |
| assert.exists(dialog, 'Dialog not found'); |
| // Test the capped dimensions |
| const dialogRight = dialog.getBoundingClientRect().right; |
| const dialogRightEdgePosition = |
| dialogRight - 2 * Dialogs.Dialog.DIALOG_SIDE_PADDING - Dialogs.Dialog.DIALOG_PADDING_FROM_WINDOW / 2; |
| |
| assert.isAtMost(dialogRightEdgePosition, containerWidth); |
| }); |
| it('sets the dialog\'s horizontal position correctly to prevent overlapping with DevTools on the left when alinged to the center.', |
| async () => { |
| host.style.left = '-260px'; |
| |
| await RenderCoordinator.done(); |
| // Open the dialog and check its position. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| const dialog = devtoolsDialog.shadowRoot?.querySelector('dialog'); |
| assert.exists(dialog, 'Dialog not found'); |
| // Test the capped dimensions |
| const containerLeft = container.getBoundingClientRect().left; |
| const dialogLeft = dialog.getBoundingClientRect().left; |
| |
| assert.isAtLeast(dialogLeft, containerLeft); |
| }); |
| }); |
| |
| // Fails on Windows only after the window-size was increased. |
| it.skip( |
| '[crbug.com/420924642]: updates the dialog client rect automatically when its dimensions change', |
| async function() { |
| host.addEventListener('click', () => dialog.setDialogVisible(true)); |
| const dialogContent = document.createElement('div'); |
| dialogContent.style.display = 'block'; |
| dialogContent.style.minWidth = '10px'; |
| dialogContent.style.minHeight = '10px'; |
| dialogContent.style.fontSize = '10px'; |
| dialogContent.innerText = 'Hello'; |
| |
| dialog.append(dialogContent); |
| container.appendChild(host); |
| container.appendChild(dialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| |
| const initialWidth = dialog.getDialogBounds().width; |
| const initialHeight = dialog.getDialogBounds().height; |
| |
| // Increase the font size to increase the dialog's dimensions |
| dialogContent.style.fontSize = '1000px'; |
| |
| // Wait for the resize handling to take effect. |
| await new Promise(res => setTimeout(res, 200)); |
| |
| const finalWidth = dialog.getDialogBounds().width; |
| const finalHeight = dialog.getDialogBounds().height; |
| |
| assert.isAbove(finalWidth, initialWidth); |
| assert.isAbove(finalHeight, initialHeight); |
| }); |
| }); |
| |
| describe('closing the dialog with the ESC key', () => { |
| let devtoolsDialog: Dialogs.Dialog.Dialog; |
| let container: HTMLElement; |
| beforeEach(async () => { |
| devtoolsDialog = new Dialogs.Dialog.Dialog(); |
| container = document.createElement('div'); |
| |
| const host = document.createElement('div'); |
| |
| const content = document.createElement('div'); |
| content.innerHTML = 'Hello, World<br/> I am <br/> a Dialog!'; |
| |
| devtoolsDialog.origin = host; |
| |
| host.addEventListener('click', () => devtoolsDialog.setDialogVisible(true)); |
| devtoolsDialog.addEventListener('clickoutsidedialog', () => devtoolsDialog.setDialogVisible(false)); |
| |
| container.appendChild(host); |
| container.appendChild(devtoolsDialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| devtoolsDialog.appendChild(content); |
| |
| // Open the dialog. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| }); |
| |
| it('closes the dialog by default when the ESC key is pressed', async () => { |
| let dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.exists(dialog, 'Dialog not found'); |
| |
| dispatchKeyDownEvent(dialog, {key: Platform.KeyboardUtilities.ESCAPE_KEY, bubbles: true, composed: true}); |
| await RenderCoordinator.done(); |
| dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.notExists(dialog, 'Dialog did not close'); |
| }); |
| |
| it('closes the dialog by default when the ESC key is pressed from document.body', async () => { |
| let dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.exists(dialog, 'Dialog not found'); |
| dispatchKeyDownEvent(document.body, {key: Platform.KeyboardUtilities.ESCAPE_KEY, bubbles: true, composed: true}); |
| await RenderCoordinator.done(); |
| dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.notExists(dialog, 'Dialog did not close'); |
| }); |
| |
| it('closes the dialog by default when the ESC key is pressed anywhere within the devtools bounding element', |
| async () => { |
| let dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.exists(dialog, 'Dialog not found'); |
| dispatchKeyDownEvent(container, {key: Platform.KeyboardUtilities.ESCAPE_KEY, bubbles: true, composed: true}); |
| await RenderCoordinator.done(); |
| dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.notExists(dialog, 'Dialog did not close'); |
| }); |
| |
| it('does not close the dialog when the ESC key is pressed if the closeOnESC prop is set to false', async () => { |
| let dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| devtoolsDialog.closeOnESC = false; |
| assert.exists(dialog, 'Dialog not found'); |
| |
| dispatchKeyDownEvent(dialog, {key: Platform.KeyboardUtilities.ESCAPE_KEY}); |
| await RenderCoordinator.done(); |
| dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.exists(dialog, 'Dialog was closed'); |
| }); |
| }); |
| |
| describe('rendering', () => { |
| setupLocaleHooks(); |
| it('do not render dialog header line if title is empty and there is no close button', async () => { |
| const dialog = new Dialogs.Dialog.Dialog(); |
| dialog.closeButton = false; |
| dialog.dialogTitle = ''; |
| renderElementIntoDOM(dialog); |
| await RenderCoordinator.done(); |
| |
| assert.isNotNull(dialog.shadowRoot); |
| const dialogHeader = dialog.shadowRoot.querySelector('.dialog-header'); |
| assert.exists(dialogHeader); |
| assert.isEmpty(dialogHeader.children); |
| }); |
| |
| it('should render a close button in the dialog if closeButton is true', async () => { |
| const dialog = new Dialogs.Dialog.Dialog(); |
| dialog.closeButton = true; |
| renderElementIntoDOM(dialog); |
| await RenderCoordinator.done(); |
| |
| assert.isNotNull(dialog.shadowRoot); |
| const dialogHeader = dialog.shadowRoot.querySelector('.dialog-header'); |
| assert.exists(dialogHeader); |
| const closeButton = dialogHeader.querySelector('devtools-button'); |
| assert.exists(closeButton); |
| }); |
| |
| it('should render dialog title if it is not empty', async () => { |
| const dialogTitle = 'Button dialog example'; |
| const dialog = new Dialogs.Dialog.Dialog(); |
| dialog.dialogTitle = dialogTitle; |
| renderElementIntoDOM(dialog); |
| await RenderCoordinator.done(); |
| |
| assert.isNotNull(dialog.shadowRoot); |
| const dialogHeader = dialog.shadowRoot.querySelector('.dialog-header'); |
| assert.exists(dialogHeader); |
| const dialogTitleElement = dialogHeader.querySelector('.dialog-header-text'); |
| assert.exists(dialogTitleElement); |
| assert.strictEqual(dialogTitleElement.textContent, dialogTitle); |
| }); |
| }); |
| }); |
| |
| describe('clo lick', () => { |
| let devtoolsDialog: Dialogs.Dialog.Dialog; |
| beforeEach(async () => { |
| devtoolsDialog = new Dialogs.Dialog.Dialog(); |
| const container = document.createElement('div'); |
| |
| const host = document.createElement('div'); |
| |
| const content = document.createElement('div'); |
| content.innerHTML = 'Hello, World<br/> I am <br/> a Dialog!'; |
| |
| devtoolsDialog.origin = Dialogs.Dialog.MODAL; |
| |
| host.addEventListener('click', () => devtoolsDialog.setDialogVisible(true)); |
| devtoolsDialog.addEventListener('clickoutsidedialog', () => devtoolsDialog.setDialogVisible(false)); |
| |
| container.appendChild(host); |
| container.appendChild(devtoolsDialog); |
| renderElementIntoDOM(container); |
| await RenderCoordinator.done(); |
| devtoolsDialog.appendChild(content); |
| |
| // Open the dialog. |
| dispatchClickEvent(host); |
| await RenderCoordinator.done(); |
| }); |
| |
| it('Only closes the dialog if the click falls outside its content', async () => { |
| let dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.exists(dialog, 'Dialog not found'); |
| const {x, width, bottom} = dialog.getBoundingClientRect(); |
| |
| // Click just inside must not close the dialog. |
| dispatchClickEvent(dialog, {clientX: x + width / 2, clientY: bottom - 1}); |
| await RenderCoordinator.done(); |
| dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.exists(dialog, 'Dialog closed when it should not'); |
| |
| dispatchClickEvent(dialog, {clientX: x + width / 2, clientY: bottom + 1}); |
| await RenderCoordinator.done(); |
| |
| // Click just outside must close the dialog. |
| dialog = devtoolsDialog.shadowRoot?.querySelector('dialog[open]'); |
| assert.notExists(dialog, 'Dialog did not close'); |
| }); |
| }); |
| |
| describe('visual appearance', () => { |
| setupLocaleHooks(); |
| /** FIXME: clean up and modularize these test **/ |
| async function renderDialogs() { |
| const verticalPositions = [Dialogs.Dialog.DialogVerticalPosition.TOP, Dialogs.Dialog.DialogVerticalPosition.BOTTOM]; |
| const horizontalAlignments = [ |
| Dialogs.Dialog.DialogHorizontalAlignment.AUTO, |
| Dialogs.Dialog.DialogHorizontalAlignment.LEFT, |
| Dialogs.Dialog.DialogHorizontalAlignment.CENTER, |
| Dialogs.Dialog.DialogHorizontalAlignment.RIGHT, |
| Dialogs.Dialog.DialogHorizontalAlignment.AUTO, |
| ]; |
| |
| const root = document.createElement('div'); |
| root.id = 'root'; |
| const style = document.createElement('style'); |
| |
| style.innerHTML = `#root { |
| height: 100%; |
| display: flex; |
| justify-content: center; |
| align-items: stretch; |
| flex-direction: column; |
| } |
| |
| .dialog-host { |
| border: 1px solid black; |
| padding: 5px; |
| border-radius: 3px; |
| width: 70px; |
| } |
| |
| .dialog-host-narrow { |
| border: 1px solid black; |
| width: 10px; |
| } |
| |
| .row { |
| display: flex; |
| justify-content: space-between; |
| margin: 2em 0; |
| } |
| .container { |
| width: 150px; |
| height: 50px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| }`; |
| root.append(style); |
| |
| let i = 0; |
| for (const verticalPosition of verticalPositions) { |
| const row = document.createElement('div'); |
| row.classList.add('row'); |
| root.appendChild(row); |
| for (const horizontalAlignment of horizontalAlignments) { |
| const dialog = new Dialogs.Dialog.Dialog(); |
| |
| const container = document.createElement('div'); |
| container.classList.add('container'); |
| container.id = `container-${i}`; |
| |
| const host = document.createElement('div'); |
| host.classList.add('dialog-host'); |
| host.id = `host-${i}`; |
| host.textContent = 'Hover me'; |
| |
| container.appendChild(host); |
| row.appendChild(container); |
| |
| dialog.position = verticalPosition; |
| dialog.horizontalAlignment = horizontalAlignment; |
| dialog.origin = host; |
| dialog.id = `dialog-${i}`; |
| |
| host.addEventListener('mouseover', () => { |
| void dialog.setDialogVisible(true); |
| }); |
| dialog.addEventListener('clickoutsidedialog', () => { |
| void dialog.setDialogVisible(false); |
| }); |
| |
| const div = document.createElement('div'); |
| div.classList.add('dialog-content'); |
| div.style.padding = '0 1em'; |
| div.innerHTML = |
| `Hello, World<br/>Vertical position: ${verticalPosition}<br/>Horizontal alignment: ${horizontalAlignment}`; |
| dialog.appendChild(div); |
| root.appendChild(dialog); |
| i++; |
| } |
| } |
| |
| for (const verticalPosition of verticalPositions) { |
| const row = document.createElement('div'); |
| row.classList.add('row'); |
| root.appendChild(row); |
| for (const horizontalAlignment of horizontalAlignments) { |
| const dialog = new Dialogs.Dialog.Dialog(); |
| |
| const container = document.createElement('div'); |
| |
| container.classList.add('container'); |
| container.id = `container-${i}`; |
| |
| const host = document.createElement('div'); |
| host.classList.add('dialog-host-narrow'); |
| host.id = `host-${i}`; |
| host.textContent = 'H'; |
| |
| container.appendChild(host); |
| row.appendChild(container); |
| |
| dialog.position = verticalPosition; |
| dialog.horizontalAlignment = horizontalAlignment; |
| dialog.origin = host; |
| dialog.id = `dialog-${i}`; |
| |
| host.addEventListener('mouseover', () => { |
| void dialog.setDialogVisible(true); |
| }); |
| dialog.addEventListener('clickoutsidedialog', () => { |
| void dialog.setDialogVisible(false); |
| }); |
| |
| const div = document.createElement('div'); |
| div.classList.add('dialog-content'); |
| div.style.padding = '0 1em'; |
| div.innerHTML = `Hello, World<br/>Show connector: true<br/>Vertical position: ${ |
| verticalPosition}<br/>Horizontal alignment: ${horizontalAlignment}`; |
| dialog.appendChild(div); |
| root.appendChild(dialog); |
| i++; |
| } |
| } |
| |
| renderDifferentModeExample(); |
| |
| function renderDifferentModeExample() { |
| const row = document.createElement('div'); |
| row.classList.add('row'); |
| root.appendChild(row); |
| renderDialogWithTitle(); |
| renderDialogWithTitleAndCloseButton(); |
| renderDialogWithoutTitleOrCloseButton(); |
| |
| function renderDialog(): Dialogs.Dialog.Dialog { |
| const dialog = new Dialogs.Dialog.Dialog(); |
| |
| const container = document.createElement('div'); |
| container.classList.add('container'); |
| container.id = `container-${i}`; |
| |
| const host = document.createElement('div'); |
| host.classList.add('dialog-host-narrow'); |
| host.id = `host-${i}`; |
| host.textContent = 'H'; |
| |
| container.appendChild(host); |
| row.appendChild(container); |
| |
| dialog.position = Dialogs.Dialog.DialogVerticalPosition.BOTTOM; |
| dialog.horizontalAlignment = Dialogs.Dialog.DialogHorizontalAlignment.AUTO; |
| dialog.origin = host; |
| dialog.id = `dialog-${i}`; |
| |
| host.addEventListener('mouseover', () => { |
| void dialog.setDialogVisible(true); |
| }); |
| dialog.addEventListener('clickoutsidedialog', () => { |
| void dialog.setDialogVisible(false); |
| }); |
| const div = document.createElement('div'); |
| div.classList.add('dialog-content'); |
| div.style.padding = '0 var(--sys-size-8)'; |
| |
| div.innerHTML = 'Hello, World'; |
| dialog.appendChild(div); |
| root.appendChild(dialog); |
| i++; |
| |
| return dialog; |
| } |
| |
| function renderDialogWithTitle() { |
| const dialog = renderDialog(); |
| dialog.dialogTitle = 'title'; |
| } |
| |
| function renderDialogWithTitleAndCloseButton() { |
| const dialog = renderDialog(); |
| dialog.dialogTitle = 'title'; |
| dialog.closeButton = true; |
| } |
| |
| function renderDialogWithoutTitleOrCloseButton() { |
| renderDialog(); |
| } |
| } |
| |
| renderElementIntoDOM(root); |
| |
| return root; |
| } |
| |
| async function openDialog(dialogNumber: number) { |
| const dialog = document.querySelector(`#dialog-${dialogNumber}`) as Dialogs.Dialog.Dialog; |
| await dialog.setDialogVisible(true); |
| await raf(); |
| return dialog; |
| } |
| |
| it('renders the dialog at the top left properly', async () => { |
| await renderDialogs(); |
| await openDialog(1); |
| await assertScreenshot('dialog/top-left-open.png'); |
| }); |
| |
| it('renders a dialog at the bottom with automatic horizontal alignment properly', async () => { |
| await renderDialogs(); |
| await openDialog(5); |
| await assertScreenshot('dialog/bottom-auto-open.png'); |
| }); |
| |
| it('renders the dialog at the bottom center properly', async () => { |
| await renderDialogs(); |
| await openDialog(7); |
| await assertScreenshot('dialog/bottom-center-open.png'); |
| }); |
| |
| it('renders a dialog for super narrow origin at the top with automatic horizontal alignment properly', async () => { |
| await renderDialogs(); |
| await openDialog(20); |
| await assertScreenshot('dialog/narrow-top-auto-open.png'); |
| }); |
| |
| it('sets open attribute when the dialog is opened', async () => { |
| await renderDialogs(); |
| const dialog = await openDialog(2); |
| assert.isTrue(dialog.hasAttribute('open')); |
| }); |
| |
| it('removed open attribute when the dialog is hidden', async () => { |
| await renderDialogs(); |
| const dialog = await openDialog(2); |
| assert.isTrue(dialog.hasAttribute('open')); |
| await dialog.setDialogVisible(false); |
| await RenderCoordinator.done(); |
| assert.isFalse(dialog.hasAttribute('open')); |
| }); |
| }); |