single-touch.html (15808B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 Test cases for Touch Events v1 Recommendation 5 http://www.w3.org/TR/touch-events/ 6 7 These tests are based on Matt Bruebeck's single-touch tests. 8 There are NO multi-touch tests in this document. 9 10 This document references Test Assertions (abbrev TA below) written by Cathy Chan 11 http://www.w3.org/2010/webevents/wiki/TestAssertions 12 --> 13 14 <head> 15 <title>Touch Events Single Touch Tests</title> 16 <meta name="viewport" content="width=device-width"> 17 <script src="/resources/testharness.js"></script> 18 <script src="/resources/testharnessreport.js"></script> 19 <script src="/resources/testdriver.js"></script> 20 <script src="/resources/testdriver-actions.js"></script> 21 <script src="/resources/testdriver-vendor.js"></script> 22 <script src="support/touch.js"></script> 23 24 25 <script> 26 // Check a Touch object's attributes for existence and correct type 27 // TA: 1.1.2, 1.1.3 28 function check_Touch_object (t, element) { 29 test(function() { 30 assert_equals(Object.prototype.toString.call(t), "[object Touch]", 31 `${name} attribute of type TouchList`); 32 }, `${element}'s touch point is a Touch object`); 33 [ 34 ["long", "identifier"], 35 ["EventTarget", "target"], 36 ["long", "screenX"], 37 ["long", "screenY"], 38 ["long", "clientX"], 39 ["long", "clientY"], 40 ["long", "pageX"], 41 ["long", "pageY"], 42 ].forEach(function(attr) { 43 var type = attr[0]; 44 var name = attr[1]; 45 46 // existence check 47 test(function() { 48 assert_true(name in t, `${name} attribute in Touch object`); 49 }, `${element}.Touch.${name} attribute exists check_Touch_object`); 50 51 // type check 52 switch(type) { 53 case "long": 54 test(function() { 55 assert_equals(typeof t[name], "number", 56 `${name} attribute of type long`); 57 }, `${element}.Touch.${name} attribute is of type number (long)`); 58 break; 59 60 case "EventTarget": 61 // An event target is some type of Element 62 test(function() { 63 assert_true(t[name] instanceof Element, 64 "EventTarget must be an Element."); 65 }, `${element}.Touch.${name} attribute is of type Element`); 66 break; 67 68 default: 69 break; 70 } 71 }); 72 } 73 74 // Check a TouchList object's attributes and methods for existence and 75 // proper type. 76 // Also make sure all of the members of the list are Touch objects 77 // TA: 1.2.1, 1.2.2, 1.2.5 78 function check_TouchList_object (tl, element) { 79 const context = "check_touchList_object"; 80 const ulong = "number (unsigned long)"; 81 test(function() { 82 assert_equals(Object.prototype.toString.call(tl), 83 "[object TouchList]", 84 `${name} attribute of type TouchList`); 85 }, `${element}'s touch list is a TouchList object`); 86 [ 87 ["unsigned long", "length"], 88 ["function", "item"], 89 ].forEach(function(attr) { 90 var type = attr[0]; 91 var name = attr[1]; 92 93 // existence check 94 test(function() { 95 assert_true(name in tl, `${name} attribute in TouchList`); 96 }, `${element}.TouchList.${name} attribute exists ${context}`); 97 98 // type check 99 switch(type) { 100 case "unsigned long": 101 test(function() { 102 assert_equals(typeof tl[name], "number", 103 `${name} attribute of type long`); 104 }, `${element}.TouchList.${name} attribute is of type ${ulong}`); 105 break; 106 107 case "function": 108 test(function() { 109 assert_equals(typeof tl[name], "function", 110 `${name} attribute of type function`); 111 }, `${element}.TouchList.${name} attribute is of type function`); 112 break; 113 114 default: 115 break; 116 } 117 }); 118 119 // Each member of tl should be a proper Touch object 120 for (var i=0; i < tl.length; i++) { 121 check_Touch_object(tl.item(i), `${element}[${i}]`); 122 } 123 } 124 125 // Check a TouchEvent event's attributes for existence and proper type 126 // Also check that each of the event's TouchList objects are valid 127 // TA: 1.{3,4,5}.1.1, 1.{3,4,5}.1.2 128 function check_TouchEvent(ev) { 129 test(function() { 130 assert_true(ev instanceof TouchEvent, "event is a TouchEvent event"); 131 }, `${ev.type} event is a TouchEvent event`); 132 [ 133 ["TouchList", "touches"], 134 ["TouchList", "targetTouches"], 135 ["TouchList", "changedTouches"], 136 ["boolean", "altKey"], 137 ["boolean", "metaKey"], 138 ["boolean", "ctrlKey"], 139 ["boolean", "shiftKey"], 140 ].forEach(function(attr) { 141 var type = attr[0]; 142 var name = attr[1]; 143 144 // existence check 145 test(function() { 146 assert_true(name in ev, 147 `${name} attribute in ${ev.type} event`); 148 }, `${ev.type}.${name} attribute exists check_TouchEvent`); 149 150 // type check 151 switch(type) { 152 case "boolean": 153 test(function() { 154 assert_equals( 155 typeof ev[name], "boolean", 156 `${name} attribute of type boolean`); 157 }, `${ev.type}.${name} attribute is of type boolean`); 158 break; 159 160 case "TouchList": 161 test(function() { 162 assert_equals( 163 Object.prototype.toString.call(ev[name]), 164 "[object TouchList]", 165 `${name} attribute of type TouchList`); 166 }, `${ev.type}.${name} attribute is of type TouchList`); 167 // Now check the validity of the TouchList 168 check_TouchList_object(ev[name], `${ev.type}.${name}`); 169 break; 170 171 default: 172 break; 173 } 174 }); 175 } 176 177 function is_touch_over_element(touch, element) { 178 var bounds = element.getBoundingClientRect(); 179 return touch.pageX >= bounds.left && touch.pageX <= bounds.right && 180 touch.pageY >= bounds.top && touch.pageY <= bounds.bottom; 181 } 182 183 function check_touch_clientXY(touch) { 184 assert_equals(touch.clientX, touch.pageX - window.pageXOffset, 185 "touch.clientX is touch.pageX - window.pageXOffset."); 186 assert_equals(touch.clientY, touch.pageY - window.pageYOffset, 187 "touch.clientY is touch.pageY - window.pageYOffset."); 188 } 189 190 function check_screenXY_clientXY_pageXY(touch) { 191 assert_greater_than_equal(touch.screenX, 0, 192 "touch.screenX is no less than 0"); 193 assert_greater_than_equal(touch.screenY, 0, 194 "touch.screenY is no less than 0"); 195 assert_greater_than_equal(touch.clientX, 0, 196 "touch.clientX is no less than 0"); 197 assert_greater_than_equal(touch.clientY, 0, 198 "touch.clientY is no less than 0"); 199 assert_greater_than_equal(touch.pageX, 0, 200 "touch.pageX is no less than 0"); 201 assert_greater_than_equal(touch.pageY, 0, 202 "touch.pageY is no less than 0"); 203 } 204 205 function validateTouchstart(ev) { 206 check_TouchEvent(ev); 207 208 // TA: 1.3.2.1, 1.3.3.1, 1.3.4.1 209 test(function() { 210 assert_equals(ev.touches.length, 1, "One touch point."); 211 assert_equals(ev.changedTouches.length, 1, 212 "One changed touch point."); 213 assert_equals(ev.targetTouches.length, 1, "One target touch point."); 214 }, "touchstart: all TouchList lengths are correct"); 215 216 var t = ev.touches[0]; 217 var ct = ev.changedTouches[0]; 218 var tt = ev.targetTouches[0]; 219 220 touchstart_identifier = t.identifier; 221 // TA: 1.3.3.3, 1.3.2.3, 1.3.3.4 (indirect (transitive)) 222 test(function() { 223 assert_equals(ct.identifier, touchstart_identifier, 224 "changedTouches identifier matches."); 225 assert_equals(tt.identifier, touchstart_identifier, 226 "targetTouches identifier matches."); 227 }, "touchstart: all TouchList identifiers are consistent"); 228 229 // TA: 1.3.3.9 230 test(function() { 231 assert_equals(tt.target, ev.target, 232 "event target same as targetTouches target."); 233 }, "touchstart: event target same as targetTouches target"); 234 235 test(function() { 236 assert_true(is_touch_over_element(t, target0), 237 "touch.pageX/pageY is over target0."); 238 }, "touchstart: touch pageX/pageY inside of target element"); 239 test(function() { 240 check_touch_clientXY(t); 241 }, "touchstart: touch clientX/clientY is consistent with pageX/pageY"); 242 test(function() { 243 check_screenXY_clientXY_pageXY(t); 244 }, "touchstart: touch screenX/screenY pageX/pageY and " + 245 "clientX/clientY values are no less than 0"); 246 } 247 248 function validateTouchmove(ev) { 249 check_TouchEvent(ev); 250 251 // TA: 1.4.2.1, 1.4.3.1 252 test(function() { 253 assert_equals(ev.touches.length, 1, "One touch point."); 254 assert_equals(ev.changedTouches.length, 1, "One changed touch point."); 255 assert_equals(ev.targetTouches.length, 1, "One target touch point."); 256 }, "touchmove: all TouchList lengths are correct"); 257 258 // 1.4.2.3, 1.4.3.3, 1.4.3.5, 1.4.4.3 259 test(function() { 260 assert_equals(ev.touches[0].identifier, touchstart_identifier, 261 "Touch identifier matches."); 262 assert_equals(ev.changedTouches[0].identifier, touchstart_identifier, 263 "Changed touch identifier matches."); 264 assert_equals(ev.targetTouches[0].identifier, touchstart_identifier, 265 "Target touch identifier matches."); 266 }, "touchmove: all TouchList identifiers matches touchstart identifier"); 267 268 // TA: 1.4.3.8 269 var tt = ev.targetTouches[0]; 270 test(function() { 271 assert_equals(tt.target, ev.target, 272 "event target same as targetTouches target."); 273 }, "touchmove: event target same as targetTouches target"); 274 275 test(function() { 276 assert_true(is_touch_over_element(tt, target0) || 277 is_touch_over_element(tt, target1), 278 "touch.pageX/pageY is over one of the targets."); 279 }, "touchmove: touch pageX/pageY inside of one of the target elements"); 280 281 test(function() { 282 check_touch_clientXY(tt); 283 }, "touchmove: touch clientX/clientY is consistent with pageX/pageY"); 284 285 test(function() { 286 check_screenXY_clientXY_pageXY(tt); 287 }, "touchmove: touch screenX/screenY pageX/pageY and clientX/clientY " + 288 "values are no less than 0"); 289 } 290 291 function validateTouchend(ev) { 292 check_TouchEvent(ev); 293 294 // TA: 1.5.1.2, 1.5.3.1, 1.5.4.1 295 test(function() { 296 assert_equals(ev.touches.length, 0, "Zero touch points."); 297 assert_equals(ev.changedTouches.length, 1, "One changed touch point."); 298 assert_equals(ev.targetTouches.length, 0, "Zero target touch points."); 299 }, "touchend: all TouchList lengths are correct"); 300 301 var t = ev.changedTouches[0]; 302 303 // TA: 1.5.2.6, 1.5.2.3 304 test(function() { 305 assert_equals(t.identifier, touchstart_identifier, 306 "changedTouches identifier matches."); 307 }, "touchend: touches identifier matches changedTouches identifier"); 308 309 test(function() { 310 assert_true(is_touch_over_element(t, target1), 311 "touch.pageX/pageY is over target1."); 312 }, "touchend: touch pageX/pageY inside expected element"); 313 314 test(function() { 315 check_touch_clientXY(t); 316 }, "touchend: touch clientX/clientY is consistent with pageX/pageY"); 317 318 test(function() { 319 check_screenXY_clientXY_pageXY(t); 320 }, "touchend: touch screenX/screenY pageX/pageY and clientX/clientY " + 321 "values are no less than 0"); 322 } 323 324 function createEventPromise(eventType) { 325 const target0 = document.getElementById("target0"); 326 const doneButton = document.getElementById('done'); 327 return new Promise((resolve, reject) => { 328 doneButton.addEventListener('click', reject, { once: true }); 329 target0.addEventListener(eventType, event => { 330 doneButton.removeEventListener('click', reject); 331 resolve(event); 332 }, { once: true }); 333 }); 334 } 335 336 async function run() { 337 await waitTillReadyForTouchInput(); 338 339 const target0 = document.getElementById("target0"); 340 const target1 = document.getElementById("target1"); 341 const doneButton = document.getElementById('done'); 342 343 promise_test(async t => { 344 const events = []; 345 346 const recordTouchEvents = (target) => { 347 const listener = event => { 348 events.push(`${target.id}:${event.type}`); 349 }; 350 target.addEventListener('touchstart', listener); 351 target.addEventListener('touchmove', listener); 352 target.addEventListener('touchend', listener); 353 }; 354 recordTouchEvents(target0); 355 recordTouchEvents(target1); 356 357 const touchstartPromise = createEventPromise('touchstart'); 358 const touchmovePromise = createEventPromise('touchmove'); 359 const touchendPromise = createEventPromise('touchend'); 360 361 await new test_driver.Actions() 362 .addPointer("touchPointer1", "touch") 363 .pointerMove(0, 0, {origin: target0, sourceName: "touchPointer1"}) 364 .pointerDown({sourceName: "touchPointer1"}) 365 .pointerMove(0, 10, {origin: target0, sourceName: "touchPointer1"}) 366 .pointerMove(0, 0, {origin: target1, sourceName: "touchPointer1"}) 367 .pointerUp({sourceName: "touchPointer1"}) 368 .send(); 369 370 // Signal test completion. Any events not processed by the time the 371 // button is clicked will not be received. This safeguards against a 372 // timeout on platforms lacking support for touch events. 373 await new test_driver.Actions() 374 .pointerMove(0, 0, {origin: doneButton}) 375 .pointerDown() 376 .pointerUp() 377 .send(); 378 379 try { 380 const touchstartEvent = await touchstartPromise; 381 validateTouchstart(touchstartEvent); 382 } catch (e) { 383 assert_unreached('touchstart event not received'); 384 } 385 386 try { 387 const touchmoveEvent = await touchmovePromise; 388 validateTouchmove(touchmoveEvent); 389 } catch (e) { 390 assert_unreached('touchmove event not received'); 391 } 392 393 try { 394 const touchendEvent = await touchendPromise; 395 validateTouchend(touchendEvent); 396 } catch (e) { 397 assert_unreached('touchend event not received'); 398 } 399 400 // Check event ordering TA: 1.6.2 401 assert_array_equals( 402 events, 403 ["target0:touchstart", "target0:touchmove", "target0:touchend"], 404 "unexpected event ordering"); 405 406 }, 'Verify touch events for a single touch drag operation'); 407 } 408 </script> 409 <style> 410 * { 411 touch-action: none; 412 } 413 div { 414 margin: 0em; 415 padding: 2em; 416 } 417 #target0 { 418 background: yellow; 419 border: 1px solid orange; 420 } 421 #target1 { 422 background: lightblue; 423 border: 1px solid blue; 424 } 425 </style> 426 </head> 427 <body onload="run()"> 428 <h1>Touch Events: single-touch tests</h1> 429 <div id="target0"> 430 Touch this box with one finger (or other pointing device)... 431 </div> 432 <div id="target1"> 433 ...then drag to this box and lift your finger. Then tap on Done. 434 </div> 435 <button id="done">Done</button> 436 <div id="log"></div> 437 </body> 438 </html>