Merge lp:~divmod-dev/divmod.org/athena-events-806545 into lp:divmod.org

Proposed by Jonathan Jacobs
Status: Needs review
Proposed branch: lp:~divmod-dev/divmod.org/athena-events-806545
Merge into: lp:divmod.org
Diff against target: 989 lines (+870/-13)
8 files modified
Nevow/nevow/athena.py (+1/-1)
Nevow/nevow/js/Divmod/Runtime/__init__.js (+33/-0)
Nevow/nevow/js/Divmod/Test/TestRuntime.js (+94/-0)
Nevow/nevow/js/Nevow/Athena/__init__.js (+231/-11)
Nevow/nevow/js/Nevow/Test/TestEvent.js (+504/-0)
Nevow/nevow/js/Nevow/Test/TestWidget.js (+2/-0)
Nevow/nevow/test/test_athena.py (+1/-1)
Nevow/nevow/test/test_javascript.py (+4/-0)
To merge this branch: bzr merge lp:~divmod-dev/divmod.org/athena-events-806545
Reviewer Review Type Date Requested Status
Divmod-dev Pending
Review via email: mp+67455@code.launchpad.net

Description of the change

Wrap DOM events in a universal way and pass these to Athena event handlers.

To post a comment you must log in.
2680. By Jonathan Jacobs

Fix typo.

2681. By Jonathan Jacobs

Fix failing test related to event changes.

Unmerged revisions

2681. By Jonathan Jacobs

Fix failing test related to event changes.

2680. By Jonathan Jacobs

Fix typo.

2679. By Jonathan Jacobs

Fix failing tests.

2678. By Jonathan Jacobs

Wrap DOM events in a universal event wrapper.

2677. By Jonathan Jacobs

Implement getMouseButtonsFromEvent for Platform and InternetExplorer in Divmod.Runtime.

2676. By Jonathan Jacobs

