on-dialog-behavior.html (24985B)
1 <!doctype html> 2 <meta charset="utf-8" /> 3 <meta name="author" title="Keith Cirkel" href="mailto:wpt@keithcirkel.co.uk" /> 4 <meta name="timeout" content="long"> 5 <link rel="help" href="https://open-ui.org/components/invokers.explainer/" /> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="resources/invoker-utils.js"></script> 9 10 <dialog id="invokee"> 11 <button id="containedinvoker" commandfor="invokee" command="close"></button> 12 </dialog> 13 <button id="invokerbutton" commandfor="invokee" command="show-modal"></button> 14 15 <script> 16 function resetState() { 17 invokee.close(); 18 try { invokee.hidePopover(); } catch {} 19 invokee.removeAttribute("popover"); 20 invokee.returnValue = ''; 21 invokerbutton.setAttribute("command", "show-modal"); 22 containedinvoker.setAttribute("command", "close"); 23 containedinvoker.removeAttribute("value"); 24 } 25 26 // opening a dialog 27 28 ["show-modal", /* test case sensitivity */ "sHoW-mOdAl"].forEach( 29 (command) => { 30 ["property", "attribute"].forEach((setType) => { 31 test( 32 function (t) { 33 t.add_cleanup(resetState); 34 assert_false(invokee.open, "invokee.open"); 35 assert_false(invokee.matches(":modal"), "invokee :modal"); 36 if (setType === "property") { 37 invokerbutton.command = command; 38 } else { 39 invokerbutton.setAttribute("command", command); 40 } 41 invokerbutton.click(); 42 assert_true(invokee.open, "invokee.open"); 43 assert_true(invokee.matches(":modal"), "invokee :modal"); 44 }, 45 `invoking (with command ${setType} as ${command}) closed dialog opens as modal`, 46 ); 47 48 test( 49 function (t) { 50 t.add_cleanup(resetState); 51 assert_false(invokee.open, "invokee.open"); 52 assert_false(invokee.matches(":modal"), "invokee :modal"); 53 invokee.addEventListener("command", (e) => e.preventDefault(), { 54 once: true, 55 }); 56 if (setType === "property") { 57 invokerbutton.command = command; 58 } else { 59 invokerbutton.setAttribute("command", command); 60 } 61 invokerbutton.click(); 62 assert_false(invokee.open, "invokee.open"); 63 assert_false(invokee.matches(":modal"), "invokee :modal"); 64 }, 65 `invoking (with command ${setType} as ${command}) closed dialog with preventDefault is noop`, 66 ); 67 68 test( 69 function (t) { 70 t.add_cleanup(resetState); 71 assert_false(invokee.open, "invokee.open"); 72 assert_false(invokee.matches(":modal"), "invokee :modal"); 73 invokee.addEventListener( 74 "command", 75 (e) => { 76 invokerbutton.setAttribute("command", "close"); 77 }, 78 { once: true }, 79 ); 80 if (setType === "property") { 81 invokerbutton.command = command; 82 } else { 83 invokerbutton.setAttribute("command", command); 84 } 85 invokerbutton.click(); 86 assert_true(invokee.open, "invokee.open"); 87 assert_true(invokee.matches(":modal"), "invokee :modal"); 88 }, 89 `invoking (with command ${setType} as ${command}) while changing command still opens as modal`, 90 ); 91 }); 92 }, 93 ); 94 95 // closing an already open dialog 96 97 ["close", /* test case sensitivity */ "cLoSe"].forEach((command) => { 98 ["property", "attribute"].forEach((setType) => { 99 test( 100 function (t) { 101 t.add_cleanup(resetState); 102 invokee.show(); 103 assert_true(invokee.open, "invokee.open"); 104 assert_false(invokee.matches(":modal"), "invokee :modal"); 105 if (setType === "property") { 106 containedinvoker.command = command; 107 } else { 108 containedinvoker.setAttribute("command", command); 109 } 110 containedinvoker.click(); 111 assert_equals(invokee.returnValue, ""); 112 assert_false(invokee.open, "invokee.open"); 113 assert_false(invokee.matches(":modal"), "invokee :modal"); 114 }, 115 `invoking to close (with command ${setType} as ${command}) open dialog closes`, 116 ); 117 118 test( 119 function (t) { 120 t.add_cleanup(resetState); 121 invokee.show(); 122 assert_true(invokee.open, "invokee.open"); 123 assert_false(invokee.matches(":modal"), "invokee :modal"); 124 if (setType === "property") { 125 containedinvoker.command = command; 126 } else { 127 containedinvoker.setAttribute("command", command); 128 } 129 containedinvoker.setAttribute("value", "foo"); 130 containedinvoker.click(); 131 assert_equals(invokee.returnValue, "foo"); 132 assert_false(invokee.open, "invokee.open"); 133 assert_false(invokee.matches(":modal"), "invokee :modal"); 134 }, 135 `invoking to close (with command ${setType} as ${command}) open dialog closes and sets returnValue`, 136 ); 137 138 test( 139 function (t) { 140 t.add_cleanup(resetState); 141 invokee.show(); 142 assert_true(invokee.open, "invokee.open"); 143 assert_false(invokee.matches(":modal"), "invokee :modal"); 144 if (setType === "property") { 145 containedinvoker.command = command; 146 } else { 147 containedinvoker.setAttribute("command", command); 148 } 149 invokee.returnValue = "test"; 150 containedinvoker.setAttribute("value", "foo"); 151 containedinvoker.click(); 152 assert_equals(invokee.returnValue, "foo"); 153 assert_false(invokee.open, "invokee.open"); 154 assert_false(invokee.matches(":modal"), "invokee :modal"); 155 }, 156 `invoking to close (with command ${setType} as ${command}) open dialog closes and overrides returnValue`, 157 ); 158 159 test( 160 function (t) { 161 t.add_cleanup(resetState); 162 invokee.show(); 163 assert_true(invokee.open, "invokee.open"); 164 assert_false(invokee.matches(":modal"), "invokee :modal"); 165 if (setType === "property") { 166 containedinvoker.command = command; 167 } else { 168 containedinvoker.setAttribute("command", command); 169 } 170 invokee.returnValue = "test"; 171 containedinvoker.setAttribute("value", ""); 172 containedinvoker.click(); 173 assert_equals(invokee.returnValue, ""); 174 assert_false(invokee.open, "invokee.open"); 175 assert_false(invokee.matches(":modal"), "invokee :modal"); 176 }, 177 `invoking to close (with command ${setType} as ${command}) open dialog closes and overrides returnValue when empty string`, 178 ); 179 180 test( 181 function (t) { 182 t.add_cleanup(resetState); 183 invokee.show(); 184 assert_true(invokee.open, "invokee.open"); 185 assert_false(invokee.matches(":modal"), "invokee :modal"); 186 if (setType === "property") { 187 containedinvoker.command = command; 188 } else { 189 containedinvoker.setAttribute("command", command); 190 } 191 invokee.returnValue = "test"; 192 containedinvoker.removeAttribute("value"); 193 containedinvoker.click(); 194 assert_equals(invokee.returnValue, "test"); 195 assert_false(invokee.open, "invokee.open"); 196 assert_false(invokee.matches(":modal"), "invokee :modal"); 197 }, 198 `invoking to close (with command ${setType} as ${command}) open dialog closes and doesn't override returnValue when missing value attribute`, 199 ); 200 201 test( 202 function (t) { 203 t.add_cleanup(resetState); 204 invokee.show(); 205 assert_true(invokee.open, "invokee.open"); 206 assert_false(invokee.matches(":modal"), "invokee :modal"); 207 if (typeof command === "string") { 208 if (setType === "property") { 209 containedinvoker.command = command; 210 } else { 211 containedinvoker.setAttribute("command", command); 212 } 213 } 214 invokee.addEventListener("command", (e) => e.preventDefault(), { 215 once: true, 216 }); 217 containedinvoker.click(); 218 assert_true(invokee.open, "invokee.open"); 219 assert_false(invokee.matches(":modal"), "invokee :modal"); 220 }, 221 `invoking to close (with command ${setType} as ${command}) open dialog with preventDefault is no-op`, 222 ); 223 224 test( 225 function (t) { 226 t.add_cleanup(resetState); 227 invokee.showModal(); 228 assert_true(invokee.open, "invokee.open"); 229 assert_true(invokee.matches(":modal"), "invokee :modal"); 230 if (setType === "property") { 231 containedinvoker.command = command; 232 } else { 233 containedinvoker.setAttribute("command", command); 234 } 235 invokee.addEventListener("command", (e) => e.preventDefault(), { 236 once: true, 237 }); 238 containedinvoker.click(); 239 assert_true(invokee.open, "invokee.open"); 240 assert_true(invokee.matches(":modal"), "invokee :modal"); 241 }, 242 `invoking to close (with command ${setType} as ${command}) open modal dialog with preventDefault is no-op`, 243 ); 244 245 test( 246 function (t) { 247 t.add_cleanup(resetState); 248 invokee.show(); 249 assert_true(invokee.open, "invokee.open"); 250 assert_false(invokee.matches(":modal"), "invokee :modal"); 251 if (setType === "property") { 252 containedinvoker.command = command; 253 } else { 254 containedinvoker.setAttribute("command", command); 255 } 256 invokee.addEventListener( 257 "command", 258 (e) => { 259 containedinvoker.setAttribute("command", "show"); 260 }, 261 { once: true }, 262 ); 263 containedinvoker.click(); 264 assert_false(invokee.open, "invokee.open"); 265 assert_false(invokee.matches(":modal"), "invokee :modal"); 266 }, 267 `invoking to close (with command ${setType} as ${command}) open dialog while changing command still closes`, 268 ); 269 270 test( 271 function (t) { 272 t.add_cleanup(resetState); 273 invokee.showModal(); 274 assert_true(invokee.open, "invokee.open"); 275 assert_true(invokee.matches(":modal"), "invokee :modal"); 276 if (setType === "property") { 277 containedinvoker.command = command; 278 } else { 279 containedinvoker.setAttribute("command", command); 280 } 281 invokee.addEventListener( 282 "command", 283 (e) => { 284 containedinvoker.setAttribute("command", "show"); 285 }, 286 { once: true }, 287 ); 288 containedinvoker.click(); 289 assert_false(invokee.open, "invokee.open"); 290 assert_false(invokee.matches(":modal"), "invokee :modal"); 291 }, 292 `invoking to close (with command ${setType} as ${command}) open modal dialog while changing command still closes`, 293 ); 294 }); 295 }); 296 297 // request to close an already open dialog 298 299 ["request-close", /* test case sensitivity */ "reQuEst-Close"].forEach((command) => { 300 ["property", "attribute"].forEach((setType) => { 301 test( 302 function (t) { 303 t.add_cleanup(resetState); 304 invokee.show(); 305 assert_true(invokee.open, "invokee.open"); 306 assert_false(invokee.matches(":modal"), "invokee :modal"); 307 if (setType === "property") { 308 containedinvoker.command = command; 309 } else { 310 containedinvoker.setAttribute("command", command); 311 } 312 containedinvoker.click(); 313 assert_equals(invokee.returnValue, ""); 314 assert_false(invokee.open, "invokee.open"); 315 assert_false(invokee.matches(":modal"), "invokee :modal"); 316 }, 317 `invoking to request-close (with command ${setType} as ${command}) open dialog closes`, 318 ); 319 320 test( 321 function (t) { 322 t.add_cleanup(resetState); 323 invokee.show(); 324 assert_true(invokee.open, "invokee.open"); 325 assert_false(invokee.matches(":modal"), "invokee :modal"); 326 if (setType === "property") { 327 containedinvoker.command = command; 328 } else { 329 containedinvoker.setAttribute("command", command); 330 } 331 containedinvoker.setAttribute("value", "foo"); 332 containedinvoker.click(); 333 assert_equals(invokee.returnValue, "foo"); 334 assert_false(invokee.open, "invokee.open"); 335 assert_false(invokee.matches(":modal"), "invokee :modal"); 336 }, 337 `invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and sets returnValue`, 338 ); 339 340 test( 341 function (t) { 342 t.add_cleanup(resetState); 343 invokee.show(); 344 assert_true(invokee.open, "invokee.open"); 345 assert_false(invokee.matches(":modal"), "invokee :modal"); 346 if (setType === "property") { 347 containedinvoker.command = command; 348 } else { 349 containedinvoker.setAttribute("command", command); 350 } 351 invokee.returnValue = "test"; 352 containedinvoker.setAttribute("value", "foo"); 353 containedinvoker.click(); 354 assert_equals(invokee.returnValue, "foo"); 355 assert_false(invokee.open, "invokee.open"); 356 assert_false(invokee.matches(":modal"), "invokee :modal"); 357 }, 358 `invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and overrides returnValue`, 359 ); 360 361 test( 362 function (t) { 363 t.add_cleanup(resetState); 364 invokee.show(); 365 assert_true(invokee.open, "invokee.open"); 366 assert_false(invokee.matches(":modal"), "invokee :modal"); 367 if (setType === "property") { 368 containedinvoker.command = command; 369 } else { 370 containedinvoker.setAttribute("command", command); 371 } 372 invokee.returnValue = "test"; 373 containedinvoker.setAttribute("value", ""); 374 containedinvoker.click(); 375 assert_equals(invokee.returnValue, ""); 376 assert_false(invokee.open, "invokee.open"); 377 assert_false(invokee.matches(":modal"), "invokee :modal"); 378 }, 379 `invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and overrides returnValue when empty string`, 380 ); 381 382 test( 383 function (t) { 384 t.add_cleanup(resetState); 385 invokee.show(); 386 assert_true(invokee.open, "invokee.open"); 387 assert_false(invokee.matches(":modal"), "invokee :modal"); 388 if (setType === "property") { 389 containedinvoker.command = command; 390 } else { 391 containedinvoker.setAttribute("command", command); 392 } 393 invokee.returnValue = "test"; 394 containedinvoker.removeAttribute("value"); 395 containedinvoker.click(); 396 assert_equals(invokee.returnValue, "test"); 397 assert_false(invokee.open, "invokee.open"); 398 assert_false(invokee.matches(":modal"), "invokee :modal"); 399 }, 400 `invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and doesn't override returnValue when missing value attribute`, 401 ); 402 403 test( 404 function (t) { 405 t.add_cleanup(resetState); 406 invokee.show(); 407 assert_true(invokee.open, "invokee.open"); 408 assert_false(invokee.matches(":modal"), "invokee :modal"); 409 if (typeof command === "string") { 410 if (setType === "property") { 411 containedinvoker.command = command; 412 } else { 413 containedinvoker.setAttribute("command", command); 414 } 415 } 416 invokee.addEventListener("command", (e) => e.preventDefault(), { 417 once: true, 418 }); 419 containedinvoker.click(); 420 assert_true(invokee.open, "invokee.open"); 421 assert_false(invokee.matches(":modal"), "invokee :modal"); 422 }, 423 `invoking to request-close (with command ${setType} as ${command}) open dialog with preventDefault is no-op`, 424 ); 425 426 test( 427 function (t) { 428 t.add_cleanup(resetState); 429 invokee.showModal(); 430 assert_true(invokee.open, "invokee.open"); 431 assert_true(invokee.matches(":modal"), "invokee :modal"); 432 if (setType === "property") { 433 containedinvoker.command = command; 434 } else { 435 containedinvoker.setAttribute("command", command); 436 } 437 invokee.addEventListener("command", (e) => e.preventDefault(), { 438 once: true, 439 }); 440 containedinvoker.click(); 441 assert_true(invokee.open, "invokee.open"); 442 assert_true(invokee.matches(":modal"), "invokee :modal"); 443 }, 444 `invoking to request-close (with command ${setType} as ${command}) open modal dialog with preventDefault is no-op`, 445 ); 446 447 test( 448 function (t) { 449 t.add_cleanup(resetState); 450 invokee.show(); 451 assert_true(invokee.open, "invokee.open"); 452 assert_false(invokee.matches(":modal"), "invokee :modal"); 453 if (setType === "property") { 454 containedinvoker.command = command; 455 } else { 456 containedinvoker.setAttribute("command", command); 457 } 458 invokee.addEventListener( 459 "command", 460 (e) => { 461 containedinvoker.setAttribute("command", "show"); 462 }, 463 { once: true }, 464 ); 465 containedinvoker.click(); 466 assert_false(invokee.open, "invokee.open"); 467 assert_false(invokee.matches(":modal"), "invokee :modal"); 468 }, 469 `invoking to request-close (with command ${setType} as ${command}) open dialog while changing command still closes`, 470 ); 471 472 test( 473 function (t) { 474 t.add_cleanup(resetState); 475 invokee.showModal(); 476 assert_true(invokee.open, "invokee.open"); 477 assert_true(invokee.matches(":modal"), "invokee :modal"); 478 if (setType === "property") { 479 containedinvoker.command = command; 480 } else { 481 containedinvoker.setAttribute("command", command); 482 } 483 invokee.addEventListener( 484 "command", 485 (e) => { 486 containedinvoker.setAttribute("command", "show"); 487 }, 488 { once: true }, 489 ); 490 containedinvoker.click(); 491 assert_false(invokee.open, "invokee.open"); 492 assert_false(invokee.matches(":modal"), "invokee :modal"); 493 }, 494 `invoking to request-close (with command ${setType} as ${command}) open modal dialog while changing command still closes`, 495 ); 496 }); 497 }); 498 499 // show-modal explicit behaviours 500 501 promise_test(async function (t) { 502 t.add_cleanup(resetState); 503 containedinvoker.setAttribute("command", "show-Modal"); 504 invokee.show(); 505 assert_true(invokee.open, "invokee.open"); 506 assert_false(invokee.matches(":modal"), "invokee :modal"); 507 containedinvoker.click(); 508 assert_true(invokee.open, "invokee.open"); 509 assert_false(invokee.matches(":modal"), "invokee :modal"); 510 }, "invoking (as show-modal) open dialog is noop"); 511 512 promise_test(async function (t) { 513 t.add_cleanup(resetState); 514 containedinvoker.setAttribute("command", "show-modal"); 515 invokee.showModal(); 516 assert_true(invokee.open, "invokee.open"); 517 assert_true(invokee.matches(":modal"), "invokee :modal"); 518 invokee.addEventListener( 519 "command", 520 (e) => { 521 containedinvoker.setAttribute("command", "close"); 522 }, 523 { once: true }, 524 ); 525 invokerbutton.click(); 526 assert_true(invokee.open, "invokee.open"); 527 assert_true(invokee.matches(":modal"), "invokee :modal"); 528 }, "invoking (as show-modal) open modal, while changing command still a no-op"); 529 530 promise_test(async function (t) { 531 t.add_cleanup(resetState); 532 invokerbutton.setAttribute("command", "show-modal"); 533 assert_false(invokee.open, "invokee.open"); 534 assert_false(invokee.matches(":modal"), "invokee :modal"); 535 invokee.setAttribute("popover", "auto"); 536 invokerbutton.click(); 537 assert_true(invokee.open, "invokee.open"); 538 assert_true(invokee.matches(":modal"), "invokee :modal"); 539 }, "invoking (as show-modal) closed popover dialog opens as modal"); 540 541 // close explicit behaviours 542 543 promise_test(async function (t) { 544 t.add_cleanup(resetState); 545 invokerbutton.setAttribute("command", "close"); 546 assert_false(invokee.open, "invokee.open"); 547 assert_false(invokee.matches(":modal"), "invokee :modal"); 548 containedinvoker.click(); 549 assert_false(invokee.open, "invokee.open"); 550 assert_false(invokee.matches(":modal"), "invokee :modal"); 551 }, "invoking (as close) already closed dialog is noop"); 552 553 554 // request-close explicit behaviours 555 556 promise_test(async function (t) { 557 t.add_cleanup(resetState); 558 invokerbutton.setAttribute("command", "request-close"); 559 assert_false(invokee.open, "invokee.open"); 560 assert_false(invokee.matches(":modal"), "invokee :modal"); 561 containedinvoker.click(); 562 assert_false(invokee.open, "invokee.open"); 563 assert_false(invokee.matches(":modal"), "invokee :modal"); 564 }, "invoking (as request-close) already closed dialog is noop"); 565 566 // Open Popovers using Dialog actions 567 ["show-modal", "close", "request-close"].forEach((command) => { 568 ["manual", "auto"].forEach((popoverState) => { 569 test( 570 function (t) { 571 t.add_cleanup(resetState); 572 invokee.setAttribute("popover", popoverState); 573 invokee.showPopover(); 574 containedinvoker.setAttribute("command", command); 575 assert_true( 576 invokee.matches(":popover-open"), 577 "invokee :popover-open", 578 ); 579 assert_false(invokee.open, "invokee.open"); 580 assert_false(invokee.matches(":modal"), "invokee :modal"); 581 invokee.addEventListener("command", (e) => e.preventDefault(), { 582 once: true, 583 }); 584 containedinvoker.click(); 585 assert_true( 586 invokee.matches(":popover-open"), 587 "invokee :popover-open", 588 ); 589 assert_false(invokee.open, "invokee.open"); 590 assert_false(invokee.matches(":modal"), "invokee :modal"); 591 }, 592 `invoking (as ${command}) dialog as open popover=${popoverState} is noop`, 593 ); 594 }); 595 }); 596 597 // Elements being disconnected during invoke steps 598 ["show-modal", "close", "request-close"].forEach((command) => { 599 promise_test( 600 async function (t) { 601 t.add_cleanup(() => { 602 document.body.prepend(invokee); 603 resetState(); 604 }); 605 const invokee = document.querySelector("#invokee"); 606 invokerbutton.setAttribute("command", command); 607 assert_false(invokee.open, "invokee.open"); 608 assert_false(invokee.matches(":modal"), "invokee :modal"); 609 invokee.addEventListener( 610 "command", 611 (e) => { 612 invokee.remove(); 613 }, 614 { 615 once: true, 616 }, 617 ); 618 invokerbutton.click(); 619 assert_false(invokee.open, "invokee.open"); 620 assert_false(invokee.matches(":modal"), "invokee :modal"); 621 }, 622 `invoking (as ${command}) dialog that is removed is noop`, 623 ); 624 625 promise_test( 626 async function (t) { 627 const invokerbutton = document.createElement("button"); 628 invokerbutton.commandForElement = invokee; 629 invokerbutton.setAttribute("command", command); 630 assert_false(invokee.open, "invokee.open"); 631 assert_false(invokee.matches(":modal"), "invokee :modal"); 632 invokerbutton.click(); 633 assert_false(invokee.open, "invokee.open"); 634 assert_false(invokee.matches(":modal"), "invokee :modal"); 635 }, 636 `invoking (as ${command}) dialog from a detached invoker`, 637 ); 638 639 promise_test( 640 async function (t) { 641 const invokerbutton = document.createElement("button"); 642 const invokee = document.createElement("dialog"); 643 invokerbutton.commandForElementt = invokee; 644 invokerbutton.setAttribute("command", command); 645 assert_false(invokee.open, "invokee.open"); 646 assert_false(invokee.matches(":modal"), "invokee :modal"); 647 invokerbutton.click(); 648 assert_false(invokee.open, "invokee.open"); 649 assert_false(invokee.matches(":modal"), "invokee :modal"); 650 }, 651 `invoking (as ${command}) detached dialog from a detached invoker`, 652 ); 653 }); 654 </script>