blob: 943f17deb7104d5ced201de502d5b6a76ec2227d [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001/*
2 * Copyright 2015 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17
18void function() {
19
20 /**
21 * Asserts that the displayed dialog is in the center of the screen.
22 *
23 * @param {HTMLDialogElement?} opt_dialog to check, or test default
24 */
25 function checkDialogCenter(opt_dialog) {
26 var d = opt_dialog || dialog;
27 var expectedTop = (window.innerHeight - d.offsetHeight) / 2;
28 var expectedLeft = (window.innerWidth - d.offsetWidth) / 2;
29 var rect = d.getBoundingClientRect();
30 assert.closeTo(rect.top, expectedTop, 1, 'top should be nearby');
31 assert.closeTo(rect.left, expectedLeft, 1, 'left should be nearby');
32 }
33
34 /**
35 * Creates a fake KeyboardEvent.
36 *
37 * @param {number} keyCode to press
38 * @param {string?} opt_type to use, default keydown
39 * @return {!Event} event
40 */
41 function createKeyboardEvent(keyCode, opt_type) {
42 var ev = document.createEvent('Events');
43 ev.initEvent(opt_type || 'keydown', true, true);
44 ev.keyCode = keyCode;
45 ev.which = keyCode;
46 return ev;
47 }
48
49 /**
50 * Cleans up any passed DOM elements.
51 *
52 * @param {!Element} el to clean up
53 * @return {!Element} the same element, for chaining
54 */
55 var cleanup = (function() {
56 var e = [];
57 teardown(function() {
58 e.forEach(function(el) {
59 try {
60 el.close(); // try to close dialogs
61 } catch (e) {}
62 el.parentElement && el.parentElement.removeChild(el);
63 });
64 e = [];
65 });
66
67 return function(el) {
68 e.push(el);
69 return el;
70 };
71 })();
72
73 /**
74 * Creates a dialog for testing that will be cleaned up later.
75 *
76 * @param {string?} opt_content to be used as innerHTML
77 */
78 function createDialog(opt_content) {
79 var dialog = document.createElement('dialog');
80 dialog.innerHTML = opt_content || 'Dialog #' + (cleanup.length);
81 document.body.appendChild(dialog);
82 if (window.location.search == '?force') {
83 dialogPolyfill.forceRegisterDialog(dialog);
84 } else {
85 dialogPolyfill.registerDialog(dialog);
86 }
87 return cleanup(dialog);
88 }
89
90 var dialog; // global dialog for all tests
91 setup(function() {
92 dialog = createDialog('Default Dialog');
93 });
94
95 suite('basic', function() {
96 test('show and close', function() {
97 assert.isFalse(dialog.hasAttribute('open'));
98 dialog.show();
99 assert.isTrue(dialog.hasAttribute('open'));
100 assert.isTrue(dialog.open);
101
102 var returnValue = 1234;
103 dialog.close(returnValue);
104 assert.isFalse(dialog.hasAttribute('open'));
105 assert.equal(dialog.returnValue, returnValue);
106
107 dialog.show();
108 dialog.close();
109 assert.isFalse(dialog.open);
110 assert.equal(dialog.returnValue, returnValue);
111 });
112 test('open property', function() {
113 assert.isFalse(dialog.hasAttribute('open'));
114 dialog.show();
115 assert.isTrue(dialog.hasAttribute('open'));
116 assert.isTrue(dialog.open);
117
118 dialog.open = false;
119 assert.isFalse(dialog.open);
120 assert.isFalse(dialog.hasAttribute('open'),
121 'open property should clear attribute');
122 assert.throws(dialog.close);
123
124 var overlay = document.querySelector('._dialog_overlay');
125 assert.isNull(overlay);
126 });
127 test('show/showModal interaction', function() {
128 assert.isFalse(dialog.hasAttribute('open'));
129 dialog.show();
130
131 // If the native dialog is being tested, show/showModal are not already
132 // bound, so wrap them in helper methods for throws/doesNotThrow.
133 var show = function() { dialog.show(); };
134 var showModal = function() { dialog.showModal(); };
135
136 assert.doesNotThrow(show);
137 assert.throws(showModal);
138
139 dialog.open = false;
140 assert.doesNotThrow(showModal);
141 assert.doesNotThrow(show); // show after showModal does nothing
142 assert.throws(showModal);
143 // TODO: check dialog is still modal
144
145 assert.isTrue(dialog.open);
146 });
147 test('setAttribute reflects property', function() {
148 dialog.setAttribute('open', '');
149 assert.isTrue(dialog.open, 'attribute opens dialog');
150 });
151 test('changing open to dummy value is ignored', function() {
152 dialog.showModal();
153
154 dialog.setAttribute('open', 'dummy, ignored');
155 assert.isTrue(dialog.open, 'dialog open with dummy open value');
156
157 var overlay = document.querySelector('._dialog_overlay');
158 assert(overlay, 'dialog is still modal');
159 });
160 test('show/showModal outside document', function() {
161 dialog.open = false;
162 dialog.parentNode.removeChild(dialog);
163
164 assert.throws(function() { dialog.showModal(); });
165
166 assert.doesNotThrow(function() { dialog.show(); });
167 assert.isTrue(dialog.open, 'can open non-modal outside document');
168 assert.isFalse(document.body.contains(dialog));
169 });
170 test('has a11y property', function() {
171 assert.equal(dialog.getAttribute('role'), 'dialog', 'role should be dialog');
172 });
173 });
174
175 suite('DOM', function() {
176 setup(function(done) {
177 // DOM tests wait for modal to settle, so MutationOberver doesn't coalesce attr changes
178 dialog.showModal();
179 window.setTimeout(done, 0);
180 });
181 test('DOM direct removal', function(done) {
182 assert.isTrue(dialog.open);
183 assert.isNotNull(document.querySelector('.backdrop'));
184
185 var parentNode = dialog.parentNode;
186 parentNode.removeChild(dialog);
187
188 // DOMNodeRemoved defers its task a frame (since it occurs before removal, not after). This
189 // doesn't effect MutationObserver, just delays the test a frame.
190 window.setTimeout(function() {
191 assert.isNull(document.querySelector('.backdrop'), 'dialog removal should clear modal');
192
193 assert.isTrue(dialog.open, 'removed dialog should still be open');
194 parentNode.appendChild(dialog);
195
196 assert.isTrue(dialog.open, 're-added dialog should still be open');
197 assert.isNull(document.querySelector('.backdrop'), 're-add dialog should not be modal');
198
199 done();
200 }, 0);
201 });
202 test('DOM removal inside other element', function(done) {
203 var div = cleanup(document.createElement('div'));
204 document.body.appendChild(div);
205 div.appendChild(dialog);
206
207 document.body.removeChild(div);
208
209 window.setTimeout(function() {
210 assert.isNull(document.querySelector('.backdrop'), 'dialog removal should clear modal');
211 assert.isTrue(dialog.open, 'removed dialog should still be open');
212 done();
213 }, 0);
214 });
215 test('DOM instant remove/add', function(done) {
216 var div = cleanup(document.createElement('div'));
217 document.body.appendChild(div);
218 dialog.parentNode.removeChild(dialog);
219 div.appendChild(dialog);
220
221 window.setTimeout(function() {
222 assert.isNull(document.querySelector('.backdrop'), 'backdrop should disappear');
223 assert.isTrue(dialog.open);
224 done();
225 }, 0);
226 });
227 });
228
229 suite('position', function() {
230 test('non-modal is not centered', function() {
231 var el = cleanup(document.createElement('div'));
232 dialog.parentNode.insertBefore(el, dialog);
233 var testRect = el.getBoundingClientRect();
234
235 dialog.show();
236 var rect = dialog.getBoundingClientRect();
237
238 assert.equal(rect.top, testRect.top, 'dialog should not be centered');
239 });
240 test('default modal centering', function() {
241 dialog.showModal();
242 checkDialogCenter();
243 assert.ok(dialog.style.top, 'expected top to be set');
244 dialog.close();
245 assert.notOk(dialog.style.top, 'expected top to be cleared');
246 });
247 test('modal respects static position', function() {
248 dialog.style.top = '10px';
249 dialog.showModal();
250
251 var rect = dialog.getBoundingClientRect();
252 assert.equal(rect.top, 10);
253 });
254 test('modal recentering', function() {
255 var pX = document.body.scrollLeft;
256 var pY = document.body.scrollTop;
257 var big = cleanup(document.createElement('div'));
258 big.style.height = '200vh'; // 2x view height
259 document.body.appendChild(big);
260
261 try {
262 var scrollValue = 200; // don't use incredibly large values
263 dialog.showModal();
264 dialog.close();
265
266 window.scrollTo(0, scrollValue);
267 dialog.showModal();
268 checkDialogCenter(); // must be centered, even after scroll
269 var rectAtScroll = dialog.getBoundingClientRect();
270
271 // after scroll, we aren't recentered, check offset
272 window.scrollTo(0, 0);
273 var rect = dialog.getBoundingClientRect();
274 assert.closeTo(rectAtScroll.top + scrollValue, rect.top, 1);
275 } finally {
276 window.scrollTo(pX, pY);
277 }
278 });
279 test('clamped to top of page', function() {
280 var big = cleanup(document.createElement('div'));
281 big.style.height = '200vh'; // 2x view height
282 document.body.appendChild(big);
283 document.documentElement.scrollTop = document.documentElement.scrollHeight / 2;
284
285 dialog.style.height = document.documentElement.scrollHeight + 200 + 'px';
286 dialog.showModal();
287
288 var visibleRect = dialog.getBoundingClientRect();
289 assert.equal(visibleRect.top, 0, 'large dialog should be visible at top of page');
290
291 var style = window.getComputedStyle(dialog);
292 assert.equal(style.top, document.documentElement.scrollTop + 'px',
293 'large dialog should be absolutely positioned at scroll top');
294 });
295 });
296
297 suite('backdrop', function() {
298 test('backdrop div on modal', function() {
299 dialog.showModal();
300 var foundBackdrop = document.querySelector('.backdrop');
301 assert.isNotNull(foundBackdrop);
302
303 var sibling = dialog.nextElementSibling;
304 assert.strictEqual(foundBackdrop, sibling);
305 });
306 test('no backdrop on non-modal', function() {
307 dialog.show();
308 assert.isNull(document.querySelector('.backdrop'));
309 dialog.close();
310 });
311 test('backdrop click appears as dialog', function() {
312 dialog.showModal();
313 var backdrop = dialog.nextElementSibling;
314
315 var clickFired = 0;
316 var helper = function(ev) {
317 assert.equal(ev.target, dialog);
318 ++clickFired;
319 };
320
321 dialog.addEventListener('click', helper)
322 backdrop.click();
323 assert.equal(clickFired, 1);
324 });
325 test('backdrop click focuses dialog', function() {
326 dialog.showModal();
327 dialog.tabIndex = 0;
328
329 var input = document.createElement('input');
330 input.type = 'text';
331 dialog.appendChild(input);
332
333 // TODO: It would be nice to check `input` instead here, but there's no more reliable ways
334 // to emulate a browser tab event (Firefox, Chrome etc have made it a security violation).
335
336 var backdrop = dialog.nextElementSibling;
337 backdrop.click();
338 assert.equal(document.activeElement, dialog);
339 });
340 });
341
342 suite('form focus', function() {
343 test('non-modal inside modal is focusable', function() {
344 var sub = createDialog();
345 dialog.appendChild(sub);
346
347 var input = document.createElement('input');
348 input.type = 'text';
349 sub.appendChild(input);
350
351 dialog.showModal();
352 sub.show();
353
354 input.focus();
355 assert.equal(input, document.activeElement);
356 });
357 test('clear focus when nothing focusable in modal', function() {
358 var input = cleanup(document.createElement('input'));
359 input.type = 'text';
360 document.body.appendChild(input);
361 input.focus();
362
363 var previous = document.activeElement;
364 dialog.showModal();
365 assert.notEqual(previous, document.activeElement);
366 });
367 test('default focus on modal', function() {
368 var input = cleanup(document.createElement('input'));
369 input.type = 'text';
370 dialog.appendChild(input);
371
372 var anotherInput = cleanup(document.createElement('input'));
373 anotherInput.type = 'text';
374 dialog.appendChild(anotherInput);
375
376 dialog.showModal();
377 assert.equal(document.activeElement, input);
378 });
379 test('default focus on non-modal', function() {
380 var div = cleanup(document.createElement('div'));
381 div.tabIndex = 4;
382 dialog.appendChild(div);
383
384 dialog.show();
385 assert.equal(document.activeElement, div);
386 });
387 test('autofocus element chosen', function() {
388 var input = cleanup(document.createElement('input'));
389 input.type = 'text';
390 dialog.appendChild(input);
391
392 var inputAF = cleanup(document.createElement('input'));
393 inputAF.type = 'text';
394 inputAF.autofocus = true;
395 dialog.appendChild(inputAF);
396
397 dialog.showModal();
398 assert.equal(document.activeElement, inputAF);
399 });
400 test('child modal dialog', function() {
401 dialog.showModal();
402
403 var input = cleanup(document.createElement('input'));
404 input.type = 'text';
405 dialog.appendChild(input);
406 input.focus();
407 assert.equal(document.activeElement, input);
408
409 // NOTE: This is a single sub-test, but all the above tests could be run
410 // again in a sub-context (i.e., dialog within dialog).
411 var child = createDialog();
412 child.showModal();
413 assert.notEqual(document.activeElement, input,
414 'additional modal dialog should clear parent focus');
415
416 child.close();
417 assert.notEqual(document.activeElement, input,
418 'parent focus should not be restored');
419 });
420 test('don\'t scroll anything into focus', function() {
421 // https://github.com/GoogleChrome/dialog-polyfill/issues/119
422
423 var div = cleanup(document.createElement('div'));
424 document.body.appendChild(div);
425
426 var inner = document.createElement('div');
427 inner.style.height = '10000px';
428 div.appendChild(inner);
429
430 div.appendChild(dialog);
431
432 var input = cleanup(document.createElement('input'));
433 input.type = 'text';
434 dialog.appendChild(input);
435
436 var prev = document.documentElement.scrollTop;
437 dialog.showModal();
438 assert.equal(document.documentElement.scrollTop, prev);
439 });
440 });
441
442 suite('top layer / inert', function() {
443 test('background focus allowed on non-modal', function() {
444 var input = cleanup(document.createElement('input'));
445 input.type = 'text';
446 document.body.appendChild(input);
447 input.focus();
448
449 dialog.show();
450 assert.notEqual(document.activeElement, input,
451 'non-modal dialog should clear focus, even with no dialog content');
452
453 document.body.focus();
454 input.focus();
455 assert.equal(document.activeElement, input,
456 'non-modal should allow background focus');
457 });
458 test('modal disallows background focus', function() {
459 var input = cleanup(document.createElement('input'));
460 input.type = 'text';
461 document.body.appendChild(input);
462
463 dialog.showModal();
464 input.focus();
465
466 if (!document.hasFocus()) {
467 // Browsers won't trigger a focus event if they're not in the
468 // foreground, so we can't intercept it. However, they'll fire one when
469 // restored, before a user can get to any incorrectly focused element.
470 console.warn('background focus test requires document focus');
471 document.documentElement.focus();
472 }
473 assert.notEqual(document.activeElement, input,
474 'modal should disallow background focus');
475 });
476 test('overlay is a sibling of topmost dialog', function() {
477 var stacking = cleanup(document.createElement('div'));
478 stacking.style.opacity = 0.8; // creates stacking context
479 document.body.appendChild(stacking);
480 stacking.appendChild(dialog);
481 dialog.showModal();
482
483 var overlay = document.querySelector('._dialog_overlay');
484 assert.isNotNull(overlay);
485 assert.equal(overlay.parentNode, dialog.parentNode);
486 });
487 test('overlay is between topmost and remaining dialogs', function() {
488 dialog.showModal();
489
490 var other = cleanup(createDialog());
491 document.body.appendChild(other);
492 other.showModal();
493
494 var overlay = document.querySelector('._dialog_overlay');
495 assert.isNotNull(overlay);
496 assert.equal(overlay.parentNode, other.parentNode);
497
498 assert.isAbove(+other.style.zIndex, +overlay.style.zIndex, 'top-most dialog above overlay');
499 assert.isAbove(+overlay.style.zIndex, +dialog.style.zIndex, 'overlay above other dialogs');
500 });
501 });
502
503 suite('events', function() {
504 test('close event', function() {
505 var closeFired = 0;
506 dialog.addEventListener('close', function() {
507 ++closeFired;
508 });
509
510 dialog.show();
511 assert.equal(closeFired, 0);
512
513 dialog.close();
514 assert.equal(closeFired, 1);
515
516 assert.throws(dialog.close); // can't close already closed dialog
517 assert.equal(closeFired, 1);
518
519 dialog.showModal();
520 dialog.close();
521 assert.equal(closeFired, 2);
522 });
523 test('cancel event', function() {
524 dialog.showModal();
525 dialog.dispatchEvent(createKeyboardEvent(27));
526 assert.isFalse(dialog.open, 'esc should close modal');
527
528 var cancelFired = 0;
529 dialog.addEventListener('cancel', function() {
530 ++cancelFired;
531 });
532 dialog.showModal();
533 dialog.dispatchEvent(createKeyboardEvent(27));
534 assert.equal(cancelFired, 1, 'expected cancel to be fired');
535 assert.isFalse(dialog.open), 'esc should close modal again';
536
537 // Sanity-check that non-modals aren't effected.
538 dialog.show();
539 dialog.dispatchEvent(createKeyboardEvent(27));
540 assert.isTrue(dialog.open, 'esc should only close modal dialog');
541 assert.equal(cancelFired, 1);
542 });
543 test('overlay click is prevented', function() {
544 dialog.showModal();
545
546 var overlay = document.querySelector('._dialog_overlay');
547 assert.isNotNull(overlay);
548
549 var helper = function(ev) {
550 throw Error('body should not be clicked');
551 };
552 try {
553 document.body.addEventListener('click', helper);
554 overlay.click();
555 } finally {
556 document.body.removeEventListener('click', helper);
557 }
558 });
559 });
560
561 suite('form', function() {
562 test('method attribute is translated to property', function() {
563 var form = document.createElement('form');
564 form.method = 'dialog';
565 assert.equal(form.method, 'dialog');
566
567 form.method = 'PoSt';
568 assert.equal(form.method, 'post');
569 assert.equal(form.getAttribute('method'), 'PoSt');
570 });
571 test('dialog method input', function() {
572 var value = 'ExpectedValue' + Math.random();
573
574 var form = document.createElement('form');
575 try {
576 form.method = 'dialog';
577 } catch (e) {
578 // Setting the method directly throws an exception in <=IE9.
579 form.setAttribute('method', 'dialog');
580 }
581 dialog.appendChild(form);
582
583 var input = document.createElement('input');
584 input.type = 'submit';
585 input.value = value;
586 form.appendChild(input);
587
588 var closeCount = 0;
589 dialog.addEventListener('close', function() {
590 ++closeCount;
591 });
592
593 dialog.show();
594 input.focus(); // emulate user focus action
595 input.click();
596
597 assert.isFalse(dialog.open);
598 assert.equal(dialog.returnValue, value);
599 assert.equal(closeCount, 1);
600 });
601 test('dialog with button preventDefault does not trigger submit', function() {
602 var form = document.createElement('form');
603 form.setAttribute('method', 'dialog');
604 dialog.appendChild(form);
605
606 var button = document.createElement('button');
607 button.value = 'does not matter';
608 form.appendChild(button);
609 button.addEventListener('click', function(ev) {
610 ev.preventDefault();
611 });
612
613 dialog.showModal();
614 button.click();
615
616 assert.isTrue(dialog.open, 'dialog should remain open');
617 assert.equal(dialog.returnValue, '');
618 });
619 test('dialog programmatic submit does not change returnValue', function() {
620 var form = document.createElement('form');
621 form.setAttribute('method', 'dialog');
622
623 dialog.returnValue = 'manually set'; // set before appending
624 dialog.appendChild(form);
625
626 dialog.showModal();
627 form.submit();
628 assert.isFalse(dialog.open);
629
630 assert.equal(dialog.returnValue, 'manually set', 'returnValue should not change');
631 });
632 test('dialog method button', function() {
633 var value = 'ExpectedValue' + Math.random();
634
635 var form = document.createElement('form');
636 form.setAttribute('method', 'dialog');
637 dialog.appendChild(form);
638
639 var button = document.createElement('button');
640 button.value = value;
641 form.appendChild(button);
642
643 dialog.showModal();
644 button.focus(); // emulate user focus action
645 button.click();
646
647 assert.isFalse(dialog.open);
648 assert.equal(dialog.returnValue, value);
649
650 // Clear button value, confirm textContent is not used as value.
651 button.value = 'blah blah';
652 button.removeAttribute('value');
653 button.textContent = value;
654 dialog.show();
655 button.focus(); // emulate user focus action
656 button.click();
657
658 assert.equal(dialog.returnValue, button.value,
659 'don\'t take button textContent as value');
660 });
661 test('boring form inside dialog', function() {
662 var form = document.createElement('form');
663 dialog.appendChild(form); // don't specify method
664 form.addEventListener('submit', function(ev) {
665 ev.preventDefault();
666 });
667
668 var button = document.createElement('button');
669 button.value = 'Moot';
670 form.appendChild(button);
671
672 dialog.showModal();
673 button.focus(); // emulate user focus action
674 button.click();
675
676 assert.isTrue(dialog.open, 'non-dialog form should not close dialog')
677 assert(!dialog.returnValue);
678 });
679 test('type="image" submitter', function() {
680 var form = document.createElement('form');
681 form.setAttribute('method', 'dialog');
682 dialog.appendChild(form);
683 dialog.show();
684
685 var image = document.createElement('input');
686 image.type = 'image';
687 image.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
688 image.setAttribute('value', 'image should not accept value');
689 form.appendChild(image);
690 image.click();
691
692 assert.notEqual(image.getAttribute('value'), dialog.returnValue);
693 assert.equal(dialog.returnValue, '0,0');
694 });
695 test('form submitter across dialogs', function() {
696 var form1 = document.createElement('form');
697 form1.setAttribute('method', 'dialog');
698 dialog.appendChild(form1);
699
700 var button1 = document.createElement('button');
701 button1.value = 'from form1: first value';
702 form1.appendChild(button1);
703 dialog.showModal();
704
705 var dialog2 = createDialog();
706 dialog2.returnValue = 'dialog2 default close value';
707 var form2 = document.createElement('form');
708 form2.setAttribute('method', 'dialog');
709 dialog2.appendChild(form2);
710 dialog2.showModal();
711
712 button1.click();
713 assert.isFalse(dialog.open);
714
715 // nb. this never fires 'submit' so the .returnValue can't be wrong: is there another way
716 // to submit a form that doesn't involve a click (enter implicitly 'clicks') or submit?
717 form2.submit();
718 assert.isFalse(dialog2.open);
719
720 assert.equal(dialog2.returnValue, 'dialog2 default close value',
721 'second dialog shouldn\'t reuse formSubmitter');
722 });
723 });
724
725 suite('order', function() {
726 test('non-modal unchanged', function() {
727 var one = createDialog();
728 var two = createDialog();
729
730 one.style.zIndex = 100;
731 two.style.zIndex = 200;
732 one.show();
733 two.show();
734
735 assert.equal(window.getComputedStyle(one).zIndex, 100);
736 assert.equal(window.getComputedStyle(two).zIndex, 200);
737
738 two.close();
739 assert.equal(window.getComputedStyle(two).zIndex, 200);
740 });
741 test('modal stacking order', function() {
742 dialog.showModal();
743
744 // Create incorrectly-named dialogs: front has a lower z-index, and back
745 // has a higher z-index.
746 var front = createDialog();
747 var back = createDialog();
748 front.style.zIndex = 100;
749 back.style.zIndex = 200;
750
751 // Show back first, then front. Thus we expect back to be behind front.
752 back.showModal();
753 front.showModal();
754
755 var zf = +window.getComputedStyle(front).zIndex;
756 var zb = +window.getComputedStyle(back).zIndex;
757 assert.isAbove(zf, zb, 'showModal order dictates z-index');
758
759 var backBackdrop = back.nextElementSibling;
760 var zbb = +window.getComputedStyle(backBackdrop).zIndex;
761 assert.equal(backBackdrop.className, 'backdrop');
762 assert.isBelow(zbb, zb, 'backdrop below dialog');
763
764 var frontBackdrop = front.nextElementSibling;
765 var zfb = +window.getComputedStyle(frontBackdrop).zIndex
766 assert.equal(frontBackdrop.className, 'backdrop');
767 assert.isBelow(zfb, zf,' backdrop below dialog');
768
769 assert.isAbove(zfb, zb, 'front backdrop is above back dialog');
770
771 front.close();
772 assert.notOk(front.style.zIndex, 'modal close should clear zindex');
773 });
774 });
775
776 suite('press tab key', function() {
777 test('tab key', function() {
778 var dialog = createDialog();
779 dialog.showModal();
780
781 document.documentElement.dispatchEvent(createKeyboardEvent(9));
782
783 var ev = document.createEvent('Events');
784 ev.initEvent('focus', true, true);
785 document.documentElement.dispatchEvent(ev);
786
787 dialog.close();
788 });
789 });
790}();