Merge pull request #2716 from arian/fix-2715-edge-ua-string
[mootools.git] / Specs / Element / Element.Event.js
blob5a9e061ee17ea7e54ff919286af3486316f0e4cd
1 /*
2 ---
3 name: Element.Event
4 requires: ~
5 provides: ~
6 ...
7 */
9 (function(){
11 var Local = Local || {};
13 var fire = 'fireEvent', create = function(){
14         return new Element('div');
17 describe('Events API: Element', function(){
19         beforeEach(function(){
20                 Local.called = 0;
21                 Local.fn = function(){
22                         return Local.called++;
23                 };
24         });
26         it('should add an Event to the Class', function(){
27                 var object = create();
29                 object.addEvent('event', Local.fn)[fire]('event');
31                 expect(Local.called).toEqual(1);
32         });
34         it('should add multiple Events to the Class', function(){
35                 create().addEvents({
36                         event1: Local.fn,
37                         event2: Local.fn
38                 })[fire]('event1')[fire]('event2');
40                 expect(Local.called).toEqual(2);
41         });
43         it('should remove a specific method for an event', function(){
44                 var object = create();
45                 var x = 0, fn = function(){ x++; };
47                 object.addEvent('event', Local.fn).addEvent('event', fn).removeEvent('event', Local.fn)[fire]('event');
49                 expect(x).toEqual(1);
50                 expect(Local.called).toEqual(0);
51         });
53         it('should remove an event and its methods', function(){
54                 var object = create();
55                 var x = 0, fn = function(){ x++; };
57                 object.addEvent('event', Local.fn).addEvent('event', fn).removeEvents('event')[fire]('event');
59                 expect(x).toEqual(0);
60                 expect(Local.called).toEqual(0);
61         });
63         it('should remove all events', function(){
64                 var object = create();
65                 var x = 0, fn = function(){ x++; };
67                 object.addEvent('event1', Local.fn).addEvent('event2', fn).removeEvents();
68                 object[fire]('event1')[fire]('event2');
70                 // Should not fail
71                 object.removeEvents()[fire]('event1')[fire]('event2');
73                 expect(x).toEqual(0);
74                 expect(Local.called).toEqual(0);
75         });
77         it('should remove events with an object', function(){
78                 var object = create();
79                 var events = {
80                         event1: Local.fn,
81                         event2: Local.fn
82                 };
84                 object.addEvent('event1', function(){ Local.fn(); }).addEvents(events)[fire]('event1');
85                 expect(Local.called).toEqual(2);
87                 object.removeEvents(events);
88                 object[fire]('event1');
89                 expect(Local.called).toEqual(3);
91                 object[fire]('event2');
92                 expect(Local.called).toEqual(3);
93         });
95         it('should remove an event immediately', function(){
96                 var object = create();
98                 var methods = [];
100                 var three = function(){
101                         methods.push(3);
102                 };
104                 object.addEvent('event', function(){
105                         methods.push(1);
106                         this.removeEvent('event', three);
107                 }).addEvent('event', function(){
108                         methods.push(2);
109                 }).addEvent('event', three);
111                 object[fire]('event');
112                 expect(methods).toEqual([1, 2]);
114                 object[fire]('event');
115                 expect(methods).toEqual([1, 2, 1, 2]);
116         });
118         it('should be able to remove itself', function(){
119                 var object = create();
121                 var methods = [];
123                 var one = function(){
124                         object.removeEvent('event', one);
125                         methods.push(1);
126                 };
127                 var two = function(){
128                         object.removeEvent('event', two);
129                         methods.push(2);
130                 };
131                 var three = function(){
132                         methods.push(3);
133                 };
135                 object.addEvent('event', one).addEvent('event', two).addEvent('event', three);
137                 object[fire]('event');
138                 expect(methods).toEqual([1, 2, 3]);
140                 object[fire]('event');
141                 expect(methods).toEqual([1, 2, 3, 3]);
142         });
146 var fragment = document.createDocumentFragment();
148 // Restore native fireEvent in IE for Syn
149 var createElement = function(tag, props){
150         var el = new Element(tag);
151         if (el._fireEvent) el.fireEvent = el._fireEvent;
152         return el.set(props);
155 describe('Element.Event', function(){
157         it('Should trigger the click event', function(){
159                 var callback = jasmine.createSpy('Element.Event click');
161                 var el = createElement('a', {
162                         text: 'test',
163                         styles: {
164                                 display: 'block',
165                                 overflow: 'hidden',
166                                 height: '1px'
167                         },
168                         events: {
169                                 click: callback
170                         }
171                 }).inject(document.body);
173                 Syn.trigger('click', null, el);
175                 expect(callback).toHaveBeenCalled();
176                 el.destroy();
177         });
179         it('Should trigger the click event and prevent the default behavior', function(){
181                 var callback = jasmine.createSpy('Element.Event click with prevent');
183                 var el = createElement('a', {
184                         text: 'test',
185                         styles: {
186                                 display: 'block',
187                                 overflow: 'hidden',
188                                 height: '1px'
189                         },
190                         events: {
191                                 click: function(event){
192                                         event.preventDefault();
193                                         callback();
194                                 }
195                         }
196                 }).inject(document.body);
198                 Syn.trigger('click', null, el);
200                 expect(callback).toHaveBeenCalled();
201                 el.destroy();
203         });
205         if (window.postMessage && !navigator.userAgent.match(/phantomjs/i)) it('Should trigger message event', function(){
207                 var theMessage, spy = jasmine.createSpy('message');
208                 window.addEvent('message', function(e){
209                         theMessage = e.event.data;
210                         spy();
211                 });
212                 window.postMessage('I am a message from outer space...', '*');
213                 waits(150);
214                 runs(function(){
215                         expect(spy).toHaveBeenCalled();
216                         expect(theMessage).toEqual('I am a message from outer space...');
217                 });
218         });
220         it('Should watch for a key-down event', function(){
222                 var callback = jasmine.createSpy('keydown');
224                 var div = createElement('div').addEvent('keydown', function(event){
225                         callback(event.key);
226                 }).inject(document.body);
228                 Syn.key('a', div);
230                 expect(callback).toHaveBeenCalledWith('a');
231                 div.destroy();
232         });
234         it('should clone events of an element', function(){
236                 var calls = 0;
238                 var element = new Element('div').addEvent('click', function(){ calls++; });
239                 element.fireEvent('click');
241                 expect(calls).toBe(1);
243                 var clone = new Element('div').cloneEvents(element, 'click');
244                 clone.fireEvent('click');
246                 expect(calls).toBe(2);
248                 element.addEvent('custom', function(){ calls += 2; }).fireEvent('custom');
250                 expect(calls).toBe(4);
252                 clone.cloneEvents(element);
253                 clone.fireEvent('click');
255                 expect(calls).toBe(5);
257                 clone.fireEvent('custom');
259                 expect(calls).toBe(7);
260         });
264 describe('Element.Event', function(){
265         // This is private API. Do not use.
267         it('should pass the name of the custom event to the callbacks', function(){
268                 var callbacks = 0;
269                 var callback = jasmine.createSpy('Element.Event custom');
271                 var fn = function(anything, type){
272                         expect(type).toEqual('customEvent');
273                         callbacks++;
274                 };
275                 Element.Events.customEvent = {
277                         base: 'click',
279                         condition: function(event, type){
280                                 fn(null, type);
281                                 return true;
282                         },
284                         onAdd: fn,
285                         onRemove: fn
287                 };
289                 var div = createElement('div').addEvent('customEvent', callback).inject(document.body);
291                 Syn.trigger('click', null, div);
293                 expect(callback).toHaveBeenCalled();
294                 div.removeEvent('customEvent', callback).destroy();
295                 expect(callbacks).toEqual(3);
296         });
300 describe('Element.Event.change', function(){
302         it('should not fire "change" for any property', function(){
303                 var callback = jasmine.createSpy('Element.Event.change');
305                 var radio = new Element('input', {
306                         'type': 'radio',
307                         'class': 'someClass',
308                         'checked': 'checked'
309                 }).addEvent('change', callback).inject(document.body);
311                 radio.removeClass('someClass');
312                 expect(callback).not.toHaveBeenCalled();
314                 var checkbox = new Element('input', {
315                         'type': 'checkbox',
316                         'class': 'someClass',
317                         'checked': 'checked'
318                 }).addEvent('change', callback).inject(document.body);
320                 checkbox.removeClass('someClass');
321                 expect(callback).not.toHaveBeenCalled();
323                 var text = new Element('input', {
324                         'type': 'text',
325                         'class': 'otherClass',
326                         'value': 'text value'
327                 }).addEvent('change', callback).inject(document.body);
329                 text.removeClass('otherClass');
330                 expect(callback).not.toHaveBeenCalled();
332                 [radio, checkbox, text].invoke('destroy');
333         });
337 describe('Element.Event keyup with f<key>', function(){
339         it('should pass event.key == f2 when pressing f2 on keyup and keydown', function(){
341                 var keydown = jasmine.createSpy('keydown');
342                 var keyup = jasmine.createSpy('keyup');
344                 var div = createElement('div')
345                         .addEvent('keydown', function(event){
346                                 keydown(event.key);
347                         })
348                         .addEvent('keyup', function(event){
349                                 keyup(event.key);
350                         })
351                         .inject(document.body);
353                 Syn.trigger('keydown', 'f2', div);
354                 Syn.trigger('keyup', 'f2', div);
356                 expect(keydown).toHaveBeenCalledWith('f2');
357                 expect(keyup).toHaveBeenCalledWith('f2');
359                 div.destroy();
361         });
365 describe('Keypress key code', function(){
367         /*<ltIE8>*/
368         // return early for IE8- because Syn.js does not fire events
369         if (!document.addEventListener) return;
370         /*</ltIE8>*/
372         var input, key, shift, done;
373         DOMEvent.defineKey(33, 'pageup');
375         function keyHandler(e){
376                 key = e.key;
377                 shift = !!e.event.shiftKey;
378         }
380         function typeWriter(action){
381                 setTimeout(function () {
382                         Syn.type(action, 'keyTester');
383                 }, 1);
384                 if (done) return true;
385         }
387         beforeEach(function(){
388                 input = new Element('input', {
389                         'type': 'text',
390                         'id': 'keyTester'
391                 }).addEvent('keypress', keyHandler).inject(document.body);
392         });
394         afterEach(function(){
395                 input.removeEvent('keypress', keyHandler).destroy();
396                 input = key = shift = done = null;
397         });
399         it('should return "enter" in event.key', function(){
400                 typeWriter('[enter]');
401                 waits(50);
402                 runs(function(){
403                         expect(key).toBe('enter');
404                         expect(shift).not.toBeTruthy();
405                 });
406         });
408         it('should return "1" in event.key', function(){
409                 typeWriter('1');
410                 waits(50);
411                 runs(function(){
412                         expect(key).toBe('1');
413                         expect(shift).not.toBeTruthy();
414                 });
415         });
417         it('should return "!" when pressing SHIFT + 1', function(){
418                 typeWriter('[shift]![shift-up]');
419                 waits(50);
420                 runs(function(){
421                         expect(key).toBe('!');
422                         expect(shift).toBeTruthy();
423                 });
424         });
426         it('should map code 33 correctly with keypress event', function(){
427                 var mock = {type: 'keypress', which: 33, shiftKey: true};
428                 var e = new DOMEvent(mock);
429                 expect(e.key).toBe('!');
430         });
434 describe('Element.removeEvent', function(){
436         it('should remove the onunload method', function(){
437                 var text;
438                 var handler = function(){ text = 'nope'; };
439                 window.addEvent('unload', handler);
440                 window.removeEvent('unload', handler);
441                 window.fireEvent('unload');
442                 expect(text).toBe(undefined);
443         });
448 describe('relatedTarget', function () {
450         var outer = new Element('div');
451         var el = new Element('div').inject(outer);
452         ['mouseenter', 'mouseleave', 'mouseover', 'mouseout'].each(function(event, i){
453                 it('should listen to a ' + event + ' event and set the correct relatedTarget', function(){
454                         var mockEvent = {type: event};
455                         mockEvent[(i % 2 == 0 ? 'from' : 'to') + 'Element'] = outer; // simulate FF that does not set relatedTarget
457                         var e = new DOMEvent(mockEvent);
458                         expect(e.type).toBe(event);
459                         expect(e.relatedTarget).toBe(outer);
460                 });
461         });
465 describe('Mouse wheel', function(){
467         function attachProperties(e, direction){
468                 e.detail = 1 * direction;
469                 e.wheelDelta = 1 * direction;
470                 e.deltaY = -1 * direction;
471         }
473         function dispatchFakeWheel(type, wheelDirection){
475                 var event;
476                 try {
477                         // Firefox
478                         event = document.createEvent("MouseEvents");
479                         event.initMouseEvent(type, true, true, window, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, null);
480                         attachProperties(event, wheelDirection);
481                         window.dispatchEvent(event);
482                 } catch(e){}
484                 try {
485                         // Chrome, PhantomJS, Safari
486                         event = document.createEvent("WheelEvent");
487                         event.initMouseEvent(type, 0, 100, window, 0, 0, 0, 0, null, null, null, null);
488                         attachProperties(event, wheelDirection);
489                         window.dispatchEvent(event);
490                 } catch(e){}
492                 try {
493                         // IE9
494                         event = document.createEvent("HTMLEvents");
495                         event.initEvent(type, true, false);
496                         attachProperties(event, wheelDirection);
497                         window.dispatchEvent(event);
498                 } catch(e){}
500                 try {
501                         // IE10+, Safari
502                         var event = document.createEvent("MouseEvents");
503                         event.initEvent(type, true, true);
504                         attachProperties(event, wheelDirection);
505                         window.dispatchEvent(event);
506                 } catch(e){}
508                 try {
509                         // IE8
510                         var event = document.createEventObject();
511                         document.documentElement.fireEvent(type, event);
512                 } catch(e){}
513         }
515         var triggered = false;
516         var wheel = false;
517         var testWheel = !!window.addEventListener;
518         var callback = function(e){
519                 if (e.wheel) wheel = e.wheel > 0 ? 'wheel moved up' : 'wheel moved down';
520                 triggered = 'triggered';
521         };
523         beforeEach(function(){
524                 wheel = triggered = false;
525                 window.addEvent('mousewheel', callback);
526                 document.documentElement.addEvent('mousewheel', callback);
527         });
529         afterEach(function(){
530                 window.removeEvent('mousewheel', callback);
531                 document.documentElement.removeEvent('mousewheel', callback);
532         });
534         it('should trigger/listen to mousewheel event', function(){
535                 // http://jsfiddle.net/W6QrS/3
537                 ['mousewheel', 'wheel' ,'DOMMouseScroll' ].each(dispatchFakeWheel);
538                 expect(triggered).toBeTruthy();
539         });
541         it('should listen to mouse wheel direction', function(){
542                 // http://jsfiddle.net/58yCr/
544                 if (!testWheel) return;
546                 // fire event with wheel going up
547                 ['mousewheel', 'wheel' ,'DOMMouseScroll' ].each(function(type){
548                         dispatchFakeWheel(type, 120);
549                 });
550                 expect(wheel).toEqual('wheel moved up');
551                 wheel = false;
553                 // fire event with wheel going down
554                 ['mousewheel', 'wheel' ,'DOMMouseScroll' ].each(function(type){
555                         dispatchFakeWheel(type, -120);
556                 });
557                 expect(wheel).toEqual('wheel moved down');
558         });
561 })();