chromium/third_party/google-closure-library/closure/goog/ui/modalpopup_test.js

/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.ui.ModalPopupTest');
goog.setTestOnly();

const EventType = goog.require('goog.events.EventType');
const GoogEventTarget = goog.require('goog.events.EventTarget');
const MockClock = goog.require('goog.testing.MockClock');
const ModalPopup = goog.require('goog.ui.ModalPopup');
const PopupBase = goog.require('goog.ui.PopupBase');
const State = goog.require('goog.a11y.aria.State');
const TagName = goog.require('goog.dom.TagName');
const Transition = goog.require('goog.fx.Transition');
const aria = goog.require('goog.a11y.aria');
const css3 = goog.require('goog.fx.css3');
const dispose = goog.require('goog.dispose');
const dom = goog.require('goog.dom');
const events = goog.require('goog.events');
const googString = goog.require('goog.string');
const style = goog.require('goog.style');
const testSuite = goog.require('goog.testing.testSuite');
const testingEvents = goog.require('goog.testing.events');

let popup;
let main;
let mockClock;

/** @implements {Transition} */
class MockTransition extends GoogEventTarget {
  constructor() {
    super();
    this.wasPlayed = false;
  }

  play() {
    this.wasPlayed = true;
  }

  stop() {}
}

testSuite({
  setUp() {
    main = /** @type {!Element} */ (dom.getElement('main'));
    mockClock = new MockClock(true);
  },

  tearDown() {
    dispose(popup);
    mockClock.dispose();
    aria.removeState(main, State.HIDDEN);
  },

  /** @suppress {checkTypes} suppression added to enable type checking */
  testOrientationChange() {
    let i = 0;
    popup = new ModalPopup();
    /** @suppress {visibility} suppression added to enable type checking */
    popup.resizeBackgroundTask_ = () => {
      i++;
    };
    popup.render();
    popup.setVisible(true);
    const event = new events.Event(
        EventType.ORIENTATIONCHANGE, popup.getDomHelper().getWindow());

    testingEvents.fireBrowserEvent(event);
    assertEquals(1, i);

    testingEvents.fireBrowserEvent(event);
    assertEquals(2, i);

    popup.setVisible(false);
    testingEvents.fireBrowserEvent(event);
    assertEquals(2, i);
  },

  testDispose() {
    popup = new ModalPopup();
    popup.render();

    dispose(popup);
    assertNull(dom.getElementByClass('goog-modalpopup-bg'));
    assertNull(dom.getElementByClass('goog-modalpopup'));
    assertEquals(0, dom.getElementsByTagNameAndClass(TagName.SPAN).length);
  },

  testRenderWithoutIframeMask() {
    popup = new ModalPopup();
    popup.render();

    assertEquals(
        0,
        dom.getElementsByTagNameAndClass(TagName.IFRAME, 'goog-modalpopup-bg')
            .length);

    const bg =
        dom.getElementsByTagNameAndClass(TagName.DIV, 'goog-modalpopup-bg');
    assertEquals(1, bg.length);
    const content = dom.getElementByClass('goog-modalpopup');
    assertNotNull(content);
    const tabCatcher = dom.getElementsByTagNameAndClass(TagName.SPAN);
    assertEquals(1, tabCatcher.length);

    assertTrue(dom.compareNodeOrder(bg[0], content) < 0);
    assertTrue(dom.compareNodeOrder(content, tabCatcher[0]) < 0);
    assertTrue(googString.isEmptyOrWhitespace(
        googString.makeSafe(aria.getState(main, State.HIDDEN))));
    popup.setVisible(true);
    assertTrue(googString.isEmptyOrWhitespace(googString.makeSafe(
        aria.getState(popup.getElementStrict(), State.HIDDEN))));
    assertEquals('true', aria.getState(main, State.HIDDEN));
    popup.setVisible(false);
    assertTrue(googString.isEmptyOrWhitespace(
        googString.makeSafe(aria.getState(main, State.HIDDEN))));
  },

  testRenderWithIframeMask() {
    popup = new ModalPopup(true);
    popup.render();

    const iframe =
        dom.getElementsByTagNameAndClass(TagName.IFRAME, 'goog-modalpopup-bg');
    assertEquals(1, iframe.length);
    const bg =
        dom.getElementsByTagNameAndClass(TagName.DIV, 'goog-modalpopup-bg');
    assertEquals(1, bg.length);
    const content = dom.getElementByClass('goog-modalpopup');
    assertNotNull(content);
    const tabCatcher = dom.getElementsByTagNameAndClass(TagName.SPAN);
    assertEquals(1, tabCatcher.length);

    assertTrue(dom.compareNodeOrder(iframe[0], bg[0]) < 0);
    assertTrue(dom.compareNodeOrder(bg[0], content) < 0);
    assertTrue(dom.compareNodeOrder(content, tabCatcher[0]) < 0);
    assertTrue(googString.isEmptyOrWhitespace(
        googString.makeSafe(aria.getState(main, State.HIDDEN))));
    popup.setVisible(true);
    assertTrue(googString.isEmptyOrWhitespace(googString.makeSafe(
        aria.getState(popup.getElementStrict(), State.HIDDEN))));
    assertEquals('true', aria.getState(main, State.HIDDEN));
    popup.setVisible(false);
    assertTrue(googString.isEmptyOrWhitespace(
        googString.makeSafe(aria.getState(main, State.HIDDEN))));
  },

  testRenderWithAriaState() {
    popup = new ModalPopup();
    popup.render();

    aria.setState(main, State.HIDDEN, true);
    popup.setVisible(true);
    assertEquals('true', aria.getState(main, State.HIDDEN));
    popup.setVisible(false);
    assertEquals('true', aria.getState(main, State.HIDDEN));

    aria.setState(main, State.HIDDEN, false);
    popup.setVisible(true);
    assertEquals('false', aria.getState(main, State.HIDDEN));
    popup.setVisible(false);
    assertEquals('false', aria.getState(main, State.HIDDEN));
  },

  testRenderDoesNotShowAnyElement() {
    popup = new ModalPopup(true);
    popup.render();

    const iframe =
        dom.getElementsByTagNameAndClass(TagName.IFRAME, 'goog-modalpopup-bg');
    assertFalse(style.isElementShown(iframe[0]));
    const bg =
        dom.getElementsByTagNameAndClass(TagName.DIV, 'goog-modalpopup-bg');
    assertFalse(style.isElementShown(bg[0]));
    assertFalse(style.isElementShown(dom.getElementByClass('goog-modalpopup')));
    const tabCatcher = dom.getElementsByTagNameAndClass(TagName.SPAN);
    assertFalse(style.isElementShown(tabCatcher[0]));
  },

  testIframeOpacityIsSetToZero() {
    popup = new ModalPopup(true);
    popup.render();

    const iframe = dom.getElementsByTagNameAndClass(
        TagName.IFRAME, 'goog-modalpopup-bg')[0];
    assertEquals(0, style.getOpacity(iframe));
  },

  testEventFiredOnShow() {
    popup = new ModalPopup(true);
    popup.render();

    let beforeShowCallCount = 0;
    const beforeShowHandler = () => {
      beforeShowCallCount++;
    };
    let showCallCount = false;
    /**
     * @suppress {strictPrimitiveOperators} suppression added to enable type
     * checking
     */
    const showHandler = () => {
      assertEquals(
          'BEFORE_SHOW is not dispatched before SHOW', 1, beforeShowCallCount);
      showCallCount++;
    };

    events.listen(popup, PopupBase.EventType.BEFORE_SHOW, beforeShowHandler);
    events.listen(popup, PopupBase.EventType.SHOW, showHandler);

    popup.setVisible(true);

    assertEquals(1, beforeShowCallCount);
    assertEquals(1, showCallCount);
  },

  testEventFiredOnHide() {
    popup = new ModalPopup(true);
    popup.render();
    popup.setVisible(true);

    let beforeHideCallCount = 0;
    const beforeHideHandler = () => {
      beforeHideCallCount++;
    };
    let hideCallCount = false;
    /**
     * @suppress {strictPrimitiveOperators} suppression added to enable type
     * checking
     */
    const hideHandler = () => {
      assertEquals(
          'BEFORE_HIDE is not dispatched before HIDE', 1, beforeHideCallCount);
      hideCallCount++;
    };

    events.listen(popup, PopupBase.EventType.BEFORE_HIDE, beforeHideHandler);
    events.listen(popup, PopupBase.EventType.HIDE, hideHandler);

    popup.setVisible(false);

    assertEquals(1, beforeHideCallCount);
    assertEquals(1, hideCallCount);
  },

  testShowEventFiredWithNoTransition() {
    popup = new ModalPopup();
    popup.render();

    let showHandlerCalled = false;
    events.listen(popup, PopupBase.EventType.SHOW, () => {
      showHandlerCalled = true;
    });

    popup.setVisible(true);
    assertTrue(showHandlerCalled);
  },

  testHideEventFiredWithNoTransition() {
    popup = new ModalPopup();
    popup.render();

    let hideHandlerCalled = false;
    events.listen(popup, PopupBase.EventType.HIDE, () => {
      hideHandlerCalled = true;
    });

    popup.setVisible(true);
    popup.setVisible(false);
    assertTrue(hideHandlerCalled);
  },

  testTransitionsPlayedOnShow() {
    popup = new ModalPopup();
    popup.render();

    const mockPopupShowTransition = new MockTransition();
    const mockPopupHideTransition = new MockTransition();
    const mockBgShowTransition = new MockTransition();
    const mockBgHideTransition = new MockTransition();

    let showHandlerCalled = false;
    events.listen(popup, PopupBase.EventType.SHOW, () => {
      showHandlerCalled = true;
    });

    popup.setTransition(
        mockPopupShowTransition, mockPopupHideTransition, mockBgShowTransition,
        mockBgHideTransition);
    assertFalse(mockPopupShowTransition.wasPlayed);
    assertFalse(mockBgShowTransition.wasPlayed);

    popup.setVisible(true);
    assertTrue(mockPopupShowTransition.wasPlayed);
    assertTrue(mockBgShowTransition.wasPlayed);

    assertFalse(showHandlerCalled);
    mockPopupShowTransition.dispatchEvent(Transition.EventType.END);
    assertTrue(showHandlerCalled);
  },

  testTransitionsPlayedOnHide() {
    popup = new ModalPopup();
    popup.render();

    const mockPopupShowTransition = new MockTransition();
    const mockPopupHideTransition = new MockTransition();
    const mockBgShowTransition = new MockTransition();
    const mockBgHideTransition = new MockTransition();

    let hideHandlerCalled = false;
    events.listen(popup, PopupBase.EventType.HIDE, () => {
      hideHandlerCalled = true;
    });

    popup.setTransition(
        mockPopupShowTransition, mockPopupHideTransition, mockBgShowTransition,
        mockBgHideTransition);
    popup.setVisible(true);
    assertFalse(mockPopupHideTransition.wasPlayed);
    assertFalse(mockBgHideTransition.wasPlayed);

    popup.setVisible(false);
    assertTrue(mockPopupHideTransition.wasPlayed);
    assertTrue(mockBgHideTransition.wasPlayed);

    assertFalse(hideHandlerCalled);
    mockPopupHideTransition.dispatchEvent(Transition.EventType.END);
    assertTrue(hideHandlerCalled);
  },

  testTransitionsAndDisposingOnHideWorks() {
    popup = new ModalPopup();
    popup.render();

    events.listen(popup, PopupBase.EventType.HIDE, () => {
      popup.dispose();
    });

    const popupShowTransition =
        css3.fadeIn(popup.getElement(), 0.1 /* duration */);
    const popupHideTransition =
        css3.fadeOut(popup.getElement(), 0.1 /* duration */);
    const bgShowTransition =
        css3.fadeIn(popup.getElement(), 0.1 /* duration */);
    const bgHideTransition =
        css3.fadeOut(popup.getElement(), 0.1 /* duration */);

    popup.setTransition(
        popupShowTransition, popupHideTransition, bgShowTransition,
        bgHideTransition);
    popup.setVisible(true);
    popup.setVisible(false);
    // Nothing to assert. We only want to ensure that there is no error.
  },

  testSetVisibleWorksCorrectlyWithTransitions() {
    popup = new ModalPopup();
    popup.render();
    popup.setTransition(
        css3.fadeIn(popup.getElement(), 1),
        css3.fadeIn(popup.getBackgroundElement(), 1),
        css3.fadeOut(popup.getElement(), 1),
        css3.fadeOut(popup.getBackgroundElement(), 1));

    // Consecutive calls to setVisible works without needing to wait for
    // transition to finish.
    popup.setVisible(true);
    assertTrue(popup.isVisible());
    popup.setVisible(false);
    assertFalse(popup.isVisible());
    mockClock.tick(1100);

    // Calling setVisible(true) immediately changed the state to visible.
    popup.setVisible(true);
    assertTrue(popup.isVisible());
    mockClock.tick(1100);

    // Consecutive calls to setVisible, in opposite order.
    popup.setVisible(false);
    popup.setVisible(true);
    assertTrue(popup.isVisible());
    mockClock.tick(1100);

    // Calling setVisible(false) immediately changed the state to not visible.
    popup.setVisible(false);
    assertFalse(popup.isVisible());
    mockClock.tick(1100);
  },

  testTransitionsDisposed() {
    popup = new ModalPopup();
    popup.render();

    const transition = css3.fadeIn(popup.getElement(), 0.1 /* duration */);

    let hideHandlerCalled = false;
    events.listen(popup, PopupBase.EventType.HIDE, () => {
      hideHandlerCalled = true;
    });

    popup.setTransition(transition, transition, transition, transition);
    popup.dispose();

    transition.dispatchEvent(Transition.EventType.END);
    assertFalse(hideHandlerCalled);
  },

  testBackgroundHeight() {
    // Insert an absolutely-positioned element larger than the viewport.
    const viewportSize = dom.getViewportSize();
    const w = viewportSize.width * 2;
    const h = viewportSize.height * 2;
    const dummy = dom.createElement(TagName.DIV);
    dummy.style.position = 'absolute';
    style.setSize(dummy, w, h);
    document.body.appendChild(dummy);

    try {
      popup = new ModalPopup();
      popup.render();
      popup.setVisible(true);

      const size = style.getSize(popup.getBackgroundElement());
      assertTrue(
          'Background element must cover the size of the content',
          size.width >= w && size.height >= h);
    } finally {
      dom.removeNode(dummy);
    }
  },

  /** @suppress {visibility} suppression added to enable type checking */
  testSetupBackwardTabWrapResetsFlagAfterTimeout() {
    popup.setupBackwardTabWrap();
    assertTrue(
        'Backward tab wrap should be in progress',
        popup.backwardTabWrapInProgress_);
    mockClock.tick(1);
    assertFalse(
        'Backward tab wrap flag should be reset after delay',
        popup.backwardTabWrapInProgress_);
  },

  testPopupGetsFocus() {
    popup = new ModalPopup();
    popup.render();
    popup.setVisible(true);
    assertTrue(
        'Dialog must receive initial focus',
        dom.getActiveElement(document) == popup.getElement());
  },

  testDecoratedPopupGetsFocus() {
    const dialogElem = dom.createElement(TagName.DIV);
    document.body.appendChild(dialogElem);
    popup = new ModalPopup();
    popup.decorate(dialogElem);
    popup.setVisible(true);
    assertTrue(
        'Dialog must receive initial focus',
        dom.getActiveElement(document) == popup.getElement());
    dom.removeNode(dialogElem);
  },
});