├── .gitignore
├── .npmignore
├── .travis.yml
├── src
├── effroi.js
├── dsl
│ ├── element.js
│ └── input.js
├── utils.js
├── devices
│ ├── pointers.js
│ ├── tactile.js
│ ├── mouse.js
│ └── keyboard.js
└── ui
│ └── focus.js
├── LICENCE
├── package.json
├── karma.conf.js
├── test
├── input.js
├── element.js
├── focus.js
├── pointers.js
├── tactile.js
├── keyboard.js
└── mouse.js
├── Gruntfile.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | *~
3 | *.swp
4 | *.swo
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*
2 | node_modules
3 | dist
4 | *~
5 | *.swp
6 | *.swo
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.10
4 |
5 | before_script:
6 | - export DISPLAY=:99.0
7 | - sh -e /etc/init.d/xvfb start
8 | - npm install -g grunt-cli
9 | - npm install --dev
10 |
11 | script:
12 | - grunt travis
--------------------------------------------------------------------------------
/src/effroi.js:
--------------------------------------------------------------------------------
1 | // Devices
2 | module.exports.mouse = require('./devices/mouse.js');
3 | module.exports.keyboard = require('./devices/keyboard.js');
4 | module.exports.tactile = require('./devices/tactile.js');
5 | module.exports.pointers = require('./devices/pointers.js');
6 |
7 | // UI
8 | module.exports.focus = require('./ui/focus.js');
9 |
10 | // DSL
11 | module.exports.element = require('./dsl/element.js');
12 | module.exports.input = require('./dsl/input.js');
13 |
--------------------------------------------------------------------------------
/src/dsl/element.js:
--------------------------------------------------------------------------------
1 | function Element(selector) {
2 |
3 | var mouse = require('../devices/mouse.js');
4 |
5 | this.selector = selector;
6 | this.element = document.querySelector(selector);
7 | if (!this.element) {
8 | throw new Error("Element not found using selector '" + selector + "'");
9 | }
10 |
11 | this.isVisible = function isVisible() {
12 | try {
13 | var comp = window.getComputedStyle(this.element, null);
14 | return comp.visibility !== 'hidden' &&
15 | comp.display !== 'none' &&
16 | this.element.offsetHeight > 0 &&
17 | this.element.offsetWidth > 0;
18 | } catch (e) {console.log(e);
19 | return false;
20 | }
21 | };
22 |
23 | this.click = function click() {
24 | return mouse.click(this.element);
25 | };
26 |
27 | this.dblclick = function dblclick() {
28 | return mouse.dblclick(this.element);
29 | };
30 | }
31 |
32 | module.exports = function element(selector) {
33 | return new Element(selector);
34 | };
35 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2013-2015 Nicolas Froidure, Raphaël Rougeron and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "effroi",
3 | "version": "0.0.0",
4 | "description": "UI testing library.",
5 | "author": "Raphaël Rougeron, Nicolas Froidure",
6 | "main": "dist/effroi.js",
7 | "scripts": {
8 | "test": "grunt test"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/francejs/effroi.git"
13 | },
14 | "license": "MIT",
15 | "bugs": {
16 | "url": "https://github.com/francejs/effroi/issues"
17 | },
18 | "devDependencies": {
19 | "karma": "~0.10.8",
20 | "karma-chrome-launcher": "~0.1.1",
21 | "karma-firefox-launcher": "~0.1.2",
22 | "karma-phantomjs-launcher": "~0.1.1",
23 | "grunt": "~0.4.2",
24 | "grunt-contrib-clean": "~0.5.0",
25 | "grunt-contrib-watch": "~0.5.3",
26 | "grunt-contrib-jshint": "~0.7.2",
27 | "grunt-browserify": "~1.3.0",
28 | "grunt-parallel": "~0.3.1",
29 | "grunt-karma": "~0.6.2",
30 | "karma-mocha": "~0.1.1",
31 | "karma-chai": "0.0.2",
32 | "karma-sinon": "1.0.0",
33 | "matchdep": "~0.3.0"
34 | },
35 | "keywords": [
36 | "effroi",
37 | "mouse",
38 | "keyboard",
39 | "touch",
40 | "ui",
41 | "test",
42 | "testing",
43 | "event",
44 | "events",
45 | "simulation"
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/src/dsl/input.js:
--------------------------------------------------------------------------------
1 | function Input(elementOrSelector) {
2 |
3 | var mouse = require('../devices/mouse.js');
4 |
5 | if (typeof elementOrSelector == 'string') {
6 | this.element = document.querySelector(elementOrSelector);
7 | if (!this.element) {
8 | throw new Error("Element not found using selector '" + elementOrSelector + "'");
9 | }
10 | } else {
11 | if (!(elementOrSelector instanceof HTMLElement)) {
12 | throw new Error("Invalid input() arg: only selector or HTMLElement are supported");
13 | }
14 | this.element = elementOrSelector;
15 | }
16 |
17 | this.val = function val() {
18 | return this.element.value;
19 | };
20 |
21 | this.set = function set(value) {
22 | try {
23 | this.element.focus();
24 | } catch (e) {
25 | throw new Error("Unable to focus() input field " + this.element.getAttribute('name') + ": " + e);
26 | }
27 |
28 | this.element.value = value;
29 | };
30 |
31 | this.fill = function fill(value, method) {
32 | method = method || 'paste';
33 | switch(method) {
34 | case 'paste':
35 | mouse.paste(this.element, value);
36 | break;
37 | }
38 | };
39 | }
40 |
41 | module.exports = function input(elementOrSelector) {
42 | return new Input(elementOrSelector);
43 | };
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Tue Oct 15 2013 15:27:53 GMT+0200 (CEST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path, that will be used to resolve files and exclude
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | frameworks: ['mocha','chai','sinon'],
13 |
14 |
15 | // list of files / patterns to load in the browser
16 | files: [
17 | 'dist/effroi.js',
18 | 'test/**/*.js'
19 | ],
20 |
21 |
22 | // list of files to exclude
23 | exclude: [
24 |
25 | ],
26 |
27 |
28 | // test results reporter to use
29 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
30 | reporters: ['progress'],
31 |
32 |
33 | // web server port
34 | port: 9876,
35 |
36 |
37 | // enable / disable colors in the output (reporters and logs)
38 | colors: true,
39 |
40 |
41 | // level of logging
42 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
43 | logLevel: config.LOG_INFO,
44 |
45 |
46 | // enable / disable watching file and executing tests whenever any file changes
47 | autoWatch: true,
48 |
49 |
50 | // Start these browsers, currently available:
51 | // - Chrome
52 | // - ChromeCanary
53 | // - Firefox
54 | // - Opera
55 | // - Safari (only Mac)
56 | // - PhantomJS
57 | // - IE (only Windows)
58 | browsers: ['Chrome', 'Firefox', 'PhantomJS'],
59 |
60 |
61 | // If browser does not capture in given timeout [ms], kill it
62 | captureTimeout: 60000,
63 |
64 |
65 | // Continuous Integration mode
66 | // if true, it capture browsers, run tests and exit
67 | singleRun: false
68 | });
69 | };
70 |
--------------------------------------------------------------------------------
/test/input.js:
--------------------------------------------------------------------------------
1 | describe("Input DSL", function() {
2 | function createElement(html) {
3 | var elt = document.createElement('div');
4 | elt.id = 'foo';
5 | elt.innerHTML = html;
6 | document.body.appendChild(elt);
7 | }
8 |
9 | var assert = chai.assert,
10 | input = effroi.input;
11 |
12 | afterEach(function() {
13 | document.body.removeChild(document.getElementById('foo'));
14 | });
15 |
16 | var textInputs = [
17 | {
18 | type: "input[type='text']",
19 | html: ''
20 | },
21 | {
22 | type: "input[type='password']",
23 | html: ''
24 | },
25 | {
26 | type: "textarea",
27 | html: ''
28 | }
29 | ];
30 |
31 | textInputs.forEach(function(textInput) {
32 | describe("input() with an "+textInput.type, function() {
33 | beforeEach(function() {
34 | createElement(textInput.html);
35 | });
36 |
37 | describe("val()", function() {
38 | it("should return the input value", function() {
39 | assert.equal('hello', input('[name="bar"]').val());
40 | });
41 | });
42 |
43 | describe("set()", function() {
44 | it("should set the input value", function() {
45 | input('[name="bar"]').set('salut');
46 | assert.equal('salut', document.querySelector('[name="bar"]').value);
47 | });
48 | });
49 |
50 | describe("fill()", function() {
51 | it("can paste the input value", function() {
52 | input('[name="bar"]').fill('salut', 'paste');
53 | assert.equal('salut', document.querySelector('[name="bar"]').value);
54 | });
55 | });
56 | });
57 | });
58 | });
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
4 |
5 | grunt.initConfig({
6 | clean: ['dist'],
7 |
8 | jshint: {
9 | all: {
10 | src: ['Gruntfile.js', 'src/**/*.js'],
11 | options: {
12 | '-W014': true
13 | }
14 | }
15 | },
16 |
17 | browserify: {
18 | lib: {
19 | src: 'src/effroi.js',
20 | dest: 'dist/effroi.js',
21 | options: {
22 | standalone: 'effroi'
23 | }
24 | }
25 | },
26 |
27 | karma: {
28 | local: {
29 | configFile: 'karma.conf.js'
30 | },
31 | travis: {
32 | configFile: 'karma.conf.js',
33 | singleRun: true,
34 | browsers: ['Firefox', 'PhantomJS']
35 | }
36 | },
37 |
38 | watch: {
39 | code: {
40 | files: ['src/**/*.js'],
41 | tasks: ['jshint', 'dist']
42 | }
43 | },
44 |
45 | parallel: {
46 | testing: {
47 | options: {
48 | stream: true
49 | },
50 | tasks: [{
51 | grunt: true,
52 | args: ['karma:local']
53 | },{
54 | grunt: true,
55 | args: ['watch']
56 | }]
57 | }
58 | }
59 | });
60 |
61 | grunt.registerTask('dist', [
62 | 'clean',
63 | 'browserify'
64 | ]);
65 |
66 | grunt.registerTask('test', [
67 | 'dist',
68 | 'parallel:testing'
69 | ]);
70 |
71 | grunt.registerTask('hint', [
72 | 'jshint'
73 | ]);
74 |
75 | grunt.registerTask('default', [
76 | 'test'
77 | ]);
78 |
79 | grunt.registerTask('travis', [
80 | 'dist',
81 | 'karma:travis'
82 | ]);
83 | };
84 |
--------------------------------------------------------------------------------
/test/element.js:
--------------------------------------------------------------------------------
1 | describe("Element DSL", function() {
2 | var assert = chai.assert,
3 | element = effroi.element,
4 | mouse = effroi.mouse;
5 |
6 | describe("element()", function() {
7 | beforeEach(function() {
8 | var elt = document.createElement('div');
9 | elt.id = 'foo';
10 | elt.innerHTML = 'test';
11 | document.body.appendChild(elt);
12 | });
13 |
14 | afterEach(function() {
15 | document.body.removeChild(document.getElementById('foo'));
16 | });
17 |
18 | it("should return an object", function() {
19 | assert(typeof element('#foo') == 'object');
20 | });
21 |
22 | it("should throw if the provided selector doesn't match any element", function() {
23 | assert.throw(function() { element('#bar'); }, Error);
24 | });
25 |
26 | describe("isVisible()", function() {
27 | it("should return true if the element is visible", function() {
28 | assert.ok(element('#foo').isVisible());
29 | });
30 |
31 | it("should return false if the element's visibility is set to hidden", function() {
32 | document.getElementById('foo').setAttribute('style', 'visibility: hidden');
33 | assert.notOk(element('#foo').isVisible());
34 | });
35 | });
36 |
37 | describe("click()", function() {
38 | before(function() {
39 | sinon.spy(mouse, 'click');
40 | });
41 |
42 | after(function() {
43 | mouse.click.restore();
44 | });
45 |
46 | it("should call mouse.click() with an element", function() {
47 | element('#foo').click();
48 | assert(mouse.click.calledOnce);
49 | assert(mouse.click.args[0][0] instanceof Element);
50 | });
51 | });
52 |
53 | describe("dblclick()", function() {
54 | before(function() {
55 | sinon.spy(mouse, 'dblclick');
56 | });
57 |
58 | after(function() {
59 | mouse.dblclick.restore();
60 | });
61 |
62 | it("should call mouse.dblclick() with an element", function() {
63 | element('#foo').dblclick();
64 | assert(mouse.dblclick.calledOnce);
65 | assert(mouse.dblclick.args[0][0] instanceof Element);
66 | });
67 | });
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/francejs/effroi)
4 |
5 | A JavaScript event-simulation, device oriented library for UI testing.
6 |
7 | ## Device oriented ?
8 |
9 | Effroi try to be the closest possible of the way your users use their input
10 | devices. To achieve this goal, effroi emulate real physical devices and fires
11 | every events a real device would fire for the same action.
12 |
13 | By example, when a user want to click an element the following events sequence
14 | are fired:
15 | - several mousewheel events to scroll to the element (if needed)
16 | - several mouseout/mousemove/mouseover events to go over the element (if needed)
17 | - mousedown/mouseup events
18 | - a click event if none of the 2 previous events have been prevented
19 |
20 | That's typically what effroi emulate for your UI tests. Effroi will also check
21 | for the feasibility of the requested action. If you try to click an element
22 | that is nor visible nor clickable, effroi will throw an error.
23 |
24 | ## How to use
25 |
26 | ### Device API
27 |
28 | The device API goal is to emulate a real device and fire every events usually
29 | fired for each action a device permit.
30 |
31 | #### Mouse
32 | ```js
33 | var mouse = effroi.mouse;
34 | // Scrolling with the mouse
35 | mouse.scroll(x, y); // Returns true if scrolled false otherwise
36 | mouse.scrollTo(element); // Returns true if scrolled
37 | // Moving the cursor
38 | mouse.move(x, y); // Returns true if dispatched
39 | mouse.moveTo(element); // Returns true if dispatched
40 | // Clicking
41 | mouse.click(element); // Returns true if dispatched
42 | // Right-clicking
43 | mouse.rightclick(element); // Returns true if dispatched
44 | // Double-clicking
45 | mouse.dblclick(element); // Returns true if dispatched
46 | // Pasting with the mouse
47 | mouse.paste(inputElement, 'content');
48 | // Cutting with the mouse
49 | mouse.cut(inputElement); // Returns the cutted content
50 | ```
51 |
52 | #### Keyboard
53 | ```js
54 | var kbd = effroi.keyboard;
55 | // Tabbing with the keyboard
56 | kbd.tab(); // Returns true if dispatched
57 | // Focusing an element
58 | kbd.focus(element); // Returns true if focus changed
59 | // Hitting a key
60 | kbd.hit('a'); // Returns true if dispatched
61 | kbd.hit('b','c','d'); // Returns true if dispatched
62 | // Combining keys
63 | kbd.combine(this.CTRL, 'c'); // Returns true if dispatched
64 | // Pasting with the keyboard
65 | kbd.paste('content');
66 | // Cutting with the keyboard
67 | kbd.cut(); // Returns the cutted content
68 | ```
69 |
70 | #### Tactile
71 | ```js
72 | var tactile = effroi.tactile;
73 | // Scrolling with the tactile display
74 | tactile.scroll(x, y); // Returns true if scrolled false otherwise
75 | tactile.scrollTo(element); // Returns true if scrolled
76 | // Performing a touch
77 | tactile.touch(); // Returns true if dispatched
78 | ```
79 |
80 | #### Unified pointing device API (PointerEvents)
81 | Under development.
82 |
83 | ## How to contribute
84 |
85 | 1. Clone this repo
86 | 2. npm install
87 | 3. To run the tests:
88 |
89 | ```sh
90 | grunt test
91 | ```
92 | 4. To build the lib:
93 |
94 | ```sh
95 | grunt dist
96 | ```
97 |
--------------------------------------------------------------------------------
/test/focus.js:
--------------------------------------------------------------------------------
1 | var focus = effroi.focus,
2 | assert = chai.assert;
3 |
4 | // Helper
5 | var elt, evts=[], listeners=[];
6 | function regEventListener(elt, type, capture, stop, prevent) {
7 | var listener=function(e) {
8 | if(e.type!=type) {
9 | throw 'Event types differs.'
10 | }
11 | evts.push({
12 | type : e.type,
13 | target : e.target,
14 | currentTarget : e.currentTarget,
15 | clientX : e.clientX,
16 | clientY : e.clientY,
17 | altKey : e.altKey,
18 | ctrlKey : e.ctrlKey,
19 | shiftKey : e.shiftKey,
20 | metaKey : e.metaKey,
21 | button : e.button,
22 | buttons : e.buttons,
23 | pointerType : e.pointerType,
24 | charCode : e.charCode,
25 | char : e.char,
26 | view : e.view,
27 | relatedTarget : e.relatedTarget
28 | });
29 | if(stop) {
30 | e.stopPropagation();
31 | }
32 | if(prevent) {
33 | e.preventDefault();
34 | }
35 | };
36 | elt.addEventListener(type, listener, capture);
37 | listeners.push({
38 | elt: elt,
39 | type: type,
40 | listener : listener,
41 | capture : capture
42 | });
43 | }
44 |
45 | function init(innerHTML) {
46 | elt = document.createElement('div');
47 | elt.innerHTML = innerHTML || 'foo';
48 | document.body.appendChild(elt);
49 | }
50 |
51 | function uninit() {
52 | document.body.removeChild(elt);
53 | for(var i=listeners.length-1; i>=0; i--) {
54 | listeners[i].elt.removeEventListener(
55 | listeners[i].type, listeners[i].listener, listeners[i].capture);
56 | }
57 | evts=[];
58 | elt=null;
59 | }
60 |
61 | describe("UI focus", function() {
62 |
63 | describe("set to an element when another element is focused", function() {
64 |
65 | var previousActiveElement;
66 |
67 | before(function() {
68 | init(
69 | '
'
70 | + ''
71 | + ''
72 | + '
'
73 | );
74 | elt.firstChild.firstChild.lastChild.focus();
75 | regEventListener(elt.firstChild.firstChild.lastChild, 'blur');
76 | regEventListener(elt.firstChild.firstChild.lastChild, 'focusout');
77 | regEventListener(elt.firstChild.lastChild.lastChild, 'focus');
78 | regEventListener(elt.firstChild.lastChild.lastChild, 'focusin');
79 | regEventListener(document.body, 'focusout');
80 | regEventListener(document.body, 'focusin');
81 | });
82 |
83 | after(uninit);
84 |
85 | it("should return true", function() {
86 | assert.equal(focus.focus(elt.firstChild.lastChild.lastChild), true);
87 | });
88 |
89 | it("should set the element as the active element", function() {
90 | assert.equal(elt.firstChild.lastChild.lastChild, document.activeElement);
91 | });
92 |
93 | it("should trigger a blur event on the previously focused element", function() {
94 | assert.equal(evts[0].type, 'blur');
95 | assert.equal(evts[0].target, elt.firstChild.firstChild.lastChild);
96 | });
97 |
98 | it("should not bubble the blur event on the element parents", function() {
99 | assert.notEqual(evts[1].type, 'blur');
100 | });
101 |
102 | it("should trigger a focusout event on the previously focused element", function() {
103 | assert.equal(evts[1].type, 'focusout');
104 | assert.equal(evts[1].target, elt.firstChild.firstChild.lastChild);
105 | });
106 |
107 | it("should bubble the focusout event to element parents", function() {
108 | assert.equal(evts[2].type, 'focusout');
109 | assert.equal(evts[2].target, elt.firstChild.firstChild.lastChild);
110 | });
111 |
112 | it("should trigger a focus event on the newly focused element", function() {
113 | assert.equal(evts[3].type, 'focus');
114 | assert.equal(evts[3].target, elt.firstChild.lastChild.lastChild);
115 | });
116 |
117 | it("should not bubble the focus event on the element parents", function() {
118 | assert.notEqual(evts[4].type, 'focus');
119 | });
120 |
121 | it("should trigger a focusin event on the previously focused element", function() {
122 | assert.equal(evts[4].type, 'focusin');
123 | assert.equal(evts[4].target, elt.firstChild.lastChild.lastChild);
124 | });
125 |
126 | it("should bubble the focusin event to element parents", function() {
127 | assert.equal(evts[5].type, 'focusin');
128 | assert.equal(evts[5].target, elt.firstChild.lastChild.lastChild);
129 | });
130 |
131 | });
132 |
133 | });
134 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | module.exports={
2 |
3 | setEventCoords: function(event, x, y) {
4 | this.setEventProperty(event, 'clientX', x);
5 | this.setEventProperty(event, 'clientY', y);
6 | this.setEventProperty(event, 'pageX', x + window.scrollX);
7 | this.setEventProperty(event, 'pageY', y + window.scrollY);
8 | },
9 |
10 | // Find the center of an element
11 | getElementCenter: function(element) {
12 | var c={};
13 | try {
14 | var rect = element.getBoundingClientRect();
15 | c.x = Math.floor((rect.left + rect.right) / 2);
16 | c.y = Math.floor((rect.top + rect.bottom) / 2);
17 | } catch(e) {
18 | c.x = 1;
19 | c.y = 1;
20 | }
21 | return c;
22 | },
23 |
24 | // Find a point in the viewport at wich an element can be the root of
25 | // a pointer event (is not under another element)
26 | getPossiblePointerCoords: function(element) {
27 | var comp, rects, coords = null;
28 | if(!(element instanceof HTMLElement)) {
29 | throw new Error('getPossiblePointerCoords needs a valid HTMLElement.');
30 | }
31 | comp = window.getComputedStyle(element, null);
32 | rects=element.getClientRects();
33 | if('none' !== comp.pointerEvents && rects.length) {
34 | mainLoop: for(var i=rects.length-1; i>=0; i--) {
35 | for(var x=rects[i].left, mX=rects[i].right; x window.innerWidth ? x - window.innerWidth : 0)
49 | ),
50 | scrollY = (y < 0 ? y :
51 | (y > window.innerHeight ? y - window.innerHeight : 0)
52 | ),
53 | moveX=Math.round(window.innerWidth/2),
54 | moveY=Math.round(window.innerHeight/2);
55 | options = options || {};
56 | // Put the finger on the middle of the screen
57 | options.type = 'touchstart';
58 | dispatched = this.dispatch(document.elementFromPoint(moveX, moveY),
59 | options);
60 | // Moving through the x/y axis
61 | while(dispatched && (scrollX !== 0 || scrollY !== 0)) {
62 | // repeat the move if the finger is about to go out of the screen
63 | if(moveX<10||moveY<10
64 | ||moveX>window.innerWidth-10
65 | ||moveY>window.innerHeight-10) {
66 | moveX = Math.round(window.innerWidth/2);
67 | moveY = Math.round(window.innerHeight/2);
68 | // Remove the finger of the screen
69 | options.type = 'touchend';
70 | dispatched = this.dispatch(document.elementFromPoint(moveX, moveY),
71 | options);
72 | // Re-put the finger on the middle of the screen
73 | options.type = 'touchstart';
74 | dispatched = this.dispatch(document.elementFromPoint(moveX, moveY),
75 | options);
76 | }
77 | // Move the finger
78 | options.type = 'touchmove';
79 | dispatched = dispatched &&
80 | this.dispatch(document.elementFromPoint(moveX, moveY), options);
81 | if(dispatched) {
82 | moveX += (scrollX < 0 ? 5 : -5);
83 | moveY += (scrollY < 0 ? 5 : -5);
84 | window.scrollTo(window.scrollX
85 | - (scrollX < 0 ? 120 : (scrollX > 0 ? -120 : 0)),
86 | window.scrollY
87 | - (scrollY < 0 ? 120 : (scrollY > 0 ? -120 : 0)));
88 | if(scrollX) {
89 | scrollX = (scrollX < 0 ?
90 | (scrollX + 120 > 0 ? 0 : scrollX + 120) :
91 | (scrollX - 120 < 0 ? 0 : scrollX - 120));
92 | }
93 | if(scrollY) {
94 | scrollY = (scrollY < 0 ?
95 | (scrollY + 120 > 0 ? 0 : scrollY + 120) :
96 | (scrollY - 120 < 0 ? 0 : scrollY - 120));
97 | }
98 | }
99 | }
100 | // Remove the finger of the screen
101 | options.type = 'touchend';
102 | dispatched = dispatched &&
103 | this.dispatch(document.elementFromPoint(moveX, moveY), options);
104 | return dispatched;
105 | };
106 |
107 | /**
108 | * Perform a scroll with the fingers to an element.
109 | *
110 | * @param DOMElement element A DOMElement on wich to scroll to
111 | * @param Object options Touch options
112 | * @return Boolean
113 | */
114 | this.scrollTo = function scrollTo(element, options) {
115 | // Getting element center
116 | var c = utils.getElementCenter(element);
117 | // Scroll only if the element is not already in the viewport
118 | if(c.x<0 || c.y<0 || c.x > window.innerWidth || c.y > window.innerHeight) {
119 | return this.scroll(c.x, c.y, options);
120 | }
121 | return false;
122 | };
123 |
124 | /**
125 | * Dispatches a touch event to the given DOM element.
126 | *
127 | * @param DOMElement element A DOMElement on wich to dispatch the event
128 | * @param Object options Event options
129 | * @return Boolean
130 | */
131 | this.dispatch = function (element,options) {
132 | var event = document.createEvent('UIEvent'), coords;
133 | options = options || {};
134 | options.canBubble = ('false' === options.canBubble ? false : true);
135 | options.cancelable = ('false' === options.cancelable ? false : true);
136 | options.view = options.view || window;
137 | options.detail = options.detail || 1;
138 | options.altKey = !!options.altKey;
139 | options.ctrlKey = !!options.ctrlKey;
140 | options.shiftKey = !!options.shiftKey;
141 | options.metaKey = !!options.metaKey;
142 | options.changedTouches = options.changedTouches || [];
143 | options.touches = options.touches || [];
144 | options.scale = options.scale || 1.0;
145 | options.rotation = options.rotation || 0.0;
146 | coords = utils.getPossiblePointerCoords(element);
147 | if(null===coords) {
148 | throw Error('Unable to find a point in the viewport at wich the given'
149 | +' element can receive a touch event.');
150 | }
151 | // Safari, Firefox: must use initTouchEvent.
152 | if ("function" === typeof event.initTouchEvent) {
153 | event.initTouchEvent(options.type,
154 | options.canBubble, options.cancelable,
155 | options.view, options.detail,
156 | // Screen coordinates (relative to the whole user screen)
157 | // FIXME: find a way to get the right screen coordinates
158 | coords.x + window.screenLeft, coords.y + window.screenTop,
159 | // Client coordinates (relative to the viewport)
160 | coords.x, coords.y,
161 | options.ctrlKey, options.altKey,
162 | options.shiftKey, options.metaKey,
163 | options.touches, options.targetTouches, options.changedTouches,
164 | options.scale, options.rotation);
165 | utils.setEventCoords(event, coords.x, coords.y);
166 | } else {
167 | event.initUIEvent(options.type,
168 | options.canBubble, options.cancelable,
169 | options.view, options.detail);
170 | utils.setEventProperty(event, 'altKey', options.altKey);
171 | utils.setEventProperty(event, 'ctrlKey', options.ctrlKey);
172 | utils.setEventProperty(event, 'shiftKey', options.shiftKey);
173 | utils.setEventProperty(event, 'metaKey', options.metaKey);
174 | utils.setEventProperty(event, 'changedTouches', options.changedTouches);
175 | utils.setEventProperty(event, 'touches', options.touches);
176 | utils.setEventProperty(event, 'scale', options.scale);
177 | utils.setEventProperty(event, 'rotation', options.rotation);
178 | utils.setEventCoords(event, coords.x, coords.y);
179 | }
180 | return element.dispatchEvent(event);
181 | };
182 |
183 | }
184 |
185 | module.exports = new Tactile();
186 |
--------------------------------------------------------------------------------
/src/devices/mouse.js:
--------------------------------------------------------------------------------
1 | function Mouse() {
2 |
3 | var utils = require('../utils.js');
4 | var uiFocus = require('../ui/focus.js');
5 |
6 | // Consts
7 | this.LEFT_BUTTON = 1;
8 | this.RIGHT_BUTTON = 2;
9 | this.MIDDLE_BUTTON = 4;
10 | this.BACK_BUTTON = 8;
11 | this.FORWARD_BUTTON = 16;
12 | this.BUTTONS_MASK = this.LEFT_BUTTON | this.RIGHT_BUTTON
13 | | this.MIDDLE_BUTTON | this.BACK_BUTTON | this.FORWARD_BUTTON;
14 |
15 | // Private vars
16 | var _x = 1, _y = 1;
17 |
18 | /**
19 | * Select content like if it were done by a user with his mouse.
20 | *
21 | * @param DOMElement element A DOMElement to dblclick
22 | * @param Number start The selection start
23 | * @param Number end The selection end
24 | * @return Boolean
25 | */
26 | this.select = function cut(element, start, end) {
27 | if(!utils.isSelectable(element)) {
28 | throw Error('Cannot select the element content.');
29 | }
30 | if(!start) {
31 | start = 0;
32 | } else if(start < 0 || start > element.value.length) {
33 | throw RangeError('Invalid selection start.');
34 | }
35 | if(!end) {
36 | end = element.value.length;
37 | } else if(end > element.value.length || end < start) {
38 | throw RangeError('Invalid selection end.');
39 | }
40 | // We move to the element if not over yet
41 | this.moveTo(element);
42 | // To select, we keep the mousedown over the input
43 | options = {};
44 | options.type = 'mousedown';
45 | // if the mousedown event is prevented we can't select content
46 | if(!this.dispatch(element, options)) {
47 | return false;
48 | }
49 | // We move over the selection to perform
50 | // FIXME: This should be done better with real coords
51 | options.type = 'mousemove';
52 | this.dispatch(element, options);
53 | // if the mouseup event is prevented the whole content is selected
54 | options.type = 'mouseup';
55 | if(!this.dispatch(element, options)) {
56 | end = element.value.length;
57 | }
58 | // finally selecting the content
59 | element.selectionStart = start;
60 | element.selectionEnd = end;
61 | return true;
62 | };
63 |
64 | /**
65 | * Cut selected content like if it were done by a user with his mouse.
66 | *
67 | * @param DOMElement element A DOMElement to dblclick
68 | * @param String content The content to paste
69 | * @return Boolean
70 | */
71 | this.cut = function cut(element) {
72 | var content;
73 | // We move to the element if not over yet
74 | this.moveTo(element);
75 | // To cut, we right-click but only the mousedown is fired due to the
76 | // contextual menu that appears
77 | options = {};
78 | options.type = 'mousedown';
79 | // if the mousedown event is prevented we can't cut content
80 | if(!this.dispatch(element, options)) {
81 | return '';
82 | }
83 | // if content is selectable, we cut only the selected content
84 | if(utils.isSelectable(element)) {
85 | content = element.value.substr(element.selectionStart, element.selectionEnd-1);
86 | element.value =
87 | (element.selectionStart ?
88 | element.value.substr(0, element.selectionStart) : '')
89 | + (element.selectionEnd ?
90 | element.value.substr(element.selectionEnd) :
91 | '');
92 | // otherwise we cut the full content
93 | } else {
94 | content = element.value;
95 | element.value = null;
96 | }
97 | // finally firing an input event
98 | utils.dispatch(element, {type: 'input'});
99 | return content;
100 | };
101 |
102 | /**
103 | * Paste content like if it were done by a user with his mouse.
104 | *
105 | * @param DOMElement element A DOMElement to dblclick
106 | * @param String content The content to paste
107 | * @return Boolean
108 | */
109 | this.paste = function paste(element, content) {
110 | // The content of a paste is always a string
111 | if('string' !== typeof content) {
112 | throw Error('Can only paste strings (received '+(typeof content)+').');
113 | }
114 | if(!utils.canAcceptContent(element, content)) {
115 | throw Error('Unable to paste content in the given element.');
116 | }
117 | // We move to the element if not over yet
118 | this.moveTo(element);
119 | options = {};
120 | options.type = 'mousedown';
121 | // if the mousedown event is prevented we can't paste content
122 | if(!this.dispatch(element, options)) {
123 | return false;
124 | }
125 | // if content is selectable, we paste content in the place of the selected content
126 | if(utils.isSelectable(element)) {
127 | element.value =
128 | (element.selectionStart ?
129 | element.value.substr(0, element.selectionStart) : '')
130 | + content
131 | + (element.selectionEnd ?
132 | element.value.substr(element.selectionEnd) :
133 | '');
134 | // otherwise we just replace the value
135 | } else {
136 | element.value = content;
137 | }
138 | // finally firing an input event
139 | return utils.dispatch(element, {type: 'input'});
140 | };
141 |
142 | /**
143 | * Perform a real mouse double click on the given DOM element.
144 | *
145 | * @param DOMElement element A DOMElement to dblclick
146 | * @param Object options Clic options
147 | * @return Boolean
148 | */
149 | this.dblclick = function dblclick(element, options) {
150 | var dispatched;
151 | // We move to the element if not over yet
152 | this.moveTo(element);
153 | options = options||{};
154 | dispatched = this.click(element, options);
155 | if(!(this.click(element, options)&&dispatched)) {
156 | return false;
157 | }
158 | options.type = 'dblclick';
159 | return this.dispatch(element, options);
160 | };
161 |
162 | /**
163 | * Perform a real mouse rightclick on the given DOM element.
164 | *
165 | * @param DOMElement element A DOMElement to rightclick
166 | * @param Object options Clic options
167 | * @return Boolean
168 | */
169 | this.rightclick = function rightclick(element, options) {
170 | options = options || {};
171 | options.buttons = this.RIGHT_BUTTON;
172 | return this.click(element, options);
173 | };
174 |
175 | /**
176 | * Perform a real mouse click on the given DOM element.
177 | *
178 | * @param DOMElement element A DOMElement to click
179 | * @param Object options Clic options
180 | * @return Boolean
181 | */
182 | this.click = function click(element, options) {
183 | var dispatched;
184 | // We move to the element if not over yet
185 | this.moveTo(element);
186 | options = options || {};
187 | options.type = 'mousedown';
188 | dispatched = this.dispatch(element, options);
189 | options.type = 'mouseup';
190 | if(!(this.dispatch(element, options)&&dispatched)) {
191 | return false;
192 | }
193 | options.type = 'click';
194 | return this.dispatch(element, options);
195 | };
196 |
197 | /**
198 | * Focus a DOM element with the mouse.
199 | *
200 | * @param DOMElement element A DOMElement to focus
201 | * @param Object options Event options
202 | * @return Boolean
203 | */
204 | this.focus = function focus(element, options) {
205 | var dispatched;
206 | // We move to the element if not over yet
207 | this.moveTo(element);
208 | options = options || {};
209 | options.type = 'mousedown';
210 | dispatched=this.dispatch(element, options);
211 | // Here, maybe find the first parent element having greater bound rect
212 | // and move on it's focusable zone or fail if none available
213 | if(dispatched) {
214 | uiFocus.focus(element);
215 | }
216 | this.moveTo(element.parentNode);
217 | options.type = 'mouseup';
218 | this.dispatch(element.parentNode, options);
219 | return dispatched;
220 | };
221 |
222 | /**
223 | * Perform a real mouse move to the given coordinates.
224 | *
225 | * @param int x The x position to go
226 | * @param int y The y position to go
227 | * @param Object options Clic options
228 | * @return Boolean
229 | */
230 | this.move = function move(x, y, options) {
231 | var curElement = document.elementFromPoint(_x, _y),
232 | targetElement,
233 | oldScrollX = window.scrollX,
234 | oldScrollY = window.scrollY,
235 | dispatched;
236 | // Could move the cursor of %n px and repeat mouseover/out events
237 | // killer feature or overkill ?
238 | options = options || {};
239 | options.type = 'mouseout';
240 | dispatched = this.dispatch(curElement, options);
241 | this.scroll(x, y, options);
242 | _x = x + oldScrollX - window.scrollX;
243 | _y = y + oldScrollY - window.scrollY;
244 | if(_x < 0 || _y < 0) {
245 | throw new Error('The mouse pointer coordinates can\'t be negative.');
246 | }
247 | if(_x >= window.innerWidth || _y >= window.innerHeight) {
248 | throw new Error('The mouse pointer coordinates can\'t be greater than the'
249 | +' viewport size.');
250 | }
251 | targetElement = document.elementFromPoint(_x, _y);
252 | if(!targetElement) {
253 | throw Error('Couldn\'t perform the move. Coordinnates seems invalid.');
254 | }
255 | if(curElement===targetElement) {
256 | return false;
257 | }
258 | options.type = 'mouseover';
259 | options.relatedTarget = curElement;
260 | dispatched = this.dispatch(targetElement, options);
261 | return true;
262 | };
263 |
264 | /**
265 | * Perform a real mouse move to an element.
266 | *
267 | * @param DOMElement element A DOMElement on wich to move the cursor
268 | * @param Object options Clic options
269 | * @return Boolean
270 | */
271 | this.moveTo = function moveTo(element, options) {
272 | var c = utils.getElementCenter(element);
273 | // We are giving the related target to avoid calculating it later
274 | options = options || {};
275 | options.relatedTarget = element;
276 | return this.move(c.x, c.y, options);
277 | };
278 |
279 | /**
280 | * Perform a scroll with the mouse wheel.
281 | *
282 | * @param int x The x delta to scroll to
283 | * @param int y The y delta to scroll to
284 | * @param Object options Clic options
285 | * @return Boolean
286 | */
287 | this.scroll = function scroll(x, y, options) {
288 | var dispatched=true, scrollX = 0, scrollY = 0;
289 | options = options || {};
290 | options.type = ('onwheel' in document ? 'wheel' :
291 | ('onmousewheel' in document ? 'mousewheel' : '')
292 | );
293 | if(options.type) {
294 | // Moving through the x axis
295 | options.shiftKey = true;
296 | options.wheelDelta = 120;
297 | options.wheelDeltaX = (x < 0 ? 120 : -120);
298 | options.wheelDeltaY = 0;
299 | while(dispatched && (x+scrollX<0 || x+scrollX > window.innerWidth)) {
300 | dispatched=this.dispatch(document.elementFromPoint(_x, _y), options);
301 | if(dispatched) {
302 | scrollX += options.wheelDeltaX;
303 | window.scrollTo(window.scrollX - options.wheelDeltaX, window.scrollY);
304 | }
305 | }
306 | // Then moving through the y axis
307 | options.wheelDelta = 120;
308 | options.wheelDeltaX = 0;
309 | options.wheelDeltaY = (y < 0 ? 120 : -120);
310 | while(dispatched && (y+scrollY<0 || y+scrollY > window.innerHeight)) {
311 | dispatched=this.dispatch(document.elementFromPoint(_x, _y), options);
312 | if(dispatched) {
313 | scrollY += options.wheelDeltaY;
314 | window.scrollTo(window.scrollX, window.scrollY - options.wheelDeltaY);
315 | }
316 | }
317 | }
318 | return dispatched;
319 | };
320 |
321 | /**
322 | * Perform a scroll with the mouse wheel.
323 | *
324 | * @param DOMElement element A DOMElement on wich to scroll to
325 | * @param Object options Clic options
326 | * @return Boolean
327 | */
328 | this.scrollTo = function scrollTo(element, options) {
329 | // Getting element center
330 | var c = utils.getElementCenter(element);
331 | // Scroll only if the element is not already in the viewport
332 | if(c.x<0 || c.y<0 || c.x > window.innerWidth || c.y > window.innerHeight) {
333 | return this.scroll(c.x, c.y, options);
334 | }
335 | return false;
336 | };
337 |
338 | /**
339 | * Dispatches a mouse event to the given DOM element.
340 | *
341 | * @param DOMElement element A DOMElement on wich to dispatch the event
342 | * @param Object options Event options
343 | * @return Boolean
344 | */
345 | this.dispatch = function dispatch(element, options) {
346 | var event, button, coords;
347 | options = options || {};
348 | options.type = options.type || 'click';
349 | if(options.buttons !== options.buttons&this.BUTTONS_MASK) {
350 | throw Error('Bad value for the "buttons" property.');
351 | }
352 | options.buttons = options.buttons || this.LEFT_BUTTON;
353 | if(options.button) {
354 | throw Error('Please use the "buttons" property.');
355 | }
356 | button=( options.buttons&this.RIGHT_BUTTON ? 2 :
357 | ( options.buttons&this.MIDDLE_BUTTON? 1 : 0 )
358 | );
359 | coords = utils.getPossiblePointerCoords(element);
360 | if(null===coords) {
361 | throw Error('Unable to find a point in the viewport at wich the given'
362 | +' element can receive a mouse event.');
363 | }
364 | options.canBubble = ('false' === options.canBubble ? false : true);
365 | options.cancelable = ('false' === options.cancelable ? false : true);
366 | options.view = options.view || window;
367 | options.detail = options.detail || 1;
368 | options.altKey = !!options.altKey;
369 | options.ctrlKey = !!options.ctrlKey;
370 | options.shiftKey = !!options.shiftKey;
371 | options.metaKey = !!options.metaKey;
372 | options.relatedTarget = options.relatedTarget||null;
373 | // try to use the constructor (recommended with DOM level 3)
374 | // http://www.w3.org/TR/DOM-Level-3-Events/#new-event-interface-initializers
375 | try {
376 | event = new MouseEvent(options.type, {
377 | bubbles: options.canBubble,
378 | cancelable: options.cancelable,
379 | view: options.view,
380 | // FIXME: find a way to get the right screen coordinates
381 | screenX: coords.x + window.screenLeft,
382 | screenY: coords.y + window.screenTop,
383 | clientX: coords.x,
384 | clientY: coords.y,
385 | altKey: options.altKey,
386 | ctrlKey: options.ctrlKey,
387 | shiftKey: options.shiftKey,
388 | metaKey: options.metaKey,
389 | button: button,
390 | buttons: options.buttons,
391 | relatedTarget: options.relatedTarget
392 | });
393 | // Chrome seems to not set the buttons property properly
394 | utils.setEventProperty(event, 'buttons', options.buttons);
395 | return element.dispatchEvent(event);
396 | } catch(e) {
397 | // old fashined event intializer
398 | if(document.createEvent) {
399 | event = document.createEvent('MouseEvent');
400 | event.initMouseEvent(options.type,
401 | options.canBubble, options.cancelable,
402 | options.view, options.detail,
403 | // Screen coordinates (relative to the whole user screen)
404 | // FIXME: find a way to get the right screen coordinates
405 | coords.x + window.screenLeft, coords.y + window.screenTop,
406 | // Client coordinates (relative to the viewport)
407 | coords.x, coords.y,
408 | options.ctrlKey, options.altKey,
409 | options.shiftKey, options.metaKey,
410 | button, options.relatedTarget);
411 | utils.setEventCoords(event, coords.x, coords.y);
412 | utils.setEventProperty(event, 'buttons', options.buttons);
413 | return element.dispatchEvent(event);
414 | // old IE event initializer
415 | } else if(document.createEventObject) {
416 | event = document.createEventObject();
417 | event.eventType = options.type;
418 | event.button = button;
419 | event.buttons = option.buttons;
420 | return element.fireEvent('on'+options.type, event);
421 | }
422 | }
423 | };
424 |
425 | }
426 |
427 | module.exports = new Mouse();
428 |
--------------------------------------------------------------------------------
/test/pointers.js:
--------------------------------------------------------------------------------
1 | var pointers = effroi.pointers,
2 | assert = chai.assert;
3 |
4 | // Helper
5 | var elt, evts=[], listeners=[];
6 | function regEventListener(elt, type, capture, stop, prevent) {
7 | var listener=function(e) {
8 | if(e.type!=type) {
9 | throw 'Event types differs.'
10 | }
11 | evts.push({
12 | type : e.type,
13 | target : e.target,
14 | currentTarget : e.currentTarget,
15 | clientX : e.clientX,
16 | clientY : e.clientY,
17 | altKey : e.altKey,
18 | ctrlKey : e.ctrlKey,
19 | shiftKey : e.shiftKey,
20 | metaKey : e.metaKey,
21 | button : e.button,
22 | buttons : e.buttons,
23 | charCode : e.charCode,
24 | char : e.char,
25 | pointerType : e.pointerType,
26 | view : e.view,
27 | relatedTarget : e.relatedTarget
28 | });
29 | if(stop) {
30 | e.stopPropagation();
31 | }
32 | if(prevent) {
33 | e.preventDefault();
34 | }
35 | };
36 | elt.addEventListener(type, listener, capture);
37 | listeners.push({
38 | elt: elt,
39 | type: type,
40 | listener : listener,
41 | capture : capture
42 | });
43 | }
44 |
45 | function init(innerHTML) {
46 | elt = document.createElement('div');
47 | elt.innerHTML = innerHTML || 'foo';
48 | document.body.appendChild(elt);
49 | }
50 |
51 | function uninit() {
52 | document.body.removeChild(elt);
53 | for(var i=listeners.length-1; i>=0; i--) {
54 | listeners[i].elt.removeEventListener(
55 | listeners[i].type, listeners[i].listener, listeners[i].capture);
56 | }
57 | evts=[];
58 | elt=null;
59 | }
60 |
61 | // Test tactile only if it's available
62 | if(pointers.isConnected()) {
63 |
64 | describe("Pointing device", function() {
65 |
66 | var prefix = (function () {
67 | var _prefixed = !!window.navigator.msPointerEnabled;
68 | return function (eventType) {
69 | return (_prefixed ? 'MSPointer' + eventType[7].toUpperCase()
70 | + eventType.substring(8) : eventType);
71 | };
72 | })();
73 |
74 | describe("pointing the screen", function() {
75 |
76 | before(function() {
77 | init();
78 | regEventListener(elt, prefix('pointerdown'));
79 | regEventListener(elt, prefix('pointerup'));
80 | regEventListener(elt, 'click');
81 | regEventListener(document.body, prefix('pointerdown'));
82 | regEventListener(document.body, prefix('pointerup'));
83 | regEventListener(document.body, 'click');
84 | });
85 |
86 | after(uninit);
87 |
88 | it("should return true", function() {
89 | assert.equal(pointers.point(elt), true);
90 | });
91 |
92 | it("should trigger a pointerdown event on the element", function() {
93 | assert.equal(evts[0].type, prefix('pointerdown'));
94 | assert.equal(evts[0].target, elt);
95 | assert.equal(evts[0].currentTarget, elt);
96 | });
97 |
98 | it("should bubble the pointerdown event on the parent", function() {
99 | assert.equal(evts[1].type, prefix('pointerdown'));
100 | assert.equal(evts[1].target, elt);
101 | assert.equal(evts[1].currentTarget, document.body);
102 | });
103 |
104 | it("should trigger a pointerup event on the element", function() {
105 | assert.equal(evts[2].type, prefix('pointerup'));
106 | assert.equal(evts[2].target, elt);
107 | assert.equal(evts[2].currentTarget, elt);
108 | });
109 |
110 | it("should bubble the pointerup event on the parent", function() {
111 | assert.equal(evts[3].type, prefix('pointerup'));
112 | assert.equal(evts[3].target, elt);
113 | assert.equal(evts[3].currentTarget, document.body);
114 | });
115 |
116 | it("should trigger a click event on the element", function() {
117 | assert.equal(evts[4].type, 'click');
118 | assert.equal(evts[4].target, elt);
119 | assert.equal(evts[4].currentTarget, elt);
120 | });
121 |
122 | it("should bubble the click event on the parent", function() {
123 | assert.equal(evts[5].type, 'click');
124 | assert.equal(evts[5].target, elt);
125 | assert.equal(evts[5].currentTarget, document.body);
126 | });
127 |
128 | });
129 |
130 | describe("pointing the screen and stop events", function() {
131 |
132 | before(function() {
133 | init();
134 | regEventListener(elt, prefix('pointerdown'), false, true);
135 | regEventListener(elt, prefix('pointerup'), false, true);
136 | regEventListener(elt, 'click', false, true);
137 | regEventListener(document.body, prefix('pointerdown'));
138 | regEventListener(document.body, prefix('pointerup'));
139 | regEventListener(document.body, 'click');
140 | });
141 |
142 | after(uninit);
143 |
144 | it("should return true", function() {
145 | assert.equal(pointers.point(elt), true);
146 | });
147 |
148 | it("should trigger a pointerdown event on the element", function() {
149 | assert.equal(evts[0].type, prefix('pointerdown'));
150 | assert.equal(evts[0].target, elt);
151 | assert.equal(evts[0].currentTarget, elt);
152 | });
153 |
154 | it("should not bubble the pointerdown event on the parent", function() {
155 | assert.notEqual(evts[1].type, prefix('pointerdown'));
156 | });
157 |
158 | it("should trigger a pointerup event on the element", function() {
159 | assert.equal(evts[1].type, prefix('pointerup'));
160 | assert.equal(evts[1].target, elt);
161 | assert.equal(evts[1].currentTarget, elt);
162 | });
163 |
164 | it("should not bubble the pointerup event on the parent", function() {
165 | assert.notEqual(evts[2].type, prefix('pointerup'));
166 | });
167 |
168 | it("should trigger a click event on the element", function() {
169 | assert.equal(evts[2].type, 'click');
170 | assert.equal(evts[2].target, elt);
171 | assert.equal(evts[2].currentTarget, elt);
172 | });
173 |
174 | it("should not bubble the click event on the parent", function() {
175 | assert.equal(evts[3], null);
176 | });
177 |
178 | });
179 |
180 | describe("pointing the screen and cancelling pointerup", function() {
181 |
182 | before(function() {
183 | init();
184 | regEventListener(elt, prefix('pointerdown'));
185 | regEventListener(elt, prefix('pointerup'), false, false, true);
186 | regEventListener(elt, 'click');
187 | regEventListener(document.body, prefix('pointerdown'));
188 | regEventListener(document.body, prefix('pointerup'));
189 | regEventListener(document.body, 'click');
190 | });
191 |
192 | after(uninit);
193 |
194 | it("should return false for IE11, true otherwise", function() {
195 | assert.equal(pointers.point(elt),
196 | window.navigator.msPointerEnabled ? true : false);
197 | });
198 |
199 | it("should trigger a pointerdown event on the element", function() {
200 | assert.equal(evts[0].type, prefix('pointerdown'));
201 | assert.equal(evts[0].target, elt);
202 | assert.equal(evts[0].currentTarget, elt);
203 | });
204 |
205 | it("should bubble the pointerdown event on the parent", function() {
206 | assert.equal(evts[1].type, prefix('pointerdown'));
207 | assert.equal(evts[1].target, elt);
208 | assert.equal(evts[1].currentTarget, document.body);
209 | });
210 |
211 | it("should trigger a pointerup event on the element", function() {
212 | assert.equal(evts[2].type, prefix('pointerup'));
213 | assert.equal(evts[2].target, elt);
214 | assert.equal(evts[2].currentTarget, elt);
215 | });
216 |
217 | it("should bubble the pointerup event on the parent", function() {
218 | assert.equal(evts[3].type, prefix('pointerup'));
219 | assert.equal(evts[3].target, elt);
220 | assert.equal(evts[3].currentTarget, document.body);
221 | });
222 |
223 | it("should not trigger a click event on the element for IE11", function() {
224 | if(window.navigator.pointerEnabled) {
225 | assert.equal(evts[4], null);
226 | }
227 | });
228 |
229 | });
230 |
231 | describe("pointing the screen and cancelling pointerdown", function() {
232 |
233 | before(function() {
234 | init();
235 | regEventListener(elt, prefix('pointerdown'), false, false, true);
236 | regEventListener(elt, prefix('pointerup'));
237 | regEventListener(elt, 'click');
238 | regEventListener(document.body, prefix('pointerdown'));
239 | regEventListener(document.body, prefix('pointerup'));
240 | regEventListener(document.body, 'click');
241 | });
242 |
243 | after(uninit);
244 |
245 | it("should return false", function() {
246 | assert.equal(pointers.point(elt),
247 | window.navigator.msPointerEnabled ? true : false);
248 | });
249 |
250 | it("should trigger a pointerdown event on the element", function() {
251 | assert.equal(evts[0].type, prefix('pointerdown'));
252 | assert.equal(evts[0].target, elt);
253 | assert.equal(evts[0].currentTarget, elt);
254 | });
255 |
256 | it("should bubble the pointerdown event on the parent", function() {
257 | assert.equal(evts[1].type, prefix('pointerdown'));
258 | assert.equal(evts[1].target, elt);
259 | assert.equal(evts[1].currentTarget, document.body);
260 | });
261 |
262 | it("should trigger a pointerup event on the element", function() {
263 | assert.equal(evts[2].type, prefix('pointerup'));
264 | assert.equal(evts[2].target, elt);
265 | assert.equal(evts[2].currentTarget, elt);
266 | });
267 |
268 | it("should bubble the pointerup event on the parent", function() {
269 | assert.equal(evts[3].type, prefix('pointerup'));
270 | assert.equal(evts[3].target, elt);
271 | assert.equal(evts[3].currentTarget, document.body);
272 | });
273 |
274 | it("should not trigger a click event on the element for IE11", function() {
275 | if(window.navigator.pointerEnabled) {
276 | assert.equal(evts[4], null);
277 | }
278 | });
279 |
280 | });
281 |
282 | describe("clicking the screen", function() {
283 |
284 | before(function() {
285 | init();
286 | regEventListener(elt, prefix('pointerdown'));
287 | regEventListener(elt, prefix('pointerup'));
288 | regEventListener(elt, 'click');
289 | });
290 |
291 | after(uninit);
292 |
293 | it("should return true", function() {
294 | assert.equal(pointers.click(elt), true);
295 | });
296 |
297 | if(window.navigator.pointerEnabled) {
298 | it("should set pointerType to 'mouse' on IE11+", function() {
299 | assert.equal(evts[0].pointerType, 'mouse');
300 | assert.equal(evts[1].pointerType, 'mouse');
301 | assert.equal(evts[2].pointerType, 'mouse');
302 | });
303 | } else {
304 | it("should set pointerType to 4 on IE10", function() {
305 | assert.equal(evts[0].pointerType, 4);
306 | assert.equal(evts[1].pointerType, 4);
307 | assert.equal(evts[2].pointerType, undefined); // IE10 click is a MouseEvent
308 | });
309 | }
310 |
311 | it("should set button to 0", function() {
312 | assert.equal(evts[0].button, 0);
313 | assert.equal(evts[1].button, 0);
314 | assert.equal(evts[1].button, 0);
315 | });
316 |
317 | });
318 |
319 | describe("touching the screen", function() {
320 |
321 | before(function() {
322 | init();
323 | regEventListener(elt, prefix('pointerdown'));
324 | regEventListener(elt, prefix('pointerup'));
325 | regEventListener(elt, 'click');
326 | });
327 |
328 | after(uninit);
329 |
330 | it("should return true", function() {
331 | assert.equal(pointers.touch(elt), true);
332 | });
333 |
334 | if(window.navigator.pointerEnabled) {
335 | it("should set pointerType to 'touch' on IE11+", function() {
336 | assert.equal(evts[0].pointerType, 'touch');
337 | assert.equal(evts[1].pointerType, 'touch');
338 | assert.equal(evts[2].pointerType, 'touch');
339 | });
340 | } else {
341 | it("should set pointerType to 2 on IE10", function() {
342 | assert.equal(evts[0].pointerType, 2);
343 | assert.equal(evts[1].pointerType, 2);
344 | assert.equal(evts[2].pointerType, undefined); // IE10 click is a MouseEvent
345 | });
346 | }
347 |
348 | it("should set button to 0", function() {
349 | assert.equal(evts[0].button, 0);
350 | assert.equal(evts[1].button, 0);
351 | assert.equal(evts[1].button, 0);
352 | });
353 |
354 | });
355 |
356 | describe("pointing the screen with a pen", function() {
357 |
358 | before(function() {
359 | init();
360 | regEventListener(elt, prefix('pointerdown'));
361 | regEventListener(elt, prefix('pointerup'));
362 | regEventListener(elt, 'click');
363 | });
364 |
365 | after(uninit);
366 |
367 | it("should return true", function() {
368 | assert.equal(pointers.pen(elt), true);
369 | });
370 |
371 | if(window.navigator.pointerEnabled) {
372 | it("should set pointerType to 'touch' on IE11+", function() {
373 | assert.equal(evts[0].pointerType, 'pen');
374 | assert.equal(evts[1].pointerType, 'pen');
375 | assert.equal(evts[2].pointerType, 'pen');
376 | });
377 | } else {
378 | it("should set pointerType to 2 on IE10", function() {
379 | assert.equal(evts[0].pointerType, 3);
380 | assert.equal(evts[1].pointerType, 3);
381 | assert.equal(evts[2].pointerType, undefined); // IE10 click is a MouseEvent
382 | });
383 | }
384 |
385 | it("should set button to 0", function() {
386 | assert.equal(evts[0].button, 0);
387 | assert.equal(evts[1].button, 0);
388 | assert.equal(evts[1].button, 0);
389 | });
390 |
391 | });
392 |
393 | describe("clicking an undisplayed element", function() {
394 |
395 | before(function() {
396 | init('Text
A link
');
397 | });
398 |
399 | after(uninit);
400 |
401 | it("should throw an exception", function() {
402 | assert.throw(function() {
403 | pointers.dispatch(elt.lastChild.firstChild, {type: 'pointerdown'});
404 | }, Error, 'Unable to find a point in the viewport at wich the'
405 | +' given element can receive a pointer event.');
406 | });
407 |
408 | });
409 |
410 | describe("clicking an hidden element", function() {
411 |
412 | before(function() {
413 | init('Text
A link
');
414 | });
415 |
416 | after(uninit);
417 |
418 | it("should throw an exception", function() {
419 | assert.throw(function() {
420 | pointers.dispatch(elt.lastChild.firstChild, {type: 'pointerdown'});
421 | }, Error, 'Unable to find a point in the viewport at wich the'
422 | +' given element can receive a pointer event.');
423 | });
424 |
425 | });
426 |
427 | describe("clicking a pointer disabled element", function() {
428 |
429 | before(function() {
430 | init('Text
A link
');
431 | });
432 |
433 | after(uninit);
434 |
435 | it("should throw an exception", function() {
436 | assert.throw(function() {
437 | pointers.dispatch(elt.lastChild.firstChild, {type: 'pointerdown'});
438 | }, Error, 'Unable to find a point in the viewport at wich the'
439 | +' given element can receive a pointer event.');
440 | });
441 |
442 | });
443 |
444 | describe("clicking an unclickable element", function() {
445 |
446 | before(function() {
447 | init('Text
A link
');
448 | });
449 |
450 | after(uninit);
451 |
452 | it("should throw an exception", function() {
453 | assert.throw(function() {
454 | pointers.dispatch(elt.lastChild.firstChild, {type: 'pointerdown'});
455 | }, Error, 'Unable to find a point in the viewport at wich the'
456 | +' given element can receive a pointer event.');
457 | });
458 |
459 | });
460 |
461 | });
462 |
463 | }
464 |
--------------------------------------------------------------------------------
/test/tactile.js:
--------------------------------------------------------------------------------
1 | var tactile = effroi.tactile,
2 | assert = chai.assert;
3 |
4 | // Helper
5 | var elt, evts=[], listeners=[];
6 | function regEventListener(elt, type, capture, stop, prevent) {
7 | var listener=function(e) {
8 | if(e.type!=type) {
9 | throw 'Event types differs.'
10 | }
11 | evts.push({
12 | type : e.type,
13 | target : e.target,
14 | currentTarget : e.currentTarget,
15 | clientX : e.clientX,
16 | clientY : e.clientY,
17 | altKey : e.altKey,
18 | ctrlKey : e.ctrlKey,
19 | shiftKey : e.shiftKey,
20 | metaKey : e.metaKey,
21 | button : e.button,
22 | buttons : e.buttons,
23 | charCode : e.charCode,
24 | char : e.char,
25 | pointerType : e.pointerType,
26 | view : e.view,
27 | relatedTarget : e.relatedTarget
28 | });
29 | if(stop) {
30 | e.stopPropagation();
31 | }
32 | if(prevent) {
33 | e.preventDefault();
34 | }
35 | };
36 | elt.addEventListener(type, listener, capture);
37 | listeners.push({
38 | elt: elt,
39 | type: type,
40 | listener : listener,
41 | capture : capture
42 | });
43 | }
44 |
45 | function init(innerHTML) {
46 | elt = document.createElement('div');
47 | elt.innerHTML = innerHTML || 'foo';
48 | document.body.appendChild(elt);
49 | }
50 |
51 | function uninit() {
52 | document.body.removeChild(elt);
53 | for(var i=listeners.length-1; i>=0; i--) {
54 | listeners[i].elt.removeEventListener(
55 | listeners[i].type, listeners[i].listener, listeners[i].capture);
56 | }
57 | evts=[];
58 | elt=null;
59 | }
60 |
61 | // Test tactile only if it's available
62 | if(tactile.isConnected()) {
63 |
64 | describe("A tactile device", function() {
65 |
66 | beforeEach(function() {
67 | // Would like to put init here but seems impossible
68 | });
69 |
70 | afterEach(function() {
71 | // Would like to put uninit here but seems impossible
72 | });
73 |
74 | describe("touching the screen", function() {
75 |
76 | before(function() {
77 | init();
78 | regEventListener(elt, 'touchstart');
79 | regEventListener(elt, 'touchend');
80 | regEventListener(elt, 'click');
81 | regEventListener(document.body, 'touchstart');
82 | regEventListener(document.body, 'touchend');
83 | regEventListener(document.body, 'click');
84 | });
85 |
86 | after(uninit);
87 |
88 | it("should return true", function() {
89 | assert.equal(tactile.touch(elt),true);
90 | });
91 |
92 | if(document.elementFromPoint&&document.body.getBoundingClientRect) {
93 | it("should return right coords", function() {
94 | assert.equal(
95 | document.elementFromPoint(evts[0].clientX, evts[0].clientY),
96 | elt
97 | );
98 | });
99 | }
100 |
101 | it("should trigger a touchstart event on the element", function() {
102 | assert.equal(evts[0].type, 'touchstart');
103 | assert.equal(evts[0].target, elt);
104 | assert.equal(evts[0].currentTarget, elt);
105 | });
106 |
107 | it("should bubble the touchstart event on the parent", function() {
108 | assert.equal(evts[1].type, 'touchstart');
109 | assert.equal(evts[1].target, elt);
110 | assert.equal(evts[1].currentTarget, document.body);
111 | });
112 |
113 | it("should trigger a touchend event on the element", function() {
114 | assert.equal(evts[2].type, 'touchend');
115 | assert.equal(evts[2].target, elt);
116 | assert.equal(evts[2].currentTarget, elt);
117 | });
118 |
119 | it("should bubble the touchend event on the parent", function() {
120 | assert.equal(evts[3].type, 'touchend');
121 | assert.equal(evts[3].target, elt);
122 | assert.equal(evts[3].currentTarget, document.body);
123 | });
124 |
125 | it("should trigger a click event on the element", function() {
126 | assert.equal(evts[4].type, 'click');
127 | assert.equal(evts[4].target, elt);
128 | assert.equal(evts[4].currentTarget, elt);
129 | });
130 |
131 | it("should bubble the click event on the parent", function() {
132 | assert.equal(evts[5].type, 'click');
133 | assert.equal(evts[5].target, elt);
134 | assert.equal(evts[5].currentTarget, document.body);
135 | });
136 |
137 | });
138 |
139 | describe("touching the screen and stop events", function() {
140 |
141 | before(function() {
142 | init();
143 | regEventListener(elt, 'touchstart', false, true);
144 | regEventListener(elt, 'touchend', false, true);
145 | regEventListener(elt, 'click', false, true);
146 | regEventListener(document.body, 'touchstart');
147 | regEventListener(document.body, 'touchend');
148 | regEventListener(document.body, 'click');
149 | });
150 |
151 | after(uninit);
152 |
153 | it("should return true", function() {
154 | assert.equal(tactile.touch(elt),true);
155 | });
156 |
157 | it("should trigger a touchstart event on the element", function() {
158 | assert.equal(evts[0].type, 'touchstart');
159 | assert.equal(evts[0].target, elt);
160 | assert.equal(evts[0].currentTarget, elt);
161 | });
162 |
163 | it("should not bubble the touchstart event on the parent", function() {
164 | assert.notEqual(evts[1].type, 'touchstart');
165 | });
166 |
167 | it("should trigger a touchend event on the element", function() {
168 | assert.equal(evts[1].type, 'touchend');
169 | assert.equal(evts[1].target, elt);
170 | assert.equal(evts[1].currentTarget, elt);
171 | });
172 |
173 | it("should not bubble the touchend event on the parent", function() {
174 | assert.notEqual(evts[2].type, 'touchend');
175 | });
176 |
177 | it("should trigger a click event on the element", function() {
178 | assert.equal(evts[2].type, 'click');
179 | assert.equal(evts[2].target, elt);
180 | assert.equal(evts[2].currentTarget, elt);
181 | });
182 |
183 | it("should not bubble the click event on the parent", function() {
184 | assert.equal(evts[3], null);
185 | });
186 |
187 | });
188 |
189 | describe("touching the screen while the touchend prevented", function() {
190 |
191 | before(function() {
192 | init();
193 | regEventListener(elt, 'touchstart');
194 | regEventListener(elt, 'touchend', false, false, true);
195 | regEventListener(elt, 'click');
196 | regEventListener(document.body, 'touchstart');
197 | regEventListener(document.body, 'touchend');
198 | regEventListener(document.body, 'click');
199 | });
200 |
201 | after(uninit);
202 |
203 | it("should return false", function() {
204 | assert.equal(tactile.touch(elt),false);
205 | });
206 |
207 | it("should trigger a touchstart event on the element", function() {
208 | assert.equal(evts[0].type, 'touchstart');
209 | assert.equal(evts[0].target, elt);
210 | assert.equal(evts[0].currentTarget, elt);
211 | });
212 |
213 | it("should bubble the touchstart event on the parent", function() {
214 | assert.equal(evts[1].type, 'touchstart');
215 | assert.equal(evts[1].target, elt);
216 | assert.equal(evts[1].currentTarget, document.body);
217 | });
218 |
219 | it("should trigger a touchend event on the element", function() {
220 | assert.equal(evts[2].type, 'touchend');
221 | assert.equal(evts[2].target, elt);
222 | assert.equal(evts[2].currentTarget, elt);
223 | });
224 |
225 | it("should bubble the touchend event on the parent", function() {
226 | assert.equal(evts[3].type, 'touchend');
227 | assert.equal(evts[3].target, elt);
228 | assert.equal(evts[3].currentTarget, document.body);
229 | });
230 |
231 | it("should not trigger a click event on the element", function() {
232 | assert.equal(evts[4], null);
233 | });
234 |
235 | });
236 |
237 | describe("touching the screen while the touchstart prevented", function() {
238 |
239 | before(function() {
240 | init();
241 | regEventListener(elt, 'touchstart', false, false, true);
242 | regEventListener(elt, 'touchend');
243 | regEventListener(elt, 'click');
244 | regEventListener(document.body, 'touchstart');
245 | regEventListener(document.body, 'touchend');
246 | regEventListener(document.body, 'click');
247 | });
248 |
249 | after(uninit);
250 |
251 | it("should return false", function() {
252 | assert.equal(tactile.touch(elt),false);
253 | });
254 |
255 | it("should trigger a touchstart event on the element", function() {
256 | assert.equal(evts[0].type, 'touchstart');
257 | assert.equal(evts[0].target, elt);
258 | assert.equal(evts[0].currentTarget, elt);
259 | });
260 |
261 | it("should bubble the touchstart event on the parent", function() {
262 | assert.equal(evts[1].type, 'touchstart');
263 | assert.equal(evts[1].target, elt);
264 | assert.equal(evts[1].currentTarget, document.body);
265 | });
266 |
267 | it("should trigger a touchend event on the element", function() {
268 | assert.equal(evts[2].type, 'touchend');
269 | assert.equal(evts[2].target, elt);
270 | assert.equal(evts[2].currentTarget, elt);
271 | });
272 |
273 | it("should bubble the touchend event on the parent", function() {
274 | assert.equal(evts[3].type, 'touchend');
275 | assert.equal(evts[3].target, elt);
276 | assert.equal(evts[3].currentTarget, document.body);
277 | });
278 |
279 | it("should not trigger a click event on the element", function() {
280 | assert.equal(evts[4], null);
281 | });
282 |
283 | });
284 |
285 |
286 |
287 | describe("touching the screen + pushing the ctrlKey", function() {
288 |
289 | before(function() {
290 | init();
291 | regEventListener(elt, 'touchstart');
292 | regEventListener(elt, 'touchend');
293 | regEventListener(elt, 'click');
294 | regEventListener(document.body, 'touchstart');
295 | regEventListener(document.body, 'touchend');
296 | regEventListener(document.body, 'click');
297 | });
298 |
299 | after(uninit);
300 |
301 | it("should return true", function() {
302 | assert.equal(tactile.touch(elt),true);
303 | });
304 |
305 | it("should trigger a touchstart event on the element", function() {
306 | assert.equal(evts[0].type, 'touchstart');
307 | assert.equal(evts[0].target, elt);
308 | assert.equal(evts[0].currentTarget, elt);
309 | });
310 |
311 | it("should bubble the touchstart event on the parent", function() {
312 | assert.equal(evts[1].type, 'touchstart');
313 | assert.equal(evts[1].target, elt);
314 | assert.equal(evts[1].currentTarget, document.body);
315 | });
316 |
317 | it("should trigger a touchend event on the element", function() {
318 | assert.equal(evts[2].type, 'touchend');
319 | assert.equal(evts[2].target, elt);
320 | assert.equal(evts[2].currentTarget, elt);
321 | });
322 |
323 | it("should bubble the touchend event on the parent", function() {
324 | assert.equal(evts[3].type, 'touchend');
325 | assert.equal(evts[3].target, elt);
326 | assert.equal(evts[3].currentTarget, document.body);
327 | });
328 |
329 | it("should trigger a click event on the element", function() {
330 | assert.equal(evts[4].type, 'click');
331 | assert.equal(evts[4].target, elt);
332 | assert.equal(evts[4].currentTarget, elt);
333 | });
334 |
335 | it("should bubble the click event on the parent", function() {
336 | assert.equal(evts[5].type, 'click');
337 | assert.equal(evts[5].target, elt);
338 | assert.equal(evts[5].currentTarget, document.body);
339 | });
340 |
341 | });
342 |
343 | describe("scrolling to an element", function() {
344 |
345 | before(function() {
346 | init('Block 1
'
347 | +'Block 2
'
348 | +'Block 3
');
349 | regEventListener(elt, 'touchstart');
350 | regEventListener(elt, 'touchmove');
351 | regEventListener(elt, 'touchend');
352 | });
353 |
354 | after(uninit);
355 |
356 | it("should return true if scrolled, false otherwise", function() {
357 | assert.equal(tactile.scrollTo(elt),
358 | (25048>window.innerHeight ? true : false));
359 | });
360 |
361 | it("should trigger a touchstart event", function() {
362 | if((25048>window.innerHeight)) {
363 | assert.equal(evts[0].type, 'touchstart');
364 | } else {
365 | assert.equal(evts[0], null);
366 | }
367 | });
368 |
369 | it("should trigger a touchend event", function() {
370 | if((25048>window.innerHeight)) {
371 | assert.equal(evts[0].type, 'touchstart');
372 | }
373 | });
374 |
375 | });
376 |
377 | describe("touching an undisplayed element", function() {
378 |
379 | before(function() {
380 | init('Text
A link
');
381 | });
382 |
383 | after(uninit);
384 |
385 | it("should throw an exception", function() {
386 | assert.throw(function() {
387 | tactile.dispatch(elt.lastChild.firstChild, {type: 'touchstart'});
388 | }, Error, 'Unable to find a point in the viewport at wich the'
389 | +' given element can receive a touch event.');
390 | });
391 |
392 | });
393 |
394 | describe("touching an hidden element", function() {
395 |
396 | before(function() {
397 | init('Text
A link
');
398 | });
399 |
400 | after(uninit);
401 |
402 | it("should throw an exception", function() {
403 | assert.throw(function() {
404 | tactile.dispatch(elt.lastChild.firstChild, {type: 'touchstart'});
405 | }, Error, 'Unable to find a point in the viewport at wich the'
406 | +' given element can receive a touch event.');
407 | });
408 |
409 | });
410 |
411 | describe("touching a pointer disabled element", function() {
412 |
413 | before(function() {
414 | init('Text
A link
');
415 | });
416 |
417 | after(uninit);
418 |
419 | it("should throw an exception", function() {
420 | assert.throw(function() {
421 | tactile.dispatch(elt.lastChild.firstChild, {type: 'touchstart'});
422 | }, Error, 'Unable to find a point in the viewport at wich the'
423 | +' given element can receive a touch event.');
424 | });
425 |
426 | });
427 |
428 | describe("touching an unclickable element", function() {
429 |
430 | before(function() {
431 | init('Text
A link
');
432 | });
433 |
434 | after(uninit);
435 |
436 | it("should throw an exception", function() {
437 | assert.throw(function() {
438 | tactile.dispatch(elt.lastChild.firstChild, {type: 'touchstart'});
439 | }, Error, 'Unable to find a point in the viewport at wich the'
440 | +' given element can receive a touch event.');
441 | });
442 |
443 | });
444 |
445 | });
446 |
447 | }
448 |
--------------------------------------------------------------------------------
/test/keyboard.js:
--------------------------------------------------------------------------------
1 | var keyboard = effroi.keyboard,
2 | assert = chai.assert;
3 |
4 | // Helper
5 | var elt, evts=[], listeners=[];
6 | function regEventListener(elt, type, capture, stop, prevent) {
7 | var listener=function(e) {
8 | if(e.type!=type) {
9 | throw 'Event types differs.'
10 | }
11 | evts.push({
12 | type : e.type,
13 | target : e.target,
14 | currentTarget : e.currentTarget,
15 | clientX : e.clientX,
16 | clientY : e.clientY,
17 | altKey : e.altKey,
18 | ctrlKey : e.ctrlKey,
19 | shiftKey : e.shiftKey,
20 | metaKey : e.metaKey,
21 | button : e.button,
22 | buttons : e.buttons,
23 | pointerType : e.pointerType,
24 | charCode : e.charCode,
25 | char : e.char,
26 | view : e.view,
27 | relatedTarget : e.relatedTarget
28 | });
29 | if(stop) {
30 | e.stopPropagation();
31 | }
32 | if(prevent) {
33 | e.preventDefault();
34 | }
35 | };
36 | elt.addEventListener(type, listener, capture);
37 | listeners.push({
38 | elt: elt,
39 | type: type,
40 | listener : listener,
41 | capture : capture
42 | });
43 | }
44 |
45 | function init(innerHTML) {
46 | elt = document.createElement('div');
47 | elt.innerHTML = innerHTML || 'foo';
48 | document.body.appendChild(elt);
49 | }
50 |
51 | function uninit() {
52 | document.body.removeChild(elt);
53 | for(var i=listeners.length-1; i>=0; i--) {
54 | listeners[i].elt.removeEventListener(
55 | listeners[i].type, listeners[i].listener, listeners[i].capture);
56 | }
57 | evts=[];
58 | elt=null;
59 | }
60 |
61 | describe("Keyboard device", function() {
62 |
63 | describe("hit a key when on an element", function() {
64 |
65 | before(function() {
66 | init();
67 | regEventListener(document.activeElement, 'keydown');
68 | regEventListener(document.activeElement, 'keypress');
69 | regEventListener(document.activeElement, 'keyup');
70 |
71 | });
72 |
73 | after(uninit);
74 |
75 | it("should return true", function() {
76 | assert.equal(keyboard.hit('a'), true);
77 | });
78 |
79 | it("should set the char and charcode property", function() {
80 | assert.equal(evts[0].char, 'a');
81 | // Seems impossible to change charCode prop with phantom
82 | if(!navigator.userAgent.match(/phantom/i)) {
83 | assert.equal(evts[0].charCode, 'a'.charCodeAt(0));
84 | }
85 | });
86 |
87 | it("should set the view property to window", function() {
88 | assert.equal(evts[0].view, window);
89 | });
90 |
91 | it("should set the special key property to false", function() {
92 | assert.equal(evts[0].ctrlKey, false);
93 | assert.equal(evts[0].altKey, false);
94 | assert.equal(evts[0].shiftKey, false);
95 | assert.equal(evts[0].metaKey, false);
96 | });
97 |
98 | it("should trigger a keydown event on the activeElement", function() {
99 | assert.equal(evts[0].type, 'keydown');
100 | assert.equal(evts[0].target, document.activeElement);
101 | });
102 |
103 | it("should trigger a keypress event on the element", function() {
104 | assert.equal(evts[1].type, 'keypress');
105 | assert.equal(evts[1].target, document.activeElement);
106 | });
107 |
108 | it("should trigger a keyup event on the element", function() {
109 | assert.equal(evts[2].type, 'keyup');
110 | assert.equal(evts[2].target, document.activeElement);
111 | });
112 |
113 | });
114 |
115 | describe("hit a key when on an input element", function() {
116 |
117 | before(function() {
118 | init('');
119 | elt.firstChild.firstChild.lastChild.focus();
120 | regEventListener(elt.firstChild.firstChild.lastChild, 'keydown');
121 | regEventListener(elt.firstChild.firstChild.lastChild, 'keypress');
122 | regEventListener(elt.firstChild.firstChild.lastChild, 'keyup');
123 | regEventListener(document.body, 'keydown');
124 | regEventListener(document.body, 'keypress');
125 | regEventListener(document.body, 'keyup');
126 |
127 | });
128 |
129 | after(uninit);
130 |
131 | it("should return true", function() {
132 | assert.equal(keyboard.hit('a'), true);
133 | });
134 |
135 | it("should set the char property correctly", function() {
136 | assert.equal(evts[0].char, 'a');
137 | });
138 |
139 | it("should set the view property to window", function() {
140 | assert.equal(evts[0].view, window);
141 | });
142 |
143 | it("should change its value", function() {
144 | assert.equal(elt.firstChild.firstChild.lastChild.value, 'boooooooba');
145 | });
146 |
147 | it("should set the special key property to false", function() {
148 | assert.equal(evts[0].ctrlKey, false);
149 | assert.equal(evts[0].altKey, false);
150 | assert.equal(evts[0].shiftKey, false);
151 | assert.equal(evts[0].metaKey, false);
152 | });
153 |
154 | it("should trigger a keydown event on the activeElement", function() {
155 | assert.equal(evts[0].type, 'keydown');
156 | assert.equal(evts[0].target, elt.firstChild.firstChild.lastChild);
157 | assert.equal(evts[0].currentTarget, elt.firstChild.firstChild.lastChild);
158 | });
159 |
160 | it("should bubble the keydown event on the parent", function() {
161 | assert.equal(evts[1].type, 'keydown');
162 | assert.equal(evts[1].target, elt.firstChild.firstChild.lastChild);
163 | assert.equal(evts[1].currentTarget, document.body);
164 | });
165 |
166 | it("should trigger a keypress event on the element", function() {
167 | assert.equal(evts[2].type, 'keypress');
168 | assert.equal(evts[2].target, elt.firstChild.firstChild.lastChild);
169 | assert.equal(evts[2].currentTarget, elt.firstChild.firstChild.lastChild);
170 | });
171 |
172 | it("should bubble the keypress event on the parent", function() {
173 | assert.equal(evts[3].type, 'keypress');
174 | assert.equal(evts[3].target, elt.firstChild.firstChild.lastChild);
175 | assert.equal(evts[3].currentTarget, document.body);
176 | });
177 |
178 | it("should trigger a keyup event on the element", function() {
179 | assert.equal(evts[4].type, 'keyup');
180 | assert.equal(evts[4].target, elt.firstChild.firstChild.lastChild);
181 | assert.equal(evts[4].currentTarget, elt.firstChild.firstChild.lastChild);
182 | });
183 |
184 | it("should bubble the keyup event on the parent", function() {
185 | assert.equal(evts[5].type, 'keyup');
186 | assert.equal(evts[5].target, elt.firstChild.firstChild.lastChild);
187 | assert.equal(evts[5].currentTarget, document.body);
188 | });
189 |
190 | });
191 |
192 | describe("combining ctrl + c", function() {
193 |
194 | before(function() {
195 | init();
196 | regEventListener(document.body, 'keydown');
197 | regEventListener(document.body, 'keypress');
198 | regEventListener(document.body, 'keyup');
199 |
200 | });
201 |
202 | after(uninit);
203 |
204 | it("should return true", function() {
205 | assert.equal(keyboard.combine(keyboard.CTRL, 'c'), true);
206 | });
207 |
208 | it("should set an empty char property for the CTRL keydown event", function() {
209 | assert.equal(evts[0].type, 'keydown');
210 | assert.equal(evts[0].char, '');
211 | });
212 |
213 | it("should set the ctrlKey property for the CTRL keydown event", function() {
214 | (!/phantom/i.test(navigator.userAgent)) &&
215 | assert.equal(evts[0].ctrlKey, true);
216 | });
217 |
218 | it("should set an empty char property for the c keydown event", function() {
219 | assert.equal(evts[1].type, 'keydown');
220 | assert.equal(evts[1].char, 'c');
221 | });
222 |
223 | it("should set the ctrlKey property for the c keydown event", function() {
224 | (!/phantom/i.test(navigator.userAgent)) &&
225 | assert.equal(evts[1].ctrlKey, true);
226 | });
227 |
228 | it("should set an empty char property for the CTRL keyup event", function() {
229 | assert.equal(evts[2].type, 'keyup');
230 | assert.equal(evts[2].char, '');
231 | });
232 |
233 | it("should set the ctrlKey property for the CTRL keyup event", function() {
234 | (!/phantom/i.test(navigator.userAgent)) &&
235 | assert.equal(evts[2].ctrlKey, false);
236 | });
237 |
238 | it("should set the char property to 'c' for the c keyup event", function() {
239 | assert.equal(evts[3].type, 'keyup');
240 | assert.equal(evts[3].char, 'c');
241 | })
242 |
243 | it("should set the ctrlKey property for the c keyup event", function() {
244 | (!/phantom/i.test(navigator.userAgent)) &&
245 | assert.equal(evts[3].ctrlKey, false);
246 | });
247 |
248 | });
249 |
250 | describe("pasting inside an input[type=text] element where some text is selected", function() {
251 |
252 | before(function() {
253 | init('');
254 | elt.firstChild.firstChild.lastChild.focus();
255 | elt.firstChild.firstChild.lastChild.selectionStart=1;
256 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
257 | });
258 |
259 | after(uninit);
260 |
261 | it("should return true", function() {
262 | assert.equal(keyboard.paste('00'), true);
263 | });
264 |
265 | it("should change it's value", function() {
266 | assert.equal(elt.firstChild.firstChild.lastChild.value,'b00b');
267 | });
268 |
269 | });
270 |
271 | describe("pasting inside an input[type=text] element where some text is selected and the keydown event prevented", function() {
272 |
273 | before(function() {
274 | init('');
275 | elt.firstChild.firstChild.lastChild.focus();
276 | elt.firstChild.firstChild.lastChild.selectionStart=1;
277 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
278 | regEventListener(elt.firstChild.firstChild.lastChild,
279 | 'keydown', false, false, true);
280 | });
281 |
282 | after(uninit);
283 |
284 | it("should return false", function() {
285 | assert.equal(
286 | keyboard.paste('00'),
287 | false);
288 | });
289 |
290 | it("should not change it's value", function() {
291 | assert.equal(elt.firstChild.firstChild.lastChild.value,'booooooob');
292 | });
293 |
294 | });
295 |
296 | describe("cutting the selected text inside an input[type=text]", function() {
297 |
298 | before(function() {
299 | init('');
300 | elt.firstChild.firstChild.lastChild.focus();
301 | elt.firstChild.firstChild.lastChild.selectionStart=1;
302 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
303 | });
304 |
305 | after(uninit);
306 |
307 | it("should return the cutted content", function() {
308 | assert.equal(keyboard.cut(),'ooooooo');
309 | });
310 |
311 | it("should change it's value", function() {
312 | assert.equal(elt.firstChild.firstChild.lastChild.value,'bb');
313 | });
314 |
315 | });
316 |
317 | describe("cutting the selected text inside an input[type=text] with a keydown event prevented", function() {
318 |
319 | before(function() {
320 | init('');
321 | elt.firstChild.firstChild.lastChild.focus();
322 | elt.firstChild.firstChild.lastChild.selectionStart=1;
323 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
324 | regEventListener(elt.firstChild.firstChild.lastChild,
325 | 'keydown', false, false, true);
326 | });
327 |
328 | after(uninit);
329 |
330 | it("should return no content", function() {
331 | assert.equal(keyboard.cut(),'');
332 | });
333 |
334 | it("should not change it's value", function() {
335 | assert.equal(elt.firstChild.firstChild.lastChild.value,'booooooob');
336 | });
337 |
338 | });
339 |
340 | describe("focusing elements with the keyboard", function() {
341 |
342 | var previousActiveElement;
343 |
344 | before(function() {
345 | init(
346 | ''
347 | + ''
348 | + ''
349 | + '
'
355 | );
356 | regEventListener(document, 'keydown');
357 | regEventListener(document, 'keyup');
358 | regEventListener(elt.firstChild.firstChild.lastChild, 'blur');
359 | regEventListener(elt.firstChild.firstChild.lastChild, 'focus');
360 | });
361 |
362 | after(uninit);
363 |
364 | it("should return true", function() {
365 | previousActiveElement = document.activeElement;
366 | regEventListener(previousActiveElement, 'blur');
367 | regEventListener(previousActiveElement, 'focus');
368 | assert.equal(keyboard.focus(elt.firstChild.firstChild.lastChild), true);
369 | });
370 |
371 | it("should set the element as the active element", function() {
372 | assert.equal(elt.firstChild.firstChild.lastChild, document.activeElement);
373 | });
374 |
375 | it("should trigger a keydown event on the previousActiveElement", function() {
376 | assert.equal(evts[0].type, 'keydown');
377 | assert.equal(evts[0].target, previousActiveElement);
378 | // Seems impossible to change charCode prop with phantom
379 | if(!navigator.userAgent.match(/phantom/i)) {
380 | assert.equal(evts[0].charCode,
381 | keyboard.KEY_TO_CHARCODE[keyboard.TAB]);
382 | }
383 | });
384 |
385 | it("should trigger a blur event on the previousActiveElement", function() {
386 | assert.equal(evts[1].type, 'blur');
387 | assert.equal(evts[1].target, previousActiveElement);
388 | // Sadly, it seems impossible to set relatedTarget while using
389 | // the focus/blur methods. Maybe do a select() and fire focus evts
390 | // manually could do the job ?
391 | // assert.equal(evts[1].relatedTarget, elt.firstChild.firstChild.lastChild);
392 | });
393 |
394 | it("should trigger a focus event on the newly activeElement", function() {
395 | assert.equal(evts[2].type, 'focus');
396 | assert.equal(evts[2].target, elt.firstChild.firstChild.lastChild);
397 | // Sadly, it seems impossible to set relatedTarget while using
398 | // the focus/blur methods. Maybe do a select() and fire focus evts
399 | // manually could do the job ?
400 | // assert.equal(evts[2].relatedTarget, previousActiveElement);
401 | });
402 |
403 | // Firefox bug : https://bugzilla.mozilla.org/show_bug.cgi?id=932897
404 | if(!navigator.userAgent.match(/firefox/i)) {
405 | it("should trigger a keyup event on the newlyActiveElement", function() {
406 | assert.equal(evts[3].type, 'keyup');
407 | assert.equal(evts[3].target, elt.firstChild.firstChild.lastChild);
408 | // Seems impossible to change charCode prop with phantom
409 | if(!navigator.userAgent.match(/phantom/i)) {
410 | assert.equal(evts[3].charCode,
411 | keyboard.KEY_TO_CHARCODE[keyboard.TAB]);
412 | }
413 | });
414 | }
415 |
416 | });
417 |
418 | describe("focusing an input, editing and blur", function() {
419 |
420 | before(function() {
421 | init(
422 | ''
423 | + 'Text: '
424 | + 'Text: '
425 | + '
'
426 | );
427 | });
428 |
429 | after(uninit);
430 |
431 | it("should trigger a change event on the edited element", function() {
432 | keyboard.focus(elt.firstChild.firstChild.lastChild);
433 | regEventListener(elt.firstChild.firstChild.lastChild, 'change');
434 | regEventListener(document.body, 'change');
435 | keyboard.hit('h');
436 | keyboard.tab();
437 | assert.equal(evts[0].type, 'change');
438 | assert.equal(evts[0].target, elt.firstChild.firstChild.lastChild);
439 | });
440 |
441 | it("should bubble the change event on the edited element parents", function() {
442 | assert.equal(evts[1].type, 'change');
443 | assert.equal(evts[1].target, elt.firstChild.firstChild.lastChild);
444 | });
445 |
446 | });
447 |
448 | });
449 |
--------------------------------------------------------------------------------
/src/devices/keyboard.js:
--------------------------------------------------------------------------------
1 | function Keyboard() {
2 |
3 | var utils = require('../utils.js');
4 | var mouse = require('./mouse.js');
5 | var uiFocus = require('../ui/focus.js');
6 |
7 | // Configuration
8 | this.locale = ''; // ex: en-US
9 |
10 | // Consts
11 | // Maps for key / char properties
12 | // http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list
13 | this.KEY_TO_CHAR = {
14 | 'Cancel': '\u0018',
15 | 'Esc': '\u001B',
16 | 'Spacebar': '\u0020',
17 | 'Add': '\u002B',
18 | 'Subtract': '\u2212',
19 | 'Multiply': '\u002A', // alternative '\u00D7'
20 | 'Divide': '\u00F7',
21 | 'Equals': '\u003D',
22 | 'Decimal': '\u2396', // alternatives '\u002E' or '\u00B7'
23 | 'Tab': '\u0009',
24 | 'Backspace': '\u0008',
25 | 'Del': '\u007F',
26 | 'DeadGrave': '\u0300',
27 | 'DeadAcute': '\u0301',
28 | 'DeadCircumflex': '\u0302',
29 | 'DeadTilde': '\u0303',
30 | 'DeadMacron': '\u0304',
31 | 'DeadBreve': '\u0306',
32 | 'DeadAboveDot': '\u0307',
33 | 'DeadUmlaut': '\u0308',
34 | 'DeadAboveRing': '\u030A',
35 | 'DeadDoubleAcute': '\u030B',
36 | 'DeadCaron': '\u030C',
37 | 'DeadCedilla': '\u0327',
38 | 'DeadOgonek': '\u0328',
39 | 'DeadIota': '\u0345',
40 | 'DeadVoicedSound': '\u3099',
41 | 'DeadSemivoicedSound': '\u309A'
42 | };
43 | this.CHAR_TO_KEY = {
44 | '\u0018': 'Cancel',
45 | '\u001B': 'Esc',
46 | '\u0020': 'Spacebar',
47 | '\u002B': 'Add',
48 | '\u2212': 'Subtract',
49 | '\u002A': 'Multiply',
50 | '\u00D7': 'Multiply',
51 | '\u00F7': 'Divide',
52 | '\u003D': 'Equals',
53 | '\u2396': 'Decimal',
54 | '\u002E': 'Decimal',
55 | '\u00B7': 'Decimal',
56 | '\u0009': 'Tab',
57 | '\u0008': 'Backspace',
58 | '\u007F': 'Del',
59 | '\u0300': 'DeadGrave',
60 | '\u0301': 'DeadAcute',
61 | '\u0302': 'DeadCircumflex',
62 | '\u0303': 'DeadTilde',
63 | '\u0304': 'DeadMacron',
64 | '\u0306': 'DeadBreve',
65 | '\u0307': 'DeadAboveDot',
66 | '\u0308': 'DeadUmlaut',
67 | '\u030A': 'DeadAboveRing',
68 | '\u030B': 'DeadDoubleAcute',
69 | '\u030C': 'DeadCaron',
70 | '\u0327': 'DeadCedilla',
71 | '\u0328': 'DeadOgonek',
72 | '\u0345': 'DeadIota',
73 | '\u3099': 'DeadVoicedSound',
74 | '\u309A': 'DeadSemivoicedSound'
75 | };
76 | this.KEYS = [
77 | 'Attn', 'Apps', 'Crsel', 'Exsel', 'F1', 'F2', 'F3', 'F4',
78 | 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12', 'F13',
79 | 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F20', 'F21',
80 | 'F22', 'F23', 'F24', 'LaunchApplication1', 'LaunchApplication2',
81 | 'LaunchMail', 'List', 'Props', 'Soft1', 'Soft2', 'Soft3', 'Soft4',
82 | 'Accept', 'Again', 'Enter', 'Find', 'Help', 'Info', 'Menu', 'Pause',
83 | 'Play', 'Scroll', 'Execute', 'Cancel', 'Esc', 'Exit', 'Zoom', 'Separator',
84 | 'Spacebar', 'Add', 'NumLock', 'Subtract', 'NumLock', 'Multiply',
85 | 'Divide', 'Equals', 'Decimal', 'BrightnessDown', 'BrightnessUp', 'Camera',
86 | 'Eject', 'Power', 'PrintScreen', 'BrowserFavorites', 'BrowserHome',
87 | 'BrowserRefresh', 'BrowserSearch', 'BrowserStop', 'BrowserBack',
88 | 'BrowserForward', 'Left', 'PageDown', 'PageUp', 'Right', 'Up', 'UpLeft',
89 | 'UpRight', 'Down', 'DownLeft', 'DownRight', 'Home', 'End', 'Select', 'Tab',
90 | 'Backspace', 'Clear', 'Copy', 'Cut', 'Del', 'EraseEof', 'Insert', 'Paste',
91 | 'Undo', 'DeadGrave', 'DeadAcute', 'DeadCircumflex', 'DeadTilde',
92 | 'DeadMacron', 'DeadBreve', 'DeadAboveDot', 'DeadUmlaut', 'DeadAboveRing',
93 | 'DeadDoubleAcute', 'DeadCaron', 'DeadCedilla', 'DeadOgonek', 'DeadIota',
94 | 'DeadVoicedSound', 'DeadSemivoicedSound', 'Alphanumeric', 'Alt', 'AltGraph',
95 | 'CapsLock', 'Control', 'Fn', 'FnLock', 'Meta', 'Process', 'NumLock', 'Shift',
96 | 'SymbolLock', 'OS', 'Compose', 'AllCandidates', 'NextCandidate',
97 | 'PreviousCandidate', 'CodeInput', 'Convert', 'Nonconvert', 'FinalMode',
98 | 'FullWidth', 'HalfWidth', 'ModeChange', 'RomanCharacters', 'HangulMode',
99 | 'HanjaMode', 'JunjaMode', 'Hiragana', 'KanaMode', 'KanjiMode', 'Katakana',
100 | 'AudioFaderFront', 'AudioFaderRear', 'AudioBalanceLeft',
101 | 'AudioBalanceRight', 'AudioBassBoostDown', 'AudioBassBoostUp', 'VolumeMute',
102 | 'VolumeDown', 'VolumeUp', 'MediaPause', 'MediaPlay', 'MediaTrackEnd',
103 | 'MediaNextTrack', 'MediaPlayPause', 'MediaPreviousTrack', 'MediaTrackSkip',
104 | 'MediaTrackStart', 'MediaStop', 'SelectMedia', 'Blue', 'Brown',
105 | 'ChannelDown', 'ChannelUp', 'ClearFavorite0', 'ClearFavorite1',
106 | 'ClearFavorite2', 'ClearFavorite3', 'Dimmer', 'DisplaySwap', 'FastFwd',
107 | 'Green', 'Grey', 'Guide', 'InstantReplay', 'MediaLast', 'Link', 'Live',
108 | 'Lock', 'NextDay', 'NextFavoriteChannel', 'OnDemand', 'PinPDown',
109 | 'PinPMove', 'PinPToggle', 'PinPUp', 'PlaySpeedDown', 'PlaySpeedReset',
110 | 'PlaySpeedUp', 'PrevDay', 'RandomToggle', 'RecallFavorite0',
111 | 'RecallFavorite1', 'RecallFavorite2', 'RecallFavorite3', 'MediaRecord',
112 | 'RecordSpeedNext', 'Red', 'MediaRewind', 'RfBypass', 'ScanChannelsToggle',
113 | 'ScreenModeNext', 'Settings', 'SplitScreenToggle', 'StoreFavorite0',
114 | 'StoreFavorite1', 'StoreFavorite2', 'StoreFavorite3', 'Subtitle',
115 | 'AudioSurroundModeNext', 'Teletext', 'VideoModeNext', 'DisplayWide', 'Wink',
116 | 'Yellow', 'Unidentified'
117 | ];
118 | // Frequently used keys
119 | this.UP = 'Up';
120 | this.DOWN = 'Down';
121 | this.LEFT = 'Left';
122 | this.RIGHT = 'Right';
123 | this.ESC = 'Esc';
124 | this.SPACE = 'Spacebar';
125 | this.BACK_SPACE = 'Backspace';
126 | this.TAB = 'Tab';
127 | this.DELETE = 'Del';
128 | this.ENTER = 'Enter';
129 | this.CTRL = 'Control';
130 | this.CAPS_LOCK = 'CapsLock';
131 | this.FN = 'Fn';
132 | this.FN_LOCK = 'FnLock';
133 | this.META = 'Meta';
134 | this.NUM_LOCK = 'NumLock';
135 | this.SCROLL_LOCK = 'ScrollLock';
136 | this.SHIFT = 'Shift';
137 | this.SYM_LOCK = 'SymbolLock';
138 | this.ALT_GR = 'AltGraph';
139 | this.OS = 'OS';
140 | // Legacy map: http://www.w3.org/TR/DOM-Level-3-Events/#fixed-virtual-key-codes
141 | this.KEY_TO_CHARCODE = {
142 | 'Up': 38,
143 | 'Down': 40,
144 | 'Left': 37,
145 | 'Right': 39,
146 | 'Esc': 27,
147 | 'Spacebar': 32,
148 | 'Backspace': 8,
149 | 'Tab': 9,
150 | 'Del': 46,
151 | 'Enter': 13,
152 | 'Control': 17,
153 | 'Caps': 20,
154 | 'NumLock': 144,
155 | 'Shift': 16,
156 | 'OS': 91,
157 | 'Alt': 18,
158 | 'CapsLock': 20,
159 | 'PageUp': 33,
160 | 'PageDown': 34,
161 | 'End': 35,
162 | 'Home': 36
163 | };
164 | // Modifiers (legacy) http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers
165 | this.MODIFIERS = [this.ALT, this.ALT_GR, this.CAPS_LOCK, this.CTRL, this.FN,
166 | this.META, this.NUM_LOCK, this.SCROLL_LOCK, this.SHIFT, this.SYM_LOCK,
167 | this.OS
168 | ];
169 |
170 | // Private vars
171 | var _downKeys = [], _keyDownDispatched = [], _that=this;
172 |
173 | // Private functions
174 |
175 | // Return the char corresponding to the key if any
176 | function _charIsPrintable(charCode) {
177 | // C0 control characters
178 | if((charCode >=0 && charCode <= 0x1F) || 0x7F === charCode) {
179 | return false;
180 | }
181 | // C1 control characters
182 | if(charCode >= 0x80 && charCode <= 0x9F) {
183 | return false;
184 | }
185 | if(-1 !== _downKeys.indexOf(this.CTRL)) {
186 | return false;
187 | }
188 | return true;
189 | }
190 |
191 | // Try to add the char corresponding to the key to the activeElement
192 | function _inputChar(char) {
193 | if(_charIsPrintable(char.charCodeAt(0))
194 | &&utils.isSelectable(document.activeElement)) {
195 | // add the char
196 | // FIXME: put at caretPosition/replace selected content
197 | document.activeElement.value += char;
198 | // fire an input event
199 | utils.dispatch(document.activeElement, {type: 'input'});
200 | }
201 | }
202 |
203 | // Compute current modifiers
204 | function _getModifiers() {
205 | var modifiers = '';
206 | if(_downKeys.length) {
207 | for(var i=_downKeys.length-1; i>=0; i--) {
208 | if(-1 !== _that.MODIFIERS.indexOf(_downKeys[i])) {
209 | modifiers += (modifiers ? ' ' : '') + _downKeys[i];
210 | }
211 | }
212 | }
213 | return modifiers;
214 | }
215 |
216 | /**
217 | * Focus an element by using tab
218 | *
219 | * @param DOMElement element A DOMElement to tab to
220 | * @return Boolean
221 | */
222 | this.focus = function focus(element) {
223 | var activeElement = document.activeElement;
224 | // If the element is already focused return false
225 | if(activeElement === element) {
226 | return false;
227 | }
228 | // Performing a first tab
229 | this.tab();
230 | activeElement = document.activeElement;
231 | while(element != document.activeElement && this.tab()
232 | && activeElement != document.activeElement) {
233 | continue;
234 | }
235 | if(activeElement !== element) {
236 | return false;
237 | }
238 | return true;
239 | };
240 |
241 | /**
242 | * Tab to the next element
243 | *
244 | * @return Boolean
245 | */
246 | this.tab = function tab() {
247 | var elements = utils.getFocusableElements(), dispatched;
248 | // if nothing/nothing else to focus, fail
249 | if(1 >= elements.length) {
250 | return false;
251 | }
252 | // Looking for the activeElement index
253 | for(var i=elements.length-1; i>=0; i--) {
254 | if(elements[i] === document.activeElement) {
255 | break;
256 | }
257 | }
258 | // Push the tab key down
259 | this.down(this.TAB);
260 | // Focus the next element
261 | dispatched = uiFocus.focus(-1 === i || i+1 >= elements.length ?
262 | elements[0] : elements[i+1]);
263 | // Release the tab key
264 | this.up(this.TAB);
265 | return dispatched;
266 | };
267 |
268 | /**
269 | * Cut selected content like if it were done by a user with Ctrl + X.
270 | *
271 | * @param DOMElement element A DOMElement to dblclick
272 | * @param String content The content to paste
273 | * @return Boolean
274 | */
275 | this.cut = function cut() {
276 | var content, element = document.activeElement;
277 | // if the keydown event is prevented we can't cut content
278 | if(!this.down(this.CTRL, 'x')) {
279 | this.up(this.CTRL, 'x');
280 | return '';
281 | }
282 | // if content is selectable, we cut only the selected content
283 | if(utils.isSelectable(element)) {
284 | content = element.value.substr(element.selectionStart, element.selectionEnd-1);
285 | element.value =
286 | (element.selectionStart ?
287 | element.value.substr(0, element.selectionStart) : '')
288 | + (element.selectionEnd ?
289 | element.value.substr(element.selectionEnd) :
290 | '');
291 | // otherwise we cut the full content
292 | } else {
293 | content = element.value;
294 | element.value = null;
295 | }
296 | // finally firing keyup events
297 | this.up(this.CTRL, 'x');
298 | return content;
299 | };
300 |
301 | /**
302 | * Paste content like if it were done by a user with Ctrl + V.
303 | *
304 | * @param DOMElement element A DOMElement to dblclick
305 | * @param String content The content to paste
306 | * @return Boolean
307 | */
308 | this.paste = function paste(content) {
309 | var element = document.activeElement;
310 | // The content of a paste is always a string
311 | if('string' !== typeof content) {
312 | throw Error('Can only paste strings (received '+(typeof content)+').');
313 | }
314 | if(!utils.canAcceptContent(element, content)) {
315 | throw Error('Unable to paste content in the given element.');
316 | }
317 | // if the keydown event is prevented we can't paste content
318 | if(!this.down(this.CTRL, 'v')) {
319 | this.up(this.CTRL, 'v');
320 | return false;
321 | }
322 | // if content is selectable, we paste content in the place of the selected content
323 | if(utils.isSelectable(element)) {
324 | element.value =
325 | (element.selectionStart ?
326 | element.value.substr(0, element.selectionStart) : '')
327 | + content
328 | + (element.selectionEnd ?
329 | element.value.substr(element.selectionEnd) :
330 | '');
331 | // otherwise we just replace the value
332 | } else {
333 | element.value = content;
334 | }
335 | // finally firing the keyup events
336 | return this.up(this.CTRL, 'v');
337 | };
338 |
339 | /**
340 | * Perform a key combination.
341 | *
342 | * @param ArgumentList arguments Keycodes of the keys
343 | * @return Boolean
344 | */
345 | this.combine = function () {
346 | var dispatched;
347 | if(0 === arguments.length) {
348 | throw Error('The combine method wait at least one key.');
349 | }
350 | // Pushing the keys sequentially
351 | dispatched = this.down.apply(this, arguments);
352 | // Releasing the keys sequentially
353 | return this.up.apply(this, arguments) && dispatched;
354 | };
355 |
356 | /**
357 | * Hit somes keys sequentially.
358 | *
359 | * @param ArgumentList arguments Keycodes of the keys
360 | * @return Boolean
361 | */
362 | this.hit = function () {
363 | var dispatched = true;
364 | if(0 === arguments.length) {
365 | throw Error('The hit method wait at least one key.');
366 | }
367 | // Hitting the keys sequentially
368 | for(var i=0, j=arguments.length; i=0; i--) {
54 | listeners[i].elt.removeEventListener(
55 | listeners[i].type, listeners[i].listener, listeners[i].capture);
56 | }
57 | evts=[];
58 | elt=null;
59 | }
60 |
61 | describe("Mouse device", function() {
62 |
63 | describe("clicking an element", function() {
64 |
65 | before(function() {
66 | init();
67 | regEventListener(elt, 'mousedown');
68 | regEventListener(elt, 'mouseup');
69 | regEventListener(elt, 'click');
70 | regEventListener(document.body, 'mousedown');
71 | regEventListener(document.body, 'mouseup');
72 | regEventListener(document.body, 'click');
73 |
74 | });
75 |
76 | after(uninit);
77 |
78 | it("should return true", function() {
79 | assert.equal(mouse.click(elt), true);
80 | });
81 |
82 | it("should set the button property to 0", function() {
83 | assert.equal(evts[0].button, 0);
84 | });
85 |
86 | it("should set the view property to window", function() {
87 | assert.equal(evts[0].view, window);
88 | });
89 |
90 | it("should set the relatedTarget property to null", function() {
91 | assert.equal(evts[0].relatedTarget, null);
92 | });
93 |
94 | if(document.elementFromPoint&&document.body.getBoundingClientRect) {
95 | it("should return right coords", function() {
96 | assert.equal(
97 | document.elementFromPoint(evts[0].clientX, evts[0].clientY),
98 | elt
99 | );
100 | });
101 | }
102 |
103 | it("should set the buttons property to LEFT_BUTTON", function() {
104 | assert.equal(evts[0].buttons, mouse.LEFT_BUTTON);
105 | });
106 |
107 | it("should trigger a mousedown event on the element", function() {
108 | assert.equal(evts[0].type, 'mousedown');
109 | assert.equal(evts[0].target, elt);
110 | assert.equal(evts[0].currentTarget, elt);
111 | });
112 |
113 | it("should bubble the mousedown event on the parent", function() {
114 | assert.equal(evts[1].type, 'mousedown');
115 | assert.equal(evts[1].target, elt);
116 | assert.equal(evts[1].currentTarget, document.body);
117 | });
118 |
119 | it("should trigger a mouseup event on the element", function() {
120 | assert.equal(evts[2].type, 'mouseup');
121 | assert.equal(evts[2].target, elt);
122 | assert.equal(evts[2].currentTarget, elt);
123 | });
124 |
125 | it("should bubble the mouseup event on the parent", function() {
126 | assert.equal(evts[3].type, 'mouseup');
127 | assert.equal(evts[3].target, elt);
128 | assert.equal(evts[3].currentTarget, document.body);
129 | });
130 |
131 | it("should trigger a click event on the element", function() {
132 | assert.equal(evts[4].type, 'click');
133 | assert.equal(evts[4].target, elt);
134 | assert.equal(evts[4].currentTarget, elt);
135 | });
136 |
137 | it("should bubble the click event on the parent", function() {
138 | assert.equal(evts[5].type, 'click');
139 | assert.equal(evts[5].target, elt);
140 | assert.equal(evts[5].currentTarget, document.body);
141 | });
142 |
143 | });
144 |
145 | describe("clicking an element and stop events", function() {
146 |
147 | before(function() {
148 | init();
149 | regEventListener(elt, 'mousedown', false, true);
150 | regEventListener(elt, 'mouseup', false, true);
151 | regEventListener(elt, 'click', false, true);
152 | regEventListener(document.body, 'mousedown');
153 | regEventListener(document.body, 'mouseup');
154 | regEventListener(document.body, 'click');
155 | });
156 |
157 | after(uninit);
158 |
159 | it("should return true", function() {
160 | assert.equal(mouse.click(elt), true);
161 | });
162 |
163 | it("should trigger a mousedown event on the element", function() {
164 | assert.equal(evts[0].type, 'mousedown');
165 | assert.equal(evts[0].target, elt);
166 | assert.equal(evts[0].currentTarget, elt);
167 | });
168 |
169 | it("should not bubble the mousedown event on the parent", function() {
170 | assert.notEqual(evts[1].type, 'mousedown');
171 | });
172 |
173 | it("should trigger a mouseup event on the element", function() {
174 | assert.equal(evts[1].type, 'mouseup');
175 | assert.equal(evts[1].target, elt);
176 | assert.equal(evts[1].currentTarget, elt);
177 | });
178 |
179 | it("should not bubble the mouseup event on the parent", function() {
180 | assert.notEqual(evts[2].type, 'mouseup');
181 | });
182 |
183 | it("should trigger a click event on the element", function() {
184 | assert.equal(evts[2].type,'click');
185 | assert.equal(evts[2].target,elt);
186 | assert.equal(evts[2].currentTarget,elt);
187 | });
188 |
189 | it("should not bubble the click event on the parent", function() {
190 | assert.equal(evts[3], null);
191 | });
192 |
193 | });
194 |
195 | describe("clicking an element and stop mouseup", function() {
196 |
197 | before(function() {
198 | init();
199 | regEventListener(elt, 'mousedown');
200 | regEventListener(elt, 'mouseup', false, false, true);
201 | regEventListener(elt, 'click');
202 | regEventListener(document.body, 'mousedown');
203 | regEventListener(document.body, 'mouseup');
204 | regEventListener(document.body, 'click');
205 |
206 | });
207 |
208 | after(uninit);
209 |
210 | it("should return false", function() {
211 | assert.equal(mouse.click(elt), false);
212 | });
213 |
214 | it("should trigger a mousedown event on the element", function() {
215 | assert.equal(evts[0].type, 'mousedown');
216 | assert.equal(evts[0].target, elt);
217 | assert.equal(evts[0].currentTarget, elt);
218 | });
219 |
220 | it("should bubble the mousedown event on the parent", function() {
221 | assert.equal(evts[1].type, 'mousedown');
222 | assert.equal(evts[1].target, elt);
223 | assert.equal(evts[1].currentTarget, document.body);
224 | });
225 |
226 | it("should trigger a mouseup event on the element", function() {
227 | assert.equal(evts[2].type, 'mouseup');
228 | assert.equal(evts[2].target, elt);
229 | assert.equal(evts[2].currentTarget, elt);
230 | });
231 |
232 | it("should bubble the mouseup event on the parent", function() {
233 | assert.equal(evts[3].type, 'mouseup');
234 | assert.equal(evts[3].target, elt);
235 | assert.equal(evts[3].currentTarget, document.body);
236 | });
237 |
238 | it("should not trigger a click event", function() {
239 | assert.equal(evts[4], null);
240 | });
241 |
242 | });
243 |
244 | describe("clicking an element and stop mousedown", function() {
245 |
246 | before(function() {
247 | init();
248 | regEventListener(elt, 'mousedown', false, false, true);
249 | regEventListener(elt, 'mouseup');
250 | regEventListener(elt, 'click');
251 | regEventListener(document.body, 'mousedown');
252 | regEventListener(document.body, 'mouseup');
253 | regEventListener(document.body, 'click');
254 |
255 | });
256 |
257 | after(uninit);
258 |
259 | it("should return false", function() {
260 | assert.equal(mouse.click(elt), false);
261 | });
262 |
263 | it("should trigger a mousedown event on the element", function() {
264 | assert.equal(evts[0].type, 'mousedown');
265 | assert.equal(evts[0].target, elt);
266 | assert.equal(evts[0].currentTarget, elt);
267 | });
268 |
269 | it("should bubble the mousedown event on the parent", function() {
270 | assert.equal(evts[1].type, 'mousedown');
271 | assert.equal(evts[1].target, elt);
272 | assert.equal(evts[1].currentTarget, document.body);
273 | });
274 |
275 | it("should trigger a mouseup event on the element", function() {
276 | assert.equal(evts[2].type, 'mouseup');
277 | assert.equal(evts[2].target, elt);
278 | assert.equal(evts[2].currentTarget, elt);
279 | });
280 |
281 | it("should bubble the mouseup event on the parent", function() {
282 | assert.equal(evts[3].type, 'mouseup');
283 | assert.equal(evts[3].target, elt);
284 | assert.equal(evts[3].currentTarget, document.body);
285 | });
286 |
287 | it("should not trigger a click event", function() {
288 | assert.equal(evts[4], null);
289 | });
290 |
291 | });
292 |
293 | describe("clicking an element with altKey pushed", function() {
294 |
295 | before(function() {
296 | init();
297 | regEventListener(elt, 'click');
298 |
299 | });
300 |
301 | after(uninit);
302 |
303 | it("should return true", function() {
304 | assert.equal(mouse.click(elt, {
305 | altKey : true
306 | }), true);
307 | });
308 |
309 | it("should set the altKey property to true", function() {
310 | assert.equal(evts[0].altKey, true);
311 | });
312 |
313 | });
314 |
315 | describe("clicking an element with ctrlKey pushed", function() {
316 |
317 | before(function() {
318 | init();
319 | regEventListener(elt, 'click');
320 |
321 | });
322 |
323 | after(uninit);
324 |
325 | it("should return true", function() {
326 | assert.equal(mouse.click(elt, {
327 | ctrlKey : true
328 | }), true);
329 | });
330 |
331 | it("should set the ctrlKey property to true", function() {
332 | assert.equal(evts[0].ctrlKey, true);
333 | });
334 |
335 | });
336 |
337 | describe("clicking an element with shiftKey pushed", function() {
338 |
339 | before(function() {
340 | init();
341 | regEventListener(elt, 'click');
342 | });
343 |
344 | after(uninit);
345 |
346 | it("should return true", function() {
347 | assert.equal(mouse.click(elt, {
348 | shiftKey : true
349 | }), true);
350 | });
351 |
352 | it("should set the shiftKey property to true", function() {
353 | assert.equal(evts[0].shiftKey, true);
354 | });
355 |
356 | });
357 |
358 | describe("clicking an element with metaKey pushed", function() {
359 |
360 | before(function() {
361 | init();
362 | regEventListener(elt, 'click');
363 | });
364 |
365 | after(uninit);
366 |
367 | it("should return true", function() {
368 | assert.equal(mouse.click(elt, {
369 | metaKey : true
370 | }), true);
371 | });
372 |
373 | it("should set the ctrlKey property to true", function() {
374 | assert.equal(evts[0].metaKey, true);
375 | });
376 |
377 | });
378 |
379 | describe("clicking an element with ctrlKey pushed", function() {
380 |
381 | before(function() {
382 | init();
383 | regEventListener(elt, 'click');
384 | });
385 |
386 | after(uninit);
387 |
388 | it("should return true", function() {
389 | assert.equal(mouse.click(elt, {
390 | ctrlKey : true
391 | }), true);
392 | });
393 |
394 | it("should set the ctrlKey property to true", function() {
395 | assert.equal(evts[0].ctrlKey, true);
396 | });
397 |
398 | });
399 |
400 | describe("clicking an element with the middle button", function() {
401 |
402 | before(function() {
403 | init();
404 | regEventListener(elt, 'click');
405 | });
406 |
407 | after(uninit);
408 |
409 | it("should return true", function() {
410 | assert.equal(mouse.click(elt, {
411 | buttons : mouse.MIDDLE_BUTTON
412 | }), true);
413 | });
414 |
415 | it("should set the button property to 1", function() {
416 | assert.equal(evts[0].button, 1);
417 | });
418 |
419 | it("should set the buttons property to MIDDLE_BUTTON", function() {
420 | assert.equal(evts[0].buttons, mouse.MIDDLE_BUTTON);
421 | });
422 |
423 | });
424 |
425 | describe("clicking an element with the right button", function() {
426 |
427 | before(function() {
428 | init();
429 | regEventListener(elt, 'click');
430 | });
431 |
432 | after(uninit);
433 |
434 | it("should return true", function() {
435 | assert.equal(mouse.click(elt, {
436 | buttons : mouse.RIGHT_BUTTON
437 | }), true);
438 | });
439 |
440 | it("should set the button property to 2", function() {
441 | assert.equal(evts[0].button, 2);
442 | });
443 |
444 | it("should set the buttons property to RIGHT_BUTTON", function() {
445 | assert.equal(evts[0].buttons, mouse.RIGHT_BUTTON);
446 | });
447 |
448 | });
449 |
450 | describe("double clicking an element with the right button", function() {
451 |
452 | before(function() {
453 | init();
454 | regEventListener(elt, 'click');
455 | regEventListener(elt, 'mousedown');
456 | regEventListener(elt, 'mouseup');
457 | regEventListener(elt, 'dblclick');
458 | regEventListener(document.body, 'dblclick');
459 | });
460 |
461 | after(uninit);
462 |
463 | it("should return true", function() {
464 | assert.equal(mouse.dblclick(elt, {
465 | buttons : mouse.RIGHT_BUTTON
466 | }), true);
467 | });
468 |
469 | it("should set the button property to 2", function() {
470 | assert.equal(evts[0].button, 2);
471 | });
472 |
473 | it("should set the buttons property to RIGHT_BUTTON", function() {
474 | assert.equal(evts[0].buttons, mouse.RIGHT_BUTTON);
475 | });
476 |
477 | it("should trigger a mousedown event on the element", function() {
478 | assert.equal(evts[0].type, 'mousedown');
479 | assert.equal(evts[0].target, elt);
480 | assert.equal(evts[0].currentTarget, elt);
481 | });
482 |
483 | it("should trigger a mouseup event on the element", function() {
484 | assert.equal(evts[1].type, 'mouseup');
485 | assert.equal(evts[1].target, elt);
486 | assert.equal(evts[1].currentTarget, elt);
487 | });
488 |
489 | it("should trigger a click event on the element", function() {
490 | assert.equal(evts[2].type, 'click');
491 | assert.equal(evts[2].target, elt);
492 | assert.equal(evts[2].currentTarget, elt);
493 | });
494 |
495 | it("should trigger a mousedown event on the element", function() {
496 | assert.equal(evts[3].type, 'mousedown');
497 | assert.equal(evts[3].target, elt);
498 | assert.equal(evts[3].currentTarget, elt);
499 | });
500 |
501 | it("should trigger a mouseup event on the element", function() {
502 | assert.equal(evts[4].type, 'mouseup');
503 | assert.equal(evts[4].target, elt);
504 | assert.equal(evts[4].currentTarget, elt);
505 | });
506 |
507 | it("should trigger a click event on the element", function() {
508 | assert.equal(evts[5].type, 'click');
509 | assert.equal(evts[5].target, elt);
510 | assert.equal(evts[5].currentTarget, elt);
511 | });
512 |
513 | it("should trigger a dblclick event on the element", function() {
514 | assert.equal(evts[6].type, 'dblclick');
515 | assert.equal(evts[6].target, elt);
516 | assert.equal(evts[6].currentTarget, elt);
517 | });
518 |
519 | it("should bubble the dblclick event on the parent", function() {
520 | assert.equal(evts[7].type, 'dblclick');
521 | assert.equal(evts[7].target, elt);
522 | assert.equal(evts[7].currentTarget, document.body);
523 | });
524 |
525 | });
526 |
527 | describe("moving to an element", function() {
528 |
529 | before(function() {
530 | init('Line 1
Line 2
'
531 | +'Line 3
Line 4
');
532 | regEventListener(elt.firstChild.firstChild, 'mouseout');
533 | regEventListener(elt.lastChild.firstChild, 'mouseover');
534 | regEventListener(elt.lastChild.firstChild, 'mouseout');
535 | });
536 |
537 | after(uninit);
538 |
539 | it("should return true", function() {
540 | assert.equal(mouse.moveTo(elt.firstChild.firstChild), true);
541 | assert.equal(mouse.moveTo(elt.lastChild.firstChild), true);
542 | });
543 |
544 | it("should trigger a mouseout event on old under cursor element", function() {
545 | assert.equal(evts[0].type, 'mouseout');
546 | assert.equal(evts[0].target, elt.firstChild.firstChild);
547 | assert.equal(evts[0].currentTarget, elt.firstChild.firstChild);
548 | });
549 |
550 | it("should set the newly under cursor element", function() {
551 | assert.equal(evts[0].relatedTarget, elt.lastChild.firstChild);
552 | });
553 |
554 | it("should trigger a mouseover event on newly under cursor element", function() {
555 | assert.equal(evts[1].type, 'mouseover');
556 | assert.equal(evts[1].target, elt.lastChild.firstChild);
557 | assert.equal(evts[1].currentTarget, elt.lastChild.firstChild);
558 | });
559 |
560 | it("should set the old under cursor element", function() {
561 | assert.equal(evts[1].relatedTarget, elt.firstChild.firstChild);
562 | });
563 |
564 | });
565 |
566 | describe("scrolling to an element", function() {
567 |
568 | var type=('onwheel' in document ? 'wheel' :
569 | ('onmousewheel' in document ? 'mousewheel' : '')
570 | );
571 |
572 | if(!type) {
573 | return;
574 | }
575 |
576 | before(function() {
577 | init('Block 1
'
578 | +'Block 2
'
579 | +'Block 3
');
580 | regEventListener(elt.firstChild, type);
581 | regEventListener(elt.lastChild, type);
582 | });
583 |
584 | after(uninit);
585 |
586 | it("should return true if scrolled, false otherwise", function() {
587 | assert.equal(mouse.scrollTo(elt.lastChild),
588 | (25048>window.innerHeight ? true : false));
589 | });
590 |
591 | it("should trigger a wheel event on the first element", function() {
592 | if(25048>window.innerHeight) {
593 | assert.equal(evts[0].type, type);
594 | assert.equal(evts[0].target, elt.firstChild);
595 | assert.equal(evts[0].currentTarget, elt.firstChild);
596 | }
597 | });
598 |
599 | });
600 |
601 | describe("focusing an element", function() {
602 |
603 | before(function() {
604 | init('Text
A link
');
605 | regEventListener(elt.lastChild.firstChild, 'mouseover');
606 | regEventListener(elt.lastChild.firstChild, 'mouseout');
607 | regEventListener(elt.lastChild.firstChild, 'focus');
608 | });
609 |
610 | after(uninit);
611 |
612 | it("should return true", function() {
613 | assert.equal(mouse.focus(elt.lastChild.firstChild), true);
614 | });
615 |
616 | it("should set the link as the active element", function() {
617 | assert.equal(elt.lastChild.firstChild, document.activeElement);
618 | });
619 |
620 | it("should trigger a mouseover event on the focused element", function() {
621 | assert.equal(evts[0].type, 'mouseover');
622 | assert.equal(evts[0].target, elt.lastChild.firstChild);
623 | assert.equal(evts[0].currentTarget, elt.lastChild.firstChild);
624 | });
625 |
626 | it("should trigger a focus event on the focused element", function() {
627 | assert.equal(evts[1].type, 'focus');
628 | assert.equal(evts[1].target, elt.lastChild.firstChild);
629 | assert.equal(evts[1].currentTarget, elt.lastChild.firstChild);
630 | });
631 |
632 | it("should trigger a mouseout event on the focused element", function() {
633 | assert.equal(evts[2].type, 'mouseout');
634 | assert.equal(evts[2].target, elt.lastChild.firstChild);
635 | assert.equal(evts[2].currentTarget, elt.lastChild.firstChild);
636 | });
637 |
638 | });
639 |
640 | describe("clicking an undisplayed element", function() {
641 |
642 | before(function() {
643 | init('Text
A link
');
644 | });
645 |
646 | after(uninit);
647 |
648 | it("should throw an exception", function() {
649 | assert.throw(function() {
650 | mouse.click(elt.lastChild.firstChild)
651 | }, Error, 'Unable to find a point in the viewport at wich the'
652 | +' given element can receive a mouse event.');
653 | });
654 |
655 | });
656 |
657 | describe("clicking an hidden element", function() {
658 |
659 | before(function() {
660 | init('Text
A link
');
661 | });
662 |
663 | after(uninit);
664 |
665 | it("should throw an exception", function() {
666 | assert.throw(function() {
667 | mouse.click(elt.lastChild.firstChild)
668 | }, Error, 'Unable to find a point in the viewport at wich the'
669 | +' given element can receive a mouse event.');
670 | });
671 |
672 | });
673 |
674 | describe("clicking a pointer disabled element", function() {
675 |
676 | before(function() {
677 | init('Text
A link
');
678 | });
679 |
680 | after(uninit);
681 |
682 | it("should throw an exception", function() {
683 | assert.throw(function() {
684 | mouse.click(elt.lastChild.firstChild)
685 | }, Error, 'Unable to find a point in the viewport at wich the'
686 | +' given element can receive a mouse event.');
687 | });
688 |
689 | });
690 |
691 | describe("clicking an unclickable element", function() {
692 |
693 | before(function() {
694 | init('Text
A link
');
695 | });
696 |
697 | after(uninit);
698 |
699 | it("should throw an exception", function() {
700 | assert.throw(function() {
701 | mouse.click(elt.lastChild.firstChild)
702 | }, Error, 'Unable to find a point in the viewport at wich the'
703 | +' given element can receive a mouse event.');
704 | });
705 |
706 | });
707 |
708 | describe("pasting with the mouse inside an span element", function() {
709 |
710 | before(function() {
711 | init('Test
');
712 | regEventListener(elt.firstChild.firstChild, 'mousedown');
713 | regEventListener(elt.firstChild.firstChild, 'focus');
714 | regEventListener(elt.firstChild.firstChild, 'input');
715 | });
716 |
717 | after(uninit);
718 |
719 | it("should throw an exception", function() {
720 | assert.throw(function() {
721 | mouse.paste(elt.firstChild.firstChild, 'test')
722 | }, Error, 'Unable to paste content in the given element.');
723 | });
724 |
725 | });
726 |
727 | describe("pasting with the mouse inside an input[type=text] element", function() {
728 |
729 | before(function() {
730 | init('Text:
');
731 | regEventListener(elt.firstChild.firstChild.lastChild, 'mousedown');
732 | regEventListener(elt.firstChild.firstChild.lastChild, 'mouseup');
733 | regEventListener(elt.firstChild.firstChild.lastChild, 'click');
734 | regEventListener(elt.firstChild.firstChild.lastChild, 'focus');
735 | regEventListener(elt.firstChild.firstChild.lastChild, 'input');
736 | });
737 |
738 | after(uninit);
739 |
740 | it("should return true", function() {
741 | assert.equal(
742 | mouse.paste(elt.firstChild.firstChild.lastChild,'Kikoolol!'),
743 | true);
744 | });
745 |
746 | it("should change it's value", function() {
747 | assert.equal(elt.firstChild.firstChild.lastChild.value,'Kikoolol!');
748 | });
749 |
750 | });
751 |
752 | describe("pasting with the mouse inside an input[type=text] element where some text is selected", function() {
753 |
754 | before(function() {
755 | init('Text:
');
756 | elt.firstChild.firstChild.lastChild.selectionStart=1;
757 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
758 | });
759 |
760 | after(uninit);
761 |
762 | it("should return true", function() {
763 | assert.equal(
764 | mouse.paste(elt.firstChild.firstChild.lastChild,'00'),
765 | true);
766 | });
767 |
768 | it("should change it's value", function() {
769 | assert.equal(elt.firstChild.firstChild.lastChild.value,'b00b');
770 | });
771 |
772 | });
773 |
774 | describe("pasting with the mouse inside an input[type=text] element where some text is selected and the mousedown event prevented", function() {
775 |
776 | before(function() {
777 | init('Text:
');
778 | elt.firstChild.firstChild.lastChild.selectionStart=1;
779 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
780 | regEventListener(elt.firstChild.firstChild.lastChild,
781 | 'mousedown', false, false, true);
782 | });
783 |
784 | after(uninit);
785 |
786 | it("should return false", function() {
787 | assert.equal(
788 | mouse.paste(elt.firstChild.firstChild.lastChild,'00'),
789 | false);
790 | });
791 |
792 | it("should not change it's value", function() {
793 | assert.equal(elt.firstChild.firstChild.lastChild.value,'booooooob');
794 | });
795 |
796 | });
797 |
798 | describe("cutting with the mouse the selected text inside an input[type=text]", function() {
799 |
800 | before(function() {
801 | init('Text:
');
802 | elt.firstChild.firstChild.lastChild.selectionStart=1;
803 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
804 | });
805 |
806 | after(uninit);
807 |
808 | it("should return the cutted content", function() {
809 | assert.equal(mouse.cut(elt.firstChild.firstChild.lastChild),'ooooooo');
810 | });
811 |
812 | it("should change it's value", function() {
813 | assert.equal(elt.firstChild.firstChild.lastChild.value,'bb');
814 | });
815 |
816 | });
817 |
818 | describe("cutting with the mouse the selected text inside an input[type=text] with a mousedown event prevented", function() {
819 |
820 | before(function() {
821 | init('Text:
');
822 | elt.firstChild.firstChild.lastChild.selectionStart=1;
823 | elt.firstChild.firstChild.lastChild.selectionEnd=8;
824 | regEventListener(elt.firstChild.firstChild.lastChild,
825 | 'mousedown', false, false, true);
826 | });
827 |
828 | after(uninit);
829 |
830 | it("should return no content", function() {
831 | assert.equal(mouse.cut(elt.firstChild.firstChild.lastChild),'');
832 | });
833 |
834 | it("should not change it's value", function() {
835 | assert.equal(elt.firstChild.firstChild.lastChild.value,'booooooob');
836 | });
837 |
838 | });
839 |
840 | describe("selecting an input[type=text] content with the mouse", function() {
841 |
842 | before(function() {
843 | init('Text:
');
844 | regEventListener(elt.firstChild.firstChild.lastChild, 'mousedown');
845 | });
846 |
847 | after(uninit);
848 |
849 | it("should return true", function() {
850 | assert.equal(mouse.select(elt.firstChild.firstChild.lastChild, 1, 8), true);
851 | });
852 |
853 | it("should not change it's value", function() {
854 | assert.equal(elt.firstChild.firstChild.lastChild.value,'booooooob');
855 | });
856 |
857 | it("set the selection start correctly", function() {
858 | assert.equal(elt.firstChild.firstChild.lastChild.selectionStart, 1);
859 | });
860 |
861 | it("set the selection end correctly", function() {
862 | assert.equal(elt.firstChild.firstChild.lastChild.selectionEnd, 8);
863 | });
864 |
865 | });
866 |
867 | describe("moving to an element outside the screen", function() {
868 |
869 | before(function() {
870 | var html = 'Text:
'
871 | , i = 10;
872 | while(i--) {
873 | html = html + html;
874 | }
875 | init(html);
876 | });
877 |
878 | after(uninit);
879 |
880 | it("should return true", function() {
881 | assert.equal(mouse.moveTo(elt.lastChild.lastChild), true);
882 | });
883 |
884 | });
885 |
886 | });
887 |
--------------------------------------------------------------------------------