forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			566 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			566 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* Any copyright is dedicated to the Public Domain.
 | |
|  * http://creativecommons.org/publicdomain/zero/1.0/ */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| /**
 | |
|  * Unit tests for the PanelMultiView module.
 | |
|  */
 | |
| 
 | |
| const PANELS_COUNT = 2;
 | |
| let gPanelAnchors = [];
 | |
| let gPanels = [];
 | |
| let gPanelMultiViews = [];
 | |
| 
 | |
| const PANELVIEWS_COUNT = 4;
 | |
| let gPanelViews = [];
 | |
| let gPanelViewLabels = [];
 | |
| 
 | |
| const EVENT_TYPES = [
 | |
|   "popupshown",
 | |
|   "popuphidden",
 | |
|   "PanelMultiViewHidden",
 | |
|   "ViewShowing",
 | |
|   "ViewShown",
 | |
|   "ViewHiding",
 | |
| ];
 | |
| 
 | |
| /**
 | |
|  * Checks that the element is displayed, including the state of the popup where
 | |
|  * the element is located. This can trigger a synchronous reflow if necessary,
 | |
|  * because even though the code under test is designed to avoid synchronous
 | |
|  * reflows, it can raise completion events while a layout flush is still needed.
 | |
|  *
 | |
|  * In production code, event handlers for ViewShown have to wait for a flush if
 | |
|  * they need to read style or layout information, like other code normally does.
 | |
|  */
 | |
