feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,6 @@
import {Manager, Pan, Pinch, DIRECTION_NONE, DIRECTION_VERTICAL, DIRECTION_HORIZONTAL, DIRECTION_ALL, PointerEventInput, TouchMouseInput, TouchInput, MouseInput} from "../src/index";
console.log(Manager, Pan, Pinch, PointerEventInput, TouchMouseInput, TouchInput, MouseInput, DIRECTION_NONE, DIRECTION_VERTICAL, DIRECTION_HORIZONTAL, DIRECTION_ALL);
export default {};

View File

@@ -0,0 +1,42 @@
@import url(http://fonts.googleapis.com/css?family=Open+Sans);
*, *:after, *:before {
box-sizing: border-box;
-moz-box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
min-height: 100%;
background: #eee;
font: 13px/1.5em 'Open Sans', Helvetica, Arial, sans-serif;
}
a {
color: #4986e7;
}
.bg1, .green { background: #42d692; }
.bg2, .blue { background: #4986e7; }
.bg3, .red { background: #d06b64; }
.bg4, .purple { background: #cd74e6; }
.bg5, .azure { background: #9fe1e7; }
body {
margin: 20px;
}
pre {
background: #fff;
padding: 20px;
margin-bottom: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.clear { clear: both; }

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no">
<title></title>
</head>
<body>
Open the inspector and play a bit with the touchAction property.
<script src="../../dist/hammer.min.js"></script>
<script>
var mc = new Hammer(document.body);
mc.add(new Hammer.Swipe({ direction: Hammer.DIRECTION_HORIZONTAL }));
mc.add(new Hammer.Pan({ direction: Hammer.DIRECTION_HORIZONTAL }));
console.log(document.body.style.touchAction)
</script>
</body>
</html>

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<meta name="msapplication-tap-highlight" content="no"/>
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
</head>
<body>
<div class="container">
<div id="hit" class="bg1" style="padding: 30px; height: 200px;">
</div>
<pre id="debug" style="overflow:hidden; background: #eee; padding: 15px;"></pre>
<pre id="log" style="overflow:hidden;"></pre>
</div>
<script src="../../dist/hammer.js"></script>
<script>
Object.prototype.toDirString = function() {
var output = [];
for(var key in this) {
if(this.hasOwnProperty(key)) {
var value = this[key];
if(Array.isArray(value)) {
value = "Array("+ value.length +"):"+ value;
} else if(value instanceof HTMLElement) {
value = value +" ("+ value.outerHTML.substring(0, 50) +"...)";
}
output.push(key +": "+ value);
}
}
return output.join("\n")
};
var el = document.querySelector("#hit");
var log = document.querySelector("#log");
var debug = document.querySelector("#debug");
var mc = new Hammer(el);
mc.get('pinch').set({ enable: true });
mc.on("hammer.input", function(ev) {
debug.innerHTML = [ev.srcEvent.type, ev.pointers.length, ev.isFinal, ev.deltaX, ev.deltaY].join("<br>");
});
</script>
</body>
</html>

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
</head>
<body>
<div class="container">
<div id="hit" class="bg1" style="padding: 30px;">
<span id="target" class="bg5" style="display: block; height: 100px;"></span>
</div>
<pre id="debug" style="overflow:hidden; background: #eee; padding: 15px;"></pre>
<pre id="log" style="overflow:hidden;"></pre>
</div>
<script src="../../dist/hammer.min.js"></script>
<script>
Object.prototype.toDirString = function() {
var output = [];
for(var key in this) {
if(this.hasOwnProperty(key)) {
var value = this[key];
if(Array.isArray(value)) {
value = "Array("+ value.length +"):"+ value;
} else if(value instanceof HTMLElement) {
value = value +" ("+ value.outerHTML.substring(0, 50) +"...)";
}
output.push(key +": "+ value);
}
}
return output.join("\n")
};
var el = document.querySelector("#hit");
var log = document.querySelector("#log");
var debug = document.querySelector("#debug");
var mc = new Hammer(el);
mc.get('pinch').set({ enable: true });
mc.get('rotate').set({ enable: true });
mc.on("swipe pan panstart panmove panend pancancel multipan press pressup pinch rotate tap doubletap",
logGesture);
function DEBUG(str) {
debug.textContent = str;
}
function logGesture(ev) {
console.log(ev.timeStamp, ev.type, ev);
log.textContent = ev.toDirString();
}
</script>
</body>
</html>

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
<style>
#right,
#left {
display: block;
width: 50%;
height: 500px;
overflow: hidden;
}
#left { float: left; }
#right { float: right; }
</style>
</head>
<body>
<div class="container">
<pre id="left" class="bg1"></pre>
<pre id="right" class="bg5"></pre>
<div class="clear"></div>
<h1>Multiple instances the same time</h1>
<p>You can run multiple instances of Hammer on your page and they will recognize each completely isolated
from each other. This makes it possible to build multi-user interfaces.</p>
</div>
<script src="../../dist/hammer.min.js"></script>
<script>
Object.prototype.toDirString = function() {
var output = [];
for(var key in this) {
if(this.hasOwnProperty(key)) {
var value = this[key];
if(Array.isArray(value)) {
value = "Array("+ value.length +"):"+ value;
} else if(value instanceof HTMLElement) {
value = value +" ("+ value.outerHTML.substring(0, 50) +"...)";
}
output.push(key +": "+ value);
}
}
return output.join("\n")
};
function addHammer(el) {
var mc = new Hammer(el, { multiUser: true });
mc.get('pan').set({ direction: Hammer.DIRECTION_ALL });
mc.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
mc.get('pinch').set({ enable: true });
mc.get('rotate').set({ enable: true });
mc.on("swipe pan press pinch rotate tap doubletap", function (ev) {
ev.preventDefault();
el.innerText = ev.toDirString();
});
}
addHammer(document.querySelector("#left"));
addHammer(document.querySelector("#right"));
</script>
</body>
</html>

View File

@@ -0,0 +1,217 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<meta name="msapplication-tap-highlight" content="no"/>
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
<style>
.container {
max-width: 900px;
margin: 0 auto;
}
.panes.wrapper {
max-height: 400px;
max-width: 800px;
background: #666;
margin: 40px auto;
}
.panes {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
.pane {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
text-align: center;
font: bold 60px/250px 'Open Sans', Helvetica, Arial, sans-serif;
color: #fff;
}
.panes.animate > .pane {
transition: all .3s;
-webkit-transition: all .3s;
}
</style>
</head>
<body>
<div class="panes wrapper">
<div class="pane bg1">
<div class="panes">
<div class="pane" style="background: rgba(0,0,0,0);">1.1</div>
<div class="pane" style="background: rgba(0,0,0,.2);">1.2</div>
<div class="pane" style="background: rgba(0,0,0,.4);">1.3</div>
<div class="pane" style="background: rgba(0,0,0,.6);">1.4</div>
<div class="pane" style="background: rgba(0,0,0,.8);">1.5</div>
</div>
</div>
<div class="pane bg2">
<div class="panes">
<div class="pane" style="background: rgba(0,0,0,0);">2.1</div>
<div class="pane" style="background: rgba(0,0,0,.2);">2.2</div>
<div class="pane" style="background: rgba(0,0,0,.4);">2.3</div>
<div class="pane" style="background: rgba(0,0,0,.6);">2.4</div>
<div class="pane" style="background: rgba(0,0,0,.8);">2.5</div>
</div>
</div>
<div class="pane bg3">
<div class="panes">
<div class="pane" style="background: rgba(0,0,0,0);">3.1</div>
<div class="pane" style="background: rgba(0,0,0,.2);">3.2</div>
<div class="pane" style="background: rgba(0,0,0,.4);">3.3</div>
<div class="pane" style="background: rgba(0,0,0,.6);">3.4</div>
<div class="pane" style="background: rgba(0,0,0,.8);">3.5</div>
</div>
</div>
<div class="pane bg4">
<div class="panes">
<div class="pane" style="background: rgba(0,0,0,0);">4.1</div>
<div class="pane" style="background: rgba(0,0,0,.2);">4.2</div>
<div class="pane" style="background: rgba(0,0,0,.4);">4.3</div>
<div class="pane" style="background: rgba(0,0,0,.6);">4.4</div>
<div class="pane" style="background: rgba(0,0,0,.8);">4.5</div>
</div>
</div>
<div class="pane bg5">
<div class="panes">
<div class="pane" style="background: rgba(0,0,0,0);">5.1</div>
<div class="pane" style="background: rgba(0,0,0,.2);">5.2</div>
<div class="pane" style="background: rgba(0,0,0,.4);">5.3</div>
<div class="pane" style="background: rgba(0,0,0,.6);">5.4</div>
<div class="pane" style="background: rgba(0,0,0,.8);">5.5</div>
</div>
</div>
</div>
<div class="container">
<h1>Nested Pan recognizers</h1>
<p>Nested recognizers are possible with some threshold and with use of <code>requireFailure()</code>.</p>
</div>
<script src="../../dist/hammer.js"></script>
<script>
var reqAnimationFrame = (function() {
return window[Hammer.prefixed(window, "requestAnimationFrame")] || function(callback) {
setTimeout(callback, 1000 / 60);
}
})();
function dirProp(direction, hProp, vProp) {
return (direction & Hammer.DIRECTION_HORIZONTAL) ? hProp : vProp
}
/**
* Carousel
* @param container
* @param direction
* @constructor
*/
function HammerCarousel(container, direction) {
this.container = container;
this.direction = direction;
this.panes = Array.prototype.slice.call(this.container.children, 0);
this.containerSize = this.container[dirProp(direction, 'offsetWidth', 'offsetHeight')];
this.currentIndex = 0;
this.hammer = new Hammer.Manager(this.container);
this.hammer.add(new Hammer.Pan({ direction: this.direction, threshold: 10 }));
this.hammer.on("panstart panmove panend pancancel", Hammer.bindFn(this.onPan, this));
this.show(this.currentIndex);
}
HammerCarousel.prototype = {
/**
* show a pane
* @param {Number} showIndex
* @param {Number} [percent] percentage visible
* @param {Boolean} [animate]
*/
show: function(showIndex, percent, animate){
showIndex = Math.max(0, Math.min(showIndex, this.panes.length - 1));
percent = percent || 0;
var className = this.container.className;
if(animate) {
if(className.indexOf('animate') === -1) {
this.container.className += ' animate';
}
} else {
if(className.indexOf('animate') !== -1) {
this.container.className = className.replace('animate', '').trim();
}
}
var paneIndex, pos, translate;
for (paneIndex = 0; paneIndex < this.panes.length; paneIndex++) {
pos = (this.containerSize / 100) * (((paneIndex - showIndex) * 100) + percent);
if(this.direction & Hammer.DIRECTION_HORIZONTAL) {
translate = 'translate3d(' + pos + 'px, 0, 0)';
} else {
translate = 'translate3d(0, ' + pos + 'px, 0)'
}
this.panes[paneIndex].style.transform = translate;
this.panes[paneIndex].style.mozTransform = translate;
this.panes[paneIndex].style.webkitTransform = translate;
}
this.currentIndex = showIndex;
},
/**
* handle pan
* @param {Object} ev
*/
onPan : function (ev) {
var delta = dirProp(this.direction, ev.deltaX, ev.deltaY);
var percent = (100 / this.containerSize) * delta;
var animate = false;
if (ev.type == 'panend' || ev.type == 'pancancel') {
if (Math.abs(percent) > 20 && ev.type == 'panend') {
this.currentIndex += (percent < 0) ? 1 : -1;
}
percent = 0;
animate = true;
}
this.show(this.currentIndex, percent, animate);
}
};
// the horizontal pane scroller
var outer = new HammerCarousel(document.querySelector(".panes.wrapper"), Hammer.DIRECTION_HORIZONTAL);
// each pane should contain a vertical pane scroller
Hammer.each(document.querySelectorAll(".pane .panes"), function(container) {
// setup the inner scroller
var inner = new HammerCarousel(container, Hammer.DIRECTION_VERTICAL);
// only recognize the inner pan when the outer is failing.
// they both have a threshold of some px
outer.hammer.get('pan').requireFailure(inner.hammer.get('pan'));
});
</script>
</body>
</html>

View File

@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
</head>
<body>
<div class="container">
<div id="maps" style="height: 500px; margin-bottom: 20px;"></div>
<h1>Gestures simulator</h1>
<p>Used for unit-testing Hammer.js. To test it on the Google Maps view, you should open your
<a href="https://developer.chrome.com/devtools/docs/mobile-emulation#emulate-touch-events">
Inspector and emulate a touch-screen.</a>
Or just open it on your touch-device.</p>
<p>Currently, it only triggers touchEvents.</p>
</div>
<script src="../../node_modules/hammer-simulator/index.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script>
Simulator.events.touch.fakeSupport();
var el, map;
var container = document.getElementById('maps');
var program = [
['pan', ['deltaX','deltaY']],
['pinch', ['scale']],
['tap', ['pos']],
['swipe', ['deltaX','deltaY']],
['pinch', ['pos','scale']],
['pan', ['pos']],
['rotate', ['pos','rotation']],
['doubleTap', ['pos']],
['pinchRotate', ['pos','scale','rotation']],
];
function randomRange(min, max) {
return Math.random() * (max - min) + min;
}
function randomRangeInt(min, max) {
return Math.round(randomRange(min, max));
}
function gestureOption(name) {
var max = map.getDiv().offsetWidth * .9;
switch(name) {
case 'deltaY':
case 'deltaX':
return randomRangeInt(10, max * .5);
case 'pos':
return [randomRangeInt(10, max), randomRangeInt(10, max)];
case 'scale':
return randomRange(.2, 2);
case 'rotation':
return randomRange(-180, 180);
}
}
function walkProgram(done) {
var clone = [].concat(program);
(function next() {
if(clone.length) {
var entry = clone.shift();
var options = {};
for(var i=0; i<entry[1].length; i++) {
options[entry[1][i]] = gestureOption(entry[1][i]);
}
Simulator.gestures[entry[0]](el, options, next);
} else {
done();
}
}());
}
function startSimulator() {
walkProgram(startSimulator);
}
(function setupGoogleMaps() {
map = new google.maps.Map(container, {
zoom: 14,
center: new google.maps.LatLng(51.98, 5.91)
});
// collect the target element
google.maps.event.addListenerOnce(map, 'idle', function(){
el = container.childNodes[0].childNodes[0];
startSimulator();
});
})();
</script>
</body>
</html>

View File

@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
</head>
<body>
<div class="container">
<div id="hit" class="bg4" style="padding: 30px; height: 300px; position: relative;">
</div>
<pre id="debug" style="overflow:hidden; background: #eee; padding: 15px;"></pre>
<pre id="log" style="overflow:hidden;"></pre>
</div>
<script src="../../node_modules/hammer-simulator/index.js"></script>
<script src="../../dist/hammer.js"></script>
<script>
var program = [
['pan', ['deltaX','deltaY']],
['pinch', ['scale']],
['tap', ['pos']],
['swipe', ['deltaX','deltaY']],
['pinch', ['pos','scale']],
['pan', ['pos']],
['rotate', ['pos','rotation']],
['doubleTap', ['pos']],
['pinchRotate', ['pos','scale','rotation']],
];
function randomRange(min, max) {
return Math.random() * (max - min) + min;
}
function randomRangeInt(min, max) {
return Math.round(randomRange(min, max));
}
function gestureOption(name) {
var max = el.offsetWidth * .9;
switch(name) {
case 'deltaY':
case 'deltaX':
return randomRangeInt(10, max * .5);
case 'pos':
return [randomRangeInt(10, max), randomRangeInt(10, max)];
case 'scale':
return randomRange(.2, 2);
case 'rotation':
return randomRange(-180, 180);
}
}
function walkProgram(done) {
var clone = [].concat(program);
(function next() {
if(clone.length) {
var entry = clone.shift();
var options = {};
for(var i=0; i<entry[1].length; i++) {
options[entry[1][i]] = gestureOption(entry[1][i]);
}
setTimeout(function() {
log.innerHTML = '';
Simulator.gestures[entry[0]](el, options, next);
}, 1000);
} else {
done();
}
}());
}
function startSimulator() {
walkProgram(startSimulator);
}
var el = document.querySelector("#hit");
var log = document.querySelector("#log");
var debug = document.querySelector("#debug");
var mc = new Hammer(el);
mc.get('pinch').set({ enable: true, threshold:.1 });
mc.get('rotate').set({ enable: true });
mc.on("swipe pan multipan press pinch rotate tap doubletap", logGesture);
function logGesture(ev) {
log.textContent = ev.toDirString();
}
Object.prototype.toDirString = function() {
var output = [];
for(var key in this) {
if(this.hasOwnProperty(key)) {
var value = this[key];
if(Array.isArray(value)) {
value = "Array("+ value.length +"):"+ value;
} else if(value instanceof HTMLElement) {
value = value +" ("+ value.outerHTML.substring(0, 50) +"...)";
}
output.push(key +": "+ value);
}
}
return output.join("\n")
};
startSimulator();
</script>
</body>
</html>

View File

@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
<style>
.tester {
margin: 20px 0;
padding: 10px;
height: 200px;
overflow: hidden;
}
.scroll-space {
height: 9000px;
}
#native, #no-native {
color: #fff;
font-weight: bold;
font-size: 1.1em;
padding: 10px 20px;
display: none;
margin: 25px 0;
}
.show {
display: block !important;
}
</style>
</head>
<body>
<div class="container">
<p>Hammer provides a <a href="../../src/touchaction.js">kind of polyfill</a>
for the browsers that don't support the <a href="http://www.w3.org/TR/pointerevents/#the-touch-action-css-property">touch-action</a> property.</p>
<div id="native" class="green">Your browser has support for the touch-action property!</div>
<div id="no-native" class="red">Your browser doesn't support the touch-action property,
so we use the polyfill.</div>
<h2>touch-action: auto</h2>
<p>Should prevent nothing.</p>
<div class="tester azure" id="auto"></div>
<h2>touch-action: pan-y</h2>
<p>Should prevent scrolling on horizontal movement. This is set by default when creating a Hammer instance.</p>
<div class="tester azure" id="pan-y"></div>
<h2>touch-action: pan-x</h2>
<p>Should prevent scrolling on vertical movement.</p>
<div class="tester azure" id="pan-x"></div>
<h2>touch-action: pan-x pan-y</h2>
<p>Should <strong>not</strong> prevent any scrolling on any movement. Horizontal and vertical scrolling handled by the browser directly.</p>
<div class="tester azure" id="pan-x-pan-y"></div>
<h2>touch-action: none</h2>
<p>Should prevent all.</p>
<div class="tester azure" id="none"></div>
</div>
<script src="../../dist/hammer.js"></script>
<script>
var support = Hammer.prefixed(document.body.style, 'touchAction');
document.getElementById(support ? 'native' : 'no-native').className += ' show';
var touchActions = ['auto', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'];
Hammer.each(touchActions, function(touchAction) {
var el = document.getElementById(touchAction.replace(" ", "-"));
var mc = Hammer(el, {
touchAction: touchAction
});
mc.get('pan').set({ direction: Hammer.DIRECTION_ALL });
mc.get('pinch').set({ enable: true });
mc.get('rotate').set({ enable: true });
mc.on("pan swipe rotate pinch tap doubletap press", function(ev) {
el.textContent = ev.type +" "+ el.textContent;
});
});
</script>
<div class="scroll-space"></div>
<p>hi.</p>
</body>
</html>

View File

@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="assets/style.css">
<title>Hammer.js</title>
<style>
html, body {
overflow: hidden;
margin: 0;
}
body {
-webkit-perspective: 500;
-moz-perspective: 500;
perspective: 500;
}
.animate {
-webkit-transition: all .3s;
-moz-transition: all .3s;
transition: all .3s;
}
#hit {
padding: 10px;
}
#log {
position: absolute;
padding: 10px;
}
</style>
</head>
<body>
<div id="log"></div>
<div id="hit" style="background: #42d692; width: 150px; height: 150px;"></div>
<script src="../../dist/hammer.js"></script>
<script>
var reqAnimationFrame = (function () {
return window[Hammer.prefixed(window, 'requestAnimationFrame')] || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var log = document.querySelector("#log");
var el = document.querySelector("#hit");
var START_X = Math.round((window.innerWidth - el.offsetWidth) / 2);
var START_Y = Math.round((window.innerHeight - el.offsetHeight) / 2);
var ticking = false;
var transform;
var timer;
var mc = new Hammer.Manager(el);
mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }));
mc.add(new Hammer.Swipe()).recognizeWith(mc.get('pan'));
mc.add(new Hammer.Rotate({ threshold: 0 })).recognizeWith(mc.get('pan'));
mc.add(new Hammer.Pinch({ threshold: 0 })).recognizeWith([mc.get('pan'), mc.get('rotate')]);
mc.add(new Hammer.Tap({ event: 'doubletap', taps: 2 }));
mc.add(new Hammer.Tap());
mc.on("panstart panmove", onPan);
mc.on("rotatestart rotatemove", onRotate);
mc.on("pinchstart pinchmove", onPinch);
mc.on("swipe", onSwipe);
mc.on("tap", onTap);
mc.on("doubletap", onDoubleTap);
mc.on("hammer.input", function(ev) {
if(ev.isFinal) {
resetElement();
}
});
function resetElement() {
el.className = 'animate';
transform = {
translate: { x: START_X, y: START_Y },
scale: 1,
angle: 0,
rx: 0,
ry: 0,
rz: 0
};
requestElementUpdate();
if (log.textContent.length > 2000) {
log.textContent = log.textContent.substring(0, 2000) + "...";
}
}
function updateElementTransform() {
var value = [
'translate3d(' + transform.translate.x + 'px, ' + transform.translate.y + 'px, 0)',
'scale(' + transform.scale + ', ' + transform.scale + ')',
'rotate3d('+ transform.rx +','+ transform.ry +','+ transform.rz +','+ transform.angle + 'deg)'
];
value = value.join(" ");
el.textContent = value;
el.style.webkitTransform = value;
el.style.mozTransform = value;
el.style.transform = value;
ticking = false;
}
function requestElementUpdate() {
if(!ticking) {
reqAnimationFrame(updateElementTransform);
ticking = true;
}
}
function logEvent(str) {
//log.insertBefore(document.createTextNode(str +"\n"), log.firstChild);
}
function onPan(ev) {
el.className = '';
transform.translate = {
x: START_X + ev.deltaX,
y: START_Y + ev.deltaY
};
requestElementUpdate();
logEvent(ev.type);
}
var initScale = 1;
function onPinch(ev) {
if(ev.type == 'pinchstart') {
initScale = transform.scale || 1;
}
el.className = '';
transform.scale = initScale * ev.scale;
requestElementUpdate();
logEvent(ev.type);
}
var initAngle = 0;
function onRotate(ev) {
if(ev.type == 'rotatestart') {
initAngle = transform.angle || 0;
}
el.className = '';
transform.rz = 1;
transform.angle = initAngle + ev.rotation;
requestElementUpdate();
logEvent(ev.type);
}
function onSwipe(ev) {
var angle = 50;
transform.ry = (ev.direction & Hammer.DIRECTION_HORIZONTAL) ? 1 : 0;
transform.rx = (ev.direction & Hammer.DIRECTION_VERTICAL) ? 1 : 0;
transform.angle = (ev.direction & (Hammer.DIRECTION_RIGHT | Hammer.DIRECTION_UP)) ? angle : -angle;
clearTimeout(timer);
timer = setTimeout(function () {
resetElement();
}, 300);
requestElementUpdate();
logEvent(ev.type);
}
function onTap(ev) {
transform.rx = 1;
transform.angle = 25;
clearTimeout(timer);
timer = setTimeout(function () {
resetElement();
}, 200);
requestElementUpdate();
logEvent(ev.type);
}
function onDoubleTap(ev) {
transform.rx = 1;
transform.angle = 80;
clearTimeout(timer);
timer = setTimeout(function () {
resetElement();
}, 500);
requestElementUpdate();
logEvent(ev.type);
}
resetElement();
</script>
</body>
</html>

View File

@@ -0,0 +1,50 @@
var utils = {
/**
* trigger simple dom event
* @param obj
* @param name
*/
triggerDomEvent: function(obj, name) {
var event = document.createEvent('Event');
event.initEvent(name, true, true);
obj.dispatchEvent(event);
},
createTouchEvent: function(name, x, y, identifier) {
var event = document.createEvent('Event');
event.initEvent('touch' + name, true, true);
event.touches = event.targetTouches = [{
clientX: x,
clientY: y,
identifier: identifier || 0
}];
//https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent.changedTouches
event.changedTouches = [{
clientX: x,
clientY: y,
identifier: identifier || 0
}];
return event;
},
dispatchTouchEvent: function(el, name, x, y, identifier) {
var event = utils.createTouchEvent(name, x, y, identifier);
el.dispatchEvent(event);
},
createHitArea: function(parent) {
if (parent == null) {
parent = document.getElementById('qunit-fixture')
}
var hitArea = document.createElement('div');
hitArea.style.background = '#eee';
hitArea.style.height = '300px';
parent.appendChild(hitArea);
return hitArea;
}
};

View File

@@ -0,0 +1,67 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils,Simulator */
var el;
var hammer;
QUnit.module('Pan Gesture', {
beforeEach: function() {
el = document.createElement('div');
document.body.appendChild(el);
hammer = new Hammer(el, { recognizers: [] });
},
afterEach: function() {
document.body.removeChild(el);
hammer.destroy();
}
});
QUnit.test('`panstart` and `panmove` should be recognized', function(assert) {
assert.expect(2);
var panMoveCount = 0;
var pan = new Hammer.Pan({ threshold: 1 });
hammer.add(pan);
hammer.on('panstart', function() {
assert.ok(true, 'Pan start triggered');
});
hammer.on('panmove', function() {
panMoveCount++;
});
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'move', 70, 50);
utils.dispatchTouchEvent(el, 'move', 90, 50);
assert.equal(panMoveCount, 1, 'exactly one panMove triggered');
});
QUnit.test('Pan event flow should be start -> left -> end', function(assert) {
var done = assert.async();
assert.expect(1);
var pan = new Hammer.Pan({ threshold: 1 });
hammer.add(pan);
var eventflow = '';
var isCalledPanleft = false;
hammer.on('panstart', function() {
eventflow += 'start';
});
hammer.on('panleft', function() {
if (!isCalledPanleft) {
isCalledPanleft = true;
eventflow += 'left';
}
});
hammer.on('panend', function() {
eventflow += 'end';
isCalledPanleft = true;
});
Simulator.gestures.pan(el, { deltaX: -100, deltaY: 0 }, function() {
assert.equal(eventflow, 'startleftend', 'correct event flow');
done();
});
});

View File

@@ -0,0 +1,46 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,Simulator */
var el;
var hammer;
QUnit.module('Pinch Gesture', {
beforeEach: function() {
el = document.createElement('div');
document.body.appendChild(el);
hammer = new Hammer(el, { recognizers: [] });
},
afterEach: function() {
document.body.removeChild(el);
hammer.destroy();
}
});
QUnit.test('Pinch event flow should be start -> in -> end', function(assert) {
var done = assert.async();
assert.expect(1);
var pinch = new Hammer.Pinch({ enable: true, threshold: 0.1 });
hammer.add(pinch);
var eventflow = '';
var isFiredPinchin = false;
hammer.on('pinchstart', function() {
eventflow += 'start';
});
hammer.on('pinchin', function() {
if (!isFiredPinchin) {
isFiredPinchin = true;
eventflow += 'in';
}
});
hammer.on('pinchend', function() {
eventflow += 'end';
isFiredPinchin = false;
});
Simulator.gestures.pinch(el, { duration: 500, scale: 0.5 }, function() {
assert.equal(eventflow, 'startinend', 'correct event flow');
done();
});
});

View File

@@ -0,0 +1,29 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils,Simulator */
var el;
var hammer;
var swipeCount = 0;
QUnit.module('Swipe Gesture', {
beforeEach: function() {
el = utils.createHitArea();
hammer = new Hammer(el, { recognizers: [] });
swipeCount = 0;
},
afterEach: function() {
hammer.destroy();
}
});
QUnit.test('swipe can be recognized', function(assert) {
assert.expect(1);
var done = assert.async();
var swipe = new Hammer.Swipe({ threshold: 1 });
hammer.add(swipe);
hammer.on('swipe', function() {
assert.ok(true);
done();
});
Simulator.gestures.swipe(el);
});

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Tests</title>
<link rel="stylesheet" href="../../node_modules/qunitjs/qunit/qunit.css">
<script src="../../node_modules/jquery/dist/jquery.min.js"></script>
<script src="../../node_modules/lodash-compat/index.js"></script>
<script src="../../node_modules/qunitjs/qunit/qunit.js"></script>
<!--[if !IE]> --><script src="../../node_modules/blanket/dist/qunit/blanket.js"></script><!-- <![endif]-->
<script src="assets/utils.js"></script>
<script src="../../node_modules/hammer-simulator/index.js"></script>
<script>
Simulator.setType('touch');
Simulator.events.touch.fakeSupport();
</script>
<script src="../../dist/hammer.js" data-cover></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="test_utils.js"></script>
<script src="test_enable.js"></script>
<script src="test_hammer.js"></script>
<script src="test_events.js"></script>
<script src="test_nested_gesture_recognizers.js"></script>
<script src="test_simultaneous_recognition.js"></script>
<script src="test_propagation_bubble.js"></script>
<script src="test_gestures.js"></script>
<script src="test_multiple_taps.js"></script>
<script src="test_require_failure.js"></script>
<script src="test_jquery_plugin.js"></script>
<script src="gestures/test_pan.js"></script>
<script src="gestures/test_pinch.js"></script>
<script src="gestures/test_swipe.js"></script>
</body>
</html>

View File

@@ -0,0 +1,179 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils */
/* jshint unused:false */
var el,
hammer,
counter;
QUnit.module('Test recognizer enable', {
beforeEach: function() {
el = utils.createHitArea();
hammer = new Hammer.Manager(el, { recognizers: [] });
counter = 0;
},
afterEach: function() {
hammer && hammer.destroy();
}
});
QUnit.test('should disable a recognizer through the `enable` constructor parameter', function(assert) {
assert.expect(1);
hammer.add(new Hammer.Tap({ enable: false }));
hammer.on('tap', function() {
counter++;
});
var done = assert.async();
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
setTimeout(function() {
assert.equal(counter, 0, 'counter is zero');
done();
}, 100);
});
QUnit.test('should disable recognizing when the manager is disabled.', function(assert) {
assert.expect(1);
hammer.set({ enable: false });
hammer.add(new Hammer.Tap());
hammer.on('tap', function() {
counter++;
});
var done = assert.async();
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
setTimeout(function() {
assert.equal(counter, 0, 'counter is zero');
done();
}, 100);
});
QUnit.test('should toggle a recognizer using the `set` call to the recognizer enable property', function(assert) {
assert.expect(2);
hammer.add(new Hammer.Tap());
hammer.on('tap', function() {
counter++;
});
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 1);
hammer.get('tap').set({ enable: false });
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 1, 'counter is 1');
});
QUnit.test('should accept the `enable` constructor parameter as function', function(assert) {
assert.expect(2);
var canRecognizeTap = false;
var tap = new Hammer.Tap({
enable: function() {
return canRecognizeTap;
}
});
hammer.add(tap);
hammer.on('tap', function() {
counter++;
});
var done = assert.async();
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
setTimeout(function() {
assert.equal(counter, 0, 'counter is zero');
canRecognizeTap = true;
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 1, 'counter is 1');
done();
}, 100);
});
QUnit.test('should accept a function parameter with `set`', function(assert) {
assert.expect(3);
hammer.add(new Hammer.Tap());
hammer.on('tap', function() {
counter++;
});
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 1, 'counter is 1');
var canRecognizeTap = false;
hammer.get('tap').set({ enable: function() {
return canRecognizeTap;
} });
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 1, 'counter is 1');
canRecognizeTap = true;
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 2, 'counter is 2');
});
QUnit.test('should pass the recognizer and optional the input parameter to the `enable` callback', function(assert) {
assert.expect(2);
var tap;
// The enable function is called initially to setup the touch-action property
// at that moment there isn't any input
var canEnable = function(recognizer, input) {
assert.equal(recognizer, tap, 'recognizer is tap');
return true;
};
tap = new Hammer.Tap({ enable: canEnable });
hammer.add(tap);
utils.dispatchTouchEvent(el, 'start', 50, 50);
});
QUnit.test('should toggle based on other object method', function(assert) {
assert.expect(2);
var view = {
state: 0,
canRecognizeTap: function(recognizer, input) {
return this.state !== 0;
}
};
hammer.add(new Hammer.Tap({ enable: function(rec, input) {
return view.canRecognizeTap(rec, input);
} }));
hammer.on('tap', function() {
counter++;
});
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 0, 'counter is 0');
view.state = 1;
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
assert.equal(counter, 1, 'counter is 1');
});

