diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..b834f866 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,50 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es2021": true + }, + "parserOptions": { + "ecmaVersion": 12 + }, + "rules": { + "curly": 2, + "eqeqeq": ["error", "smart"], + "no-extend-native": 2, + "indent": 0, + "no-use-before-define": ["error", "nofunc"], + "no-caller": 2, + "no-undef": 2, + "no-unused-vars": ["error", { "vars": "all", "args": "none" }], + "comma-dangle": 2, + "max-depth": ["error", 4], + "no-trailing-spaces": 2, + "no-loop-func": 2, + "semi": 2 + }, + "globals": { + "steal": true, + "can": true, + + "Zepto": true, + + "QUnit": true, + "test": true, + "asyncTest": true, + "expect": true, + "module": true, + "ok": true, + "equal": true, + "notEqual": true, + "deepEqual": true, + "notDeepEqual": true, + "strictEqual": true, + "notStrictEqual": true, + "raises": true, + "start": true, + "stop": true, + + "formElExp": true, + "KeyboardEvent": true + } +} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index ca1763ad..00000000 --- a/.jshintrc +++ /dev/null @@ -1,58 +0,0 @@ -{ - "globals": { - - "steal": true, - "can": true, - - - "Zepto": true, - - - "QUnit": true, - "test": true, - "asyncTest": true, - "expect": true, - "module": true, - "ok": true, - "equal": true, - "notEqual": true, - "deepEqual": true, - "notDeepEqual": true, - "strictEqual": true, - "notStrictEqual": true, - "raises": true, - "start": true, - "stop": true, - - "formElExp": true, - "KeyboardEvent": true - - }, - - - "curly": true, - "eqeqeq": true, - "freeze": true, - "indent": 2, - "latedef": true, - "noarg": true, - "undef": true, - "unused": "vars", - "trailing": true, - "maxdepth": 4, - "boss" : true, - - "eqnull": true, - "evil": true, - "loopfunc": true, - "smarttabs": true, - "maxerr" : 200, - - "jquery": true, - "dojo": true, - "mootools": true, - "yui": true, - "browser": true, - "phantom": true, - "rhino": true -} diff --git a/.npmignore b/.npmignore index 31cd5f64..fb8c4303 100644 --- a/.npmignore +++ b/.npmignore @@ -5,7 +5,7 @@ utils/ Gruntfile.js .gitignore .jsbeautifyrc -.jshintrc +.eslintrc.json .travis.yml .editorconfig !dist/ diff --git a/.travis.yml b/.travis.yml index dae72feb..2bd43f93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - 8 + - 10 addons: firefox: "63.0" dist: xenial diff --git a/README.md b/README.md index 5c83fcc2..790b7660 100644 --- a/README.md +++ b/README.md @@ -11,105 +11,207 @@ For questions or discussion, check out our [forums](https://forums.donejs.com/c/ > If you are looking for the Synergy project, you can find it [here](https://www.npmjs.com/~postquagga). Thanks > [postquagga](https://www.npmjs.com/~postquagga) for letting us use the name! -Syn lets you simulate user behavior like typing, clicking, moving, and -dragging exactly how a real user would perform those actions. +`syn` lets you simulate user behavior like typing, clicking, moving, and +dragging exactly how a real user would perform those actions. It can be easily dropped into +existing ```js -Syn.click( 'hello' ) - .type( 'Hello World' ) - .drag( 'trash' ); +it("does something when you click and type and drag", async function(){ + + await syn.click( '#hello' ); + await syn.type( '#hello', 'Hello World' ); + await syn.drag( '#hello', '#trash' ) + + // check the results +}); ``` ## Install Install Syn via the command line with npm: - > npm install syn - -or bower: - - > bower install syn - -Or by downloading it [here](https://github.com/bitovi/syn/archive/v0.2.0.zip). +```shell +npm install syn +``` -Inside the download, npm package and bower package, there are the following folders: +Inside the download and npm package there are the following folders: - `dist/global/syn.js` - A standalone file that can be used with a ` -### AMD / RequireJS +_PATH/TO_ should be the path to the installed syn folder. -Add the following package configuration: +## Use - require.config({ - packages: [{ - name: 'syn', - location: 'PATH/TO/syn/dist/amd', - main: 'syn' - }] - }); +`syn` has 6 core user behavior actions that +simulate most of how a user interacts with a page. -_PATH/TO_ should be the path from your baseUrl to the location of the syn folder. Once this is -properly configured, you should be able to write: +`syn` also has the ability to configure event behavior. This can be useful if you need to simulate something that +syn does not currently support. - define(['syn'], function(syn){ - syn.click(document.getElementById('hello')); - }); +### Core user behavior actions -### Script Tag / Standalone +The core user behavior actions all: -If you don't use a module loader, you can simply add the following to your page: +- take an element (either has a CSS selector ) +- take optional options that can configure the behavior of the +- return a promise - +These methods are all intended to work within an +[async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) and use `await` as follows: -_PATH/TO_ should be the path to the installed syn folder. +```js +it("your test function", async function(){ + + await syn.click( '#hello' ); + await syn.rightClick( '#hello' ); + await syn.dblClick( '#hello' ); + await syn.type( '#hello', 'Hello World' ); + await syn.key( '#hello', '!' ); + await syn.drag( '#hello', '#trash' ); + await syn.move( '#trash', '#hello' ); + // check the results +}); +``` -## Use +#### syn.click( element [, options] ) +Click an element. This will properly dispatch all the mouse, pointer and focus events +and default behavior that a user would create when "clicking" on an element. +Examples: +```js +// clicks the element with 'big-button' as its ID +await syn.click( "#big-button" ) -### syn.click( element [, options][, callback] ) +// does the same thing as the previous example, but passes an +// element directly. +await syn.click( document.querySelector('#big-button') ) +``` -### syn.dblclick( element [, options][, callback] ) +Parameters: -### syn.type( element, text [, callback] ) +- __element__ `HTMLElement|CSSSelector` An element to click or a CSS selector used to find the element to click. +- __options__ `Object` Options used to extend the event objects that are dispatched. -### syn.key( element, key [, callback] ) +Returns: -### syn.delay( time=600 ) +- `Promise` A promise that resolves to `false` if `event.preventDefault()` was called, returns `true` if otherwise. -### syn.drag( element, optionsOrTarget [, callback]) +#### syn.rightClick( element [, options] ) +#### syn.dblclick( element [, options] ) +#### syn.type( element, text ) -## Contributing +#### syn.key( element, key ) -Check out the [contribution guide](CONTRIBUTING.md). +#### syn.drag( element, optionsOrTarget ) + +#### syn.move( element, optionsOrTarget ) + +Moves a pointer from one position to another. +Note that this will will dispatch all the move, enter, and leave events +one might expect from a mouse moving over the page. + +Examples: + +```js +// Move from the center of the '#from' element to the center of the '#to' element +await syn.move("#from", "#to"); + +// Move from the center of the '#from' element to the center of the '#to' element +// in one second +await syn.move( document,{ + from: "#from", + to: "#to", + duration: 1000 +}); + +// Move from the top-left corner of the page to 100px down and 100px to the right +await syn.move( document, { + from: "0x0", + to: "100x100" +}); + +// Same as above, but a longer syntax +await syn.move( document, { + from: {pageX: 0, pageY: 0}, + to: {pageX: 100, pageY: 100} +}); + +// Move from the top-left corner of the screen to 100px down and 100px to the right +await syn.move( document, { + from: {clientX: 0, clientY: 0}, + to: {clientX: 100, clientY: 100} +}); + +// Move from the center of '#from' right 20 pixels and up 30 pixels +await syn.move("#from", "+20 +30"); +``` + +Parameters: + +- __element__ `HTMLElement|CSSSelector` If `optionsOrTarget.from` is not specified, the move will + use the center of this element as the starting point. An element reference + _MUST_ be provided so syn knows how to dispatch the events correctly. + The `document` can be provided in this case. +- __optionsOrTarget__ `HTMLElement|String|Object` If an HTMLElement or a String is + provided, the move will use the center of the specified element as the endpoint of + the move. Otherwise, an object with the following values can be provided: + + ```js + { + // Specifies where the move should start at + // Element - start at the geometric center + // CSSSelector - find this element in the page, start at the geometric center + // PagePosition - where in the page move will start. Ex: "300x300" + // ClientPosition - where in the window move will start. Ex: "300X300" + // RelativePosition - where to start relative to the element. Ex: "+300 -20" + from: Element|CSSSelector|PagePosition|ClientPosition|RelativePosition, -## Relevant Links + // Specifies where the move should start at + // Element - start at the geometric center + // CSSSelector - find this element in the page, start at the geometric center + // PagePosition - where in the page move will start. Ex: `"300x300"` + // ClientPosition - where in the window move will start. Ex: `"300X300"` + // RelativePosition - where to start relative to the element. Ex: `"+300 -20"` + to: Element|CSSSelector|PagePosition|ClientPosition|RelativePosition, -1. Blog post introducing [Syn — a Standalone Synthetic Event Library](https://www.bitovi.com/blog/syn-a-standalone-synthetic-event-library) -2. [Gitter chat](https://gitter.im/bitovi/syn) + // Number of ms the move should occur within. Ex: `100`. Defaults to 500. + duration: Integer + } + ``` + +Returns: + +- `Promise<>` Returns a promise that resolves when the move has completed. + + +### Configuration + +> This section will be + + +## Contributing + +Check out the [contribution guide](CONTRIBUTING.md). diff --git a/demo/record.js b/demo/record.js index 04cbb446..58da9c06 100644 --- a/demo/record.js +++ b/demo/record.js @@ -95,8 +95,8 @@ steal('jquery', 'can/control', 'src/syn.js', function($, Control, syn) { }, getKey: function(code) { - for(var key in syn.keycodes) { - if(syn.keycodes[key] === code) { + for(var key in syn.key.keycodes) { + if(syn.key.keycodes[key] === code) { return key; } } @@ -136,7 +136,7 @@ steal('jquery', 'can/control', 'src/syn.js', function($, Control, syn) { keyup: function(ev) { var key = h.getKey(ev.keyCode); - if(syn.key.isSpecial(ev.keyCode)) { + if(syn.key.helpers.isSpecial(ev.keyCode)) { h.showChar(key+'-up', ev.target); } @@ -180,7 +180,7 @@ steal('jquery', 'can/control', 'src/syn.js', function($, Control, syn) { } }, - lastX, + lastX, lastY, justKey = false, diff --git a/package.json b/package.json index a5d89f9a..21d13f14 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,23 @@ { "name": "syn", - "version": "0.15.0", + "version": "1.0.0-pre.3", "main": "dist/cjs/syn.js", "scripts": { "preversion": "npm test && npm run build", "postpublish": "git push --tags && git push", + "release:pre": "npm version prerelease && npm publish --tag=pre", "release:patch": "npm version patch && npm publish", "release:minor": "npm version minor && npm publish", "release:major": "npm version major && npm publish", - "jshint": "jshint src/. --config", + "eslint": "eslint src/**", "testee": "testee test/test.html --browsers firefox", - "test": "npm run jshint && npm run testee", + "test": "npm run eslint && npm run testee", "build": "node build.js" }, "devDependencies": { + "eslint": "^7.22.0", "jquery": ">=2.0.0", "jquery-ui-dist": "^1.12.1", - "jshint": "^2.8.0", "requirejs": "^2.3.3", "steal": "^2.0.0", "steal-qunit": "^1.0.1", diff --git a/src/browsers.js b/src/browsers.js index a69ae1b6..fd5b3c53 100644 --- a/src/browsers.js +++ b/src/browsers.js @@ -424,4 +424,3 @@ syn.mouse.browser = } return syn.mouse.browsers.gecko; })(); - diff --git a/src/drag.js b/src/drag.js index 804931d1..0a640fa7 100644 --- a/src/drag.js +++ b/src/drag.js @@ -1,233 +1,361 @@ var syn = require('./synthetic'); - +require('./drag.support'); /* -TODO: This is getting very complicated. We should probably separate the DRAG and MOVE abilities +TODO: This is getting very complicated. We should probably separate the DRAG and MOVE abilities into two separate actions -TODO: It might also be worth giving html5drag and jQuery drag two different code paths, +TODO: It might also be worth giving html5drag and jQuery drag two different code paths, rather than constantly checking and switching behaviors accordingly mid function -TODO: Very few of our events actually fill the bubbles and cancelable fields. Any products that - rely on these will not react properly. Is there a way to look up default behaviors for these and +TODO: Very few of our events actually fill the bubbles and cancelable fields. Any products that + rely on these will not react properly. Is there a way to look up default behaviors for these and set to those unless somehow overridden? */ +// Add high-level functions directly to syn +syn.helpers.extend(syn,{ + /** + * @function syn.move move() + * @parent mouse + * @signature `syn.move(from, options, callback)` + * Moves the cursor from one point to another. + * + * ### Quick Example + * + * The following moves the cursor from (0,0) in + * the window to (100,100) in 1 second. + * + * syn.move( + * document.document, + * { + * from: {clientX: 0, clientY: 0}, + * to: {clientX: 100, clientY: 100}, + * duration: 1000 + * }) + * + * ## Options + * + * There are many ways to configure the endpoints of the move. + * + * ### PageX and PageY + * + * If you pass pageX or pageY, these will get converted + * to client coordinates. + * + * syn.move( + * document.document, + * { + * from: {pageX: 0, pageY: 0}, + * to: {pageX: 100, pageY: 100} + * }) + * + * ### String Coordinates + * + * You can set the pageX and pageY as strings like: + * + * syn.move( + * document.document, + * { + * from: "0x0", + * to: "100x100" + * }) + * + * ### Element Coordinates + * + * If jQuery is present, you can pass an element as the from or to option + * and the coordinate will be set as the center of the element. + + * syn.move( + * document.document, + * { + * from: $(".recipe")[0], + * to: $("#trash")[0] + * }) + * + * ### Query Strings + * + * If jQuery is present, you can pass a query string as the from or to option. + * + * syn.move( + * document.document, + * { + * from: ".recipe", + * to: "#trash" + * }) + * + * ### No From + * + * If you don't provide a from, the element argument passed to syn is used. + * + * syn.move( + * 'myrecipe', + * { to: "#trash" }) + * + * ### Relative + * + * You can move the drag relative to the center of the from element. + * + * syn.move("myrecipe", "+20 +30"); + * + * @param {HTMLElement} from the element to move + * @param {Object} options options to configure the drag + * @param {Function} callback a callback that happens after the drag motion has completed + */ + move: function (from, options) { -// returns the element that exists at a provided x, y coordinate -// TODO: move this to element.js -var elementFromPoint = function (point, win) { - var clientX = point.clientX; - var clientY = point.clientY; - - if(point == null){return null;} - - if (syn.support.elementFromPage) { - var off = syn.helpers.scrollOffset(win); - clientX = clientX + off.left; //convert to pageX - clientY = clientY + off.top; //convert to pageY - } - - return win.document.elementFromPoint(Math.round(clientX), Math.round(clientY)); -}; + var win = syn.helpers.getWindow(from); + var sourceCoordinates = convertOption(options.from || from, win, from); + var destinationCoordinates = convertOption(options.to || options, win, from); + //DragonDrop.html5drag = syn.support.pointerEvents; + if (options.adjust !== false) { + adjust(sourceCoordinates, destinationCoordinates, win); + } -//====================================================================================================== -// DragonDrop prototype STARTS -//====================================================================================================== -var DragonDrop = { - - html5drag : false, - focusWindow : null, - - /** - * Performs a series of dragStart, dragEnter, dragOver and drop operations to simulate a dragDrop - * @param fromElement: element from where drag is started - * @param channel: destination element for drop - */ - dragAndDrop: function(focusWindow, fromPoint, toPoint, duration, callback){ - this.currentDataTransferItem = null; - this.focusWindow = focusWindow; - - // A series of events to simulate a drag operation - this._mouseOver(fromPoint); - this._mouseEnter(fromPoint); - this._mouseMove(fromPoint); - this._mouseDown(fromPoint); - this._dragStart(fromPoint); - this._drag(fromPoint); - this._dragEnter(fromPoint); - this._dragOver(fromPoint); - - - - - DragonDrop.startMove(fromPoint, toPoint, duration, function () { - - // this happens later - DragonDrop._dragLeave(fromPoint); - DragonDrop._dragEnd(fromPoint); - DragonDrop._mouseOut(fromPoint); - DragonDrop._mouseLeave(fromPoint); - - DragonDrop._drop(toPoint); - DragonDrop._dragEnd(toPoint); - DragonDrop._mouseOver(toPoint); - DragonDrop._mouseEnter(toPoint); - - // these are the "user" moving the mouse away after the drop - DragonDrop._mouseMove(toPoint); - DragonDrop._mouseOut(toPoint); - DragonDrop._mouseLeave(toPoint); - - - callback(); - DragonDrop.cleanup(); + return pointerMoves({ + start: sourceCoordinates, + end:destinationCoordinates, + duration: options.duration || 500, + startingState: elementFromPoint(sourceCoordinates, win), + win, + triggerPointerMove: triggerBasicPointerMove }); }, - - + /** + * @function syn.drag drag() + * @parent mouse + * @signature `syn.drag(from, options, callback)` + * Creates a mousedown and drags from one point to another. + * Check out [syn.prototype.move move] for API details. + * + * @param {HTMLElement} from + * @param {Object} options + * @param {Object} callback + */ + drag: function (from, options, callback) { - _dragStart: function(node){ - var options = { bubbles:false, cancelable:false }; - this.createAndDispatchEvent(node, 'dragstart', options); - }, - - _drag: function(node){ - var options = { bubbles:true, cancelable:true }; - this.createAndDispatchEvent(node, 'drag', options); + var win = syn.helpers.getWindow(from); + var sourceCoordinates = convertOption(options.from || from, win, from); + var destinationCoordinates = convertOption(options.to || options, win, from); + + if (options.adjust !== false) { + adjust(sourceCoordinates, destinationCoordinates, win); + } + + if(from.draggable){ + return html5DragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500); + } else{ + return pointerDragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500); + } + } +}); + +syn.helpers.extend(syn.events.types,{ + dragstart: { + options: { bubbles:false, cancelable:false }, + create: createDragEvent }, - - _dragEnter: function(node){ - var options = { bubbles:true, cancelable:true }; - this.createAndDispatchEvent(node, 'dragenter', options); + drag: { + options: { bubbles:true, cancelable:true }, + create: createDragEvent }, - - _dragOver: function(node){ - var options = { bubbles:true, cancelable:true }; - this.createAndDispatchEvent(node, 'dragover', options); + dragenter: { + options: { bubbles:true, cancelable:true }, + create: createDragEvent }, - - _dragLeave: function(node){ - var options = { bubbles:true, cancelable:false }; - this.createAndDispatchEvent(node, 'dragleave', options); + dragover: { + options: { bubbles:true, cancelable:true }, + create: createDragEvent }, - - _drop: function(node){ - var options = { bubbles:true, cancelable:true, buttons:1 }; - this.createAndDispatchEvent(node, 'drop', options); + dragleave: { + options: { bubbles:true, cancelable:false }, + create: createDragEvent }, - - _dragEnd: function(node){ - var options = { bubbles:true, cancelable:false }; - this.createAndDispatchEvent(node, 'dragend', options); + drop: { + options: { bubbles:true, cancelable:true, buttons:1 }, + create: createDragEvent }, + dragend: { + options: { bubbles:true, cancelable:false }, + create: createDragEvent + } +}); - _mouseDown: function(node, options){ this.createAndDispatchEvent(node, 'mousedown', options); }, - _mouseMove: function(node, options){ this.createAndDispatchEvent(node, 'mousemove', options); }, - _mouseEnter: function(node, options){ this.createAndDispatchEvent(node, 'mouseenter', options); }, - _mouseOver: function(node, options){ this.createAndDispatchEvent(node, 'mouseover', options); }, - _mouseOut: function(node, options){ this.createAndDispatchEvent(node, 'mouseout', options); }, - _mouseLeave: function(node, options){ this.createAndDispatchEvent(node, 'mouseleave', options); }, - - /** - * Creates and dispatches an event on the node received - * @param node - * @param eventName - */ - createAndDispatchEvent: function(point, eventName, options){ - if (point){ - var targetElement = elementFromPoint(point, this.focusWindow); - syn.trigger(targetElement, eventName, options); + +async function pointerDragAndDrop(win, fromPoint, toPoint, duration = 500) { + if(syn.support.pointerEvents){ + createEventAtPoint("pointerover", fromPoint, win); + createEventAtPoint("pointerenter", fromPoint, win); + } + createEventAtPoint("mouseover", fromPoint, win); + createEventAtPoint("mouseenter", fromPoint, win); + + if(syn.support.pointerEvents){ createEventAtPoint("pointermove", fromPoint, win); } + createEventAtPoint("mousemove", fromPoint, win); + + if(syn.support.pointerEvents){createEventAtPoint("pointerdown", fromPoint, win);} + if(syn.support.touchEvents){createEventAtPoint("touchstart", fromPoint, win);} + createEventAtPoint("mousedown", fromPoint, win); + + await pointerMoves({ + start: fromPoint, + end: toPoint, + duration, + // record the element each go-round .. + startingState: elementFromPoint(fromPoint, win), + win, + triggerPointerMove: triggerBasicPointerMove + }); + + if(syn.support.pointerEvents){ + createEventAtPoint("pointerup", toPoint, win); + } + if(syn.support.touchEvents){ + createEventAtPoint("touchend", toPoint, win); + } + createEventAtPoint("mouseup", toPoint, win); + if(syn.support.pointerEvents){ + createEventAtPoint("pointerleave", toPoint, win); + } + createEventAtPoint("mouseleave", toPoint, win); +} + +// Given a new point (and the last element we were on) +// Simulates what would happen moving the mouse to this new position. +function triggerBasicPointerMove(point, last, win){ + var el = elementFromPoint(point, win); + + if (last !== el && el && last) { + console.log("STATE CHANGE!"); + var options = syn.helpers.extend({}, point); + + // QUESTION: Should we also be sending a pointerleave event? + options.relatedTarget = el; + if(syn.support.pointerEvents){ + syn.trigger(last, 'pointerout', options); + syn.trigger(last, 'pointerleave', options); } - }, - - getDataTransferObject : function(){ - // For a series of dragOperations, we want the same dataTransfer Object to be carried over - if (!this.currentDataTransferItem){ - return this.currentDataTransferItem = this.createDataTransferObject(); - }else { - return this.currentDataTransferItem; + syn.trigger(last, "mouseout", options); + syn.trigger(last, "mouseleave", options); + + options.relatedTarget = last; + if(syn.support.pointerEvents){ + syn.trigger(el, 'pointerover', options); + syn.trigger(el, 'pointerenter', options); } - }, - + syn.trigger(el, "mouseover", options); + syn.trigger(el, "mouseenter", options); + } - // Cleanup the currentDataTransferItem object - cleanup: function(){ - this.currentDataTransferItem=null; - this.focusWindow = null; - }, - - - /** - * This function defines the dataTransfer Object, which otherwise is immutable. d= DataTrnsfer() is not a valid constructor - * @param node + if(syn.support.pointerEvents){syn.trigger(el || win, "pointermove", point);} + if(syn.support.touchEvents){syn.trigger(el || win, "touchmove", point);} + + //console.log("DRAGGED: " + DragonDrop.html5drag); + + /* + The following code needs some explanation. Firefox and Chrome DO NOT issue mousemove events during HTML5-dragdrops + However, they do issue mousemoves during jQuery-dragdrops. I am making the assumption here (which may or may not + be valid - let me know if it is wrong and I'll adjust,) that all PointerEvent-type browsers DO NOT issue + mousemoves during HTML5-dragdrop, but DO issue during jQuery. */ - createDataTransferObject: function(){ - var dataTransfer = { - dropEffect : "none", - effectAllowed : "uninitialized", - files: [], - items:[], - types:[], - data:[], - - // setData function assigns the dragValue to an object's property - setData: function(dataFlavor, value){ - var tempdata = {}; - tempdata.dataFlavor = dataFlavor; - tempdata.val = value; - this.data.push(tempdata); - }, - - // getData fetches the dragValue based on the input dataFlavor provided. - getData: function(dataFlavor){ - for (var i = 0; i < this.data.length; i++){ - var tempdata = this.data[i]; - if (tempdata.dataFlavor === dataFlavor){ - return tempdata.val; - } - } + //if(DragonDrop.html5drag){ + // if(!syn.support.pointerEvents){ syn.trigger(el || win, "mousemove", point); } + //}else{ + syn.trigger(el || win, "mousemove", point); + //} + + return el; +} + +let dragAndDropTransferObject =null; + +async function html5DragAndDrop(focusWindow, fromPoint, toPoint, duration = 500){ + dragAndDropTransferObject = createDataTransferObject(); + // A series of events to simulate a drag operation + createEventAtPoint("mouseover", fromPoint, focusWindow); + createEventAtPoint("mouseenter", fromPoint, focusWindow); + createEventAtPoint("mousemove", fromPoint, focusWindow); + createEventAtPoint("mousedown", fromPoint, focusWindow); + createEventAtPoint("dragstart", fromPoint, focusWindow); + createEventAtPoint("drag", fromPoint, focusWindow); + createEventAtPoint("dragenter", fromPoint, focusWindow); + createEventAtPoint("dragover", fromPoint, focusWindow); + + await pointerMoves({ + start: fromPoint, + end: toPoint, + duration, + startingState: fromPoint, + win: focusWindow, + triggerPointerMove: function(newPoint, previousPoint, win){ + var thisElement = elementFromPoint(newPoint, focusWindow); + var previousElement = elementFromPoint(previousPoint, focusWindow); + var options = syn.helpers.extend({}, newPoint); + + if (thisElement !== previousElement) { + + options.relatedTarget = thisElement; + createEventAtPoint("dragleave", options, focusWindow); + + options.relatedTarget = previousElement; + createEventAtPoint("dragenter", options, focusWindow); } - }; - return dataTransfer; - }, + createEventAtPoint("dragover", options, focusWindow); + return newPoint; + } + }); + + createEventAtPoint("dragleave", toPoint, focusWindow); + createEventAtPoint("dragend", toPoint, focusWindow); + createEventAtPoint("mouseout", toPoint, focusWindow); + createEventAtPoint("mouseleave", toPoint, focusWindow); + + createEventAtPoint("drop", toPoint, focusWindow); + createEventAtPoint("dragend", toPoint, focusWindow); + createEventAtPoint("mouseover", toPoint, focusWindow); + createEventAtPoint("mouseenter", toPoint, focusWindow); + + // these are the "user" moving the mouse away after the drop + createEventAtPoint("mousemove", toPoint, focusWindow); + createEventAtPoint("mouseout", toPoint, focusWindow); + createEventAtPoint("mouseleave", toPoint, focusWindow); +} - - startMove : function (start, end, duration, callback) { - +// used to move an abstract pointer (triggerPointerMove actually does dispatching) +function pointerMoves({start, end, duration, startingState, win, triggerPointerMove}) { + return new Promise( (resolve)=> { var startTime = new Date(); var distX = end.clientX - start.clientX; var distY = end.clientY - start.clientY; - var win = this.focusWindow; - var current = start; //elementFromPoint(start, win); + var currentState = startingState; var cursor = win.document.createElement('div'); var calls = 0; var move; // TODO: Does this actually do anything? - + move = function onmove() { //get what fraction we are at var now = new Date(); var scrollOffset = syn.helpers.scrollOffset(win); var fraction = (calls === 0 ? 0 : now - startTime) / duration; - var options = { + var newPoint = { clientX: distX * fraction + start.clientX, clientY: distY * fraction + start.clientY }; calls++; - + if (fraction < 1) { syn.helpers.extend(cursor.style, { - left: (options.clientX + scrollOffset.left + 2) + "px", - top: (options.clientY + scrollOffset.top + 2) + "px" + left: (newPoint.clientX + scrollOffset.left + 2) + "px", + top: (newPoint.clientY + scrollOffset.top + 2) + "px" }); - current = DragonDrop.mouseMove(options, current); - syn.schedule(onmove, 15); // TODO: What's with the 15 here? What does that even mean? Also: Should it be configurable? + currentState = triggerPointerMove(newPoint, currentState, win); + syn.helpers.schedule(onmove, 15); // TODO: What's with the 15 here? What does that even mean? Also: Should it be configurable? } else { - current = DragonDrop.mouseMove(end, current); + triggerPointerMove(end, currentState, win); win.document.body.removeChild(cursor); - callback(); + resolve(); } }; syn.helpers.extend(cursor.style, { @@ -239,67 +367,16 @@ var DragonDrop = { fontSize: "1px" }); win.document.body.appendChild(cursor); - move(); - }, - - mouseMove : function (thisPoint, previousPoint) { - var thisElement = elementFromPoint(thisPoint, this.focusWindow); - var previousElement = elementFromPoint(previousPoint, this.focusWindow); - var options = syn.helpers.extend({}, thisPoint); - - if (thisElement !== previousElement) { - - options.relatedTarget = thisElement; - this._dragLeave(previousPoint, options); - - options.relatedTarget = previousElement; - this._dragEnter(thisPoint, options); - } - this._dragOver(thisPoint, options); - return thisPoint; - } - -}; - -function createDragEvent(eventName, options, element){ - var dragEvent = syn.create.mouse.event(eventName, options, element); + move(); - dragEvent.dataTransfer = DragonDrop.getDataTransferObject(); - return syn.dispatch(dragEvent, element, eventName, false); + }); } -syn.create.dragstart = { - event : createDragEvent -}; -syn.create.dragenter = { - event : createDragEvent -}; -syn.create.dragover = { - event : createDragEvent -}; -syn.create.dragleave = { - event : createDragEvent -}; - -syn.create.drag = { - event : createDragEvent -}; - -syn.create.drop = { - event : createDragEvent -}; - -syn.create.dragend = { - event : createDragEvent -}; -//====================================================================================================== -// Drag 2.0 prototype ENDS -//====================================================================================================== @@ -307,421 +384,141 @@ syn.create.dragend = { - - -// check if elementFromPageExists -(function dragSupport() { - - // document body has to exists for this test - if (!document.body) { - syn.schedule(dragSupport, 1); - return; +//creates an event at a certain point. Note: Redundant to DragonDrop.createAndDispatchEvent +// TODO: Consolidate this with DragonDrop.createAndDispatchEvent !!! +function createEventAtPoint(event, point, win) { + var el = elementFromPoint(point, win); + syn.trigger(el || win, event, point); + return el; +} +function center(el) { + return syn.helpers.addOffset({}, el); +} +function convertOption(option, win, from) { + var page = /(\d+)[x ](\d+)/, + client = /(\d+)X(\d+)/, + relative = /([+-]\d+)[xX ]([+-]\d+)/, + parts; + //check relative "+22x-44" + if (typeof option === 'string' && relative.test(option) && from) { + var cent = center(from); + parts = option.match(relative); + option = { + pageX: cent.pageX + parseInt(parts[1]), + pageY: cent.pageY + parseInt(parts[2]) + }; } - var div = document.createElement('div'); - document.body.appendChild(div); - syn.helpers.extend(div.style, { - width: "100px", - height: "10000px", - backgroundColor: "blue", - position: "absolute", - top: "10px", - left: "0px", - zIndex: 19999 - }); - document.body.scrollTop = 11; - if (!document.elementFromPoint) { - return; + if (typeof option === "string" && page.test(option)) { + parts = option.match(page); + option = { + pageX: parseInt(parts[1]), + pageY: parseInt(parts[2]) + }; } - var el = document.elementFromPoint(3, 1); - if (el === div) { - syn.support.elementFromClient = true; - } else { - syn.support.elementFromPage = true; + if (typeof option === 'string' && client.test(option)) { + parts = option.match(client); + option = { + clientX: parseInt(parts[1]), + clientY: parseInt(parts[2]) + }; } - document.body.removeChild(div); - document.body.scrollTop = 0; -})(); - - - - + if (typeof option === 'string') { + option = win.document.querySelector( option ); + } + if (option.nodeName) { + option = center(option); + } + if (option.pageX != null) { + var off = syn.helpers.scrollOffset(win); + option = { + clientX: option.pageX - off.left, + clientY: option.pageY - off.top + }; + } + return option; +} - // creates a mousemove event, but first triggering mouseout / mouseover if appropriate - var mouseMove = function (point, win, last) { - var el = elementFromPoint(point, win); - - if (last !== el && el && last) { - var options = syn.helpers.extend({}, point); - // QUESTION: Should we also be sending a pointerleave event? - options.relatedTarget = el; - if(syn.support.pointerEvents){ - syn.trigger(last, 'pointerout', options); - syn.trigger(last, 'pointerleave', options); - } - syn.trigger(last, "mouseout", options); - syn.trigger(last, "mouseleave", options); +// if the client chords are not going to be visible ... scroll the page so they will be ... +function adjust(from, to, win) { + if (from.clientY < 0) { + var off = syn.helpers.scrollOffset(win); + var top = off.top + (from.clientY) - 100, + diff = top - off.top; - options.relatedTarget = last; - if(syn.support.pointerEvents){ - syn.trigger(el, 'pointerover', options); - syn.trigger(el, 'pointerenter', options); - } - syn.trigger(el, "mouseover", options); - syn.trigger(el, "mouseenter", options); - } + // first, lets see if we can scroll 100 px + if (top > 0) { - if(syn.support.pointerEvents){syn.trigger(el || win, "pointermove", point);} - if(syn.support.touchEvents){syn.trigger(el || win, "touchmove", point);} - - //console.log("DRAGGED: " + DragonDrop.html5drag); - - /* - The following code needs some explanation. Firefox and Chrome DO NOT issue mousemove events during HTML5-dragdrops - However, they do issue mousemoves during jQuery-dragdrops. I am making the assumption here (which may or may not - be valid - let me know if it is wrong and I'll adjust,) that all PointerEvent-type browsers DO NOT issue - mousemoves during HTML5-dragdrop, but DO issue during jQuery. - */ - if(DragonDrop.html5drag){ - if(!syn.support.pointerEvents){ syn.trigger(el || win, "mousemove", point); } - }else{ - syn.trigger(el || win, "mousemove", point); + } else { + top = 0; + diff = -off.top; } - - - return el; - }, - - - + from.clientY = from.clientY - diff; + to.clientY = to.clientY - diff; + syn.helpers.scrollOffset(win, { + top: top, + left: off.left + }); + } +}; - //creates an event at a certain point. Note: Redundant to DragonDrop.createAndDispatchEvent - // TODO: Consolidate this with DragonDrop.createAndDispatchEvent !!! - createEventAtPoint = function (event, point, win) { - var el = elementFromPoint(point, win); - syn.trigger(el || win, event, point); - return el; - }, - - - - - // start and end are in clientX, clientY - // TODO: Moves should go to a move script, not be part of the drag script - startMove = function (win, start, end, duration, callback) { - - var startTime = new Date(), - distX = end.clientX - start.clientX, - distY = end.clientY - start.clientY, - current = elementFromPoint(start, win), - cursor = win.document.createElement('div'), - calls = 0, - move; - move = function onmove() { - //get what fraction we are at - var now = new Date(), - scrollOffset = syn.helpers.scrollOffset(win), - fraction = (calls === 0 ? 0 : now - startTime) / duration, - options = { - clientX: distX * fraction + start.clientX, - clientY: distY * fraction + start.clientY - }; - calls++; - if (fraction < 1) { - syn.helpers.extend(cursor.style, { - left: (options.clientX + scrollOffset.left + 2) + "px", - top: (options.clientY + scrollOffset.top + 2) + "px" - }); - current = mouseMove(options, win, current); - syn.schedule(onmove, 15); - } else { - current = mouseMove(end, win, current); - win.document.body.removeChild(cursor); - callback(); - } - }; - syn.helpers.extend(cursor.style, { - height: "5px", - width: "5px", - backgroundColor: "red", - position: "absolute", - zIndex: 19999, - fontSize: "1px" - }); - win.document.body.appendChild(cursor); - move(); - }, +function createDragEvent(eventName, options, element){ + var dragEvent = syn.events.kinds.mouse.create(eventName, options, element); + // TODO: find a nicer way of doing this. + dragEvent.dataTransfer = dragAndDropTransferObject; + return syn.dispatch(dragEvent, element, eventName, false); +} +// returns the element that exists at a provided x, y coordinate +// TODO: move this to element.js +function elementFromPoint(point, win) { + var clientX = point.clientX; + var clientY = point.clientY; + if(point == null){return null;} - startDrag = function (win, fromPoint, toPoint, duration, callback) { - if(syn.support.pointerEvents){ - createEventAtPoint("pointerover", fromPoint, win); - createEventAtPoint("pointerenter", fromPoint, win); - } - createEventAtPoint("mouseover", fromPoint, win); - createEventAtPoint("mouseenter", fromPoint, win); - - if(syn.support.pointerEvents){ createEventAtPoint("pointermove", fromPoint, win); } - createEventAtPoint("mousemove", fromPoint, win); - - - if(syn.support.pointerEvents){createEventAtPoint("pointerdown", fromPoint, win);} - if(syn.support.touchEvents){createEventAtPoint("touchstart", fromPoint, win);} - createEventAtPoint("mousedown", fromPoint, win); - startMove(win, fromPoint, toPoint, duration, function () { - if(syn.support.pointerEvents){createEventAtPoint("pointerup", toPoint, win);} - if(syn.support.touchEvents){createEventAtPoint("touchend", toPoint, win);} - createEventAtPoint("mouseup", toPoint, win); - if(syn.support.pointerEvents){createEventAtPoint("pointerleave", toPoint, win);} - createEventAtPoint("mouseleave", toPoint, win); - callback(); - }); - }, - - - - - center = function (el) { - var j = syn.jquery()(el), - o = j.offset(); - return { - pageX: o.left + (j.outerWidth() / 2), - pageY: o.top + (j.outerHeight() / 2) - }; - }, - convertOption = function (option, win, from) { - var page = /(\d+)[x ](\d+)/, - client = /(\d+)X(\d+)/, - relative = /([+-]\d+)[xX ]([+-]\d+)/, - parts; - //check relative "+22x-44" - if (typeof option === 'string' && relative.test(option) && from) { - var cent = center(from); - parts = option.match(relative); - option = { - pageX: cent.pageX + parseInt(parts[1]), - pageY: cent.pageY + parseInt(parts[2]) - }; - } - if (typeof option === "string" && page.test(option)) { - parts = option.match(page); - option = { - pageX: parseInt(parts[1]), - pageY: parseInt(parts[2]) - }; - } - if (typeof option === 'string' && client.test(option)) { - parts = option.match(client); - option = { - clientX: parseInt(parts[1]), - clientY: parseInt(parts[2]) - }; - } - if (typeof option === 'string') { - option = syn.jquery()(option, win.document)[0]; - } - if (option.nodeName) { - option = center(option); - } - if (option.pageX != null) { - var off = syn.helpers.scrollOffset(win); - option = { - clientX: option.pageX - off.left, - clientY: option.pageY - off.top - }; - } - return option; - }, + if (syn.support.elementFromPage) { + var off = syn.helpers.scrollOffset(win); + clientX = clientX + off.left; //convert to pageX + clientY = clientY + off.top; //convert to pageY + } - - - - - - - // if the client chords are not going to be visible ... scroll the page so they will be ... - adjust = function (from, to, win) { - if (from.clientY < 0) { - var off = syn.helpers.scrollOffset(win); - var top = off.top + (from.clientY) - 100, - diff = top - off.top; - - // first, lets see if we can scroll 100 px - if (top > 0) { + return win.document.elementFromPoint(Math.round(clientX), Math.round(clientY)); +} - } else { - top = 0; - diff = -off.top; +/** +* This function defines the dataTransfer Object, which otherwise is immutable. d= DataTrnsfer() is not a valid constructor +* @param node +*/ +function createDataTransferObject(){ + var dataTransfer = { + dropEffect : "none", + effectAllowed : "uninitialized", + files: [], + items:[], + types:[], + data:[], + + // setData function assigns the dragValue to an object's property + setData: function(dataFlavor, value){ + var tempdata = {}; + tempdata.dataFlavor = dataFlavor; + tempdata.val = value; + this.data.push(tempdata); + }, + + // getData fetches the dragValue based on the input dataFlavor provided. + getData: function(dataFlavor){ + for (var i = 0; i < this.data.length; i++){ + var tempdata = this.data[i]; + if (tempdata.dataFlavor === dataFlavor){ + return tempdata.val; + } } - from.clientY = from.clientY - diff; - to.clientY = to.clientY - diff; - syn.helpers.scrollOffset(win, { - top: top, - left: off.left - }); } }; - - - - - - - - - - - - - -/** - * @add syn prototype - */ -syn.helpers.extend(syn.init.prototype, { - /** - * @function syn.move move() - * @parent mouse - * @signature `syn.move(from, options, callback)` - * Moves the cursor from one point to another. - * - * ### Quick Example - * - * The following moves the cursor from (0,0) in - * the window to (100,100) in 1 second. - * - * syn.move( - * document.document, - * { - * from: {clientX: 0, clientY: 0}, - * to: {clientX: 100, clientY: 100}, - * duration: 1000 - * }) - * - * ## Options - * - * There are many ways to configure the endpoints of the move. - * - * ### PageX and PageY - * - * If you pass pageX or pageY, these will get converted - * to client coordinates. - * - * syn.move( - * document.document, - * { - * from: {pageX: 0, pageY: 0}, - * to: {pageX: 100, pageY: 100} - * }) - * - * ### String Coordinates - * - * You can set the pageX and pageY as strings like: - * - * syn.move( - * document.document, - * { - * from: "0x0", - * to: "100x100" - * }) - * - * ### Element Coordinates - * - * If jQuery is present, you can pass an element as the from or to option - * and the coordinate will be set as the center of the element. - - * syn.move( - * document.document, - * { - * from: $(".recipe")[0], - * to: $("#trash")[0] - * }) - * - * ### Query Strings - * - * If jQuery is present, you can pass a query string as the from or to option. - * - * syn.move( - * document.document, - * { - * from: ".recipe", - * to: "#trash" - * }) - * - * ### No From - * - * If you don't provide a from, the element argument passed to syn is used. - * - * syn.move( - * 'myrecipe', - * { to: "#trash" }) - * - * ### Relative - * - * You can move the drag relative to the center of the from element. - * - * syn.move("myrecipe", "+20 +30"); - * - * @param {HTMLElement} from the element to move - * @param {Object} options options to configure the drag - * @param {Function} callback a callback that happens after the drag motion has completed - */ - _move: function (from, options, callback) { - - var win = syn.helpers.getWindow(from); - var sourceCoordinates = convertOption(options.from || from, win, from); - var destinationCoordinates = convertOption(options.to || options, win, from); - - DragonDrop.html5drag = syn.support.pointerEvents; - - if (options.adjust !== false) { - adjust(sourceCoordinates, destinationCoordinates, win); - } - startMove(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); - - }, - - - - - - - - - - - /** - * @function syn.drag drag() - * @parent mouse - * @signature `syn.drag(from, options, callback)` - * Creates a mousedown and drags from one point to another. - * Check out [syn.prototype.move move] for API details. - * - * @param {HTMLElement} from - * @param {Object} options - * @param {Object} callback - */ - _drag: function (from, options, callback) { - - var win = syn.helpers.getWindow(from); - var sourceCoordinates = convertOption(options.from || from, win, from); - var destinationCoordinates = convertOption(options.to || options, win, from); - - if (options.adjust !== false) { - adjust(sourceCoordinates, destinationCoordinates, win); - } - - DragonDrop.html5drag = from.draggable; - - if(DragonDrop.html5drag){ - DragonDrop.dragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); - }else{ - startDrag(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); - } - } -}); - - + return dataTransfer; +} diff --git a/src/drag.support.js b/src/drag.support.js new file mode 100644 index 00000000..2fdf6ea5 --- /dev/null +++ b/src/drag.support.js @@ -0,0 +1,33 @@ +var syn = require('./synthetic'); +// check if elementFromPageExists +(function dragSupport() { + + // document body has to exists for this test + if (!document.body) { + syn.helpers.schedule(dragSupport, 1); + return; + } + var div = document.createElement('div'); + document.body.appendChild(div); + syn.helpers.extend(div.style, { + width: "100px", + height: "10000px", + backgroundColor: "blue", + position: "absolute", + top: "10px", + left: "0px", + zIndex: 19999 + }); + document.body.scrollTop = 11; + if (!document.elementFromPoint) { + return; + } + var el = document.elementFromPoint(3, 1); + if (el === div) { + syn.support.elementFromClient = true; + } else { + syn.support.elementFromPage = true; + } + document.body.removeChild(div); + document.body.scrollTop = 0; +})(); diff --git a/src/key.js b/src/key.js index 460ad555..8ddbfcb4 100644 --- a/src/key.js +++ b/src/key.js @@ -3,437 +3,254 @@ require('./typeable'); require('./browsers'); -var h = syn.helpers, +var h = syn.helpers; +var synKey = syn.key; - formElExp = /input|textarea/i, - // selection is not supported by some inputs and would throw in Chrome. - supportsSelection = function(el) { - var result; - - try { - result = el.selectionStart !== undefined && el.selectionStart !== null; - } - catch(e) { - result = false; - } - - return result; - }, - // gets the selection of an input or textarea - getSelection = function (el) { - var real, r, start; - - // use selectionStart if we can - if (supportsSelection(el)) { - // this is for opera, so we don't have to focus to type how we think we would - if (document.activeElement && document.activeElement !== el && - el.selectionStart === el.selectionEnd && el.selectionStart === 0) { - return { - start: el.value.length, - end: el.value.length - }; - } - return { - start: el.selectionStart, - end: el.selectionEnd - }; - } else { - //check if we aren't focused - try { - //try 2 different methods that work differently (IE breaks depending on type) - if (el.nodeName.toLowerCase() === 'input') { - real = h.getWindow(el) - .document.selection.createRange(); - r = el.createTextRange(); - r.setEndPoint("EndToStart", real); - - start = r.text.length; - return { - start: start, - end: start + real.text.length - }; +// Add high-level functions directly to syn +syn.helpers.extend(syn,{ + /** + * @function syn.key key() + * @parent keys + * @signature `syn.key(element, options, callback)` + * Types a single key. The key should be + * a string that matches a + * [syn.static.keycodes]. + * + * The following sends a carridge return + * to the 'name' element. + * @codestart + * syn.key('name', '\r') + * @codeend + * For each character, a keydown, keypress, and keyup is triggered if + * appropriate. + * @param {HTMLElement} [element] + * @param {String|Number} options + * @param {Function} [callback] + * @return {HTMLElement} the element currently focused. + */ + key: function (element, options) { + var args = syn.args(options, element); + + return new Promise((resolve) => { + var element = args.element; + //first check if it is a special up + if (/-up$/.test(options) && h.inArray(options.replace("-up", ""), + syn.key.kinds.special) !== -1) { + syn.trigger(element, 'keyup', options.replace("-up", "")); + return resolve({runDefaults: true, element}); + } + + // keep reference to current activeElement + var activeElement = h.getWindow(element) + .document.activeElement, + caret = syn.typeable.test(element) && getSelection(element), + key = syn.key.convert[options] || options, + // should we run default events + runDefaults = syn.trigger(element, 'keydown', key), + + // how this browser handles preventing default events + prevent = syn.key.browser.prevent, + + // the result of the default event + defaultResult, + + keypressOptions = getKeyOptions(key, 'keypress'); + + if (runDefaults) { + //if the browser doesn't create keypresses for this key, run default + if (!keypressOptions) { + defaultResult = getKeyDefault(key) + .call(element, keypressOptions, h.getWindow(element), + key, undefined, caret); } else { - real = h.getWindow(el) - .document.selection.createRange(); - r = real.duplicate(); - var r2 = real.duplicate(), - r3 = real.duplicate(); - r2.collapse(); - r3.collapse(false); - r2.moveStart('character', -1); - r3.moveStart('character', -1); - //select all of our element - r.moveToElementText(el); - //now move our endpoint to the end of our real range - r.setEndPoint('EndToEnd', real); - start = r.text.length - real.text.length; - var end = r.text.length; - if (start !== 0 && r2.text === "") { - start += 2; + //do keypress + // check if activeElement changed b/c someone called focus in keydown + if (activeElement !== h.getWindow(element) + .document.activeElement) { + element = h.getWindow(element) + .document.activeElement; } - if (end !== 0 && r3.text === "") { - end += 2; + + runDefaults = syn.trigger(element, 'keypress', keypressOptions); + if (runDefaults) { + defaultResult = getKeyDefault(key) + .call(element, keypressOptions, h.getWindow(element), + key, undefined, caret); } - //if we aren't at the start, but previous is empty, we are at start of newline - return { - start: start, - end: end - }; } - } catch (e) { - var prop = formElExp.test(el.nodeName) ? "value" : "textContent"; + } else { + //canceled ... possibly don't run keypress + if (keypressOptions && h.inArray('keypress', prevent.keydown) === -1) { + // check if activeElement changed b/c someone called focus in keydown + if (activeElement !== h.getWindow(element) + .document.activeElement) { + element = h.getWindow(element) + .document.activeElement; + } - return { - start: el[prop].length, - end: el[prop].length - }; + syn.trigger(element, 'keypress', keypressOptions); + } } - } - }, - // gets all focusable elements - getFocusable = function (el) { - var document = h.getWindow(el) - .document, - res = []; - - var els = document.getElementsByTagName('*'), - len = els.length; - - for (var i = 0; i < len; i++) { - if (syn.isFocusable(els[i]) && els[i] !== document.documentElement) { - res.push(els[i]); + if (defaultResult && defaultResult.nodeName) { + element = defaultResult; + } + + if (defaultResult !== null) { + syn.helpers.schedule(function () { + + if((key === '\r') && (element.nodeName.toLowerCase() === 'input')){ + // do nothing. In the case of textInputs, RETURN key does not create an input event + }else if (syn.support.oninput) { + syn.trigger(element, 'input', getKeyOptions(key, 'input')); + } + syn.trigger(element, 'keyup', getKeyOptions(key, 'keyup')); + resolve({runDefaults: true, element}); + }, 1); + } else { + resolve({runDefaults: true, element}); } - } - return res; - }, - textProperty = (function(){ - var el = document.createElement("span"); - return el.textContent != null ? 'textContent' : 'innerText'; - })(), - - // Get the text from an element. - getText = function (el) { - if (formElExp.test(el.nodeName)) { - return el.value; - } - return el[textProperty]; - }, - // Set the text of an element. - setText = function (el, value) { - if (formElExp.test(el.nodeName)) { - el.value = value; - } else { - el[textProperty] = value; - } - }; -/** - * - */ -h.extend(syn, { + }); + }, /** - * @attribute + * @function syn.type type() * @parent keys - * A list of the keys and their keycodes codes you can type. - * You can add type keys with - * @codestart - * syn('key', 'title', 'delete'); - * - * //or + * @signature `syn.type(element, options, callback)` + * Types sequence of [syn.key key actions]. Each + * character is typed, one at a type. + * Multi-character keys like 'left' should be + * enclosed in square brackents. * - * syn('type', 'title', 'One Two Three[left][left][delete]'); + * The following types 'JavaScript MVC' then deletes the space. + * @codestart + * syn.type('#name', 'JavaScript MVC[left][left][left]\b') * @codeend * - * The following are a list of keys you can type: - * @codestart text - * \b - backspace - * \t - tab - * \r - enter - * ' ' - space - * a-Z 0-9 - normal characters - * /!@#$*,.? - All other typeable characters - * page-up - scrolls up - * page-down - scrolls down - * end - scrolls to bottom - * home - scrolls to top - * insert - changes how keys are entered - * delete - deletes the next character - * left - moves cursor left - * right - moves cursor right - * up - moves the cursor up - * down - moves the cursor down - * f1-12 - function buttons - * shift, ctrl, alt, meta - special keys - * pause-break - the pause button - * scroll-lock - locks scrolling - * caps - makes caps - * escape - escape button - * num-lock - allows numbers on keypad - * print - screen capture - * subtract - subtract (keypad) - - * dash - dash - - * divide - divide (keypad) / - * forward-slash - forward slash / - * decimal - decimal (keypad) . - * period - period . + * Type is able to handle (and move with) tabs (\t). + * The following simulates tabing and entering values in a form and + * eventually submitting the form. + * @codestart + * syn.type("#Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r") * @codeend + * @param {HTMLElement} [element] an element or an id of an element + * @param {String} options the text to type + * @param {Function} [callback] a function to callback */ - keycodes: { - //backspace - '\b': 8, + type: async function (element, options) { + var args = syn.args(options,element); + //break it up into parts ... + //go through each type and run + var parts = (options + "") + .match(/(\[[^\]]+\])|([^\[])/g), + el = args.element, + part; + while( part = parts.shift() ) { + if (part.length > 1) { + part = part.substr(1, part.length - 2); + } + var result = await syn.key(el, part); - //tab - '\t': 9, + el = result.element || el; - //enter - '\r': 13, + } + return {element:el}; - //modifier keys - 'shift': 16, - 'ctrl': 17, - 'alt': 18, - 'meta': 91, + } +}); - //weird - 'pause-break': 19, - 'caps': 20, - 'escape': 27, - 'num-lock': 144, - 'scroll-lock': 145, - 'print': 44, +// Add key type +syn.helpers.extend(syn.events.kinds, { + key: { + events: syn.helpers.toMap("keypress|keyup|keydown"), + // return the options for a key event + options: function (type, options, element) { + //check if options is character or has character + options = typeof options !== "object" ? { + character: options + } : options; - //navigation - 'page-up': 33, - 'page-down': 34, - 'end': 35, - 'home': 36, - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - 'insert': 45, - 'delete': 46, + //don't change the orignial + options = h.extend({}, options); + if (options.character) { + h.extend(options, getKeyOptions(options.character, type)); + delete options.character; + } - //normal characters - ' ': 32, - '0': 48, - '1': 49, - '2': 50, - '3': 51, - '4': 52, - '5': 53, - '6': 54, - '7': 55, - '8': 56, - '9': 57, - 'a': 65, - 'b': 66, - 'c': 67, - 'd': 68, - 'e': 69, - 'f': 70, - 'g': 71, - 'h': 72, - 'i': 73, - 'j': 74, - 'k': 75, - 'l': 76, - 'm': 77, - 'n': 78, - 'o': 79, - 'p': 80, - 'q': 81, - 'r': 82, - 's': 83, - 't': 84, - 'u': 85, - 'v': 86, - 'w': 87, - 'x': 88, - 'y': 89, - 'z': 90, - //normal-characters, numpad - 'num0': 96, - 'num1': 97, - 'num2': 98, - 'num3': 99, - 'num4': 100, - 'num5': 101, - 'num6': 102, - 'num7': 103, - 'num8': 104, - 'num9': 105, - '*': 106, - '+': 107, - 'subtract': 109, - 'decimal': 110, - //normal-characters, others - 'divide': 111, - ';': 186, - '=': 187, - ',': 188, - 'dash': 189, - '-': 189, - 'period': 190, - '.': 190, - 'forward-slash': 191, - '/': 191, - '`': 192, - '[': 219, - '\\': 220, - ']': 221, - "'": 222, + options = h.extend({ + ctrlKey: !! syn.keysBeingHeldDown.ctrlKey, + altKey: !! syn.keysBeingHeldDown.altKey, + shiftKey: !! syn.keysBeingHeldDown.shiftKey, + metaKey: !! syn.keysBeingHeldDown.metaKey + }, options); - //ignore these, you shouldn't use them - 'left window key': 91, - 'right window key': 92, - 'select key': 93, + return options; + }, + // creates a key event + create: function (type, options, element) { //Everyone Else + var doc = h.getWindow(element) + .document || document, + event; + if (typeof KeyboardEvent !== 'undefined') { + var keyboardEventKeys = syn.key.keyboardEventKeys; - 'f1': 112, - 'f2': 113, - 'f3': 114, - 'f4': 115, - 'f5': 116, - 'f6': 117, - 'f7': 118, - 'f8': 119, - 'f9': 120, - 'f10': 121, - 'f11': 122, - 'f12': 123 - }, + if (options.key && keyboardEventKeys[options.key]) { + options.key = keyboardEventKeys[options.key]; + } - // selects text on an element - selectText: function (el, start, end) { - if (supportsSelection(el)) { - if (!end) { - syn.__tryFocus(el); - el.setSelectionRange(start, start); + event = new KeyboardEvent(type, options); + event.synthetic = true; + return event; + } else if (doc.createEvent) { + try { + event = doc.createEvent("KeyEvents"); + event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); + } catch (e) { + event = h.createBasicStandardEvent(type, options, doc); + } + event.synthetic = true; + return event; } else { - el.selectionStart = start; - el.selectionEnd = end; + try { + event = h.createEventObject.apply(this, arguments); + h.extend(event, options); + } catch (e) {} + + return event; } - } else if (el.createTextRange) { - //syn.__tryFocus(el); - var r = el.createTextRange(); - r.moveStart('character', start); - end = end || start; - r.moveEnd('character', end - el.value.length); - - r.select(); - } - }, - getText: function (el) { - //first check if the el has anything selected .. - if (syn.typeable.test(el)) { - var sel = getSelection(el); - return el.value.substring(sel.start, sel.end); - } - //otherwise get from page - var win = syn.helpers.getWindow(el); - if (win.getSelection) { - return win.getSelection() - .toString(); - } else if (win.document.getSelection) { - return win.document.getSelection() - .toString(); - } else { - return win.document.selection.createRange() - .text; } - }, - getSelection: getSelection + } }); -h.extend(syn.key, { - // retrieves a description of what events for this character should look like - data: function (key) { - //check if it is described directly - if (syn.key.browser[key]) { - return syn.key.browser[key]; - } - for (var kind in syn.key.kinds) { - if (h.inArray(key, syn.key.kinds[kind]) > -1) { - return syn.key.browser[kind]; +// Add specific behavior to specific events +syn.helpers.extend(syn.events.types,{ + keydown: { + setup: function (type, options, element) { + if (h.inArray(options, syn.key.kinds.special) !== -1) { + syn.keysBeingHeldDown[options + "Key"] = element; } } - return syn.key.browser.character; }, - - //returns the special key if special - isSpecial: function (keyCode) { - var specials = syn.key.kinds.special; - for (var i = 0; i < specials.length; i++) { - if (syn.keycodes[specials[i]] === keyCode) { - return specials[i]; + keypress: { + setup: function (type, options, element) { + // if this browsers supports writing keys on events + // but doesn't write them if the element isn't focused + // focus on the element (ignored if already focused) + if (syn.support.keyCharacters && !syn.support.keysOnNotFocused) { + syn.helpers.tryFocus(element); } } }, - /** - * @hide - * gets the options for a key and event type ... - * @param {Object} key - * @param {Object} event - */ - options: function (key, event) { - var keyData = syn.key.data(key); - - if (!keyData[event]) { - //we shouldn't be creating this event - return null; - } - - var charCode = keyData[event][0], - keyCode = keyData[event][1], - result = { - key: key - }; - - if (keyCode === 'key') { - result.keyCode = syn.keycodes[key]; - } else if (keyCode === 'char') { - result.keyCode = key.charCodeAt(0); - } else { - result.keyCode = keyCode; - } - - if (charCode === 'char') { - result.charCode = key.charCodeAt(0); - } else if (charCode !== null) { - result.charCode = charCode; - } - - // all current browsers have which property to normalize keyCode/charCode - if (result.keyCode) { - result.which = result.keyCode; - } else { - result.which = result.charCode; - } - - return result; - }, - //types of event keys - kinds: { - special: ["shift", 'ctrl', 'alt', 'meta', 'caps'], - specialChars: ["\b"], - navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'], - 'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'] - }, - //returns the default function - // some keys have default functions - // some 'kinds' of keys have default functions - getDefault: function (key) { - //check if it is described directly - if (syn.key.defaults[key]) { - return syn.key.defaults[key]; - } - for (var kind in syn.key.kinds) { - if (h.inArray(key, syn.key.kinds[kind]) > -1 && syn.key.defaults[kind]) { - return syn.key.defaults[kind]; + keyup: { + setup: function (type, options, element) { + if (h.inArray(options, syn.key.kinds.special) !== -1) { + syn.keysBeingHeldDown[options + "Key"] = null; } } - return syn.key.defaults.character; - }, + } +}); + +// Key behavior configuration. In theory, someone could make overwrites here. +syn.helpers.extend(syn.key, { // default behavior when typing defaults: { 'character': function (options, scope, key, force, sel) { @@ -450,33 +267,33 @@ h.extend(syn.key, { setText(this, before + character + after); //handle IE inserting \r\n var charLength = character === "\n" && syn.support.textareaCarriage ? 2 : character.length; - syn.selectText(this, before.length + charLength); + selectText(this, before.length + charLength); } }, 'c': function (options, scope, key, force, sel) { - if (syn.key.ctrlKey) { - syn.key.clipboard = syn.getText(this); + if (syn.keysBeingHeldDown.ctrlKey) { + syn.key.clipboard = getSelectionText(this); } else { syn.key.defaults.character.apply(this, arguments); } }, 'v': function (options, scope, key, force, sel) { - if (syn.key.ctrlKey) { + if (syn.keysBeingHeldDown.ctrlKey) { syn.key.defaults.character.call(this, options, scope, syn.key.clipboard, true, sel); } else { syn.key.defaults.character.apply(this, arguments); } }, 'a': function (options, scope, key, force, sel) { - if (syn.key.ctrlKey) { - syn.selectText(this, 0, getText(this) + if (syn.keysBeingHeldDown.ctrlKey) { + selectText(this, 0, getText(this) .length); } else { syn.key.defaults.character.apply(this, arguments); } }, 'home': function () { - syn.onParents(this, function (el) { + syn.helpers.onParents(this, function (el) { if (el.scrollHeight !== el.clientHeight) { el.scrollTop = 0; return false; @@ -484,7 +301,7 @@ h.extend(syn.key, { }); }, 'end': function () { - syn.onParents(this, function (el) { + syn.helpers.onParents(this, function (el) { if (el.scrollHeight !== el.clientHeight) { el.scrollTop = el.scrollHeight; return false; @@ -493,7 +310,7 @@ h.extend(syn.key, { }, 'page-down': function () { //find the first parent we can scroll - syn.onParents(this, function (el) { + syn.helpers.onParents(this, function (el) { if (el.scrollHeight !== el.clientHeight) { var ch = el.clientHeight; el.scrollTop += ch; @@ -502,7 +319,7 @@ h.extend(syn.key, { }); }, 'page-up': function () { - syn.onParents(this, function (el) { + syn.helpers.onParents(this, function (el) { if (el.scrollHeight !== el.clientHeight) { var ch = el.clientHeight; el.scrollTop -= ch; @@ -520,10 +337,10 @@ h.extend(syn.key, { if (sel.start === sel.end && sel.start > 0) { //remove a character setText(this, before.substring(0, before.length - 1) + after); - syn.selectText(this, sel.start - 1); + selectText(this, sel.start - 1); } else { setText(this, before + after); - syn.selectText(this, sel.start); + selectText(this, sel.start); } //set back the selection @@ -540,7 +357,7 @@ h.extend(syn.key, { } else { setText(this, before + after); } - syn.selectText(this, sel.start); + selectText(this, sel.start); } }, '\r': function (options, scope, key, force, sel) { @@ -552,7 +369,7 @@ h.extend(syn.key, { } if (!syn.support.keypressSubmits && nodeName === 'input') { - var form = syn.closest(this, "form"); + var form = syn.helpers.closest(this, "form"); if (form) { syn.trigger(form, "submit", {}); } @@ -568,7 +385,7 @@ h.extend(syn.key, { syn.trigger(this, "click", {}); } }, - + // Gets all focusable elements. If the element (this) // doesn't have a tabindex, finds the next element after. // If the element (this) has a tabindex finds the element @@ -592,8 +409,8 @@ h.extend(syn.key, { var sort = function (order1, order2) { var el1 = order1[0], el2 = order2[0], - tab1 = syn.tabIndex(el1) || 0, - tab2 = syn.tabIndex(el2) || 0; + tab1 = syn.helpers.tabIndex(el1) || 0, + tab2 = syn.helpers.tabIndex(el2) || 0; if (tab1 === tab2) { return order1[1] - order2[1]; } else { @@ -607,332 +424,468 @@ h.extend(syn.key, { } }; - orders.sort(sort); - var ordersLength = orders.length; + orders.sort(sort); + var ordersLength = orders.length; + + //now find current + for (i = 0; i < ordersLength; i++) { + el = orders[i][0]; + if (this === el) { + var nextIndex = i; + if (syn.keysBeingHeldDown.shiftKey) { + nextIndex--; + // Select the previous item or loop down to the last + current = nextIndex >= 0 && orders[nextIndex][0] || orders[ordersLength-1][0]; + } else { + nextIndex++; + // Select the next item or loop back to the first + current = nextIndex < ordersLength && orders[nextIndex][0] || orders[0][0]; + } + } + } + + //restart if we didn't find anything + if (!current) { + current = firstNotIndexed; + } else { + syn.helpers.tryFocus(current); + } + return current; + }, + 'left': function (options, scope, key, force, sel) { + if (syn.typeable.test(this)) { + if (syn.keysBeingHeldDown.shiftKey) { + selectText(this, sel.start === 0 ? 0 : sel.start - 1, sel.end); + } else { + selectText(this, sel.start === 0 ? 0 : sel.start - 1); + } + } + }, + 'right': function (options, scope, key, force, sel) { + if (syn.typeable.test(this)) { + if (syn.keysBeingHeldDown.shiftKey) { + selectText(this, sel.start, sel.end + 1 > getText(this) + .length ? getText(this) + .length : sel.end + 1); + } else { + selectText(this, sel.end + 1 > getText(this) + .length ? getText(this) + .length : sel.end + 1); + } + } + }, + 'up': function () { + if (/select/i.test(this.nodeName)) { + + this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0; + //set this to change on blur? + } + }, + 'down': function () { + if (/select/i.test(this.nodeName)) { + syn.helpers.changeOnBlur(this, "selectedIndex", this.selectedIndex); + this.selectedIndex = this.selectedIndex + 1; + //set this to change on blur? + } + }, + 'shift': function () { + return null; + }, + 'ctrl': function () { + return null; + }, + 'alt': function () { + return null; + }, + 'meta': function () { + return null; + } + }, + //types of event keys + kinds: { + special: ["shift", 'ctrl', 'alt', 'meta', 'caps'], + specialChars: ["\b"], + navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'], + 'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'] + }, + convert: { + "enter": "\r", + "backspace": "\b", + "tab": "\t", + "space": " " + }, + keycodes: { + //backspace + '\b': 8, + + //tab + '\t': 9, + + //enter + '\r': 13, + + //modifier keys + 'shift': 16, + 'ctrl': 17, + 'alt': 18, + 'meta': 91, + + //weird + 'pause-break': 19, + 'caps': 20, + 'escape': 27, + 'num-lock': 144, + 'scroll-lock': 145, + 'print': 44, + + //navigation + 'page-up': 33, + 'page-down': 34, + 'end': 35, + 'home': 36, + 'left': 37, + 'up': 38, + 'right': 39, + 'down': 40, + 'insert': 45, + 'delete': 46, + + //normal characters + ' ': 32, + '0': 48, + '1': 49, + '2': 50, + '3': 51, + '4': 52, + '5': 53, + '6': 54, + '7': 55, + '8': 56, + '9': 57, + 'a': 65, + 'b': 66, + 'c': 67, + 'd': 68, + 'e': 69, + 'f': 70, + 'g': 71, + 'h': 72, + 'i': 73, + 'j': 74, + 'k': 75, + 'l': 76, + 'm': 77, + 'n': 78, + 'o': 79, + 'p': 80, + 'q': 81, + 'r': 82, + 's': 83, + 't': 84, + 'u': 85, + 'v': 86, + 'w': 87, + 'x': 88, + 'y': 89, + 'z': 90, + //normal-characters, numpad + 'num0': 96, + 'num1': 97, + 'num2': 98, + 'num3': 99, + 'num4': 100, + 'num5': 101, + 'num6': 102, + 'num7': 103, + 'num8': 104, + 'num9': 105, + '*': 106, + '+': 107, + 'subtract': 109, + 'decimal': 110, + //normal-characters, others + 'divide': 111, + ';': 186, + '=': 187, + ',': 188, + 'dash': 189, + '-': 189, + 'period': 190, + '.': 190, + 'forward-slash': 191, + '/': 191, + '`': 192, + '[': 219, + '\\': 220, + ']': 221, + "'": 222, + + //ignore these, you shouldn't use them + 'left window key': 91, + 'right window key': 92, + 'select key': 93, - //now find current - for (i = 0; i < ordersLength; i++) { - el = orders[i][0]; - if (this === el) { - var nextIndex = i; - if (syn.key.shiftKey) { - nextIndex--; - // Select the previous item or loop down to the last - current = nextIndex >= 0 && orders[nextIndex][0] || orders[ordersLength-1][0]; - } else { - nextIndex++; - // Select the next item or loop back to the first - current = nextIndex < ordersLength && orders[nextIndex][0] || orders[0][0]; - } - } - } + 'f1': 112, + 'f2': 113, + 'f3': 114, + 'f4': 115, + 'f5': 116, + 'f6': 117, + 'f7': 118, + 'f8': 119, + 'f9': 120, + 'f10': 121, + 'f11': 122, + 'f12': 123 + }, - //restart if we didn't find anything - if (!current) { - current = firstNotIndexed; - } else { - syn.__tryFocus(current); - } - return current; - }, - 'left': function (options, scope, key, force, sel) { - if (syn.typeable.test(this)) { - if (syn.key.shiftKey) { - syn.selectText(this, sel.start === 0 ? 0 : sel.start - 1, sel.end); - } else { - syn.selectText(this, sel.start === 0 ? 0 : sel.start - 1); - } - } - }, - 'right': function (options, scope, key, force, sel) { - if (syn.typeable.test(this)) { - if (syn.key.shiftKey) { - syn.selectText(this, sel.start, sel.end + 1 > getText(this) - .length ? getText(this) - .length : sel.end + 1); - } else { - syn.selectText(this, sel.end + 1 > getText(this) - .length ? getText(this) - .length : sel.end + 1); + // these are helpers that are used by tests + helpers: { + //returns the special key if special (this is used by the recorder) + isSpecial: function (keyCode) { + var specials = syn.key.kinds.special; + for (var i = 0; i < specials.length; i++) { + if (syn.key.keycodes[specials[i]] === keyCode) { + return specials[i]; } } - }, - 'up': function () { - if (/select/i.test(this.nodeName)) { - - this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0; - //set this to change on blur? - } - }, - 'down': function () { - if (/select/i.test(this.nodeName)) { - syn.changeOnBlur(this, "selectedIndex", this.selectedIndex); - this.selectedIndex = this.selectedIndex + 1; - //set this to change on blur? - } - }, - 'shift': function () { - return null; - }, - 'ctrl': function () { - return null; - }, - 'alt': function () { - return null; - }, - 'meta': function () { - return null; } } + + }); -h.extend(syn.create, { - keydown: { - setup: function (type, options, element) { - if (h.inArray(options, syn.key.kinds.special) !== -1) { - syn.key[options + "Key"] = element; - } - } - }, - keypress: { - setup: function (type, options, element) { - // if this browsers supports writing keys on events - // but doesn't write them if the element isn't focused - // focus on the element (ignored if already focused) - if (syn.support.keyCharacters && !syn.support.keysOnNotFocused) { - syn.__tryFocus(element); - } - } - }, - keyup: { - setup: function (type, options, element) { - if (h.inArray(options, syn.key.kinds.special) !== -1) { - syn.key[options + "Key"] = null; - } +// ## Start helpers +var textProperty = (function(){ + var el = document.createElement("span"); + return el.textContent != null ? 'textContent' : 'innerText'; +})(), + formElExp = /input|textarea/i; + + +function getKeyDefault(key) { + //check if it is described directly + if (syn.key.defaults[key]) { + return syn.key.defaults[key]; + } + for (var kind in syn.key.kinds) { + if (h.inArray(key, syn.key.kinds[kind]) > -1 && syn.key.defaults[kind]) { + return syn.key.defaults[kind]; } - }, - key: { - // return the options for a key event - options: function (type, options, element) { - //check if options is character or has character - options = typeof options !== "object" ? { - character: options - } : options; + } + return syn.key.defaults.character; +} - //don't change the orignial - options = h.extend({}, options); - if (options.character) { - h.extend(options, syn.key.options(options.character, type)); - delete options.character; - } +function getKeyOptions(key, event) { + var keyData = getKeyData(key); - options = h.extend({ - ctrlKey: !! syn.key.ctrlKey, - altKey: !! syn.key.altKey, - shiftKey: !! syn.key.shiftKey, - metaKey: !! syn.key.metaKey - }, options); + if (!keyData[event]) { + //we shouldn't be creating this event + return null; + } - return options; - }, - // creates a key event - event: function (type, options, element) { //Everyone Else - var doc = h.getWindow(element) - .document || document, - event; - if (typeof KeyboardEvent !== 'undefined') { - var keyboardEventKeys = syn.key.keyboardEventKeys; + var charCode = keyData[event][0], + keyCode = keyData[event][1], + result = { + key: key + }; + + if (keyCode === 'key') { + result.keyCode = syn.key.keycodes[key]; + } else if (keyCode === 'char') { + result.keyCode = key.charCodeAt(0); + } else { + result.keyCode = keyCode; + } - if (options.key && keyboardEventKeys[options.key]) { - options.key = keyboardEventKeys[options.key]; - } - - event = new KeyboardEvent(type, options); - event.synthetic = true; - return event; - } else if (doc.createEvent) { - try { - event = doc.createEvent("KeyEvents"); - event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); - } catch (e) { - event = h.createBasicStandardEvent(type, options, doc); - } - event.synthetic = true; - return event; - } else { - try { - event = h.createEventObject.apply(this, arguments); - h.extend(event, options); - } catch (e) {} + if (charCode === 'char') { + result.charCode = key.charCodeAt(0); + } else if (charCode !== null) { + result.charCode = charCode; + } - return event; - } - } + // all current browsers have which property to normalize keyCode/charCode + if (result.keyCode) { + result.which = result.keyCode; + } else { + result.which = result.charCode; } -}); -var convert = { - "enter": "\r", - "backspace": "\b", - "tab": "\t", - "space": " " -}; - -/** - * - */ -h.extend(syn.init.prototype, { - /** - * @function syn.key key() - * @parent keys - * @signature `syn.key(element, options, callback)` - * Types a single key. The key should be - * a string that matches a - * [syn.static.keycodes]. - * - * The following sends a carridge return - * to the 'name' element. - * @codestart - * syn.key('name', '\r') - * @codeend - * For each character, a keydown, keypress, and keyup is triggered if - * appropriate. - * @param {HTMLElement} [element] - * @param {String|Number} options - * @param {Function} [callback] - * @return {HTMLElement} the element currently focused. - */ - _key: function (element, options, callback) { - //first check if it is a special up - if (/-up$/.test(options) && h.inArray(options.replace("-up", ""), - syn.key.kinds.special) !== -1) { - syn.trigger(element, 'keyup', options.replace("-up", "")); - return callback(true, element); + return result; +} + +function getKeyData(key) { + //check if it is described directly + if (syn.key.browser[key]) { + return syn.key.browser[key]; + } + for (var kind in syn.key.kinds) { + if (h.inArray(key, syn.key.kinds[kind]) > -1) { + return syn.key.browser[kind]; } + } + return syn.key.browser.character; +} - // keep reference to current activeElement - var activeElement = h.getWindow(element) - .document.activeElement, - caret = syn.typeable.test(element) && getSelection(element), - key = convert[options] || options, - // should we run default events - runDefaults = syn.trigger(element, 'keydown', key), +// selection is not supported by some inputs and would throw in Chrome. +function supportsSelection(el) { + var result; - // a function that gets the default behavior for a key - getDefault = syn.key.getDefault, + try { + result = el.selectionStart !== undefined && el.selectionStart !== null; + } + catch(e) { + result = false; + } - // how this browser handles preventing default events - prevent = syn.key.browser.prevent, + return result; +} - // the result of the default event - defaultResult, +function selectText(el, start, end) { + if (supportsSelection(el)) { + if (!end) { + syn.helpers.tryFocus(el); + el.setSelectionRange(start, start); + } else { + el.selectionStart = start; + el.selectionEnd = end; + } + } else if (el.createTextRange) { + //syn.helpers.tryFocus(el); + var r = el.createTextRange(); + r.moveStart('character', start); + end = end || start; + r.moveEnd('character', end - el.value.length); + + r.select(); + } +} - keypressOptions = syn.key.options(key, 'keypress'); +// gets the selection of an input or textarea +function getSelection(el) { + var real, r, start; - if (runDefaults) { - //if the browser doesn't create keypresses for this key, run default - if (!keypressOptions) { - defaultResult = getDefault(key) - .call(element, keypressOptions, h.getWindow(element), - key, undefined, caret); + // use selectionStart if we can + if (supportsSelection(el)) { + // this is for opera, so we don't have to focus to type how we think we would + if (document.activeElement && document.activeElement !== el && + el.selectionStart === el.selectionEnd && el.selectionStart === 0) { + return { + start: el.value.length, + end: el.value.length + }; + } + return { + start: el.selectionStart, + end: el.selectionEnd + }; + } else { + //check if we aren't focused + try { + //try 2 different methods that work differently (IE breaks depending on type) + if (el.nodeName.toLowerCase() === 'input') { + real = h.getWindow(el) + .document.selection.createRange(); + r = el.createTextRange(); + r.setEndPoint("EndToStart", real); + + start = r.text.length; + return { + start: start, + end: start + real.text.length + }; } else { - //do keypress - // check if activeElement changed b/c someone called focus in keydown - if (activeElement !== h.getWindow(element) - .document.activeElement) { - element = h.getWindow(element) - .document.activeElement; + real = h.getWindow(el) + .document.selection.createRange(); + r = real.duplicate(); + var r2 = real.duplicate(), + r3 = real.duplicate(); + r2.collapse(); + r3.collapse(false); + r2.moveStart('character', -1); + r3.moveStart('character', -1); + //select all of our element + r.moveToElementText(el); + //now move our endpoint to the end of our real range + r.setEndPoint('EndToEnd', real); + start = r.text.length - real.text.length; + var end = r.text.length; + if (start !== 0 && r2.text === "") { + start += 2; } - - runDefaults = syn.trigger(element, 'keypress', keypressOptions); - if (runDefaults) { - defaultResult = getDefault(key) - .call(element, keypressOptions, h.getWindow(element), - key, undefined, caret); + if (end !== 0 && r3.text === "") { + end += 2; } + //if we aren't at the start, but previous is empty, we are at start of newline + return { + start: start, + end: end + }; } - } else { - //canceled ... possibly don't run keypress - if (keypressOptions && h.inArray('keypress', prevent.keydown) === -1) { - // check if activeElement changed b/c someone called focus in keydown - if (activeElement !== h.getWindow(element) - .document.activeElement) { - element = h.getWindow(element) - .document.activeElement; - } + } catch (e) { + var prop = formElExp.test(el.nodeName) ? "value" : "textContent"; - syn.trigger(element, 'keypress', keypressOptions); - } - } - if (defaultResult && defaultResult.nodeName) { - element = defaultResult; + return { + start: el[prop].length, + end: el[prop].length + }; } + } +} - if (defaultResult !== null) { - syn.schedule(function () { - - if((key === '\r') && (element.nodeName.toLowerCase() === 'input')){ - // do nothing. In the case of textInputs, RETURN key does not create an input event - }else if (syn.support.oninput) { - syn.trigger(element, 'input', syn.key.options(key, 'input')); - } - syn.trigger(element, 'keyup', syn.key.options(key, 'keyup')); - callback(runDefaults, element); - }, 1); - } else { - callback(runDefaults, element); - } +function getSelectionText(el) { + //first check if the el has anything selected .. + if (syn.typeable.test(el)) { + var sel = getSelection(el); + return el.value.substring(sel.start, sel.end); + } + //otherwise get from page + var win = syn.helpers.getWindow(el); + if (win.getSelection) { + return win.getSelection() + .toString(); + } else if (win.document.getSelection) { + return win.document.getSelection() + .toString(); + } else { + return win.document.selection.createRange() + .text; + } +} - //do mouseup - return element; - // is there a keypress? .. if not , run default - // yes -> did we prevent it?, if not run ... - }, - /** - * @function syn.type type() - * @parent keys - * @signature `syn.type(element, options, callback)` - * Types sequence of [syn.key key actions]. Each - * character is typed, one at a type. - * Multi-character keys like 'left' should be - * enclosed in square brackents. - * - * The following types 'JavaScript MVC' then deletes the space. - * @codestart - * syn.type('name', 'JavaScript MVC[left][left][left]\b') - * @codeend - * - * Type is able to handle (and move with) tabs (\t). - * The following simulates tabing and entering values in a form and - * eventually submitting the form. - * @codestart - * syn.type("Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r") - * @codeend - * @param {HTMLElement} [element] an element or an id of an element - * @param {String} options the text to type - * @param {Function} [callback] a function to callback - */ - _type: function (element, options, callback) { - //break it up into parts ... - //go through each type and run - var parts = (options + "") - .match(/(\[[^\]]+\])|([^\[])/g), - self = this, - runNextPart = function (runDefaults, el) { - var part = parts.shift(); - if (!part) { - callback(runDefaults, el); - return; - } - el = el || element; - if (part.length > 1) { - part = part.substr(1, part.length - 2); - } - self._key(el, part, runNextPart); - }; +// gets all focusable elements +function getFocusable(el) { + var document = h.getWindow(el) + .document, + res = []; - runNextPart(); + var els = document.getElementsByTagName('*'), + len = els.length; + for (var i = 0; i < len; i++) { + if (syn.helpers.isFocusable(els[i]) && els[i] !== document.documentElement) { + res.push(els[i]); + } } -}); + return res; +} + + +// Get the text from an element. +function getText(el) { + if (formElExp.test(el.nodeName)) { + return el.value; + } + return el[textProperty]; +} +// Set the text of an element. +function setText(el, value) { + if (formElExp.test(el.nodeName)) { + el.value = value; + } else { + el[textProperty] = value; + } +} +// TODO: remove this hack +syn.helpers.extend(syn.key, synKey); diff --git a/src/key.support.js b/src/key.support.js index 92b4ddaf..2c4be247 100644 --- a/src/key.support.js +++ b/src/key.support.js @@ -6,11 +6,11 @@ if (!syn.config.support) { //do support code (function checkForSupport() { if (!document.body) { - return syn.schedule(checkForSupport, 1); + return syn.helpers.schedule(checkForSupport, 1); } var div = document.createElement("div"), - checkbox, submit, form, anchor, textarea, inputter, one, doc; + form, anchor, textarea, inputter, one, doc; doc = document.documentElement; @@ -27,8 +27,6 @@ if (!syn.config.support) { doc.insertBefore(div, doc.firstElementChild || doc.children[0]); form = div.firstChild; - checkbox = form.childNodes[0]; - submit = form.childNodes[2]; anchor = form.getElementsByTagName("a")[0]; textarea = form.getElementsByTagName("textarea")[0]; inputter = form.childNodes[3]; @@ -43,7 +41,7 @@ if (!syn.config.support) { return false; }; // Firefox 4 won't write key events if the element isn't focused - syn.__tryFocus(inputter); + syn.helpers.tryFocus(inputter); syn.trigger(inputter, "keypress", "\r"); syn.trigger(inputter, "keypress", "a"); @@ -56,14 +54,14 @@ if (!syn.config.support) { inputter.onchange = function () { syn.support.focusChanges = true; }; - syn.__tryFocus(inputter); + syn.helpers.tryFocus(inputter); syn.trigger(inputter, "keypress", "a"); - syn.__tryFocus(form.childNodes[5]); // this will throw a change event + syn.helpers.tryFocus(form.childNodes[5]); // this will throw a change event syn.trigger(inputter, "keypress", "b"); syn.support.keysOnNotFocused = inputter.value === "ab"; //test keypress \r on anchor submits - syn.bind(anchor, "click", function (ev) { + syn.helpers.bind(anchor, "click", function (ev) { if (ev.preventDefault) { ev.preventDefault(); } @@ -85,5 +83,3 @@ if (!syn.config.support) { } else { syn.helpers.extend(syn.support, syn.config.support); } - - diff --git a/src/mouse.js b/src/mouse.js index 4b8ebb07..927dacd1 100644 --- a/src/mouse.js +++ b/src/mouse.js @@ -1,95 +1,90 @@ var syn = require('./synthetic'); -//handles mosue events +// Add high-level functions directly to syn +syn.helpers.extend(syn, { + click: function (element, options, force) { + var args = syn.args(options,element); -var h = syn.helpers, - getWin = h.getWindow; - -syn.mouse = {}; -h.extend(syn.defaults, { - mousedown: function (options) { - syn.trigger(this, "focus", {}); - }, - click: function () { - // prevents the access denied issue in IE if the click causes the element to be destroyed - var element = this, - href, type, createChange, radioChanged, nodeName, scope; - try { - href = element.href; - type = element.type; - createChange = syn.data(element, "createChange"); - radioChanged = syn.data(element, "radioChanged"); - scope = getWin(element); - nodeName = element.nodeName.toLowerCase(); - } catch (e) { - return; - } - //get old values - - //this code was for restoring the href attribute to prevent popup opening - //if ((href = syn.data(element, "href"))) { - // element.setAttribute('href', href) - //} - - //run href javascript - if (!syn.support.linkHrefJS && /^\s*javascript:/.test(href)) { - //eval js - var code = href.replace(/^\s*javascript:/, ""); - - //try{ - if (code !== "//" && code.indexOf("void(0)") === -1) { - if (window.selenium) { - eval("with(selenium.browserbot.getCurrentWindow()){" + code + "}"); - } else { - eval("with(scope){" + code + "}"); - } + return new Promise((resolve) => { + var element = args.element; + syn.helpers.addOffset(options, element); + if(syn.support.pointerEvents){ + syn.trigger(element, 'pointerdown', options); } - } - - //submit a form - if (!(syn.support.clickSubmits) && ((nodeName === "input" || - nodeName === "button") && - type === "submit")) { - - var form = syn.closest(element, "form"); - if (form) { - syn.trigger(form, "submit", {}); + if(syn.support.touchEvents){ + syn.trigger(element, 'touchstart', options); } - } - //follow a link, probably needs to check if in an a. - if (nodeName === "a" && element.href && !/^\s*javascript:/.test(href)) { - scope.location.href = href; + syn.trigger(element, "mousedown", options); - } + //timeout is b/c IE is stupid and won't call focus handlers + syn.helpers.schedule(function () { + if(syn.support.pointerEvents){ + syn.trigger(element, 'pointerup', options); + } + if(syn.support.touchEvents){ + syn.trigger(element, 'touchend', options); + } - //change a checkbox - if (nodeName === "input" && type === "checkbox") { + syn.trigger(element, "mouseup", options); + if (!syn.support.mouseDownUpClicks || force) { + syn.trigger(element, "click", options); + resolve(true); + } else { + //we still have to run the default (presumably) + syn.create.click.setup('click', options, element); + syn.defaults.click.call(element); + //must give time for callback + syn.helpers.schedule(function () { + resolve(true); + }, 1); + } - //if(!syn.support.clickChecks && !syn.support.changeChecks){ - // element.checked = !element.checked; - //} - if (!syn.support.clickChanges) { - syn.trigger(element, "change", {}); + }, 1); + }); + }, + rightClick: function (element, options) { + var args = syn.args(options,element); + return new Promise((resolve)=>{ + var element = args.element; + syn.helpers.addOffset(options, element); + var mouseopts = syn.helpers.extend(syn.helpers.extend({}, syn.mouse.browser.right.mouseup), options); + if(syn.support.pointerEvents){ + syn.trigger(element, 'pointerdown', mouseopts); } - } - //change a radio button - if (nodeName === "input" && type === "radio") { // need to uncheck others if not checked - if (radioChanged && !syn.support.radioClickChanges) { - syn.trigger(element, "change", {}); - } - } - // change options - if (nodeName === "option" && createChange) { - syn.trigger(element.parentNode, "change", {}); //does not bubble - syn.data(element, "createChange", false); - } + syn.trigger(element, "mousedown", mouseopts); + + //timeout is b/c IE is stupid and won't call focus handlers + syn.helpers.schedule(function () { + if(syn.support.pointerEvents){ + syn.trigger(element, 'pointerup', mouseopts); + } + syn.trigger(element, "mouseup", mouseopts); + if (syn.mouse.browser.right.contextmenu) { + syn.trigger(element, "contextmenu", + syn.helpers.extend( + syn.helpers.extend({}, syn.mouse.browser.right.contextmenu), options ) + ); + } + resolve(); + }, 1); + }); + + }, + dblclick: async function (element, options) { + var args = syn.args(options,element); + syn.helpers.addOffset(options, args.element); + await syn.click(args.element, options); + await syn.helpers.schedulePromise(2); + await syn.click(args.element, options); + syn.trigger(args.element, "dblclick", options); + return true; } }); -//add create and setup behavior for mouse events -h.extend(syn.create, { +// Add the mouse kind of events +syn.helpers.extend(syn.events.kinds, { mouse: { options: function (type, options, element) { var doc = document.documentElement, @@ -98,7 +93,7 @@ h.extend(syn.create, { //browser might not be loaded yet (doing support code) left = syn.mouse.browser && syn.mouse.browser.left[type], right = syn.mouse.browser && syn.mouse.browser.right[type]; - return h.extend({ + return syn.helpers.extend({ bubbles: true, cancelable: true, view: window, @@ -107,25 +102,25 @@ h.extend(syn.create, { screenY: 1, clientX: options.clientX || center[0] - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0), clientY: options.clientY || center[1] - (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0), - ctrlKey: !! syn.key.ctrlKey, - altKey: !! syn.key.altKey, - shiftKey: !! syn.key.shiftKey, - metaKey: !! syn.key.metaKey, + ctrlKey: !! syn.keysBeingHeldDown.ctrlKey, + altKey: !! syn.keysBeingHeldDown.altKey, + shiftKey: !! syn.keysBeingHeldDown.shiftKey, + metaKey: !! syn.keysBeingHeldDown.metaKey, button: left && left.button !== null ? left.button : right && right.button || (type === 'contextmenu' ? 2 : 0), relatedTarget: document.documentElement }, options); }, - event: function (type, defaults, element) { //Everyone Else - var doc = getWin(element) + create: function (type, defaults, element) { //Everyone Else + var doc = syn.helpers.getWindow(element) .document || document, event; if (doc.createEvent) { try { defaults.view = doc.defaultView; - + /* TODO: Eventually replace doc.createEvent / initMouseEvent down below (its deprecated ) https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent - + Replace it with this: event = new MouseEvent(type, defaults); */ @@ -139,19 +134,35 @@ h.extend(syn.create, { defaults.shiftKey, defaults.metaKey, defaults.button, defaults.relatedTarget); } catch (e) { - event = h.createBasicStandardEvent(type, defaults, doc); + event = syn.helpers.createBasicStandardEvent(type, defaults, doc); } event.synthetic = true; return event; } else { try { - event = h.createEventObject(type, defaults, element); + event = syn.helpers.createEventObject(type, defaults, element); } catch (e) {} return event; } } + } +}); + +// add type specific overwrites +syn.helpers.extend(syn.events.types, { + mousedown: { + setup: function (type, options, element) { + var nn = element.nodeName.toLowerCase(); + //we have to auto prevent default to prevent freezing error in safari + if (syn.browser.safari && (nn === "select" || nn === "option")) { + options._autoPrevent = true; + } + }, + default: function (options) { + syn.trigger(this, "focus", {}); + } }, click: { setup: function (type, options, element) { @@ -165,10 +176,10 @@ h.extend(syn.create, { element.checked = !element.checked; } if (type === "radio") { - //do the checks manually + //do the checks manually if (!element.checked) { //do nothing, no change try { - syn.data(element, "radioChanged", true); + syn.helpers.data(element, "radioChanged", true); } catch (e) {} element.checked = true; } @@ -178,7 +189,7 @@ h.extend(syn.create, { if (nodeName === "a" && element.href && !/^\s*javascript:/.test(element.href)) { //save href - syn.data(element, "href", element.href); + syn.helpers.data(element, "href", element.href); //remove b/c safari/opera will open a new tab instead of changing the page // this has been removed because newer versions don't have this problem @@ -204,20 +215,86 @@ h.extend(syn.create, { //shouldn't this wait on triggering //change? element.parentNode.selectedIndex = i; - syn.data(element, "createChange", true); + syn.helpers.data(element, "createChange", true); } } - } - }, - mousedown: { - setup: function (type, options, element) { - var nn = element.nodeName.toLowerCase(); - //we have to auto prevent default to prevent freezing error in safari - if (syn.browser.safari && (nn === "select" || nn === "option")) { - options._autoPrevent = true; + }, + default: function () { + // prevents the access denied issue in IE if the click causes the element to be destroyed + var element = this, + href, type, createChange, radioChanged, nodeName, scope; + try { + href = element.href; + type = element.type; + createChange = syn.helpers.data(element, "createChange"); + radioChanged = syn.helpers.data(element, "radioChanged"); + scope = syn.helpers.getWindow(element); + nodeName = element.nodeName.toLowerCase(); + } catch (e) { + return; + } + //get old values + + //this code was for restoring the href attribute to prevent popup opening + //if ((href = syn.helpers.data(element, "href"))) { + // element.setAttribute('href', href) + //} + + //run href javascript + if (!syn.support.linkHrefJS && /^\s*javascript:/.test(href)) { + //eval js + var code = href.replace(/^\s*javascript:/, ""); + + //try{ + if (code !== "//" && code.indexOf("void(0)") === -1) { + if (window.selenium) { + eval("with(selenium.browserbot.getCurrentWindow()){" + code + "}"); + } else { + eval("with(scope){" + code + "}"); + } + } + } + + //submit a form + if (!(syn.support.clickSubmits) && ((nodeName === "input" || + nodeName === "button") && + type === "submit")) { + + var form = syn.helpers.closest(element, "form"); + if (form) { + syn.trigger(form, "submit", {}); + } + + } + //follow a link, probably needs to check if in an a. + if (nodeName === "a" && element.href && !/^\s*javascript:/.test(href)) { + scope.location.href = href; + + } + + //change a checkbox + if (nodeName === "input" && type === "checkbox") { + + //if(!syn.support.clickChecks && !syn.support.changeChecks){ + // element.checked = !element.checked; + //} + if (!syn.support.clickChanges) { + syn.trigger(element, "change", {}); + } + } + + //change a radio button + if (nodeName === "input" && type === "radio") { // need to uncheck others if not checked + if (radioChanged && !syn.support.radioClickChanges) { + syn.trigger(element, "change", {}); + } + } + // change options + if (nodeName === "option" && createChange) { + syn.trigger(element.parentNode, "change", {}); //does not bubble + syn.helpers.data(element, "createChange", false); } } } }); - diff --git a/src/mouse.support.js b/src/mouse.support.js index e43dc51f..1d8f1981 100644 --- a/src/mouse.support.js +++ b/src/mouse.support.js @@ -4,39 +4,39 @@ require('./mouse'); (function checkSupport() { if (!document.body) { - return syn.schedule(checkSupport, 1); + return syn.helpers.schedule(checkSupport, 1); } window.__synthTest = function () { syn.support.linkHrefJS = true; }; - + var div = document.createElement("div"), checkbox, submit, form, select; - + div.innerHTML = "
" + "" + "" + "" + "" + "" + "" + "" + "" + "
"; document.documentElement.appendChild(div); form = div.firstChild; checkbox = form.childNodes[0]; submit = form.childNodes[2]; select = form.getElementsByTagName('select')[0]; - + //trigger click for linkHrefJS support, childNodes[6] === anchor syn.trigger(form.childNodes[6], 'click', {}); - + checkbox.checked = false; checkbox.onchange = function () { syn.support.clickChanges = true; }; - + syn.trigger(checkbox, "click", {}); syn.support.clickChecks = checkbox.checked; - + checkbox.checked = false; - + syn.trigger(checkbox, "change", {}); - + syn.support.changeChecks = checkbox.checked; - + form.onsubmit = function (ev) { if (ev.preventDefault) { ev.preventDefault(); @@ -45,36 +45,33 @@ require('./mouse'); return false; }; syn.trigger(submit, "click", {}); - + form.childNodes[1].onchange = function () { syn.support.radioClickChanges = true; }; syn.trigger(form.childNodes[1], "click", {}); - - syn.bind(div, 'click', function onclick() { + + syn.helpers.bind(div, 'click', function onclick() { syn.support.optionClickBubbles = true; - syn.unbind(div, 'click', onclick); + syn.helpers.unbind(div, 'click', onclick); }); syn.trigger(select.firstChild, "click", {}); - - syn.support.changeBubbles = syn.eventSupported('change'); - + + syn.support.changeBubbles = syn.helpers.eventSupported('change'); + //test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this div.onclick = function () { syn.support.mouseDownUpClicks = true; }; syn.trigger(div, "mousedown", {}); syn.trigger(div, "mouseup", {}); - + document.documentElement.removeChild(div); - + // register whether or not the current browser supports either pointer or touch events - syn.support.pointerEvents = (syn.eventSupported('pointerdown')); - syn.support.touchEvents = (syn.eventSupported('touchstart')); - + syn.support.pointerEvents = (syn.helpers.eventSupported('pointerdown')); + syn.support.touchEvents = (syn.helpers.eventSupported('touchstart')); + //check stuff syn.support.ready++; }()); - - - diff --git a/src/synthetic.js b/src/synthetic.js index 8f0e505c..c7165371 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -2,228 +2,114 @@ //allow for configuration of syn var opts = window.syn ? window.syn : {}; -var extend = function (d, s) { - var p; - for (p in s) { - d[p] = s[p]; - } - return d; -}, - // only uses browser detection for dispatching proper events - browser = { - msie: (!!(window.attachEvent && !window.opera) || (navigator.userAgent.indexOf('Trident/') > -1)), - opera: !! window.opera, - webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1, - gecko: navigator.userAgent.indexOf('Gecko') > -1, - mobilesafari: !! navigator.userAgent.match(/Apple.*Mobile.*Safari/), - rhino: navigator.userAgent.match(/Rhino/) && true, - chrome: !!window.chrome && !!window.chrome.webstore - }, - createEventObject = function (type, options, element) { - var event = element.ownerDocument.createEventObject(); - return extend(event, options); - }, - data = {}, +var data = {}, id = 1, - expando = "_synthetic" + new Date() - .getTime(), - bind, unbind, schedule, key = /keypress|keyup|keydown/, - page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/, + expando = "_synthetic" + new Date().getTime(), + //this is maintained so we can click on html and blur the active element - activeElement, + activeElement; - /** - * @class syn - * @download funcunit/dist/syn.js - * @test funcunit/synthetic/qunit.html - * syn is used to simulate user actions. It creates synthetic events and - * performs their default behaviors. - * - *

Basic Use

- * The following clicks an input element with id='description' - * and then types 'Hello World'. - * - @codestart - syn.click('description', {}) - .type("Hello World") - @codeend - *

User Actions and Events

- *

syn is typically used to simulate user actions as opposed to triggering events. Typing characters - * is an example of a user action. The keypress that represents an 'a' - * character being typed is an example of an event. - *

- *

- * While triggering events is supported, it's much more useful to simulate actual user behavior. The - * following actions are supported by syn: - *

- * - * All actions run asynchronously. - * Click on the links above for more - * information on how to use the specific action. - *

Asynchronous Callbacks

- * Actions don't complete immediately. This is almost - * entirely because focus() - * doesn't run immediately in IE. - * If you provide a callback function to syn, it will - * be called after the action is completed. - *
The following checks that "Hello World" was entered correctly: - @codestart - syn.click('description', {}) - .type("Hello World", function(){ - - ok("Hello World" == document.getElementById('description').value) - }) - @codeend -

Asynchronous Chaining

-

You might have noticed the [syn.prototype.then then] method. It provides chaining - so you can do a sequence of events with a single (final) callback. -

- If an element isn't provided to then, it uses the previous syn's element. -

- The following does a lot of stuff before checking the result: - @codestart - syn.type('title', 'ice water') - .type('description', 'ice and water') - .click('create', {}) - .drag('newRecipe', {to: 'favorites'}, - function(){ - ok($('#newRecipe').parents('#favorites').length); - }) - @codeend - -

jQuery Helper

- If jQuery is present, syn adds a triggersyn helper you can use like: - @codestart - $("#description").triggersyn("type","Hello World"); - @codeend - *

Key Event Recording

- *

Every browser has very different rules for dispatching key events. - * As there is no way to feature detect how a browser handles key events, - * synthetic uses a description of how the browser behaves generated - * by a recording application.

- *

- * If you want to support a browser not currently supported, you can - * record that browser's key event description and add it to - * syn.key.browsers by it's navigator agent. - *

- @codestart - syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = { - 'prevent': - {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, - 'character': - { ... } - } - @codeend - *

Limitations

- * syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+. - * With FF 1+, drag / move events are only partially supported. They will - * not trigger mouseover / mouseout events.
- * Safari crashes when a mousedown is triggered on a select. syn will not - * create this event. - *

Contributing to syn

- * Have we missed something? We happily accept patches. The following are - * important objects and properties of syn: - * - *

Roll Your Own Functional Test Framework

- *

syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit]. - * But, we've purposely made syn work without any dependencies in the hopes that other frameworks or - * testing solutions can use it as well. - *

- * @constructor - * @signature `syn(type, element, options, callback)` - * Creates a synthetic event on the element. - * @param {Object} type - * @param {HTMLElement} element - * @param {Object} options - * @param {Function} callback - * @return {syn} returns the syn object. - */ - syn = function (type, element, options, callback) { - return (new syn.init(type, element, options, callback)); - }; -syn.config = opts; -// helper for supporting IE8 and below: -// focus will throw in some circumnstances, like element being invisible -syn.__tryFocus = function tryFocus(element) { - try { - element.focus(); - } catch (e) {} -}; +var syn = { + // ## User actions + // These are the methods that cause multiple actual events to happen. + // Click here is for the user-action, not the actual event. -bind = function (el, ev, f) { - return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent("on" + ev, f); -}; -unbind = function (el, ev, f) { - return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent("on" + ev, f); -}; + // ## Event dispatching methods -schedule = syn.config.schedule || function (fn, ms) { - setTimeout(fn, ms); -}; -/** - * @Static - */ -extend(syn, { /** - * Creates a new synthetic event instance - * @hide - * @param {String} type - * @param {HTMLElement} element - * @param {Object} options - * @param {Function} callback + * Trigger will attempt to create and dispatch a SINGLE event on an element. + * It will also run any registered default actions. + * + * It looks in syn.events for event configuration. The configuration can + * either exist on the specific event or the event "kind" (key, page, or mouse). + * + * Creates a synthetic event and dispatches it on the element. + * This will run any default actions for the element. + * Typically you want to use syn, but if you want the return value, use this. */ - init: function (type, element, options, callback) { - var args = syn.args(options, element, callback), - self = this; - this.queue = []; - this.element = args.element; - - //run event - if (typeof this[type] === "function") { - this[type](args.element, args.options, function (defaults, el) { - if (args.callback) { - args.callback.apply(self, arguments); - } - self.done.apply(self, arguments); - }); + trigger: function (element, type, options) { + if (!options) { + options = {}; + } + var eventKinds = syn.events.kinds, + eventTypes = syn.events.types, + kind = eventKinds.key.events[type] ? 'key' : (eventKinds.page.events[type] ? 'page' : 'mouse'), + eventType = eventTypes[type], + setup = eventType && eventType.setup, + eventKind = eventKinds[kind]; + + var event, ret, autoPrevent, dispatchEl = element; + + //any setup code? + if (syn.support.ready === 2 && setup) { + setup(type, options, element); + } + + autoPrevent = options._autoPrevent; + //get kind + delete options._autoPrevent; + + if ( eventType && eventType.create ) { + ret = eventType && eventType.create(type, options, element); } else { - this.result = syn.trigger(args.element, type, args.options); - if (args.callback) { - args.callback.call(this, args.element, this.result); + //convert options + if(typeof eventKind.options === "function") { + options = eventKind.options(type, options, element); + } else if(eventKind.options) { + options = syn.helpers.extend(eventKind.options); } + + if (!syn.support.changeBubbles && /option/i.test(element.nodeName)) { + dispatchEl = element.parentNode; //jQuery expects clicks on select + } + + //create the event + event = eventKind.create(type, options, dispatchEl); + + //send the event + ret = syn.dispatch(event, dispatchEl, type, autoPrevent); } - }, - jquery: function (el, fast) { - if (window.FuncUnit && window.FuncUnit.jQuery) { - return window.FuncUnit.jQuery; + + if (ret && syn.support.ready === 2 && eventType && eventType.default) { + eventType.default.call(element, options, autoPrevent); } - if (el) { - return syn.helpers.getWindow(el) - .jQuery || window.jQuery; + return ret; + }, + + // Triggers an event on an element, returns true if default events should be run + dispatch: function (event, element, type, autoPrevent) { + // dispatchEvent doesn't always work in IE (mostly in a popup) + if (element.dispatchEvent && event) { + var preventDefault = event.preventDefault, + prevents = autoPrevent ? -1 : 0; + + //automatically prevents the default behavior for this event + //this is to protect agianst nasty browser freezing bug in safari + if (autoPrevent) { + syn.helpers.bind(element, type, function ontype(ev) { + ev.preventDefault(); + syn.helpers.unbind(this, type, ontype); + }); + } + + event.preventDefault = function () { + prevents++; + if (++prevents > 0) { + preventDefault.apply(this, []); + } + }; + element.dispatchEvent(event); + return prevents <= 0; } else { - return window.jQuery; + try { + window.event = event; + } catch (e) {} + //source element makes sure element is still in the document + return element.sourceIndex <= 0 || (element.fireEvent && element.fireEvent('on' + type, event)); } }, - /** - * Returns an object with the args for a syn. - * @hide - * @return {Object} - */ args: function () { var res = {}, i = 0; @@ -235,174 +121,197 @@ extend(syn, { } else if (arguments[i] && arguments[i].nodeName) { res.element = arguments[i]; } else if (res.options && typeof arguments[i] === 'string') { //we can get by id - res.element = document.getElementById(arguments[i]); + res.element = document.querySelector(arguments[i]); } else if (arguments[i]) { res.options = arguments[i]; } } return res; }, - click: function (element, options, callback) { - syn('click!', element, options, callback); + + // ## Configuration based information. + + //Store if these keys are being held down + keysBeingHeldDown: { + ctrlKey: null, + altKey: null, + shiftKey: null, + metaKey: null }, + // a placeholder for key behavior to be added + key: {}, + mouse: {}, + + + // The following properties are used to make sure the right behavior happens. + + // how to create specific events if the default way isn't good enough. + + // {[type]: {create,default}} + /** - * @hide - * @attribute defaults * Default actions for events. Each default function is called with this as its * element. It should return true if a timeout * should happen after it. If it returns an element, a timeout will happen * and the next event will happen on that element. */ - defaults: { - focus: function focus() { - if (!syn.support.focusChanges) { - var element = this, - nodeName = element.nodeName.toLowerCase(); - syn.data(element, "syntheticvalue", element.value); - - //TODO, this should be textarea too - //and this might be for only text style inputs ... hmmmmm .... - if (nodeName === "input" || nodeName === "textarea") { - bind(element, "blur", function blur() { - if (syn.data(element, "syntheticvalue") !== element.value) { - - syn.trigger(element, "change", {}); + events: { + kinds: { + page: { + events: toMap("load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll"), + create: function (type, options, element) { + var doc = syn.helpers.getWindow(element) + .document || document, + event; + if (doc.createEvent) { + event = doc.createEvent("Events"); + + event.initEvent(type, true, true); + return event; + } else { + try { + event = syn.helpers.createEventObject(type, options, element); + } catch (e) {} + return event; + } + } + }, + key: { + events: toMap("keypress|keyup|keydown") + } + }, + types: { + focus: { + create: function (type, options, element) { + syn.helpers.onParents(element, function (el) { + if (syn.helpers.isFocusable(el)) { + if (el.nodeName.toLowerCase() !== 'html') { + syn.helpers.tryFocus(el); + activeElement = el; + } else if (activeElement) { + // TODO: The HTML element isn't focasable in IE, but it is + // in FF. We should detect this and do a true focus instead + // of just a blur + var doc = syn.helpers.getWindow(element) + .document; + if (doc !== window.document) { + return false; + } else if (doc.activeElement) { + doc.activeElement.blur(); + activeElement = null; + } else { + activeElement.blur(); + activeElement = null; + } + + } + return false; } - unbind(element, "blur", blur); }); + return true; + }, + default: function focus() { + if (!syn.support.focusChanges) { + var element = this, + nodeName = element.nodeName.toLowerCase(); + syn.helpers.data(element, "syntheticvalue", element.value); + + //TODO, this should be textarea too + //and this might be for only text style inputs ... hmmmmm .... + if (nodeName === "input" || nodeName === "textarea") { + syn.helpers.bind(element, "blur", function blur() { + if (syn.helpers.data(element, "syntheticvalue") !== element.value) { + + syn.trigger(element, "change", {}); + } + syn.helpers.unbind(element, "blur", blur); + }); + } + } } - } - }, - submit: function () { - syn.onParents(this, function (el) { - if (el.nodeName.toLowerCase() === 'form') { - el.submit(); - return false; + }, + submit: { + default: function () { + syn.helpers.onParents(this, function (el) { + if (el.nodeName.toLowerCase() === 'form') { + el.submit(); + return false; + } + }); } - }); - } - }, - changeOnBlur: function (element, prop, value) { - - bind(element, "blur", function onblur() { - if (value !== element[prop]) { - syn.trigger(element, "change", {}); } - unbind(element, "blur", onblur); - }); - - }, - /** - * Returns the closest element of a particular type. - * @hide - * @param {Object} el - * @param {Object} type - */ - closest: function (el, type) { - while (el && el.nodeName.toLowerCase() !== type.toLowerCase()) { - el = el.parentNode; - } - return el; - }, - /** - * adds jQuery like data (adds an expando) and data exists FOREVER :) - * @hide - * @param {Object} el - * @param {Object} key - * @param {Object} value - */ - data: function (el, key, value) { - var d; - if (!el[expando]) { - el[expando] = id++; - } - if (!data[el[expando]]) { - data[el[expando]] = {}; - } - d = data[el[expando]]; - if (value) { - data[el[expando]][key] = value; - } else { - return data[el[expando]][key]; - } - }, - /** - * Calls a function on the element and all parents of the element until the function returns - * false. - * @hide - * @param {Object} el - * @param {Object} func - */ - onParents: function (el, func) { - var res; - while (el && res !== false) { - res = func(el); - el = el.parentNode; } - return el; }, - //regex to match focusable elements - focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i, - /** - * Returns if an element is focusable - * @hide - * @param {Object} elem - */ - isFocusable: function (elem) { - var attributeNode; - // IE8 Standards doesn't like this on some elements - if (elem.getAttributeNode) { - attributeNode = elem.getAttributeNode("tabIndex"); - } - return this.focusable.test(elem.nodeName) || - (attributeNode && attributeNode.specified) && - syn.isVisible(elem); - }, - /** - * Returns if an element is visible or not - * @hide - * @param {Object} elem - */ - isVisible: function (elem) { - return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight); - }, - /** - * Gets the tabIndex as a number or null - * @hide - * @param {Object} elem - */ - tabIndex: function (elem) { - var attributeNode = elem.getAttributeNode("tabIndex"); - return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0); + + // only uses browser detection for dispatching proper events + browser: { + msie: (!!(window.attachEvent && !window.opera) || (navigator.userAgent.indexOf('Trident/') > -1)), + opera: !! window.opera, + webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1, + gecko: navigator.userAgent.indexOf('Gecko') > -1, + mobilesafari: !! navigator.userAgent.match(/Apple.*Mobile.*Safari/), + rhino: navigator.userAgent.match(/Rhino/) && true, + chrome: !!window.chrome && !!window.chrome.webstore }, - bind: bind, - unbind: unbind, /** - * @function syn.schedule schedule() - * @param {Function} fn Function to be ran - * @param {Number} ms Milliseconds to way before calling fn - * @signature `syn.schedule(fn, ms)` - * @parent config + * Feature detected properties of a browser's event system. + * Support has the following properties: + * + * - `backspaceWorks` - typing a backspace removes a character + * - `clickChanges` - clicking on an option element creates a change event. + * - `clickSubmits` - clicking on a form button submits the form. + * - `focusChanges` - focus/blur creates a change event. + * - `keypressOnAnchorClicks` - Keying enter on an anchor triggers a click. + * - `keypressSubmits` - enter key submits + * - `keyCharacters` - typing a character shows up + * - `keysOnNotFocused` - enters keys when not focused. + * - `linkHrefJS` - An achor's href JavaScript is run. + * - `mouseDownUpClicks` - A mousedown followed by mouseup creates a click event. + * - `mouseupSubmits` - a mouseup on a form button submits the form. + * - `pointerEvents` - does this browser natively support pointer events (for newer browsers). + * - `radioClickChanges` - clicking a radio button changes the radio. + * - `tabKeyTabs` - A tab key changes tabs. + * - `textareaCarriage` - a new line in a textarea creates a carriage return. + * - `touchEvents` - does this browser natively support touch events (for older mobile browsers, mostly). * - * Schedules a function to be ran later. - * Must be registered prior to syn loading, otherwise `setTimeout` will be - * used as the scheduler. - * @codestart - * syn = { - * schedule: function(fn, ms) { - * Platform.run.later(fn, ms); - * } - * }; - * @codeend */ - schedule: schedule, - browser: browser, - //some generic helpers + support: { + clickChanges: false, + clickSubmits: false, + keypressSubmits: false, + mouseupSubmits: false, + radioClickChanges: false, + focusChanges: false, + linkHrefJS: false, + keyCharacters: false, + backspaceWorks: false, + mouseDownUpClicks: false, + tabKeyTabs: false, + keypressOnAnchorClicks: false, + optionClickBubbles: false, + pointerEvents: false, + touchEvents: false, + ready: 0 + }, + + // these helpers shouldn't be used directly helpers: { - createEventObject: createEventObject, + toMap: toMap, + extend: function (d, s) { + var p; + for (p in s) { + d[p] = s[p]; + } + return d; + }, + createEventObject: function (type, options, element) { + var event = element.ownerDocument.createEventObject(); + return syn.helpers.extend(event, options); + }, createBasicStandardEvent: function (type, defaults, doc) { var event; try { @@ -411,7 +320,7 @@ extend(syn, { event = doc.createEvent("UIEvents"); } finally { event.initEvent(type, true, true); - extend(event, defaults); + syn.helpers.extend(event, defaults); } return event; }, @@ -427,9 +336,10 @@ extend(syn, { getWindow: function (element) { if (element.ownerDocument) { return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; + } else if(element.defaultView) { + return element.defaultView; } }, - extend: extend, scrollOffset: function (win, set) { var doc = win.document.documentElement, body = win.document.body; @@ -459,466 +369,139 @@ extend(syn, { }; }, addOffset: function (options, el) { - var jq = syn.jquery(el), - off; - if (typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq) { - el = jq(el); - off = el.offset(); - options.pageX = off.left + el.width() / 2; - options.pageY = off.top + el.height() / 2; - } - } - }, - // place for key data - key: { - ctrlKey: null, - altKey: null, - shiftKey: null, - metaKey: null - }, - //triggers an event on an element, returns true if default events should be run - /** - * Dispatches an event and returns true if default events should be run. - * @hide - * @param {Object} event - * @param {Object} element - * @param {Object} type - * @param {Object} autoPrevent - */ - dispatch: function (event, element, type, autoPrevent) { - // dispatchEvent doesn't always work in IE (mostly in a popup) - if (element.dispatchEvent && event) { - var preventDefault = event.preventDefault, - prevents = autoPrevent ? -1 : 0; - - //automatically prevents the default behavior for this event - //this is to protect agianst nasty browser freezing bug in safari - if (autoPrevent) { - bind(element, type, function ontype(ev) { - ev.preventDefault(); - unbind(this, type, ontype); - }); + var rect; + if (typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined) { + rect = el.getBoundingClientRect(); + options.pageX = syn.helpers.getWindow(el).scrollX + rect.left + rect.width / 2; + options.pageY = syn.helpers.getWindow(el).scrollY + rect.top + rect.height / 2; } - - event.preventDefault = function () { - prevents++; - if (++prevents > 0) { - preventDefault.apply(this, []); - } - }; - element.dispatchEvent(event); - return prevents <= 0; - } else { + return options; + }, + tryFocus: function tryFocus(element) { try { - window.event = event; + element.focus(); } catch (e) {} - //source element makes sure element is still in the document - return element.sourceIndex <= 0 || (element.fireEvent && element.fireEvent('on' + type, event)); - } - }, - /** - * @attribute - * @hide - * An object of eventType -> function that create that event. - */ - create: { - //-------- PAGE EVENTS --------------------- - page: { - event: function (type, options, element) { - var doc = syn.helpers.getWindow(element) - .document || document, - event; - if (doc.createEvent) { - event = doc.createEvent("Events"); - - event.initEvent(type, true, true); - return event; - } else { - try { - event = createEventObject(type, options, element); - } catch (e) {} - return event; - } - } }, - // unique events - focus: { - event: function (type, options, element) { - syn.onParents(element, function (el) { - if (syn.isFocusable(el)) { - if (el.nodeName.toLowerCase() !== 'html') { - syn.__tryFocus(el); - activeElement = el; - } else if (activeElement) { - // TODO: The HTML element isn't focasable in IE, but it is - // in FF. We should detect this and do a true focus instead - // of just a blur - var doc = syn.helpers.getWindow(element) - .document; - if (doc !== window.document) { - return false; - } else if (doc.activeElement) { - doc.activeElement.blur(); - activeElement = null; - } else { - activeElement.blur(); - activeElement = null; - } - - } - return false; - } - }); - return true; - } - } - }, - /** - * @attribute support - * @hide - * - * Feature detected properties of a browser's event system. - * Support has the following properties: - * - * - `backspaceWorks` - typing a backspace removes a character - * - `clickChanges` - clicking on an option element creates a change event. - * - `clickSubmits` - clicking on a form button submits the form. - * - `focusChanges` - focus/blur creates a change event. - * - `keypressOnAnchorClicks` - Keying enter on an anchor triggers a click. - * - `keypressSubmits` - enter key submits - * - `keyCharacters` - typing a character shows up - * - `keysOnNotFocused` - enters keys when not focused. - * - `linkHrefJS` - An achor's href JavaScript is run. - * - `mouseDownUpClicks` - A mousedown followed by mouseup creates a click event. - * - `mouseupSubmits` - a mouseup on a form button submits the form. - * - `pointerEvents` - does this browser natively support pointer events (for newer browsers). - * - `radioClickChanges` - clicking a radio button changes the radio. - * - `tabKeyTabs` - A tab key changes tabs. - * - `textareaCarriage` - a new line in a textarea creates a carriage return. - * - `touchEvents` - does this browser natively support touch events (for older mobile browsers, mostly). - * - * - */ - support: { - clickChanges: false, - clickSubmits: false, - keypressSubmits: false, - mouseupSubmits: false, - radioClickChanges: false, - focusChanges: false, - linkHrefJS: false, - keyCharacters: false, - backspaceWorks: false, - mouseDownUpClicks: false, - tabKeyTabs: false, - keypressOnAnchorClicks: false, - optionClickBubbles: false, - pointerEvents: false, - touchEvents: false, - ready: 0 - }, - /** - * @function syn.trigger trigger() - * @parent actions - * @signature `syn.trigger(element, type, options)` - * Creates a synthetic event and dispatches it on the element. - * This will run any default actions for the element. - * Typically you want to use syn, but if you want the return value, use this. - * @param {HTMLElement} element - * @param {String} type - * @param {Object} options - * @return {Boolean} true if default events were run, false if otherwise. - */ - trigger: function (element, type, options) { - if (!options) { - options = {}; - } - - var create = syn.create, - setup = create[type] && create[type].setup, - kind = key.test(type) ? 'key' : (page.test(type) ? "page" : "mouse"), - createType = create[type] || {}, - createKind = create[kind], - event, ret, autoPrevent, dispatchEl = element; - - //any setup code? - if (syn.support.ready === 2 && setup) { - setup(type, options, element); - } - autoPrevent = options._autoPrevent; - //get kind - delete options._autoPrevent; + bind: function (el, ev, f) { + return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent("on" + ev, f); + }, + unbind: function (el, ev, f) { + return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent("on" + ev, f); + }, - if (createType.event) { - ret = createType.event(type, options, element); - } else { - //convert options - options = createKind.options ? createKind.options(type, options, element) : options; + schedule: function (fn, ms) { + setTimeout(fn, ms); + }, + schedulePromise: function(time) { + return new Promise(function(resolve){ + setTimeout(resolve, time); + }); + }, + // Fire a change event if the element is blured and there's a new value + changeOnBlur: function (element, prop, value) { + syn.helpers.bind(element, "blur", function onblur() { + if (value !== element[prop]) { + syn.trigger(element, "change", {}); + } + syn.helpers.unbind(element, "blur", onblur); + }); - if (!syn.support.changeBubbles && /option/i.test(element.nodeName)) { - dispatchEl = element.parentNode; //jQuery expects clicks on select + }, + // Returns the closest element of a particular type. + closest: function (el, type) { + while (el && el.nodeName.toLowerCase() !== type.toLowerCase()) { + el = el.parentNode; + } + return el; + }, + // adds jQuery like data (adds an expando) and data exists FOREVER :) + // This could start using weakmap + data: function (el, key, value) { + if (!el[expando]) { + el[expando] = id++; + } + if (!data[el[expando]]) { + data[el[expando]] = {}; + } + if (value) { + data[el[expando]][key] = value; + } else { + return data[el[expando]][key]; + } + }, + /** + * Calls a function on the element and all parents of the element until the function returns + * false. + */ + onParents: function (el, func) { + var res; + while (el && res !== false) { + res = func(el); + el = el.parentNode; + } + return el; + }, + /** + * Returns if an element is focusable + */ + isFocusable: function (elem) { + var attributeNode; + + // IE8 Standards doesn't like this on some elements + if (elem.getAttributeNode) { + attributeNode = elem.getAttributeNode("tabIndex"); } - //create the event - event = createKind.event(type, options, dispatchEl); + return syn.helpers.focusable.test(elem.nodeName) || + (attributeNode && attributeNode.specified) && + syn.helpers.isVisible(elem); + }, + //regex to match focusable elements + focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i, + /** + * Returns if an element is visible or not + */ + isVisible: function (elem) { + return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight); + }, + /** + * Gets the tabIndex as a number or null + */ + tabIndex: function (elem) { + var attributeNode = elem.getAttributeNode("tabIndex"); + return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0); + }, + eventSupported: function (eventName) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if (!isSupported) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; - //send the event - ret = syn.dispatch(event, dispatchEl, type, autoPrevent); + return isSupported; } + } +}; - if (ret && syn.support.ready === 2 && syn.defaults[type]) { - syn.defaults[type].call(element, options, autoPrevent); - } - return ret; - }, - - - - - eventSupported: function (eventName) { - var el = document.createElement("div"); - eventName = "on" + eventName; - - var isSupported = (eventName in el); - if (!isSupported) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; - } - el = null; +syn.config = opts; - return isSupported; - } +function toMap(str){ + var map = {}; + str.split("|").forEach( ch => { map[ch] = true; }); + return map; +} -}); -/** - * @Prototype - */ -extend(syn.init.prototype, { - /** - * @function syn.then then() - * @parent chained - *

