937 строки
30 KiB
JavaScript
937 строки
30 KiB
JavaScript
/*
|
|
* Copyright 2015 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
* use this file except in compliance with the License. You may obtain a copy of
|
|
* the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations under
|
|
* the License.
|
|
*/
|
|
|
|
void function() {
|
|
|
|
/**
|
|
* Asserts that the displayed dialog is in the center of the screen.
|
|
*
|
|
* @param {HTMLDialogElement?} opt_dialog to check, or test default
|
|
*/
|
|
function checkDialogCenter(opt_dialog) {
|
|
var d = opt_dialog || dialog;
|
|
var expectedTop = (window.innerHeight - d.offsetHeight) / 2;
|
|
var expectedLeft = (window.innerWidth - d.offsetWidth) / 2;
|
|
var rect = d.getBoundingClientRect();
|
|
assert.closeTo(rect.top, expectedTop, 1, 'top should be nearby');
|
|
assert.closeTo(rect.left, expectedLeft, 1, 'left should be nearby');
|
|
}
|
|
|
|
/**
|
|
* Creates a fake KeyboardEvent.
|
|
*
|
|
* @param {number} keyCode to press
|
|
* @param {string?} opt_type to use, default keydown
|
|
* @return {!Event} event
|
|
*/
|
|
function createKeyboardEvent(keyCode, opt_type) {
|
|
var ev = document.createEvent('Events');
|
|
ev.initEvent(opt_type || 'keydown', true, true);
|
|
ev.keyCode = keyCode;
|
|
ev.which = keyCode;
|
|
return ev;
|
|
}
|
|
|
|
/**
|
|
* Cleans up any passed DOM elements.
|
|
*
|
|
* @param {!Element} el to clean up
|
|
* @return {!Element} the same element, for chaining
|
|
*/
|
|
var cleanup = (function() {
|
|
var e = [];
|
|
teardown(function() {
|
|
e.forEach(function(el) {
|
|
try {
|
|
el.close(); // try to close dialogs
|
|
} catch (e) {}
|
|
el.parentElement && el.parentElement.removeChild(el);
|
|
});
|
|
e = [];
|
|
});
|
|
|
|
return function(el) {
|
|
e.push(el);
|
|
return el;
|
|
};
|
|
})();
|
|
|
|
/**
|
|
* Creates a dialog for testing that will be cleaned up later.
|
|
*
|
|
* @param {string?} opt_content to be used as innerHTML
|
|
*/
|
|
function createDialog(opt_content) {
|
|
var dialog = document.createElement('dialog');
|
|
dialog.innerHTML = opt_content || 'Dialog #' + (cleanup.length);
|
|
document.body.appendChild(dialog);
|
|
if (window.location.search == '?force') {
|
|
dialogPolyfill.forceRegisterDialog(dialog);
|
|
} else {
|
|
dialogPolyfill.registerDialog(dialog);
|
|
}
|
|
return cleanup(dialog);
|
|
}
|
|
|
|
var dialog; // global dialog for all tests
|
|
setup(function() {
|
|
dialog = createDialog('Default Dialog');
|
|
});
|
|
|
|
suite('basic', function() {
|
|
test('show and close', function() {
|
|
assert.isFalse(dialog.hasAttribute('open'));
|
|
dialog.show();
|
|
assert.isTrue(dialog.hasAttribute('open'));
|
|
assert.isTrue(dialog.open);
|
|
|
|
var returnValue = 1234;
|
|
dialog.close(returnValue);
|
|
assert.isFalse(dialog.hasAttribute('open'));
|
|
assert.equal(dialog.returnValue, returnValue);
|
|
|
|
dialog.show();
|
|
dialog.close();
|
|
assert.isFalse(dialog.open);
|
|
assert.equal(dialog.returnValue, returnValue);
|
|
});
|
|
test('open property', function() {
|
|
assert.isFalse(dialog.hasAttribute('open'));
|
|
dialog.show();
|
|
assert.isTrue(dialog.hasAttribute('open'));
|
|
assert.isTrue(dialog.open);
|
|
|
|
dialog.open = false;
|
|
assert.isFalse(dialog.open);
|
|
assert.isFalse(dialog.hasAttribute('open'),
|
|
'open property should clear attribute');
|
|
assert.throws(dialog.close);
|
|
|
|
var overlay = document.querySelector('._dialog_overlay');
|
|
assert.isNull(overlay);
|
|
});
|
|
test('show/showModal interaction', function() {
|
|
assert.isFalse(dialog.hasAttribute('open'));
|
|
dialog.show();
|
|
|
|
// If the native dialog is being tested, show/showModal are not already
|
|
// bound, so wrap them in helper methods for throws/doesNotThrow.
|
|
var show = function() { dialog.show(); };
|
|
var showModal = function() { dialog.showModal(); };
|
|
|
|
assert.doesNotThrow(show);
|
|
assert.throws(showModal);
|
|
|
|
dialog.open = false;
|
|
assert.doesNotThrow(showModal);
|
|
assert.doesNotThrow(show); // show after showModal does nothing
|
|
assert.throws(showModal);
|
|
// TODO: check dialog is still modal
|
|
|
|
assert.isTrue(dialog.open);
|
|
});
|
|
test('setAttribute reflects property', function() {
|
|
dialog.setAttribute('open', '');
|
|
assert.isTrue(dialog.open, 'attribute opens dialog');
|
|
});
|
|
test('changing open to dummy value is ignored', function() {
|
|
dialog.showModal();
|
|
|
|
dialog.setAttribute('open', 'dummy, ignored');
|
|
assert.isTrue(dialog.open, 'dialog open with dummy open value');
|
|
|
|
var overlay = document.querySelector('._dialog_overlay');
|
|
assert(overlay, 'dialog is still modal');
|
|
});
|
|
test('show/showModal outside document', function() {
|
|
dialog.open = false;
|
|
dialog.parentNode.removeChild(dialog);
|
|
|
|
assert.throws(function() { dialog.showModal(); });
|
|
|
|
assert.doesNotThrow(function() { dialog.show(); });
|
|
assert.isTrue(dialog.open, 'can open non-modal outside document');
|
|
assert.isFalse(document.body.contains(dialog));
|
|
});
|
|
test('has a11y property', function() {
|
|
assert.equal(dialog.getAttribute('role'), 'dialog', 'role should be dialog');
|
|
});
|
|
});
|
|
|
|
suite('DOM', function() {
|
|
setup(function(done) {
|
|
// DOM tests wait for modal to settle, so MutationOberver doesn't coalesce attr changes
|
|
dialog.showModal();
|
|
window.setTimeout(done, 0);
|
|
});
|
|
test('DOM direct removal', function(done) {
|
|
assert.isTrue(dialog.open);
|
|
assert.isNotNull(document.querySelector('.backdrop'));
|
|
|
|
var parentNode = dialog.parentNode;
|
|
parentNode.removeChild(dialog);
|
|
|
|
// DOMNodeRemoved defers its task a frame (since it occurs before removal, not after). This
|
|
// doesn't effect MutationObserver, just delays the test a frame.
|
|
window.setTimeout(function() {
|
|
assert.isNull(document.querySelector('.backdrop'), 'dialog removal should clear modal');
|
|
|
|
assert.isTrue(dialog.open, 'removed dialog should still be open');
|
|
parentNode.appendChild(dialog);
|
|
|
|
assert.isTrue(dialog.open, 're-added dialog should still be open');
|
|
assert.isNull(document.querySelector('.backdrop'), 're-add dialog should not be modal');
|
|
|
|
done();
|
|
}, 0);
|
|
});
|
|
test('DOM removal inside other element', function(done) {
|
|
var div = cleanup(document.createElement('div'));
|
|
document.body.appendChild(div);
|
|
div.appendChild(dialog);
|
|
|
|
document.body.removeChild(div);
|
|
|
|
window.setTimeout(function() {
|
|
assert.isNull(document.querySelector('.backdrop'), 'dialog removal should clear modal');
|
|
assert.isTrue(dialog.open, 'removed dialog should still be open');
|
|
done();
|
|
}, 0);
|
|
});
|
|
test('DOM instant remove/add', function(done) {
|
|
var div = cleanup(document.createElement('div'));
|
|
document.body.appendChild(div);
|
|
dialog.parentNode.removeChild(dialog);
|
|
div.appendChild(dialog);
|
|
|
|
window.setTimeout(function() {
|
|
assert.isNull(document.querySelector('.backdrop'), 'backdrop should disappear');
|
|
assert.isTrue(dialog.open);
|
|
done();
|
|
}, 0);
|
|
});
|
|
});
|
|
|
|
suite('position', function() {
|
|
test('non-modal is not centered', function() {
|
|
var el = cleanup(document.createElement('div'));
|
|
dialog.parentNode.insertBefore(el, dialog);
|
|
var testRect = el.getBoundingClientRect();
|
|
|
|
dialog.show();
|
|
var rect = dialog.getBoundingClientRect();
|
|
|
|
assert.equal(rect.top, testRect.top, 'dialog should not be centered');
|
|
});
|
|
test('default modal centering', function() {
|
|
dialog.showModal();
|
|
checkDialogCenter();
|
|
assert.ok(dialog.style.top, 'expected top to be set');
|
|
dialog.close();
|
|
assert.notOk(dialog.style.top, 'expected top to be cleared');
|
|
});
|
|
test('modal respects static position', function() {
|
|
dialog.style.top = '10px';
|
|
dialog.showModal();
|
|
|
|
var rect = dialog.getBoundingClientRect();
|
|
assert.equal(rect.top, 10);
|
|
});
|
|
test('modal recentering', function() {
|
|
var pX = document.body.scrollLeft;
|
|
var pY = document.body.scrollTop;
|
|
var big = cleanup(document.createElement('div'));
|
|
big.style.height = '200vh'; // 2x view height
|
|
document.body.appendChild(big);
|
|
|
|
try {
|
|
var scrollValue = 200; // don't use incredibly large values
|
|
dialog.showModal();
|
|
dialog.close();
|
|
|
|
window.scrollTo(0, scrollValue);
|
|
dialog.showModal();
|
|
checkDialogCenter(); // must be centered, even after scroll
|
|
var rectAtScroll = dialog.getBoundingClientRect();
|
|
|
|
// after scroll, we aren't recentered, check offset
|
|
window.scrollTo(0, 0);
|
|
var rect = dialog.getBoundingClientRect();
|
|
assert.closeTo(rectAtScroll.top + scrollValue, rect.top, 1);
|
|
} finally {
|
|
window.scrollTo(pX, pY);
|
|
}
|
|
});
|
|
test('clamped to top of page', function() {
|
|
var big = cleanup(document.createElement('div'));
|
|
big.style.height = '200vh'; // 2x view height
|
|
document.body.appendChild(big);
|
|
document.documentElement.scrollTop = document.documentElement.scrollHeight / 2;
|
|
|
|
dialog.style.height = document.documentElement.scrollHeight + 200 + 'px';
|
|
dialog.showModal();
|
|
|
|
var visibleRect = dialog.getBoundingClientRect();
|
|
assert.equal(visibleRect.top, 0, 'large dialog should be visible at top of page');
|
|
|
|
var style = window.getComputedStyle(dialog);
|
|
assert.equal(style.top, document.documentElement.scrollTop + 'px',
|
|
'large dialog should be absolutely positioned at scroll top');
|
|
});
|
|
});
|
|
|
|
suite('backdrop', function() {
|
|
test('backdrop div on modal', function() {
|
|
dialog.showModal();
|
|
var foundBackdrop = document.querySelector('.backdrop');
|
|
assert.isNotNull(foundBackdrop);
|
|
|
|
var sibling = dialog.nextElementSibling;
|
|
assert.strictEqual(foundBackdrop, sibling);
|
|
});
|
|
test('no backdrop on non-modal', function() {
|
|
dialog.show();
|
|
assert.isNull(document.querySelector('.backdrop'));
|
|
dialog.close();
|
|
});
|
|
test('backdrop click appears as dialog', function() {
|
|
dialog.showModal();
|
|
var backdrop = dialog.nextElementSibling;
|
|
|
|
var clickFired = 0;
|
|
var helper = function(ev) {
|
|
assert.equal(ev.target, dialog);
|
|
++clickFired;
|
|
};
|
|
|
|
dialog.addEventListener('click', helper)
|
|
backdrop.click();
|
|
assert.equal(clickFired, 1);
|
|
});
|
|
test('backdrop click focuses dialog', function() {
|
|
dialog.showModal();
|
|
dialog.tabIndex = 0;
|
|
|
|
var input = document.createElement('input');
|
|
input.type = 'text';
|
|
dialog.appendChild(input);
|
|
|
|
// TODO: It would be nice to check `input` instead here, but there's no more reliable ways
|
|
// to emulate a browser tab event (Firefox, Chrome etc have made it a security violation).
|
|
|
|
var backdrop = dialog.nextElementSibling;
|
|
backdrop.click();
|
|
assert.equal(document.activeElement, dialog);
|
|
});
|
|
});
|
|
|
|
suite('form focus', function() {
|
|
test('non-modal inside modal is focusable', function() {
|
|
var sub = createDialog();
|
|
dialog.appendChild(sub);
|
|
|
|
var input = document.createElement('input');
|
|
input.type = 'text';
|
|
sub.appendChild(input);
|
|
|
|
dialog.showModal();
|
|
sub.show();
|
|
|
|
// trick Safari into allowing focus
|
|
input.offsetLeft;
|
|
input.focus();
|
|
input.offsetLeft;
|
|
|
|
assert.equal(input, document.activeElement);
|
|
});
|
|
test('clear focus when nothing focusable in modal', function() {
|
|
var input = cleanup(document.createElement('input'));
|
|
input.type = 'text';
|
|
document.body.appendChild(input);
|
|
input.focus();
|
|
|
|
var previous = document.activeElement;
|
|
dialog.showModal();
|
|
assert.notEqual(previous, document.activeElement);
|
|
});
|
|
test('default focus on modal', function() {
|
|
var input = cleanup(document.createElement('input'));
|
|
input.type = 'text';
|
|
dialog.appendChild(input);
|
|
|
|
var anotherInput = cleanup(document.createElement('input'));
|
|
anotherInput.type = 'text';
|
|
dialog.appendChild(anotherInput);
|
|
|
|
dialog.showModal();
|
|
assert.equal(document.activeElement, input);
|
|
});
|
|
test('default focus on non-modal', function() {
|
|
var div = cleanup(document.createElement('div'));
|
|
div.tabIndex = 4;
|
|
dialog.appendChild(div);
|
|
|
|
dialog.show();
|
|
assert.equal(document.activeElement, div);
|
|
});
|
|
test('autofocus element chosen', function() {
|
|
var input = cleanup(document.createElement('input'));
|
|
input.type = 'text';
|
|
dialog.appendChild(input);
|
|
|
|
var inputAF = cleanup(document.createElement('input'));
|
|
inputAF.type = 'text';
|
|
inputAF.autofocus = true;
|
|
dialog.appendChild(inputAF);
|
|
|
|
dialog.showModal();
|
|
assert.equal(document.activeElement, inputAF);
|
|
});
|
|
test('child modal dialog', function() {
|
|
dialog.showModal();
|
|
|
|
var input = cleanup(document.createElement('input'));
|
|
input.type = 'text';
|
|
dialog.appendChild(input);
|
|
input.focus();
|
|
assert.equal(document.activeElement, input);
|
|
|
|
// NOTE: This is a single sub-test, but all the above tests could be run
|
|
// again in a sub-context (i.e., dialog within dialog).
|
|
var child = createDialog();
|
|
child.showModal();
|
|
assert.notEqual(document.activeElement, input,
|
|
'additional modal dialog should clear parent focus');
|
|
|
|
child.close();
|
|
assert.notEqual(document.activeElement, input,
|
|
'parent focus should not be restored');
|
|
});
|
|
test('don\'t scroll anything into focus', function() {
|
|
// https://github.com/GoogleChrome/dialog-polyfill/issues/119
|
|
|
|
var div = cleanup(document.createElement('div'));
|
|
document.body.appendChild(div);
|
|
|
|
var inner = document.createElement('div');
|
|
inner.style.height = '10000px';
|
|
div.appendChild(inner);
|
|
|
|
div.appendChild(dialog);
|
|
|
|
var input = cleanup(document.createElement('input'));
|
|
input.type = 'text';
|
|
dialog.appendChild(input);
|
|
|
|
var prev = document.documentElement.scrollTop;
|
|
dialog.showModal();
|
|
assert.equal(document.documentElement.scrollTop, prev);
|
|
});
|
|
});
|
|
|
|
suite('top layer / inert', function() {
|
|
test('background focus allowed on non-modal', function() {
|
|
var input = cleanup(document.createElement('input'));
|
|
input.type = 'text';
|
|
document.body.appendChild(input);
|
|
input.focus();
|
|
|
|
dialog.show();
|
|
assert.notEqual(document.activeElement, input,
|
|
'non-modal dialog should clear focus, even with no dialog content');
|
|
|
|
document.body.focus();
|
|
input.focus();
|
|
assert.equal(document.activeElement, input,
|
|
'non-modal should allow background focus');
|
|
});
|
|
test('modal disallows background focus', function() {
|
|
var input = cleanup(document.createElement('input'));
|
|
input.type = 'text';
|
|
document.body.appendChild(input);
|
|
|
|
dialog.showModal();
|
|
input.focus();
|
|
|
|
if (!document.hasFocus()) {
|
|
// Browsers won't trigger a focus event if they're not in the
|
|
// foreground, so we can't intercept it. However, they'll fire one when
|
|
// restored, before a user can get to any incorrectly focused element.
|
|
console.warn('background focus test requires document focus');
|
|
document.documentElement.focus();
|
|
}
|
|
assert.notEqual(document.activeElement, input,
|
|
'modal should disallow background focus');
|
|
});
|
|
test('overlay is a sibling of topmost dialog', function() {
|
|
var stacking = cleanup(document.createElement('div'));
|
|
stacking.style.opacity = 0.8; // creates stacking context
|
|
document.body.appendChild(stacking);
|
|
stacking.appendChild(dialog);
|
|
dialog.showModal();
|
|
|
|
var overlay = document.querySelector('._dialog_overlay');
|
|
assert.isNotNull(overlay);
|
|
assert.equal(overlay.parentNode, dialog.parentNode);
|
|
});
|
|
test('overlay is between topmost and remaining dialogs', function() {
|
|
dialog.showModal();
|
|
|
|
var other = cleanup(createDialog());
|
|
document.body.appendChild(other);
|
|
other.showModal();
|
|
|
|
var overlay = document.querySelector('._dialog_overlay');
|
|
assert.isNotNull(overlay);
|
|
assert.equal(overlay.parentNode, other.parentNode);
|
|
|
|
assert.isAbove(+other.style.zIndex, +overlay.style.zIndex, 'top-most dialog above overlay');
|
|
assert.isAbove(+overlay.style.zIndex, +dialog.style.zIndex, 'overlay above other dialogs');
|
|
});
|
|
});
|
|
|
|
suite('events', function() {
|
|
test('close event', function() {
|
|
var closeFired = 0;
|
|
dialog.addEventListener('close', function() {
|
|
++closeFired;
|
|
});
|
|
|
|
dialog.show();
|
|
assert.equal(closeFired, 0);
|
|
|
|
dialog.close();
|
|
assert.equal(closeFired, 1);
|
|
|
|
assert.throws(dialog.close); // can't close already closed dialog
|
|
assert.equal(closeFired, 1);
|
|
|
|
dialog.showModal();
|
|
dialog.close();
|
|
assert.equal(closeFired, 2);
|
|
});
|
|
test('cancel event', function() {
|
|
dialog.showModal();
|
|
dialog.dispatchEvent(createKeyboardEvent(27));
|
|
assert.isFalse(dialog.open, 'esc should close modal');
|
|
|
|
var cancelFired = 0;
|
|
dialog.addEventListener('cancel', function() {
|
|
++cancelFired;
|
|
});
|
|
dialog.showModal();
|
|
dialog.dispatchEvent(createKeyboardEvent(27));
|
|
assert.equal(cancelFired, 1, 'expected cancel to be fired');
|
|
assert.isFalse(dialog.open, 'esc should close modal again');
|
|
|
|
// Sanity-check that non-modals aren't effected.
|
|
dialog.show();
|
|
dialog.dispatchEvent(createKeyboardEvent(27));
|
|
assert.isTrue(dialog.open, 'esc should only close modal dialog');
|
|
assert.equal(cancelFired, 1);
|
|
});
|
|
test('cancel event via oncancel', function() {
|
|
dialog.showModal();
|
|
|
|
var cancelFired = 0;
|
|
dialog.oncancel = function(event) {
|
|
++cancelFired;
|
|
event.preventDefault();
|
|
};
|
|
|
|
dialog.dispatchEvent(createKeyboardEvent(27));
|
|
assert.equal(cancelFired, 1, 'expected cancel to be fired');
|
|
assert.isTrue(dialog.open, 'behavior should be prevented');
|
|
});
|
|
test('overlay click is prevented', function() {
|
|
dialog.showModal();
|
|
|
|
var overlay = document.querySelector('._dialog_overlay');
|
|
assert.isNotNull(overlay);
|
|
|
|
var helper = function(ev) {
|
|
throw Error('body should not be clicked');
|
|
};
|
|
try {
|
|
document.body.addEventListener('click', helper);
|
|
overlay.click();
|
|
} finally {
|
|
document.body.removeEventListener('click', helper);
|
|
}
|
|
});
|
|
});
|
|
|
|
suite('form', function() {
|
|
test('method attribute is translated to property', function() {
|
|
var form = document.createElement('form');
|
|
form.method = 'dialog';
|
|
assert.equal(form.method, 'dialog');
|
|
|
|
form.method = 'PoSt';
|
|
assert.equal(form.method, 'post');
|
|
assert.equal(form.getAttribute('method'), 'PoSt');
|
|
});
|
|
test('dialog method input', function() {
|
|
var value = 'ExpectedValue' + Math.random();
|
|
|
|
var form = document.createElement('form');
|
|
try {
|
|
form.method = 'dialog';
|
|
} catch (e) {
|
|
// Setting the method directly throws an exception in <=IE9.
|
|
form.setAttribute('method', 'dialog');
|
|
}
|
|
dialog.appendChild(form);
|
|
|
|
var input = document.createElement('input');
|
|
input.type = 'submit';
|
|
input.value = value;
|
|
form.appendChild(input);
|
|
|
|
var closeCount = 0;
|
|
dialog.addEventListener('close', function() {
|
|
++closeCount;
|
|
});
|
|
|
|
dialog.show();
|
|
input.focus(); // emulate user focus action
|
|
input.click();
|
|
|
|
assert.isFalse(dialog.open);
|
|
assert.equal(dialog.returnValue, value);
|
|
assert.equal(closeCount, 1);
|
|
});
|
|
test('dialog with form submit preventDefault does not trigger close', function() {
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
dialog.appendChild(form);
|
|
|
|
var input = document.createElement('input');
|
|
input.type = 'submit';
|
|
input.value = 'Does not matter';
|
|
form.appendChild(input);
|
|
|
|
form.addEventListener('submit', function(ev) {
|
|
ev.preventDefault();
|
|
});
|
|
|
|
dialog.showModal();
|
|
form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
|
|
|
|
assert.isTrue(dialog.open, 'dialog should remain open');
|
|
assert.equal(dialog.returnValue, '');
|
|
});
|
|
test('dialog with button preventDefault does not trigger submit', function() {
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
dialog.appendChild(form);
|
|
|
|
var button = document.createElement('button');
|
|
button.value = 'does not matter';
|
|
form.appendChild(button);
|
|
button.addEventListener('click', function(ev) {
|
|
ev.preventDefault();
|
|
});
|
|
|
|
dialog.showModal();
|
|
button.click();
|
|
|
|
assert.isTrue(dialog.open, 'dialog should remain open');
|
|
assert.equal(dialog.returnValue, '');
|
|
});
|
|
test('dialog programmatic submit does not change returnValue', function() {
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
|
|
dialog.returnValue = 'manually set'; // set before appending
|
|
dialog.appendChild(form);
|
|
|
|
dialog.showModal();
|
|
form.submit();
|
|
assert.isFalse(dialog.open);
|
|
|
|
assert.equal(dialog.returnValue, 'manually set', 'returnValue should not change');
|
|
});
|
|
test('dialog method button', function() {
|
|
var value = 'ExpectedValue' + Math.random();
|
|
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
dialog.appendChild(form);
|
|
|
|
var button = document.createElement('button');
|
|
button.value = value;
|
|
form.appendChild(button);
|
|
|
|
dialog.showModal();
|
|
button.focus(); // emulate user focus action
|
|
button.click();
|
|
|
|
assert.isFalse(dialog.open);
|
|
assert.equal(dialog.returnValue, value);
|
|
|
|
// Clear button value, confirm textContent is not used as value.
|
|
button.value = 'blah blah';
|
|
button.removeAttribute('value');
|
|
button.textContent = value;
|
|
dialog.show();
|
|
button.focus(); // emulate user focus action
|
|
button.click();
|
|
|
|
assert.equal(dialog.returnValue, button.value,
|
|
'don\'t take button textContent as value');
|
|
});
|
|
test('boring form inside dialog', function() {
|
|
var form = document.createElement('form');
|
|
dialog.appendChild(form); // don't specify method
|
|
form.addEventListener('submit', function(ev) {
|
|
ev.preventDefault();
|
|
});
|
|
|
|
var button = document.createElement('button');
|
|
button.value = 'Moot';
|
|
form.appendChild(button);
|
|
|
|
dialog.showModal();
|
|
button.focus(); // emulate user focus action
|
|
button.click();
|
|
|
|
assert.isTrue(dialog.open, 'non-dialog form should not close dialog')
|
|
assert(!dialog.returnValue);
|
|
});
|
|
test('type="image" submitter', function() {
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
dialog.appendChild(form);
|
|
dialog.show();
|
|
|
|
var image = document.createElement('input');
|
|
image.type = 'image';
|
|
image.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
|
image.setAttribute('value', 'image should not accept value');
|
|
form.appendChild(image);
|
|
image.click();
|
|
|
|
assert.notEqual(image.getAttribute('value'), dialog.returnValue);
|
|
assert.equal(dialog.returnValue, '0,0');
|
|
});
|
|
test('form submitter across dialogs', function() {
|
|
var form1 = document.createElement('form');
|
|
form1.setAttribute('method', 'dialog');
|
|
dialog.appendChild(form1);
|
|
|
|
var button1 = document.createElement('button');
|
|
button1.value = 'from form1: first value';
|
|
form1.appendChild(button1);
|
|
dialog.showModal();
|
|
|
|
var dialog2 = createDialog();
|
|
dialog2.returnValue = 'dialog2 default close value';
|
|
var form2 = document.createElement('form');
|
|
form2.setAttribute('method', 'dialog');
|
|
dialog2.appendChild(form2);
|
|
dialog2.showModal();
|
|
|
|
button1.click();
|
|
assert.isFalse(dialog.open);
|
|
|
|
// nb. this never fires 'submit' so the .returnValue can't be wrong: is there another way
|
|
// to submit a form that doesn't involve a click (enter implicitly 'clicks') or submit?
|
|
form2.submit();
|
|
assert.isFalse(dialog2.open);
|
|
|
|
assert.equal(dialog2.returnValue, 'dialog2 default close value',
|
|
'second dialog shouldn\'t reuse formSubmitter');
|
|
});
|
|
test('form submit inside SD', function(done) {
|
|
if (!window.ShadowRoot) {
|
|
return;
|
|
}
|
|
const iframeName = 'iframe_secret_blah';
|
|
|
|
var holder = document.createElement('div');
|
|
document.body.append(holder);
|
|
cleanup(holder);
|
|
|
|
var root = holder.attachShadow({mode: 'open'});
|
|
var rootHolder = document.createElement('div');
|
|
root.append(rootHolder);
|
|
rootHolder.append(dialog);
|
|
|
|
var iframe = document.createElement('iframe');
|
|
iframe.setAttribute('name', iframeName);
|
|
document.body.append(iframe);
|
|
cleanup(iframe);
|
|
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
form.setAttribute('target', iframeName);
|
|
form.setAttribute('action', '/test-invalid.html');
|
|
dialog.append(form);
|
|
|
|
var button = document.createElement('button');
|
|
button.value = 'from form1: first value';
|
|
form.append(button);
|
|
dialog.showModal();
|
|
|
|
iframe.addEventListener('load', function() {
|
|
var href = iframe.contentWindow.location.href;
|
|
if (href !== 'about:blank') {
|
|
assert.fail('should not load a new page: ' + href);
|
|
}
|
|
});
|
|
button.click();
|
|
|
|
window.setTimeout(function() {
|
|
assert.isFalse(dialog.open, 'dialog should be closed by button');
|
|
if (iframe.contentWindow && iframe.contentWindow.location) {
|
|
assert.notStrictEqual(iframe.contentWindow.location.pathname, '/test-invalid.html');
|
|
}
|
|
done();
|
|
}, 50);
|
|
});
|
|
test('form submit with formmethod', function(done) {
|
|
const iframeName = 'formmethod_test_frame';
|
|
|
|
var iframe = document.createElement('iframe');
|
|
iframe.setAttribute('name', iframeName);
|
|
document.body.append(iframe);
|
|
cleanup(iframe);
|
|
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
form.setAttribute('target', iframeName);
|
|
form.setAttribute('action', '/test-invalid.html');
|
|
dialog.append(form);
|
|
dialog.show();
|
|
|
|
var button = document.createElement('button');
|
|
button.setAttribute('formmethod', 'get');
|
|
form.append(button);
|
|
button.value = '123';
|
|
button.textContent = 'Long button';
|
|
|
|
var timeout = window.setTimeout(function() {
|
|
assert.fail('page should load (form submit with formmethod)');
|
|
}, 500);
|
|
iframe.addEventListener('load', function() {
|
|
window.clearTimeout(timeout);
|
|
assert.isTrue(dialog.open);
|
|
done();
|
|
});
|
|
button.click();
|
|
});
|
|
test('form method="dialog" prevented outside dialog', function(done) {
|
|
const iframeName = 'outside_dialog_test';
|
|
|
|
var iframe = document.createElement('iframe');
|
|
iframe.setAttribute('name', iframeName);
|
|
document.body.append(iframe);
|
|
cleanup(iframe);
|
|
|
|
var form = document.createElement('form');
|
|
form.setAttribute('method', 'dialog');
|
|
form.setAttribute('target', iframeName);
|
|
form.setAttribute('action', '/test-invalid.html');
|
|
document.body.append(form);
|
|
cleanup(form);
|
|
|
|
iframe.addEventListener('load', function() {
|
|
if (iframe.contentWindow.location.href !== 'about:blank') {
|
|
assert.fail('should not load a new page: ' + iframe.contentWindow.location.href);
|
|
}
|
|
});
|
|
|
|
form.submit();
|
|
|
|
// Try with an actual button, too.
|
|
var button = document.createElement('button');
|
|
form.append(button);
|
|
button.click();
|
|
|
|
var timeout = window.setTimeout(function() {
|
|
done();
|
|
}, 1000);
|
|
});
|
|
});
|
|
|
|
suite('order', function() {
|
|
test('non-modal unchanged', function() {
|
|
var one = createDialog();
|
|
var two = createDialog();
|
|
|
|
one.style.zIndex = 100;
|
|
two.style.zIndex = 200;
|
|
one.show();
|
|
two.show();
|
|
|
|
assert.equal(window.getComputedStyle(one).zIndex, 100);
|
|
assert.equal(window.getComputedStyle(two).zIndex, 200);
|
|
|
|
two.close();
|
|
assert.equal(window.getComputedStyle(two).zIndex, 200);
|
|
});
|
|
test('modal stacking order', function() {
|
|
dialog.showModal();
|
|
|
|
// Create incorrectly-named dialogs: front has a lower z-index, and back
|
|
// has a higher z-index.
|
|
var front = createDialog();
|
|
var back = createDialog();
|
|
front.style.zIndex = 100;
|
|
back.style.zIndex = 200;
|
|
|
|
// Show back first, then front. Thus we expect back to be behind front.
|
|
back.showModal();
|
|
front.showModal();
|
|
|
|
var zf = +window.getComputedStyle(front).zIndex;
|
|
var zb = +window.getComputedStyle(back).zIndex;
|
|
assert.isAbove(zf, zb, 'showModal order dictates z-index');
|
|
|
|
var backBackdrop = back.nextElementSibling;
|
|
var zbb = +window.getComputedStyle(backBackdrop).zIndex;
|
|
assert.equal(backBackdrop.className, 'backdrop');
|
|
assert.isBelow(zbb, zb, 'backdrop below dialog');
|
|
|
|
var frontBackdrop = front.nextElementSibling;
|
|
var zfb = +window.getComputedStyle(frontBackdrop).zIndex
|
|
assert.equal(frontBackdrop.className, 'backdrop');
|
|
assert.isBelow(zfb, zf,' backdrop below dialog');
|
|
|
|
assert.isAbove(zfb, zb, 'front backdrop is above back dialog');
|
|
|
|
front.close();
|
|
assert.notOk(front.style.zIndex, 'modal close should clear zindex');
|
|
});
|
|
});
|
|
|
|
suite('press tab key', function() {
|
|
test('tab key', function() {
|
|
var dialog = createDialog();
|
|
dialog.showModal();
|
|
|
|
document.documentElement.dispatchEvent(createKeyboardEvent(9));
|
|
|
|
var ev = document.createEvent('Events');
|
|
ev.initEvent('focus', true, true);
|
|
document.documentElement.dispatchEvent(ev);
|
|
|
|
dialog.close();
|
|
});
|
|
});
|
|
}();
|