View File

@@ -0,0 +1,64 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils,el */
QUnit.module('eventEmitter');
QUnit.test('test the eventemitter', function(assert) {
assert.expect(6);
var ee = new Hammer.Manager(utils.createHitArea());
var inputData = {
target: document.body,
srcEvent: {
preventDefault: function() {
assert.ok(true, 'preventDefault ref');
},
target: document.body
}
};
function event3Handler() {
assert.ok(true, 'emitted event3');
}
ee.on('testEvent1', function() {
assert.ok(true, 'emitted event');
});
ee.on('testEvent2', function(ev) {
assert.ok(true, 'emitted event');
ev.preventDefault();
assert.ok(ev.target === document.body, 'target is the body');
});
ee.on('testEvent3', event3Handler);
ee.emit('testEvent1', inputData);
ee.emit('testEvent2', inputData);
ee.emit('testEvent3', inputData);
// Unbind testEvent2
ee.off('testEvent2');
ee.off('testEvent3', event3Handler);
ee.emit('testEvent1', inputData); // Should trigger testEvent1 again
ee.emit('testEvent2', inputData); // Doenst trigger a thing
ee.emit('testEvent3', inputData); // Doenst trigger a thing
// Destroy
ee.destroy();
ee.emit('testEvent1', inputData); // Doenst trigger a thing
ee.emit('testEvent2', inputData); // Doenst trigger a thing
ee.emit('testEvent3', inputData); // Doenst trigger a thing
});
/*
* Hammer.Manager.off method : exception handling
*/
QUnit.test('When Hammer.Manager didnt attach an event, `off` method is ignored', function(assert) {
var count = 0;
var hammer = new Hammer(el, { inputTarget: document.body });
hammer.off('swipeleft', function() {
count++;
});
assert.ok(true, 'nothing');
});

