UNPKG

jsaction

Version:

Google's event delegation library

1,539 lines (1,271 loc) 54.9 kB
// Copyright 2007 Google Inc. All rights reserved. /** */ /** @suppress {extraProvide} */ goog.provide('jsaction.EventContractTest'); goog.setTestOnly('jsaction.EventContractTest'); goog.require('goog.testing.MockClock'); goog.require('goog.testing.MockControl'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.events.Event'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.mockmatchers'); goog.require('goog.testing.mockmatchers.IgnoreArgument'); goog.require('goog.testing.mockmatchers.SaveArgument'); goog.require('goog.testing.recordFunction'); goog.require('goog.userAgent'); goog.require('jsaction'); goog.require('jsaction.Attribute'); goog.require('jsaction.EventContract'); goog.require('jsaction.EventType'); goog.require('jsaction.Property'); goog.require('jsaction.event'); goog.require('jsaction.replayEvent'); var mockClock_; var mockControl_; var propertyReplacer_; var isFunction_ = goog.testing.mockmatchers.isFunction; var SaveArgument_ = goog.testing.mockmatchers.SaveArgument; function setUp() { mockControl_ = new goog.testing.MockControl; mockClock_ = new goog.testing.MockClock(true); propertyReplacer_ = new goog.testing.PropertyReplacer(); jsaction.EventContract.USE_EVENT_PATH = true; jsaction.EventContract.A11Y_CLICK_SUPPORT = true; jsaction.EventContract.MOUSE_SPECIAL_SUPPORT = true; jsaction.EventContract.STOP_PROPAGATION = true; jsaction.EventContract.FAST_CLICK_SUPPORT = true; } function tearDown() { mockControl_.$tearDown(); mockClock_.dispose(); propertyReplacer_.reset(); jsaction.EventContract.resetFastClickNode_(); jsaction.EventContract.CUSTOM_EVENT_SUPPORT = false; } function elem(id) { return document.getElementById(id); } function createElement(tag) { return document.createElement(tag); } function testAddContainerInstallsHandlersForRegisteredEvents() { var container = elem('container2'); var mockAddEvent = mockControl_.createMethodMock( jsaction.event, 'addEventListener'); // TODO(user): Use jsaction.testing.nativeEvents here instead of mocks. mockAddEvent(container, jsaction.EventType.CLICK, isFunction_); mockAddEvent(container, jsaction.EventType.KEYDOWN, isFunction_); // From jsaction.EventContract.FAST_CLICK mockAddEvent(container, jsaction.EventType.TOUCHSTART, isFunction_); mockAddEvent(container, jsaction.EventType.TOUCHEND, isFunction_); mockAddEvent(container, jsaction.EventType.TOUCHMOVE, isFunction_); mockAddEvent(container, 'mousemove', isFunction_); mockAddEvent(container, 'webkitTransitionEnd', isFunction_); mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addEvent(jsaction.EventType.CLICK); e.addEvent('mousemove'); e.addEvent('transitionEnd', 'webkitTransitionEnd'); e.addContainer(container); mockControl_.$verifyAll(); } function testAddEventInstallsHandlersOnExistingContainers() { var container1 = elem('container'); var container2 = elem('container2'); var mockAddEvent = mockControl_.createMethodMock( jsaction.event, 'addEventListener'); mockAddEvent(container1, 'mousemove', isFunction_); mockAddEvent(container2, 'mousemove', isFunction_); mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container1); e.addContainer(container2); e.addEvent('mousemove'); mockControl_.$verifyAll(); } function testGetEventHandler() { var container = elem('container2'); var mockAddEvent = mockControl_.createMethodMock( jsaction.event, 'addEventListener'); var clickHandler = new SaveArgument_(); var a11yClickHandler = new SaveArgument_(); var mousemoveHandler = new SaveArgument_(); mockAddEvent(container, jsaction.EventType.CLICK, clickHandler); mockAddEvent(container, jsaction.EventType.KEYDOWN, a11yClickHandler); mockAddEvent(container, jsaction.EventType.TOUCHSTART, isFunction_); mockAddEvent(container, jsaction.EventType.TOUCHEND, isFunction_); mockAddEvent(container, jsaction.EventType.TOUCHMOVE, isFunction_); mockAddEvent(container, 'mousemove', mousemoveHandler); mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addEvent(jsaction.EventType.CLICK); e.addEvent('mousemove'); e.addContainer(container); mockControl_.$verifyAll(); assertEquals(clickHandler.arg, e.handler(jsaction.EventType.CLICK)); assertEquals(a11yClickHandler.arg, e.handler(jsaction.EventType.KEYDOWN)); assertEquals(mousemoveHandler.arg, e.handler('mousemove')); assertUndefined(e.handler('does-not-exist')); } function testDispatchCallbackGetsEventInfo() { var container = elem('container2'); var targetElement = elem('target2'); var actionElement = elem('host2'); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container); e.addEvent('click'); e.dispatchTo(dispatchCallback); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: targetElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals('clickaction', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(targetElement, eventInfo.targetElement); assertEquals(actionElement, eventInfo.actionElement); mockControl_.$verifyAll(); } function testCustomEvent() { var container = elem('container16'); var targetElement = elem('target16'); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; var e = new jsaction.EventContract; e.addContainer(container); e.addEvent('event-16'); e.dispatchTo(dispatchCallback); if (!document.createEvent) { // We don't care about ie8 because this feature is primarily used by // Polymer, which doesn't support ie8 return; } var event = document.createEvent('Event'); event.initEvent('event-16', true, true); targetElement.dispatchEvent(event); assertEquals('action16', eventInfo.action); } function testFindActionStopAtMatch() { var container = elem('container4'); var target = elem('target4'); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container); e.addEvent(jsaction.EventType.CLICK); e.dispatchTo(dispatchCallback); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: target, event: jsaction.createEvent({type: 'click'}) }); assertEquals('clickaction_child', eventInfo.action); mockControl_.$verifyAll(); } function testFindActionStopAtMatch_EventPath() { var container = elem('container14'); assertNotNull(container); var pathitem = elem('pathitem14'); var target = elem('target14'); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container); e.addEvent(jsaction.EventType.CLICK); e.dispatchTo(dispatchCallback); var event = jsaction.createEvent( {type: 'click', path: [target, pathitem, container]}); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: target, event: event }); assertEquals('action14', eventInfo.action); mockControl_.$verifyAll(); } function testFindActionStopAtMatchWithOwner_EventPath() { var container = elem('container14'); var pathitem = elem('pathitem14'); pathitem[jsaction.Property.OWNER] = container; var target = elem('target14'); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container); e.addEvent(jsaction.EventType.CLICK); e.dispatchTo(dispatchCallback); var event = jsaction.createEvent( {type: 'click', path: [target, pathitem, container]}); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: target, event: event }); assertEquals('action14', eventInfo.action); mockControl_.$verifyAll(); } function testFindActionMatchOnSelfAndBubble_EventPath() { jsaction.EventContract.STOP_PROPAGATION = false; var container = elem('container15'); assertNotNull(container); var pathitem = elem('pathitem15'); var target = elem('target15'); // TODO(user): How does one test the case where the handler // bubbles the event? var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container); e.addEvent(jsaction.EventType.CLICK); e.dispatchTo(dispatchCallback); var event = jsaction.createEvent( {type: 'click', path: [target, pathitem, container]}); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: target, event: event }); assertEquals('action15', eventInfo.action); mockControl_.$verifyAll(); } function testFindActionStopAtMatchOnSelf_EventPath() { var container = elem('container15'); assertNotNull(container); var pathitem = elem('pathitem15'); var target = elem('target15'); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; return false; }; mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container); e.addEvent(jsaction.EventType.CLICK); e.dispatchTo(dispatchCallback); var event = jsaction.createEvent( {type: 'click', path: [target, pathitem, container]}); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: target, event: event }); assertEquals('action15', eventInfo.action); mockControl_.$verifyAll(); } function testFindActionAliased() { var container = elem('container17'); var target = elem('target17'); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; mockControl_.$replayAll(); var e = new jsaction.EventContract; e.addContainer(container); // A better examples is e.addEvent('animationend', 'webkitanimationend'); // but IE8 doesn't like that, so we don't use it for this test. e.addEvent('clickAlias', 'click'); e.dispatchTo(dispatchCallback); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: target, event: jsaction.createEvent({type: 'click'}) }); assertEquals('clicked', eventInfo.action); mockControl_.$verifyAll(); } function testPreventDefaultForClickOnAnchorChild() { var container = elem('container6'); var target = elem('inside_anchor6'); var mockPreventDefault = mockControl_.createMethodMock( jsaction.event, 'preventDefault'); // preventDefault should be called even if the target is not an anchor, // because the detected action is attached to an anchor which is a parent of // the target. mockPreventDefault(new goog.testing.mockmatchers.IgnoreArgument()); mockControl_.$replayAll(); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; var e = new jsaction.EventContract; e.addContainer(container); e.addEvent(jsaction.EventType.CLICK); e.dispatchTo(dispatchCallback); // Replay a fake event to trigger a DOM event on the target element. jsaction.replayEvent({ targetElement: target, event: jsaction.createEvent({type: 'click'}) }); assertEquals('myaction', eventInfo.action); mockControl_.$verifyAll(); } function testPreventDefaultForModClickOnAnchorChild() { var container = elem('container6'); var target = elem('inside_anchor6'); var mockPreventDefault = mockControl_.createMethodMock( jsaction.event, 'preventDefault'); // preventDefault should be called even if the target is not an anchor, // because the detected action is attached to an anchor which is a parent of // the target. mockPreventDefault(new goog.testing.mockmatchers.IgnoreArgument()). $atLeastOnce(); mockControl_.$replayAll(); var eventInfo = null; var dispatchCallback = function(ei) { eventInfo = ei; }; var e = new jsaction.EventContract; e.addContainer(container); e.addEvent(jsaction.EventType.CLICK); e.dispatchTo(dispatchCallback); // Replay a fake modified mouse button click event to trigger a DOM event on // the target element. var event = jsaction.createEvent({type: 'click', shiftKey: true}); jsaction.triggerEvent(target, event); assertEquals('myclickmodaction', eventInfo.action); mockControl_.$verifyAll(); } function testAddContainerChildOfExistingContainer() { var e = new jsaction.EventContract; assertNotNull(e.addContainer(elem('container'))); assertNotNull(e.addContainer(elem('innercontainer'))); assertNotNull(e.addContainer(elem('container2'))); } function testNestedContainersWithStopPropagation() { jsaction.EventContract.STOP_PROPAGATION = true; var outerContainer = elem('outercontainer11'); var innerContainer = elem('innercontainer11'); var innerActionElement = elem('inneraction11'); var outerActionElement = elem('outeraction11'); var eventInfo = null; var count = 0; var dispatchCallback = function(ei, isGlobalHandler) { if (!isGlobalHandler) { eventInfo = ei; count++; } }; var e = new jsaction.EventContract; // Add both the inner and outer container to the event contract. var outerContractContainer = e.addContainer(outerContainer); e.addContainer(innerContainer); e.addEvent('click'); e.dispatchTo(dispatchCallback); // Replay a fake event to trigger a DOM event on the inner action element. jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('inner', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(innerActionElement, eventInfo.targetElement); assertEquals(innerActionElement, eventInfo.actionElement); // Replay a fake event to trigger a DOM event on the outer action element. eventInfo = null; count = 0; jsaction.replayEvent({ targetElement: outerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('outer', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(outerActionElement, eventInfo.targetElement); assertEquals(outerActionElement, eventInfo.actionElement); // Remove the outer container. e.removeContainer(outerContractContainer); // Replay a fake event to trigger a DOM event on the inner action element. eventInfo = null; count = 0; jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('inner', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(innerActionElement, eventInfo.targetElement); assertEquals(innerActionElement, eventInfo.actionElement); // Replay a fake event to trigger a DOM event on the outer action element. The // outer container was removed so this should do nothing. eventInfo = null; count = 0; jsaction.replayEvent({ targetElement: outerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNull(eventInfo); assertEquals(0, count); } function testNestedContainersWithoutStopPropagation() { jsaction.EventContract.STOP_PROPAGATION = false; var outerContainer = elem('outercontainer11'); var innerContainer = elem('innercontainer11'); var innerActionElement = elem('inneraction11'); var outerActionElement = elem('outeraction11'); var eventInfo = null; count = 0; var dispatchCallback = function(ei, isGlobalHandler) { if (!isGlobalHandler) { eventInfo = ei; count++; } }; var e = new jsaction.EventContract; // Add both the inner and outer container to the event contract. var outerContractContainer = e.addContainer(outerContainer); e.addContainer(innerContainer); e.addEvent('click'); e.dispatchTo(dispatchCallback); // Replay a fake event to trigger a DOM event on the inner action element. jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('inner', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(innerActionElement, eventInfo.targetElement); assertEquals(innerActionElement, eventInfo.actionElement); // Replay a fake event to trigger a DOM event on the outer action element. eventInfo = null; count = 0; jsaction.replayEvent({ targetElement: outerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('outer', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(outerActionElement, eventInfo.targetElement); assertEquals(outerActionElement, eventInfo.actionElement); // Remove the outer container. e.removeContainer(outerContractContainer); // Replay a fake event to trigger a DOM event on the inner action element. eventInfo = null; count = 0; jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('inner', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(innerActionElement, eventInfo.targetElement); assertEquals(innerActionElement, eventInfo.actionElement); // Replay a fake event to trigger a DOM event on the outer action element. The // outer container was removed so this should do nothing. eventInfo = null; count = 0; jsaction.replayEvent({ targetElement: outerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNull(eventInfo); assertEquals(0, count); } function testNestedContainersWithoutStopPropagation_AddOuterContainerLast() { jsaction.EventContract.STOP_PROPAGATION = false; var outerContainer = elem('outercontainer11'); var innerContainer = elem('innercontainer11'); var innerActionElement = elem('inneraction11'); var outerActionElement = elem('outeraction11'); var eventInfo = null; var count = 0; var dispatchCallback = function(ei, isGlobalHandler) { if (!isGlobalHandler) { eventInfo = ei; count++; } }; var e = new jsaction.EventContract; // Add both the inner and outer container to the event contract. Add the outer // container last. var innerContractContainer = e.addContainer(innerContainer); var outerContractContainer = e.addContainer(outerContainer); e.addEvent('click'); e.dispatchTo(dispatchCallback); // Replay a fake event to trigger a DOM event on the inner action element. The // event should only be handled once. jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('inner', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(innerActionElement, eventInfo.targetElement); assertEquals(innerActionElement, eventInfo.actionElement); e.removeContainer(outerContractContainer); eventInfo = null; count = 0; // Replay a fake event to trigger a DOM event on the inner action element. jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNotNull(eventInfo); assertEquals(1, count); assertEquals('inner', eventInfo.action); assertEquals(jsaction.EventType.CLICK, eventInfo.eventType); assertEquals(innerActionElement, eventInfo.targetElement); assertEquals(innerActionElement, eventInfo.actionElement); e.removeContainer(innerContractContainer); eventInfo = null; count = 0; jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNull(eventInfo); assertEquals(0, count); } function testNestedContainersWithoutStopPropagation_RemoveContainers() { jsaction.EventContract.STOP_PROPAGATION = false; var outerContainer = elem('outercontainer11'); var innerContainer = elem('innercontainer11'); var innerActionElement = elem('inneraction11'); var outerActionElement = elem('outeraction11'); var eventInfo = null; var count = 0; var dispatchCallback = function(ei, isGlobalHandler) { if (!isGlobalHandler) { eventInfo = ei; count++; } }; var e = new jsaction.EventContract; // Add both the inner and outer container to the event contract. Add the outer // container last. var innerContractContainer = e.addContainer(innerContainer); var outerContractContainer = e.addContainer(outerContainer); e.addEvent('click'); e.dispatchTo(dispatchCallback); e.removeContainer(innerContractContainer); e.removeContainer(outerContractContainer); // Replay a fake event to trigger a DOM event on the inner action element. The // containers were removed so this should do nothing. jsaction.replayEvent({ targetElement: innerActionElement, event: jsaction.createEvent({type: 'click'}) }); assertNull(eventInfo); } function testEventContractMaybeCreateEventInfoAddsTimestamp() { var container = elem('container10'); var element = document.getElementById('action10-1'); var event = { type: 'click', srcElement: element, target: element, timeStamp: 1234 }; mockControl_.createMethodMock(jsaction.event, 'isModifiedClickEvent'); jsaction.event.isModifiedClickEvent(event).$returns(false); mockControl_.createMethodMock(jsaction.event, 'isActionKeyEvent'); jsaction.event.isActionKeyEvent(event).$returns(false); mockControl_.createMethodMock(goog, 'now'); goog.now().$returns(1234); mockControl_.$replayAll(); var eventInfo = jsaction.EventContract.createEventInfo_( 'click', event, container); assertEquals('click', eventInfo.eventType); assertEquals('action10', eventInfo.action); assertEquals(1234, eventInfo.timeStamp); mockControl_.$verifyAll(); } function testEventContractMaybeCreateEventInfoClick() { var container = elem('container8'); var element = document.getElementById('action8-1'); var event = { type: 'click', srcElement: element, target: element }; mockControl_.createMethodMock(jsaction.event, 'isModifiedClickEvent'); jsaction.event.isModifiedClickEvent(event).$returns(false); mockControl_.createMethodMock(jsaction.event, 'isActionKeyEvent'); jsaction.event.isActionKeyEvent(event).$returns(false); mockControl_.$replayAll(); var eventInfo = jsaction.EventContract.createEventInfo_( 'click', event, container); assertEquals('click', eventInfo.eventType); assertEquals('action8', eventInfo.action); mockControl_.$verifyAll(); } function testEventContractMaybeCreateEventInfoClickMod() { var container = elem('container8'); var element = document.getElementById('action8-2'); var event = { type: 'click', srcElement: element, target: element }; mockControl_.createMethodMock(jsaction.event, 'isModifiedClickEvent'); jsaction.event.isModifiedClickEvent(event).$returns(true); mockControl_.$replayAll(); var eventInfo = jsaction.EventContract.createEventInfo_( 'click', event, container); assertEquals('clickmod', eventInfo.eventType); assertEquals('action8', eventInfo.action); mockControl_.$verifyAll(); } function testEventContractMaybeCreateEventInfoClickKey() { var container = elem('container8'); var element = document.getElementById('action8-1'); var event = { type: jsaction.EventType.KEYDOWN, srcElement: element, target: element }; mockControl_.createMethodMock(jsaction.event, 'isActionKeyEvent'); jsaction.event.isActionKeyEvent(event).$returns(true); mockControl_.$replayAll(); var eventInfo = jsaction.EventContract.createEventInfo_( jsaction.EventType.KEYDOWN, event, container); assertEquals('click', eventInfo.eventType); assertEquals('action8', eventInfo.action); mockControl_.$verifyAll(); } function testEventContractMaybeCreateEventInfoKeypress() { var container = elem('container8'); var element = document.getElementById('action8-3'); var event = { type: jsaction.EventType.KEYDOWN, srcElement: element, target: element }; mockControl_.createMethodMock(jsaction.event, 'isActionKeyEvent'); jsaction.event.isActionKeyEvent(event).$returns(false); mockControl_.$replayAll(); var eventInfo = jsaction.EventContract.createEventInfo_( jsaction.EventType.KEYDOWN, event, container); assertEquals(jsaction.EventType.KEYDOWN, eventInfo.eventType); assertEquals('action8', eventInfo.action); mockControl_.$verifyAll(); } function testEventContractMaybeCreateEventInfoMouseenter() { var container = elem('container9'); var element = document.getElementById('action9-1'); var event = new goog.testing.events.Event( jsaction.EventType.MOUSEOVER, element); event.relatedTarget = container; var eventInfo = jsaction.EventContract.createEventInfo_( jsaction.EventType.MOUSEENTER, event, container); assertEquals(jsaction.EventType.MOUSEENTER, eventInfo.eventType); assertEquals('action9', eventInfo.action); } function testEventContractMaybeCreateEventInfoNotMouseenter() { var container = elem('container9'); var element = document.getElementById('action9-1'); var event = new goog.testing.events.Event( jsaction.EventType.MOUSEOVER, container); event.relatedTarget = element; assertNull(jsaction.EventContract.createEventInfo_( jsaction.EventType.MOUSEENTER, event, container).actionElement); } function testEventContractMaybeCreateEventInfoMouseleave() { var container = elem('container9'); var element = document.getElementById('action9-2'); var event = new goog.testing.events.Event( jsaction.EventType.MOUSEOUT, element); event.relatedTarget = container; var eventInfo = jsaction.EventContract.createEventInfo_( jsaction.EventType.MOUSELEAVE, event, container); assertEquals(jsaction.EventType.MOUSELEAVE, eventInfo.eventType); assertEquals('action9', eventInfo.action); } function testEventContractMaybeCreateEventInfoNotMouseleave() { var container = elem('container9'); var element = document.getElementById('action9-2'); var event = new goog.testing.events.Event( jsaction.EventType.MOUSEOUT, container); event.relatedTarget = element; assertNull(jsaction.EventContract.createEventInfo_( jsaction.EventType.MOUSELEAVE, event, container).actionElement); } function testEventContractMaybeCreateEventInfoFastClick() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; } }; // Touch somewhere else, but that sequence will never terminate. assertNull(sendEvent( jsaction.EventType.TOUCHSTART, otherElement, container).actionElement); assertEquals(otherElement.parentNode, jsaction.EventContract.fastClickNode_.node); // Touch an element. assertNull(sendEvent( jsaction.EventType.TOUCHSTART, element, container).actionElement); assertEquals(actionNode, jsaction.EventContract.fastClickNode_.node); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives, but it's canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is issued. assertTrue(clickDispatched); } function testEventContractPreventDefaultCancelFastClick() { var container = elem('container12'); var element = elem('action12-1'); var actionNode = element.parentNode; var clickDispatched = false; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; } }; // Touch an element. assertNull(sendEvent( jsaction.EventType.TOUCHSTART, element, container).actionElement); assertEquals(actionNode, jsaction.EventContract.fastClickNode_.node); var touchendEvent = createEvent(jsaction.EventType.TOUCHEND, element); touchendEvent.preventDefault(); var eventInfo = jsaction.EventContract.createEventInfo_( jsaction.EventType.TOUCHEND, touchendEvent, container); // TOUCHEND arrives, but prevent default on touchend event. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is not issued. assertFalse(clickDispatched); assertNull(jsaction.EventContract.fastClickNode_); } function testEventContractMaybeCreateEventInfoFastClick_touchstartStopsMagic() { var container = elem('container12'); var element = elem('action12-3'); assertNotNull(sendEvent( jsaction.EventType.TOUCHSTART, element, container).actionElement); assertNull(jsaction.EventContract.fastClickNode_); } function testEventContractMaybeCreateEventInfoFastClick_needsClickEvent() { var container = elem('container12'); var element = elem('action12-4'); assertNull(sendEvent( jsaction.EventType.TOUCHSTART, element, container).actionElement); assertNull(jsaction.EventContract.fastClickNode_); } function testEventContractMaybeCreateEventInfoFastClick_touchmoveTolerates() { var container = elem('container12'); var element = elem('action12-1'); // touchstart var event = new goog.testing.events.Event('touchstart', element); event.touches = [{ clientX: 100, clientY: 100 }, {}]; jsaction.EventContract.createEventInfo_(event.type, event, container); assertEquals(element.parentNode, jsaction.EventContract.fastClickNode_.node); // touchmove: less than 4px Manhattan move is tolerated var event = new goog.testing.events.Event('touchmove', element); event.touches = [{ clientX: 102, clientY: 102 }, {}]; jsaction.EventContract.createEventInfo_(event.type, event, container); assertNotNull(jsaction.EventContract.fastClickNode_); } function testEventContractMaybeCreateEventInfoFastClick_touchmoveCancels() { var container = elem('container12'); var element = elem('action12-1'); // touchstart var event = new goog.testing.events.Event('touchstart', element); event.touches = [{ clientX: 100, clientY: 100 }, {}]; jsaction.EventContract.createEventInfo_(event.type, event, container); assertEquals(element.parentNode, jsaction.EventContract.fastClickNode_.node); // touchmove: over 4px Manhattan move cancels fast click var event = new goog.testing.events.Event('touchmove', element); event.touches = [{ clientX: 103, clientY: 102 }, {}]; jsaction.EventContract.createEventInfo_(event.type, event, container); assertNull(jsaction.EventContract.fastClickNode_); } function testEventContractMaybeCreateEventInfoFastClick_timesout() { var container = elem('container12'); var element = elem('action12-1'); sendEvent(jsaction.EventType.TOUCHSTART, element, container); assertEquals(element.parentNode, jsaction.EventContract.fastClickNode_.node); mockClock_.tick(400); assertNull(jsaction.EventContract.fastClickNode_); assertNull(sendEvent( jsaction.EventType.TOUCHEND, element, container).actionElement); } function testEventContractMaybeCreateEventInfoFastClick_specialElements() { assertNotNull( sendEvent(jsaction.EventType.TOUCHSTART, elem('text12'), container)); assertNull(jsaction.EventContract.fastClickNode_); assertNotNull( sendEvent(jsaction.EventType.TOUCHSTART, elem('textarea12'), container)); assertNull(jsaction.EventContract.fastClickNode_); assertNotNull( sendEvent(jsaction.EventType.TOUCHSTART, elem('search12'), container)); assertNull(jsaction.EventContract.fastClickNode_); assertNotNull( sendEvent(jsaction.EventType.TOUCHSTART, elem('password12'), container)); assertNull(jsaction.EventContract.fastClickNode_); } function testFastClick_distanceCancelsFastClick() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; var clickEvent = null; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; clickEvent = event; } }; sendEvent(jsaction.EventType.TOUCHSTART, element, container, {clientX: 100, clientY: 100}); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container, {clientX: 105, clientY: 105}); // TOUCHEND arrives, and is not canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertFalse(eventInfo.event.defaultPrevented); // CLICK event is not issued. assertFalse(clickDispatched); assertNull(clickEvent); assertNull(jsaction.EventContract.preventingMouseEvents_); } function testFastClick_allowFastClick() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; var clickEvent = null; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; clickEvent = event; } }; sendEvent(jsaction.EventType.TOUCHSTART, element, container); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives, but it's canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is issued. assertTrue(clickDispatched); // The "fastclick" event will be allowed to proceed, "fastclick" is still // pending. jsaction.EventContract.sweepupPreventedMouseEvents_(clickEvent); assertFalse(clickEvent.defaultPrevented); assertNotNull(jsaction.EventContract.preventingMouseEvents_); } function testFastClick_cancelFollowingClick() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; } }; sendEvent(jsaction.EventType.TOUCHSTART, element, container); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives, but it's canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is issued. assertTrue(clickDispatched); // Quick follow with a subsequent CLICK event, e.g. as iOS sometimes does. var clickEvent = new goog.testing.events.Event(jsaction.EventType.CLICK, element); jsaction.EventContract.sweepupPreventedMouseEvents_(clickEvent); // Event matched and stopped from propagating, but not canceled. assertTrue(clickEvent.defaultPrevented); assertNull(jsaction.EventContract.preventingMouseEvents_); } function testFastClick_cancelFollowingClick_wrongElement() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; } }; sendEvent(jsaction.EventType.TOUCHSTART, element, container); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives, but it's canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is issued. assertTrue(clickDispatched); // Quick follow with a subsequent CLICK event, e.g. as iOS sometimes does. // However, the target is a different element! var clickEvent = new goog.testing.events.Event(jsaction.EventType.CLICK, otherElement); jsaction.EventContract.sweepupPreventedMouseEvents_(clickEvent); // Event didn't match. assertFalse(clickEvent.defaultPrevented); assertNull(jsaction.EventContract.preventingMouseEvents_); } function testFastClick_cancelFollowingClick_oldTimestamp() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; } }; sendEvent(jsaction.EventType.TOUCHSTART, element, container); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives, but it's canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is issued. assertTrue(clickDispatched); // Follow with a subsequent CLICK event, e.g. as iOS sometimes does. // However, the new event is further in the future! var clickEvent = new goog.testing.events.Event(jsaction.EventType.CLICK, element); jsaction.EventContract.preventingMouseEvents_.timeStamp = goog.now() - 10000; jsaction.EventContract.sweepupPreventedMouseEvents_(clickEvent); // Event didn't match. assertFalse(clickEvent.defaultPrevented); assertNull(jsaction.EventContract.preventingMouseEvents_); } function testFastClick_cancelFollowingClick_touchstartElsewhere() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; } }; sendEvent(jsaction.EventType.TOUCHSTART, element, container); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives, but it's canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is issued. assertTrue(clickDispatched); // Touchstart arrived on a different element. sendEvent(jsaction.EventType.TOUCHSTART, otherElement, container); var clickEvent = new goog.testing.events.Event(jsaction.EventType.CLICK, element); jsaction.EventContract.sweepupPreventedMouseEvents_(clickEvent); // Event didn't match. assertFalse(clickEvent.defaultPrevented); assertNull(jsaction.EventContract.preventingMouseEvents_); } function testFastClick_retargetClickWithWrongTarget() { if (!document.createEvent) { // We don't care about ie8 because this feature is only used by touch // devices return; } var container = elem('container12'); var element = elem('action12-1'); var eventDispatched = null; element.dispatchEvent = function(event) { if (event.type == 'click') { eventDispatched = event; } }; var otherElement = elem('action12-2'); var actionNode = element.parentNode; sendEvent(jsaction.EventType.TOUCHSTART, element, container, { clientX: 99, clientY: 99 }); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container, { clientX: 101, clientY: 101 }); // TOUCHEND arrives, but it's canceled. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertTrue(eventInfo.event.defaultPrevented); // CLICK event is issued. assertNotNull(eventDispatched); // Quick follow with a subsequent CLICK event, e.g. as iOS sometimes does. // However, the new event's target is different, while it's close to the // original event! var clickEvent = new goog.testing.events.Event(jsaction.EventType.CLICK, otherElement); clickEvent.clientX = 101; clickEvent.clientY = 101; jsaction.EventContract.sweepupPreventedMouseEvents_(clickEvent); // Event matched, stopped AND canceled since the target doesn't match. assertTrue(clickEvent.defaultPrevented); assertNull(jsaction.EventContract.preventingMouseEvents_); } function testFastClick_clearSelection() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; var clickEvent = null; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; clickEvent = event; } }; var origGetSelection = window.getSelection; var removeSelectionCalled = false; propertyReplacer_.replace(window, 'getSelection', function() { return { removeAllRanges: function() { removeSelectionCalled = true; } }; }); var getSelectionReplaced = origGetSelection === window.getSelection; sendEvent(jsaction.EventType.TOUCHSTART, element, container); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives and it's canceled. CLICK event is issued. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertTrue(eventInfo.event.defaultPrevented); assertTrue(clickDispatched); // Remove Selection API called. if (getSelectionReplaced) { assertTrue(removeSelectionCalled); } } function testFastClick_clearSelectionNotCalled() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; var clickDispatched = false; var clickEvent = null; element.dispatchEvent = function(event) { if (event.type == 'click') { clickDispatched = true; clickEvent = event; // Cancel the issued CLICK event. event.preventDefault(); } }; var origGetSelection = window.getSelection; var removeSelectionCalled = false; propertyReplacer_.replace(window, 'getSelection', function() { return { removeAllRanges: function() { removeSelectionCalled = true; } }; }); var getSelectionReplaced = origGetSelection === window.getSelection; sendEvent(jsaction.EventType.TOUCHSTART, element, container); var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container); // TOUCHEND arrives and it's canceled. CLICK event is issued. assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertTrue(eventInfo.event.defaultPrevented); assertTrue(clickDispatched); // Remove Selection API was not been called. if (getSelectionReplaced) { assertFalse(removeSelectionCalled); } } function testPreventMouseEvents_notPrevented() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; // Send touchend var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container, {clientX: 0, clientY: 0}); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertFalse(eventInfo.event.defaultPrevented); jsaction.EventContract.afterEventHandler_(eventInfo); assertNull(jsaction.EventContract.preventingMouseEvents_); var otherEvent; otherEvent = createEvent('mouseup', element, {clientX: 0, clientY: 0}); jsaction.EventContract.sweepupPreventedMouseEvents_(otherEvent); assertFalse(otherEvent.defaultPrevented); otherEvent = createEvent('mousedown', element, {clientX: 0, clientY: 0}); jsaction.EventContract.sweepupPreventedMouseEvents_(otherEvent); assertFalse(otherEvent.defaultPrevented); otherEvent = createEvent('click', element, {clientX: 0, clientY: 0}); jsaction.EventContract.sweepupPreventedMouseEvents_(otherEvent); assertFalse(otherEvent.defaultPrevented); } function testPreventMouseEvents_prevented() { var container = elem('container12'); var element = elem('action12-1'); var otherElement = elem('action12-2'); var actionNode = element.parentNode; // Send touchend var eventInfo = sendEvent(jsaction.EventType.TOUCHEND, element, container, {clientX: 0, clientY: 0}); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.eventType); assertEquals(jsaction.EventType.TOUCHEND, eventInfo.event.type); assertFalse(eventInfo.event.defaultPrevented); eventInfo.event._preventMouseEvents(); jsaction.EventContract.afterEventHandler_(eventInfo); assertNotNull(jsaction.EventContract.preventingMouseEvents_); var otherEvent; otherEvent = createEvent('mouseup', element, {clientX: 0, clientY: 0}); jsaction.EventContract.sweepupPreventedMouseEvents_(otherEvent); assertTrue(otherEvent.defaultPrevented); assertNotNull(jsaction.EventContract.preventingMouseEvents_); otherEvent = createEvent('mousedown', element, {clientX: 0, clientY: 0}); jsaction.EventContract.sweepupPreventedMouseEvents_(otherEvent); assertTrue(otherEvent.defaultPrevented); assertNotNull(jsaction.EventContract.preventingMouseEvents_); otherEvent = createEvent('click', element, {clientX: 0, clientY: 0}); jsaction.EventContract.sweepupPreventedMouseEvents_(otherEvent); assertTrue(otherEvent.defaultPrevented); assertNull(jsaction.EventContract.preventingMouseEvents_); } function createEvent(type, target, opt_template) { var event = new goog.testing.events.Event(type, target); if (opt_template) { event.clientX = opt_template.clientX || 0; event.clientY = opt_template.clientY || 0; if (type == 'touchstart' || type == 'touchend') { event.touches = [{ clientX: event.clientX, clientY: event.clientY }]; } } return event; } function sendEvent(type, target, container, opt_template) { var event = createEvent(type, target, opt_template); return jsaction.EventContract.createEventInfo_(type, event, container); } function testEventContractGetAction() { var target = elem('host5'); var container = elem('outercontainer5'); var action = jsaction.EventContract.defaultEventType_; var actionFound = jsaction.EventContract.getAction_( target, action, container); assertEquals('namespace5.clickaction', actionFound.action); } function testEventContractOwnerTraversal() { var container = elem('container8'); var owned = elem('owned'); var element = elem('action8-1'); owned[jsaction.Property.OWNER] = element; var event = { type: 'click', srcElement: owned, target: owned }; var eventInfo = jsaction.EventContract.createEventInfo_( 'click', event, container); assertEquals('click', eventInfo.eventType); assertEquals('action8', eventInfo.action); } function testEventContractGetAction_NoCache() { var target = elem('action7'); var container = elem('container7'); var action = jsaction.EventContract.defaultEventType_; var actionFound = jsaction.EventContract.getAction_( target, action, container); assertEquals('namespace7.action7', actionFound.action); var oldName = target.getAttribute('jsaction'); var name = 'action7updated'; target.setAttribute('jsaction', name); jsaction.Cache.clear(target); var actionFound = jsaction.EventContract.getAction_( target, action, container); assertEquals('namespace7.' + name, actionFound.action); target.setAttribute('jsaction', oldName); } function testEventContractGetActionEmptySubstrings() { // Make sure that an empty substring (caused by a trailing semicolon) // does not overwrite the default action. var elem = createElement('div'); elem.setAttribute('jsaction', 'foo;'); var actionInfo = jsaction.EventContract.getAction_( elem, jsaction.EventContract.defaultEventType_, elem); assertEquals('foo', actionInfo.action); } function testEventContractGetActionClickKeyMapsToClick() { var elem = createElement('div'); elem.setAttribute('jsaction', 'foo;'); var actionInfo = jsaction.EventContract.getAction_( elem, 'clickkey', elem); assertEquals('click', actionInfo.eventType); assertEquals('foo', actionInfo.action); } function testEventContractGetActionClickMapsToClickOnlyIfNoClick() { var elem = createElement('div'); elem.setAttribute('jsaction', 'clickonly:foo;'); var actionInfo = jsaction.EventContract.getAction_( elem, 'click', elem); assertEquals('clickonly', actionInfo.eventType); assertEquals('foo', actionInfo.action); } function testEventContractGetActionClickMapsToClickIfBothClickAndClickOnly() { var elem = createElement('div'); elem.setAttribute('jsaction', 'foo;clickonly:bar;'); var actionInfo = jsaction.EventContract.getAction_( elem, 'click', elem); assertEquals('click', actionInfo.eventType); assertEquals('foo', actionInfo.act