browser_PanelMultiView.js (15352B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * Unit tests for the PanelMultiView module. 8 */ 9 10 const PANELS_COUNT = 2; 11 let gPanelAnchors = []; 12 let gPanels = []; 13 let gPanelMultiViews = []; 14 15 const PANELVIEWS_COUNT = 4; 16 let gPanelViews = []; 17 let gPanelViewLabels = []; 18 19 const EVENT_TYPES = [ 20 "popupshown", 21 "popuphidden", 22 "PanelMultiViewHidden", 23 "ViewShowing", 24 "ViewShown", 25 "ViewHiding", 26 ]; 27 28 /** 29 * Checks that the element is displayed, including the state of the popup where 30 * the element is located. This can trigger a synchronous reflow if necessary, 31 * because even though the code under test is designed to avoid synchronous 32 * reflows, it can raise completion events while a layout flush is still needed. 33 * 34 * In production code, event handlers for ViewShown have to wait for a flush if 35 * they need to read style or layout information, like other code normally does. 36 */ 37 function is_visible(element) { 38 let win = element.ownerGlobal; 39 let style = win.getComputedStyle(element); 40 if (style.display == "none") { 41 return false; 42 } 43 if (style.visibility != "visible") { 44 return false; 45 } 46 if (win.XULPopupElement.isInstance(element) && element.state != "open") { 47 return false; 48 } 49 50 // Hiding a parent element will hide all its children 51 if (element.parentNode != element.ownerDocument) { 52 return is_visible(element.parentNode); 53 } 54 55 return true; 56 } 57 58 /** 59 * Checks whether the label in the specified view is visible. 60 */ 61 function assertLabelVisible(viewIndex, expectedVisible) { 62 Assert.equal( 63 is_visible(gPanelViewLabels[viewIndex]), 64 expectedVisible, 65 `Visibility of label in view ${viewIndex}` 66 ); 67 } 68 69 /** 70 * Opens the specified view as the main view in the specified panel. 71 */ 72 async function openPopup(panelIndex, viewIndex) { 73 gPanelMultiViews[panelIndex].setAttribute( 74 "mainViewId", 75 gPanelViews[viewIndex].id 76 ); 77 78 let promiseShown = BrowserTestUtils.waitForEvent( 79 gPanelViews[viewIndex], 80 "ViewShown" 81 ); 82 PanelMultiView.openPopup( 83 gPanels[panelIndex], 84 gPanelAnchors[panelIndex], 85 "bottomright topright" 86 ); 87 await promiseShown; 88 89 Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active); 90 assertLabelVisible(viewIndex, true); 91 } 92 93 /** 94 * Closes the specified panel. 95 */ 96 async function hidePopup(panelIndex) { 97 gPanelMultiViews[panelIndex].setAttribute( 98 "mainViewId", 99 gPanelViews[panelIndex].id 100 ); 101 102 let promiseHidden = BrowserTestUtils.waitForEvent( 103 gPanels[panelIndex], 104 "popuphidden" 105 ); 106 PanelMultiView.hidePopup(gPanels[panelIndex]); 107 await promiseHidden; 108 } 109 110 /** 111 * Opens the specified subview in the specified panel. 112 */ 113 async function showSubView(panelIndex, viewIndex) { 114 let promiseShown = BrowserTestUtils.waitForEvent( 115 gPanelViews[viewIndex], 116 "ViewShown" 117 ); 118 gPanelMultiViews[panelIndex].showSubView(gPanelViews[viewIndex]); 119 await promiseShown; 120 121 Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active); 122 assertLabelVisible(viewIndex, true); 123 } 124 125 /** 126 * Navigates backwards to the specified view, which is displayed as a result. 127 */ 128 async function goBack(panelIndex, viewIndex) { 129 let promiseShown = BrowserTestUtils.waitForEvent( 130 gPanelViews[viewIndex], 131 "ViewShown" 132 ); 133 gPanelMultiViews[panelIndex].goBack(); 134 await promiseShown; 135 136 Assert.ok(PanelView.forNode(gPanelViews[viewIndex]).active); 137 assertLabelVisible(viewIndex, true); 138 } 139 140 /** 141 * Records the specified events on an element into the specified array. An 142 * optional callback can be used to respond to events and trigger nested events. 143 */ 144 function recordEvents( 145 element, 146 eventTypes, 147 recordArray, 148 eventCallback = () => {} 149 ) { 150 let nestedEvents = []; 151 element.recorders = eventTypes.map(eventType => { 152 let recorder = { 153 eventType, 154 listener(event) { 155 let eventString = 156 nestedEvents.join("") + `${event.originalTarget.id}: ${event.type}`; 157 info(`Event on ${eventString}`); 158 recordArray.push(eventString); 159 // Any synchronous event triggered from within the given callback will 160 // include information about the current event. 161 nestedEvents.unshift(`${eventString} > `); 162 eventCallback(event); 163 nestedEvents.shift(); 164 }, 165 }; 166 element.addEventListener(recorder.eventType, recorder.listener); 167 return recorder; 168 }); 169 } 170 171 /** 172 * Stops recording events on an element. 173 */ 174 function stopRecordingEvents(element) { 175 for (let recorder of element.recorders) { 176 element.removeEventListener(recorder.eventType, recorder.listener); 177 } 178 delete element.recorders; 179 } 180 181 /** 182 * Sets up the elements in the browser window that will be used by all the other 183 * regression tests. Since the panel and view elements can live anywhere in the 184 * document, they are simply added to the same toolbar as the panel anchors. 185 * 186 * <toolbar id="nav-bar"> 187 * <toolbarbutton/> -> gPanelAnchors[panelIndex] 188 * <panel> -> gPanels[panelIndex] 189 * <panelmultiview/> -> gPanelMultiViews[panelIndex] 190 * </panel> 191 * <panelview> -> gPanelViews[viewIndex] 192 * <label/> -> gPanelViewLabels[viewIndex] 193 * </panelview> 194 * </toolbar> 195 */ 196 add_task(async function test_setup() { 197 let navBar = document.getElementById("nav-bar"); 198 199 for (let i = 0; i < PANELS_COUNT; i++) { 200 gPanelAnchors[i] = document.createXULElement("toolbarbutton"); 201 gPanelAnchors[i].classList.add( 202 "toolbarbutton-1", 203 "chromeclass-toolbar-additional" 204 ); 205 navBar.appendChild(gPanelAnchors[i]); 206 207 gPanels[i] = document.createXULElement("panel"); 208 gPanels[i].id = "panel-" + i; 209 gPanels[i].setAttribute("type", "arrow"); 210 gPanels[i].setAttribute("photon", true); 211 navBar.appendChild(gPanels[i]); 212 213 gPanelMultiViews[i] = document.createXULElement("panelmultiview"); 214 gPanelMultiViews[i].id = "panelmultiview-" + i; 215 gPanels[i].appendChild(gPanelMultiViews[i]); 216 } 217 218 for (let i = 0; i < PANELVIEWS_COUNT; i++) { 219 gPanelViews[i] = document.createXULElement("panelview"); 220 gPanelViews[i].id = "panelview-" + i; 221 navBar.appendChild(gPanelViews[i]); 222 223 gPanelViewLabels[i] = document.createXULElement("label"); 224 gPanelViewLabels[i].setAttribute("value", "PanelView " + i); 225 gPanelViews[i].appendChild(gPanelViewLabels[i]); 226 } 227 228 registerCleanupFunction(() => { 229 [...gPanelAnchors, ...gPanels, ...gPanelViews].forEach(e => e.remove()); 230 }); 231 }); 232 233 /** 234 * Shows and hides all views in a panel with this static structure: 235 * 236 * - Panel 0 237 * - View 0 238 * - View 1 239 * - View 3 240 * - View 2 241 */ 242 add_task(async function test_simple() { 243 // Show main view 0. 244 await openPopup(0, 0); 245 246 // Show and hide subview 1. 247 await showSubView(0, 1); 248 assertLabelVisible(0, false); 249 await goBack(0, 0); 250 assertLabelVisible(1, false); 251 252 // Show subview 3. 253 await showSubView(0, 3); 254 assertLabelVisible(0, false); 255 256 // Show and hide subview 2. 257 await showSubView(0, 2); 258 assertLabelVisible(3, false); 259 await goBack(0, 3); 260 assertLabelVisible(2, false); 261 262 // Hide subview 3. 263 await goBack(0, 0); 264 assertLabelVisible(3, false); 265 266 // Hide main view 0. 267 await hidePopup(0); 268 assertLabelVisible(0, false); 269 }); 270 271 /** 272 * Tests the event sequence in a panel with this static structure: 273 * 274 * - Panel 0 275 * - View 0 276 * - View 1 277 * - View 3 278 * - View 2 279 */ 280 add_task(async function test_simple_event_sequence() { 281 let recordArray = []; 282 recordEvents(gPanels[0], EVENT_TYPES, recordArray); 283 284 await openPopup(0, 0); 285 await showSubView(0, 1); 286 await goBack(0, 0); 287 await showSubView(0, 3); 288 await showSubView(0, 2); 289 await goBack(0, 3); 290 await goBack(0, 0); 291 await hidePopup(0); 292 293 stopRecordingEvents(gPanels[0]); 294 295 Assert.deepEqual(recordArray, [ 296 "panelview-0: ViewShowing", 297 "panelview-0: ViewShown", 298 "panel-0: popupshown", 299 "panelview-1: ViewShowing", 300 "panelview-1: ViewShown", 301 "panelview-1: ViewHiding", 302 "panelview-0: ViewShown", 303 "panelview-3: ViewShowing", 304 "panelview-3: ViewShown", 305 "panelview-2: ViewShowing", 306 "panelview-2: ViewShown", 307 "panelview-2: ViewHiding", 308 "panelview-3: ViewShown", 309 "panelview-3: ViewHiding", 310 "panelview-0: ViewShown", 311 "panelview-0: ViewHiding", 312 "panelmultiview-0: PanelMultiViewHidden", 313 "panel-0: popuphidden", 314 ]); 315 }); 316 317 /** 318 * Tests that further navigation is suppressed until the new view is shown. 319 */ 320 add_task(async function test_navigation_suppression() { 321 await openPopup(0, 0); 322 323 // Test re-entering the "showSubView" method. 324 let promiseShown = BrowserTestUtils.waitForEvent(gPanelViews[1], "ViewShown"); 325 gPanelMultiViews[0].showSubView(gPanelViews[1]); 326 Assert.ok( 327 !PanelView.forNode(gPanelViews[0]).active, 328 "The previous view should become inactive synchronously." 329 ); 330 331 // The following call will have no effect. 332 gPanelMultiViews[0].showSubView(gPanelViews[2]); 333 await promiseShown; 334 335 // Test re-entering the "goBack" method. 336 promiseShown = BrowserTestUtils.waitForEvent(gPanelViews[0], "ViewShown"); 337 gPanelMultiViews[0].goBack(); 338 Assert.ok( 339 !PanelView.forNode(gPanelViews[1]).active, 340 "The previous view should become inactive synchronously." 341 ); 342 343 // The following call will have no effect. 344 gPanelMultiViews[0].goBack(); 345 await promiseShown; 346 347 // Main view 0 should be displayed. 348 assertLabelVisible(0, true); 349 350 await hidePopup(0); 351 }); 352 353 /** 354 * Tests reusing views that are already open in another panel. In this test, the 355 * structure of the first panel will change dynamically: 356 * 357 * - Panel 0 358 * - View 0 359 * - View 1 360 * - Panel 1 361 * - View 1 362 * - View 2 363 * - Panel 0 364 * - View 1 365 * - View 0 366 */ 367 add_task(async function test_switch_event_sequence() { 368 let recordArray = []; 369 recordEvents(gPanels[0], EVENT_TYPES, recordArray); 370 recordEvents(gPanels[1], EVENT_TYPES, recordArray); 371 372 // Show panel 0. 373 await openPopup(0, 0); 374 await showSubView(0, 1); 375 376 // Show panel 1 with the view that is already open and visible in panel 0. 377 // This will close panel 0 automatically. 378 await openPopup(1, 1); 379 await showSubView(1, 2); 380 381 // Show panel 0 with a view that is already open but invisible in panel 1. 382 // This will close panel 1 automatically. 383 await openPopup(0, 1); 384 await showSubView(0, 0); 385 386 // Hide panel 0. 387 await hidePopup(0); 388 389 stopRecordingEvents(gPanels[0]); 390 stopRecordingEvents(gPanels[1]); 391 392 Assert.deepEqual(recordArray, [ 393 "panelview-0: ViewShowing", 394 "panelview-0: ViewShown", 395 "panel-0: popupshown", 396 "panelview-1: ViewShowing", 397 "panelview-1: ViewShown", 398 "panelview-1: ViewHiding", 399 "panelview-0: ViewHiding", 400 "panelmultiview-0: PanelMultiViewHidden", 401 "panel-0: popuphidden", 402 "panelview-1: ViewShowing", 403 "panel-1: popupshown", 404 "panelview-1: ViewShown", 405 "panelview-2: ViewShowing", 406 "panelview-2: ViewShown", 407 "panel-1: popuphidden", 408 "panelview-2: ViewHiding", 409 "panelview-1: ViewHiding", 410 "panelmultiview-1: PanelMultiViewHidden", 411 "panelview-1: ViewShowing", 412 "panelview-1: ViewShown", 413 "panel-0: popupshown", 414 "panelview-0: ViewShowing", 415 "panelview-0: ViewShown", 416 "panelview-0: ViewHiding", 417 "panelview-1: ViewHiding", 418 "panelmultiview-0: PanelMultiViewHidden", 419 "panel-0: popuphidden", 420 ]); 421 }); 422 423 /** 424 * Tests the event sequence when opening the main view is canceled. 425 */ 426 add_task(async function test_cancel_mainview_event_sequence() { 427 let recordArray = []; 428 recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => { 429 if (event.type == "ViewShowing") { 430 event.preventDefault(); 431 } 432 }); 433 434 gPanelMultiViews[0].setAttribute("mainViewId", gPanelViews[0].id); 435 436 let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden"); 437 PanelMultiView.openPopup( 438 gPanels[0], 439 gPanelAnchors[0], 440 "bottomright topright" 441 ); 442 await promiseHidden; 443 444 stopRecordingEvents(gPanels[0]); 445 446 Assert.deepEqual(recordArray, [ 447 "panelview-0: ViewShowing", 448 "panelview-0: ViewHiding", 449 "panelmultiview-0: PanelMultiViewHidden", 450 "panelmultiview-0: popuphidden", 451 ]); 452 }); 453 454 /** 455 * Tests the event sequence when opening a subview is canceled. 456 */ 457 add_task(async function test_cancel_subview_event_sequence() { 458 let recordArray = []; 459 recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => { 460 if ( 461 event.type == "ViewShowing" && 462 event.originalTarget.id == gPanelViews[1].id 463 ) { 464 event.preventDefault(); 465 } 466 }); 467 468 await openPopup(0, 0); 469 470 let promiseHiding = BrowserTestUtils.waitForEvent( 471 gPanelViews[1], 472 "ViewHiding" 473 ); 474 gPanelMultiViews[0].showSubView(gPanelViews[1]); 475 await promiseHiding; 476 477 // Only the subview should have received the hidden event at this point. 478 Assert.deepEqual(recordArray, [ 479 "panelview-0: ViewShowing", 480 "panelview-0: ViewShown", 481 "panel-0: popupshown", 482 "panelview-1: ViewShowing", 483 "panelview-1: ViewHiding", 484 ]); 485 recordArray.length = 0; 486 487 await hidePopup(0); 488 489 stopRecordingEvents(gPanels[0]); 490 491 Assert.deepEqual(recordArray, [ 492 "panelview-0: ViewHiding", 493 "panelmultiview-0: PanelMultiViewHidden", 494 "panel-0: popuphidden", 495 ]); 496 }); 497 498 /** 499 * Tests the event sequence when closing the panel while opening the main view. 500 */ 501 add_task(async function test_close_while_showing_mainview_event_sequence() { 502 let recordArray = []; 503 recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => { 504 if (event.type == "ViewShowing") { 505 PanelMultiView.hidePopup(gPanels[0]); 506 } 507 }); 508 509 gPanelMultiViews[0].setAttribute("mainViewId", gPanelViews[0].id); 510 511 let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden"); 512 let promiseHiding = BrowserTestUtils.waitForEvent( 513 gPanelViews[0], 514 "ViewHiding" 515 ); 516 PanelMultiView.openPopup( 517 gPanels[0], 518 gPanelAnchors[0], 519 "bottomright topright" 520 ); 521 await promiseHiding; 522 await promiseHidden; 523 524 stopRecordingEvents(gPanels[0]); 525 526 Assert.deepEqual(recordArray, [ 527 "panelview-0: ViewShowing", 528 "panelview-0: ViewShowing > panelview-0: ViewHiding", 529 "panelview-0: ViewShowing > panelmultiview-0: PanelMultiViewHidden", 530 "panelview-0: ViewShowing > panelmultiview-0: popuphidden", 531 ]); 532 }); 533 534 /** 535 * Tests the event sequence when closing the panel while opening a subview. 536 */ 537 add_task(async function test_close_while_showing_subview_event_sequence() { 538 let recordArray = []; 539 recordEvents(gPanels[0], EVENT_TYPES, recordArray, event => { 540 if ( 541 event.type == "ViewShowing" && 542 event.originalTarget.id == gPanelViews[1].id 543 ) { 544 PanelMultiView.hidePopup(gPanels[0]); 545 } 546 }); 547 548 await openPopup(0, 0); 549 550 let promiseHidden = BrowserTestUtils.waitForEvent(gPanels[0], "popuphidden"); 551 gPanelMultiViews[0].showSubView(gPanelViews[1]); 552 await promiseHidden; 553 554 stopRecordingEvents(gPanels[0]); 555 556 Assert.deepEqual(recordArray, [ 557 "panelview-0: ViewShowing", 558 "panelview-0: ViewShown", 559 "panel-0: popupshown", 560 "panelview-1: ViewShowing", 561 "panelview-1: ViewShowing > panelview-1: ViewHiding", 562 "panelview-1: ViewShowing > panelview-0: ViewHiding", 563 "panelview-1: ViewShowing > panelmultiview-0: PanelMultiViewHidden", 564 "panelview-1: ViewShowing > panel-0: popuphidden", 565 ]); 566 });