| function is_visible(element) {
 | |
|   let win = element.ownerGlobal;
 | |
|   let style = win.getComputedStyle(element);
 | |
|   if (style.display == "none") {
 | |
|     return false;
 | |
|   }
 | |
|   if (style.visibility != "visible") {
 | |
|     return false;
 | |
|   }
 | |
|   if (win.XULPopupElement.isInstance(element) && element.state != "open") {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Hiding a parent element will hide all its children
 | |
|   if (element.parentNode != element.ownerDocument) {
 | |
|     return is_visible(element.parentNode);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Checks whether the label in the specified view is visible.
 | |
|  */
 | |
| function assertLabelVisible(viewIndex, expectedVisible) {
 | |
|   Assert.equal(
 | |
|     is_visible(gPanelViewLabels[viewIndex]),
 | |
|     expectedVisible,
 | |
|     `Visibility of label in view ${viewIndex}`
 | |
|   );
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Opens the specified view as the main view in the specified panel.
 | |
|  */
 | |
| async function openPopup(panelIndex, viewIndex) {
 | |
|   gPanelMultiViews[panelIndex].setAttribute(
 | |
|     "mainViewId",
 | |
|     gPanelViews[viewIndex].id
 | |
|   );
 | |
| 
 | |
|   let promiseShown = BrowserTestUtils.waitForEvent(
 | |
|     gPanelViews[viewIndex],
 | |
|     "ViewShown"
 | |
|   );
 | |
|   PanelMultiView.openPopup(
 | |
|     gPanels[panelIndex],
 | |
|     gPanelAnchors[panelIndex],
 | |
|     "bottomright topright"
 | |
|   );
 | |
|   await promiseShown;
 | |
| 
 | |
|   Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active);
 | |
|   assertLabelVisible(viewIndex, true);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Closes the specified panel.
 | |
|  */
 | |
| async function hidePopup(panelIndex) {
 | |
|   gPanelMultiViews[panelIndex].setAttribute(
 | |
|     "mainViewId",
 | |
|     gPanelViews[panelIndex].id
 | |
|   );
 | |
| 
 | |
|   let promiseHidden = BrowserTestUtils.waitForEvent(
 | |
|     gPanels[panelIndex],
 | |
|     "popuphidden"
 | |
|   );
 | |
|   PanelMultiView.hidePopup(gPanels[panelIndex]);
 | |
|   await promiseHidden;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Opens the specified subview in the specified panel.
 | |
|  */
 | |
| async function showSubView(panelIndex, viewIndex) {
 | |
|   let promiseShown = BrowserTestUtils.waitForEvent(
 | |
|     gPanelViews[viewIndex],
 | |
|     "ViewShown"
 | |
|   );
 | |
|   gPanelMultiViews[panelIndex].showSubView(gPanelViews[viewIndex]);
 | |
|   await promiseShown;
 | |
| 
 | |
|   Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active);
 | |
|   assertLabelVisible(viewIndex, true);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Navigates backwards to the specified view, which is displayed as a result.
 | |
|  */
 | |
| async function goBack(panelIndex, viewIndex) {
 | |
|   let promiseShown = BrowserTestUtils.waitForEvent(
 | |
|     gPanelViews[viewIndex],
 | |
|     "ViewShown"
 | |
|   );
 | |
|   gPanelMultiViews[panelIndex].goBack();
 | |
|   await promiseShown;
 | |
| 
 | |
|   Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active);
 | |
|   assertLabelVisible(viewIndex, true);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Records the specified events on an element into the specified array. An
 | |
|  * optional callback can be used to respond to events and trigger nested events.
 | |
|  */
 | |
| function recordEvents(
 | |
|   element,
 | |
|   eventTypes,
 | |
|   recordArray,
 | |
|   eventCallback = () => {}
 | |
| ) {
 | |
|   let nestedEvents = [];
 | |
|   element.recorders = eventTypes.map(eventType => {
 | |
|     let recorder = {
 | |
|       eventType,
 | |
|       listener(event) {
 | |
|         let eventString =
 | |
|           nestedEvents.join("") + `${event.originalTarget.id}: ${event.type}`;
 | |
|         info(`Event on ${eventString}`);
 | |
|         recordArray.push(eventString);
 | |
|         // Any synchronous event triggered from within the given callback will
 | |
|         // include information about the current event.
 | |
|         nestedEvents.unshift(`${eventString} > `);
 | |
|         eventCallback(event);
 | |
|         nestedEvents.shift();
 | |
|       },
 | |
|     };
 | |
|     element.addEventListener(recorder.eventType, recorder.listener);
 | |
|     return recorder;
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Stops recording events on an element.
 | |
|  */
 | |
| function stopRecordingEvents(element) {
 | |
|   for (let recorder of element.recorders) {
 | |
|     element.removeEventListener(recorder.eventType, recorder.listener);
 | |
|   }
 | |
|   delete element.recorders;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Sets up the elements in the browser window that will be used by all the other
 | |
|  * regression tests. Since the panel and view elements can live anywhere in the
 | |
|  * document, they are simply added to the same toolbar as the panel anchors.
 | |
|  *
 | |
|  * <toolbar id="nav-bar">
 | |
|  *     <toolbarbutton/>      ->  gPanelAnchors[panelIndex]
 | |
|  *     <panel>               ->  gPanels[panelIndex]
 | |
|  *       <panelmultiview/>   ->  gPanelMultiViews[panelIndex]
 | |
|  *     </panel>
 | |
|  *     <panelview>           ->  gPanelViews[viewIndex]
 | |
|  *       <label/>            ->  gPanelViewLabels[viewIndex]
 | |
|  *     </panelview>
 | |
|  * </toolbar>
 | |
|  */
 | |
| add_task(async function test_setup() {
 | |
|   let navBar = document.getElementById("nav-bar");
 | |
| 
 | |
|   for (let i = 0; i < PANELS_COUNT; i++) {
 | |
|     gPanelAnchors[i] = document.createXULElement("toolbarbutton");
 | |
|     gPanelAnchors[i].classList.add(
 | |
|       "toolbarbutton-1",
 | |
|       "chromeclass-toolbar-additional"
 | |
|     );
 | |
|     navBar.appendChild(gPanelAnchors[i]);
 | |
| 
 | |
|     gPanels[i] = document.createXULElement("panel");
 | |
|     gPanels[i].id = "panel-" + i;
 | |
|     gPanels[i].setAttribute("type", "arrow");
 | |
|     gPanels[i].setAttribute("photon", true);
 | |
|     navBar.appendChild(gPanels[i]);
 | |
| 
 | |
|     gPanelMultiViews[i] = document.createXULElement("panelmultiview");
 | |
|     gPanelMultiViews[i].id = "panelmultiview-" + i;
 | |
|     gPanels[i].appendChild(gPanelMultiViews[i]);
 | |
|   }
 | |
| 
 | |
|   for (let i = 0; i < PANELVIEWS_COUNT; i++) {
 | |
|     gPanelViews[i] = document.createXULElement("panelview");
 | |
|     gPanelViews[i].id = "panelview-" + i;
 | |
|     navBar.appendChild(gPanelViews[i]);
 | |
| 
 | |
|     gPanelViewLabels[i] = document.createXULElement("label");
 | |
|     gPanelViewLabels[i].setAttribute("value", "PanelView " + i);
 | |
|     gPanelViews[i].appendChild(gPanelViewLabels[i]);
 | |
|   }
 | |
| 
 | |
|   registerCleanupFunction(() => {
 | |
|     [...gPanelAnchors, ...gPanels, ...gPanelViews].forEach(e => e.remove());
 | |
|   });
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Shows and hides all views in a panel with this static structure:
 | |
|  *
 | |
|  * - Panel 0
 | |
|  *   - View 0
 | |
|  *     - View 1
 | |
|  *     - View 3
 | |
|  *       - View 2
 | |
|  */
 | |
| add_task(async function test_simple() {
 | |
|   // Show main view 0.
 | |
|   await openPopup(0, 0);
 | |
| 
 | |
|   // Show and hide subview 1.
 | |
|   await showSubView(0, 1);
 | |
|   assertLabelVisible(0, false);
 | |
|   await goBack(0, 0);
 | |
|   assertLabelVisible(1, false);
 | |
| 
 | |
|   // Show subview 3.
 | |
|   await showSubView(0, 3);
 | |
|   assertLabelVisible(0, false);
 | |
| 
 | |
|   // Show and hide subview 2.
 | |
|   await showSubView(0, 2);
 | |
|   assertLabelVisible(3, false);
 | |
|   await goBack(0, 3);
 | |
|   assertLabelVisible(2, false);
 | |
| 
 | |
|   // Hide subview 3.
 | |
|   await goBack(0, 0);
 | |
|   assertLabelVisible(3, false);
 | |
| 
 | |
|   // Hide main view 0.
 | |
|   await hidePopup(0);
 | |
|   assertLabelVisible(0, false);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Tests the event sequence in a panel with this static structure:
 | |
|  *
 | |
|  * - Panel 0
 | |
|  *   - View 0
 | |
|  *     - View 1
 | |
|  *     - View 3
 | |
|  *       - View 2
 | |
|  */
 | |
| add_task(async function test_simple_event_sequence() {
 | |
|   let recordArray = [];
 | |
|   recordEvents(gPanels[0], EVENT_TYPES, recordArray);
 | |
| 
 | |
|   await openPopup(0, 0);
 | |
|   await showSubView(0, 1);
 | |
|   await goBack(0, 0);
 | |
|   await showSubView(0, 3);
 | |
|   await showSubView(0, 2);
 | |
|   await goBack(0, 3);
 | |
|   await goBack(0, 0);
 | |
|   await hidePopup(0);
 | |
| 
 | |
|   stopRecordingEvents(gPanels[0]);
 | |
| 
 | |
|   Assert.deepEqual(recordArray, [
 | |
|     "panelview-0: ViewShowing",
 | |
|     "panelview-0: ViewShown",
 | |
|     "panel-0: popupshown",
 | |
|     "panelview-1: ViewShowing",
 | |
|     "panelview-1: ViewShown",
 | |
|     "panelview-1: ViewHiding",
 | |
|     "panelview-0: ViewShown",
 | |
|     "panelview-3: ViewShowing",
 | |
|     "panelview-3: ViewShown",
 | |
|     "panelview-2: ViewShowing",
 | |
|     "panelview-2: ViewShown",
 | |
|     "panelview-2: ViewHiding",
 | |
|     "panelview-3: ViewShown",
 | |
|     "panelview-3: ViewHiding",
 | |
|     "panelview-0: ViewShown",
 | |
|     "panelview-0: ViewHiding",
 | |
|     "panelmultiview-0: PanelMultiViewHidden",
 | |
|     "panel-0: popuphidden",
 | |
|   ]);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Tests that further navigation is suppressed until the new view is shown.
 | |
|  */
 | |
| add_task(async function test_navigation_suppression() {
 | |
|   await openPopup(0, 0);
 | |
| 
 | |
|   // Test re-entering the "showSubView" method.
 | |
|   let promiseShown = BrowserTestUtils.waitForEvent(gPanelViews[1], "ViewShown");
 | |
|   gPanelMultiViews[0].showSubView(gPanelViews[1]);
 | |
|   Assert.ok(
 | |
|     !PanelView.forNode(gPanelViews[0]).active,
 | |
|     "The previous view should become inactive synchronously."
 | |
|   );
 | |
| 
 | |
|   // The following call will have no effect.
 | |
|   gPanelMultiViews[0].showSubView(gPanelViews[2]);
 | |
|   await promiseShown;
 | |
| 
 | |
|   // Test re-entering the "goBack" method.
 | |
|   promiseShown = BrowserTestUtils.waitForEvent(gPanelViews[0], "ViewShown");
 | |
|   gPanelMultiViews[0].goBack();
 | |
|   Assert.ok(
 | |
|     !PanelView.forNode(gPanelViews[1]).active,
 | |
|     "The previous view should become inactive synchronously."
 | |
|   );
 | |
| 
 | |
|   // The following call will have no effect.
 | |
|   gPanelMultiViews[0].goBack();
 | |
|   await promiseShown;
 | |
| 
 | |
|   // Main view 0 should be displayed.
 | |
|   assertLabelVisible(0, true);
 | |
| 
 | |
|   await hidePopup(0);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Tests reusing views that are already open in another panel. In this test, the
 | |
|  * structure of the first panel will change dynamically:
 | |
|  *
 | |
|  * - Panel 0
 | |
|  *   - View 0
 | |
|  *     - View 1
 | |
|  * - Panel 1
 | |
|  *   - View 1
 | |
|  *     - View 2
 | |
|  * - Panel 0
 | |
|  *   - View 1
 | |
|  *     - View 0
 | |
|  */
 | |
| add_task(async function test_switch_event_sequence() {
 | |
|   let recordArray = [];
 | |
|   recordEvents(gPanels[0], EVENT_TYPES, recordArray);
 | |
|   recordEvents(gPanels[1], EVENT_TYPES, recordArray);
 | |
| 
 | |
|   // Show panel 0.
 | |
|   await openPopup(0, 0);
 | |
|   await showSubView(0, 1);
 | |
| 
 | |
|   // Show panel 1 with the view that is already open and visible in panel 0.
 | |
|   // This will close panel 0 automatically.
 | |
|   await openPopup(1, 1);
 | |
|   await showSubView(1, 2);
 | |
| 
 | |
|   // Show panel 0 with a view that is already open but invisible in panel 1.
 | |
|   // This will close panel 1 automatically.
 | |
|   await openPopup(0, 1);
 | |
|   await showSubView(0, 0);
 | |
| 
 | |
|   // Hide panel 0.
 | |
|   await hidePopup(0);
 | |
| 
 | |
|   stopRecordingEvents(gPanels[0]);
 | |
|   stopRecordingEvents(gPanels[1]);
 | |
| 
 | |
|   Assert.deepEqual(recordArray, [
 | |
|     "panelview-0: ViewShowing",
 | |
|     "panelview-0: ViewShown",
 | |
|     "panel-0: popupshown",
 | |
|     "panelview-1: ViewShowing",
 | |
|     "panelview-1: ViewShown",
 | |
|     "panelview-1: ViewHiding",
 | |
|     "panelview-0: ViewHiding",
 | |
|     "panelmultiview-0: PanelMultiViewHidden",
 | |
|     "panel-0: popuphidden",
 | |
|     "panelview-1: ViewShowing",
 | |
|     "panel-1: popupshown",
 | |
|     "panelview-1: ViewShown",
 | |
|     "panelview-2: ViewShowing",
 | |
|     "panelview-2: ViewShown",
 | |
|     "panel-1: popuphidden",
 | |
|     "panelview-2: ViewHiding",
 | |
|     "panelview-1: ViewHiding",
 | |
|     "panelmultiview-1: PanelMultiViewHidden",
 | |
|     "panelview-1: ViewShowing",
 | |
|     "panelview-1: ViewShown",
 | |
|     "panel-0: popupshown",
 | |
|     "panelview-0: ViewShowing",
 | |
|     "panelview-0: ViewShown",
 | |
|     "panelview-0: ViewHiding",
 | |
|     "panelview-1: ViewHiding",
 | |
|     "panelmultiview-0: PanelMultiViewHidden",
 | |
|     "panel-0: popuphidden",
 | |
|   ]);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Tests the event sequence when opening the main view is canceled.
 | |
|  */
 | |
| add_task(async function test_cancel_mainview_event_sequence() {
 | |
|   let recordArray = [];
 | |
|   recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
 | |
|     if (event.type == "ViewShowing") {
 | |
|       event.preventDefault();
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   gPanelMultiViews[0].setAttribute("mainViewId", gPanelViews[0].id);
 | |
| 
 | |
|   let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden");
 | |
|   PanelMultiView.openPopup(
 | |
|     gPanels[0],
 | |
|     gPanelAnchors[0],
 | |
|     "bottomright topright"
 | |
|   );
 | |
|   await promiseHidden;
 | |
| 
 | |
|   stopRecordingEvents(gPanels[0]);
 | |
| 
 | |
|   Assert.deepEqual(recordArray, [
 | |
|     "panelview-0: ViewShowing",
 | |
|     "panelview-0: ViewHiding",
 | |
|     "panelmultiview-0: PanelMultiViewHidden",
 | |
|     "panelmultiview-0: popuphidden",
 | |
|   ]);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Tests the event sequence when opening a subview is canceled.
 | |
|  */
 | |
| add_task(async function test_cancel_subview_event_sequence() {
 | |
|   let recordArray = [];
 | |
|   recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
 | |
|     if (
 | |
|       event.type == "ViewShowing" &&
 | |
|       event.originalTarget.id == gPanelViews[1].id
 | |
|     ) {
 | |
|       event.preventDefault();
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   await openPopup(0, 0);
 | |
| 
 | |
|   let promiseHiding = BrowserTestUtils.waitForEvent(
 | |
|     gPanelViews[1],
 | |
|     "ViewHiding"
 | |
|   );
 | |
|   gPanelMultiViews[0].showSubView(gPanelViews[1]);
 | |
|   await promiseHiding;
 | |
| 
 | |
|   // Only the subview should have received the hidden event at this point.
 | |
|   Assert.deepEqual(recordArray, [
 | |
|     "panelview-0: ViewShowing",
 | |
|     "panelview-0: ViewShown",
 | |
|     "panel-0: popupshown",
 | |
|     "panelview-1: ViewShowing",
 | |
|     "panelview-1: ViewHiding",
 | |
|   ]);
 | |
|   recordArray.length = 0;
 | |
| 
 | |
|   await hidePopup(0);
 | |
| 
 | |
|   stopRecordingEvents(gPanels[0]);
 | |
| 
 | |
|   Assert.deepEqual(recordArray, [
 | |
|     "panelview-0: ViewHiding",
 | |
|     "panelmultiview-0: PanelMultiViewHidden",
 | |
|     "panel-0: popuphidden",
 | |
|   ]);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Tests the event sequence when closing the panel while opening the main view.
 | |
|  */
 | |
| add_task(async function test_close_while_showing_mainview_event_sequence() {
 | |
|   let recordArray = [];
 | |
|   recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
 | |
|     if (event.type == "ViewShowing") {
 | |
|       PanelMultiView.hidePopup(gPanels[0]);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   gPanelMultiViews[0].setAttribute("mainViewId", gPanelViews[0].id);
 | |
| 
 | |
|   let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden");
 | |
|   let promiseHiding = BrowserTestUtils.waitForEvent(
 | |
|     gPanelViews[0],
 | |
|     "ViewHiding"
 | |
|   );
 | |
|   PanelMultiView.openPopup(
 | |
|     gPanels[0],
 | |
|     gPanelAnchors[0],
 | |
|     "bottomright topright"
 | |
|   );
 | |
|   await promiseHiding;
 | |
|   await promiseHidden;
 | |
| 
 | |
|   stopRecordingEvents(gPanels[0]);
 | |
| 
 | |
|   Assert.deepEqual(recordArray, [
 | |
|     "panelview-0: ViewShowing",
 | |
|     "panelview-0: ViewShowing > panelview-0: ViewHiding",
 | |
|     "panelview-0: ViewShowing > panelmultiview-0: PanelMultiViewHidden",
 | |
|     "panelview-0: ViewShowing > panelmultiview-0: popuphidden",
 | |
|   ]);
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Tests the event sequence when closing the panel while opening a subview.
 | |
|  */
 | |
| add_task(async function test_close_while_showing_subview_event_sequence() {
 | |
|   let recordArray = [];
 | |
|   recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => {
 | |
|     if (
 | |
|       event.type == "ViewShowing" &&
 | |
|       event.originalTarget.id == gPanelViews[1].id
 | |
|     ) {
 | |
|       PanelMultiView.hidePopup(gPanels[0]);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   await openPopup(0, 0);
 | |
| 
 | |
|   let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden");
 | |
|   gPanelMultiViews[0].showSubView(gPanelViews[1]);
 | |
|   await promiseHidden;
 | |
| 
 | |
|   stopRecordingEvents(gPanels[0]);
 | |
| 
 | |
|   Assert.deepEqual(recordArray, [
 | |
|     "panelview-0: ViewShowing",
 | |
|     "panelview-0: ViewShown",
 | |
|     "panel-0: popupshown",
 | |
|     "panelview-1: ViewShowing",
 | |
|     "panelview-1: ViewShowing > panelview-1: ViewHiding",
 | |
|     "panelview-1: ViewShowing > panelview-0: ViewHiding",
 | |
|     "panelview-1: ViewShowing > panelmultiview-0: PanelMultiViewHidden",
 | |
|     "panelview-1: ViewShowing > panel-0: popuphidden",
 | |
|   ]);
 | |
| });
 | 