Pass the raw browser event object to Athena event handlers.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Nevow/nevow/athena.py'
2--- Nevow/nevow/athena.py 2010-07-12 19:00:11 +0000
3+++ Nevow/nevow/athena.py 2011-07-10 13:56:31 +0000
4@@ -1511,7 +1511,7 @@
5
6
7 handler = stan.Proto('athena:handler')
8-_handlerFormat = "return Nevow.Athena.Widget.handleEvent(this, %(event)s, %(handler)s);"
9+_handlerFormat = "return Nevow.Athena.Widget.handleEvent(this, %(event)s, %(handler)s, event);"
10
11 def _rewriteEventHandlerToAttribute(tag):
12 """
13
14=== modified file 'Nevow/nevow/js/Divmod/Runtime/__init__.js'
15--- Nevow/nevow/js/Divmod/Runtime/__init__.js 2008-12-31 18:44:01 +0000
16+++ Nevow/nevow/js/Divmod/Runtime/__init__.js 2011-07-10 13:56:31 +0000
17@@ -709,6 +709,25 @@
18 */
19 function addBeforeUnloadHandler(self, aWindow, handler) {
20 Divmod.Base.addToCallStack(aWindow, 'onbeforeunload', handler);
21+ },
22+
23+
24+ /**
25+ * Determine which mouse buttons were pressed from a browser event object.
26+ *
27+ * The default implementation matches the W3C DOM Level 2 Events
28+ * specifications.
29+ *
30+ * @return: A mapping of C{'left'}, C{'middle'}, C{'right'} to C{Boolean}
31+ * values indicating the state of the named mouse buttons.
32+ *
33+ * @see: <http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MouseEvent>
34+ */
35+ function getMouseButtonsFromEvent(self, event) {
36+ return {
37+ 'left': event.button == 0,
38+ 'middle': event.button == 1,
39+ 'right': event.button == 2};
40 });
41
42
43@@ -1205,6 +1224,20 @@
44 */
45 function addBeforeUnloadHandler(self, aWindow, handler) {
46 Divmod.Base.addToCallStack(aWindow.document.body, 'onbeforeunload', handler);
47+ },
48+
49+
50+ /**
51+ * Internet Explorer specific handling of the C{event.button} property.
52+ *
53+ * @see: L{Divmod.Runtime.Platform.getMouseButtonsFromEvent}
54+ * @see: <http://msdn.microsoft.com/en-us/library/ms533544%28v=vs.85%29.aspx>
55+ */
56+ function getMouseButtonsFromEvent(self, event) {
57+ return {
58+ 'left': (event.button & 1) != 0,
59+ 'middle': (event.button & 4) != 0,
60+ 'right': (event.button & 2) != 0};
61 });
62
63
64
65=== modified file 'Nevow/nevow/js/Divmod/Test/TestRuntime.js'
66--- Nevow/nevow/js/Divmod/Test/TestRuntime.js 2008-12-31 18:44:01 +0000
67+++ Nevow/nevow/js/Divmod/Test/TestRuntime.js 2011-07-10 13:56:31 +0000
68@@ -713,6 +713,100 @@
69 });
70
71
72+
73+/**
74+ * Tests for L{Divmod.Runtime.Platform}.
75+ */
76+Divmod.UnitTest.TestCase.subclass(Divmod.Test.TestRuntime,
77+ 'PlatformTests').methods(
78+ function setUp(self) {
79+ self.runtime = Divmod.Runtime.Platform('name');
80+ },
81+
82+
83+ /**
84+ * Assert that C{evt.button} when processed with
85+ * C{getMouseButtonsFromEvent} yields the same values for its C{'left'},
86+ * C{'middle'} and C{'right'} attributes as the parameters with these same
87+ * names.
88+ */
89+ function assertMouseButtons(self, evt, left, middle, right) {
90+ var buttons = self.runtime.getMouseButtonsFromEvent(evt);
91+ self.assertIdentical(buttons.left, left)
92+ self.assertIdentical(buttons.middle, middle)
93+ self.assertIdentical(buttons.right, right)
94+ },
95+
96+
97+ /**
98+ * Most platforms follow the W3C definitions for C{event.button} values.
99+ *
100+ * These values cannot be combined to indicate multiple buttons being
101+ * pushed.
102+ */
103+ function test_getMouseButtonsFromEvent(self) {
104+ var evt = {};
105+ // Unknown value.
106+ evt.button = 99;
107+ self.assertMouseButtons(evt, false, false, false);
108+ // Left mouse button.
109+ evt.button = 0;
110+ self.assertMouseButtons(evt, true, false, false);
111+ // Middle mouse button.
112+ evt.button = 1;
113+ self.assertMouseButtons(evt, false, true, false);
114+ // Right mouse button.
115+ evt.button = 2;
116+ self.assertMouseButtons(evt, false, false, true);
117+ });
118+
119+
120+
121+/**
122+ * Tests for L{Divmod.Runtime.InternetExplorer}.
123+ */
124+Divmod.Test.TestRuntime.PlatformTests.subclass(
125+ Divmod.Test.TestRuntime,
126+ 'InternetExplorerPlatformTests').methods(
127+ function setUp(self) {
128+ self.runtime = Divmod.Runtime.InternetExplorer();
129+ },
130+
131+
132+ /**
133+ * Internet Explorer uses different values for L{event.button} and can
134+ * combine these values to indicate multiple buttons being pressed.
135+ */
136+ function test_getMouseButtonsFromEvent(self) {
137+ var evt = {};
138+ // No buttons.
139+ evt.button = 0;
140+ self.assertMouseButtons(evt, false, false, false);
141+ // Left mouse button.
142+ evt.button = 1;
143+ self.assertMouseButtons(evt, true, false, false);
144+ // Right mouse button.
145+ evt.button = 2;
146+ self.assertMouseButtons(evt, false, false, true);
147+ // Middle mouse button.
148+ evt.button = 4;
149+ self.assertMouseButtons(evt, false, true, false);
150+ // Left and right mouse buttons.
151+ evt.button = 3;
152+ self.assertMouseButtons(evt, true, false, true);
153+ // Left and middle mouse buttons.
154+ evt.button = 5;
155+ self.assertMouseButtons(evt, true, true, false);
156+ // Right and middle mouse buttons.
157+ evt.button = 6;
158+ self.assertMouseButtons(evt, false, true, true);
159+ // Left, middle and right mouse buttons.
160+ evt.button = 7;
161+ self.assertMouseButtons(evt, true, true, true);
162+ });
163+
164+
165+
166 Divmod.Test.TestRuntime.SpidermonkeyRuntimeTests = Divmod.UnitTest.TestCase.subclass(
167 'Divmod.Test.TestRuntime.SpidermonkeyRuntimeTests');
168 /**
169
170=== modified file 'Nevow/nevow/js/Nevow/Athena/__init__.js'
171--- Nevow/nevow/js/Nevow/Athena/__init__.js 2009-01-21 22:58:03 +0000
172+++ Nevow/nevow/js/Nevow/Athena/__init__.js 2011-07-10 13:56:31 +0000
173@@ -1176,7 +1176,8 @@
174 */
175 Nevow.Athena.Widget._makeEventHandler = function (domEventName, methodName) {
176 return function () {
177- return Nevow.Athena.Widget.handleEvent(this, domEventName, methodName);
178+ return Nevow.Athena.Widget.handleEvent(
179+ this, domEventName, methodName, event);
180 };
181 };
182
183@@ -1205,15 +1206,233 @@
184 return Nevow.Athena.page.dispatchEvent(widget, eventName, handlerName, callable);
185 };
186
187-/**
188- * Given a node and a method name in an event handling context, dispatch the
189- * event to the named method on the widget which owns the given node. This
190- * also sets up error handling and does return value translation as
191- * appropriate for an event handler. It also pauses the outgoing message
192- * queue to allow multiple messages from the event handler to be batched up
193- * into a single request.
194- */
195-Nevow.Athena.Widget.handleEvent = function handleEvent(node, eventName, handlerName) {
196+
197+
198+/**
199+ * Lowest-common denominator DOM event wrapper.
200+ *
201+ * DOM event objects should wrapped by calling
202+ * L{Nevow.Athena.Event.fromDOMEvent}.
203+ *
204+ * @type knownEventTypes: C{Array} of C{String}
205+ * @cvar knownEventTypes: Array of event types that this event wrapper can wrap.
206+ *
207+ * @ivar event: Original DOM event object.
208+ *
209+ * @ivar type: C{String}
210+ * @ivar type: Event type.
211+ *
212+ * @ivar target: Element to which the DOM event was originally dispatched.
213+ *
214+ * @see: <http://www.w3.org/TR/DOM-Level-2-Events/events.html>
215+ * @see: <http://www.quirksmode.org/js/introevents.html>
216+ */
217+Divmod.Class.subclass(Nevow.Athena, 'Event').methods(
218+ function __init__(self, event) {
219+ self.event = event;
220+ self.type = self.event.type;
221+ self.target = self.event.target;
222+ if (self.target === undefined) {
223+ self.target = self.event.srcElement;
224+ }
225+ },
226+
227+
228+ /**
229+ * Cancels the event if it is cancelable, without stopping further
230+ * propagation of the event.
231+ */
232+ function preventDefault(self) {
233+ if (self.event.preventDefault) {
234+ self.event.preventDefault();
235+ } else {
236+ self.event.returnValue = false;
237+ }
238+ },
239+
240+
241+ /**
242+ * Stops the propagation of events further along in the DOM.
243+ */
244+ function stopPropagation(self) {
245+ if (self.event.stopPropagation) {
246+ self.event.stopPropagation();
247+ } else {
248+ self.event.cancelBubble = true;
249+ }
250+ });
251+
252+
253+
254+/**
255+ * Specific subclass of L{Nevow.Athena.Event} relating to key events.
256+ *
257+ * @ivar altKey: Was the I{alt} key pressed when the event fired?
258+ *
259+ * @ivar ctrlKey: Was the I{ctrl} key pressed when the event fired?
260+ *
261+ * @ivar shiftKey: Was the I{shift} key pressed when the event fired?
262+ *
263+ * @ivar metaKey: Was the I{meta} key pressed when the event fire?
264+ */
265+Nevow.Athena.Event.subclass(Nevow.Athena, 'KeyEvent').methods(
266+ function __init__(self, event) {
267+ Nevow.Athena.KeyEvent.upcall(self, '__init__', event);
268+ self.altKey = !!self.event.altKey;
269+ self.ctrlKey = !!self.event.ctrlKey;
270+ self.shiftKey = !!self.event.shiftKey;
271+ self.metaKey = !!self.event.metaKey; // Not in IE < 9.
272+ },
273+
274+
275+ /**
276+ * Get the Unicode value of key press.
277+ *
278+ * For the I{keydown} or I{keyup} events this is the virtual key code of the
279+ * physical button pushed. For I{keypress} event this is the character code
280+ * for an alphanumeric key.
281+ *
282+ * @see: <https://developer.mozilla.org/en/DOM/event.keyCode>
283+ * @see: <http://msdn.microsoft.com/en-us/library/ms533927%28v=VS.85%29.aspx>
284+ */
285+ function getKeyCode(self) {
286+ return self.event.keyCode || self.event.which;
287+ },
288+
289+
290+ /**
291+ * Set the Unicode value of key press.
292+ */
293+ function setKeyCode(self, value) {
294+ self.event.keyCode = value;
295+ });
296+
297+Nevow.Athena.KeyEvent.knownEventTypes = ['keydown', 'keypress', 'keyup'];
298+
299+
300+
301+/**
302+ * Specific subclass of L{Nevow.Athena.Event} relating to mouse events.
303+ *
304+ * @ivar altKey: Was the I{alt} key pressed when the event fired?
305+ *
306+ * @ivar ctrlKey: Was the I{ctrl} key pressed when the event fired?
307+ *
308+ * @ivar shiftKey: Was the I{shift} key pressed when the event fired?
309+ *
310+ * @ivar metaKey: Was the I{meta} key pressed when the event fire?
311+ */
312+Nevow.Athena.Event.subclass(Nevow.Athena, 'MouseEvent').methods(
313+ function __init__(self, event) {
314+ Nevow.Athena.MouseEvent.upcall(self, '__init__', event);
315+ self.altKey = self.event.altKey;
316+ self.ctrlKey = self.event.ctrlKey;
317+ self.shiftKey = self.event.shiftKey;
318+ self.metaKey = self.event.metaKey; // Not in IE < 9.
319+ },
320+
321+
322+ /**
323+ * Determine which mouse buttons were pressed in this event.
324+ *
325+ * @see: L{Divmod.Runtime.Platform.getMouseButtonsFromEvent}
326+ */
327+ function getMouseButtons(self) {
328+ return Divmod.Runtime.theRuntime.getMouseButtonsFromEvent(self.event);
329+ },
330+
331+
332+ /**
333+ * Get the coordinates of the event relative to the whole document.
334+ *
335+ * @return: Mapping of C{'x'} and C{'y'} to the horizontal and vertical
336+ * coordinates respectively.
337+ */
338+ function getPagePosition(self) {
339+ return Divmod.Runtime.theRuntime.getEventCoords(self.event);
340+ },
341+
342+
343+ /**
344+ * Get the coordinates within the browser's client area at which the event
345+ * occurred (as opposed to the coordinates within the page).
346+ *
347+ * For example, clicking in the top-left corner of the client area will
348+ * always result in a mouse event with a clientX value of 0, regardless of
349+ * whether the page is scrolled horizontally.
350+ *
351+ * @return: Mapping of C{'x'} and C{'y'} to the horizontal and vertical
352+ * coordinates respectively.
353+ */
354+ function getClientPosition(self) {
355+ return {
356+ 'x': self.event.clientX,
357+ 'y': self.event.clientY};
358+ },
359+
360+
361+ /**
362+ * Get the coordinates of the event within the screen as a whole.
363+ *
364+ * @return: Mapping of C{'x'} and C{'y'} to the horizontal and vertical
365+ * coordinates respectively.
366+ */
367+ function getScreenPosition(self) {
368+ return {
369+ 'x': self.event.screenX,
370+ 'y': self.event.screenY};
371+ });
372+
373+Nevow.Athena.MouseEvent.knownEventTypes = [
374+ 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup',
375+ 'mousewheel'];
376+
377+
378+
379+/**
380+ * Mapping of event types to known event handlers.
381+ */
382+Nevow.Athena.Event._eventHandlerMapping = (function() {
383+ var handlers = [
384+ Nevow.Athena.KeyEvent,
385+ Nevow.Athena.MouseEvent];
386+ var mapping = {}
387+ for (var i = 0; i < handlers.length; ++i) {
388+ var handler = handlers[i];
389+ var knownEventTypes = handler.knownEventTypes;
390+ for (var j = 0; j < knownEventTypes.length; ++j) {
391+ mapping[knownEventTypes[j]] = handler;
392+ }
393+ }
394+ return mapping;
395+})();
396+
397+
398+
399+/**
400+ * Wrap a DOM event object with an appropriate L{Nevow.Athena.Event} subclass
401+ * or L{Nevow.Athena.Event} if there is no specific handler for the event type.
402+ */
403+Nevow.Athena.Event.fromDOMEvent = function fromDOMEvent(event) {
404+ var handler = Nevow.Athena.Event._eventHandlerMapping[event.type];
405+ if (handler === undefined) {
406+ handler = Nevow.Athena.Event;
407+ }
408+ return handler(event);
409+};
410+
411+
412+
413+/**
414+ * Given a node, a method name in an event handling context and an event
415+ * object, dispatch the event to the named method on the widget which owns the
416+ * given node. This also sets up error handling and does return value
417+ * translation as appropriate for an event handler. It also pauses the
418+ * outgoing message queue to allow multiple messages from the event handler to
419+ * be batched up into a single request.
420+ */
421+Nevow.Athena.Widget.handleEvent = function handleEvent(node, eventName,
422+ handlerName, event) {
423 var widget = Nevow.Athena.Widget.get(node);
424 var method = widget[handlerName];
425 var result = false;
426@@ -1223,7 +1442,8 @@
427 result = Nevow.Athena.Widget.dispatchEvent(
428 widget, eventName, handlerName,
429 function() {
430- return method.call(widget, node);
431+ return method.call(
432+ widget, node, Nevow.Athena.Event.fromDOMEvent(event));
433 });
434 }
435 return result;
436
437=== added file 'Nevow/nevow/js/Nevow/Test/TestEvent.js'
438--- Nevow/nevow/js/Nevow/Test/TestEvent.js 1970-01-01 00:00:00 +0000
439+++ Nevow/nevow/js/Nevow/Test/TestEvent.js 2011-07-10 13:56:31 +0000
440@@ -0,0 +1,504 @@
441+// import Divmod
442+// import Divmod.UnitTest
443+// import Nevow.Athena
444+
445+
446+
447+/**
448+ * A mock DOM event that behaves according to the W3C guide that most browsers
449+ * follow.
450+ *
451+ * @ivar type: Event type.
452+ *
453+ * @ivar target: Element to which the DOM event was originally dispatched.
454+ *
455+ * @ivar cancelled: Has the default action been cancelled?
456+ *
457+ * @ivar stopped: Has event propagation been stopped?
458+ */
459+Divmod.Class.subclass(Nevow.Test.TestEvent, 'W3CEvent').methods(
460+ function __init__(self, type, target) {
461+ self.type = type;
462+ self.target = target;
463+ self.cancelled = false;
464+ self.stopped = false;
465+ },
466+
467+
468+ /**
469+ * If an event is cancelable, the preventDefault method is used to signify
470+ * that the event is to be canceled, meaning any default action normally
471+ * taken by the implementation as a result of the event will not occur.
472+ */
473+ function preventDefault(self) {
474+ self.cancelled = true;
475+ },
476+
477+
478+ /**
479+ * The stopPropagation method is used prevent further propagation of an
480+ * event during event flow.
481+ */
482+ function stopPropagation(self) {
483+ self.stopped = true;
484+ });
485+
486+
487+
488+/**
489+ * A mock DOM event that behaves according to IE before IE9.
490+ *
491+ * @ivar type: Event type.
492+ *
493+ * @ivar srcElement: Element to which the DOM event was originally dispatched.
494+ *
495+ * @ivar returnValue: Has the default action been cancelled?
496+ *
497+ * @ivar cancelBubble: Has event propagation been stopped?
498+ */
499+Divmod.Class.subclass(Nevow.Test.TestEvent, 'IEEvent').methods(
500+ function __init__(self, type, srcElement) {
501+ self.type = type;
502+ self.srcElement = srcElement;
503+ self.returnValue = true;
504+ self.cancelBubble = false;
505+ });
506+
507+
508+
509+/**
510+ * Tests for L{Nevow.Athena.Event} when using W3C-style event objects.
511+ */
512+Divmod.UnitTest.TestCase.subclass(Nevow.Test.TestEvent, 'TestEventW3C').methods(
513+ function createDOMEvent(self, type, target) {
514+ return Nevow.Test.TestEvent.W3CEvent(type, target);
515+ },
516+
517+
518+ /**
519+ * L{Nevow.Athena.Event.fromDOMEvent} creates a specific I{Event} subclass
520+ * if it supports the DOM event otherwise L{Nevow.Athena.Event} is used.
521+ */
522+ function test_fromDOMEvent(self) {
523+ function assertInstanceOf(inst, type, msg) {
524+ self.assertIdentical(
525+ inst instanceof type,
526+ true,
527+ 'Expected ' + inst.toString() +
528+ ' to be an instance of ' + type.toString());
529+ }
530+
531+ var handlers = [
532+ Nevow.Athena.KeyEvent,
533+ Nevow.Athena.MouseEvent];
534+
535+ for (var i = 0; i < handlers.length; ++i) {
536+ var handler = handlers[i];
537+ var knownEventTypes = handler.knownEventTypes;
538+ for (var j = 0; j < knownEventTypes.length; ++j) {
539+ var domEvent = self.createDOMEvent(
540+ knownEventTypes[j], null);
541+ var evt = Nevow.Athena.Event.fromDOMEvent(domEvent);
542+ assertInstanceOf(evt, handler);
543+ }
544+ }
545+
546+ var evt = Nevow.Athena.Event.fromDOMEvent(
547+ self.createDOMEvent('definitelyunknown', null));
548+ assertInstanceOf(evt, Nevow.Athena.Event);
549+ },
550+
551+
552+ /**
553+ * L{Nevow.Athena.Event} extracts information from different kinds of DOM
554+ * events and puts them into a universal API.
555+ */
556+ function test_attributes(self) {
557+ var eventType = 'eventType';
558+ var target = 42;
559+ var domEvent = self.createDOMEvent(eventType, target);
560+ var event = Nevow.Athena.Event.fromDOMEvent(domEvent);
561+ self.assertIdentical(event.event, domEvent);
562+ self.assertIdentical(event.type, eventType);
563+ self.assertIdentical(event.target, target);
564+ },
565+
566+
567+ /**
568+ * L{Nevow.Athena.Event.preventDefault} calls the method or sets the
569+ * attribute on the underlying DOM event that prevents the event's default
570+ * return value from being used.
571+ */
572+ function test_preventDefault(self) {
573+ var domEvent = self.createDOMEvent('eventtype', null);
574+ var event = Nevow.Athena.Event.fromDOMEvent(domEvent);
575+ self.assertIdentical(domEvent.cancelled, false);
576+ event.preventDefault();
577+ self.assertIdentical(domEvent.cancelled, true);
578+ },
579+
580+
581+ /**
582+ * L{Nevow.Athena.Event.stopPropagation} calls the method or sets the
583+ * attribute on the underlying DOM event that stops the event from
584+ * propogating futher along in the DOM.
585+ */
586+ function test_stopPropagation(self) {
587+ var domEvent = self.createDOMEvent('eventtype', null);
588+ var event = Nevow.Athena.Event.fromDOMEvent(domEvent);
589+ self.assertIdentical(domEvent.stopped, false);
590+ event.stopPropagation();
591+ self.assertIdentical(domEvent.stopped, true);
592+ });
593+
594+
595+
596+/**
597+ * Tests for L{Nevow.Athena.Event} when using IE-style event objects.
598+ */
599+Nevow.Test.TestEvent.TestEventW3C.subclass(Nevow.Test.TestEvent,
600+ 'TestEventIE').methods(
601+ function createDOMEvent(self, type, target) {
602+ return Nevow.Test.TestEvent.IEEvent(type, target);
603+ },
604+
605+
606+ function test_preventDefault(self) {
607+ var domEvent = self.createDOMEvent('eventtype', null);
608+ var event = Nevow.Athena.Event.fromDOMEvent(domEvent);
609+ self.assertIdentical(domEvent.returnValue, true);
610+ event.preventDefault();
611+ self.assertIdentical(domEvent.returnValue, false);
612+ },
613+
614+
615+ function test_stopPropagation(self) {
616+ var domEvent = self.createDOMEvent('eventtype', null);
617+ var event = Nevow.Athena.Event.fromDOMEvent(domEvent);
618+ self.assertIdentical(domEvent.cancelBubble, false);
619+ event.stopPropagation();
620+ self.assertIdentical(domEvent.cancelBubble, true);
621+ });
622+
623+
624+
625+/**
626+ * Tests for L{Nevow.Athena.KeyEvent} when using W3C-style event objects.
627+ */
628+Divmod.UnitTest.TestCase.subclass(Nevow.Test.TestEvent,
629+ 'TestKeyEventW3C').methods(
630+ function setUp(self) {
631+ self.supportsMeta = true;
632+ self.eventType = Nevow.Test.TestEvent.W3CEvent;
633+ },
634+
635+
636+ function createDOMEvent(self, type, target, args) {
637+ var evt = self.eventType(type, target);
638+ for (var key in args) {
639+ evt[key] = args[key];
640+ }
641+ return evt;
642+ },
643+
644+
645+ /**
646+ * Properties relating to modifier keys on the original DOM are accessible
647+ * on L{Nevow.Athena.KeyEvent}.
648+ */
649+ function test_modifiers(self) {
650+ function createEvent(altKey, ctrlKey, shiftKey, metaKey) {
651+ return Nevow.Athena.Event.fromDOMEvent(
652+ self.createDOMEvent('keypress', null, {
653+ 'altKey': !!altKey,
654+ 'ctrlKey': !!ctrlKey,
655+ 'shiftKey': !!shiftKey,
656+ 'metaKey': !!metaKey}));
657+ }
658+
659+ function assertModifiers(evt, altKey, ctrlKey, shiftKey, metaKey) {
660+ self.assertIdentical(evt.altKey, altKey);
661+ self.assertIdentical(evt.ctrlKey, ctrlKey);
662+ self.assertIdentical(evt.shiftKey, shiftKey);
663+ self.assertIdentical(evt.metaKey, metaKey);
664+ }
665+
666+ assertModifiers(
667+ createEvent(true),
668+ true, false, false, false);
669+ assertModifiers(
670+ createEvent(true, true),
671+ true, true, false, false);
672+ assertModifiers(
673+ createEvent(true, true, true),
674+ true, true, true, false);
675+
676+ if (self.supportsMeta) {
677+ assertModifiers(
678+ createEvent(true, true, true, true),
679+ true, true, true, true);
680+ } else {
681+ assertModifiers(
682+ createEvent(true, true, true, true),
683+ true, true, true, false)
684+ };
685+ },
686+
687+
688+ /**
689+ * L{Nevow.Athena.KeyEvent.getKeyCode} returns the Unicode key code for the
690+ * DOM event, preferring I{keyCode} over I{which}.
691+ */
692+ function test_getKeyCode(self) {
693+ function createEvent(keyCode, which) {
694+ return Nevow.Athena.Event.fromDOMEvent(
695+ self.createDOMEvent('keypress', null, {
696+ 'keyCode': keyCode,
697+ 'which': which}));
698+ }
699+
700+ function assertKeyCode(evt, keyCode) {
701+ self.assertIdentical(evt.getKeyCode(), keyCode);
702+ }
703+
704+ assertKeyCode(createEvent(65), 65);
705+ assertKeyCode(createEvent(65, 97), 65);
706+ assertKeyCode(createEvent(0, 65), 65);
707+ },
708+
709+
710+ /**
711+ * L{Nevow.Athena.KeyEvent.retKeyCode} sets the Unicode key code for the
712+ * DOM event.
713+ */
714+ function test_setKeyCode(self) {
715+ var evt = Nevow.Athena.Event.fromDOMEvent(
716+ self.createDOMEvent('keypress', null, {
717+ 'keyCode': 65}));
718+
719+ self.assertIdentical(evt.getKeyCode(), 65);
720+ evt.setKeyCode(97);
721+ self.assertIdentical(evt.event.keyCode, 97);
722+ self.assertIdentical(evt.getKeyCode(), 97);
723+ });
724+
725+
726+
727+/**
728+ * Tests for L{Nevow.Athena.KeyEvent} when using IE-style event objects.
729+ */
730+Nevow.Test.TestEvent.TestKeyEventW3C.subclass(Nevow.Test.TestEvent,
731+ 'TestKeyEventIE').methods(
732+ function setUp(self) {
733+ Nevow.Test.TestEvent.TestKeyEventIE.upcall(self, 'setUp');
734+ self.supportsMeta = false;
735+ self.eventType = Nevow.Test.TestEvent.IEEvent;
736+ },
737+
738+
739+ function createDOMEvent(self, type, target, args) {
740+ // IE < 9 doesn't support "metaKey".
741+ delete args['metaKey'];
742+ return Nevow.Test.TestEvent.TestKeyEventIE.upcall(
743+ self, 'createDOMEvent', type, target, args);
744+ });
745+
746+
747+
748+/**
749+ * Tests for L{Nevow.Athena.MouseEvent} when using W3C-style event objects.
750+ */
751+Divmod.UnitTest.TestCase.subclass(Nevow.Test.TestEvent,
752+ 'TestMouseEventW3C').methods(
753+ function setUp(self) {
754+ self.eventType = Nevow.Test.TestEvent.W3CEvent;
755+ },
756+
757+
758+ function createDOMEvent(self, type, target, args) {
759+ var evt = self.eventType(type, target);
760+ for (var key in args) {
761+ evt[key] = args[key];
762+ }
763+ return evt;
764+ },
765+
766+
767+ /**
768+ * Get the platform-specific value for the specified mouse button
769+ * configuration.
770+ */
771+ function getButtonValue(self, left, middle, right) {
772+ if (left) {
773+ return 0;
774+ } else if (middle) {
775+ return 1;
776+ } else if (right) {
777+ return 2;
778+ }
779+ },
780+
781+
782+ /**
783+ * Assert that L{Nevow.Athena.MouseEvent.getMouseButtons} produces a mapping
784+ * that matches an expected mouse button configuration.
785+ */
786+ function assertMouseButtons(self, evt, left, middle, right) {
787+ var buttons = evt.getMouseButtons();
788+ self.assertIdentical(buttons.left, !!left);
789+ self.assertIdentical(buttons.middle, !!middle);
790+ self.assertIdentical(buttons.right, !!right);
791+ },
792+
793+
794+ /**
795+ * L{Nevow.Athena.MouseEvent.getMouseButtons} decodes the platform-specific
796+ * C{event.button} value into a mapping that expresses the mouse button
797+ * configuration.
798+ */
799+ function test_getMouseButtons(self) {
800+ function createEvent(left, middle, right) {
801+ return Nevow.Athena.Event.fromDOMEvent(
802+ self.createDOMEvent('mouseup', null, {
803+ 'button': self.getButtonValue(left, middle, right)}));
804+ }
805+
806+ self.assertMouseButtons(
807+ createEvent(true, false, false),
808+ true, false, false);
809+ self.assertMouseButtons(
810+ createEvent(false, true, false),
811+ false, true, false);
812+ self.assertMouseButtons(
813+ createEvent(false, false, true),
814+ false, false, true);
815+ },
816+
817+
818+ /**
819+ * L{Nevow.Athena.MouseEvent.getPagePosition} gets the coordinates of the
820+ * event relative to the whole document.
821+ */
822+ function test_getPagePosition(self) {
823+ var evt = Nevow.Athena.Event.fromDOMEvent(
824+ self.createDOMEvent('mouseup', null, {
825+ 'pageX': 51,
826+ 'pageY': 44}));
827+ var pt = evt.getPagePosition();
828+ self.assertIdentical(pt.x, 51);
829+ self.assertIdentical(pt.y, 44);
830+ },
831+
832+
833+ /**
834+ * L{Nevow.Athena.MouseEvent.getClientPosition} gets the coordinates of the
835+ * event within the browse's client area.
836+ */
837+ function test_getClientPosition(self) {
838+ var evt = Nevow.Athena.Event.fromDOMEvent(
839+ self.createDOMEvent('mouseup', null, {
840+ 'clientX': 51,
841+ 'clientY': 44}));
842+ var pt = evt.getClientPosition();
843+ self.assertIdentical(pt.x, 51);
844+ self.assertIdentical(pt.y, 44);
845+ },
846+
847+
848+ /**
849+ * L{Nevow.Athena.MouseEvent.getScreenPosition} gets the coordinates of the
850+ * event within the screen as a whole.
851+ */
852+ function test_getScreenPosition(self) {
853+ var evt = Nevow.Athena.Event.fromDOMEvent(
854+ self.createDOMEvent('mouseup', null, {
855+ 'screenX': 51,
856+ 'screenY': 44}));
857+ var pt = evt.getScreenPosition();
858+ self.assertIdentical(pt.x, 51);
859+ self.assertIdentical(pt.y, 44);
860+ });
861+
862+
863+
864+/**
865+ * Tests for L{Nevow.Athena.MouseEvent} when using IE-style event objects.
866+ */
867+Nevow.Test.TestEvent.TestMouseEventW3C.subclass(
868+ Nevow.Test.TestEvent,
869+ 'TestMouseEventIE').methods(
870+ function setUp(self) {
871+ self.eventType = Nevow.Test.TestEvent.IEEvent;
872+ self.oldRuntime = Divmod.Runtime.theRuntime;
873+ Divmod.Runtime.theRuntime = Divmod.Runtime.InternetExplorer();
874+ },
875+
876+
877+ function tearDown(self) {
878+ Divmod.Runtime.theRuntime = self.oldRuntime;
879+ },
880+
881+
882+ /**
883+ * Get the platform-specific value for the specified mouse button
884+ * configuration.
885+ */
886+ function getButtonValue(self, left, middle, right) {
887+ var button = 0;
888+ if (left) {
889+ button |= 1;
890+ }
891+ if (middle) {
892+ button |= 4;
893+ }
894+ if (right) {
895+ button |= 2;
896+ }
897+ return button;
898+ },
899+
900+
901+ /**
902+ * Internet Explorer can express configurations where multiple mouse
903+ * buttons are pushed.
904+ */
905+ function test_getMouseButtonsMultiple(self) {
906+ function createEvent(left, middle, right) {
907+ return Nevow.Athena.Event.fromDOMEvent(
908+ self.createDOMEvent('mouseup', null, {
909+ 'button': self.getButtonValue(left, middle, right)}));
910+ }
911+
912+ self.assertMouseButtons(
913+ createEvent(true, true, false),
914+ true, true, false);
915+ self.assertMouseButtons(
916+ createEvent(false, true, true),
917+ false, true, true);
918+ self.assertMouseButtons(
919+ createEvent(true, true, true),
920+ true, true, true);
921+ },
922+
923+
924+ /**
925+ * L{Nevow.Athena.MouseEvent.getPagePosition} gets the coordinates of the
926+ * event relative to the whole document. In Internet Explorer < 9 there are
927+ * no C{'pageX'} or C{'pageY'} attributes instead a page position is
928+ * derived from client position and the document and body scroll offsets.
929+ */
930+ function test_getPagePosition(self) {
931+ var evt = Nevow.Athena.Event.fromDOMEvent(
932+ self.createDOMEvent('mouseup', null, {
933+ 'clientX': 41,
934+ 'clientY': 34}));
935+ document.documentElement = {
936+ 'scrollLeft': 4,
937+ 'scrollTop': 6};
938+ document.body.scrollLeft = 6;
939+ document.body.scrollTop = 4;
940+
941+ var pt = evt.getPagePosition();
942+ self.assertIdentical(pt.x, 51);
943+ self.assertIdentical(pt.y, 44);
944+ });
945
946=== modified file 'Nevow/nevow/js/Nevow/Test/TestWidget.js'
947--- Nevow/nevow/js/Nevow/Test/TestWidget.js 2008-12-31 18:44:01 +0000
948+++ Nevow/nevow/js/Nevow/Test/TestWidget.js 2011-07-10 13:56:31 +0000
949@@ -128,6 +128,7 @@
950 * the browser, and the explicitly selected handler will be invoked.
951 */
952 function test_connectDOMEventCustomMethod(self) {
953+ event = {};
954 self.widget.connectDOMEvent("onclick", "explicitClick");
955 self.node.onclick();
956 self.assertIdentical(self.widget.clicked, "explicitly");
957@@ -138,6 +139,7 @@
958 * the browser, and the explicitly selected node will be used.
959 */
960 function test_connectDOMEventCustomNode(self) {
961+ event = {};
962 self.widget.connectDOMEvent("onclick", "explicitClick", self.otherNode);
963 self.otherNode.onclick();
964 self.assertIdentical(self.widget.clicked, "explicitly");
965
966=== modified file 'Nevow/nevow/test/test_athena.py'
967--- Nevow/nevow/test/test_athena.py 2010-07-12 19:00:11 +0000
968+++ Nevow/nevow/test/test_athena.py 2011-07-10 13:56:31 +0000
969@@ -554,7 +554,7 @@
970 """
971 expectedOutput = (
972 'return Nevow.Athena.Widget.handleEvent('
973- 'this, &quot;onclick&quot;, &quot;bar&quot;);')
974+ 'this, &quot;onclick&quot;, &quot;bar&quot;, event);')
975 tag = tags.span[athena.handler(event='onclick', handler='bar')]
976 mutated = athena._rewriteEventHandlerToAttribute(tag)
977 output = flat.flatten(mutated)
978
979=== modified file 'Nevow/nevow/test/test_javascript.py'
980--- Nevow/nevow/test/test_javascript.py 2008-07-07 08:58:45 +0000
981+++ Nevow/nevow/test/test_javascript.py 2011-07-10 13:56:31 +0000
982@@ -51,3 +51,7 @@
983
984 def test_tabbedPane(self):
985 return 'Nevow.Test.TestTabbedPane'
986+
987+
988+ def test_event(self):
989+ return 'Nevow.Test.TestEvent'

Subscribers

People subscribed via source and target branches

to all changes: