From dd1e75c386c99668d38167029af2f33c7b10c1f6 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Fri, 12 Mar 2021 21:53:37 -0600 Subject: [PATCH 01/23] got key tests to pass --- src/key.js | 180 +++++++++--------- src/mouse.js | 7 +- src/synthetic.js | 118 ++++++------ test/key_test.js | 461 ++++++++++++++++++++++++----------------------- test/syn_test.js | 29 ++- 5 files changed, 394 insertions(+), 401 deletions(-) diff --git a/src/key.js b/src/key.js index 460ad555..5a72b07f 100644 --- a/src/key.js +++ b/src/key.js @@ -409,7 +409,7 @@ h.extend(syn.key, { } else { result.which = result.charCode; } - + return result; }, //types of event keys @@ -568,7 +568,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 @@ -618,11 +618,11 @@ h.extend(syn.key, { 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]; + 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]; + current = nextIndex < ordersLength && orders[nextIndex][0] || orders[0][0]; } } } @@ -746,12 +746,12 @@ h.extend(syn.create, { 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 { + 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) { @@ -802,91 +802,89 @@ h.extend(syn.init.prototype, { * @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); - } - - // 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), + _key: function (element, options) { + return new Promise((resolve) => { + //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}); + } - // a function that gets the default behavior for a key - getDefault = syn.key.getDefault, + // 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), - // how this browser handles preventing default events - prevent = syn.key.browser.prevent, + // a function that gets the default behavior for a key + getDefault = syn.key.getDefault, - // the result of the default event - defaultResult, + // how this browser handles preventing default events + prevent = syn.key.browser.prevent, - keypressOptions = syn.key.options(key, 'keypress'); + // the result of the default event + defaultResult, - 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); - } 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; - } + keypressOptions = syn.key.options(key, 'keypress'); - runDefaults = syn.trigger(element, 'keypress', keypressOptions); - if (runDefaults) { + 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); + } 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; + } + + runDefaults = syn.trigger(element, 'keypress', keypressOptions); + if (runDefaults) { + defaultResult = getDefault(key) + .call(element, keypressOptions, h.getWindow(element), + key, undefined, caret); + } } - } - } 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; - } + } 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; + } - syn.trigger(element, 'keypress', keypressOptions); + syn.trigger(element, 'keypress', keypressOptions); + } + } + if (defaultResult && defaultResult.nodeName) { + element = defaultResult; } - } - if (defaultResult && defaultResult.nodeName) { - element = defaultResult; - } - 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); - } + if (defaultResult !== null) { + syn.schedule(function () { - //do mouseup - return element; - // is there a keypress? .. if not , run default - // yes -> did we prevent it?, if not run ... + 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')); + resolve({runDefaults: true, element}); + }, 1); + } else { + resolve({runDefaults: true, element}); + } + + }); }, /** * @function syn.type type() @@ -912,27 +910,23 @@ h.extend(syn.init.prototype, { * @param {String} options the text to type * @param {Function} [callback] a function to callback */ - _type: function (element, options, callback) { + _type: async function (element, options) { //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); - }; + el = element, + part; + while( part = parts.shift() ) { + if (part.length > 1) { + part = part.substr(1, part.length - 2); + } + var result = await this._key(el, part); + + el = result.element || el; - runNextPart(); + } + return {element:el}; } }); - diff --git a/src/mouse.js b/src/mouse.js index 4b8ebb07..67efef57 100644 --- a/src/mouse.js +++ b/src/mouse.js @@ -122,10 +122,10 @@ h.extend(syn.create, { 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); */ @@ -165,7 +165,7 @@ 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); @@ -220,4 +220,3 @@ h.extend(syn.create, { } } }); - diff --git a/src/synthetic.js b/src/synthetic.js index 8f0e505c..ae2fa73e 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -39,11 +39,11 @@ var extend = function (d, s) { * @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") @@ -51,10 +51,10 @@ var extend = function (d, s) { *

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. + * 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 + * 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 + * 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() + * 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 + * 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: + *
The following checks that "Hello World" was entered correctly: @codestart syn.click('description', {}) .type("Hello World", function(){ - - ok("Hello World" == document.getElementById('description').value) + + ok("Hello World" == document.getElementById('description').value) }) @codeend

Asynchronous Chaining