View File

@@ -0,0 +1,225 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils,Simulator */
// TODO: this tests fails because tapRecognizer changes
// it could be that tapRecognizer setup its BEGAN state and
// disable the other gesture recognition
var el;
var hammer;
var events;
var allGestureEvents = [
'tap doubletap press',
'pinch pinchin pinchout pinchstart pinchmove pinchend pinchcancel',
'rotate rotatestart rotatemove rotateend rotatecancel',
'pan panstart panmove panup pandown panleft panright panend pancancel',
'swipe swipeleft swiperight swipeup swipedown'
].join(' ');
QUnit.module('Gesture recognition', {
beforeEach: function() {
el = utils.createHitArea();
hammer = new Hammer(el);
hammer.get('pinch')
.set({ // Some threshold, since the simulator doesnt stays at scale:1 when rotating
enable: true,
threshold: 0.1
});
hammer.get('rotate')
.set({ enable: true });
hammer.on(allGestureEvents, function(ev) {
events[ ev.type ] = true;
});
events = {};
},
afterEach: function() {
hammer && hammer.destroy();
events = null;
}
});
QUnit.test('recognize pan', function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.pan(el, { duration: 500, deltaX: 100, deltaY: 0 }, function() {
assert.deepEqual(events, {
pan: true,
panstart: true,
panmove: true,
panright: true,
panend: true
}, 'Pan events recognized');
done();
});
});
QUnit.test('recognize press', function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.press(el, null, function() {
assert.deepEqual(events, {
press: true
});
done();
}, 'only press was recognized');
});
QUnit.test('recognize swipe', function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.swipe(el, { duration: 300, deltaX: 400, deltaY: 0 }, function() {
assert.deepEqual(events, {
pan: true,
panstart: true,
panmove: true,
panright: true,
panend: true,
swipe: true,
swiperight: true
}, 'pan and swipe events were recognized');
done();
});
});
QUnit.test('recognize pinch', function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.pinch(el, { duration: 500, scale: 0.5 }, function() {
assert.deepEqual(events, {
pinch: true,
pinchstart: true,
pinchmove: true,
pinchend: true,
pinchin: true
}, 'pinch events were recognized');
done();
});
});
QUnit.test('recognize children multitouch pinch', function(assert) {
var done = assert.async();
assert.expect(1);
var el1 = utils.createHitArea(el);
var el2 = utils.createHitArea(el);
Simulator.gestures.pinch([ el1, el2 ], { duration: 500, scale: 0.5 }, function() {
assert.deepEqual(events, {
pinch: true,
pinchstart: true,
pinchmove: true,
pinchend: true,
pinchin: true
}, 'pinch events on child were recognized');
done();
});
});
QUnit.test('recognize parent-child multitouch pinch', function(assert) {
var done = assert.async();
assert.expect(1);
var el1 = utils.createHitArea(el);
Simulator.gestures.pinch([ el, el1 ], { duration: 100, scale: 0.5 }, function() {
assert.deepEqual(events, {
pinch: true,
pinchstart: true,
pinchmove: true,
pinchend: true,
pinchin: true
}, 'Pinch events on parent were recognized');
done();
});
});
QUnit.test('recognize rotate', function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.rotate(el, { duration: 500, scale: 1 }, function() {
assert.deepEqual(events, {
rotate: true,
rotatestart: true,
rotatemove: true,
rotateend: true
}, 'Rotate events recognized');
done();
});
});
QUnit.test('recognize multitouch rotate', function(assert) {
var done = assert.async();
assert.expect(1);
var el1 = utils.createHitArea(el);
Simulator.gestures.rotate([ el, el1 ], { duration: 500, scale: 1 }, function() {
assert.deepEqual(events, {
rotate: true,
rotatestart: true,
rotatemove: true,
rotateend: true
}, 'Rotate events were recognized');
done();
});
});
QUnit.test('recognize rotate and pinch simultaneous', function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.pinchRotate(el, { duration: 500, scale: 2 }, function() {
assert.deepEqual(events, {
rotate: true,
rotatestart: true,
rotatemove: true,
rotateend: true,
pinch: true,
pinchstart: true,
pinchmove: true,
pinchend: true,
pinchout: true
}, 'Rotate and pinch were recognized together');
done();
});
});
QUnit.test("don't recognize pan and swipe when moving down, when only horizontal is allowed", function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.swipe(el, { duration: 250, deltaX: 0, deltaZ: 200 }, function() {
assert.deepEqual(events, { }, 'No events were recognized');
done();
});
});
QUnit.test("don't recognize press if duration is too short.", function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.press(el, { duration: 240 });
setTimeout(function() {
assert.deepEqual(events, { tap: true }, 'Tap gesture has been recognized.');
done();
}, 275);
});
QUnit.test("don't recognize tap if duration is too long.", function(assert) {
var done = assert.async();
assert.expect(1);
Simulator.gestures.tap(el, { duration: 255 });
setTimeout(function() {
assert.deepEqual(events, { press: true }, 'Press gesture has been recognized.');
done();
}, 275);
});

View File

@@ -0,0 +1,197 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals,requireTemplateStringsForConcatenation,requireArrayDestructuring
/* globals QUnit,Hammer,utils,Simulator */
var el, el2,
hammer, hammer2;
QUnit.module('Tests', {
beforeEach: function() {
el = utils.createHitArea();
el2 = utils.createHitArea();
},
afterEach: function() {
if (hammer) {
hammer.destroy();
hammer = null;
}
if (hammer2) {
hammer2.destroy();
hammer2 = null;
}
}
});
// since Hammer is now a ES6 Class and we cannot call a class as a function,
// it needs a `new` keyword prefixed that makes this Shortcut test kinda Redundant.
// QUnit.test( "hammer shortcut", function( assert ) {
// assert.expect( 2 );
//
// Hammer.defaults.touchAction = "pan-y";
// hammer = Hammer( el );
//
// assert.ok( hammer instanceof Hammer.Manager, "returns an instance of Manager" );
// assert.ok( hammer.touchAction.actions == Hammer.defaults.touchAction, "set the default touchAction" );
// } );
//
// QUnit.test( "hammer shortcut with options", function( assert ) {
// assert.expect( 2 );
//
// hammer = Hammer( el, {
// touchAction: "none"
// } );
// assert.ok( hammer instanceof Hammer.Manager, "returns an instance of Manager" );
// assert.ok( hammer.touchAction.actions == "none", "set the default touchAction" );
// } );
/* Creating a hammer instance does not work on the same way
* when using Hammer or Hammer.Manager.
*
* This can confuse developers who read tests to use the library when doc is missing.
*/
QUnit.test('Hammer and Hammer.Manager constructors work exactly on the same way.', function(assert) {
assert.expect(2);
hammer = new Hammer(el, {});
assert.equal(Hammer.defaults.preset.length, hammer.recognizers.length,
'Correct number of recognizers by default');
hammer2 = new Hammer.Manager(el, {});
assert.equal(0, hammer2.recognizers.length, 'No default recognizers with manager and empty object');
});
/* DOC to disable default recognizers should be added.
*
* - Hammer(el). IMO: Currently, well done.
* - Hammer(el, {}) . IMO: should disable default recognizers
* - Hammer(el, {recognizers: null}). IMO: now, it fails.
* - Hammer(el, {recognizers: []}). It works, but it is likely not intuitive.
*/
QUnit.test('A Hammer instance can be setup to not having default recognizers.', function(assert) {
assert.expect(1);
hammer = new Hammer(el, { recognizers: false });
assert.equal(0, hammer.recognizers.length, 'No default recognizers with recognizers false');
});
/* The case was when I added a custom tap event which was added to the default
* recognizers, and my custom tap gesture wasn't working (I do not know exactly the reason),
* but removing the default recognizers solved the issue.
*/
QUnit.test('Adding the same recognizer type should remove the old recognizer', function(assert) {
assert.expect(4);
hammer = new Hammer(el);
assert.ok(!!hammer.get('tap'));
assert.equal(7, hammer.recognizers.length, '7 recognizers found');
var newTap = new Hammer.Tap({ time: 1337 });
hammer.add(newTap);
assert.equal(7, hammer.recognizers.length, '7 recognizers found after adding tap');
assert.equal(1337, hammer.get('tap').options.time, 'Time has been updated to reflect new tap');
});
/*
* Swipe gesture:
* - in this tests, it does not update input.velocity ( always 0)
* - does not fire swipeleft or swiperight events
*/
QUnit.test('Swiping to the left should fire swipeleft event', function(assert) {
var done = assert.async();
assert.expect(2);
hammer = new Hammer(el, { recognizers: [] });
hammer.add(new Hammer.Swipe());
hammer.on('swipe swipeleft', function() {
assert.ok(true);
});
Simulator.gestures.swipe(el, { pos: [ 300, 300 ], deltaY: 0, deltaX: -200 }, function() {
done();
});
});
/*
* Input target change
*/
QUnit.test('Should detect input while on other element', function(assert) {
var done = assert.async();
assert.expect(1);
hammer = new Hammer(el, { inputTarget: document.body });
hammer.on('tap', function() {
assert.ok(true);
});
Simulator.gestures.tap(document.body, null, function() {
done();
});
});
/* Hammer.Manager constructor accepts a "recognizers" option in which each
* element is an array representation of a Recognizer.
*/
QUnit.test('Hammer.Manager accepts recognizers as arrays.', function(assert) {
assert.expect(4);
hammer = new Hammer.Manager(el, {
recognizers: [
[ Hammer.Swipe ],
[ Hammer.Pinch ],
[ Hammer.Rotate ],
[ Hammer.Pan, { direction: Hammer.DIRECTION_UP }, [ 'swipe', 'pinch' ], [ 'rotate' ] ]
]
});
assert.equal(4, hammer.recognizers.length, '4 recognizers found');
var recognizerActual = hammer.recognizers[ 3 ];
assert.equal(recognizerActual.options.direction, Hammer.DIRECTION_UP,
'Recognize direction from options');
assert.equal(2, Object.keys(recognizerActual.simultaneous).length, '2 simultanious recognizers found');
assert.equal(1, recognizerActual.requireFail.length, '1 require failing recognizer found');
});
/*
* Removing a recognizer which cannot be found would errantly remove the last recognizer in the
* manager's list.
*/
QUnit.test('Remove non-existent recognizer.', function(assert) {
assert.expect(1);
hammer = new Hammer(el, { recognizers: [] });
hammer.add(new Hammer.Swipe());
hammer.remove('tap');
assert.equal(1, hammer.recognizers.length, '1 recognizer found');
});
QUnit.test('check whether Hammer.defaults.cssProps is restored', function(assert) {
var beforeCssProps = {
userSelect: 'text',
touchSelect: 'grippers',
touchCallout: 'default',
contentZooming: 'chained',
userDrag: 'element',
tapHighlightColor: 'rgba(0, 1, 0, 0)'
};
var prop;
Hammer.each(Hammer.defaults.cssProps, function(value, name) {
prop = Hammer.prefixed(el.style, name);
if (prop) {
el.style[ prop ] = beforeCssProps[ name ];
}
});
hammer = new Hammer(el);
hammer.destroy();
hammer = null;
Hammer.each(Hammer.defaults.cssProps, function(value, name) {
prop = Hammer.prefixed(el.style, name);
if (prop) {
assert.equal(el.style[ prop ], beforeCssProps[ name ], 'check if ' + name + ' is restored');
}
});
});

