UNPKG

spincycle

Version:

A reactive message router and object manager that lets clients subscribe to object property changes on the server

1,236 lines (1,085 loc) 50.5 kB
<!doctype html> <!-- @license Copyright (c) 2015 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <html> <head> <title>iron-overlay-behavior tests</title> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes"> <script src="../../webcomponentsjs/webcomponents-lite.js"></script> <script src="../../web-component-tester/browser.js"></script> <link rel="import" href="../../iron-test-helpers/iron-test-helpers.html"> <link rel="import" href="test-overlay.html"> <link rel="import" href="test-overlay2.html"> <link rel="import" href="test-buttons.html"> <link rel="import" href="test-menu-button.html"> <style is="custom-style"> iron-overlay-backdrop { /* For quicker tests */ --iron-overlay-backdrop: { transition: none; } } </style> </head> <body> <test-fixture id="basic"> <template> <test-overlay> Basic Overlay </test-overlay> </template> </test-fixture> <test-fixture id="opened"> <template> <test-overlay opened> Basic Overlay </test-overlay> </template> </test-fixture> <test-fixture id="autofocus"> <template> <test-overlay> Autofocus <button autofocus>button</button> </test-overlay> </template> </test-fixture> <test-fixture id="focusables"> <template> <test-overlay tabindex="-1"> <h2>Focusables (no tabindex)</h2> <div> <input class="focusable1" placeholder="1 (nested)"> </div> <button class="focusable2">1</button> <button disabled> disabled button</button> <div tabindex="-1">not focusable</div> <button class="focusable3">2</button> </test-overlay> <test-overlay tabindex="-1"> <h2>Focusables (with tabindex)</h2> <div tabindex="-1">not focusable</div> <div tabindex="3" class="focusable3">3</div> <div tabindex="4" class="focusable4">4</div> <div tabindex="5" class="focusable5">5</div> <div> <div tabindex="1" class="focusable1">1 (nested)</div> <div tabindex="6" class="focusable6">6 (nested)</div> </div> <div tabindex="2" class="focusable2">2</div> </test-overlay> <test-overlay2> Overlay with optimized focusableNodes getter <button class="focusable1">1</button> </test-overlay2> </template> </test-fixture> <test-fixture id="backdrop"> <template> <test-overlay with-backdrop> Overlay with backdrop </test-overlay> </template> </test-fixture> <test-fixture id="multiple"> <template> <test-overlay class="overlay-1"> Test overlay 1 </test-overlay> <test-overlay class="overlay-2"> Test overlay 2 <button>Click</button> </test-overlay> <test-overlay2 class="overlay-3"> Other overlay 3 </test-overlay2> </template> </test-fixture> <test-fixture id="composed"> <template> <test-menu-button></test-menu-button> </template> </test-fixture> <test-buttons id="buttons"></test-buttons> <input id="focusInput" placeholder="focus input"> <script> function runAfterOpen(overlay, callback) { overlay.addEventListener('iron-overlay-opened', callback); overlay.open(); } function runAfterClose(overlay, callback) { overlay.addEventListener('iron-overlay-closed', callback); overlay.close(); } suite('basic overlay', function() { var overlay; setup(function() { overlay = fixture('basic'); }); test('overlay starts hidden', function() { assert.isFalse(overlay.opened, 'overlay starts closed'); assert.equal(getComputedStyle(overlay).display, 'none', 'overlay starts hidden'); }); test('_renderOpened called only after is attached', function(done) { var overlay = document.createElement('test-overlay'); // The overlay is ready at this point, but not yet attached. var spy = sinon.spy(overlay, '_renderOpened'); // This triggers _openedChanged. overlay.opened = true; // Wait long enough for requestAnimationFrame callback. overlay.async(function() { assert.isFalse(spy.called, '_renderOpened not called'); // Because not attached yet, overlay should not be the current overlay! assert.isNotOk(overlay._manager.currentOverlay(), 'currentOverlay not set'); done(); }, 100); }); test('overlay open/close events', function(done) { var nevents = 0; overlay.addEventListener('iron-overlay-opened', function() { nevents += 1; overlay.opened = false; }); overlay.addEventListener('iron-overlay-closed', function() { nevents += 1; assert.equal(nevents, 2, 'opened and closed events fired'); done(); }); overlay.opened = true; }); test('open() refits overlay only once', function(done) { var spy = sinon.spy(overlay, 'refit'); runAfterOpen(overlay, function() { assert.equal(spy.callCount, 1, 'overlay did refit only once'); done(); }); }); test('open overlay refits on iron-resize', function(done) { runAfterOpen(overlay, function() { var spy = sinon.spy(overlay, 'refit'); overlay.fire('iron-resize'); Polymer.dom.flush(); requestAnimationFrame(function() { assert.isTrue(spy.called, 'overlay did refit'); done(); }); }); }); test('closed overlay does not refit on iron-resize', function(done) { var spy = sinon.spy(overlay, 'refit'); overlay.fire('iron-resize'); Polymer.dom.flush(); requestAnimationFrame(function() { assert.isFalse(spy.called, 'overlay should not refit'); done(); }); }); test('open() triggers iron-resize', function(done) { var callCount = 0; // Ignore iron-resize triggered by window resize. window.addEventListener('resize', function() { callCount--; }, true); overlay.addEventListener('iron-resize', function() { callCount++; }); runAfterOpen(overlay, function() { assert.equal(callCount, 1, 'iron-resize called once before iron-overlay-opened'); done(); }); }); test('close() triggers iron-resize', function(done) { runAfterOpen(overlay, function() { var spy = sinon.stub(); overlay.addEventListener('iron-resize', spy); runAfterClose(overlay, function() { assert.equal(spy.callCount, 1, 'iron-resize called once before iron-overlay-closed'); done(); }); }); }); test('closed overlay does not trigger iron-resize when its content changes', function() { // Ignore iron-resize triggered by window resize. var callCount = 0; window.addEventListener('resize', function() { callCount--; }, true); overlay.addEventListener('iron-resize', function() { callCount++; }); Polymer.dom(overlay).appendChild(document.createElement('div')); Polymer.dom.flush(); assert.equal(callCount, 0, 'iron-resize should not be called'); }); test('open overlay triggers iron-resize when its content changes', function(done) { runAfterOpen(overlay, function() { var spy = sinon.stub(); overlay.addEventListener('iron-resize', spy); Polymer.dom(overlay).appendChild(document.createElement('div')); Polymer.dom.flush(); assert.equal(spy.callCount, 1, 'iron-resize should be called once'); done(); }); }); test('close an overlay quickly after open', function(done) { // first, open the overlay overlay.open(); overlay.async(function() { // during the opening transition, close the overlay this.close(); // wait for any exceptions to be thrown until the transition is done this.async(function() { done(); }, 300); }); }); test('clicking an overlay does not close it', function(done) { runAfterOpen(overlay, function() { var spy = sinon.stub(); overlay.addEventListener('iron-overlay-closed', spy); MockInteractions.tap(overlay); overlay.async(function() { assert.isFalse(spy.called, 'iron-overlay-closed should not fire'); done(); }, 10); }); }); test('open overlay on mousedown does not close it', function(done) { var btn = document.createElement('button'); btn.addEventListener('mousedown', overlay.open.bind(overlay)); document.body.appendChild(btn); // It triggers mousedown, mouseup, and click. MockInteractions.tap(btn); document.body.removeChild(btn); assert.isTrue(overlay.opened, 'overlay opened'); overlay.async(function() { assert.isTrue(overlay.opened, 'overlay is still open'); done(); }, 10); }); test('clicking outside fires iron-overlay-canceled', function(done) { runAfterOpen(overlay, function() { overlay.addEventListener('iron-overlay-canceled', function(event) { assert.equal(event.detail.target, document.body, 'detail contains original click event'); done(); }); MockInteractions.tap(document.body); }); }); test('clicking outside closes the overlay', function(done) { runAfterOpen(overlay, function() { overlay.addEventListener('iron-overlay-closed', function(event) { assert.isTrue(event.detail.canceled, 'overlay is canceled'); done(); }); MockInteractions.tap(document.body); }); }); test('iron-overlay-canceled event can be prevented', function(done) { runAfterOpen(overlay, function() { overlay.addEventListener('iron-overlay-canceled', function(event) { event.preventDefault(); }); var spy = sinon.stub(); overlay.addEventListener('iron-overlay-closed', spy); MockInteractions.tap(document.body); Polymer.Base.async(function() { assert.isTrue(overlay.opened, 'overlay is still open'); assert.isFalse(spy.called, 'iron-overlay-closed not fired'); done(); }, 10); }); }); test('cancel an overlay with esc key', function(done) { runAfterOpen(overlay, function() { overlay.addEventListener('iron-overlay-canceled', function(event) { assert.equal(event.detail.type, 'keydown'); done(); }); MockInteractions.pressAndReleaseKeyOn(document, 27); }); }); test('close an overlay with esc key', function(done) { runAfterOpen(overlay, function() { overlay.addEventListener('iron-overlay-closed', function(event) { assert.isTrue(event.detail.canceled, 'overlay is canceled'); done(); }); MockInteractions.pressAndReleaseKeyOn(document, 27); }); }); test('no-cancel-on-outside-click property', function(done) { overlay.noCancelOnOutsideClick = true; runAfterOpen(overlay, function() { var spy = sinon.stub(); overlay.addEventListener('iron-overlay-closed', spy); MockInteractions.tap(document.body); Polymer.Base.async(function() { assert.isFalse(spy.called, 'iron-overlay-closed should not fire'); done(); }, 10); }); }); test('no-cancel-on-esc-key property', function(done) { overlay.noCancelOnEscKey = true; runAfterOpen(overlay, function() { var spy = sinon.stub(); overlay.addEventListener('iron-overlay-closed', spy); MockInteractions.pressAndReleaseKeyOn(document, 27); Polymer.Base.async(function() { assert.isFalse(spy.called, 'iron-overlay-cancel should not fire'); done(); }, 10); }); }); test('with-backdrop sets tabindex=-1 and removes it', function() { overlay.withBackdrop = true; assert.equal(overlay.getAttribute('tabindex'), '-1', 'tabindex is -1'); overlay.withBackdrop = false; assert.isFalse(overlay.hasAttribute('tabindex'), 'tabindex removed'); }); test('with-backdrop does not override tabindex if already set', function() { overlay.setAttribute('tabindex', '1'); overlay.withBackdrop = true; assert.equal(overlay.getAttribute('tabindex'), '1', 'tabindex is 1'); overlay.withBackdrop = false; assert.equal(overlay.getAttribute('tabindex'), '1', 'tabindex is still 1'); }); }); suite('keyboard event listener', function() { var overlay; var preventKeyDown = function(event) { event.preventDefault(); event.stopPropagation(); } suiteSetup(function() { // Worst case scenario: listener with useCapture = true that prevents & stops propagation // added before the overlay is initialized. document.addEventListener('keydown', preventKeyDown, true); }); setup(function() { overlay = fixture('basic'); }); suiteTeardown(function() { document.removeEventListener('keydown', preventKeyDown, true); }); test('cancel an overlay with esc key even if event is prevented by other listeners', function(done) { runAfterOpen(overlay, function() { overlay.addEventListener('iron-overlay-canceled', function(event) { done(); }); MockInteractions.pressAndReleaseKeyOn(document, 27); }); }); }); suite('opened overlay', function() { var overlay; setup(function() { overlay = fixture('opened'); }); test('overlay open by default', function(done) { overlay.addEventListener('iron-overlay-opened', function() { assert.isTrue(overlay.opened, 'overlay starts opened'); assert.notEqual(getComputedStyle(overlay).display, 'none', 'overlay starts showing'); done(); }); }); test('overlay positioned & sized properly', function(done) { overlay.addEventListener('iron-overlay-opened', function() { var s = getComputedStyle(overlay); assert.closeTo(parseFloat(s.left), (window.innerWidth - overlay.offsetWidth) / 2, 1, 'centered horizontally'); assert.closeTo(parseFloat(s.top), (window.innerHeight - overlay.offsetHeight) / 2, 1, 'centered vertically'); done(); }); }); }); suite('focus handling', function() { var overlay; setup(function() { // Ensure focus is set to document.body document.body.focus(); overlay = fixture('autofocus'); }); test('node with autofocus is focused', function(done) { runAfterOpen(overlay, function() { assert.equal(Polymer.dom(overlay).querySelector('[autofocus]'), document.activeElement, '<button autofocus> is focused'); done(); }); }); test('no-auto-focus will not focus node with autofocus', function(done) { overlay.noAutoFocus = true; runAfterOpen(overlay, function() { assert.notEqual(Polymer.dom(overlay).querySelector('[autofocus]'), document.activeElement, '<button autofocus> not focused after opened'); done(); }); // In Safari the element with autofocus will immediately receive focus when displayed for the first time http://jsbin.com/woroci/2/ // Ensure this is not the case for overlay. assert.notEqual(Polymer.dom(overlay).querySelector('[autofocus]'), document.activeElement, '<button autofocus> not immediately focused'); }); test('no-cancel-on-outside-click property; focus stays on overlay when click outside', function(done) { overlay.noCancelOnOutsideClick = true; runAfterOpen(overlay, function() { MockInteractions.tap(document.body); Polymer.Base.async(function() { assert.equal(Polymer.dom(overlay).querySelector('[autofocus]'), document.activeElement, '<button autofocus> is focused'); done(); }, 10); }); }); test('with-backdrop traps the focus within the overlay', function(done) { var focusSpy = sinon.stub(); var button = document.createElement('button'); document.body.appendChild(button); button.addEventListener('focus', focusSpy, true); overlay.withBackdrop = true; runAfterOpen(overlay, function() { // Try to steal the focus MockInteractions.focus(button); assert.equal(Polymer.dom(overlay).querySelector('[autofocus]'), document.activeElement, '<button autofocus> is focused'); assert.equal(focusSpy.callCount, 0, 'button in body did not get the focus'); document.body.removeChild(button); done(); }); }); test('overlay with-backdrop and 1 focusable: prevent TAB and trap the focus', function(done) { overlay.withBackdrop = true; runAfterOpen(overlay, function() { // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function() { // Spy keydown. var tabSpy = sinon.spy(); document.addEventListener('keydown', tabSpy); // Simulate TAB. MockInteractions.pressAndReleaseKeyOn(document, 9); assert.equal(Polymer.dom(overlay).querySelector('[autofocus]'), document.activeElement, 'focus stays on button'); assert.isTrue(tabSpy.calledOnce, 'keydown spy called'); assert.isTrue(tabSpy.getCall(0).args[0].defaultPrevented, 'keydown default prevented'); // Cleanup. document.removeEventListener('keydown', tabSpy); done(); }, 1); }); }); test('empty overlay with-backdrop: prevent TAB and trap the focus', function(done) { overlay = fixture('basic'); overlay.withBackdrop = true; runAfterOpen(overlay, function() { // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function() { // Spy keydown. var tabSpy = sinon.spy(); document.addEventListener('keydown', tabSpy); // Simulate TAB. MockInteractions.pressAndReleaseKeyOn(document, 9); assert.equal(overlay, document.activeElement, 'focus stays on overlay'); assert.isTrue(tabSpy.calledOnce, 'keydown spy called'); assert.isTrue(tabSpy.getCall(0).args[0].defaultPrevented, 'keydown default prevented'); // Cleanup. document.removeEventListener('keydown', tabSpy); done(); }, 1); }); }); }); suite('focusable nodes', function() { var overlay, overlayWithTabIndex, overlayFocusableNodes; setup(function() { var f = fixture('focusables'); overlay = f[0]; overlayWithTabIndex = f[1]; overlayFocusableNodes = f[2]; }); test('_focusableNodes returns nodes that are focusable', function(done) { runAfterOpen(overlay, function() { var focusableNodes = overlay._focusableNodes; assert.equal(focusableNodes.length, 3, '3 nodes are focusable'); assert.equal(focusableNodes[0], Polymer.dom(overlay).querySelector('.focusable1')); assert.equal(focusableNodes[1], Polymer.dom(overlay).querySelector('.focusable2')); assert.equal(focusableNodes[2], Polymer.dom(overlay).querySelector('.focusable3')); done(); }); }); test('_focusableNodes includes overlay if it has a valid tabindex', function(done) { runAfterOpen(overlay, function() { overlay.setAttribute('tabindex', '0'); var focusableNodes = overlay._focusableNodes; assert.equal(focusableNodes.length, 4, '4 focusable nodes'); assert.notEqual(focusableNodes.indexOf(overlay), -1, 'overlay is included'); done(); }); }); test('_focusableNodes respects the tabindex order', function(done) { runAfterOpen(overlayWithTabIndex, function() { var focusableNodes = overlayWithTabIndex._focusableNodes; assert.equal(focusableNodes.length, 6, '6 nodes are focusable'); assert.equal(focusableNodes[0], Polymer.dom(overlayWithTabIndex).querySelector('.focusable1')); assert.equal(focusableNodes[1], Polymer.dom(overlayWithTabIndex).querySelector('.focusable2')); assert.equal(focusableNodes[2], Polymer.dom(overlayWithTabIndex).querySelector('.focusable3')); assert.equal(focusableNodes[3], Polymer.dom(overlayWithTabIndex).querySelector('.focusable4')); assert.equal(focusableNodes[4], Polymer.dom(overlayWithTabIndex).querySelector('.focusable5')); assert.equal(focusableNodes[5], Polymer.dom(overlayWithTabIndex).querySelector('.focusable6')); done(); }); }); test('_focusableNodes can be overridden', function(done) { runAfterOpen(overlayFocusableNodes, function() { // It has 1 focusable in the light dom, and 2 in the shadow dom. var focusableNodes = overlayFocusableNodes._focusableNodes; assert.equal(focusableNodes.length, 2, 'length ok'); assert.equal(focusableNodes[0], overlayFocusableNodes.$.first, 'first ok'); assert.equal(focusableNodes[1], overlayFocusableNodes.$.last, 'last ok'); done(); }); }); test('with-backdrop: TAB & Shift+TAB wrap focus', function(done) { overlay.withBackdrop = true; runAfterOpen(overlay, function() { var focusableNodes = overlay._focusableNodes; // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function() { // Go to last element. focusableNodes[focusableNodes.length-1].focus(); // Spy keydown. var tabSpy = sinon.spy(); document.addEventListener('keydown', tabSpy); // Simulate TAB. MockInteractions.pressAndReleaseKeyOn(document, 9); assert.equal(focusableNodes[0], document.activeElement, 'focus wrapped to first focusable'); assert.isTrue(tabSpy.calledOnce, 'keydown spy called'); assert.isTrue(tabSpy.getCall(0).args[0].defaultPrevented, 'keydown default prevented'); // Simulate Shift+TAB. MockInteractions.pressAndReleaseKeyOn(document, 9, ['shift']); assert.equal(focusableNodes[focusableNodes.length-1], document.activeElement, 'focus wrapped to last focusable'); assert.isTrue(tabSpy.calledTwice, 'keydown spy called again'); assert.isTrue(tabSpy.getCall(1).args[0].defaultPrevented, 'keydown default prevented again'); // Cleanup. document.removeEventListener('keydown', tabSpy); done(); }, 1); }); }); test('with-backdrop: TAB & Shift+TAB wrap focus respecting tabindex', function(done) { overlayWithTabIndex.withBackdrop = true; runAfterOpen(overlayWithTabIndex, function() { var focusableNodes = overlayWithTabIndex._focusableNodes; // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function() { // Go to last element. focusableNodes[focusableNodes.length-1].focus(); // Simulate TAB. MockInteractions.pressAndReleaseKeyOn(document, 9); assert.equal(focusableNodes[0], document.activeElement, 'focus wrapped to first focusable'); // Simulate Shift+TAB. MockInteractions.pressAndReleaseKeyOn(document, 9, ['shift']); assert.equal(focusableNodes[focusableNodes.length-1], document.activeElement, 'focus wrapped to last focusable'); done(); }, 1); }); }); test('with-backdrop: Shift+TAB after open wrap focus', function(done) { overlay.withBackdrop = true; runAfterOpen(overlay, function() { var focusableNodes = overlay._focusableNodes; // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function() { // Spy keydown. var tabSpy = sinon.spy(); document.addEventListener('keydown', tabSpy); // Simulate Shift+TAB. MockInteractions.pressAndReleaseKeyOn(document, 9, ['shift']); assert.equal(focusableNodes[focusableNodes.length-1], document.activeElement, 'focus wrapped to last focusable'); assert.isTrue(tabSpy.calledOnce, 'keydown spy called'); assert.isTrue(tabSpy.getCall(0).args[0].defaultPrevented, 'keydown default prevented'); // Cleanup. document.removeEventListener('keydown', tabSpy); done(); }, 1); }); }); test('with-backdrop: after open, update last focusable node and then Shift+TAB', function(done) { overlay.withBackdrop = true; runAfterOpen(overlay, function() { var focusableNodes = overlay._focusableNodes; // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function() { // Before tabbing, make lastFocusable non-tabbable. This will make // the one before it (focusableNodes.length - 2), the new last focusable node. focusableNodes[focusableNodes.length-1].setAttribute('tabindex', '-1'); overlay.invalidateTabbables(); // Simulate Shift+TAB. MockInteractions.pressAndReleaseKeyOn(document, 9, ['shift']); assert.equal(focusableNodes[focusableNodes.length-2], document.activeElement, 'focus wrapped correctly'); done(); }, 1); }); }); test('with-backdrop: Shift+TAB wrap focus in shadowDOM', function(done) { overlayFocusableNodes.withBackdrop = true; runAfterOpen(overlayFocusableNodes, function() { // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function() { // Spy keydown. var tabSpy = sinon.spy(); document.addEventListener('keydown', tabSpy); // Simulate Shift+TAB. MockInteractions.pressAndReleaseKeyOn(document, 9, ['shift']); assert.equal(overlayFocusableNodes.$.last, Polymer.IronOverlayManager.deepActiveElement, 'focus wrapped to last focusable in the shadowDOM'); assert.isTrue(tabSpy.calledOnce, 'keydown spy called'); assert.isTrue(tabSpy.getCall(0).args[0].defaultPrevented, 'keydown default prevented'); // Cleanup. document.removeEventListener('keydown', tabSpy); done(); }, 1); }); }); }); suite('Polymer.IronOverlayManager.deepActiveElement', function() { test('handles document.body', function () { document.body.focus(); assert.equal(Polymer.IronOverlayManager.deepActiveElement, document.body); }); test('handles light dom', function () { var focusable = document.getElementById('focusInput'); focusable.focus(); assert.equal(Polymer.IronOverlayManager.deepActiveElement, focusable, 'input is handled'); focusable.blur(); }); test('handles shadow dom', function () { var focusable = document.getElementById('buttons').$.button0; focusable.focus(); assert.equal(Polymer.IronOverlayManager.deepActiveElement, focusable); focusable.blur(); }); }); suite('restore-focus-on-close', function() { var overlay; setup(function () { overlay = fixture('autofocus'); overlay.restoreFocusOnClose = true; }); teardown(function () { // No matter what, return the focus to body! document.body.focus(); }); test('does not return focus on close by default (restore-focus-on-close=false)', function(done) { overlay.restoreFocusOnClose = false; var focusable = document.getElementById('focusInput'); focusable.focus(); runAfterOpen(overlay, function() { runAfterClose(overlay, function() { assert.notEqual(Polymer.IronOverlayManager.deepActiveElement, focusable, 'focus is not restored to focusable'); done(); }); }); }); test('overlay returns focus on close', function(done) { var focusable = document.getElementById('focusInput'); focusable.focus(); runAfterOpen(overlay, function() { runAfterClose(overlay, function() { assert.equal(Polymer.IronOverlayManager.deepActiveElement, focusable, 'focus restored to focusable'); done(); }); }); }); test('overlay returns focus on close (ShadowDOM)', function(done) { var focusable = document.getElementById('buttons').$.button0; focusable.focus(); runAfterOpen(overlay, function() { runAfterClose(overlay, function() { assert.equal(Polymer.IronOverlayManager.deepActiveElement, focusable, 'focus restored to focusable'); done(); }); }); }); }); suite('overlay with backdrop', function() { var overlay; setup(function() { overlay = fixture('backdrop'); }); test('backdrop is opened when overlay is opened', function(done) { assert.isOk(overlay.backdropElement, 'backdrop is defined'); runAfterOpen(overlay, function() { assert.isTrue(overlay.backdropElement.opened, 'backdrop is opened'); assert.isOk(overlay.backdropElement.parentNode, 'backdrop is inserted in the DOM'); done(); }); }); test('backdrop appears behind the overlay', function(done) { runAfterOpen(overlay, function() { styleZ = parseInt(window.getComputedStyle(overlay).zIndex, 10); backdropStyleZ = parseInt(window.getComputedStyle(overlay.backdropElement).zIndex, 10); assert.isTrue(styleZ > backdropStyleZ, 'overlay has higher z-index than backdrop'); done(); }); }); test('backdrop is removed when overlay is closed', function(done) { runAfterOpen(overlay, function() { runAfterClose(overlay, function() { assert.isFalse(overlay.backdropElement.opened, 'backdrop is closed'); assert.isNotOk(overlay.backdropElement.parentNode, 'backdrop is removed from the DOM'); assert.lengthOf(document.querySelectorAll('iron-overlay-backdrop'), 0, 'no backdrop elements on body'); done(); }); }); }); test('backdrop is removed when the element is removed from DOM', function(done) { runAfterOpen(overlay, function() { Polymer.dom(overlay).parentNode.removeChild(overlay); // Ensure detached is executed. Polymer.dom.flush(); assert.isFalse(overlay.backdropElement.opened, 'backdrop is closed'); assert.isNotOk(overlay.backdropElement.parentNode, 'backdrop is removed from the DOM'); assert.lengthOf(document.querySelectorAll('iron-overlay-backdrop'), 0, 'no backdrop elements on body'); assert.isNotOk(overlay._manager.currentOverlay(), 'currentOverlay ok'); done(); }); }); test('manager.getBackdrops() updated on opened changes', function(done) { runAfterOpen(overlay, function() { assert.equal(Polymer.IronOverlayManager.getBackdrops().length, 1, 'overlay added to manager backdrops'); runAfterClose(overlay, function() { assert.equal(Polymer.IronOverlayManager.getBackdrops().length, 0, 'overlay removed from manager backdrops'); done(); }); }); }); test('updating with-backdrop to false closes backdrop', function(done) { runAfterOpen(overlay, function() { overlay.withBackdrop = false; assert.isFalse(overlay.backdropElement.opened, 'backdrop is closed'); assert.isNotObject(overlay.backdropElement.parentNode, 'backdrop is removed from document'); done(); }); }); test('backdrop is removed when toggling overlay opened', function(done) { overlay.open(); runAfterClose(overlay, function() { assert.isFalse(overlay.backdropElement.opened, 'backdrop is closed'); assert.isNotOk(overlay.backdropElement.parentNode, 'backdrop is removed from document'); done(); }); }); test('withBackdrop = false does not prevent click outside event', function(done) { overlay.withBackdrop = false; runAfterOpen(overlay, function() { overlay.addEventListener('iron-overlay-canceled', function(event) { assert.isFalse(event.detail.defaultPrevented, 'click event not prevented'); done(); }); MockInteractions.tap(document.body); }); }); }); suite('multiple overlays', function() { var overlay1, overlay2; setup(function() { var f = fixture('multiple'); overlay1 = f[0]; overlay2 = f[1]; }); test('new overlays appear on top', function(done) { runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { var styleZ = parseInt(window.getComputedStyle(overlay1).zIndex, 10); var styleZ1 = parseInt(window.getComputedStyle(overlay2).zIndex, 10); assert.isTrue(styleZ1 > styleZ, 'overlay2 has higher z-index than overlay1'); done(); }); }); }); test('ESC closes only the top overlay', function(done) { runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { MockInteractions.pressAndReleaseKeyOn(document, 27); assert.isFalse(overlay2.opened, 'overlay2 was closed'); assert.isTrue(overlay1.opened, 'overlay1 is still opened'); done(); }); }); }); test('close an overlay in proximity to another overlay', function(done) { // Open and close a separate overlay. overlay1.open(); overlay1.close(); // Open the overlay we care about. overlay2.open(); // Immediately close the first overlay. // Wait for infinite recursion, otherwise we win. runAfterClose(overlay2, function() { done(); }) }); }); suite('Manager overlays in sync', function() { var overlay1, overlay2; var overlays; setup(function() { var f = fixture('multiple'); overlay1 = f[0]; overlay2 = f[1]; overlays = Polymer.IronOverlayManager._overlays; }); test('no duplicates after attached', function(done) { overlay1 = document.createElement('test-overlay'); runAfterOpen(overlay1, function() { assert.equal(overlays.length, 1, 'correct count after open and attached'); document.body.removeChild(overlay1); done(); }); document.body.appendChild(overlay1); }); test('call open multiple times handled', function(done) { overlay1.open(); overlay1.open(); runAfterOpen(overlay1, function() { assert.equal(overlays.length, 1, '1 overlay after open'); done(); }) }); test('close handled', function(done) { runAfterOpen(overlay1, function() { runAfterClose(overlay1, function() { assert.equal(overlays.length, 0, '0 overlays after close'); done(); }); }); }); test('open/close brings overlay on top', function(done) { overlay1.open(); runAfterOpen(overlay2, function() { assert.equal(overlays.indexOf(overlay1), 0, 'overlay1 at index 0'); assert.equal(overlays.indexOf(overlay2), 1, 'overlay2 at index 1'); overlay1.close(); runAfterOpen(overlay1, function() { assert.equal(overlays.indexOf(overlay1), 1, 'overlay1 moved at index 1'); assert.isAbove(parseInt(overlay1.style.zIndex), parseInt(overlay2.style.zIndex), 'overlay1 on top of overlay2'); done(); }); }); }); }); suite('z-ordering', function() { var originalMinimumZ; var overlay1, overlay2; setup(function() { var f = fixture('multiple'); overlay1 = f[0]; overlay2 = f[1]; originalMinimumZ = Polymer.IronOverlayManager._minimumZ; }); teardown(function() { Polymer.IronOverlayManager._minimumZ = originalMinimumZ; }); // for iframes test('default z-index is greater than 100', function(done) { runAfterOpen(overlay1, function() { var styleZ = parseInt(window.getComputedStyle(overlay1).zIndex, 10); assert.isTrue(styleZ > 100, 'overlay1 z-index is <= 100'); done(); }); }); test('ensureMinimumZ() effects z-index', function(done) { Polymer.IronOverlayManager.ensureMinimumZ(1000); runAfterOpen(overlay1, function() { var styleZ = parseInt(window.getComputedStyle(overlay1).zIndex, 10); assert.isTrue(styleZ > 1000, 'overlay1 z-index is <= 1000'); done(); }); }); test('ensureMinimumZ() never decreases the minimum z-index', function(done) { Polymer.IronOverlayManager.ensureMinimumZ(1000); Polymer.IronOverlayManager.ensureMinimumZ(500); runAfterOpen(overlay1, function() { var styleZ = parseInt(window.getComputedStyle(overlay1).zIndex, 10); assert.isTrue(styleZ > 1000, 'overlay1 z-index is <= 1000'); done(); }); }); }); suite('multiple overlays with backdrop', function() { var overlay1, overlay2, overlay3; setup(function() { var f = fixture('multiple'); overlay1 = f[0]; overlay2 = f[1]; overlay3 = f[2]; overlay1.withBackdrop = overlay2.withBackdrop = overlay3.withBackdrop = true; }); test('multiple overlays share the same backdrop', function() { assert.isTrue(overlay1.backdropElement === overlay2.backdropElement, 'overlay1 and overlay2 have the same backdrop element'); assert.isTrue(overlay1.backdropElement === overlay3.backdropElement, 'overlay1 and overlay3 have the same backdrop element'); }); test('only one iron-overlay-backdrop in the DOM', function(done) { // Open them all. overlay1.opened = true; overlay2.opened = true; runAfterOpen(overlay3, function() { assert.lengthOf(document.querySelectorAll('iron-overlay-backdrop'), 1, 'only one backdrop element in the DOM'); done(); }); }); test('iron-overlay-backdrop is removed from the DOM when all overlays with backdrop are closed', function(done) { // Open & close them all. overlay1.opened = true; overlay2.opened = true; runAfterOpen(overlay3, function() { overlay1.opened = overlay2.opened = false; runAfterClose(overlay3, function() { assert.lengthOf(document.querySelectorAll('iron-overlay-backdrop'), 0, 'backdrop element removed from the DOM'); done(); }); }); }); test('newest overlay appear on top', function(done) { runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { var styleZ = parseInt(window.getComputedStyle(overlay1).zIndex, 10); var style1Z = parseInt(window.getComputedStyle(overlay2).zIndex, 10); var bgStyleZ = parseInt(window.getComputedStyle(overlay1.backdropElement).zIndex, 10); assert.isTrue(style1Z > styleZ, 'overlay2 has higher z-index than overlay1'); assert.isTrue(styleZ > bgStyleZ, 'overlay1 has higher z-index than backdrop'); done(); }); }); }); var clickEvents = ['click', 'tap']; for (var i = 0; i < clickEvents.length; i++) { var eventName = clickEvents[i]; test(eventName + ' event handled only by top overlay', function(done) { runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { var btn = Polymer.dom(overlay2).querySelector('button'); btn.addEventListener(eventName, overlay2.close.bind(overlay2)); MockInteractions.tap(btn); assert.isFalse(overlay2.opened, 'overlay2 closed'); assert.isTrue(overlay1.opened, 'overlay1 opened'); overlay2.addEventListener('iron-overlay-closed', function() { assert.isTrue(overlay1.opened, 'overlay1 still opened'); done(); }); }); }); }); } test('updating with-backdrop updates z-index', function(done) { runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { overlay1.withBackdrop = false; var styleZ = parseInt(window.getComputedStyle(overlay1).zIndex, 10); var style1Z = parseInt(window.getComputedStyle(overlay2).zIndex, 10); var bgStyleZ = parseInt(window.getComputedStyle(overlay1.backdropElement).zIndex, 10); assert.isTrue(style1Z > bgStyleZ, 'overlay2 has higher z-index than backdrop'); assert.isTrue(styleZ < bgStyleZ, 'overlay1 has lower z-index than backdrop'); done(); }); }); }); }); suite('overlay in composed tree', function() { var composed, overlay, trigger; setup(function(done) { composed = fixture('composed'); overlay = composed.$.overlay; trigger = composed.$.trigger; overlay.withBackdrop = true; overlay.addEventListener('iron-overlay-opened', function() { done(); }); // Opens the overlay. MockInteractions.tap(trigger); }); test('click on overlay content does not close it', function(done) { // Tap on button inside overlay. MockInteractions.tap(Polymer.dom(overlay).querySelector('button')); Polymer.Base.async(function(){ assert.isTrue(overlay.opened, 'overlay still opened'); done(); }, 1); }); test('with-backdrop wraps the focus within the overlay', function(done) { // 1ms timeout needed by IE10 to have proper focus switching. Polymer.Base.async(function(){ var buttons = Polymer.dom(overlay).querySelectorAll('button'); // Go to last element. buttons[buttons.length-1].focus(); // Spy keydown. var tabSpy = sinon.spy(); document.addEventListener('keydown', tabSpy); // Simulate TAB. MockInteractions.pressAndReleaseKeyOn(document, 9); assert.equal(buttons[0], Polymer.IronOverlayManager.deepActiveElement, 'focus wrapped to first focusable'); assert.isTrue(tabSpy.calledOnce, 'keydown spy called'); assert.isTrue(tabSpy.getCall(0).args[0].defaultPrevented, 'keydown default prevented'); // Simulate Shift+TAB. MockInteractions.pressAndReleaseKeyOn(document, 9, ['shift']); assert.equal(buttons[buttons.length-1], Polymer.IronOverlayManager.deepActiveElement, 'focus wrapped to last focusable'); assert.isTrue(tabSpy.calledTwice, 'keydown spy called again'); assert.isTrue(tabSpy.getCall(1).args[0].defaultPrevented, 'keydown default prevented again'); // Cleanup. document.removeEventListener('keydown', tabSpy); done(); }, 1); }); }); suite('always-on-top', function() { var overlay1, overlay2; setup(function() { var f = fixture('multiple'); overlay1 = f[0]; overlay2 = f[1]; overlay1.alwaysOnTop = true; }); test('stays on top', function(done) { runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { var zIndex1 = parseInt(window.getComputedStyle(overlay1).zIndex, 10); var zIndex2 = parseInt(window.getComputedStyle(overlay2).zIndex, 10); assert.isAbove(zIndex1, zIndex2, 'overlay1 on top'); assert.equal(Polymer.IronOverlayManager.currentOverlay(), overlay1, 'currentOverlay ok'); done(); }); }); }); test('stays on top also if another overlay is with-backdrop', function(done) { overlay2.withBackdrop = true; runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { var zIndex1 = parseInt(window.getComputedStyle(overlay1).zIndex, 10); var zIndex2 = parseInt(window.getComputedStyle(overlay2).zIndex, 10); assert.isAbove(zIndex1, zIndex2, 'overlay1 on top'); assert.equal(Polymer.IronOverlayManager.currentOverlay(), overlay1, 'currentOverlay ok'); done(); }); }); }); test('last overlay with always-on-top wins', function(done) { overlay2.alwaysOnTop = true; runAfterOpen(overlay1, function() { runAfterOpen(overlay2, function() { var zIndex1 = parseInt(window.getComputedStyle(overlay1).zIndex, 10); var zIndex2 = parseInt(window.getComputedStyle(overlay2).zIndex, 10); assert.isAbove(zIndex2, zIndex1, 'overlay2 on top'); assert.equal(Polymer.IronOverlayManager.currentOverlay(), overlay2, 'currentOverlay ok'); done(); }); }); }); }); suite('animations', function() { test('overlay animations correctly triggered', function(done) { var overlay = fixture('basic'); overlay.animated = true; overlay.open(); overlay.addEventListener('simple-overlay-open-animation-start', function() { // Since animated overlay will transition center + 300px to center, // we should not find the element at the center when the open animation starts. var centerElement = document.elementFromPoint(window.innerWidth/2, window.innerHeight/2); assert.notEqual(centerElement, overlay, 'overlay should not be centered already'); done(); }); }); }); suite('a11y', function() {