- * Then is used to chain a sequence of actions to be run one after the other. - * This is useful when many asynchronous actions need to be performed before some - * final check needs to be made. - *

- *

The following clicks and types into the id='age' element and then checks that only numeric characters can be entered.

- *

Example

- * @codestart - * syn('click', 'age', {}) - * .then('type','I am 12',function(){ - * equals($('#age').val(),"12") - * }) - * @codeend - * If the element argument is undefined, then the last element is used. - * - * @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type". - * @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element. - * @param {Object} options Optiosn to pass to the event. - - * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run. - */ - then: function (type, element, options, callback) { - if (syn.autoDelay) { - this.delay(); - } - var args = syn.args(options, element, callback), - self = this; - - //if stack is empty run right away - //otherwise ... unshift it - this.queue.unshift(function (el, prevented) { - - if (typeof this[type] === "function") { - this.element = args.element || el; - this[type](this.element, args.options, function (defaults, el) { - if (args.callback) { - args.callback.apply(self, arguments); - } - self.done.apply(self, arguments); - }); - } else { - this.result = syn.trigger(args.element, type, args.options); - if (args.callback) { - args.callback.call(this, args.element, this.result); - } - return this; - } - }); - return this; - }, - /** - * @function syn.delay delay() - * @parent chained - * Delays the next command a set timeout. - * @param {Number} [timeout] - * @param {Function} [callback] - */ - delay: function (timeout, callback) { - if (typeof timeout === 'function') { - callback = timeout; - timeout = null; - } - timeout = timeout || 600; - var self = this; - this.queue.unshift(function () { - schedule(function () { - if (callback) { - callback.apply(self, []); - } - self.done.apply(self, arguments); - }, timeout); - }); - return this; - }, - done: function (defaults, el) { - if (el) { - this.element = el; - } - if (this.queue.length) { - this.queue.pop() - .call(this, this.element, defaults); - } - }, - /** - * @function syn.click click() - * @parent mouse - * @signature `syn.click(element, options, callback, force)` - * Clicks an element by triggering a mousedown, - * mouseup, - * and a click event. - *