View File

@@ -0,0 +1,63 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils,Simulator,$,jQuery */
var el, hammer, events;
var jQueryPluginPath = '../../node_modules/jquery-hammerjs/jquery.hammer.js';
QUnit.module('jQuery plugin', {
beforeEach: function() {
el = utils.createHitArea();
events = {};
},
afterEach: function() {
hammer && hammer.destroy();
}
});
QUnit.test('trigger pan with jQuery', function(assert) {
var done = assert.async();
assert.expect(2);
$.getScript(jQueryPluginPath, function() {
jQuery(el).hammer();
jQuery(el).bind('panstart pan panmove panright panend', function(ev) {
if (ev.gesture) {
events[ ev.type ] = true;
}
});
Simulator.gestures.pan(el, { deltaX: 50, deltaY: 0 }, function() {
assert.deepEqual(events, {
pan: true,
panstart: true,
panmove: true,
panright: true,
panend: true
}, 'Pan events recognized');
assert.ok(jQuery(el).data('hammer') instanceof Hammer.Manager, 'data attribute refers to the instance');
done();
});
});
});
QUnit.test('trigger pan without jQuery should still work', function(assert) {
var done = assert.async();
assert.expect(1);
var hammer = new Hammer(el);
hammer.on('panstart pan panmove panright panend', function(ev) {
events[ev.type] = true;
});
Simulator.gestures.pan(el, { deltaX: 50, deltaY: 0 }, function() {
assert.deepEqual(events, {
pan: true,
panstart: true,
panmove: true,
panright: true,
panend: true
}, 'Pan events recognized');
done();
});
});

View File

@@ -0,0 +1,97 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils*/
var el;
var hammer;
var tripleTapCount = 0;
var doubleTapCount = 0;
var tapCount = 0;
QUnit.module('Tap delay', {
beforeEach: function() {
el = utils.createHitArea();
hammer = new Hammer(el, { recognizers: [] });
var tap = new Hammer.Tap();
var doubleTap = new Hammer.Tap({ event: 'doubleTap', taps: 2 });
var tripleTap = new Hammer.Tap({ event: 'tripleTap', taps: 3 });
hammer.add([ tripleTap, doubleTap, tap ]);
tripleTap.recognizeWith([ doubleTap, tap ]);
doubleTap.recognizeWith(tap);
doubleTap.requireFailure(tripleTap);
tap.requireFailure([ tripleTap, doubleTap ]);
tripleTapCount = 0;
doubleTapCount = 0;
tapCount = 0;
hammer.on('tap', function() {
tapCount++;
});
hammer.on('doubleTap', function() {
doubleTapCount++;
});
hammer.on('tripleTap', function() {
tripleTapCount++;
});
},
afterEach: function() {
hammer.destroy();
}
});
QUnit.test('When a tripleTap is fired, doubleTap and Tap should not be recognized', function(assert) {
var done = assert.async();
assert.expect(3);
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
setTimeout(function() {
assert.equal(tripleTapCount, 1, 'one tripletap event');
assert.equal(doubleTapCount, 0, 'no doubletap event');
assert.equal(tapCount, 0, 'no singletap event');
done();
}, 350);
});
QUnit.test('When a doubleTap is fired, tripleTap and Tap should not be recognized', function(assert) {
var done = assert.async();
assert.expect(3);
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
setTimeout(function() {
assert.equal(tripleTapCount, 0, 'No tripple taps recognized');
assert.equal(doubleTapCount, 1, '1 double tap recognized');
assert.equal(tapCount, 0, 'No single taps recognized');
done();
}, 350);
});
QUnit.test('When a tap is fired, tripleTap and doubleTap should not be recognized', function(assert) {
var done = assert.async();
assert.expect(3);
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'end', 50, 50);
setTimeout(function() {
assert.equal(tripleTapCount, 0, 'No tripple taps recognized');
assert.equal(doubleTapCount, 0, 'No double taps recognized');
assert.equal(tapCount, 1, '1 single tap recognized');
done();
}, 350);
});

View File

@@ -0,0 +1,170 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils*/
/*jshint -W079 */
var parent,
child,
hammerChild,
hammerParent;
QUnit.module('Nested gesture recognizers (Tap Child + Pan Parent)', {
beforeEach: function() {
parent = document.createElement('div');
child = document.createElement('div');
document.getElementById('qunit-fixture').appendChild(parent);
parent.appendChild(child);
hammerParent = new Hammer.Manager(parent, {
touchAction: 'none'
});
hammerChild = new Hammer.Manager(child, {
touchAction: 'none'
});
hammerChild.add(new Hammer.Tap());
hammerParent.add(new Hammer.Pan({ threshold: 5, pointers: 1 }));
},
afterEach: function() {
hammerChild.destroy();
hammerParent.destroy();
}
});
QUnit.test('Tap on the child', function(assert) {
assert.expect(1);
hammerChild.on('tap', function() {
assert.ok(true);
});
hammerParent.on('tap', function() {
throw new Error('tap should not fire on parent');
});
utils.dispatchTouchEvent(child, 'start', 0, 10);
utils.dispatchTouchEvent(child, 'end', 0, 10);
});
QUnit.test('Panning on the child should fire parent pan and should not fire child tap event', function(assert) {
assert.expect(1);
hammerChild.on('tap', function() {
throw new Error('tap should not fire on parent');
});
hammerParent.on('panend', function() {
assert.ok(true);
});
utils.dispatchTouchEvent(child, 'start', 10, 0);
utils.dispatchTouchEvent(child, 'move', 20, 0);
utils.dispatchTouchEvent(child, 'end', 30, 0);
});
/*
// test (optional pointers validation)
test('Panning with one finger down on child, other on parent', function () {
expect(1);
var event,
touches;
hammerParent.on('panend', function () {
ok(true);
});
// one finger one child
utils.dispatchTouchEvent(child, 'start', 10, 0, 0);
utils.dispatchTouchEvent(parent, 'start', 12, 0, 1);
touches = [
{clientX: 20, clientY: 0, identifier: 0 },
{clientX: 20, clientY: 0, identifier: 1 }
];
event = document.createEvent('Event');
event.initEvent('touchmove', true, true);
event.touches = touches;
event.changedTouches = touches;
parent.dispatchEvent(event);
touches = [
{clientX: 30, clientY: 0, identifier: 0 },
{clientX: 30, clientY: 0, identifier: 1 }
];
event = document.createEvent('Event');
event.initEvent('touchend', true, true);
event.touches = touches;
event.changedTouches = touches;
parent.dispatchEvent(event);
});
*/
var pressPeriod = 600;
QUnit.module('Nested gesture recognizers (Press Child + Pan Parent)', {
beforeEach: function() {
parent = document.createElement('div');
child = document.createElement('div');
document.getElementById('qunit-fixture').appendChild(parent);
parent.appendChild(child);
hammerParent = new Hammer.Manager(parent, {
touchAction: 'none'
});
hammerChild = new Hammer.Manager(child, {
touchAction: 'none'
});
hammerChild.add(new Hammer.Press({ time: pressPeriod }));
hammerParent.add(new Hammer.Pan({ threshold: 5, pointers: 1 }));
},
afterEach: function() {
hammerChild.destroy();
hammerParent.destroy();
}
});
QUnit.test('Press on the child', function(assert) {
assert.expect(1);
hammerChild.on('press', function() {
assert.ok(true);
});
hammerParent.on('press', function() {
throw new Error('press should not fire on parent');
});
utils.dispatchTouchEvent(child, 'start', 0, 10);
var done = assert.async();
setTimeout(function() {
done();
}, pressPeriod);
});
QUnit.test('When Press is followed by Pan on the same element, both gestures are recognized', function(assert) {
assert.expect(2);
hammerChild.on('press', function() {
assert.ok(true);
});
hammerParent.on('panend', function() {
assert.ok(true);
});
utils.dispatchTouchEvent(child, 'start', 0, 10);
var done = assert.async();
setTimeout(function() {
utils.dispatchTouchEvent(child, 'move', 10, 10);
utils.dispatchTouchEvent(child, 'move', 20, 10);
utils.dispatchTouchEvent(child, 'move', 30, 10);
utils.dispatchTouchEvent(child, 'end', 30, 10);
done();
}, pressPeriod);
});

View File

@@ -0,0 +1,60 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils*/
/*jshint -W079 */
var parent;
var child;
var hammerChild;
var hammerParent;
QUnit.module('Propagation (Tap in Child and Parent)', {
beforeEach: function() {
parent = document.createElement('div');
child = document.createElement('div');
document.getElementById('qunit-fixture').appendChild(parent);
parent.appendChild(child);
hammerParent = new Hammer.Manager(parent);
hammerChild = new Hammer.Manager(child);
hammerChild.add(new Hammer.Tap());
hammerParent.add(new Hammer.Tap());
},
afterEach: function() {
hammerChild.destroy();
hammerParent.destroy();
}
});
QUnit.test('Tap on the child, fires also the tap event to the parent', function(assert) {
assert.expect(2);
hammerChild.on('tap', function() {
assert.ok(true);
});
hammerParent.on('tap', function() {
assert.ok(true);
});
utils.dispatchTouchEvent(child, 'start', 0, 10);
utils.dispatchTouchEvent(child, 'end', 0, 10);
});
QUnit.test('When tap on the child and the child stops the input event propagation, the tap event does not get fired in the parent', function(assert) {
assert.expect(1);
hammerChild.on('tap', function() {
assert.ok(true);
});
hammerParent.on('tap', function() {
throw new Error('parent tap gesture should not be recognized');
});
child.addEventListener('touchend', function(ev) {
ev.stopPropagation();
});
utils.dispatchTouchEvent(child, 'start', 0, 10);
utils.dispatchTouchEvent(child, 'end', 0, 10);
});

View File

@@ -0,0 +1,118 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils,Simulator */
var el;
var hammer;
var pressPeriod = 200;
var pressThreshold = 20;
var pressCount = 0;
var panStartCount = 0;
var swipeCount = 0;
QUnit.module('Require Failure ( Swipe & Press )', {
beforeEach: function() {
el = utils.createHitArea();
hammer = new Hammer(el, { recognizers: [] });
var swipe = new Hammer.Swipe({ threshold: 1 });
var press = new Hammer.Press({ time: pressPeriod, threshold: pressThreshold });
hammer.add(swipe);
hammer.add(press);
swipe.recognizeWith(press);
press.requireFailure(swipe);
pressCount = 0;
swipeCount = 0;
hammer.on('press', function() {
pressCount++;
});
hammer.on('swipe', function() {
swipeCount++;
});
},
afterEach: function() {
hammer.destroy();
}
});
QUnit.test('When swipe does not recognize the gesture, a press gesture can be fired', function(assert) {
var done = assert.async();
assert.expect(1);
utils.dispatchTouchEvent(el, 'start', 50, 50);
setTimeout(function() {
assert.equal(pressCount, 1, '1 press recognized');
done();
}, pressPeriod + 100);
});
QUnit.test('When swipe does recognize the gesture, a press gesture cannot be fired', function(assert) {
var done = assert.async();
assert.expect(2);
Simulator.gestures.swipe(el, null, function() {
assert.ok(swipeCount > 0, 'swipe gesture should be recognizing');
assert.equal(pressCount, 0, 'press gesture should not be recognized because swipe gesture is recognizing');
done();
});
});
QUnit.module('Require Failure ( Pan & Press )', {
beforeEach: function() {
el = document.createElement('div');
document.body.appendChild(el);
hammer = new Hammer(el, { recognizers: [] });
var pan = new Hammer.Pan({ threshold: 1 });
var press = new Hammer.Press({ time: pressPeriod, threshold: pressThreshold });
hammer.add([ pan, press ]);
pan.recognizeWith(press);
press.requireFailure(pan);
pressCount = 0;
panStartCount = 0;
hammer.on('press', function() {
pressCount++;
});
hammer.on('panstart', function() {
panStartCount++;
});
},
afterEach: function() {
document.body.removeChild(el);
hammer.destroy();
}
});
QUnit.test('When pan does not recognize the gesture, a press gesture can be fired', function(assert) {
var done = assert.async();
assert.expect(1);
utils.dispatchTouchEvent(el, 'start', 50, 50);
setTimeout(function() {
assert.equal(pressCount, 1, '1 press recognized');
done();
}, pressPeriod + 100);
});
QUnit.test('When pan recognizes the gesture, a press gesture cannot be fired', function(assert) {
var done = assert.async();
assert.expect(2);
utils.dispatchTouchEvent(el, 'start', 50, 50);
utils.dispatchTouchEvent(el, 'move', 50 + pressThreshold / 4, 50);
setTimeout(function() {
assert.ok(panStartCount > 0, 'pan gesture should be recognizing');
assert.equal(pressCount, 0, 'press gesture should not be recognized because pan gesture is recognizing');
done();
}, pressPeriod + 100);
});

View File

@@ -0,0 +1,238 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils*/
var el;
var hammer;
QUnit.module('Simultaenous recognition', {
beforeEach: function() {
el = utils.createHitArea();
},
afterEach: function() {
hammer && hammer.destroy();
}
});
QUnit.test('should pinch and pan simultaneously be recognized when enabled', function(assert) {
var done = assert.async();
assert.expect(4);
var panCount = 0;
var pinchCount = 0;
hammer = new Hammer.Manager(el, {
touchAction: 'none'
});
hammer.add(new Hammer.Pan({ threshold: 5, pointers: 2 }));
var pinch = new Hammer.Pinch({ threshold: 0, pointers: 2 });
hammer.add(pinch);
pinch.recognizeWith(hammer.get('pan'));
hammer.on('panend', function() {
panCount++;
});
hammer.on('pinchend', function() {
pinchCount++;
});
var executeGesture = function(cb) {
var event, touches;
touches = [
{ clientX: 0, clientY: 10, identifier: 0, target: el },
{ clientX: 10, clientY: 10, identifier: 1, target: el }
];
event = document.createEvent('Event');
event.initEvent('touchstart', true, true);
event.touches = touches;
event.targetTouches = touches;
event.changedTouches = touches;
el.dispatchEvent(event);
setTimeout(function() {
touches = [
{ clientX: 10, clientY: 20, identifier: 0, target: el },
{ clientX: 20, clientY: 20, identifier: 1, target: el }
];
event = document.createEvent('Event');
event.initEvent('touchmove', true, true);
event.touches = touches;
event.targetTouches = touches;
event.changedTouches = touches;
el.dispatchEvent(event);
}, 100);
setTimeout(function() {
touches = [
{ clientX: 20, clientY: 30, identifier: 0, target: el },
{ clientX: 40, clientY: 30, identifier: 1, target: el }
];
event = document.createEvent('Event');
event.initEvent('touchmove', true, true);
event.touches = touches;
event.targetTouches = touches;
event.changedTouches = touches;
el.dispatchEvent(event);
event = document.createEvent('Event');
event.initEvent('touchend', true, true);
event.touches = touches;
event.targetTouches = touches;
event.changedTouches = touches;
el.dispatchEvent(event);
cb();
}, 200);
};
// 2 gesture will be recognized
executeGesture(function() {
assert.equal(panCount, 1, '1 pan event recognized');
assert.equal(pinchCount, 1, '1 pinch event recognized');
pinch.dropRecognizeWith(hammer.get('pan'));
// Only the pan gesture will be recognized
executeGesture(function() {
assert.equal(panCount, 2, '2 pan events recognized');
assert.equal(pinchCount, 1, 'One pinch event recognized');
done();
});
});
});
QUnit.test('the first gesture should block the following gestures (Tap & DoubleTap)', function(assert) {
assert.expect(4);
var tapCount = 0;
var doubleTapCount = 0;
hammer = new Hammer.Manager(el, {
touchAction: 'none'
});
var tap = new Hammer.Tap();
var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 });
hammer.add(tap);
hammer.add(doubleTap);
hammer.on('tap', function() {
tapCount++;
});
hammer.on('doubletap', function() {
doubleTapCount++;
});
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
assert.equal(tapCount, 2, 'on a double tap gesture, the tap gesture is recognized twice');
assert.equal(doubleTapCount, 0, 'double tap gesture is not recognized because the prior tap gesture does not recognize it simultaneously');
doubleTap.recognizeWith(hammer.get('tap'));
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
assert.equal(tapCount, 4, '4 tap events recognized');
assert.equal(doubleTapCount, 1, 'when the tap gesture is configured to work simultaneously, tap & doubleTap can be recognized simultaneously');
});
QUnit.test('when disabled, the first gesture should not block gestures (Tap & DoubleTap )', function(assert) {
assert.expect(4);
var tapCount = 0;
var doubleTapCount = 0;
hammer = new Hammer.Manager(el, {
touchAction: 'none'
});
var tap = new Hammer.Tap();
var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 });
hammer.add(tap);
hammer.add(doubleTap);
hammer.on('tap', function() {
tapCount++;
});
hammer.on('doubletap', function() {
doubleTapCount++;
});
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
assert.equal(tapCount, 2, 'on a double tap gesture, the tap gesture is recognized twice');
assert.equal(doubleTapCount, 0, 'double tap gesture is not recognized because the prior tap gesture does not recognize it simultaneously');
hammer.get('tap').set({ enable: false });
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
assert.equal(tapCount, 2, 'tap gesture should not be recognized when the recognizer is disabled');
assert.equal(doubleTapCount, 1, 'when the tap gesture is disabled, doubleTap can be recognized');
});
QUnit.test('the first gesture should block the following gestures (DoubleTap & Tap)', function(assert) {
assert.expect(4);
var tapCount = 0;
var doubleTapCount = 0;
hammer = new Hammer.Manager(el, {
touchAction: 'none'
});
var tap = new Hammer.Tap();
var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 });
hammer.add(doubleTap);
hammer.add(tap);
hammer.on('tap', function() {
tapCount++;
});
hammer.on('doubletap', function() {
doubleTapCount++;
});
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
assert.equal(doubleTapCount, 1, 'double tap is recognized');
assert.equal(tapCount, 1, 'tap is detected, the doubletap is only catched by the doubletap recognizer');
// Doubletap and tap together
doubleTap.recognizeWith(hammer.get('tap'));
doubleTapCount = 0;
tapCount = 0;
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
utils.dispatchTouchEvent(el, 'start', 0, 10);
utils.dispatchTouchEvent(el, 'end', 0, 10);
assert.equal(doubleTapCount, 1, '1 double tap recognized');
assert.equal(tapCount, 2, 'when the tap gesture is configured to work simultaneously, tap & doubleTap can be recognized simultaneously');
});

View File

@@ -0,0 +1,167 @@
// jscs:disable requireArrowFunctions,disallowVar,requireEnhancedObjectLiterals
/* globals QUnit,Hammer,utils,_*/
QUnit.module('utils');
// For the tests, all hammer properties and methods of Hammer are exposed to window.Hammer
QUnit.test('get/set prefixed util', function(assert) {
assert.ok(_.isUndefined(Hammer.prefixed(window, 'FakeProperty')), 'non existent property returns undefined');
window.webkitFakeProperty = 1337;
assert.ok(Hammer.prefixed(window, 'FakeProperty') == 'webkitFakeProperty', 'existent prefixed property returns the prefixed name');
delete window.webkitFakeProperty;
});
QUnit.test('fnBind', function(assert) {
var context = { a: true };
Hammer.bindFn(function(b) {
assert.ok(this.a === true, 'bindFn scope');
assert.ok(b === 123, 'bindFn argument');
}, context)(123);
});
QUnit.test('Inherit objects', function(assert) {
function Base() {
this.name = true;
}
function Child() {
Base.call(this);
}
Hammer.inherit(Child, Base, {
newMethod: function() {
}
});
var inst = new Child();
assert.ok(inst.name == true, 'child has extended from base');
assert.ok(inst.newMethod, 'child has a new method');
assert.ok(Child.prototype.newMethod, 'child has a new prototype method');
assert.ok(inst instanceof Child, 'is instanceof Child');
assert.ok(inst instanceof Base, 'is instanceof Base');
assert.ok(inst._super === Base.prototype, '_super is ref to prototype of Base');
});
QUnit.test('toArray', function(assert) {
assert.ok(_.isArray(Hammer.toArray({ 0: true, 1: 'second', length: 2 })), 'converted an array-like object to an array');
assert.ok(_.isArray(Hammer.toArray([ true, true ])), 'array stays an array');
});
QUnit.test('inArray', function(assert) {
assert.ok(Hammer.inArray([ 1, 2, 3, 4, 'hammer' ], 'hammer') === 4, 'found item and returned the index');
assert.ok(Hammer.inArray([ 1, 2, 3, 4, 'hammer' ], 'notfound') === -1, 'not found an item and returned -1');
assert.ok(Hammer.inArray([
{ id: 2 },
{ id: 24 }
], '24', 'id') === 1, 'find by key and return the index');
assert.ok(Hammer.inArray([
{ id: 2 },
{ id: 24 }
], '22', 'id') === -1, 'not found by key and return -1');
});
QUnit.test('splitStr', function(assert) {
assert.deepEqual(Hammer.splitStr(' a b c d '), [ 'a', 'b', 'c', 'd' ], 'str split valid');
});
QUnit.test('uniqueArray', function(assert) {
assert.deepEqual(Hammer.uniqueArray([
{ id: 1 },
{ id: 2 },
{ id: 2 }
], 'id'), [
{ id: 1 },
{ id: 2 }
], 'remove duplicate ids');
});
QUnit.test('boolOrFn', function(assert) {
assert.equal(Hammer.boolOrFn(true), true, 'Passing an boolean');
assert.equal(Hammer.boolOrFn(false), false, 'Passing an boolean');
assert.equal(Hammer.boolOrFn(function() {
return true;
}), true, 'Passing an boolean');
assert.equal(Hammer.boolOrFn(1), true, 'Passing an integer');
});
QUnit.test('hasParent', function(assert) {
var parent = document.createElement('div');
var child = document.createElement('div');
document.body.appendChild(parent);
parent.appendChild(child);
assert.equal(Hammer.hasParent(child, parent), true, 'Found parent');
assert.equal(Hammer.hasParent(parent, child), false, 'Not in parent');
document.body.removeChild(parent);
});
QUnit.test('each', function(assert) {
var object = { hi: true };
var array = [ 'a', 'b', 'c' ];
var loop;
loop = false;
Hammer.each(object, function(value, key) {
if (key == 'hi' && value === true) {
loop = true;
}
});
assert.ok(loop, 'object loop');
loop = 0;
Hammer.each(array, function(value) {
if (value) {
loop++;
}
});
assert.ok(loop == 3, 'array loop');
loop = 0;
array.forEach = null;
Hammer.each(array, function(value) {
if (value) {
loop++;
}
});
assert.ok(loop == 3, 'array loop without Array.forEach');
});
QUnit.test('assign', function(assert) {
assert.expect(2);
assert.deepEqual(
Hammer.assign(
{ a: 1, b: 3 },
{ b: 2, c: 3 }
),
{ a: 1, b: 2, c: 3 },
'Simple extend'
);
var src = { foo: true };
var dest = Hammer.assign({}, src);
src.foo = false;
assert.deepEqual(dest, { foo: true }, 'Clone reference');
});
QUnit.test('test add/removeEventListener', function(assert) {
function handleEvent() {
assert.ok(true, 'triggered event');
}
assert.expect(2);
Hammer.addEventListeners(window, 'testEvent1 testEvent2 ', handleEvent);
utils.triggerDomEvent(window, 'testEvent1');
utils.triggerDomEvent(window, 'testEvent2');
Hammer.removeEventListeners(window, ' testEvent1 testEvent2 ', handleEvent);
utils.triggerDomEvent(window, 'testEvent1');
utils.triggerDomEvent(window, 'testEvent2');
});