@@ -98,14 +98,14 @@ var extend = function (d, s) { 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. + *

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.

@@ -126,10 +126,10 @@ var extend = function (d, s) { * 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 + * 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 + * 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 + * 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 @@ -195,12 +195,7 @@ extend(syn, { //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); - }); + return this[type](args.element, args.options); } else { this.result = syn.trigger(args.element, type, args.options); if (args.callback) { @@ -614,7 +609,7 @@ extend(syn, { keypressOnAnchorClicks: false, optionClickBubbles: false, pointerEvents: false, - touchEvents: false, + touchEvents: false, ready: 0 }, /** @@ -672,10 +667,10 @@ extend(syn, { } return ret; }, - - - - + + + + eventSupported: function (eventName) { var el = document.createElement("div"); eventName = "on" + eventName; @@ -716,7 +711,7 @@ extend(syn.init.prototype, { * @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) { @@ -811,41 +806,44 @@ extend(syn.init.prototype, { * @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 () { + "_click": function (element, options, force) { + return new Promise((resolve) => { + syn.helpers.addOffset(options, element); if(syn.support.pointerEvents){ - syn.trigger(element, 'pointerup', options); + syn.trigger(element, 'pointerdown', 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); + syn.trigger(element, 'touchstart', options); } - }, 1); + 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); + 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 + schedule(function () { + resolve(true); + }, 1); + } + + }, 1); + }) + }, /** * @function syn.rightClick rightClick() @@ -862,7 +860,7 @@ extend(syn.init.prototype, { 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 diff --git a/test/key_test.js b/test/key_test.js index 2ed4ab15..413e91c5 100644 --- a/test/key_test.js +++ b/test/key_test.js @@ -41,7 +41,7 @@ QUnit.test("Key Characters", function () { .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(); }); @@ -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 @@ -578,7 +584,7 @@ QUnit.test("meta keycodes", function () { //QUnit.test("insert practical test", function () { // stop(); - + // syn.type('key', "Hello World", function () { // equal(key.value, "Hello World", "Verified initial state"); // selectText(key, 6, 6); @@ -586,13 +592,13 @@ QUnit.test("meta keycodes", function () { // TODO: this actually hangs the test. Should I be using something like insert-up ? //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 () { // 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 () { // 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/syn_test.js b/test/syn_test.js index 19c082ac..ad6bba4b 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,7 +117,7 @@ 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 () { @@ -136,8 +136,7 @@ QUnit.test("syn.schedule gets called when syn.delay is used", function () { st.g("qunit-fixture") .appendChild(iframe); - + }); - -}); +}); From e7542872c156cf81b109149d8506f2fb6be3eac1 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Sat, 13 Mar 2021 10:21:40 -0600 Subject: [PATCH 02/23] all tests pass with async --- src/drag.js | 452 ++++++++++++++++----------------- src/synthetic.js | 64 ++--- test/drag_test_basic.js | 68 +++-- test/focus_test.js | 182 ++++++------- test/key_regressions_test.js | 102 ++++---- test/mouse_button_test.js | 189 +++++++------- test/mouse_move_test.js | 267 +++++++++---------- test/mouse_regressions_test.js | 47 ++-- test/pages/syn.schedule.html | 7 +- 9 files changed, 678 insertions(+), 700 deletions(-) diff --git a/src/drag.js b/src/drag.js index 804931d1..eaea5dfe 100644 --- a/src/drag.js +++ b/src/drag.js @@ -1,12 +1,12 @@ var syn = require('./synthetic'); /* -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? */ @@ -19,13 +19,13 @@ var elementFromPoint = function (point, win) { 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)); }; @@ -35,19 +35,19 @@ var elementFromPoint = function (point, 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){ + dragAndDrop: async 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); @@ -57,66 +57,63 @@ var DragonDrop = { 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(); - }); + + + + + await DragonDrop.startMove(fromPoint, toPoint, duration); + + // 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); + + DragonDrop.cleanup(); }, - - + + _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); }, - - _dragEnter: function(node){ + + _dragEnter: function(node){ var options = { bubbles:true, cancelable:true }; this.createAndDispatchEvent(node, 'dragenter', options); }, - + _dragOver: function(node){ var options = { bubbles:true, cancelable:true }; this.createAndDispatchEvent(node, 'dragover', options); }, - + _dragLeave: function(node){ var options = { bubbles:true, cancelable:false }; this.createAndDispatchEvent(node, 'dragleave', options); }, - + _drop: function(node){ var options = { bubbles:true, cancelable:true, buttons:1 }; this.createAndDispatchEvent(node, 'drop', options); }, - + _dragEnd: function(node){ var options = { bubbles:true, cancelable:false }; this.createAndDispatchEvent(node, 'dragend', options); @@ -128,7 +125,7 @@ var DragonDrop = { _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 @@ -140,7 +137,7 @@ var DragonDrop = { var targetElement = elementFromPoint(point, this.focusWindow); syn.trigger(targetElement, eventName, options); } - }, + }, getDataTransferObject : function(){ // For a series of dragOperations, we want the same dataTransfer Object to be carried over @@ -150,15 +147,15 @@ var DragonDrop = { return this.currentDataTransferItem; } }, - + // 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 @@ -171,7 +168,7 @@ var DragonDrop = { items:[], types:[], data:[], - + // setData function assigns the dragValue to an object's property setData: function(dataFlavor, value){ var tempdata = {}; @@ -193,65 +190,67 @@ var DragonDrop = { return dataTransfer; }, - - startMove : function (start, end, duration, callback) { - - 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 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 = { - clientX: distX * fraction + start.clientX, - clientY: distY * fraction + start.clientY + + startMove : function (start, end, duration) { + 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 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 = { + 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 = 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? + } else { + current = DragonDrop.mouseMove(end, current); + win.document.body.removeChild(cursor); + resolve(); + } }; - calls++; - - if (fraction < 1) { - syn.helpers.extend(cursor.style, { - left: (options.clientX + scrollOffset.left + 2) + "px", - top: (options.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? - } else { - current = DragonDrop.mouseMove(end, current); - win.document.body.removeChild(cursor); - callback(); - } - }; - syn.helpers.extend(cursor.style, { - height: "5px", - width: "5px", - backgroundColor: "red", - position: "absolute", - zIndex: 19999, - fontSize: "1px" + syn.helpers.extend(cursor.style, { + height: "5px", + width: "5px", + backgroundColor: "red", + position: "absolute", + zIndex: 19999, + fontSize: "1px" + }); + win.document.body.appendChild(cursor); + move(); + }); - 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) { - + + if (thisElement !== previousElement) { + options.relatedTarget = thisElement; this._dragLeave(previousPoint, options); - + options.relatedTarget = previousElement; this._dragEnter(thisPoint, options); } @@ -350,7 +349,7 @@ syn.create.dragend = { // 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); @@ -374,22 +373,22 @@ syn.create.dragend = { 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 + 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); } + if(!syn.support.pointerEvents){ syn.trigger(el || win, "mousemove", point); } }else{ syn.trigger(el || win, "mousemove", point); } - - + + return el; }, @@ -404,60 +403,61 @@ syn.create.dragend = { 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" + return new Promise((resolve) => { + 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); + resolve(); + } + }; + syn.helpers.extend(cursor.style, { + height: "5px", + width: "5px", + backgroundColor: "red", + position: "absolute", + zIndex: 19999, + fontSize: "1px" + }); + win.document.body.appendChild(cursor); + move(); }); - win.document.body.appendChild(cursor); - move(); }, - startDrag = function (win, fromPoint, toPoint, duration, callback) { + startDrag = async function (win, fromPoint, toPoint, duration) { if(syn.support.pointerEvents){ createEventAtPoint("pointerover", fromPoint, win); createEventAtPoint("pointerenter", fromPoint, win); @@ -467,24 +467,23 @@ syn.create.dragend = { 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(); - }); - }, + await startMove(win, fromPoint, toPoint, duration); + + 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); + }, + + + - - - center = function (el) { var j = syn.jquery()(el), o = j.offset(); @@ -537,12 +536,12 @@ syn.create.dragend = { return option; }, - - - - - - + + + + + + // 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) { @@ -566,18 +565,18 @@ syn.create.dragend = { } }; - - - - - - - - - - - - + + + + + + + + + + + + /** * @add syn prototype */ @@ -586,13 +585,13 @@ 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. - * + * 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, * { @@ -600,76 +599,76 @@ syn.helpers.extend(syn.init.prototype, { * 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) { + _move: function (from, options) { var win = syn.helpers.getWindow(from); var sourceCoordinates = convertOption(options.from || from, win, from); @@ -680,19 +679,20 @@ syn.helpers.extend(syn.init.prototype, { if (options.adjust !== false) { adjust(sourceCoordinates, destinationCoordinates, win); } - startMove(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); + + return startMove(win, sourceCoordinates, destinationCoordinates, options.duration || 500); }, - - - - - - - - - - + + + + + + + + + + /** * @function syn.drag drag() * @parent mouse @@ -713,15 +713,13 @@ syn.helpers.extend(syn.init.prototype, { if (options.adjust !== false) { adjust(sourceCoordinates, destinationCoordinates, win); } - + DragonDrop.html5drag = from.draggable; if(DragonDrop.html5drag){ - DragonDrop.dragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); + return DragonDrop.dragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); }else{ - startDrag(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); + return startDrag(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); } } }); - - diff --git a/src/synthetic.js b/src/synthetic.js index ae2fa73e..a7578051 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -28,7 +28,7 @@ var extend = function (d, s) { id = 1, expando = "_synthetic" + new Date() .getTime(), - bind, unbind, schedule, key = /keypress|keyup|keydown/, + bind, unbind, schedule, schedulePromise, key = /keypress|keyup|keydown/, page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/, //this is maintained so we can click on html and blur the active element activeElement, @@ -175,6 +175,12 @@ unbind = function (el, ev, f) { schedule = syn.config.schedule || function (fn, ms) { setTimeout(fn, ms); }; +schedulePromise = function(time) { + return new Promise(function(resolve){ + setTimeout(resolve, time) + }); +} + /** * @Static */ @@ -420,6 +426,7 @@ extend(syn, { return -1; }, getWindow: function (element) { + if(!element) {debugger;} if (element.ownerDocument) { return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; } @@ -854,26 +861,29 @@ extend(syn.init.prototype, { * @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 () { + "_rightClick": function (element, options) { + return new Promise((resolve)=>{ + syn.helpers.addOffset(options, element); + var mouseopts = extend(extend({}, syn.mouse.browser.right.mouseup), options); 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)); + syn.trigger(element, 'pointerdown', mouseopts); } - callback(true); - }, 1); + + 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)); + } + resolve(); + }, 1); + }) + }, /** * @function syn.dblclick dblclick() @@ -889,18 +899,14 @@ extend(syn.init.prototype, { * @param {HTMLElement} element * @param {Function} callback */ - "_dblclick": function (element, options, callback) { + "_dblclick": async function (element, options) { 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); - - }); + await this._click(element, options); + await schedulePromise(2); + await this._click(element, options); + syn.trigger(element, "dblclick", options); + return true; } }); 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/mouse_button_test.js b/test/mouse_button_test.js index e68cad1a..df74a092 100644 --- a/test/mouse_button_test.js +++ b/test/mouse_button_test.js @@ -142,7 +142,7 @@ QUnit.test("Select is changed on click", function () { select2 = 0; st.g("qunit-fixture") - .innerHTML = + .innerHTML = '"; @@ -266,13 +268,13 @@ QUnit.test("Click! Event Order", syn.skipFocusTests ? 3 : 4, function () { }); stop(); - syn.click("focusme", {}, function () { - QUnit.start(); - }); + await syn.click("focusme", {}); + + 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 +296,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 +325,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 +343,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 +380,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 +402,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 +422,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 +437,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 +457,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 +481,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 +507,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 +541,7 @@ if(htmlClassName.indexOf('ie9') > -1){ setTimeout(ready,100); } }; - + setTimeout(ready, 100); }); }); @@ -550,22 +550,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 +572,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..7a89de49 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'); @@ -26,33 +26,32 @@ var mouseMoveEnd = 'pointerover, pointerenter, mouseover, mouseenter, pointermov 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(); - }); + + 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..d3cd7b41 100644 --- a/test/pages/syn.schedule.html +++ b/test/pages/syn.schedule.html @@ -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); }); From 8a88128fce588805bd161ad03aff0bc6165634cb Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Sat, 13 Mar 2021 11:13:09 -0600 Subject: [PATCH 03/23] removing jquery --- src/drag.js | 9 ++------- src/synthetic.js | 38 +++++++++++++------------------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/drag.js b/src/drag.js index eaea5dfe..878e58a5 100644 --- a/src/drag.js +++ b/src/drag.js @@ -485,12 +485,7 @@ syn.create.dragend = { center = function (el) { - var j = syn.jquery()(el), - o = j.offset(); - return { - pageX: o.left + (j.outerWidth() / 2), - pageY: o.top + (j.outerHeight() / 2) - }; + return syn.helpers.addOffset({}, el); }, convertOption = function (option, win, from) { var page = /(\d+)[x ](\d+)/, @@ -521,7 +516,7 @@ syn.create.dragend = { }; } if (typeof option === 'string') { - option = syn.jquery()(option, win.document)[0]; + option = win.document.querySelector( option ); } if (option.nodeName) { option = center(option); diff --git a/src/synthetic.js b/src/synthetic.js index a7578051..5068489c 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -196,28 +196,14 @@ extend(syn, { init: function (type, element, options, callback) { var args = syn.args(options, element, callback), self = this; - this.queue = []; - this.element = args.element; + //this.queue = []; + //this.element = args.element; //run event if (typeof this[type] === "function") { return this[type](args.element, args.options); } else { - this.result = syn.trigger(args.element, type, args.options); - if (args.callback) { - args.callback.call(this, args.element, this.result); - } - } - }, - jquery: function (el, fast) { - if (window.FuncUnit && window.FuncUnit.jQuery) { - return window.FuncUnit.jQuery; - } - if (el) { - return syn.helpers.getWindow(el) - .jQuery || window.jQuery; - } else { - return window.jQuery; + return syn.trigger(args.element, type, args.options); } }, /** @@ -461,14 +447,13 @@ 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; + 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; } + return options; } }, // place for key data @@ -757,7 +742,7 @@ extend(syn.init.prototype, { * @param {Number} [timeout] * @param {Function} [callback] */ - delay: function (timeout, callback) { + /*delay: function (timeout, callback) { if (typeof timeout === 'function') { callback = timeout; timeout = null; @@ -774,6 +759,8 @@ extend(syn.init.prototype, { }); return this; }, + */ + /* done: function (defaults, el) { if (el) { this.element = el; @@ -784,6 +771,7 @@ extend(syn.init.prototype, { } }, + */ /** * @function syn.click click() * @parent mouse From 25184c1b887254121292275814af3895bc59400e Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Sat, 13 Mar 2021 22:00:14 -0600 Subject: [PATCH 04/23] a big refactor --- demo/record.js | 8 +- src/browsers.js | 1 - src/drag.js | 1073 ++++++++++++----------------- src/drag.support.js | 33 + src/key.js | 1261 ++++++++++++++++------------------ src/key.support.js | 12 +- src/mouse.js | 268 +++++--- src/mouse.support.js | 47 +- src/synthetic.js | 1138 ++++++++++-------------------- test/pages/syn.schedule.html | 2 +- test/syn_test.js | 21 - utils/recorder.html | 4 +- 12 files changed, 1653 insertions(+), 2215 deletions(-) create mode 100644 src/drag.support.js 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/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 878e58a5..a7ccccb7 100644 --- a/src/drag.js +++ b/src/drag.js @@ -1,5 +1,5 @@ var syn = require('./synthetic'); - +require('./drag.support'); /* TODO: This is getting very complicated. We should probably separate the DRAG and MOVE abilities into two separate actions @@ -10,558 +10,365 @@ TODO: Very few of our events actually fill the bubbles and cancelable fields. An set to those unless somehow overridden? */ +// Add high-level functions directly to syn - -// 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)); -}; - - - -//====================================================================================================== -// DragonDrop prototype STARTS -//====================================================================================================== -var DragonDrop = { - - html5drag : false, - focusWindow : null, - +syn.helpers.extend(syn,{ /** - * 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: async 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); - - - - - await DragonDrop.startMove(fromPoint, toPoint, duration); - - // 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); - - DragonDrop.cleanup(); - }, - + * @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) { + var win = syn.helpers.getWindow(from); + var sourceCoordinates = convertOption(options.from || from, win, from); + var destinationCoordinates = convertOption(options.to || options, win, from); - _dragStart: function(node){ - var options = { bubbles:false, cancelable:false }; - this.createAndDispatchEvent(node, 'dragstart', options); - }, + DragonDrop.html5drag = syn.support.pointerEvents; - _drag: function(node){ - var options = { bubbles:true, cancelable:true }; - this.createAndDispatchEvent(node, 'drag', options); - }, + if (options.adjust !== false) { + adjust(sourceCoordinates, destinationCoordinates, win); + } - _dragEnter: function(node){ - var options = { bubbles:true, cancelable:true }; - this.createAndDispatchEvent(node, 'dragenter', options); + 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) { - _dragOver: function(node){ - var options = { bubbles:true, cancelable:true }; - this.createAndDispatchEvent(node, 'dragover', options); - }, + var win = syn.helpers.getWindow(from); + var sourceCoordinates = convertOption(options.from || from, win, from); + var destinationCoordinates = convertOption(options.to || options, win, from); - _dragLeave: function(node){ - var options = { bubbles:true, cancelable:false }; - this.createAndDispatchEvent(node, 'dragleave', options); - }, + if (options.adjust !== false) { + adjust(sourceCoordinates, destinationCoordinates, win); + } - _drop: function(node){ - var options = { bubbles:true, cancelable:true, buttons:1 }; - this.createAndDispatchEvent(node, 'drop', options); - }, + if(from.draggable){ + return html5DragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500); + } else{ + return pointerDragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500); + } + } +}); - _dragEnd: function(node){ - var options = { bubbles:true, cancelable:false }; - this.createAndDispatchEvent(node, 'dragend', options); +syn.helpers.extend(syn.events.types,{ + dragStart: { + options: { bubbles:false, 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); - } + drag: { + options: { bubbles:true, cancelable:true }, + create: createDragEvent }, - - 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; - } + dragEnter: { + options: { bubbles:true, cancelable:true }, + create: createDragEvent }, - - - // Cleanup the currentDataTransferItem object - cleanup: function(){ - this.currentDataTransferItem=null; - this.focusWindow = null; + dragOver: { + options: { bubbles:true, cancelable:true }, + create: createDragEvent }, - - - /** - * This function defines the dataTransfer Object, which otherwise is immutable. d= DataTrnsfer() is not a valid constructor - * @param node - */ - 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; - } - } - } - }; - return dataTransfer; + dragLeave: { + options: { bubbles:true, cancelable:false }, + create: createDragEvent }, - - - startMove : function (start, end, duration) { - 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 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 = { - 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 = 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? - } else { - current = DragonDrop.mouseMove(end, current); - win.document.body.removeChild(cursor); - resolve(); - } - }; - syn.helpers.extend(cursor.style, { - height: "5px", - width: "5px", - backgroundColor: "red", - position: "absolute", - zIndex: 19999, - fontSize: "1px" - }); - win.document.body.appendChild(cursor); - move(); - - }); + drop: { + options: { bubbles:true, cancelable:true, buttons:1 }, + create: createDragEvent }, - - 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; + dragEnd: { + options: { bubbles:true, cancelable:false }, + create: createDragEvent } - -}; - -function createDragEvent(eventName, options, element){ - var dragEvent = syn.create.mouse.event(eventName, options, element); - - 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 -//====================================================================================================== - - +}); +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); -// check if elementFromPageExists -(function dragSupport() { + await pointerMoves({ + start: fromPoint, + end: toPoint, + duration, + // record the element each go-round .. + startingState: elementFromPoint(fromPoint, win), + focusWindow, + triggerPointerMove: triggerBasicPointerMove + }); - // document body has to exists for this test - if (!document.body) { - syn.schedule(dragSupport, 1); - return; + if(syn.support.pointerEvents){ + createEventAtPoint("pointerup", toPoint, win); } - 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(syn.support.touchEvents){ + createEventAtPoint("touchend", toPoint, win); } - var el = document.elementFromPoint(3, 1); - if (el === div) { - syn.support.elementFromClient = true; - } else { - syn.support.elementFromPage = true; + createEventAtPoint("mouseup", toPoint, win); + if(syn.support.pointerEvents){ + createEventAtPoint("pointerleave", toPoint, win); } - document.body.removeChild(div); - document.body.scrollTop = 0; -})(); - - - - - - - // 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); + createEventAtPoint("mouseleave", toPoint, win); +} - // 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); +// 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); - 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); - } + if (last !== el && el && last) { + var options = syn.helpers.extend({}, point); - 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); + // 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); - - return el; - }, - - - - - - //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) { - return new Promise((resolve) => { - 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); - resolve(); - } - }; - syn.helpers.extend(cursor.style, { - height: "5px", - width: "5px", - backgroundColor: "red", - position: "absolute", - zIndex: 19999, - fontSize: "1px" - }); - win.document.body.appendChild(cursor); - move(); - }); - }, - - - - - startDrag = async function (win, fromPoint, toPoint, duration) { + options.relatedTarget = last; if(syn.support.pointerEvents){ - createEventAtPoint("pointerover", fromPoint, win); - createEventAtPoint("pointerenter", fromPoint, win); + syn.trigger(el, 'pointerover', options); + syn.trigger(el, 'pointerenter', options); } - 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 startMove(win, fromPoint, toPoint, duration); + syn.trigger(el, "mouseover", options); + syn.trigger(el, "mouseenter", options); + } - 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); - }, + 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); + //} + return el; +} - center = function (el) { - return syn.helpers.addOffset({}, el); - }, - 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 = 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 - }; +async function html5DragAndDrop(focusWindow, fromPoint, toPoint, duration = 500){ + + // 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, + focusWindow, + triggerPointerMove: function(newPoint, previousPoint, win){ + var thisElement = elementFromPoint(newPoint, this.focusWindow); + var previousElement = elementFromPoint(previousPoint, this.focusWindow); + var options = syn.helpers.extend({}, newPoint); + + if (thisElement !== previousElement) { + + options.relatedTarget = thisElement; + createEventAtPoint("dragleave", options, focusWindow); + + options.relatedTarget = previousElement; + createEventAtPoint("dragenter", options, focusWindow); + } + createEventAtPoint("dragover", options, focusWindow); + return newPoint; } - return option; - }, - - - - - + }); + createEventAtPoint("dragleave", toPoint, focusWindow); + createEventAtPoint("dragend", toPoint, focusWindow); + createEventAtPoint("mouseout", toPoint, focusWindow); + createEventAtPoint("mouseleave", toPoint, focusWindow); - // 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; + createEventAtPoint("drop", toPoint, focusWindow); + createEventAtPoint("dragend", toPoint, focusWindow); + createEventAtPoint("mouseover", toPoint, focusWindow); + createEventAtPoint("mouseenter", toPoint, focusWindow); - // first, lets see if we can scroll 100 px - if (top > 0) { + // these are the "user" moving the mouse away after the drop + createEventAtPoint("mousemove", toPoint, focusWindow); + createEventAtPoint("mouseout", toPoint, focusWindow); + createEventAtPoint("mouseleave", toPoint, focusWindow); +} +// 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 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 newPoint = { + clientX: distX * fraction + start.clientX, + clientY: distY * fraction + start.clientY + }; + calls++; + + if (fraction < 1) { + syn.helpers.extend(cursor.style, { + left: (newPoint.clientX + scrollOffset.left + 2) + "px", + top: (newPoint.clientY + scrollOffset.top + 2) + "px" + }); + 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 { - top = 0; - diff = -off.top; + triggerPointerMove(end, currentState, win); + win.document.body.removeChild(cursor); + resolve(); } - from.clientY = from.clientY - diff; - to.clientY = to.clientY - diff; - syn.helpers.scrollOffset(win, { - top: top, - left: off.left - }); - } - }; - - + }; + syn.helpers.extend(cursor.style, { + height: "5px", + width: "5px", + backgroundColor: "red", + position: "absolute", + zIndex: 19999, + fontSize: "1px" + }); + win.document.body.appendChild(cursor); + move(); + }); +} @@ -572,149 +379,155 @@ syn.create.dragend = { -/** - * @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) { - 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); - } +//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]) + }; + } + 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 = 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; +} - return startMove(win, sourceCoordinates, destinationCoordinates, options.duration || 500); - }, +// 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; + // first, lets see if we can scroll 100 px + if (top > 0) { + } else { + top = 0; + diff = -off.top; + } + from.clientY = from.clientY - diff; + to.clientY = to.clientY - diff; + syn.helpers.scrollOffset(win, { + top: top, + left: off.left + }); + } +}; +function getDataTransferObject(){ + // For a series of dragOperations, we want the same dataTransfer Object to be carried over + throw new Error("get from cDragEvent options"); + if (!this.currentDataTransferItem){ + return this.currentDataTransferItem = this.createDataTransferObject(); + }else { + return this.currentDataTransferItem; + } +} +function createDragEvent(eventName, options, element){ + var dragEvent = syn.events.kinds.mouse.create(eventName, options, element); + dragEvent.dataTransfer = getDataTransferObject(); + return syn.dispatch(dragEvent, element, eventName, false); +} - /** - * @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) { +// 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; - var win = syn.helpers.getWindow(from); - var sourceCoordinates = convertOption(options.from || from, win, from); - var destinationCoordinates = convertOption(options.to || options, win, from); + if(point == null){return null;} - if (options.adjust !== false) { - adjust(sourceCoordinates, destinationCoordinates, win); - } + if (syn.support.elementFromPage) { + var off = syn.helpers.scrollOffset(win); + clientX = clientX + off.left; //convert to pageX + clientY = clientY + off.top; //convert to pageY + } - DragonDrop.html5drag = from.draggable; + return win.document.elementFromPoint(Math.round(clientX), Math.round(clientY)); +} - if(DragonDrop.html5drag){ - return DragonDrop.dragAndDrop(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); - }else{ - return startDrag(win, sourceCoordinates, destinationCoordinates, options.duration || 500, callback); +/** +* 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; + } + } } - } -}); + }; + 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 5a72b07f..0dbbabb2 100644 --- a/src/key.js +++ b/src/key.js @@ -3,437 +3,250 @@ 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; +// 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) { + return new Promise((resolve) => { + //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}); + } - try { - result = el.selectionStart !== undefined && el.selectionStart !== null; - } - catch(e) { - result = false; - } + // 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), - 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 - }; + // 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; } - } - 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, { + 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}); + } + + }); + }, /** - * @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) { + //break it up into parts ... + //go through each type and run + var parts = (options + "") + .match(/(\[[^\]]+\])|([^\[])/g), + el = element, + part; + while( part = parts.shift() ) { + if (part.length > 1) { + part = part.substr(1, part.length - 2); + } + var result = await this._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; - } - } 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 -}); + try { + event = h.createEventObject.apply(this, arguments); + h.extend(event, options); + } catch (e) {} -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]; + return event; } } - 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]; +// 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.key[options + "Key"] = 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; + 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); + } } - - 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.key[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 +263,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 +297,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 +306,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 +315,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 +333,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 +353,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 +365,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", {}); } @@ -615,7 +428,7 @@ h.extend(syn.key, { el = orders[i][0]; if (this === el) { var nextIndex = i; - if (syn.key.shiftKey) { + 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]; @@ -631,27 +444,27 @@ h.extend(syn.key, { if (!current) { current = firstNotIndexed; } else { - syn.__tryFocus(current); + syn.helpers.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); + if (syn.keysBeingHeldDown.shiftKey) { + selectText(this, sel.start === 0 ? 0 : sel.start - 1, sel.end); } else { - syn.selectText(this, sel.start === 0 ? 0 : sel.start - 1); + 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) + if (syn.keysBeingHeldDown.shiftKey) { + 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) + selectText(this, sel.end + 1 > getText(this) .length ? getText(this) .length : sel.end + 1); } @@ -660,273 +473,415 @@ h.extend(syn.key, { '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? + 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, + + 'f1': 112, + 'f2': 113, + 'f3': 114, + 'f4': 115, + 'f5': 116, + 'f6': 117, + 'f7': 118, + 'f8': 119, + 'f9': 120, + 'f10': 121, + 'f11': 122, + 'f12': 123 + }, + + // 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]; + } } - }, - '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; - } - } - }, - 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; +// ## Start helpers +var textProperty = (function(){ + var el = document.createElement("span"); + return el.textContent != null ? 'textContent' : 'innerText'; +})(), + formElExp = /input|textarea/i; - //don't change the orignial - options = h.extend({}, options); - if (options.character) { - h.extend(options, syn.key.options(options.character, type)); - delete options.character; - } - options = h.extend({ - ctrlKey: !! syn.key.ctrlKey, - altKey: !! syn.key.altKey, - shiftKey: !! syn.key.shiftKey, - metaKey: !! syn.key.metaKey - }, options); +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]; + } + } + return syn.key.defaults.character; +} - 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; +function getKeyOptions(key, event) { + var keyData = getKeyData(key); - if (options.key && keyboardEventKeys[options.key]) { - options.key = keyboardEventKeys[options.key]; - } + if (!keyData[event]) { + //we shouldn't be creating this event + return null; + } - 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) {} + 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; + } - return event; - } - } + if (charCode === 'char') { + result.charCode = key.charCodeAt(0); + } else if (charCode !== null) { + result.charCode = 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) { - return new Promise((resolve) => { - //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}); - } + // all current browsers have which property to normalize keyCode/charCode + if (result.keyCode) { + result.which = result.keyCode; + } else { + result.which = result.charCode; + } - // 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), + return result; +} - // a function that gets the default behavior for a key - getDefault = syn.key.getDefault, +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; +} - // how this browser handles preventing default events - prevent = syn.key.browser.prevent, +// selection is not supported by some inputs and would throw in Chrome. +function supportsSelection(el) { + var result; - // the result of the default event - defaultResult, + try { + result = el.selectionStart !== undefined && el.selectionStart !== null; + } + catch(e) { + result = false; + } - keypressOptions = syn.key.options(key, 'keypress'); + return result; +} - 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); - } 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; - } +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(); + } +} - runDefaults = syn.trigger(element, 'keypress', keypressOptions); - if (runDefaults) { - defaultResult = getDefault(key) - .call(element, keypressOptions, h.getWindow(element), - key, undefined, caret); - } - } - } 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; - } +// gets the selection of an input or textarea +function getSelection(el) { + var real, r, start; - syn.trigger(element, 'keypress', keypressOptions); + // 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 { + 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; } + 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 + }; } - if (defaultResult && defaultResult.nodeName) { - element = defaultResult; - } + } catch (e) { + var prop = formElExp.test(el.nodeName) ? "value" : "textContent"; - if (defaultResult !== null) { - syn.schedule(function () { + return { + start: el[prop].length, + end: el[prop].length + }; + } + } +} - 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')); - resolve({runDefaults: true, element}); - }, 1); - } else { - resolve({runDefaults: true, 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; + } +} - }); - }, - /** - * @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: async function (element, options) { - //break it up into parts ... - //go through each type and run - var parts = (options + "") - .match(/(\[[^\]]+\])|([^\[])/g), - el = element, - part; - while( part = parts.shift() ) { - if (part.length > 1) { - part = part.substr(1, part.length - 2); - } - var result = await this._key(el, part); - - el = result.element || el; +// gets all focusable elements +function getFocusable(el) { + var document = h.getWindow(el) + .document, + res = []; + + 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 {element:el}; + } + 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..5f0daeb8 100644 --- a/src/key.support.js +++ b/src/key.support.js @@ -6,7 +6,7 @@ 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"), @@ -43,7 +43,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 +56,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 +85,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 67efef57..163803f7 100644 --- a/src/mouse.js +++ b/src/mouse.js @@ -1,95 +1,82 @@ var syn = require('./synthetic'); -//handles mosue events +// Add high-level functions directly to syn +syn.helpers.extend(syn, { + click: function (element, options, force) { + return new Promise((resolve) => { + syn.helpers.addOffset(options, element); + if(syn.support.pointerEvents){ + syn.trigger(element, 'pointerdown', options); + } + if(syn.support.touchEvents){ + syn.trigger(element, 'touchstart', options); + } -var h = syn.helpers, - getWin = h.getWindow; + syn.trigger(element, "mousedown", options); -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 + "}"); + //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); } - } - } - //submit a form - if (!(syn.support.clickSubmits) && ((nodeName === "input" || - nodeName === "button") && - type === "submit")) { + 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); + } - var form = syn.closest(element, "form"); - if (form) { - syn.trigger(form, "submit", {}); + }, 1); + }) + }, + rightClick: function (element, options) { + return new Promise((resolve)=>{ + syn.helpers.addOffset(options, element); + var mouseopts = extend(extend({}, syn.mouse.browser.right.mouseup), options); + if(syn.support.pointerEvents){ + syn.trigger(element, 'pointerdown', mouseopts); } - } - //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") { + syn.trigger(element, "mousedown", mouseopts); - //if(!syn.support.clickChecks && !syn.support.changeChecks){ - // element.checked = !element.checked; - //} - if (!syn.support.clickChanges) { - syn.trigger(element, "change", {}); - } - } + //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", extend(extend({}, syn.mouse.browser.right.contextmenu), options)); + } + resolve(); + }, 1); + }) - //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); - } + }, + dblclick: async function (element, options) { + syn.helpers.addOffset(options, element); + var self = this; + await this._click(element, options); + await schedulePromise(2); + await this._click(element, options); + syn.trigger(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 +85,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,16 +94,16 @@ 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) { @@ -139,19 +126,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) { @@ -168,7 +171,7 @@ h.extend(syn.create, { //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 +181,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,18 +207,85 @@ 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 5068489c..7ba4cd90 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -2,215 +2,116 @@ //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, schedulePromise, key = /keypress|keyup|keydown/, - page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/, + bind, unbind, schedule, schedulePromise, + + //this is maintained so we can click on html and blur the active element - activeElement, + activeElement; + + +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. + + // ## Event dispatching methods /** - * @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. + * Trigger will attempt to create and dispatch a SINGLE event on an element. + * It will also run any registered default actions. * - *

Basic Use

- * The following clicks an input element with id='description' - * and then types 'Hello World'. + * 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). * - @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: - *

- *
    - *
  • [syn.prototype.click click] - a mousedown, focus, mouseup, and click.
  • - *
  • [syn.prototype.dblclick dblclick] - two click! events followed by a dblclick.
  • - *
  • [syn.prototype.key key] - types a single character (keydown, keypress, keyup).
  • - *
  • [syn.prototype.type type] - types multiple characters into an element.
  • - *
  • [syn.prototype.move move] - moves the mouse from one position to another (triggering mouseover / mouseouts).
  • - *
  • [syn.prototype.drag drag] - a mousedown, followed by mousemoves, and a mouseup.
  • - *
- * 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: - *
    - *
  • syn.create - contains methods to setup, convert options, and create an event of a specific type.
  • - *
  • syn.defaults - default behavior by event type (except for keys).
  • - *
  • syn.key.defaults - default behavior by key.
  • - *
  • syn.keycodes - supported keys you can type.
  • - *
- *

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. + * 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. */ - syn = function (type, element, options, callback) { - return (new syn.init(type, element, options, callback)); - }; + 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]; -syn.config = opts; + var event, ret, autoPrevent, dispatchEl = element; -// 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) {} -}; - -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); -}; - -schedule = syn.config.schedule || function (fn, ms) { - setTimeout(fn, ms); -}; -schedulePromise = function(time) { - return new Promise(function(resolve){ - setTimeout(resolve, time) - }); -} + //any setup code? + if (syn.support.ready === 2 && setup) { + setup(type, options, element); + } -/** - * @Static - */ -extend(syn, { - /** - * Creates a new synthetic event instance - * @hide - * @param {String} type - * @param {HTMLElement} element - * @param {Object} options - * @param {Function} callback - */ - 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") { - return this[type](args.element, args.options); + autoPrevent = options._autoPrevent; + //get kind + delete options._autoPrevent; + + if ( eventType && eventType.create ) { + ret = eventType && eventType.create(type, options, element); } else { - return syn.trigger(args.element, type, args.options); + //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); + } + + if (ret && syn.support.ready === 2 && eventType && eventType.default) { + eventType.default.call(element, options, autoPrevent); + } + 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 { + 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; @@ -229,167 +130,190 @@ extend(syn, { } 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 extend(event, options); + }, createBasicStandardEvent: function (type, defaults, doc) { var event; try { @@ -417,7 +341,6 @@ extend(syn, { return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; } }, - extend: extend, scrollOffset: function (win, set) { var doc = win.document.documentElement, body = win.document.body; @@ -454,463 +377,134 @@ extend(syn, { options.pageY = syn.helpers.getWindow(el).scrollY + rect.top + rect.height / 2; } return options; - } - }, - // 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); - }); - } - - event.preventDefault = function () { - prevents++; - if (++prevents > 0) { - preventDefault.apply(this, []); - } - }; - element.dispatchEvent(event); - return prevents <= 0; - } else { + }, + 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; } - - //create the event - event = createKind.event(type, options, dispatchEl); - - //send the event - ret = syn.dispatch(event, dispatchEl, type, autoPrevent); - } - - 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; - - return isSupported; - } - -}); -/** - * @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); - }); + return el; + }, + // adds jQuery like data (adds an expando) and data exists FOREVER :) + // This could start using weakmap + 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 { - this.result = syn.trigger(args.element, type, args.options); - if (args.callback) { - args.callback.call(this, args.element, this.result); - } - return this; + return data[el[expando]][key]; } - }); - 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, force) { - return new Promise((resolve) => { - syn.helpers.addOffset(options, element); - if(syn.support.pointerEvents){ - syn.trigger(element, 'pointerdown', options); + }, + /** + * 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; } - if(syn.support.touchEvents){ - syn.trigger(element, 'touchstart', options); + 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"); } - syn.trigger(element, "mousedown", options); + 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; - //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); - } + return isSupported; + } + } +} - 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 - schedule(function () { - resolve(true); - }, 1); - } +syn.config = opts; - }, 1); - }) +function toMap(str){ + var map = {}; + str.split("|").forEach( ch => { map[ch] = true; }); + return map; +} - }, - /** - * @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) { - return new Promise((resolve)=>{ - 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)); - } - resolve(); - }, 1); - }) +// helper for supporting IE8 and below: +// focus will throw in some circumnstances, like element being invisible + - }, - /** - * @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": async function (element, options) { - syn.helpers.addOffset(options, element); - var self = this; - await this._click(element, options); - await schedulePromise(2); - await this._click(element, options); - syn.trigger(element, "dblclick", options); - return true; - } -}); - -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/pages/syn.schedule.html b/test/pages/syn.schedule.html index d3cd7b41..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 diff --git a/test/syn_test.js b/test/syn_test.js index ad6bba4b..898b98b2 100644 --- a/test/syn_test.js +++ b/test/syn_test.js @@ -119,24 +119,3 @@ QUnit.test("syn.support effect on scroll position, #30", function () { }); }); - -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/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', From 3420c574b6cbc3aa1fbe3eba86b36c6b63facb19 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Sun, 14 Mar 2021 13:07:41 -0500 Subject: [PATCH 05/23] tests pass in chrome --- src/drag.js | 41 ++++++++++++------------------ src/key.js | 16 +++++++----- src/mouse.js | 23 ++++++++++++----- src/synthetic.js | 4 +-- test/mouse_button_test.js | 40 ++++++++++++++--------------- test/mouse_move_test.js | 6 ++--- test/testpages/html5_dragdrop.html | 14 +++++----- test/testpages/mousemove.html | 26 +++++++++---------- 8 files changed, 86 insertions(+), 84 deletions(-) diff --git a/src/drag.js b/src/drag.js index a7ccccb7..acd642b6 100644 --- a/src/drag.js +++ b/src/drag.js @@ -106,7 +106,7 @@ syn.helpers.extend(syn,{ var sourceCoordinates = convertOption(options.from || from, win, from); var destinationCoordinates = convertOption(options.to || options, win, from); - DragonDrop.html5drag = syn.support.pointerEvents; + //DragonDrop.html5drag = syn.support.pointerEvents; if (options.adjust !== false) { adjust(sourceCoordinates, destinationCoordinates, win); @@ -151,7 +151,7 @@ syn.helpers.extend(syn,{ }); syn.helpers.extend(syn.events.types,{ - dragStart: { + dragstart: { options: { bubbles:false, cancelable:false }, create: createDragEvent }, @@ -159,15 +159,15 @@ syn.helpers.extend(syn.events.types,{ options: { bubbles:true, cancelable:true }, create: createDragEvent }, - dragEnter: { + dragenter: { options: { bubbles:true, cancelable:true }, create: createDragEvent }, - dragOver: { + dragover: { options: { bubbles:true, cancelable:true }, create: createDragEvent }, - dragLeave: { + dragleave: { options: { bubbles:true, cancelable:false }, create: createDragEvent }, @@ -175,7 +175,7 @@ syn.helpers.extend(syn.events.types,{ options: { bubbles:true, cancelable:true, buttons:1 }, create: createDragEvent }, - dragEnd: { + dragend: { options: { bubbles:true, cancelable:false }, create: createDragEvent } @@ -194,7 +194,6 @@ async function pointerDragAndDrop(win, fromPoint, toPoint, duration = 500) { 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); @@ -205,7 +204,7 @@ async function pointerDragAndDrop(win, fromPoint, toPoint, duration = 500) { duration, // record the element each go-round .. startingState: elementFromPoint(fromPoint, win), - focusWindow, + win, triggerPointerMove: triggerBasicPointerMove }); @@ -228,6 +227,7 @@ 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? @@ -268,8 +268,10 @@ function triggerBasicPointerMove(point, last, win){ return el; } -async function html5DragAndDrop(focusWindow, fromPoint, toPoint, duration = 500){ +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); @@ -285,10 +287,10 @@ async function html5DragAndDrop(focusWindow, fromPoint, toPoint, duration = 500) end: toPoint, duration, startingState: fromPoint, - focusWindow, + win: focusWindow, triggerPointerMove: function(newPoint, previousPoint, win){ - var thisElement = elementFromPoint(newPoint, this.focusWindow); - var previousElement = elementFromPoint(previousPoint, this.focusWindow); + var thisElement = elementFromPoint(newPoint, focusWindow); + var previousElement = elementFromPoint(previousPoint, focusWindow); var options = syn.helpers.extend({}, newPoint); if (thisElement !== previousElement) { @@ -462,22 +464,11 @@ function adjust(from, to, win) { }; - - -function getDataTransferObject(){ - // For a series of dragOperations, we want the same dataTransfer Object to be carried over - throw new Error("get from cDragEvent options"); - if (!this.currentDataTransferItem){ - return this.currentDataTransferItem = this.createDataTransferObject(); - }else { - return this.currentDataTransferItem; - } -} - function createDragEvent(eventName, options, element){ var dragEvent = syn.events.kinds.mouse.create(eventName, options, element); - dragEvent.dataTransfer = getDataTransferObject(); + // TODO: find a nicer way of doing this. + dragEvent.dataTransfer = dragAndDropTransferObject; return syn.dispatch(dragEvent, element, eventName, false); } diff --git a/src/key.js b/src/key.js index 0dbbabb2..e2db0e10 100644 --- a/src/key.js +++ b/src/key.js @@ -29,7 +29,10 @@ syn.helpers.extend(syn,{ * @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) { @@ -134,17 +137,18 @@ syn.helpers.extend(syn,{ * @param {Function} [callback] a function to callback */ 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 = element, + el = args.element, part; while( part = parts.shift() ) { if (part.length > 1) { part = part.substr(1, part.length - 2); } - var result = await this._key(el, part); + var result = await syn.key(el, part); el = result.element || el; @@ -222,7 +226,7 @@ syn.helpers.extend(syn.events.types,{ keydown: { setup: function (type, options, element) { if (h.inArray(options, syn.key.kinds.special) !== -1) { - syn.key[options + "Key"] = element; + syn.keysBeingHeldDown[options + "Key"] = element; } } }, @@ -239,7 +243,7 @@ syn.helpers.extend(syn.events.types,{ keyup: { setup: function (type, options, element) { if (h.inArray(options, syn.key.kinds.special) !== -1) { - syn.key[options + "Key"] = null; + syn.keysBeingHeldDown[options + "Key"] = null; } } }, @@ -405,8 +409,8 @@ syn.helpers.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 { diff --git a/src/mouse.js b/src/mouse.js index 163803f7..443b0b51 100644 --- a/src/mouse.js +++ b/src/mouse.js @@ -3,7 +3,10 @@ var syn = require('./synthetic'); // Add high-level functions directly to syn syn.helpers.extend(syn, { click: function (element, options, force) { + var args = syn.args(options,element); + return new Promise((resolve) => { + var element = args.element; syn.helpers.addOffset(options, element); if(syn.support.pointerEvents){ syn.trigger(element, 'pointerdown', options); @@ -41,9 +44,11 @@ syn.helpers.extend(syn, { }) }, 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 = extend(extend({}, syn.mouse.browser.right.mouseup), options); + var mouseopts = syn.helpers.extend(syn.helpers.extend({}, syn.mouse.browser.right.mouseup), options); if(syn.support.pointerEvents){ syn.trigger(element, 'pointerdown', mouseopts); } @@ -57,7 +62,10 @@ syn.helpers.extend(syn, { } syn.trigger(element, "mouseup", mouseopts); if (syn.mouse.browser.right.contextmenu) { - syn.trigger(element, "contextmenu", extend(extend({}, syn.mouse.browser.right.contextmenu), options)); + syn.trigger(element, "contextmenu", + syn.helpers.extend( + syn.helpers.extend({}, syn.mouse.browser.right.contextmenu), options ) + ); } resolve(); }, 1); @@ -65,12 +73,13 @@ syn.helpers.extend(syn, { }, dblclick: async function (element, options) { - syn.helpers.addOffset(options, element); + var args = syn.args(options,element); + syn.helpers.addOffset(options, args.element); var self = this; - await this._click(element, options); - await schedulePromise(2); - await this._click(element, options); - syn.trigger(element, "dblclick", options); + await this.click(args.element, options); + await syn.helpers.schedulePromise(2); + await this.click(args.element, options); + syn.trigger(args.element, "dblclick", options); return true; } }) diff --git a/src/synthetic.js b/src/synthetic.js index 7ba4cd90..34512aef 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -312,7 +312,7 @@ var syn = { }, createEventObject: function (type, options, element) { var event = element.ownerDocument.createEventObject(); - return extend(event, options); + return syn.helpers.extend(event, options); }, createBasicStandardEvent: function (type, defaults, doc) { var event; @@ -322,7 +322,7 @@ var syn = { event = doc.createEvent("UIEvents"); } finally { event.initEvent(type, true, true); - extend(event, defaults); + syn.helpers.extend(event, defaults); } return event; }, diff --git a/test/mouse_button_test.js b/test/mouse_button_test.js index df74a092..34a9ca6d 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"); @@ -241,35 +241,33 @@ QUnit.test("Click Radio Buttons", function () { }); -QUnit.test("Click! Event Order", syn.skipFocusTests ? 3 : 4, async function () { +// this test can be flaky ... I think we need methods like .click to wait on the page loading +QUnit.test("Click! Event Order", 1, async function () { var order = 0; st.g("qunit-fixture") .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(); await syn.click("focusme", {}); + QUnit.deepEqual(actualOrder, expectedOrder); QUnit.start(); }); diff --git a/test/mouse_move_test.js b/test/mouse_move_test.js index 7a89de49..26766770 100644 --- a/test/mouse_move_test.js +++ b/test/mouse_move_test.js @@ -21,8 +21,8 @@ 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(); @@ -35,7 +35,7 @@ QUnit.test("Move Cursor Upward", 8, function () { var pageUnderTest = document.getElementById('pageUnderTest').contentDocument.querySelector('body'); var source = pageUnderTest.querySelector('#cell_c2'); var destination = pageUnderTest.querySelector('#cell_a2'); - + //debugger; await syn.move(source, {to: destination, duration: testSpeed}); // ensure we get mouse events over the places that we expect 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 + From 62b5cf393b8ff6d66a37417a53662f4661330e9e Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Sun, 14 Mar 2021 14:11:14 -0500 Subject: [PATCH 06/23] uses css selectors --- src/key.js | 4 +- src/synthetic.js | 5 +- test/key_test.js | 116 +++++++++++++++++++------------------- test/mouse_button_test.js | 26 ++++----- 4 files changed, 77 insertions(+), 74 deletions(-) diff --git a/src/key.js b/src/key.js index e2db0e10..8062a611 100644 --- a/src/key.js +++ b/src/key.js @@ -123,14 +123,14 @@ syn.helpers.extend(syn,{ * * The following types 'JavaScript MVC' then deletes the space. * @codestart - * syn.type('name', 'JavaScript MVC[left][left][left]\b') + * 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") + * 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 diff --git a/src/synthetic.js b/src/synthetic.js index 34512aef..047912ba 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -123,7 +123,10 @@ var 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]); + if(arguments[i].match(/^\w+$/)) { + throw new Error(arguments[i]); + } + res.element = document.querySelector(arguments[i]); } else if (arguments[i]) { res.options = arguments[i]; } diff --git a/test/key_test.js b/test/key_test.js index 413e91c5..97aa07cb 100644 --- a/test/key_test.js +++ b/test/key_test.js @@ -24,19 +24,19 @@ 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"); }); @@ -63,7 +63,7 @@ QUnit.test("Key \\r Submits Forms", 2, async function () { stop(); - await syn.key("key", "\r"); + await syn.key("#key", "\r"); equal(submit, 1, "submit on keypress"); equal(change, 1, "submit on keypress"); @@ -81,7 +81,7 @@ QUnit.test("Key \\r Clicks Links", 1, async function () { return false; }); stop(); - await syn.key("focusLink", "\r"); + await syn.key("#focusLink", "\r"); equal(clicked, 1, "clicked"); start(); @@ -98,7 +98,7 @@ QUnit.test("Key Event Order", 1, async function () { st.binder("key", "input", recorder); st.binder("key", "keyup", recorder); stop(); - await syn.key("key", "B"); + await syn.key("#key", "B"); var expected = ["keydown", "keypress", "keyup"]; if (syn.support.oninput) { @@ -115,7 +115,7 @@ QUnit.test("Key \\r Adds Newline in Textarea", async function () { .value = ""; stop(); - await syn.type("synTextArea", "ab\rcd"); + await syn.type("#synTextArea", "ab\rcd"); console.log("After") equal(st.g('synTextArea') .value.replace("\r", ""), "ab\ncd", "typed new line correctly"); @@ -126,11 +126,11 @@ QUnit.test("Key \\b", async function () { st.g("key") .value = ""; stop(); - await syn.type("key", "abc"); + await syn.type("#key", "abc"); equal(st.g("key") .value, "abc", "abc written"); - syn.key("key", "\b"); + syn.key("#key", "\b"); equal(st.g("key") .value, "ab", "ab written (key deleted)"); start(); @@ -156,7 +156,7 @@ QUnit.test("Key Character Order", async function () { .value; }); stop(); - await syn.key("key", "J"); + await syn.key("#key", "J"); equal(upVal, "J", "Up Typing works"); @@ -203,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)) { @@ -255,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"); @@ -263,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 @@ -339,7 +339,7 @@ QUnit.test("Type with tabs", function async () { stop(); //give ie a second to focus setTimeout(async function () { - await syn.type('first', '\r\tSecond\tThird\tFourth'); + await syn.type('#first', '\r\tSecond\tThird\tFourth'); equal(clicked, 1, "clicked first"); equal(st.g('second') @@ -354,12 +354,12 @@ QUnit.test("Type with tabs", function async () { QUnit.test("Type left and right", async function () { stop(); - await syn.type('key', "012345678[left][left][left]\b"); + await syn.type('#key', "012345678[left][left][left]\b"); equal(st.g('key') .value, "01234678", "left works"); - await syn.type('key', "[right][right]a"); + await syn.type('#key', "[right][right]a"); equal(st.g('key') .value, "0123467a8", "right works"); @@ -369,7 +369,7 @@ QUnit.test("Type left and right", async function () { QUnit.test("Type left and delete", async function () { stop(); - await syn.type('key', "123[left][delete]"); + await syn.type('#key', "123[left][delete]"); equal(st.g('key') .value, "12", "left delete works"); @@ -383,7 +383,7 @@ QUnit.test("Typing Shift", async function () { st.binder('key', 'keypress', function (ev) { shift = ev.shiftKey; }); - await syn.type('key', "[shift]A[shift-up]"); + await syn.type('#key', "[shift]A[shift-up]"); ok(shift, "Shift key on"); start(); @@ -397,9 +397,9 @@ QUnit.test("Typing Shift then clicking", async function () { shift = ev.shiftKey; }); - await syn.type('key', "[shift]A") - await syn.click('inner', {}) - await syn.type('key', "[shift-up]"); + await syn.type('#key', "[shift]A") + await syn.click('#inner', {}) + await syn.type('#key', "[shift-up]"); ok(shift, "Shift key on click"); @@ -410,12 +410,12 @@ QUnit.test("Typing Shift then clicking", async function () { QUnit.test("Typing Shift Left and Right", async function () { stop(); - await syn.type('key', "012345678[shift][left][left][left][shift-up]\b[left]\b"); + await syn.type('#key', "012345678[shift][left][left][left][shift-up]\b[left]\b"); equal(st.g('key') .value, "01235", "shift left works"); - await syn.type('key', "[left][left][shift][right][right]\b[shift-up]"); + await syn.type('#key', "[left][left][shift][right][right]\b[shift-up]"); equal(st.g('key').value, "015", "shift right works"); @@ -427,7 +427,7 @@ QUnit.test("Typing Shift Left and Right", async function () { QUnit.test("shift characters", async function () { stop(); - await syn.type('key', "@"); + await syn.type('#key', "@"); equal(st.g('key') .value, "@", "@ character works"); @@ -451,11 +451,11 @@ QUnit.test("shift keycodes", async function () { ok(ev.which === ev.keyCode, "which is normalized"); }); - await syn.type('key', "[shift]"); + await syn.type('#key', "[shift]"); ok(keyIsDown, "shift modifier key pressed successfully"); - await syn.type('key', "[shift-up]"); + await syn.type('#key', "[shift-up]"); ok(!keyIsUp, "shift modifier key released successfully"); start(); @@ -464,7 +464,7 @@ QUnit.test("shift keycodes", async function () { QUnit.skip("shift practical test", async function () { stop(); - await syn.type('key', "hello [shift]world[shift-up]"); + await syn.type('#key', "hello [shift]world[shift-up]"); // TODO: Fix this! //equal(key.value, "hello WORLD", "uppercasing successful while using shift"); @@ -488,11 +488,11 @@ QUnit.test("ctrl keycodes", async function () { ok(ev.which === ev.keyCode, "which is normalized"); }); - await syn.type('key', "[ctrl]"); + await syn.type('#key', "[ctrl]"); ok(keyIsDown, "ctrl modifier key pressed successfully"); - await syn.type('key', "[ctrl-up]"); + await syn.type('#key', "[ctrl-up]"); ok(!keyIsUp, "ctrl modifier key released successfully"); start(); @@ -501,13 +501,13 @@ QUnit.test("ctrl keycodes", async function () { QUnit.test("ctrl practical test", async function () { stop(); - await syn.type('key', "Hello World"); + 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]"); + 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"); @@ -530,11 +530,11 @@ QUnit.test("alt keycodes", async function () { ok(ev.which === ev.keyCode, "which is normalized"); }); - await syn.type('key', "[alt]"); + await syn.type('#key', "[alt]"); ok(keyIsDown, "alt modifier key pressed successfully"); - await syn.type('key', "[alt-up]"); + await syn.type('#key', "[alt-up]"); ok(!keyIsUp, "alt modifier key released successfully"); start(); @@ -556,11 +556,11 @@ QUnit.test("meta keycodes", async function () { ok(ev.which === ev.keyCode, "which is normalized"); }); - await syn.type('key', "[meta]"); + await syn.type('#key', "[meta]"); ok(keyIsDown, "meta modifier key pressed successfully"); - await syn.type('key', "[meta-up]"); + await syn.type('#key', "[meta-up]"); ok(!keyIsUp, "meta modifier key released successfully"); start(); @@ -577,7 +577,7 @@ QUnit.test("meta keycodes", async function () { // start(); //}); - //syn.type('key', "[insert]", function () {}); + //syn.type('#key', "[insert]", function () {}); //}); // INSERT TEST disabled because of https://github.com/bitovi/syn/issues/131 @@ -585,12 +585,12 @@ QUnit.test("meta keycodes", async function () { //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(); @@ -607,7 +607,7 @@ QUnit.test("caps keycodes", async function () { start(); }); - await syn.type('key', "[caps]"); + await syn.type('#key', "[caps]"); }); @@ -616,11 +616,11 @@ QUnit.test("caps keycodes", async 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(); @@ -639,7 +639,7 @@ test("number key codes", 2, async function () { start(); }); - await syn.type('key', "[down]"); + await syn.type('#key', "[down]"); }); QUnit.test("Key codes of like-keys", async function () { @@ -670,7 +670,7 @@ QUnit.test("Key codes of like-keys", async function () { ok(ev.which === ev.keyCode); done(); }); - await syn.type("key", "[" + key + "]"); + await syn.type("#key", "[" + key + "]"); }; for (var key in keys) { @@ -689,12 +689,12 @@ QUnit.test("focus moves on keydown to another element", async function () { ok(true, "keypress called"); start(); }); - await syn.type('key', "a"); + await syn.type('#key', "a"); }); QUnit.test("typing in a number works", async function () { stop(); - await syn.type('key', 9999); + await syn.type('#key', 9999); equal(st.g('key') .value, "9999", "typing in numbers works"); @@ -703,7 +703,7 @@ QUnit.test("typing in a number works", async function () { QUnit.test("typing in a contenteditable works", async function () { stop(); - await syn.type("editable", "hello world"); + await syn.type("#editable", "hello world"); var editable = st.g("editable"); var text = editable.textContent || editable.innerText; @@ -721,7 +721,7 @@ QUnit.test("typing in an input type=number works", async function() { "" + ""; - await syn.type("number", 123); + await syn.type("#number", 123); var val = st.g("number").value; equal(val, "123", "number input was edited"); start(); @@ -736,7 +736,7 @@ QUnit.test("Key property, a typed", async function () { a = ev.key; equal('a', ev.key); }); - await syn.type('key', "a"); + await syn.type('#key', "a"); ok(a, "a key typed"); start(); @@ -757,11 +757,11 @@ QUnit.test("Control key", async function () { ok(ev.key === 'Control', "key is normalized"); }); - await syn.type('key', "[ctrl]"); + await syn.type('#key', "[ctrl]"); ok(keyIsDown, "Control modifier key pressed successfully"); - await syn.type('key', "[ctrl-up]"); + await syn.type('#key', "[ctrl-up]"); ok(!keyIsUp, "Control modifier key released successfully"); start(); }); @@ -782,11 +782,11 @@ QUnit.test("alt keycodes", async function () { ok(ev.key === 'Alt', "key is normalized"); }); - await syn.type('key', "[alt]"); + await syn.type('#key', "[alt]"); ok(keyIsDown, "Alt modifier key pressed successfully"); - await syn.type('key', "[alt-up]"); + await syn.type('#key', "[alt-up]"); ok(!keyIsUp, "Alt modifier key released successfully"); start(); @@ -807,11 +807,11 @@ QUnit.test("meta keycodes", async function () { ok(ev.key === 'Meta', "key is normalized"); }); - await syn.type('key', "[meta]"); + await syn.type('#key', "[meta]"); ok(keyIsDown, "meta modifier key pressed successfully"); - await syn.type('key', "[meta-up]"); + 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 34a9ca6d..12b6dc2c 100644 --- a/test/mouse_button_test.js +++ b/test/mouse_button_test.js @@ -265,7 +265,7 @@ QUnit.test("Click! Event Order", 1, async function () { st.binder("focusme", "click", pushType); stop(); - await syn.click("focusme", {}); + await syn.click("#focusme", {}); QUnit.deepEqual(actualOrder, expectedOrder); QUnit.start(); @@ -296,7 +296,7 @@ QUnit.test("Click! Pointer Event Order", syn.support.pointerEvents ? 3 : 0, asyn } stop(); - await syn.click("pointerTarget", {}); + await syn.click("#pointerTarget", {}); QUnit.start(); }); @@ -325,7 +325,7 @@ QUnit.test("Click! Touch Event Order", syn.support.touchEvents ? 3 : 0, async fu } stop(); - syn.click("touchTarget", {}); + syn.click("#touchTarget", {}); QUnit.start(); }); @@ -348,7 +348,7 @@ QUnit.test("Click! Anchor has href", async function () { QUnit.ok(target.href.indexOf("#aHash") > -1, "got href"); }); - await syn.click("jsHrefHash", {}); + await syn.click("#jsHrefHash", {}); QUnit.equal(window.location.hash, "#aHash", "hash set ..."); QUnit.start(); @@ -378,7 +378,7 @@ QUnit.test("Click! Anchor Focuses", syn.skipFocusTests ? 1 : 2, async function ( stop(); //need to give browsers a second to show element - await syn.click("focusme", {}); + await syn.click("#focusme", {}); QUnit.start(); @@ -400,9 +400,9 @@ if (!syn.skipFocusTests) { }); stop(); - await syn.click("one", {}); - await syn.key("one","a"); - await syn.click("two", {}); + await syn.click("#one", {}); + await syn.key("#one","a"); + await syn.click("#two", {}); QUnit.start(); QUnit.equal(change, 1, "Change called once"); @@ -420,8 +420,8 @@ if (!syn.skipFocusTests) { }); stop(); - await syn.click("one", {}); - await syn.key("one","a"); + await syn.click("#one", {}); + await syn.key("#one","a"); await syn.click(document.documentElement, {}); QUnit.start(); QUnit.equal(change, 1, "Change called once"); @@ -435,7 +435,7 @@ QUnit.test("Right Click", async function () { context++; }); - await syn.rightClick("one", {}); + await syn.rightClick("#one", {}); if (syn.mouse.browser.contextmenu) { QUnit.equal(1, context, "context was called"); @@ -462,7 +462,7 @@ QUnit.test("Right Click Issues PointerEvents", syn.support.pointerEvents ? 2 : 0 }); } stop(); - await syn.rightClick("pointerTarget", {}); + await syn.rightClick("#pointerTarget", {}); QUnit.start(); }); @@ -479,7 +479,7 @@ QUnit.test("Double Click", async function () { eventSequence.push('click'); }); - await syn.dblclick("dblclickme", {}); + await syn.dblclick("#dblclickme", {}); QUnit.equal(eventSequence.join(', '), 'click, click, dblclick', 'expected event sequence for doubleclick'); QUnit.start(); From 4a1cc01c242877cec3b3b0c82ad77bc386cd95f8 Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Sun, 14 Mar 2021 15:30:10 -0500 Subject: [PATCH 07/23] rough readme updates --- README.md | 175 ++++++++++++++++++++++++++++++++++++++++++----- src/synthetic.js | 6 +- 2 files changed, 159 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 5c83fcc2..ba219b67 100644 --- a/README.md +++ b/README.md @@ -11,24 +11,34 @@ 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 +```shell +npm install syn +``` or bower: - > bower install syn +```shell +bower install syn +``` Or by downloading it [here](https://github.com/bitovi/syn/archive/v0.2.0.zip). @@ -88,28 +98,157 @@ _PATH/TO_ should be the path to the installed syn folder. ## Use +`syn` has 6 core user behavior actions that +simulate most of how a user interacts with a page. +`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. +### Core user behavior actions -### syn.click( element [, options][, callback] ) +The core user behavior actions all: -### syn.dblclick( element [, options][, callback] ) +- take an element (either has a CSS selector ) +- take optional options that can configure the behavior of the +- return a promise -### syn.type( element, text [, callback] ) +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: -### syn.key( element, key [, callback] ) +```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 +}); +``` -### syn.delay( time=600 ) +#### syn.click( element [, options] ) -### syn.drag( element, optionsOrTarget [, callback]) +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" ) -## Contributing +// does the same thing as the previous example, but passes an +// element directly. +await syn.click( document.querySelector('#big-button') ) +``` -Check out the [contribution guide](CONTRIBUTING.md). +Parameters: + +- __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. + +Returns: + +- `Promise` A promise that resolves to `false` if `event.preventDefault()` was called, returns `true` if otherwise. + +#### syn.rightClick( element [, options] ) + +#### syn.dblclick( element [, options] ) + +#### syn.type( element, text ) + +#### syn.key( element, key ) + +#### syn.drag( element, optionsOrTarget ) + +#### syn.move( element, optionsOrTarget ) -## Relevant Links +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. -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) +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, + + // 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, + + // 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/src/synthetic.js b/src/synthetic.js index 047912ba..cdc97f56 100644 --- a/src/synthetic.js +++ b/src/synthetic.js @@ -123,9 +123,6 @@ var 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 - if(arguments[i].match(/^\w+$/)) { - throw new Error(arguments[i]); - } res.element = document.querySelector(arguments[i]); } else if (arguments[i]) { res.options = arguments[i]; @@ -339,9 +336,10 @@ var syn = { return -1; }, getWindow: function (element) { - if(!element) {debugger;} if (element.ownerDocument) { return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; + } else if(element.defaultView) { + return element.defaultView; } }, scrollOffset: function (win, set) { From de925cf5c906d783f25787e653d3622d64b7415f Mon Sep 17 00:00:00 2001 From: Justin Meyer Date: Sun, 14 Mar 2021 15:47:13 -0500 Subject: [PATCH 08/23] more ready for a release --- .jshintrc | 3 ++- README.md | 49 ++++++------------------------------------------- package.json | 2 +- 3 files changed, 9 insertions(+), 45 deletions(-) diff --git a/.jshintrc b/.jshintrc index ca1763ad..4153e54a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -54,5 +54,6 @@ "yui": true, "browser": true, "phantom": true, - "rhino": true + "rhino": true, + "esversion": 8 } diff --git a/README.md b/README.md index ba219b67..790b7660 100644 --- a/README.md +++ b/README.md @@ -34,59 +34,22 @@ Install Syn via the command line with npm: npm install syn ``` -or bower: - -```shell -bower install syn -``` - -Or by downloading it [here](https://github.com/bitovi/syn/archive/v0.2.0.zip). - -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 `