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

Proposed by Jonathan Jacobs on 2011-07-10
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 2011-07-10 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 on 2011-07-10

Fix typo.

2681. By Jonathan Jacobs on 2011-07-10

Fix failing test related to event changes.

Unmerged revisions

2681. By Jonathan Jacobs on 2011-07-10

Fix failing test related to event changes.

2680. By Jonathan Jacobs on 2011-07-10

Fix typo.

2679. By Jonathan Jacobs on 2011-07-09

Fix failing tests.

2678. By Jonathan Jacobs on 2011-07-09

Wrap DOM events in a universal event wrapper.

2677. By Jonathan Jacobs on 2011-07-08

Implement getMouseButtonsFromEvent for Platform and InternetExplorer in Divmod.Runtime.

2676. By Jonathan Jacobs on 2011-07-08

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: