| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>focus_test.html</title> | |
| <script src="test_bootstrap.js"></script> | |
| <script type="text/javascript"> | |
| goog.require('bot.action'); | |
| goog.require('bot.test'); | |
| goog.require('goog.Promise'); | |
| goog.require('goog.dom'); | |
| goog.require('goog.events'); | |
| goog.require('goog.events.EventType'); | |
| goog.require('goog.testing.jsunit'); | |
| goog.require('goog.userAgent'); | |
| goog.require('goog.userAgent.product'); | |
| </script> | |
| <script type="text/javascript"> | |
| var events = goog.events; | |
| var eventType = goog.events.EventType; | |
| var text1, text2, actualEvents, expectedEvents; | |
| function setUp() { | |
| actualEvents = []; | |
| expectedEvents = []; | |
| text1 = goog.dom.$('text1'); | |
| text2 = goog.dom.$('text2'); | |
| // Make sure neither text1 nor text2 have focus. Since this could | |
| // cause events to dispatch, we need to wait for them to do so | |
| // before continuing. In IE, this means continuing in a timeout. | |
| bot.action.focusOnElement(goog.dom.$('dummyFocusHolder')); | |
| return timeout(); | |
| } | |
| function tearDown() { | |
| events.removeAll(text1); | |
| events.removeAll(text2); | |
| } | |
| function buildRecord(id, type) { | |
| return id + ': ' + type; | |
| } | |
| function recordBlurAndFocus(el) { | |
| events.listen(el, [eventType.FOCUS, eventType.BLUR], function(e) { | |
| actualEvents.push(buildRecord(e.target.id, e.type)); | |
| }); | |
| } | |
| function expect(eventType, onElement) { | |
| expectedEvents.push(buildRecord(onElement.id, eventType)); | |
| } | |
| function assertHasExpectedEventOrder() { | |
| assertArrayEquals(expectedEvents, actualEvents); | |
| } | |
| function assertElementIsActiveElement(expected) { | |
| var actual = goog.dom.getActiveElement(document); | |
| if (actual) { | |
| // NOTE(jleyba): Using assertEquals(expected, document.activeElement) | |
| // causes an error in IE when the assertion fails (from trying to | |
| // convert the DOM elements into strings for the error message): | |
| // "'constructor' is null or not an object". | |
| // The following assertions give us the same check without this error. | |
| var msg = 'Expected "' + expected.id + '" to be the activeElement, ' + | |
| 'but was "' + actual.id + '".'; | |
| assertTrue(msg, expected == actual); | |
| } | |
| } | |
| /** | |
| * Pauses the test momentarily so IE can dispatch the onfocus event, which | |
| * is scheduled for the next event loop after element.focus() is called. | |
| */ | |
| function timeout() { | |
| return new goog.Promise(function(done) { | |
| setTimeout(done, 5); | |
| }); | |
| } | |
| function testShouldBeAbleToFocusOnAnElement() { | |
| // NOTE(jleyba): Test does not work on IE 8 with the new IE Driver, | |
| // I suspect due to something similar to | |
| // http://support.microsoft.com/kb/973528 | |
| if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8')) { | |
| return; | |
| } | |
| if (!bot.test.isWindowFocused()) { | |
| return; | |
| } | |
| recordBlurAndFocus(text1); | |
| recordBlurAndFocus(text2); | |
| expect(eventType.FOCUS, text1); | |
| bot.action.focusOnElement(text1); | |
| return timeout().then(function() { | |
| assertHasExpectedEventOrder(); | |
| assertElementIsActiveElement(text1); | |
| expect(eventType.BLUR, text1); | |
| expect(eventType.FOCUS, text2); | |
| bot.action.focusOnElement(text2); | |
| return timeout(); | |
| }).then(function() { | |
| assertHasExpectedEventOrder(); | |
| assertElementIsActiveElement(text2); | |
| expect(eventType.BLUR, text2); | |
| expect(eventType.FOCUS, text1); | |
| bot.action.focusOnElement(text1); | |
| return timeout(); | |
| }).then(function() { | |
| assertHasExpectedEventOrder(); | |
| assertElementIsActiveElement(text1); | |
| }); | |
| } | |
| function testShouldRetainFocusIfElementIsAlreadyActive() { | |
| if (!bot.test.isWindowFocused()) { | |
| return; | |
| } | |
| recordBlurAndFocus(text1); | |
| recordBlurAndFocus(text2); | |
| expect(eventType.FOCUS, text1); | |
| bot.action.focusOnElement(text1); | |
| return timeout().then(function() { | |
| assertHasExpectedEventOrder(); | |
| assertElementIsActiveElement(text1); | |
| bot.action.focusOnElement(text1); | |
| return timeout(); | |
| }).then(function() { | |
| assertHasExpectedEventOrder(); | |
| assertElementIsActiveElement(text1); | |
| }); | |
| } | |
| function testShouldRetainFocusIfFirstFocusableAncestorIsAlreadyActive() { | |
| if (!bot.test.isWindowFocused()) { | |
| return; | |
| } | |
| var parent = document.getElementById('hideOnBlur'); | |
| var child = document.getElementById('hideOnBlurChild'); | |
| recordBlurAndFocus(parent); | |
| expect(eventType.FOCUS, parent); | |
| bot.action.focusOnElement(parent); | |
| return timeout().then(function() { | |
| assertHasExpectedEventOrder(); | |
| assertElementIsActiveElement(parent); | |
| bot.action.focusOnElement(child); | |
| // No additional events expected. The parent should retain focus. | |
| return timeout(); | |
| }).then(function() { | |
| assertHasExpectedEventOrder(); | |
| assertElementIsActiveElement(parent); | |
| bot.action.focusOnElement(text1); | |
| expect(eventType.BLUR, parent); | |
| return timeout(); | |
| }).then(function() { | |
| assertHasExpectedEventOrder(); | |
| }); | |
| } | |
| function testFocusOnHiddenElementThrows() { | |
| var element = document.getElementById('invisibleText'); | |
| assertThrows(goog.partial(bot.action.focusOnElement, element)); | |
| } | |
| function testFocusOnDisabledElementThrows() { | |
| var element = document.getElementById('disabledText'); | |
| assertThrows(goog.partial(bot.action.focusOnElement, element)); | |
| } | |
| function testIsFocusable() { | |
| if (goog.userAgent.IE) { | |
| // TODO: Don't skip this. | |
| return; | |
| } | |
| function assertIsFocusable(element, opt_reason) { | |
| assertTrue(opt_reason || '', bot.dom.isFocusable(element)); | |
| } | |
| function assertNotFocusable(element, opt_reason) { | |
| assertFalse(opt_reason || '', bot.dom.isFocusable(element)); | |
| } | |
| goog.array.forEach(bot.dom.FOCUSABLE_FORM_FIELDS_, function(tagName) { | |
| assertIsFocusable(goog.dom.createDom(tagName), | |
| tagName + ' is a focusable form field'); | |
| }); | |
| var div = goog.dom.createDom('DIV'); | |
| assertNotFocusable(div); | |
| div.setAttribute('tabindex', '0'); | |
| assertIsFocusable(div, 'has a tab index of 0'); | |
| div.tabIndex = -1; | |
| assertEquals('-1', bot.dom.getAttribute(div, 'tabindex')); | |
| assertNotFocusable(div, 'has a negative tab index'); | |
| div.tabIndex = 2; | |
| assertEquals('2', bot.dom.getAttribute(div, 'tabindex')); | |
| assertIsFocusable(div, 'has a tab index of 2'); | |
| div.removeAttribute('tabindex'); | |
| assertNull(bot.dom.getAttribute(div, 'tabindex')); | |
| assertNotFocusable(div, 'tab index was removed'); | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <form action="javascript:void(0)"> | |
| <input id="text1" type="text"/> | |
| <input id="text2" type="text"/> | |
| <input id="dummyFocusHolder" type="text"/> | |
| <div> | |
| <div>Input fields for manual testing. Each will report when they receive or | |
| lose focus.</div> | |
| Test 1: <input id="test1" type="text"/> | |
| Test 2: <input id="test2" type="text"/> | |
| <div> | |
| <button id="focusDance">Click to simulate focus change</button> | |
| <button id="clearTestLog">Click to clear log</button> | |
| <button id="opennewwindow" onclick="window.open(window.location)"> | |
| Re-run test in a new window</button> | |
| </div> | |
| <div style="border:1px solid black; margin: 5px; padding: 5px; width:50%"> | |
| <div style="border-bottom: 1px solid black">Test Log</div> | |
| <div id="testLog"></div> | |
| </div> | |
| <div> | |
| <div><strong>Invisible textbox</strong></div> | |
| <input id="invisibleText" type="text" style="visibility:hidden"/> | |
| </div> | |
| <div> | |
| <div><strong>Disabled textbox</strong></div> | |
| <input id="disabledText" type="text" value="I'm disabled" disabled/> | |
| </div> | |
| <div id="hideOnBlur" tabindex="0" | |
| style="width: 100px; height: 100px; border: 1px solid red" | |
| onblur="this.style.display = 'none';"> | |
| <div id="hideOnBlurChild" | |
| style="width: 30px; height: 30px; border: 1px solid blue"> | |
| x | |
| </div> | |
| Focusable. Will hide when focus is lost. | |
| </div> | |
| <script type="text/javascript"> | |
| function log(msg) { | |
| goog.dom.$('testLog').appendChild( | |
| goog.dom.createDom('div', null, | |
| goog.dom.createTextNode(msg))); | |
| } | |
| function reportBlurFocus(e) { | |
| var et = eventType; | |
| goog.events.listen(e, [et.FOCUS, et.BLUR], function(e) { | |
| log(e.type + ' on ' + e.target.id); | |
| }); | |
| } | |
| reportBlurFocus(goog.dom.$('test1')); | |
| reportBlurFocus(goog.dom.$('test2')); | |
| goog.events.listen(goog.dom.$('focusDance'), eventType.CLICK, | |
| function(e) { | |
| log('starting focus dance in .5s'); | |
| log(' will focus on 1, then 2, then 1 again'); | |
| window.setTimeout(function() { | |
| bot.action.focusOnElement(goog.dom.$('test1')); | |
| bot.action.focusOnElement(goog.dom.$('test2')); | |
| bot.action.focusOnElement(goog.dom.$('test1')); | |
| log('if you see this before event logs, then events are ' + | |
| 'fired in a separate event loop'); | |
| }, 500); | |
| }); | |
| goog.events.listen(goog.dom.$('clearTestLog'), eventType.CLICK, | |
| function(e) { | |
| goog.dom.removeChildren(goog.dom.$('testLog')); | |
| }); | |
| </script> | |
| </div> | |
| </form> | |
| </body> | |
| </html> |