Example

- * @codestart - * syn.click('create', {}, function(){ - * //check something - * }) - * @codeend - * You can also provide the coordinates of the click. - * If jQuery is present, it will set clientX and clientY - * for you. Here's how to set it yourself: - * @codestart - * syn.click( - * 'create', - * {clientX: 20, clientY: 100}, - * function(){ - * //check something - * }) - * @codeend - * You can also provide pageX and pageY and syn will convert it for you. - * @param {HTMLElement} element - * @param {Object} options - * @param {Function} callback - */ - "_click": function (element, options, callback, force) { - syn.helpers.addOffset(options, element); - if(syn.support.pointerEvents){ - syn.trigger(element, 'pointerdown', options); - } - if(syn.support.touchEvents){ - syn.trigger(element, 'touchstart', options); - } - - syn.trigger(element, "mousedown", options); - //timeout is b/c IE is stupid and won't call focus handlers - schedule(function () { - if(syn.support.pointerEvents){ - syn.trigger(element, 'pointerup', options); - } - if(syn.support.touchEvents){ - syn.trigger(element, 'touchend', options); - } - - syn.trigger(element, "mouseup", options); - if (!syn.support.mouseDownUpClicks || force) { - syn.trigger(element, "click", options); - callback(true); - } else { - //we still have to run the default (presumably) - syn.create.click.setup('click', options, element); - syn.defaults.click.call(element); - //must give time for callback - schedule(function () { - callback(true); - }, 1); - } +// helper for supporting IE8 and below: +// focus will throw in some circumnstances, like element being invisible - }, 1); - }, - /** - * @function syn.rightClick rightClick() - * @parent mouse - * @signature `syn.rightClick(element, options, callback)` - * Right clicks in browsers that support it (everyone but opera). - * @param {Object} element - * @param {Object} options - * @param {Object} callback - */ - "_rightClick": function (element, options, callback) { - syn.helpers.addOffset(options, element); - var mouseopts = extend(extend({}, syn.mouse.browser.right.mouseup), options); - if(syn.support.pointerEvents){ - syn.trigger(element, 'pointerdown', mouseopts); - } - - syn.trigger(element, "mousedown", mouseopts); - //timeout is b/c IE is stupid and won't call focus handlers - schedule(function () { - if(syn.support.pointerEvents){ - syn.trigger(element, 'pointerup', mouseopts); - } - syn.trigger(element, "mouseup", mouseopts); - if (syn.mouse.browser.right.contextmenu) { - syn.trigger(element, "contextmenu", extend(extend({}, syn.mouse.browser.right.contextmenu), options)); - } - callback(true); - }, 1); - }, - /** - * @function syn.dblclick dblclick() - * @parent mouse - * @signature `syn.dblclick(element, options, callback)` - * Dblclicks an element. This runs two [syn.click click] events followed by - * a dblclick on the element. - *

Example

- * @codestart - * syn.dblclick('open', {}); - * @codeend - * @param {Object} options - * @param {HTMLElement} element - * @param {Function} callback - */ - "_dblclick": function (element, options, callback) { - syn.helpers.addOffset(options, element); - var self = this; - this._click(element, options, function () { - schedule(function () { - self._click(element, options, function () { - syn.trigger(element, "dblclick", options); - callback(true); - }, true); - }, 2); - - }); - } -}); - -var actions = ["click", "dblclick", "move", "drag", "key", "type", 'rightClick'], - makeAction = function (name) { - syn[name] = function (element, options, callback) { - return syn("_" + name, element, options, callback); - }; - syn.init.prototype[name] = function (element, options, callback) { - return this.then("_" + name, element, options, callback); - }; - }, - i = 0; -for (; i < actions.length; i++) { - makeAction(actions[i]); -} module.exports = syn; diff --git a/test/drag_test_basic.js b/test/drag_test_basic.js index 2e6fcba3..1d5df9c7 100644 --- a/test/drag_test_basic.js +++ b/test/drag_test_basic.js @@ -10,22 +10,21 @@ QUnit.test("Drag Item Upward HTML5", 2, function () { stop(); var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ - testFrame.removeEventListener('load', loadListener); + + testFrame.addEventListener('load', async function loadListener(){ + testFrame.removeEventListener('load', loadListener); var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var draggable = pageUnderTest.querySelector('#drag1'); var target = pageUnderTest.querySelector('#cell_a2'); - - syn.drag(draggable, {to: target}, function () { - - var cell_a2 = pageUnderTest.querySelector('#cell_a2') - var cell_b2 = pageUnderTest.querySelector('#cell_b2') - ok(cell_a2.querySelector('#drag1') != null , "Dragged element expected to exist in node: #cell_a2."); - ok(cell_b2.classList.contains('dragOver'), "MouseOver on expected node: #cell_b2."); - - start(); - }); + + await syn.drag(draggable, {to: target}); + + var cell_a2 = pageUnderTest.querySelector('#cell_a2') + var cell_b2 = pageUnderTest.querySelector('#cell_b2') + ok(cell_a2.querySelector('#drag1') != null , "Dragged element expected to exist in node: #cell_a2."); + ok(cell_b2.classList.contains('dragOver'), "MouseOver on expected node: #cell_b2."); + + start(); }); testFrame.height = 350; @@ -39,22 +38,23 @@ QUnit.test("Drag Item Downward jQuery", 1, function () { stop(); var testFrame = document.getElementById('pageUnderTest'); - + testFrame.addEventListener('load', function loadListener(){ - testFrame.removeEventListener('load', loadListener); + testFrame.removeEventListener('load', loadListener); var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var draggable = pageUnderTest.querySelector('#draggable'); var target = pageUnderTest.querySelector('#dropTarget'); - + // Timeout is required because jQuery isn't ready yet on the target page - setTimeout(function () { - syn.drag(draggable, {to: target}, function () { - var check = pageUnderTest.querySelector('#draggable') - ok(check.classList.contains('leftOfMark'), "jQuery drag did not register."); - - start(); - }); - }, 500); + setTimeout(async function () { + + await syn.drag(draggable, {to: target}); + + var check = pageUnderTest.querySelector('#draggable') + ok(check.classList.contains('leftOfMark'), "jQuery drag did not register."); + + start(); + }, 500); }); testFrame.height = 160; @@ -66,7 +66,7 @@ QUnit.test("Drag Item Downward jQuery", 1, function () { /* TODO: This test is incomplete. If you run it, you should see the "inner" element dragged into the receptive box above. However, the word "inner" should become "outer" since that is the drag element. - + My theory is that we are sending the final drag events by page offset rather than dragdrop target, and that it is "hitting" the element being dragged because we are over the dragdrop target at the time of the drop. */ @@ -74,22 +74,20 @@ QUnit.test("Drag Regressions 1 - cancel and bubble", 1, function () { stop(); var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ - testFrame.removeEventListener('load', loadListener); + + testFrame.addEventListener('load', async function loadListener(){ + testFrame.removeEventListener('load', loadListener); var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var draggable = pageUnderTest.querySelector('#outer'); var target = pageUnderTest.querySelector('#div1'); - - syn.drag(draggable, {to: target}, function () { - - ok(true , "Dragged element expected to exist in node: #cell_a2."); - start(); - }); + + await syn.drag(draggable, {to: target}); + + ok(true , "Dragged element expected to exist in node: #cell_a2."); + start(); }); testFrame.height = 350; testFrame.src = 'testpages/regressions_drag.html'; }); - diff --git a/test/focus_test.js b/test/focus_test.js index 7b9c19d4..0cf40ad6 100644 --- a/test/focus_test.js +++ b/test/focus_test.js @@ -24,21 +24,21 @@ Tests left to add: QUnit.test("Focus from click", 1, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelectorAll('span.focusbox'); - - - syn.click(source[0], function(){ - checkForFocus(source[0], true); - - start(); - }); + + + await syn.click(source[0]); + + checkForFocus(source[0], true); + + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -50,21 +50,21 @@ QUnit.test("Focus from click", 1, function () { QUnit.test("Focus from double-click", 1, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelectorAll('span.focusbox'); - - - syn.dblclick(source[0], function(){ - checkForFocus(source[0], true); - - start(); - }); + + + await syn.dblclick(source[0]); + + checkForFocus(source[0], true); + + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -76,21 +76,21 @@ QUnit.test("Focus from double-click", 1, function () { QUnit.test("Focus from right-click", 1, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelectorAll('span.focusbox'); - - - syn.rightClick(source[0], function(){ - checkForFocus(source[0], true); - - start(); - }); + + + await syn.rightClick(source[0]); + + checkForFocus(source[0], true); + + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -102,24 +102,25 @@ QUnit.test("Focus from right-click", 1, function () { QUnit.test("Focus on next element upon tab", 3, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelectorAll('span.focusbox'); - - - syn.click(source[1], function(){ - checkForFocus(source[1], true); - syn.type(source[1], '\t', function(){ - checkForFocus(source[1], false); - checkForFocus(source[2], true); - start(); - }); - }); + + + await syn.click(source[1],{}); + + checkForFocus(source[1], true); + + await syn.type(source[1], '\t'); + + checkForFocus(source[1], false); + checkForFocus(source[2], true); + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -131,24 +132,25 @@ QUnit.test("Focus on next element upon tab", 3, function () { QUnit.test("Focus wraps when tabbed upon last focusable element", 3, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelectorAll('span.focusbox'); - - - syn.click(source[3], function(){ - checkForFocus(source[3], true); - syn.type(source[3], '\t', function(){ - checkForFocus(source[3], false); - checkForFocus(source[0], true); - start(); - }); - }); + + + await syn.click(source[3], {}); + + checkForFocus(source[3], true); + + await syn.type(source[3], '\t'); + + checkForFocus(source[3], false); + checkForFocus(source[0], true); + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -160,24 +162,24 @@ QUnit.test("Focus wraps when tabbed upon last focusable element", 3, function () QUnit.test("Focus on prev element upon anti-tab", 3, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelectorAll('span.focusbox'); - - - syn.click(source[1], function(){ - checkForFocus(source[1], true); - syn.type(source[1], '[shift]\t[shift-up]', function(){ - checkForFocus(source[1], false); - checkForFocus(source[0], true); - start(); - }); - }); + + + await syn.click(source[1], {}); + + checkForFocus(source[1], true); + await syn.type(source[1], '[shift]\t[shift-up]'); + + checkForFocus(source[1], false); + checkForFocus(source[0], true); + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -189,24 +191,25 @@ QUnit.test("Focus on prev element upon anti-tab", 3, function () { QUnit.test("Focus wraps when anti-tabbed upon first focusable element", 3, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelectorAll('span.focusbox'); - - - syn.click(source[0], function(){ - checkForFocus(source[0], true); - syn.type(source[0], '[shift]\t[shift-up]', function(){ - checkForFocus(source[0], false); - checkForFocus(source[3], true); - start(); - }); - }); + + + await syn.click(source[0], {}); + + checkForFocus(source[0], true); + + await syn.type(source[0], '[shift]\t[shift-up]'); + + checkForFocus(source[0], false); + checkForFocus(source[3], true); + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -218,4 +221,3 @@ QUnit.test("Focus wraps when anti-tabbed upon first focusable element", 3, funct function checkForFocus(elem, expectedState){ ok((elem.classList.contains('hasfocus') == expectedState), "Focus on expected node."); } - diff --git a/test/key_regressions_test.js b/test/key_regressions_test.js index 8f04db68..848f19dd 100644 --- a/test/key_regressions_test.js +++ b/test/key_regressions_test.js @@ -7,23 +7,21 @@ QUnit.module("synthetic/key/regressions"); var frameHeight = 110; var frameUrl = 'testpages/regressions_keyboard.html'; -QUnit.test("Testing Keycodes for !", 2, function () { +QUnit.test("Testing Keycodes for !", 2, async function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#Box1'); - - syn.type(source, "!", function(){ - - ok(pageUnderTest.querySelector('#keycodeOutput').innerHTML == "33"); - ok(pageUnderTest.querySelector('#charcodeOutput').innerHTML == "33"); - start(); - }); + + await syn.type(source, "!"); + ok(pageUnderTest.querySelector('#keycodeOutput').innerHTML == "33"); + ok(pageUnderTest.querySelector('#charcodeOutput').innerHTML == "33"); + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -32,33 +30,31 @@ QUnit.test("Testing Keycodes for !", 2, function () { }); -QUnit.test("Special keycodes for enter on TextBox", 1, function () { +QUnit.test("Special keycodes for enter on TextBox", 1, async function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var keyTarget = pageUnderTest.querySelector('#synthetic'); var output = pageUnderTest.querySelector('#synthetic_events'); var browser = BrowserDetective.getBrowserName(); - - console.log(browser); - - syn.type(keyTarget, "\b\r", function(){ - - var outputText = output.textContent; - var expected = (browser == 'firefox') ? - 'keydown, keypress, input, keyup, keydown, keypress, change, keyup, ' : - 'keydown, input, keyup, keydown, keypress, change, keyup, ' ; - ok(outputText == expected, - 'Recorded events were not as-expected. \n' + - 'Expect: ' + expected + '\n' + - 'Actual: ' + outputText); - start(); - }); + + + await syn.type(keyTarget, "\b\r"); + + var outputText = output.textContent; + var expected = (browser == 'firefox') ? + 'keydown, keypress, input, keyup, keydown, keypress, change, keyup, ' : + 'keydown, input, keyup, keydown, keypress, change, keyup, ' ; + ok(outputText == expected, + 'Recorded events were not as-expected. \n' + + 'Expect: ' + expected + '\n' + + 'Actual: ' + outputText); + start(); }); testFrame.height = 100; @@ -66,38 +62,34 @@ QUnit.test("Special keycodes for enter on TextBox", 1, function () { }); -QUnit.test("Special keycodes for enter on TextArea", 1, function () { +QUnit.test("Special keycodes for enter on TextArea", 1, async function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var keyTarget = pageUnderTest.querySelector('#area'); var output = pageUnderTest.querySelector('#synthetic_events'); var browser = BrowserDetective.getBrowserName(); - - syn.type(keyTarget, "\b\r", function(){ - - var outputText = output.textContent; - - // TODO: Maybe also add "change" at the end, which should occur on blur - var expected = (browser == 'firefox') ? - 'keydown, keypress, input, keyup, keydown, keypress, input, keyup, ' : - 'keydown, input, keyup, keydown, keypress, input, keyup, ' ; - ok(outputText == expected, - 'Recorded events were not as-expected. \n' + - 'Expect: ' + expected + '\n' + - 'Actual: ' + outputText); - start(); - }); + + await syn.type(keyTarget, "\b\r"); + + var outputText = output.textContent; + + // TODO: Maybe also add "change" at the end, which should occur on blur + var expected = (browser == 'firefox') ? + 'keydown, keypress, input, keyup, keydown, keypress, input, keyup, ' : + 'keydown, input, keyup, keydown, keypress, input, keyup, ' ; + ok(outputText == expected, + 'Recorded events were not as-expected. \n' + + 'Expect: ' + expected + '\n' + + 'Actual: ' + outputText); + start(); }); testFrame.height = 100; testFrame.src = 'testpages/regressions_keyboard2.html'; }); - - - diff --git a/test/key_test.js b/test/key_test.js index 2ed4ab15..97aa07cb 100644 --- a/test/key_test.js +++ b/test/key_test.js @@ -24,24 +24,24 @@ QUnit.module("synthetic/key", { QUnit.test("Key Characters", function () { st.g("key") .value = ""; - syn.key("key", "a"); + syn.key("#key", "a"); equal(st.g("key") .value, "a", "a written"); st.g("key") .value = ""; - syn.key("key", "A"); + syn.key("#key", "A"); equal(st.g("key") .value, "A", "A written"); st.g("key") .value = ""; - syn.key("key", "1"); + syn.key("#key", "1"); equal(st.g("key") .value, "1", "1 written"); }); -QUnit.test("Key \\r Submits Forms", 2, function () { +QUnit.test("Key \\r Submits Forms", 2, async function () { var submit = 0, change = 0; st.binder("key", "change", function (ev) { @@ -61,14 +61,16 @@ QUnit.test("Key \\r Submits Forms", 2, function () { return false; }); stop(); - syn.key("key", "\r", function () { - equal(submit, 1, "submit on keypress"); - equal(change, 1, "submit on keypress"); - start(); - }); + + + await syn.key("#key", "\r"); + + equal(submit, 1, "submit on keypress"); + equal(change, 1, "submit on keypress"); + start(); }); -QUnit.test("Key \\r Clicks Links", 1, function () { +QUnit.test("Key \\r Clicks Links", 1, async function () { var clicked = 0; st.binder("focusLink", "click", function (ev) { clicked++; @@ -79,13 +81,13 @@ QUnit.test("Key \\r Clicks Links", 1, function () { return false; }); stop(); - syn.key("focusLink", "\r", function () { - equal(clicked, 1, "clicked"); - start(); - }); + await syn.key("#focusLink", "\r"); + + equal(clicked, 1, "clicked"); + start(); }); -QUnit.test("Key Event Order", 1, function () { +QUnit.test("Key Event Order", 1, async function () { var order = [], recorder = function (ev) { order.push(ev.type); @@ -96,44 +98,46 @@ QUnit.test("Key Event Order", 1, function () { st.binder("key", "input", recorder); st.binder("key", "keyup", recorder); stop(); - syn.key("key", "B", function () { - var expected = ["keydown", "keypress", "keyup"]; - if (syn.support.oninput) { - expected.splice(2, 0, "input"); - } + await syn.key("#key", "B"); + + var expected = ["keydown", "keypress", "keyup"]; + if (syn.support.oninput) { + expected.splice(2, 0, "input"); + } + + deepEqual(order, expected, "Key order is correct"); + start(); - deepEqual(order, expected, "Key order is correct"); - start(); - }); }); -QUnit.test("Key \\r Adds Newline in Textarea", function () { +QUnit.test("Key \\r Adds Newline in Textarea", async function () { st.g('synTextArea') .value = ""; stop(); - syn.type("synTextArea", "ab\rcd", function () { - equal(st.g('synTextArea') - .value.replace("\r", ""), "ab\ncd", "typed new line correctly"); - start(); - }); + + await syn.type("#synTextArea", "ab\rcd"); + console.log("After") + equal(st.g('synTextArea') + .value.replace("\r", ""), "ab\ncd", "typed new line correctly"); + start(); }); -QUnit.test("Key \\b", function () { +QUnit.test("Key \\b", async function () { st.g("key") .value = ""; stop(); - syn.type("key", "abc", function () { - equal(st.g("key") - .value, "abc", "abc written"); - syn.key("key", "\b"); - equal(st.g("key") - .value, "ab", "ab written (key deleted)"); - start(); - }); + await syn.type("#key", "abc"); + + equal(st.g("key") + .value, "abc", "abc written"); + syn.key("#key", "\b"); + equal(st.g("key") + .value, "ab", "ab written (key deleted)"); + start(); }); //tests when the key is inserted -QUnit.test("Key Character Order", function () { +QUnit.test("Key Character Order", async function () { var upVal, pressVal, @@ -152,12 +156,13 @@ QUnit.test("Key Character Order", function () { .value; }); stop(); - syn.key("key", "J", function () { - equal(upVal, "J", "Up Typing works"); - equal(pressVal, "", "Press Typing works"); - equal(downVal, "", "Down Typing works"); - start(); - }); + await syn.key("#key", "J"); + + + equal(upVal, "J", "Up Typing works"); + equal(pressVal, "", "Press Typing works"); + equal(downVal, "", "Down Typing works"); + start(); }); @@ -198,7 +203,7 @@ QUnit.test("page down, page up, home, end", function () { start(); return; } - syn.key("scrolldiv", name); + syn.key("#scrolldiv", name); }; for (var name in keyTest) { if (keyTest.hasOwnProperty(name)) { @@ -250,7 +255,7 @@ QUnit.test("range tests", function () { keyEl.value = "012345"; selectText(keyEl, 1, 3); - syn.key("key", "delete"); + syn.key("#key", "delete"); equal(keyEl.value, "0345", "delete range works"); @@ -258,33 +263,33 @@ QUnit.test("range tests", function () { keyEl.value = "012345"; selectText(keyEl, 2); - syn.key("key", "delete"); + syn.key("#key", "delete"); equal(keyEl.value, "01345", "delete works"); // test character range keyEl.value = "123456"; selectText(keyEl, 1, 3); - syn.key("key", "a"); + syn.key("#key", "a"); equal(keyEl.value, "1a456", "character range works"); // test character key keyEl.value = "123456"; selectText(keyEl, 2); - syn.key("key", "a"); + syn.key("#key", "a"); equal(keyEl.value, "12a3456", "character insertion works"); // test backspace range keyEl.value = "123456"; selectText(keyEl, 1, 3); - syn.key("key", "\b"); + syn.key("#key", "\b"); equal(keyEl.value, "1456", "backspace range works"); // test backspace key keyEl.value = "123456"; selectText(keyEl, 2); - syn.key("key", "\b"); + syn.key("#key", "\b"); equal(keyEl.value, "13456", "backspace works"); // test textarea ranges @@ -317,7 +322,7 @@ QUnit.test("range tests", function () { }); -QUnit.test("Type with tabs", function () { +QUnit.test("Type with tabs", function async () { st.g("qunit-fixture") .innerHTML = "" + @@ -333,102 +338,104 @@ QUnit.test("Type with tabs", function () { }); stop(); //give ie a second to focus - setTimeout(function () { - syn.type('first', '\r\tSecond\tThird\tFourth', function () { - equal(clicked, 1, "clickd first"); - equal(st.g('second') - .value, "Second", "moved to second"); - equal(st.g('third') - .value, "Third", "moved to Third"); - equal(st.g('fourth') - .value, "Fourth", "moved to Fourth"); - start(); - }); + setTimeout(async function () { + await syn.type('#first', '\r\tSecond\tThird\tFourth'); + + equal(clicked, 1, "clicked first"); + equal(st.g('second') + .value, "Second", "moved to second"); + equal(st.g('third') + .value, "Third", "moved to Third"); + equal(st.g('fourth') + .value, "Fourth", "moved to Fourth"); + start(); }, 1); }); -QUnit.test("Type left and right", function () { +QUnit.test("Type left and right", async function () { stop(); - syn.type('key', "012345678[left][left][left]\b", function () { - equal(st.g('key') - .value, "01234678", "left works"); + await syn.type('#key', "012345678[left][left][left]\b"); - syn.type('key', "[right][right]a", function () { - equal(st.g('key') - .value, "0123467a8", "right works"); - start(); - }); + equal(st.g('key') + .value, "01234678", "left works"); - }); + await syn.type('#key', "[right][right]a"); + + equal(st.g('key') + .value, "0123467a8", "right works"); + start(); }); -QUnit.test("Type left and delete", function () { +QUnit.test("Type left and delete", async function () { stop(); - syn.type('key', "123[left][delete]", function () { - equal(st.g('key') - .value, "12", "left delete works"); - start(); - }); + await syn.type('#key', "123[left][delete]"); + equal(st.g('key') + .value, "12", "left delete works"); + start(); }); -QUnit.test("Typing Shift", function () { +QUnit.test("Typing Shift", async function () { stop(); var shift = false; st.binder('key', 'keypress', function (ev) { shift = ev.shiftKey; }); - syn.type('key', "[shift]A[shift-up]", function () { - ok(shift, "Shift key on"); - start(); - }); + await syn.type('#key', "[shift]A[shift-up]"); + + ok(shift, "Shift key on"); + start(); }); -QUnit.test("Typing Shift then clicking", function () { +QUnit.test("Typing Shift then clicking", async function () { stop(); var shift = false; st.binder('inner', 'click', function (ev) { shift = ev.shiftKey; }); - syn.type('key', "[shift]A") - .click('inner', {}) - .type('key', "[shift-up]", function () { - ok(shift, "Shift key on click"); - start(); - }); + + await syn.type('#key', "[shift]A") + await syn.click('#inner', {}) + await syn.type('#key', "[shift-up]"); + + + ok(shift, "Shift key on click"); + start(); + }); -QUnit.test("Typing Shift Left and Right", function () { +QUnit.test("Typing Shift Left and Right", async function () { stop(); - syn.type('key', "012345678[shift][left][left][left][shift-up]\b[left]\b", function () { - equal(st.g('key') - .value, "01235", "shift left works"); + await syn.type('#key', "012345678[shift][left][left][left][shift-up]\b[left]\b"); - syn.type('key', "[left][left][shift][right][right]\b[shift-up]", function () { + equal(st.g('key') + .value, "01235", "shift left works"); + + await syn.type('#key', "[left][left][shift][right][right]\b[shift-up]"); + + + equal(st.g('key').value, "015", "shift right works"); + start(); - equal(st.g('key') - .value, "015", "shift right works"); - start(); - }); - }); }); -QUnit.test("shift characters", function () { +QUnit.test("shift characters", async function () { stop(); - syn.type('key', "@", function () { - equal(st.g('key') - .value, "@", "@ character works"); - start(); - }); + + await syn.type('#key', "@"); + + equal(st.g('key') + .value, "@", "@ character works"); + start(); }); -QUnit.test("shift keycodes", function () { +QUnit.test("shift keycodes", async function () { stop(); var keyIsDown = false; @@ -437,35 +444,35 @@ QUnit.test("shift keycodes", function () { ok(ev.shiftKey, "Shift key functioning. Expected: " + ev.which + ", Actual: "+ev.keyCode); ok(ev.which === ev.keyCode, "which is normalized"); }); - + var keyIsUp = true; st.binder("key", "keyup", function (ev) { keyIsUp = ev.shiftKey; ok(ev.which === ev.keyCode, "which is normalized"); }); - - syn.type('key', "[shift]", function () { - ok(keyIsDown, "shift modifier key pressed successfully"); - syn.type('key', "[shift-up]", function () { - ok(!keyIsUp, "shift modifier key released successfully"); - start(); - }); - }); + await syn.type('#key', "[shift]"); + + ok(keyIsDown, "shift modifier key pressed successfully"); + + await syn.type('#key', "[shift-up]"); + + ok(!keyIsUp, "shift modifier key released successfully"); + start(); }); -QUnit.test("shift practical test", function () { +QUnit.skip("shift practical test", async function () { stop(); - - syn.type('key', "hello [shift]world[shift-up]", function () { - // TODO: Fix this! - //equal(key.value, "hello WORLD", "uppercasing successful while using shift"); - equal(true, true, "Test was not run due to known Syn issue : https://github.com/bitovi/syn/issues/97"); - start(); - }); + + await syn.type('#key', "hello [shift]world[shift-up]"); + + // TODO: Fix this! + //equal(key.value, "hello WORLD", "uppercasing successful while using shift"); + equal(true, true, "Test was not run due to known Syn issue : https://github.com/bitovi/syn/issues/97"); + start(); }); -QUnit.test("ctrl keycodes", function () { +QUnit.test("ctrl keycodes", async function () { stop(); var keyIsDown = false; @@ -474,41 +481,40 @@ QUnit.test("ctrl keycodes", function () { ok(ev.ctrlKey, "Ctrl key functioning. Expected: " + ev.which + ", Actual: "+ev.keyCode); ok(ev.which === ev.keyCode, "which is normalized"); }); - + var keyIsUp = true; st.binder("key", "keyup", function (ev) { keyIsUp = ev.ctrlKey; ok(ev.which === ev.keyCode, "which is normalized"); }); - - syn.type('key', "[ctrl]", function () { - ok(keyIsDown, "ctrl modifier key pressed successfully"); - syn.type('key', "[ctrl-up]", function () { - ok(!keyIsUp, "ctrl modifier key released successfully"); - start(); - }); - }); + await syn.type('#key', "[ctrl]"); + + ok(keyIsDown, "ctrl modifier key pressed successfully"); + + await syn.type('#key', "[ctrl-up]"); + + ok(!keyIsUp, "ctrl modifier key released successfully"); + start(); }); -QUnit.test("ctrl practical test", function () { +QUnit.test("ctrl practical test", async function () { stop(); - - syn.type('key', "Hello World", function () { - ok(key.value, "Hello World"); - equal(key.selectionStart, 11, "pre-selectAll has correct start of 11"); - equal(key.selectionEnd, 11, "pre-selectAll has correct end of 11"); - - syn.type('key', "[ctrl]a[ctrl-up]", function () { - - equal(key.selectionStart, 0, "post-selectAll has correct start of 0"); - equal(key.selectionEnd, 11, "post-selectAll has correct end of 11"); - start(); - }); - }); + + await syn.type('#key', "Hello World"); + + ok(key.value, "Hello World"); + equal(key.selectionStart, 11, "pre-selectAll has correct start of 11"); + equal(key.selectionEnd, 11, "pre-selectAll has correct end of 11"); + + await syn.type('#key', "[ctrl]a[ctrl-up]"); + + equal(key.selectionStart, 0, "post-selectAll has correct start of 0"); + equal(key.selectionEnd, 11, "post-selectAll has correct end of 11"); + start(); }); -QUnit.test("alt keycodes", function () { +QUnit.test("alt keycodes", async function () { stop(); var keyIsDown = false; @@ -517,24 +523,24 @@ QUnit.test("alt keycodes", function () { ok(ev.altKey, "Alt key functioning. Expected: " + ev.which + ", Actual: "+ev.keyCode); ok(ev.which === ev.keyCode, "which is normalized"); }); - + var keyIsUp = true; - st.binder("key", "keyup", function (ev) { + st.binder("key", "keyup", async function (ev) { keyIsUp = ev.altKey; ok(ev.which === ev.keyCode, "which is normalized"); }); - - syn.type('key', "[alt]", function () { - ok(keyIsDown, "alt modifier key pressed successfully"); - syn.type('key', "[alt-up]", function () { - ok(!keyIsUp, "alt modifier key released successfully"); - start(); - }); - }); + await syn.type('#key', "[alt]"); + + ok(keyIsDown, "alt modifier key pressed successfully"); + + await syn.type('#key', "[alt-up]"); + + ok(!keyIsUp, "alt modifier key released successfully"); + start(); }); -QUnit.test("meta keycodes", function () { +QUnit.test("meta keycodes", async function () { stop(); var keyIsDown = false; @@ -543,21 +549,21 @@ QUnit.test("meta keycodes", function () { ok(ev.metaKey, "Meta key functioning. Expected: " + ev.which + ", Actual: "+ev.keyCode); ok(ev.which === ev.keyCode, "which is normalized"); }); - + var keyIsUp = true; st.binder("key", "keyup", function (ev) { keyIsUp = ev.metaKey; ok(ev.which === ev.keyCode, "which is normalized"); }); - - syn.type('key', "[meta]", function () { - ok(keyIsDown, "meta modifier key pressed successfully"); - syn.type('key', "[meta-up]", function () { - ok(!keyIsUp, "meta modifier key released successfully"); - start(); - }); - }); + await syn.type('#key', "[meta]"); + + ok(keyIsDown, "meta modifier key pressed successfully"); + + await syn.type('#key', "[meta-up]"); + + ok(!keyIsUp, "meta modifier key released successfully"); + start(); }); // INSERT TEST disabled because of https://github.com/bitovi/syn/issues/131 @@ -571,28 +577,28 @@ QUnit.test("meta keycodes", function () { // start(); //}); - //syn.type('key', "[insert]", function () {}); + //syn.type('#key', "[insert]", function () {}); //}); // INSERT TEST disabled because of https://github.com/bitovi/syn/issues/131 //QUnit.test("insert practical test", function () { // stop(); - -// syn.type('key', "Hello World", function () { + +// syn.type('#key', "Hello World", function () { // equal(key.value, "Hello World", "Verified initial state"); // selectText(key, 6, 6); // TODO: this actually hangs the test. Should I be using something like insert-up ? - //syn.type('key', "[insert]Universe[insert-up]", function () { + //syn.type('#key', "[insert]Universe[insert-up]", function () { //equal(key.value, "Hello Universe", "Verified modified state"); - + // start(); //}); // }); //}); -QUnit.test("caps keycodes", function () { +QUnit.test("caps keycodes", async function () { stop(); st.binder("key", "keydown", function (ev) { @@ -601,7 +607,7 @@ QUnit.test("caps keycodes", function () { start(); }); - syn.type('key', "[caps]", function () {}); + await syn.type('#key', "[caps]"); }); @@ -609,14 +615,14 @@ QUnit.test("caps keycodes", function () { //QUnit.test("caps practical test", function () { // stop(); - -// syn.type('key', "Hello", function () { + +// syn.type('#key', "Hello", function () { // equal(key.value, "Hello", "Verified initial state"); // TODO: this actually hangs the test. Should I be using something like insert-up ? - //syn.type('key', "[caps] universe[caps]", function () { + //syn.type('#key', "[caps] universe[caps]", function () { // equal(key.value, "Hello UNIVERSE", "Verified modified state"); - + // start(); //}); //}); @@ -624,7 +630,7 @@ QUnit.test("caps keycodes", function () { -test("number key codes", 2, function () { +test("number key codes", 2, async function () { stop(); st.binder("key", "keydown", function (ev) { @@ -633,10 +639,10 @@ test("number key codes", 2, function () { start(); }); - syn.type('key', "[down]", function () {}); + await syn.type('#key', "[down]"); }); -QUnit.test("Key codes of like-keys", function () { +QUnit.test("Key codes of like-keys", async function () { stop(); var keys = { @@ -656,7 +662,7 @@ QUnit.test("Key codes of like-keys", function () { } }; - var testKeyCode = function (key, code) { + var testKeyCode = async function (key, code) { var f; st.binder("key", "keydown", f = function (ev) { st.unbinder("key", "keydown", f); @@ -664,7 +670,7 @@ QUnit.test("Key codes of like-keys", function () { ok(ev.which === ev.keyCode); done(); }); - syn.type("key", "[" + key + "]"); + await syn.type("#key", "[" + key + "]"); }; for (var key in keys) { @@ -672,7 +678,7 @@ QUnit.test("Key codes of like-keys", function () { } }); -QUnit.test("focus moves on keydown to another element", function () { +QUnit.test("focus moves on keydown to another element", async function () { stop(); st.binder("key", "keydown", function (ev) { st.g('synTextArea') @@ -683,29 +689,29 @@ QUnit.test("focus moves on keydown to another element", function () { ok(true, "keypress called"); start(); }); - syn.type('key', "a", function () {}); + await syn.type('#key', "a"); }); -QUnit.test("typing in a number works", function () { +QUnit.test("typing in a number works", async function () { stop(); - syn.type('key', 9999, function () { - equal(st.g('key') - .value, "9999", "typing in numbers works"); - start(); - }); + await syn.type('#key', 9999); + + equal(st.g('key') + .value, "9999", "typing in numbers works"); + start(); }); -QUnit.test("typing in a contenteditable works", function () { +QUnit.test("typing in a contenteditable works", async function () { stop(); - syn.type("editable", "hello world", function () { - var editable = st.g("editable"); - var text = editable.textContent || editable.innerText; - equal(text, "hello world", "Content editable was edited"); - start(); - }); + await syn.type("#editable", "hello world"); + + var editable = st.g("editable"); + var text = editable.textContent || editable.innerText; + equal(text, "hello world", "Content editable was edited"); + start(); }); -QUnit.test("typing in an input type=number works", function() { +QUnit.test("typing in an input type=number works", async function() { stop(); st.g("qunit-fixture").innerHTML = @@ -715,15 +721,14 @@ QUnit.test("typing in an input type=number works", function() { "" + ""; - syn.type("number", 123, function() { - var val = st.g("number").value; - equal(val, "123", "number input was edited"); - start(); - }); + await syn.type("#number", 123); + var val = st.g("number").value; + equal(val, "123", "number input was edited"); + start(); }); -QUnit.test("Key property, a typed", function () { +QUnit.test("Key property, a typed", async function () { stop(); var a = false; @@ -731,13 +736,13 @@ QUnit.test("Key property, a typed", function () { a = ev.key; equal('a', ev.key); }); - syn.type('key', "a", function () { - ok(a, "a key typed"); - start(); - }); + await syn.type('#key', "a"); + + ok(a, "a key typed"); + start(); }); -QUnit.test("Control key", function () { +QUnit.test("Control key", async function () { stop(); var keyIsDown = false; @@ -745,25 +750,24 @@ QUnit.test("Control key", function () { keyIsDown = ev.ctrlKey; ok(ev.key === 'Control', "key is normalized"); }); - + var keyIsUp = true; st.binder("key", "keyup", function (ev) { keyIsUp = ev.ctrlKey; ok(ev.key === 'Control', "key is normalized"); }); - - syn.type('key', "[ctrl]", function () { - ok(keyIsDown, "Control modifier key pressed successfully"); - syn.type('key', "[ctrl-up]", function () { - ok(!keyIsUp, "Control modifier key released successfully"); - start(); - }); - }); + await syn.type('#key', "[ctrl]"); + + ok(keyIsDown, "Control modifier key pressed successfully"); + + await syn.type('#key', "[ctrl-up]"); + ok(!keyIsUp, "Control modifier key released successfully"); + start(); }); -QUnit.test("alt keycodes", function () { +QUnit.test("alt keycodes", async function () { stop(); var keyIsDown = false; @@ -771,24 +775,24 @@ QUnit.test("alt keycodes", function () { keyIsDown = ev.altKey; ok(ev.key === 'Alt', "key is normalized"); }); - + var keyIsUp = true; st.binder("key", "keyup", function (ev) { keyIsUp = ev.altKey; ok(ev.key === 'Alt', "key is normalized"); }); - - syn.type('key', "[alt]", function () { - ok(keyIsDown, "Alt modifier key pressed successfully"); - syn.type('key', "[alt-up]", function () { - ok(!keyIsUp, "Alt modifier key released successfully"); - start(); - }); - }); + await syn.type('#key', "[alt]"); + + ok(keyIsDown, "Alt modifier key pressed successfully"); + + await syn.type('#key', "[alt-up]"); + + ok(!keyIsUp, "Alt modifier key released successfully"); + start(); }); -QUnit.test("meta keycodes", function () { +QUnit.test("meta keycodes", async function () { stop(); var keyIsDown = false; @@ -796,20 +800,19 @@ QUnit.test("meta keycodes", function () { keyIsDown = ev.metaKey; ok(ev.key === 'Meta', "key is normalized"); }); - + var keyIsUp = true; st.binder("key", "keyup", function (ev) { keyIsUp = ev.metaKey; ok(ev.key === 'Meta', "key is normalized"); }); - - syn.type('key', "[meta]", function () { - ok(keyIsDown, "meta modifier key pressed successfully"); - syn.type('key', "[meta-up]", function () { - ok(!keyIsUp, "meta modifier key released successfully"); - start(); - }); - }); -}); + await syn.type('#key', "[meta]"); + + ok(keyIsDown, "meta modifier key pressed successfully"); + await syn.type('#key', "[meta-up]"); + + ok(!keyIsUp, "meta modifier key released successfully"); + start(); +}); diff --git a/test/mouse_button_test.js b/test/mouse_button_test.js index e68cad1a..12b6dc2c 100644 --- a/test/mouse_button_test.js +++ b/test/mouse_button_test.js @@ -30,7 +30,7 @@ QUnit.module("syn/mouse_button", { } }); -QUnit.test("syn basics", function () { +QUnit.test("syn trigger", function () { QUnit.ok(syn, "syn exists"); @@ -41,11 +41,11 @@ QUnit.test("syn basics", function () { mouseover++; }; st.bind(st.g("outer"), "mouseover", mouseoverf); - syn("mouseover", st.g("inner")); + syn.trigger(st.g("inner"), "mouseover"); st.unbinder("outer", "mouseover", mouseoverf); QUnit.equal(mouseover, 1, "Mouseover"); - syn("mouseover", 'inner', {}); + syn.trigger(st.g("inner"), "mouseover", {}); QUnit.equal(mouseover, 1, "Mouseover on no event handlers"); st.g("qunit-fixture") @@ -65,7 +65,7 @@ QUnit.test("Click Forms", function () { }; st.bind(st.g("outer"), "submit", submitf); syn.trigger(st.g("submit"), "click", {}); - syn("submit", "outer", {}); + syn.trigger(st.g("outer"), "submit", {}); QUnit.equal(submit, 2, "Click on submit"); @@ -142,7 +142,7 @@ QUnit.test("Select is changed on click", function () { select2 = 0; st.g("qunit-fixture") - .innerHTML = + .innerHTML = '"; - st.binder("focusme", "mousedown", function () { - QUnit.equal(++order, 1, "mousedown"); - }); + var actualOrder = []; + var expectedOrder = ["mousedown","focus","mouseup","click"]; + if(syn.skipFocusTests) { + expectedOrder = ["mousedown","mouseup","click"]; + } + function pushType(event) { + actualOrder.push(event.type); + } + st.binder("focusme", "mousedown", pushType); if (!syn.skipFocusTests) { - st.binder("focusme", "focus", function () { - QUnit.equal(++order, 2, "focus"); - }); + st.binder("focusme", "focus", pushType); } - st.binder("focusme", "mouseup", function () { - QUnit.equal(++order, syn.skipFocusTests ? 2 : 3, "mouseup"); - }); - st.binder("focusme", "click", function (ev) { - QUnit.equal(++order, syn.skipFocusTests ? 3 : 4, "click"); - if (ev.preventDefault) { - ev.preventDefault(); - } - ev.returnValue = false; - }); + st.binder("focusme", "mouseup", pushType); + st.binder("focusme", "click", pushType); stop(); - syn.click("focusme", {}, function () { - QUnit.start(); - }); + await syn.click("#focusme", {}); + + QUnit.deepEqual(actualOrder, expectedOrder); + QUnit.start(); }); -QUnit.test("Click! Pointer Event Order", syn.support.pointerEvents ? 3 : 0, function () { +QUnit.test("Click! Pointer Event Order", syn.support.pointerEvents ? 3 : 0, async function () { var order = 0; st.g("qunit-fixture").innerHTML = ""; @@ -294,14 +294,14 @@ QUnit.test("Click! Pointer Event Order", syn.support.pointerEvents ? 3 : 0, func ev.returnValue = false; }); } - + stop(); - syn.click("pointerTarget", {}, function () { - QUnit.start(); - }); + await syn.click("#pointerTarget", {}); + + QUnit.start(); }); -QUnit.test("Click! Touch Event Order", syn.support.touchEvents ? 3 : 0, function () { +QUnit.test("Click! Touch Event Order", syn.support.touchEvents ? 3 : 0, async function () { var order = 0; st.g("qunit-fixture").innerHTML = ""; @@ -323,11 +323,11 @@ QUnit.test("Click! Touch Event Order", syn.support.touchEvents ? 3 : 0, function ev.returnValue = false; }); } - + stop(); - syn.click("touchTarget", {}, function () { - QUnit.start(); - }); + syn.click("#touchTarget", {}); + + QUnit.start(); }); QUnit.test("Click Anchor Runs HREF JavaScript", function () { @@ -341,21 +341,21 @@ QUnit.test("Click Anchor Runs HREF JavaScript", function () { }, 50); }); -QUnit.test("Click! Anchor has href", function () { +QUnit.test("Click! Anchor has href", async function () { stop(); st.binder("jsHrefHash", "click", function (ev) { var target = ev.target || ev.srcElement; QUnit.ok(target.href.indexOf("#aHash") > -1, "got href"); }); - syn.click("jsHrefHash", {}, function () { - QUnit.equal(window.location.hash, "#aHash", "hash set ..."); - QUnit.start(); - window.location.hash = ""; - }); + await syn.click("#jsHrefHash", {}); + + QUnit.equal(window.location.hash, "#aHash", "hash set ..."); + QUnit.start(); + window.location.hash = ""; }); -QUnit.test("Click! Anchor Focuses", syn.skipFocusTests ? 1 : 2, function () { +QUnit.test("Click! Anchor Focuses", syn.skipFocusTests ? 1 : 2, async function () { st.g("qunit-fixture") .innerHTML = "I am visible"; @@ -378,14 +378,14 @@ QUnit.test("Click! Anchor Focuses", syn.skipFocusTests ? 1 : 2, function () { stop(); //need to give browsers a second to show element - syn.click("focusme", {}, function () { - QUnit.start(); - }); + await syn.click("#focusme", {}); + + QUnit.start(); }); if (!syn.skipFocusTests) { - QUnit.test("Click away causes Blur Change", function () { + QUnit.test("Click away causes Blur Change", async function() { st.g("qunit-fixture") .innerHTML = ""; @@ -400,17 +400,17 @@ if (!syn.skipFocusTests) { }); stop(); - syn.click("one", {}) - .key("a") - .click("two", {}, function () { - QUnit.start(); - QUnit.equal(change, 1, "Change called once"); - QUnit.equal(blur, 1, "Blur called once"); - }); + await syn.click("#one", {}); + await syn.key("#one","a"); + await syn.click("#two", {}); + + QUnit.start(); + QUnit.equal(change, 1, "Change called once"); + QUnit.equal(blur, 1, "Blur called once"); }); - QUnit.test("Click HTML causes blur change", function () { + QUnit.test("Click HTML causes blur change", async function () { st.g("qunit-fixture") .innerHTML = ""; @@ -420,15 +420,14 @@ if (!syn.skipFocusTests) { }); stop(); - syn.click("one", {}) - .key("a") - .click(document.documentElement, {}, function () { - QUnit.start(); - QUnit.equal(change, 1, "Change called once"); - }); + await syn.click("#one", {}); + await syn.key("#one","a"); + await syn.click(document.documentElement, {}); + QUnit.start(); + QUnit.equal(change, 1, "Change called once"); }); } -QUnit.test("Right Click", function () { +QUnit.test("Right Click", async function () { st.g("qunit-fixture").innerHTML = "
right click me
"; stop(); var context = 0; @@ -436,18 +435,18 @@ QUnit.test("Right Click", function () { context++; }); - syn.rightClick("one", {}, function () { - if (syn.mouse.browser.contextmenu) { - QUnit.equal(1, context, "context was called"); - } else { - QUnit.ok(true, "context shouldn't be called in this browser"); - } - QUnit.start(); - }); + await syn.rightClick("#one", {}); + + if (syn.mouse.browser.contextmenu) { + QUnit.equal(1, context, "context was called"); + } else { + QUnit.ok(true, "context shouldn't be called in this browser"); + } + QUnit.start(); }); -QUnit.test("Right Click Issues PointerEvents", syn.support.pointerEvents ? 2 : 0, function () { +QUnit.test("Right Click Issues PointerEvents", syn.support.pointerEvents ? 2 : 0, async function () { var order = 1; st.g("qunit-fixture").innerHTML = ""; @@ -456,20 +455,19 @@ QUnit.test("Right Click Issues PointerEvents", syn.support.pointerEvents ? 2 : 0 QUnit.equal(ev.button, 2, "pointerdown"); }); } - + if(syn.support.pointerEvents){ // skips test on browsers that do not support pointer events st.binder("pointerTarget", "pointerup", function (ev) { QUnit.equal(ev.button, 2, "pointerup"); }); - } + } stop(); - syn.rightClick("pointerTarget", {}, function () { - QUnit.start(); - }); + await syn.rightClick("#pointerTarget", {}); + QUnit.start(); }); -QUnit.test("Double Click", function () { +QUnit.test("Double Click", async function () { st.g("qunit-fixture") .innerHTML = "
double click me
"; stop(); @@ -481,10 +479,10 @@ QUnit.test("Double Click", function () { eventSequence.push('click'); }); - syn.dblclick("dblclickme", {}, function () { - QUnit.equal(eventSequence.join(', '), 'click, click, dblclick', 'expected event sequence for doubleclick'); - QUnit.start(); - }); + await syn.dblclick("#dblclickme", {}); + + QUnit.equal(eventSequence.join(', '), 'click, click, dblclick', 'expected event sequence for doubleclick'); + QUnit.start(); }); @@ -507,31 +505,31 @@ if(htmlClassName.indexOf('ie9') > -1){ var el = iframe.contentWindow.document.getElementById('strange') st.bind(el,"click",function(){ QUnit.ok(true, "h3 was clicked"); - + }); syn.click(el ,{}, function(){ QUnit.start(); }) - - + + }); iframe.src = page1 st.g("qunit-fixture").appendChild(iframe);*/ locate("test/pages/h3.html",function(path){ path = path.replace(".js",""); - + var popup = window.open(path, "synthing"); - - var runTest = function(el){ + + var runTest = async function(el){ st.bind(el, "click", function () { QUnit.ok(true, "h3 was clicked"); }); - syn.click(el, {}, function () { - QUnit.start(); - popup.close(); - }); + await syn.click(el, {}); + + QUnit.start(); + popup.close(); }; var ready = function(){ var el = popup.document.getElementById('strange'); @@ -541,7 +539,7 @@ if(htmlClassName.indexOf('ie9') > -1){ setTimeout(ready,100); } }; - + setTimeout(ready, 100); }); }); @@ -550,22 +548,21 @@ if(htmlClassName.indexOf('ie9') > -1){ QUnit.test("focus on an element then another in another page", function () { stop(); locate("test/pages/page1.html", function(page1){ - + locate("test/pages/page2.html", function(page2){ var iframe = document.createElement('iframe'), calls = 0; - - st.bind(iframe, "load", function () { + + st.bind(iframe, "load", async function () { if (calls === 0) { - syn.click(iframe.contentWindow.document.getElementById("first"), {}, function () { - iframe.contentWindow.location = page2; - }); + await syn.click(iframe.contentWindow.document.getElementById("first"), {}); + iframe.contentWindow.location = page2; calls++; } else { - syn.click(iframe.contentWindow.document.getElementById("second"), {}, function () { - QUnit.ok(iframe.contentWindow.document.getElementById("second") === iframe.contentWindow.document.activeElement); - QUnit.start(); - }); + await syn.click(iframe.contentWindow.document.getElementById("second"), {}); + + QUnit.ok(iframe.contentWindow.document.getElementById("second") === iframe.contentWindow.document.activeElement); + QUnit.start(); } }); iframe.src = page1; @@ -573,6 +570,6 @@ QUnit.test("focus on an element then another in another page", function () { .appendChild(iframe); }); }); - + }); diff --git a/test/mouse_move_test.js b/test/mouse_move_test.js index 2bfce235..26766770 100644 --- a/test/mouse_move_test.js +++ b/test/mouse_move_test.js @@ -1,12 +1,12 @@ /* MOUSE MOVE TEST - + Ensures the accuracy of mouse events specific to mouse movement. - + Planned Improvements: TODO: change checkThatMousePassedOver to include not-only the order of events, but the number of events. For example, duplicate mouseEnters would be a bug, but would not be caught under the current system. - + */ var syn = require('syn'); @@ -21,38 +21,37 @@ var testSpeed = 200; var frameHeight = 350; var frameUrl = 'testpages/mousemove.html'; -var mouseMoveOver = 'pointerover, pointerenter, mouseover, mouseenter, pointermove, pointerout, pointerleave, mouseout, mouseleave, '; -var mouseMoveEnd = 'pointerover, pointerenter, mouseover, mouseenter, pointermove, '; +var mouseMoveOver = 'pointerover, pointerenter, mouseover, mouseenter, pointermove, mousemove, pointerout, pointerleave, mouseout, mouseleave, '; +var mouseMoveEnd = 'pointerover, pointerenter, mouseover, mouseenter, pointermove, mousemove, '; QUnit.test("Move Cursor Upward", 8, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_a2'); - - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // ensure we get mouse events over the places that we expect - checkThatMousePassedOver(pageUnderTest, "#cell_a2", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_b2", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2", mouseMoveOver); // TODO: Starting cell gets no entry events! - - // ensure that neighbors to expected mouse event locations do not get events - checkThatMousePassedOver(pageUnderTest, "#cell_a1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_b1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_c1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_a3", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_b3", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_c3", ''); - - start(); - }); + //debugger; + await syn.move(source, {to: destination, duration: testSpeed}); + + // ensure we get mouse events over the places that we expect + checkThatMousePassedOver(pageUnderTest, "#cell_a2", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_b2", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2", mouseMoveOver); // TODO: Starting cell gets no entry events! + + // ensure that neighbors to expected mouse event locations do not get events + checkThatMousePassedOver(pageUnderTest, "#cell_a1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_b1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_c1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_a3", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_b3", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_c3", ''); + + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -65,33 +64,32 @@ QUnit.test("Move Cursor Upward", 8, function () { QUnit.test("Move Cursor Downward", 8, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - testFrame.addEventListener('load', function loadListener(){ + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); var pageUnderTest = testFrame.contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_e2'); - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // ensure we get mouse events over the places that we expect - checkThatMousePassedOver(pageUnderTest, "#cell_e2", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_d2", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? - - // ensure that neighbors to expected mouse event locations do not get events - checkThatMousePassedOver(pageUnderTest, "#cell_e1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_c1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_e3", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d3", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_c3", ''); - - start(); - }); + await syn.move(source, {to: destination, duration: testSpeed}); + + // ensure we get mouse events over the places that we expect + checkThatMousePassedOver(pageUnderTest, "#cell_e2", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_d2", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? + + // ensure that neighbors to expected mouse event locations do not get events + checkThatMousePassedOver(pageUnderTest, "#cell_e1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_c1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_e3", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d3", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_c3", ''); + + start(); }); testFrame.height = frameHeight; @@ -103,33 +101,32 @@ QUnit.test("Move Cursor Downward", 8, function () { QUnit.test("Move Cursor Leftward", 8, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - testFrame.addEventListener('load', function loadListener(){ + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); var pageUnderTest = testFrame.contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_c0'); - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // ensure we get mouse events over the places that we expect - checkThatMousePassedOver(pageUnderTest, "#cell_c0", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_c1", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? - - // ensure that neighbors to expected mouse event locations do not get events - checkThatMousePassedOver(pageUnderTest, "#cell_b0", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_b1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_b2", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d0", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d1", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d2", ''); - - start(); - }); + await syn.move(source, {to: destination, duration: testSpeed}); + + // ensure we get mouse events over the places that we expect + checkThatMousePassedOver(pageUnderTest, "#cell_c0", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_c1", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? + + // ensure that neighbors to expected mouse event locations do not get events + checkThatMousePassedOver(pageUnderTest, "#cell_b0", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_b1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_b2", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d0", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d1", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d2", ''); + + start(); }); testFrame.height = frameHeight; @@ -141,33 +138,33 @@ QUnit.test("Move Cursor Leftward", 8, function () { QUnit.test("Move Cursor Rightward", 8, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - testFrame.addEventListener('load', function loadListener(){ + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); var pageUnderTest = testFrame.contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_c4'); - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // ensure we get mouse events over the places that we expect - checkThatMousePassedOver(pageUnderTest, "#cell_c4", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_c3", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? - - // ensure that neighbors to expected mouse event locations do not get events - checkThatMousePassedOver(pageUnderTest, "#cell_b2", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_b3", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_b4", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d2", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d3", ''); - checkThatMousePassedOver(pageUnderTest, "#cell_d4", ''); - - start(); - }); + await syn.move(source, {to: destination, duration: testSpeed}); + + // ensure we get mouse events over the places that we expect + checkThatMousePassedOver(pageUnderTest, "#cell_c4", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_c3", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? + + // ensure that neighbors to expected mouse event locations do not get events + checkThatMousePassedOver(pageUnderTest, "#cell_b2", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_b3", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_b4", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d2", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d3", ''); + checkThatMousePassedOver(pageUnderTest, "#cell_d4", ''); + + start(); + }); testFrame.height = frameHeight; @@ -179,25 +176,24 @@ QUnit.test("Move Cursor Rightward", 8, function () { QUnit.test("Move Cursor Diagonal Up+Left", 2, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - testFrame.addEventListener('load', function loadListener(){ + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); var pageUnderTest = testFrame.contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_a0'); - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // ensure we get mouse events over the places that we expect - checkThatMousePassedOver(pageUnderTest, "#cell_a0", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_b1", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? - - start(); - }); + await syn.move(source, {to: destination, duration: testSpeed}); + + // ensure we get mouse events over the places that we expect + checkThatMousePassedOver(pageUnderTest, "#cell_a0", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_b1", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? + + start(); }); testFrame.height = frameHeight; @@ -209,25 +205,24 @@ QUnit.test("Move Cursor Diagonal Up+Left", 2, function () { QUnit.test("Move Cursor Diagonal Up+Right", 2, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - testFrame.addEventListener('load', function loadListener(){ + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); var pageUnderTest = testFrame.contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_a4'); - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // ensure we get mouse events over the places that we expect - checkThatMousePassedOver(pageUnderTest, "#cell_a4", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_b3", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? - - start(); - }); + await syn.move(source, {to: destination, duration: testSpeed}); + + // ensure we get mouse events over the places that we expect + checkThatMousePassedOver(pageUnderTest, "#cell_a4", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_b3", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? + + start(); }); testFrame.height = frameHeight; @@ -239,25 +234,24 @@ QUnit.test("Move Cursor Diagonal Up+Right", 2, function () { QUnit.test("Move Cursor Diagonal Down+Left", 2, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - testFrame.addEventListener('load', function loadListener(){ + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); var pageUnderTest = testFrame.contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_e0'); - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // ensure we get mouse events over the places that we expect - checkThatMousePassedOver(pageUnderTest, "#cell_e0", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_d1", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? - - start(); - }); + await syn.move(source, {to: destination, duration: testSpeed}); + + // ensure we get mouse events over the places that we expect + checkThatMousePassedOver(pageUnderTest, "#cell_e0", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_d1", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? + + start(); }); testFrame.height = frameHeight; @@ -269,26 +263,25 @@ QUnit.test("Move Cursor Diagonal Down+Left", 2, function () { QUnit.test("Move Cursor Diagonal Down+Right", 1, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - testFrame.addEventListener('load', function loadListener(){ + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); var pageUnderTest = testFrame.contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_e4'); - syn.move(source, {to: destination, duration: testSpeed}, function () { - - // NOTE: The test sporadically moves the mouse at the end of the test, causing extra events to appear here. - // so we can't rely on the #cell_e4 check - // checkThatMousePassedOver(pageUnderTest, "#cell_e4", mouseMoveEnd); - checkThatMousePassedOver(pageUnderTest, "#cell_d3", mouseMoveOver); - //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? - - start(); - }); + await syn.move(source, {to: destination, duration: testSpeed}); + + // NOTE: The test sporadically moves the mouse at the end of the test, causing extra events to appear here. + // so we can't rely on the #cell_e4 check + // checkThatMousePassedOver(pageUnderTest, "#cell_e4", mouseMoveEnd); + checkThatMousePassedOver(pageUnderTest, "#cell_d3", mouseMoveOver); + //checkThatMousePassedOver(pageUnderTest, "#cell_c2" true); // TODO: starting cell not working, why? + + start(); }); testFrame.height = frameHeight; @@ -300,21 +293,13 @@ function checkThatMousePassedOver(pageUnderTest, cellName, expectedEvents){ var cell = pageUnderTest.querySelector(cellName); var browser = BrowserDetective.getBrowserName(); var actualEvents = ''; - + //var i; - for (var i = 0; i < cell.eventRecorder.length; i++) { + for (var i = 0; i < cell.eventRecorder.length; i++) { actualEvents += cell.eventRecorder[i] + ", "; - } - - //cell.eventRecorder.forEach(function(elem) { actualEvents += elem + ", "; }); - equal(actualEvents, expectedEvents, "Recorded events must match expected events. CellId: " + cellName); - -} - - - - - - + } + //cell.eventRecorder.forEach(function(elem) { actualEvents += elem + ", "; }); + equal(actualEvents, expectedEvents, "Recorded events must match expected events. CellId: " + cellName); +} diff --git a/test/mouse_regressions_test.js b/test/mouse_regressions_test.js index b072f8f4..2b32f013 100644 --- a/test/mouse_regressions_test.js +++ b/test/mouse_regressions_test.js @@ -12,26 +12,25 @@ var frameUrl = 'testpages/regressions_mouse.html'; QUnit.test("Testing Button codes: left click", 2, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var clickable = pageUnderTest.querySelector('#clickable'); var output1 = pageUnderTest.querySelector('#output1'); var output2 = pageUnderTest.querySelector('#output2'); - syn.click(clickable, {}, function () { + await syn.click(clickable, {}); - var buttonCode = getValue('button', output1.value); - ok( buttonCode == '0', "Mouse 'button' code expected: '0', received: '" + buttonCode + "'."); - var clickCode = getValue('button', output2.value); - ok( clickCode == '0', "Mouse 'button' code expected: '0', received: '" + clickCode + "'."); + var buttonCode = getValue('button', output1.value); + ok( buttonCode == '0', "Mouse 'button' code expected: '0', received: '" + buttonCode + "'."); + var clickCode = getValue('button', output2.value); + ok( clickCode == '0', "Mouse 'button' code expected: '0', received: '" + clickCode + "'."); - start(); - }); + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -44,24 +43,23 @@ QUnit.test("Testing Button codes: left click", 2, function () { QUnit.test("Testing Button codes: right click", 1, function () { stop(); - + var testFrame = document.getElementById('pageUnderTest'); - - testFrame.addEventListener('load', function loadListener(){ + + testFrame.addEventListener('load', async function loadListener(){ testFrame.removeEventListener('load', loadListener); - + var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var clickable = pageUnderTest.querySelector('#clickable'); var output1 = pageUnderTest.querySelector('#output1'); var output2 = pageUnderTest.querySelector('#output2'); - syn.rightClick(clickable, {}, function () { + await syn.rightClick(clickable, {}); - var buttonCode = getValue('button', output1.value); - ok( buttonCode == '2', "Mouse 'button' code expected: '2', received: '" + buttonCode + "'."); + var buttonCode = getValue('button', output1.value); + ok( buttonCode == '2', "Mouse 'button' code expected: '2', received: '" + buttonCode + "'."); - start(); - }); + start(); }); // TODO: Boilerplate. Can we move this to a setup function? @@ -75,18 +73,17 @@ QUnit.test("Testing Button codes: right click", 1, function () { function getValue(needle, haystack){ - + needle = needle + "::"; // Add formatting - + var startIndex = haystack.indexOf(needle); console.log("Start: "+startIndex); - + var endIndex = haystack.indexOf("\n", startIndex); console.log("End: " + endIndex) - + var substring = haystack.substring(startIndex + needle.length, endIndex); console.log("Substring: " + substring) return substring; } - diff --git a/test/pages/syn.schedule.html b/test/pages/syn.schedule.html index 015c0faf..7b3e13be 100644 --- a/test/pages/syn.schedule.html +++ b/test/pages/syn.schedule.html @@ -1,7 +1,7 @@ -Syn.schedule test +syn.helpers.schedule( test @@ -11,10 +11,11 @@ parent.synSchedule.apply(parent, arguments); } }; - - steal('syn', function(syn){ + + steal('syn', async function(syn){ var body = document.body; - syn.click(body, {}).delay(function(){}, 0); + await syn.click(body, {}) + syn.delay(function(){}, 0); }); diff --git a/test/syn_test.js b/test/syn_test.js index 19c082ac..898b98b2 100644 --- a/test/syn_test.js +++ b/test/syn_test.js @@ -18,7 +18,7 @@ setTimeout(function supportLog() { } }, 1); -QUnit.test("Selecting a select element", function () { +QUnit.test("Selecting a select element", async function() { st.g("qunit-fixture") .innerHTML = "
"; @@ -35,18 +35,18 @@ QUnit.test("Selecting a select element", function () { .select, "change", changef); stop(); - syn.click(st.g("two"), function () { - equal(change, 1, "change called once"); - equal(st.g("outer") - .select.selectedIndex, 1, "Change Selected Index"); + await syn.click(st.g("two")); - start(); - st.g("qunit-fixture") - .innerHTML = ""; - }); + equal(change, 1, "change called once"); + equal(st.g("outer") + .select.selectedIndex, 1, "Change Selected Index"); + + start(); + st.g("qunit-fixture") + .innerHTML = ""; }); -QUnit.test("scrollTop triggers scroll events", function () { +QUnit.test("scrollTop triggers scroll events", async function () { st.g("qunit-fixture") .innerHTML = "
" + "
text" + @@ -97,7 +97,7 @@ QUnit.test("syn.support effect on scroll position, #30", function () { // test/qunit/page1.html locate("test/pages/scroll_30.html",function(scroll30){ scroll30 = scroll30.replace(".js",""); - + var iframe = document.createElement("iframe"); iframe.setAttribute("height", "100"); iframe.src = scroll30; @@ -117,27 +117,5 @@ QUnit.test("syn.support effect on scroll position, #30", function () { st.g("qunit-fixture") .appendChild(iframe); }); - -}); - -QUnit.test("syn.schedule gets called when syn.delay is used", function () { - stop(); - locate("test/pages/syn.schedule.html",function(synUrl){ - var iframe = document.createElement("iframe"); - iframe.src = synUrl.replace(".js",""); - window.synSchedule = function (fn, ms) { - // fn should be a function - equal(typeof fn, "function"); - // ms is a Number - equal(typeof ms, "number"); - - start(); - }; - st.g("qunit-fixture") - .appendChild(iframe); - - }); - }); - diff --git a/test/testpages/html5_dragdrop.html b/test/testpages/html5_dragdrop.html index 7d25a191..7ec77a8c 100644 --- a/test/testpages/html5_dragdrop.html +++ b/test/testpages/html5_dragdrop.html @@ -9,7 +9,7 @@ if(targetElement){ targetElement.classList.add('dragOver'); } } - function drag(ev) { + function drag(ev) { ev.dataTransfer.setData("text", ev.target.id); } @@ -18,7 +18,7 @@ var data = ev.dataTransfer.getData("text"); ev.target.appendChild(document.getElementById(data)); } - + function badDrop(ev, targetElement) { ev.preventDefault(); var data = ev.dataTransfer.getData("text"); @@ -54,11 +54,11 @@ margin-left: auto; margin-right: auto; } - + span.dragOver { background-color: DarkGreen; } - + span.badDrop { background-color: Red; } @@ -68,7 +68,7 @@ } - + @@ -115,7 +115,7 @@
- + - \ No newline at end of file + diff --git a/test/testpages/mousemove.html b/test/testpages/mousemove.html index 672c7d80..36882ee4 100644 --- a/test/testpages/mousemove.html +++ b/test/testpages/mousemove.html @@ -4,12 +4,12 @@ Mouse Move testing @@ -46,7 +46,7 @@ span.mouseUp { background-color: blue; - } + } span.dragOrigin { display:inline-block; @@ -63,7 +63,7 @@ } - + @@ -107,13 +107,13 @@
- + - \ No newline at end of file + diff --git a/utils/recorder.html b/utils/recorder.html index 24878c99..fc480a5f 100644 --- a/utils/recorder.html +++ b/utils/recorder.html @@ -98,13 +98,13 @@

Click Data

return document.getElementById(id) }, bind = function(el, ev, func){ - syn.bind(gId(el.replace('\\','\\\\')), ev,func); + syn.helpers.bind(gId(el.replace('\\','\\\\')), ev,func); }; $(function(){ $('#ua').html(window.navigator.userAgent) }); - var keycodes = syn.keycodes, + var keycodes = syn.key.keycodes, special = { backspace : '\b', tab : '\t',