/* * FuncUnit - 2.0.4 * http://funcunit.com * Copyright (c) 2013 Bitovi * Tue, 19 Nov 2013 20:58:56 GMT * Licensed MIT */ /* * Syn - 3.3.1 * * Copyright (c) 2013 Bitovi * Tue, 08 Oct 2013 00:20:41 GMT * Licensed MIT */ !function(window) { // ## synthetic.js var __m2 = (function(){ //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 key events browser = { msie: !! (window.attachEvent && !window.opera), 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 }, createEventObject = function( type, options, element ) { var event = element.ownerDocument.createEventObject(); return extend(event, options); }, data = {}, id = 1, expando = "_synthetic" + new Date().getTime(), bind, unbind, 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, /** * @class Syn * @download funcunit/dist/syn.js * @test funcunit/synthetic/qunit.html * Syn is used to simulate user actions. It creates synthetic events and * performs their default behaviors. * *

Basic Use

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

User Actions and Events

*

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

*

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

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

Asynchronous Callbacks

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

Asynchronous Chaining

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

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

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

jQuery Helper

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

Key Event Recording

*

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

*

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

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

Limitations

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

Contributing to Syn

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

Roll Your Own Functional Test Framework

*

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

* @constructor * Creates a synthetic event on the element. * @param {Object} type * @param {Object} options * @param {Object} element * @param {Object} callback * @return Syn */ Syn = function( type, options, element, callback ) { return (new Syn.init(type, options, element, callback)); }; Syn.config = opts; 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); }; /** * @Static */ extend(Syn, { /** * Creates a new synthetic event instance * @hide * @param {Object} type * @param {Object} options * @param {Object} element * @param {Object} callback */ init: function( type, options, element, callback ) { var args = Syn.args(options, element, callback), self = this; this.queue = []; this.element = args.element; //run event if ( typeof this[type] === "function" ) { this[type](args.options, args.element, function( defaults, el ) { args.callback && args.callback.apply(self, arguments); self.done.apply(self, arguments); }); } else { this.result = Syn.trigger(type, args.options, args.element); 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; } }, /** * Returns an object with the args for a Syn. * @hide * @return {Object} */ args: function() { var res = {}, i = 0; for ( ; i < arguments.length; i++ ) { if ( typeof arguments[i] === 'function' ) { res.callback = arguments[i]; } else if ( arguments[i] && arguments[i].jquery ) { res.element = arguments[i][0]; } 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]); } else if ( arguments[i] ) { res.options = arguments[i]; } } return res; }, click: function( options, element, callback ) { Syn('click!', options, element, callback); }, /** * @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() { 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() { if ( Syn.data(element, "syntheticvalue") != element.value ) { Syn.trigger("change", {}, element); } unbind(element, "blur", arguments.callee); }); } } }, submit: function() { Syn.onParents(this, function( el ) { if ( el.nodeName.toLowerCase() === 'form' ) { el.submit(); return false; } }); } }, changeOnBlur: function( element, prop, value ) { bind(element, "blur", function() { if ( value !== element[prop] ) { Syn.trigger("change", {}, element); } unbind(element, "blur", arguments.callee); }); }, /** * 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); }, bind: bind, unbind: unbind, browser: browser, //some generic helpers helpers: { createEventObject: createEventObject, createBasicStandardEvent: function( type, defaults, doc ) { var event; try { event = doc.createEvent("Events"); } catch (e2) { event = doc.createEvent("UIEvents"); } finally { event.initEvent(type, true, true); extend(event, defaults); } return event; }, inArray: function( item, array ) { var i =0; for ( ; i < array.length; i++ ) { if ( array[i] === item ) { return i; } } return -1; }, getWindow: function( element ) { if(element.ownerDocument){ return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; } }, extend: extend, scrollOffset: function( win , set) { var doc = win.document.documentElement, body = win.document.body; if(set){ window.scrollTo(set.left, set.top); } else { return { left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0), top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0) }; } }, scrollDimensions: function(win){ var doc = win.document.documentElement, body = win.document.body, docWidth = doc.clientWidth, docHeight = doc.clientHeight, compat = win.document.compatMode === "CSS1Compat"; return { height: compat && docHeight || body.clientHeight || docHeight, width: compat && docWidth || body.clientWidth || docWidth }; }, addOffset: function( options, el ) { var jq = Syn.jquery(el), off; if ( typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq ) { el = jq(el); off = el.offset(); options.pageX = off.left + el.width() / 2; options.pageY = off.top + el.height() / 2; } } }, // place for key data key: { ctrlKey: null, altKey: null, shiftKey: null, metaKey: null }, //triggers an event on an element, returns true if default events should be run /** * Dispatches an event and returns true if default events should be run. * @hide * @param {Object} event * @param {Object} element * @param {Object} type * @param {Object} autoPrevent */ dispatch: function( event, element, type, autoPrevent ) { // dispatchEvent doesn't always work in IE (mostly in a popup) if ( element.dispatchEvent && event ) { var preventDefault = event.preventDefault, prevents = autoPrevent ? -1 : 0; //automatically prevents the default behavior for this event //this is to protect agianst nasty browser freezing bug in safari if ( autoPrevent ) { bind(element, type, function( ev ) { ev.preventDefault(); unbind(this, type, arguments.callee); }); } 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)); } }, /** * @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' ) { el.focus(); 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 * * 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. * - `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. * * */ 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, ready: 0 }, /** * 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 {String} type * @param {Object} options * @param {HTMLElement} element * @return {Boolean} true if default events were run, false if otherwise. */ trigger: function( type, options, element ) { 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? Syn.support.ready === 2 && setup && setup(type, options, element); autoPrevent = options._autoPrevent; //get kind delete options._autoPrevent; if ( createType.event ) { ret = createType.event(type, options, element); } else { //convert options options = createKind.options ? createKind.options(type, options, element) : options; if (!Syn.support.changeBubbles && /option/i.test(element.nodeName) ) { dispatchEl = element.parentNode; //jQuery expects clicks on select } //create the event event = createKind.event(type, options, dispatchEl); //send the event ret = Syn.dispatch(event, dispatchEl, type, autoPrevent); } 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 then *

* 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 {Object} options Optiosn to pass to the event. * @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element. * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run. */ then: function( type, options, element, 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](args.options, this.element, function( defaults, el ) { args.callback && args.callback.apply(self, arguments); self.done.apply(self, arguments); }); } else { this.result = Syn.trigger(type, args.options, args.element); args.callback && args.callback.call(this, args.element, this.result); return this; } }) return this; }, /** * 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() { setTimeout(function() { callback && callback.apply(self, []) self.done.apply(self, arguments); }, timeout); }); return this; }, done: function( defaults, el ) { el && (this.element = el); if ( this.queue.length ) { this.queue.pop().call(this, this.element, defaults); } }, /** * @function click * 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( * {clientX: 20, clientY: 100}, * 'create', * function(){ * //check something * }) * @codeend * You can also provide pageX and pageY and Syn will convert it for you. * @param {Object} options * @param {HTMLElement} element * @param {Function} callback */ "_click": function( options, element, callback, force ) { Syn.helpers.addOffset(options, element); Syn.trigger("mousedown", options, element); //timeout is b/c IE is stupid and won't call focus handlers setTimeout(function() { Syn.trigger("mouseup", options, element); if (!Syn.support.mouseDownUpClicks || force ) { Syn.trigger("click", options, element); 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 setTimeout(function() { callback(true); }, 1); } }, 1); }, /** * Right clicks in browsers that support it (everyone but opera). * @param {Object} options * @param {Object} element * @param {Object} callback */ "_rightClick": function( options, element, callback ) { Syn.helpers.addOffset(options, element); var mouseopts = extend(extend({}, Syn.mouse.browser.right.mouseup), options); Syn.trigger("mousedown", mouseopts, element); //timeout is b/c IE is stupid and won't call focus handlers setTimeout(function() { Syn.trigger("mouseup", mouseopts, element); if ( Syn.mouse.browser.right.contextmenu ) { Syn.trigger("contextmenu", extend(extend({}, Syn.mouse.browser.right.contextmenu), options), element); } callback(true); }, 1); }, /** * @function dblclick * Dblclicks an element. This runs two [Syn.prototype.click click] events followed by * a dblclick on the element. *

Example

* @codestart * Syn.dblclick({},'open') * @codeend * @param {Object} options * @param {HTMLElement} element * @param {Function} callback */ "_dblclick": function( options, element, callback ) { Syn.helpers.addOffset(options, element); var self = this; this._click(options, element, function() { setTimeout(function() { self._click(options, element, function() { Syn.trigger("dblclick", options, element); callback(true); }, true); }, 2); }); } }); var actions = ["click", "dblclick", "move", "drag", "key", "type", 'rightClick'], makeAction = function( name ) { Syn[name] = function( options, element, callback ) { return Syn("_" + name, options, element, callback); }; Syn.init.prototype[name] = function( options, element, callback ) { return this.then("_" + name, options, element, callback); }; }, i = 0; for ( ; i < actions.length; i++ ) { makeAction(actions[i]); } return Syn; })(); // ## mouse.js var __m3 = (function(Syn) { //handles mosue events var h = Syn.helpers, getWin = h.getWindow; Syn.mouse = {}; h.extend(Syn.defaults, { mousedown: function( options ) { Syn.trigger("focus", {}, this) }, click: function() { // prevents the access denied issue in IE if the click causes the element to be destroyed var element = this, href, type, 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 + "}") } } } //submit a form if (!(Syn.support.clickSubmits) && (nodeName == "input" && type == "submit") || nodeName == 'button' ) { var form = Syn.closest(element, "form"); if ( form ) { Syn.trigger("submit", {}, form) } } //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("change", {}, element); } } //change a radio button if ( nodeName == "input" && type == "radio" ) { // need to uncheck others if not checked if ( radioChanged && !Syn.support.radioClickChanges ) { Syn.trigger("change", {}, element); } } // change options if ( nodeName == "option" && createChange ) { Syn.trigger("change", {}, element.parentNode); //does not bubble Syn.data(element, "createChange", false) } } }) //add create and setup behavior for mosue events h.extend(Syn.create, { mouse: { options: function( type, options, element ) { var doc = document.documentElement, body = document.body, center = [options.pageX || 0, options.pageY || 0], //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({ bubbles: true, cancelable: true, view: window, detail: 1, screenX: 1, 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, 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).document || document if ( doc.createEvent ) { var event; try { event = doc.createEvent('MouseEvents'); event.initMouseEvent(type, defaults.bubbles, defaults.cancelable, defaults.view, defaults.detail, defaults.screenX, defaults.screenY, defaults.clientX, defaults.clientY, defaults.ctrlKey, defaults.altKey, defaults.shiftKey, defaults.metaKey, defaults.button, defaults.relatedTarget); } catch (e) { event = h.createBasicStandardEvent(type, defaults, doc) } event.synthetic = true; return event; } else { var event; try { event = h.createEventObject(type, defaults, element) } catch (e) {} return event; } } }, click: { setup: function( type, options, element ) { var nodeName = element.nodeName.toLowerCase(), type; //we need to manually 'check' in browser that can't check //so checked has the right value if (!Syn.support.clickChecks && !Syn.support.changeChecks && nodeName === "input" ) { type = element.type.toLowerCase(); //pretty sure lowercase isn't needed if ( type === 'checkbox' ) { element.checked = !element.checked; } if ( type === "radio" ) { //do the checks manually if (!element.checked ) { //do nothing, no change try { Syn.data(element, "radioChanged", true); } catch (e) {} element.checked = true; } } } if ( nodeName == "a" && element.href && !/^\s*javascript:/.test(element.href) ) { //save href Syn.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 //element.setAttribute('href', 'javascript://') //however this breaks scripts using the href //we need to listen to this and prevent the default behavior //and run the default behavior ourselves. Boo! } //if select or option, save old value and mark to change if (/option/i.test(element.nodeName) ) { var child = element.parentNode.firstChild, i = -1; while ( child ) { if ( child.nodeType == 1 ) { i++; if ( child == element ) break; } child = child.nextSibling; } if ( i !== element.parentNode.selectedIndex ) { //shouldn't this wait on triggering //change? element.parentNode.selectedIndex = i; Syn.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; } } } }); //do support code (function() { if (!document.body ) { setTimeout(arguments.callee, 1) return; } var oldSynth = window.__synthTest; window.__synthTest = function() { Syn.support.linkHrefJS = true; } var div = document.createElement("div"), checkbox, submit, form, input, select; div.innerHTML = "
" + "" + "" + "" + "" + "" + "" + "" + "" + "
"; document.documentElement.appendChild(div); form = div.firstChild checkbox = form.childNodes[0]; submit = form.childNodes[2]; select = form.getElementsByTagName('select')[0] checkbox.checked = false; checkbox.onchange = function() { Syn.support.clickChanges = true; } Syn.trigger("click", {}, checkbox) Syn.support.clickChecks = checkbox.checked; checkbox.checked = false; Syn.trigger("change", {}, checkbox); Syn.support.changeChecks = checkbox.checked; form.onsubmit = function( ev ) { if ( ev.preventDefault ) ev.preventDefault(); Syn.support.clickSubmits = true; return false; } Syn.trigger("click", {}, submit) form.childNodes[1].onchange = function() { Syn.support.radioClickChanges = true; } Syn.trigger("click", {}, form.childNodes[1]) Syn.bind(div, 'click', function() { Syn.support.optionClickBubbles = true; Syn.unbind(div, 'click', arguments.callee) }) Syn.trigger("click", {}, select.firstChild) Syn.support.changeBubbles = Syn.eventSupported('change'); //test if mousedown followed by mouseup causes click (opera), make sure there are no clicks after this var clicksCount = 0 div.onclick = function() { Syn.support.mouseDownUpClicks = true; //we should use this to check for opera potentially, but would //be difficult to remove element correctly //Syn.support.mouseDownUpRepeatClicks = (2 == (++clicksCount)) } Syn.trigger("mousedown", {}, div) Syn.trigger("mouseup", {}, div) //setTimeout(function(){ // Syn.trigger("mousedown",{},div) // Syn.trigger("mouseup",{},div) //},1) document.documentElement.removeChild(div); //check stuff window.__synthTest = oldSynth; Syn.support.ready++; })(); return Syn; })(__m2); // ## browsers.js var __m4 = (function(Syn) { Syn.key.browsers = { webkit : { 'prevent': {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, 'character': {"keydown":[0,"key"],"keypress":["char","char"],"keyup":[0,"key"]}, 'specialChars': {"keydown":[0,"char"],"keyup":[0,"char"]}, 'navigation': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'special': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'tab': {"keydown":[0,"char"],"keyup":[0,"char"]}, 'pause-break': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'caps': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'escape': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'num-lock': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'scroll-lock': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'print': {"keyup":[0,"key"]}, 'function': {"keydown":[0,"key"],"keyup":[0,"key"]}, '\r': {"keydown":[0,"key"],"keypress":["char","key"],"keyup":[0,"key"]} }, gecko : { 'prevent': {"keyup":[],"keydown":["char"],"keypress":["char"]}, 'character': {"keydown":[0,"key"],"keypress":["char",0],"keyup":[0,"key"]}, 'specialChars': {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 'navigation': {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 'special': {"keydown":[0,"key"],"keyup":[0,"key"]}, '\t': {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 'pause-break': {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 'caps': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'escape': {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]}, 'num-lock': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'scroll-lock': {"keydown":[0,"key"],"keyup":[0,"key"]}, 'print': {"keyup":[0,"key"]}, 'function': {"keydown":[0,"key"],"keyup":[0,"key"]}, '\r': {"keydown":[0,"key"],"keypress":[0,"key"],"keyup":[0,"key"]} }, msie : { 'prevent':{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, 'character':{"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, 'specialChars':{"keydown":[null,"char"],"keyup":[null,"char"]}, 'navigation':{"keydown":[null,"key"],"keyup":[null,"key"]}, 'special':{"keydown":[null,"key"],"keyup":[null,"key"]}, 'tab':{"keydown":[null,"char"],"keyup":[null,"char"]}, 'pause-break':{"keydown":[null,"key"],"keyup":[null,"key"]}, 'caps':{"keydown":[null,"key"],"keyup":[null,"key"]}, 'escape':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 'num-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, 'scroll-lock':{"keydown":[null,"key"],"keyup":[null,"key"]}, 'print':{"keyup":[null,"key"]}, 'function':{"keydown":[null,"key"],"keyup":[null,"key"]}, '\r':{"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} }, opera : { 'prevent': {"keyup":[],"keydown":[],"keypress":["char"]}, 'character': {"keydown":[null,"key"],"keypress":[null,"char"],"keyup":[null,"key"]}, 'specialChars': {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, 'navigation': {"keydown":[null,"key"],"keypress":[null,"key"]}, 'special': {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 'tab': {"keydown":[null,"char"],"keypress":[null,"char"],"keyup":[null,"char"]}, 'pause-break': {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 'caps': {"keydown":[null,"key"],"keyup":[null,"key"]}, 'escape': {"keydown":[null,"key"],"keypress":[null,"key"]}, 'num-lock': {"keyup":[null,"key"],"keydown":[null,"key"],"keypress":[null,"key"]}, 'scroll-lock': {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, 'print': {}, 'function': {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]}, '\r': {"keydown":[null,"key"],"keypress":[null,"key"],"keyup":[null,"key"]} } }; Syn.mouse.browsers = { webkit : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, opera: {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3}}, "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, msie: { "right":{"mousedown":{"button":2},"mouseup":{"button":2},"contextmenu":{"button":0}}, "left":{"mousedown":{"button":1},"mouseup":{"button":1},"click":{"button":0}}}, chrome : {"right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}, "left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}}, gecko: {"left":{"mousedown":{"button":0,"which":1},"mouseup":{"button":0,"which":1},"click":{"button":0,"which":1}}, "right":{"mousedown":{"button":2,"which":3},"mouseup":{"button":2,"which":3},"contextmenu":{"button":2,"which":3}}} } //set browser Syn.key.browser = (function(){ if(Syn.key.browsers[window.navigator.userAgent]){ return Syn.key.browsers[window.navigator.userAgent]; } for(var browser in Syn.browser){ if(Syn.browser[browser] && Syn.key.browsers[browser]){ return Syn.key.browsers[browser] } } return Syn.key.browsers.gecko; })(); Syn.mouse.browser = (function(){ if(Syn.mouse.browsers[window.navigator.userAgent]){ return Syn.mouse.browsers[window.navigator.userAgent]; } for(var browser in Syn.browser){ if(Syn.browser[browser] && Syn.mouse.browsers[browser]){ return Syn.mouse.browsers[browser] } } return Syn.mouse.browsers.gecko; })(); return Syn; })(__m2, __m3); // ## key.js var __m5 = (function(Syn) { var h = Syn.helpers, // gets the selection of an input or textarea getSelection = function( el ) { // use selectionStart if we can if ( el.selectionStart !== undefined ) { // 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' ) { var real = h.getWindow(el).document.selection.createRange(), r = el.createTextRange(); r.setEndPoint("EndToStart", real); var start = r.text.length return { start: start, end: start + real.text.length } } else { var real = h.getWindow(el).document.selection.createRange(), r = real.duplicate(), 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); var start = r.text.length - real.text.length, 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 } } } catch (e) { return { start: el.value.length, end: el.value.length }; } } }, // 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++ ) { Syn.isFocusable(els[i]) && els[i] != document.documentElement && res.push(els[i]) } return res; }; /** * @add Syn static */ h.extend(Syn, { /** * @attribute * A list of the keys and their keycodes codes you can type. * You can add type keys with * @codestart * Syn('key','delete','title'); * * //or * * Syn('type','One Two Three[left][left][delete]','title') * @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 - 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 * @codeend */ keycodes: { //backspace '\b': 8, //tab '\t': 9, //enter '\r': 13, //special 'shift': 16, 'ctrl': 17, 'alt': 18, //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, '-': 109, '.': 110, //normal-characters, others '/': 111, ';': 186, '=': 187, ',': 188, '-': 189, '.': 190, '/': 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 }, // what we can type in typeable: /input|textarea/i, // selects text on an element selectText: function( el, start, end ) { if ( el.setSelectionRange ) { if (!end ) { el.focus(); el.setSelectionRange(start, start); } else { el.selectionStart = start; el.selectionEnd = end; } } else if ( el.createTextRange ) { //el.focus(); 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.nodeName) ) { var sel = getSelection(el); return el.value.substring(sel.start, sel.end) } //otherwise get from page var win = Syn.helpers.getWindow(el); if ( win.getSelection ) { return win.getSelection().toString(); } else if ( win.document.getSelection ) { return win.document.getSelection().toString() } else { return win.document.selection.createRange().text; } }, getSelection: getSelection }); h.extend(Syn.key, { // retrieves a description of what events for this character should look like data: function( key ) { //check if it is described directly if ( Syn.key.browser[key] ) { return Syn.key.browser[key]; } for ( var kind in Syn.key.kinds ) { if ( h.inArray(key, Syn.key.kinds[kind]) > -1 ) { return Syn.key.browser[kind] } } 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]; } } }, /** * @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 = {}; if ( keyCode == 'key' ) { result.keyCode = Syn.keycodes[key] } else if ( keyCode == 'char' ) { result.keyCode = key.charCodeAt(0) } else { result.keyCode = keyCode; } if ( charCode == 'char' ) { result.charCode = key.charCodeAt(0) } else if ( charCode !== null ) { result.charCode = charCode; } // all current browsers have which property to normalize keyCode/charCode if(result.keyCode){ result.which = result.keyCode; } else { result.which = result.charCode; } return result }, //types of event keys kinds: { special: ["shift", 'ctrl', 'alt', '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]; } } return Syn.key.defaults.character }, // default behavior when typing defaults: { 'character': function( options, scope, key, force, sel ) { if (/num\d+/.test(key) ) { key = key.match(/\d+/)[0] } if ( force || (!Syn.support.keyCharacters && Syn.typeable.test(this.nodeName)) ) { var current = this.value, before = current.substr(0, sel.start), after = current.substr(sel.end), character = key; this.value = before + character + after; //handle IE inserting \r\n var charLength = character == "\n" && Syn.support.textareaCarriage ? 2 : character.length; Syn.selectText(this, before.length + charLength) } }, 'c': function( options, scope, key, force, sel ) { if ( Syn.key.ctrlKey ) { Syn.key.clipboard = Syn.getText(this) } else { Syn.key.defaults.character.apply(this, arguments); } }, 'v': function( options, scope, key, force, sel ) { if ( Syn.key.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, this.value.length) } else { Syn.key.defaults.character.apply(this, arguments); } }, 'home': function() { Syn.onParents(this, function( el ) { if ( el.scrollHeight != el.clientHeight ) { el.scrollTop = 0; return false; } }) }, 'end': function() { Syn.onParents(this, function( el ) { if ( el.scrollHeight != el.clientHeight ) { el.scrollTop = el.scrollHeight; return false; } }) }, 'page-down': function() { //find the first parent we can scroll Syn.onParents(this, function( el ) { if ( el.scrollHeight != el.clientHeight ) { var ch = el.clientHeight el.scrollTop += ch; return false; } }) }, 'page-up': function() { Syn.onParents(this, function( el ) { if ( el.scrollHeight != el.clientHeight ) { var ch = el.clientHeight el.scrollTop -= ch; return false; } }) }, '\b': function( options, scope, key, force, sel ) { //this assumes we are deleting from the end if (!Syn.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { var current = this.value, before = current.substr(0, sel.start), after = current.substr(sel.end); if ( sel.start == sel.end && sel.start > 0 ) { //remove a character this.value = before.substring(0, before.length - 1) + after Syn.selectText(this, sel.start - 1) } else { this.value = before + after; Syn.selectText(this, sel.start) } //set back the selection } }, 'delete': function( options, scope, key, force, sel ) { if (!Syn.support.backspaceWorks && Syn.typeable.test(this.nodeName) ) { var current = this.value, before = current.substr(0, sel.start), after = current.substr(sel.end); if ( sel.start == sel.end && sel.start <= this.value.length - 1 ) { this.value = before + after.substring(1) } else { this.value = before + after; } Syn.selectText(this, sel.start) } }, '\r': function( options, scope, key, force, sel ) { var nodeName = this.nodeName.toLowerCase() // submit a form if (nodeName == 'input' ) { Syn.trigger("change", {}, this); } if (!Syn.support.keypressSubmits && nodeName == 'input' ) { var form = Syn.closest(this, "form"); if ( form ) { Syn.trigger("submit", {}, form); } } //newline in textarea if (!Syn.support.keyCharacters && nodeName == 'textarea' ) { Syn.key.defaults.character.call(this, options, scope, "\n", undefined, sel) } // 'click' hyperlinks if (!Syn.support.keypressOnAnchorClicks && nodeName == 'a' ) { Syn.trigger("click", {}, this); } }, // // 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 // with the next higher tabindex OR the element with the same // tabindex after it in the document. // @return the next element // '\t': function( options, scope ) { // focusable elements var focusEls = getFocusable(this), // the current element's tabindex tabIndex = Syn.tabIndex(this), // will be set to our guess for the next element current = null, // the next index we care about currentIndex = 1000000000, // set to true once we found 'this' element found = false, i = 0, el, //the tabindex of the tabable element we are looking at elIndex, firstNotIndexed, prev; orders = []; for (; i < focusEls.length; i++ ) { orders.push([focusEls[i], i]); } var sort = function( order1, order2 ) { var el1 = order1[0], el2 = order2[0], tab1 = Syn.tabIndex(el1) || 0, tab2 = Syn.tabIndex(el2) || 0; if ( tab1 == tab2 ) { return order1[1] - order2[1] } else { if ( tab1 == 0 ) { return 1; } else if ( tab2 == 0 ) { return -1; } else { return tab1 - tab2; } } } orders.sort(sort); //now find current for ( i = 0; i < orders.length; i++ ) { el = orders[i][0]; if ( this == el ) { if (!Syn.key.shiftKey ) { current = orders[i + 1][0]; if (!current ) { current = orders[0][0] } } else { current = orders[i - 1][0]; if (!current ) { current = orders[focusEls.length - 1][0] } } } } //restart if we didn't find anything if (!current ) { current = firstNotIndexed; } current && current.focus(); return current; }, 'left': function( options, scope, key, force, sel ) { if ( Syn.typeable.test(this.nodeName) ) { if ( Syn.key.shiftKey ) { Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1, sel.end) } else { Syn.selectText(this, sel.start == 0 ? 0 : sel.start - 1) } } }, 'right': function( options, scope, key, force, sel ) { if ( Syn.typeable.test(this.nodeName) ) { if ( Syn.key.shiftKey ) { Syn.selectText(this, sel.start, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) } else { Syn.selectText(this, sel.end + 1 > this.value.length ? this.value.length : sel.end + 1) } } }, 'up': function() { if (/select/i.test(this.nodeName) ) { this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0; //set this to change on blur? } }, 'down': function() { if (/select/i.test(this.nodeName) ) { Syn.changeOnBlur(this, "selectedIndex", this.selectedIndex) this.selectedIndex = this.selectedIndex + 1; //set this to change on blur? } }, 'shift': function() { return null; }, 'ctrl': 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 ) { element.focus() } } }, 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; //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) return options; }, // creates a key event event: function( type, options, element ) { //Everyone Else var doc = h.getWindow(element).document || document; if ( doc.createEvent ) { var event; 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 { var event; try { event = h.createEventObject.apply(this, arguments); h.extend(event, options) } catch (e) {} return event; } } } }); var convert = { "enter": "\r", "backspace": "\b", "tab": "\t", "space": " " } /** * @add Syn prototype */ h.extend(Syn.init.prototype, { /** * @function key * 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('\r','name') * @codeend * For each character, a keydown, keypress, and keyup is triggered if * appropriate. * @param {String} options * @param {HTMLElement} [element] * @param {Function} [callback] * @return {HTMLElement} the element currently focused. */ _key: function( options, element, 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('keyup', options.replace("-up", ""), element) callback(true, element); return; } // keep reference to current activeElement var activeElement = h.getWindow(element).document.activeElement, caret = Syn.typeable.test(element.nodeName) && getSelection(element), key = convert[options] || options, // should we run default events runDefaults = Syn.trigger('keydown', key, element), // a function that gets the default behavior for a key getDefault = Syn.key.getDefault, // how this browser handles preventing default events prevent = Syn.key.browser.prevent, // the result of the default event defaultResult, keypressOptions = Syn.key.options(key, 'keypress'); 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('keypress', keypressOptions, element) 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; } Syn.trigger('keypress', keypressOptions, element) } } if ( defaultResult && defaultResult.nodeName ) { element = defaultResult } if ( defaultResult !== null ) { setTimeout(function() { Syn.trigger('keyup', Syn.key.options(key, 'keyup'), element) callback(runDefaults, element) }, 1) } else { callback(runDefaults, element) } //do mouseup return element; // is there a keypress? .. if not , run default // yes -> did we prevent it?, if not run ... }, /** * @function type * 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('JavaScript MVC[left][left][left]\b','name') * @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 {String} options the text to type * @param {HTMLElement} [element] an element or an id of an element * @param {Function} [callback] a function to callback */ _type: function( options, element, callback ) { //break it up into parts ... //go through each type and run var parts = options.match(/(\[[^\]]+\])|([^\[])/g), self = this, runNextPart = function( runDefaults, el ) { var part = parts.shift(); if (!part ) { callback(runDefaults, el); return; } el = el || element; if ( part.length > 1 ) { part = part.substr(1, part.length - 2) } self._key(part, el, runNextPart) } runNextPart(); } }); if(!Syn.config.support) { //do support code !function() { if (!document.body ) { setTimeout(arguments.callee, 1) return; } var div = document.createElement("div"), checkbox, submit, form, input, submitted = false, anchor, textarea, inputter; div.innerHTML = "
" + "" + "" + "" + "" + "" + "" + "" + "" + "
"; document.documentElement.appendChild(div); form = div.firstChild; checkbox = form.childNodes[0]; submit = form.childNodes[2]; anchor = form.getElementsByTagName("a")[0]; textarea = form.getElementsByTagName("textarea")[0]; inputter = form.childNodes[3]; form.onsubmit = function( ev ) { if ( ev.preventDefault ) ev.preventDefault(); Syn.support.keypressSubmits = true; ev.returnValue = false; return false; }; // Firefox 4 won't write key events if the element isn't focused inputter.focus(); Syn.trigger("keypress", "\r", inputter); Syn.trigger("keypress", "a", inputter); Syn.support.keyCharacters = inputter.value == "a"; inputter.value = "a"; Syn.trigger("keypress", "\b", inputter); Syn.support.backspaceWorks = inputter.value == ""; inputter.onchange = function() { Syn.support.focusChanges = true; } inputter.focus(); Syn.trigger("keypress", "a", inputter); form.childNodes[5].focus(); // this will throw a change event Syn.trigger("keypress", "b", inputter); Syn.support.keysOnNotFocused = inputter.value == "ab"; //test keypress \r on anchor submits Syn.bind(anchor, "click", function( ev ) { if ( ev.preventDefault ) ev.preventDefault(); Syn.support.keypressOnAnchorClicks = true; ev.returnValue = false; return false; }) Syn.trigger("keypress", "\r", anchor); Syn.support.textareaCarriage = textarea.value.length == 4; document.documentElement.removeChild(div); Syn.support.ready++; }(); } else { Syn.helpers.extend(Syn.support, Syn.config.support); } return Syn; })(__m2, __m4); // ## drag/drag.js var __m6 = (function(Syn) { // check if elementFromPageExists (function() { // document body has to exists for this test if (!document.body ) { setTimeout(arguments.callee, 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; })(); //gets an element from a point var elementFromPoint = function( point, element ) { var clientX = point.clientX, clientY = point.clientY, win = Syn.helpers.getWindow(element), el; if ( Syn.support.elementFromPage ) { var off = Syn.helpers.scrollOffset(win); clientX = clientX + off.left; //convert to pageX clientY = clientY + off.top; //convert to pageY } el = win.document.elementFromPoint ? win.document.elementFromPoint(clientX, clientY) : element; if ( el === win.document.documentElement && (point.clientY < 0 || point.clientX < 0) ) { return element; } else { return el; } }, //creates an event at a certain point createEventAtPoint = function( event, point, element ) { var el = elementFromPoint(point, element) Syn.trigger(event, point, el || element) return el; }, // creates a mousemove event, but first triggering mouseout / mouseover if appropriate mouseMove = function( point, element, last ) { var el = elementFromPoint(point, element) if ( last != el && el && last ) { var options = Syn.helpers.extend({}, point); options.relatedTarget = el; Syn.trigger("mouseout", options, last); options.relatedTarget = last; Syn.trigger("mouseover", options, el); } Syn.trigger("mousemove", point, el || element) return el; }, // start and end are in clientX, clientY startMove = function( start, end, duration, element, callback ) { var startTime = new Date(), distX = end.clientX - start.clientX, distY = end.clientY - start.clientY, win = Syn.helpers.getWindow(element), current = elementFromPoint(start, element), cursor = win.document.createElement('div'), calls = 0; move = function() { //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, element, current) setTimeout(arguments.callee, 15) } else { current = mouseMove(end, element, current); win.document.body.removeChild(cursor) callback(); } } Syn.helpers.extend(cursor.style, { height: "5px", width: "5px", backgroundColor: "red", position: "absolute", zIndex: 19999, fontSize: "1px" }) win.document.body.appendChild(cursor) move(); }, startDrag = function( start, end, duration, element, callback ) { createEventAtPoint("mousedown", start, element); startMove(start, end, duration, element, function() { createEventAtPoint("mouseup", end, element); callback(); }) }, center = function( el ) { var j = Syn.jquery()(el), o = j.offset(); return { pageX: o.left + (j.outerWidth() / 2), pageY: o.top + (j.outerHeight() / 2) } }, convertOption = function( option, win, from ) { var page = /(\d+)[x ](\d+)/, client = /(\d+)X(\d+)/, relative = /([+-]\d+)[xX ]([+-]\d+)/ //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) ) { var parts = option.match(page) option = { pageX: parseInt(parts[1]), pageY: parseInt(parts[2]) } } if ( typeof option == 'string' && client.test(option) ) { var parts = option.match(client) option = { clientX: parseInt(parts[1]), clientY: parseInt(parts[2]) } } if ( typeof option == 'string' ) { option = Syn.jquery()(option, win.document)[0]; } if ( option.nodeName ) { option = center(option) } if ( option.pageX ) { var off = Syn.helpers.scrollOffset(win); option = { clientX: option.pageX - off.left, clientY: option.pageY - off.top } } 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){ var off = Syn.helpers.scrollOffset(win); var dimensions = Syn.helpers.scrollDimensions(win), 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}); //throw "out of bounds!" } } /** * @add Syn prototype */ Syn.helpers.extend(Syn.init.prototype, { /** * @function move * 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( * { * from: {clientX: 0, clientY: 0}, * to: {clientX: 100, clientY: 100}, * duration: 1000 * }, * document.document) * * ## 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( * { * from: {pageX: 0, pageY: 0}, * to: {pageX: 100, pageY: 100} * }, * document.document) * * ### String Coordinates * * You can set the pageX and pageY as strings like: * * Syn.move( * { * from: "0x0", * to: "100x100" * }, * document.document) * * ### 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( * { * from: $(".recipe")[0], * to: $("#trash")[0] * }, * document.document) * * ### Query Strings * * If jQuery is present, you can pass a query string as the from or to option. * * Syn.move( * { * from: ".recipe", * to: "#trash" * }, * document.document) * * ### No From * * If you don't provide a from, the element argument passed to Syn is used. * * Syn.move( * { to: "#trash" }, * 'myrecipe') * * ### Relative * * You can move the drag relative to the center of the from element. * * Syn.move("+20 +30", "myrecipe"); * * @param {Object} options options to configure the drag * @param {HTMLElement} from the element to move * @param {Function} callback a callback that happens after the drag motion has completed */ _move: function( options, from, callback ) { //need to convert if elements var win = Syn.helpers.getWindow(from), fro = convertOption(options.from || from, win, from), to = convertOption(options.to || options, win, from); options.adjust !== false && adjust(fro, to, win); startMove(fro, to, options.duration || 500, from, callback); }, /** * @function drag * Creates a mousedown and drags from one point to another. * Check out [Syn.prototype.move move] for API details. * * @param {Object} options * @param {Object} from * @param {Object} callback */ _drag: function( options, from, callback ) { //need to convert if elements var win = Syn.helpers.getWindow(from), fro = convertOption(options.from || from, win, from), to = convertOption(options.to || options, win, from); options.adjust !== false && adjust(fro, to, win); startDrag(fro, to, options.duration || 500, from, callback); } }) return Syn; })(__m2); // ## syn.js var __m1 = (function(Syn){ window.Syn = Syn; return Syn; })(__m2, __m3, __m4, __m5, __m6); }(window); !function(window) { // ## browser/init.js var __m5 = (function(jQuery) { var FuncUnit = window.FuncUnit || {}; jQuery.sub = function() { function jQuerySub( selector, context ) { return new jQuerySub.fn.init( selector, context ); } jQuery.extend( true, jQuerySub, this ); jQuerySub.superclass = this; jQuerySub.fn = jQuerySub.prototype = this(); jQuerySub.fn.constructor = jQuerySub; jQuerySub.sub = this.sub; jQuerySub.fn.init = function init( selector, context ) { if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { context = jQuerySub( context ); } return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); }; jQuerySub.fn.init.prototype = jQuerySub.fn; var rootjQuerySub = jQuerySub(document); return jQuerySub; }; FuncUnit.jQuery = jQuery; return FuncUnit; })(jQuery); // ## browser/core.js var __m3 = (function(jQuery, oldFuncUnit) { var FuncUnit = oldFuncUnit.jQuery.sub(); var origFuncUnit = FuncUnit; // override the subbed init method // context can be an object with frame and forceSync: // - a number or string: this is a frame name/number, and means only do a sync query // - true: means force the query to be sync only FuncUnit = function( selector, frame ) { // if you pass true as context, this will avoid doing a synchronous query var frame, forceSync, isSyncOnly = false; if(frame && frame.forceSync){ forceSync = frame.forceSync; } if(frame && typeof frame.frame !== "undefined"){ // its passed as an object frame = frame.frame; } isSyncOnly = typeof forceSync === "boolean"? forceSync: isSyncOnly; // if its a function, just run it in the queue if(typeof selector == "function"){ return FuncUnit.wait(0, selector); } // if the app window already exists, adjust the params (for the sync return value) this.selector = selector; // run this method in the queue also if(isSyncOnly === true){ var collection = performSyncQuery(selector, frame); return collection; } else { // do both performAsyncQuery(selector, frame, this); var collection = performSyncQuery(selector, frame); return collection; } } var getContext = function(context){ if (typeof context === "number" || typeof context === "string") { // try to get the context from an iframe in the funcunit document var sel = (typeof context === "number" ? "iframe:eq(" + context + ")" : "iframe[name='" + context + "']"), frames = new origFuncUnit.fn.init(sel, FuncUnit.win.document.documentElement, true); var frame = (frames.length ? frames.get(0).contentWindow : FuncUnit.win).document.documentElement; } else { frame = FuncUnit.win.document.documentElement; } return frame; }, performAsyncQuery = function(selector, frame, self){ FuncUnit.add({ method: function(success, error){ this.frame = frame; if (FuncUnit.win) { frame = getContext(frame); } this.selector = selector; this.bind = new origFuncUnit.fn.init( selector, frame, true ); success(); return this; }, error: "selector failed: " + selector, type: "query" }); }, performSyncQuery = function(selector, frame){ var origFrame = frame; if (FuncUnit.win) { frame = getContext(frame); } var obj = new origFuncUnit.fn.init( selector, frame, true ); obj.frame = origFrame; return obj; } oldFuncUnit.jQuery.extend(FuncUnit, oldFuncUnit, origFuncUnit) FuncUnit.prototype = origFuncUnit.prototype; return FuncUnit; })(jQuery, __m5); // ## browser/adapters/jasmine.js var __m7 = (function(FuncUnit) { if(window.jasmine) { var paused = false; FuncUnit.unit = { pauseTest:function(){ paused = true; waitsFor(function(){ return paused === false; }, 60000) }, resumeTest: function(){ paused = false; }, assertOK: function(assertion, message){ expect(assertion).toBeTruthy(); }, equiv: function(expected, actual){ return jasmine.getEnv().equals_(expected, actual) } } return FuncUnit; } })(__m3); // ## browser/adapters/qunit.js var __m8 = (function(FuncUnit) { if(window.QUnit) { FuncUnit.unit = { pauseTest:function(){ stop(); }, resumeTest: function(){ start(); }, assertOK: function(assertion, message){ ok(assertion, message) }, equiv: function(expected, actual){ return QUnit.equiv(expected, actual); } } } })(__m3); // ## browser/adapters/adapters.js var __m6 = (function() {})(__m7, __m8); // ## browser/open.js var __m9 = (function($, FuncUnit) { if(FuncUnit.frameMode){ var ifrm = document.createElement("iframe"); ifrm.id = 'funcunit_app'; document.body.insertBefore(ifrm, document.body.firstChild); } var confirms = [], prompts = [], currentDocument, currentHref, // pointer to the popup window appWin, lookingForNewDocument = false, urlWithoutHash = function(url){ return url.replace(/\#.*$/, ""); }, // returns true if url matches current window's url isCurrentPage = function(url){ var pathname = urlWithoutHash(FuncUnit.win.location.pathname), href = urlWithoutHash(FuncUnit.win.location.href), url = urlWithoutHash(url); // must strip off hash from URLs if( pathname === url || href === url ){ return true; } return false; }; /** * @add FuncUnit */ // $.extend(FuncUnit,{ // open is a method /** * @parent utilities * @function FuncUnit.open F.open() * @signature `open(path, success, timeout)` * * Opens a page. It will error if the page can't be opened before timeout. If a URL begins with "//", pages are opened * from the FuncUnit root (the root folder where funcunit is located) * ### Example * * @codestart * F.open("//app/app.html") * @codeend * * @param {String} path a full or partial url to open. * @param {Function} success * @param {Number} timeout * @return {undefined} */ open: function( path, success, timeout ) { if(typeof success != 'function'){ timeout = success; success = undefined; } FuncUnit.add({ method: function(success, error){ //function that actually does stuff, if this doesn't call success by timeout, error will be called, or can call error itself if(typeof path === "string"){ var fullPath = FuncUnit.getAbsolutePath(path); FuncUnit._open(fullPath, error); FuncUnit._onload(function(){ success() }, error); } else { FuncUnit.win = path; success(); } }, success: success, error: "Page " + path + " not loaded in time!", timeout: timeout || 30000 }); }, _open: function(url){ FuncUnit.win = appWin; hasSteal = false; // this will determine if this is supposed to open within a frame FuncUnit.frame = $('#funcunit_app').length? $('#funcunit_app')[0]: null; // if the first time .. if (newPage) { if(FuncUnit.frame){ FuncUnit.win = FuncUnit.frame.contentWindow; FuncUnit.win.location = url; } else{ // giving a large height forces it to not open in a new tab and just opens to the window's height var width = $(window).width(); FuncUnit.win = window.open(url, "funcunit", "height=1000,toolbar=yes,status=yes,width="+width/2+",left="+width/2); // This is mainly for opera. Other browsers will hit the unload event and close the popup. // This block breaks in IE (which never reaches it) because after closing a window, it throws access // denied any time you try to access it, even after reopening. if(FuncUnit.win.___FUNCUNIT_OPENED) { FuncUnit.win.close(); FuncUnit.win = window.open(url, "funcunit", "height=1000,toolbar=yes,status=yes,left="+width/2); } if(!FuncUnit.win){ throw "Could not open a popup window. Your popup blocker is probably on. Please turn it off and try again"; } } appWin = FuncUnit.win; } // otherwise, change the frame's url else { lookingForNewDocument = true; if(isCurrentPage(url)){ /*Sometimes readyState does not correctly reset itself, so we remove the body from the document we are navigating away from, which will get set again when the page has reloaded*/ FuncUnit.win.document.body.parentNode.removeChild(FuncUnit.win.document.body); // set the hash and reload FuncUnit.win.location.hash = url.split('#')[1] || ''; FuncUnit.win.location.reload(true); } else { // setting the location forces a reload; IE freaks out if you try to do both FuncUnit.win.location = url; } // setting to null b/c opera uses the same document currentDocument = null; } lookingForNewDocument = true; }, /** * @parent utilities * @function FuncUnit.confirm F.confirm() * @signature `confirm(answer)` * * When a browser's native confirm dialog is used, this method is used to repress the dialog and simulate * clicking OK or Cancel. Alerts are repressed by default in FuncUnit application windows. * * @codestart * F.confirm(true); * @codeend * * @param {Boolean} answer true if you want to click OK, false otherwise * @return {undefined} */ confirm: function(answer){ confirms.push(!!answer) }, /** * @parent utilities * @function FuncUnit.prompt F.prompt() * @signature `prompt(answer)` * * When a browser's native prompt dialog is used, this method is used to repress the dialog and simulate * clicking typing something into the dialog. * @codestart * F.prompt("Harry Potter"); * @codeend * @param {String} answer Whatever you want to simulate a user typing in the prompt box * @return {undefined} */ prompt: function(answer){ prompts.push(answer) }, _opened: function(){ if (!this._isOverridden("alert")) { FuncUnit.win.alert = function(){} } if (!this._isOverridden("confirm")) { FuncUnit.win.confirm = function(){ var res = confirms.shift(); return res; } } if (!this._isOverridden("prompt")) { FuncUnit.win.prompt = function(){ return prompts.shift(); } } }, _isOverridden:function(type) { return !(/(native code)|(source code not available)/.test(FuncUnit.win[type])); }, _onload: function(success, error){ // saver reference to success loadSuccess = function(){ if(FuncUnit.win.steal){ hasSteal = true; } // called when load happens ... here we check for steal // console.log("success", (FuncUnit.win.steal && FuncUnit.win.steal.isReady) || !hasSteal, // "isReady", (FuncUnit.win.steal && FuncUnit.win.steal.isReady)); if((FuncUnit.win.steal && FuncUnit.win.steal.isReady) || !hasSteal){ success(); }else{ setTimeout(arguments.callee, 200) } } // we only need to do this setup stuff once ... if (!newPage) { return; } newPage = false; if (FuncUnit.support.readystate) { poller(); } else { unloadLoader(); } }, /** * @hide * @parent utilities * Gets a path * @param {String} path */ getAbsolutePath: function( path ) { if ( /^\/\//.test(path) ){ path = path.substr(2); } return path; }, /** * @parent utilities * @property {window} FuncUnit.win F.win() * Use this to refer to the window of the application page. * @codestart *F(F.window).innerWidth(function(w){ * ok(w > 1000, "window is more than 1000 px wide") * }) * @codeend */ win: window, // for feature detection support: { readystate: "readyState" in document }, /** * @parent utilities * @function FuncUnit.eval F.eval() * @signature `eval(str)` * * Used to evaluate code in the application page. * @param {String} str the code to evaluate * @return {Object} the result of the evaluated code */ eval: function(str){ return FuncUnit.win.eval(str) }, // return true if document is currently loaded, false if its loading // actions check this documentLoaded: function(){ var loaded = FuncUnit.win.document.readyState === "complete" && FuncUnit.win.location.href != "about:blank" && FuncUnit.win.document.body; return loaded; }, // return true if new document found checkForNewDocument: function(){ var documentFound = false; // right after setting a new hash and reloading, IE barfs on this occassionally (only the first time) try { documentFound = ((FuncUnit.win.document !== currentDocument && // new document !FuncUnit.win.___FUNCUNIT_OPENED) // hasn't already been marked loaded // covers opera case after you click a link, since document doesn't change in opera || (currentHref != FuncUnit.win.location.href)) && // url is different FuncUnit.documentLoaded(); // fully loaded } catch(e){} if(documentFound){ // reset flags lookingForNewDocument = false; currentDocument = FuncUnit.win.document; currentHref = FuncUnit.win.location.href; // mark it as opened FuncUnit.win.___FUNCUNIT_OPENED = true; // reset confirm, prompt, alert FuncUnit._opened(); } return documentFound; } }); //don't do any of this if in rhino if (navigator.userAgent.match(/Rhino/)) { return; } var newPage = true, hasSteal = false, unloadLoader, loadSuccess, firstLoad = true, onload = function(){ FuncUnit.win.document.documentElement.tabIndex = 0; setTimeout(function(){ FuncUnit.win.focus(); var ls = loadSuccess loadSuccess = null; if (ls) { ls(); } }, 0); Syn.unbind(FuncUnit.win, "load", onload); }, onunload = function(){ FuncUnit.stop = true; removeListeners(); setTimeout(unloadLoader, 0) }, removeListeners = function(){ Syn.unbind(FuncUnit.win, "unload", onunload); Syn.unbind(FuncUnit.win, "load", onload); } unloadLoader = function(){ if(!firstLoad) // dont remove the first run, fixes issue in FF 3.6 removeListeners(); Syn.bind(FuncUnit.win, "load", onload); //listen for unload to re-attach Syn.bind(FuncUnit.win, "unload", onunload) } //check for window location change, documentChange, then readyState complete -> fire load if you have one var newDocument = false, poller = function(){ var ls; if (lookingForNewDocument && FuncUnit.checkForNewDocument() ) { ls = loadSuccess; loadSuccess = null; if (ls) { FuncUnit.win.focus(); FuncUnit.win.document.documentElement.tabIndex = 0; ls(); } } setTimeout(arguments.callee, 500) } // All browsers except Opera close the app window on a reload. This is to fix the case the URL to be opened // has a hash. In this case, window.open doesn't cause a reload if you reuse an existing popup, so we need to close. $(window).unload(function(){ if(FuncUnit.win && FuncUnit.win !== window.top) { FuncUnit.win.close(); } }); return FuncUnit; })(jQuery, __m3); // ## browser/actions.js var __m10 = (function($, FuncUnit, Syn) { window.Syn = Syn; /** * @add FuncUnit */ var clicks = [ // methods /** * * @function FuncUnit.prototype.click .click() * @parent actions * @signature `click(options [,success])` * * Clicks an element. This uses [Syn.prototype.click] to issue a: * * If no clientX/Y or pageX/Y is provided as options, the click happens at the * center of the element. *

For a right click or double click use [FuncUnit.prototype.rightClick] or * [FuncUnit.prototype.dblclick].

*

Example

* @codestart * //clicks the bar element *F("#bar").click() * @codeend * @param {Object} [options] options to pass to the click event. Typically, this is clientX/Y or pageX/Y: * @codestart * $('#foo').click({pageX: 200, pageY: 100}); * @codeend * You can pass it any of the serializable parameters you'd send to * [http://developer.mozilla.org/en/DOM/event.initMouseEvent initMouseEvent], but command keys are * controlled by [FuncUnit.prototype.type]. * * @param {Function} [success] a callback that runs after the click, but before the next action. * @return {funcUnit} returns the funcunit object for chaining. */ 'click', /** * @function FuncUnit.prototype.dblclick .dblclick() * @parent actions * @signature `dblclick(options [,success])` * * Double clicks an element by [FuncUnit.prototype.click clicking] it twice and triggering a dblclick event. * @param {Object} options options to add to the mouse events. This works * the same as [FuncUnit.prototype.click]'s options. * @param {Function} [success] a callback that runs after the double click, but before the next action. * @return {funcUnit} returns the funcunit object for chaining. */ 'dblclick', /** * @function FuncUnit.prototype.rightClick .rightClick() * @parent actions * @signature `rightClick(options [,success])` * Right clicks an element. This typically results in a contextmenu event for browsers that * support it. * @param {Object} options options to add to the mouse events. This works * the same as [FuncUnit.prototype.click]'s options. * @param {Function} [success] a callback that runs after the click, but before the next action. * @return {funcUnit} returns the funcunit object for chaining. */ 'rightClick'], makeClick = function(name){ FuncUnit.prototype[name] = function(options, success){ this._addExists(); if(typeof options == 'function'){ success = options; options = {}; } var selector = this.selector; FuncUnit.add({ method: function(success, error){ options = options || {}; Syn("_" + name, options, this.bind[0],success); }, success: success, error: "Could not " + name + " '" + this.selector+"'", bind: this, type: "action" }); return this; } } for(var i=0; i < clicks.length; i++){ makeClick(clicks[i]) } $.extend(FuncUnit.prototype, { // perform check even if last queued item is a wait beacuse certain waits don't guarantee the element is visible (like text) _addExists: function(){ this.exists(false); }, /** * @function FuncUnit.prototype.type .type() * @parent actions * @signature `type(text [,success])` * * Types text into an element. This makes use of [Syn.type] and works in * a very similar way. *

Quick Examples

* @codestart * //types hello world *F('#bar').type('hello world') * * //submits a form by typing \r *F("input[name=age]").type("27\r") * * //types FuncUnit, then deletes the Unit *F('#foo').type("FuncUnit\b\b\b\b") * * //types JavaScriptMVC, then removes the MVC *F('#zar').type("JavaScriptMVC[left][left][left]"+ * "[delete][delete][delete]") * * //types JavaScriptMVC, then selects the MVC and * //deletes it *F('#zar').type("JavaScriptMVC[shift]"+ * "[left][left][left]"+ * "[shift-up][delete]") * @codeend * *

Characters

* * For a list of the characters you can type, check [Syn.keycodes]. * * @param {String} text the text you want to type * @param {Function} [success] a callback that is run after typing, but before the next action. * @return {FuncUnit} returns the funcUnit object for chaining. */ type: function( text, success ) { this._addExists(); // when you type in something you have to click on it first this.click(); var selector = this.selector; // type("") is a shortcut for clearing out a text input if(text === ""){ text = "[ctrl]a[ctrl-up]\b" } FuncUnit.add({ method : function(success, error){ Syn("_type", text, this.bind[0], success); }, success : success, error : "Could not type " + text + " into " + this.selector, bind : this, type: "action" }); return this; }, trigger: function(evName, success){ this._addExists(); FuncUnit.add({ method : function(success, error){ // need to use the page's jquery to trigger events FuncUnit.win.jQuery(this.bind.selector).trigger(evName) success() }, success : success, error : "Could not trigger " + evName, bind : this, type: "action" }); return this; }, /** * @function FuncUnit.prototype.drag .drag() * @parent actions * @signature `drag(options [,success])` * Drags an element into another element or coordinates. * This takes the same paramameters as [Syn.prototype.move move]. * @param {String|Object} options A selector or coordinates describing the motion of the drag. *
Options as a Selector
* Passing a string selector to drag the mouse. The drag runs to the center of the element * matched by the selector. The following drags from the center of #foo to the center of #bar. * @codestart *F('#foo').drag('#bar') * @codeend *
Options as Coordinates
* You can pass in coordinates as clientX and clientY: * @codestart *F('#foo').drag('100x200') * @codeend * Or as pageX and pageY * @codestart *F('#foo').drag('100X200') * @codeend * Or relative to the start position *F('#foo').drag('+10 +20') *
Options as an Object
* You can configure the duration, start, and end point of a drag by passing in a json object. * @codestart * //drags from 0x0 to 100x100 in 2 seconds *F('#foo').drag({ * from: "0x0", * to: "100x100", * duration: 2000 * }) * @codeend * @param {Function} [success] a callback that runs after the drag, but before the next action. * @return {funcUnit} returns the funcunit object for chaining. */ drag: function( options, success ) { this._addExists(); if(typeof options == 'string'){ options = {to: options} } options.from = this.selector; var selector = this.selector; FuncUnit.add({ method: function(success, error){ Syn("_drag", options, this.bind[0],success); }, success: success, error: "Could not drag " + this.selector, bind: this, type: "action" }) return this; }, /** * @function FuncUnit.prototype.move .move() * @parent actions * @signature `move(options [,success])` * Moves an element into another element or coordinates. This will trigger mouseover * mouseouts accordingly. * This takes the same paramameters as [Syn.prototype.move move]. * @param {String|Object} options A selector or coordinates describing the motion of the move. *
Options as a Selector
* Passing a string selector to move the mouse. The move runs to the center of the element * matched by the selector. The following moves from the center of #foo to the center of #bar. * @codestart *F('#foo').move('#bar') * @codeend *
Options as Coordinates
* You can pass in coordinates as clientX and clientY: * @codestart *F('#foo').move('100x200') * @codeend * Or as pageX and pageY * @codestart *F('#foo').move('100X200') * @codeend * Or relative to the start position *F('#foo').move('+10 +20') *
Options as an Object
* You can configure the duration, start, and end point of a move by passing in a json object. * @codestart * //drags from 0x0 to 100x100 in 2 seconds *F('#foo').move({ * from: "0x0", * to: "100x100", * duration: 2000 * }) * @codeend * @param {Function} [success] a callback that runs after the drag, but before the next action. * @return {funcUnit} returns the funcunit object for chaining. */ move: function( options, success ) { this._addExists(); if(typeof options == 'string'){ options = {to: options} } options.from = this.selector; var selector = this.selector; FuncUnit.add({ method: function(success, error){ Syn("_move", options, this.bind[0], success); }, success: success, error: "Could not move " + this.selector, bind: this, type: "action" }); return this; }, /** * @function FuncUnit.prototype.scroll .scroll() * @parent actions * @signature `scroll(direction, amount, success)` * Scrolls an element in a particular direction by setting the scrollTop or srollLeft. * @param {String} direction "left" or "top" * @param {Number} amount number of pixels to scroll * @param {Function} success */ scroll: function( direction, amount, success ) { this._addExists(); var selector = this.selector, direction; if (direction == "left" || direction == "right") { direction = "Left"; } else if (direction == "top" || direction == "bottom") { direction = "Top"; } FuncUnit.add({ method: function(success, error){ this.bind.each(function(i, el){ this["scroll" + direction] = amount; }) success(); }, success: success, error: "Could not scroll " + this.selector, bind: this, type: "action" }); return this; } }) return FuncUnit; })(jQuery, __m3, Syn); // ## browser/getters.js var __m11 = (function($, FuncUnit) { /** * @add FuncUnit */ //list of jQuery functions we want, number is argument index //for wait instead of getting value FuncUnit.funcs = { // methods /** * @function FuncUnit.prototype.size .size() * @parent dimensions * @signature `size([size] [,timeout] [,success] [,message])` * Gets the number of elements matched by the selector or * waits until the the selector is size. You can also * provide a function that continues to the next action when * it returns true. * @codestart *F(".recipe").size() //gets the number of recipes * *F(".recipe").size(2) //waits until there are 2 recipes * * //waits until size is count *F(".recipe").size(function(size){ * return size == count; * }) * @codeend * @param {Number|Function} [size] number or a checking function. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {Number} if the size parameter is not provided, size returns the number * of elements matched. */ 'size' : 0, /** * @function FuncUnit.prototype.attr .attr() * @parent manipulation * @signature `attr(data, value [,timeout] [,success] [,message])` * Gets the value of an attribute from an element or waits until the attribute * equals the attr param. * @codestart * //waits until the abc attribute == some *F("#something").attr("abc","some") * @codeend * @param {String} data The attribute to get, or wait for. * @param {String|Function} value If provided uses this as a check before continuing to the next action * * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {Object} if the attr parameter is not provided, returns * the attribute. */ 'attr' : 1, /** * @function FuncUnit.prototype.hasClass .hasClass() * @parent css * @signature `hasClass(className [,value] [,timeout] [,success] [,message])` * @codestart * //returns if the element has the class in its className *F("#something").hasClass("selected"); * * //waits until #something has selected in its className *F("#something").hasClass("selected",true); * * //waits until #something does not have selected in its className *F("#something").hasClass("selected",false); * @codeend * @param {String} className The part of the className to search for. * @param {Boolean|Function} [value] If provided uses this as a check before continuing to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {Boolean|funcUnit} if the value parameter is not provided, returns * if the className is found in the element's className. If a value paramters is provided, returns funcUnit for chaining. */ 'hasClass' : 1, //makes wait /** * @function FuncUnit.prototype.html .html() * @parent manipulation * @signature `html([html] [,timeout] [,success] [,message])` * Gets the [http://api.jquery.com/html/ html] from an element or waits until the html is a certain value. * @codestart * //checks foo's html has "JupiterJS" * ok( /JupiterJS/.test(F('#foo').html() ) ) * * //waits until foo's html has JupiterJS *F('#foo').html(/JupiterJS/) * * //waits until foo's html is JupiterJS *F('#foo').html("JupiterJS") * @codeend * * @param {String|Function} [html] If provided uses this as a check before continuing to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if the html parameter is provided, * returns the funcUnit selector for chaining, otherwise returns the html of the selector. */ 'html' : 0, /** * @function FuncUnit.prototype.text .text() * @parent manipulation * @signature `text([text] [,timeout] [,success] [,message])` * Gets the [http://api.jquery.com/text/ text] from an element or waits until the text is a certain value. * @codestart * //checks foo's text has "JupiterJS" * ok( /JupiterJS/.test(F('#foo').text() ) ) * * //waits until bar's text has JupiterJS *F('#foo').text(/JupiterJS/) * * //waits until bar's text is JupiterJS *F('#foo').text("JupiterJS") * @codeend * * @param {String|Function} [text] If provided uses this as a check before continuing to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if the text parameter is provided, * returns the funcUnit selector for chaining, otherwise returns the html of the selector. */ 'text' : 0, /** * @function FuncUnit.prototype.val .val() * @parent manipulation * @signature `val([val] [,timeout] [,success] [,message])` * Gets the [http://api.jquery.com/val/ val] from an element or waits until the val is a certain value. * @codestart * //checks foo's val has "JupiterJS" * ok( /JupiterJS/.test(F('input#foo').val() ) ) * * //waits until bar's val has JupiterJS *F('input#foo').val(/JupiterJS/) * * //waits until bar's val is JupiterJS *F('input#foo').val("JupiterJS") * @codeend * * @param {String|Function} [val] If provided uses this as a check before continuing to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if the val parameter is provided, * returns the funcUnit selector for chaining, otherwise returns the html of the selector. */ 'val' : 0, /** * @function FuncUnit.prototype.css .css() * @parent css * @signature `css(prop [,val] [,timeout] [,success] [,message])` * Gets a [http://api.jquery.com/css/ css] property from an element or waits until the property is * a specified value. * @codestart * // gets the color *F("#foo").css("color") * * // waits until the color is red *F("#foo").css("color","red") * @codeend * * @param {String} prop A css property to get or wait until it is a specified value. * @param {String|Function} [val] If provided uses this as a check before continuing to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if the val parameter is provided, * returns the funcUnit selector for chaining, otherwise returns the css of the selector. */ 'css': 1, 'prop': 1, /** * @function FuncUnit.prototype.offset .offset() * @parent dimensions * @signature `offset([offset] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/offset/ offset] or waits until * the offset is a specified value. * @codestart * // gets the offset *F("#foo").offset(); * * // waits until the offset is 100, 200 *F("#foo").offset({top: 100, left: 200}) * @codeend * * @param {Object|Function} [offset] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if the offset parameter is provided, * returns the funcUnit selector for chaining, otherwise returns the css of the selector. */ 'offset' : 0, /** * @function FuncUnit.prototype.position .position() * @parent dimensions * @signature `position([position] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/position/ position] or waits until * the position is a specified value. * @codestart * // gets the position *F("#foo").position(); * * // waits until the position is 100, 200 *F("#foo").position({top: 100, left: 200}) * @codeend * * @param {Object|Function} [position] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if the position parameter is provided, * returns the funcUnit selector for chaining, otherwise returns the offset of the selector. */ 'position' : 0, /** * @function FuncUnit.prototype.scrollTop .scrollTop() * @parent dimensions * @signature `scrollTop([scrollTop] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/scrollTop/ scrollTop] or waits until * it equals a specified value. * @codestart * // gets the scrollTop *F("#foo").scrollTop(); * * // waits until the scrollTop is 100 *F("#foo").scrollTop(100) * @codeend * * @param {Number|Function} [scrollTop] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if scrollTop is provided, * returns the funcUnit selector for chaining, otherwise returns the scrollTop of the selector. */ 'scrollTop' : 0, /** * @function FuncUnit.prototype.scrollLeft .scrollLeft() * @parent dimensions * @signature `scrollLeft([scrollLeft] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/scrollLeft/ scrollLeft] or waits until * it equals a specified value. * @codestart * // gets the scrollLeft *F("#foo").scrollLeft(); * * // waits until the scrollLeft is 100 *F("#foo").scrollLeft(100) * @codeend * * @param {Number|Function} [scrollLeft] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if scrollLeft is provided, * returns the funcUnit selector for chaining, otherwise returns the scrollLeft of the selector. */ 'scrollLeft' : 0, /** * @function FuncUnit.prototype.height .height() * @parent dimensions * @signature `height([height] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/height/ height] or waits until * it equals a specified value. * @codestart * // gets the height *F("#foo").height(); * * // waits until the height is 100 *F("#foo").height(100) * @codeend * * @param {Number|Function} [height] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if height is provided, * returns the funcUnit selector for chaining, otherwise returns the height of the selector. */ 'height' : 0, /** * @function FuncUnit.prototype.width .width() * @parent dimensions * @signature `width([width] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/width/ width] or waits until * it equals a specified value. * @codestart * // gets the width *F("#foo").width(); * * // waits until the width is 100 *F("#foo").width(100) * @codeend * * @param {Number|Function} [width] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if width is provided, * returns the funcUnit selector for chaining, otherwise returns the width of the selector. */ 'width' : 0, /** * @function FuncUnit.prototype.innerHeight .innerHeight() * @parent dimensions * @signature `innerHeight([innerHeight] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/innerHeight/ innerHeight] or waits until * it equals a specified value. * @codestart * // gets the innerHeight *F("#foo").innerHeight(); * * // waits until the innerHeight is 100 *F("#foo").innerHeight(100) * @codeend * * @param {Number|Function} [innerHeight] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if innerHeight is provided, * returns the funcUnit selector for chaining, otherwise returns the innerHeight of the selector. */ 'innerHeight' : 0, /** * @function FuncUnit.prototype.innerWidth .innerWidth() * @parent dimensions * @signature `innerWidth([innerWidth] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/innerWidth/ innerWidth] or waits until * it equals a specified value. * @codestart * // gets the innerWidth *F("#foo").innerWidth(); * * // waits until the innerWidth is 100 *F("#foo").innerWidth(100) * @codeend * * @param {Number|Function} [innerWidth] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if innerWidth is provided, * returns the funcUnit selector for chaining, otherwise returns the innerWidth of the selector. */ 'innerWidth' : 0, /** * @function FuncUnit.prototype.outerHeight .outerHeight() * @parent dimensions * @signature `outerHeight([outerHeight] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/outerHeight/ outerHeight] or waits until * it equals a specified value. * @codestart * // gets the outerHeight *F("#foo").outerHeight(); * * // waits until the outerHeight is 100 *F("#foo").outerHeight(100) * @codeend * * @param {Number|Function} [outerHeight] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if outerHeight is provided, * returns the funcUnit selector for chaining, otherwise returns the outerHeight of the selector. */ 'outerHeight' : 0, /** * @function FuncUnit.prototype.outerWidth .outerWidth() * @parent dimensions * @signature `outerWidth([outerWidth] [,timeout] [,success] [,message])` * Gets an element's [http://api.jquery.com/outerWidth/ outerWidth] or waits until * it equals a specified value. * @codestart * // gets the outerWidth *F("#foo").outerWidth(); * * // waits until the outerWidth is 100 *F("#foo").outerWidth(100) * @codeend * * @param {Number|Function} [outerWidth] If provided uses this as a check before continuing to the next action. Or you can * provide a function that returns true to continue to the next action. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {String|funcUnit} if outerWidth is provided, * returns the funcUnit selector for chaining, otherwise returns the outerWidth of the selector. */ 'outerWidth' : 0} //makes a jQuery like command. FuncUnit.makeFunc = function(fname, argIndex){ var orig = FuncUnit.fn[fname]; //makes a read / wait function FuncUnit.prototype[fname] = function(){ //assume last arg is success var args = FuncUnit.makeArray(arguments), isWait = args.length > argIndex, success, self = this; args.unshift(this.selector,this.frame,fname) if(isWait){ //get the args greater and equal to argIndex var tester = args[argIndex+3], timeout = args[argIndex+4], success = args[argIndex+5], message = args[argIndex+6], testVal = tester, errorMessage = "waiting for "+fname +" on " + this.selector, frame = this.frame, logMessage = "Checking "+fname+" on '"+this.selector+"'", ret; // can pass in an object or list of arguments if(typeof tester == 'object' && !(tester instanceof RegExp)){ timeout = tester.timeout; success = tester.success; message = tester.message; if(tester.errorMessage){ errorMessage = tester.errorMessage } if(typeof tester.logMessage !== "undefined"){ logMessage = tester.logMessage } tester = tester.condition; } if(typeof timeout == 'function'){ success = timeout; message = success; timeout = undefined; } if(typeof timeout == 'string'){ message = timeout; timeout = undefined; success = undefined; } if(typeof message !== 'string'){ message = undefined; } args.splice(argIndex+3, args.length- argIndex - 3); if(typeof tester != 'function'){ errorMessage += " !== "+testVal tester = function(val){ return FuncUnit.unit.equiv(val, testVal) || (testVal instanceof RegExp && testVal.test(val) ); } } if(message){ errorMessage = message; } FuncUnit.repeat({ method : function(print){ // keep getting new collection because the page might be updating, we need to keep re-checking if(this.bind.prevObject && this.bind.prevTraverser){ var prev = this.bind; this.bind = this.bind.prevObject[this.bind.prevTraverser](this.bind.prevTraverserSelector) this.bind.prevTraverser = prev.prevTraverser; this.bind.prevTraverserSelector = prev.prevTraverserSelector; } else { // pass false so it will only do one synchronous request this.bind =F(this.selector, { frame: frame, forceSync: true }) } if(logMessage){ print(logMessage) } var methodArgs = []; // might need an argument if(argIndex > 0){ methodArgs.push(args[3]); } // lazy flag to ignore the getter error below FuncUnit._ignoreGetterError = true; ret = this.bind[fname].apply(this.bind, methodArgs) FuncUnit._ignoreGetterError = false; var passed = tester.call(this.bind, ret); // unless this is a "size" command, require size to be non-zero (don't pass through if there's no elements) if(this.bind.length === 0 && fname !== "size"){ passed = false; } if(passed){ // if document is still loading if(!FuncUnit.documentLoaded()){ passed = false; } else { // after every successful wait, check for a new document (if someone clicked a link), // and overwrite alert/confirm/prompt // TODO this creates a potential race if a new document is loaded but its steal isn't ready...should poll FuncUnit.checkForNewDocument(); } } return passed; }, success : function(){ if(message){ FuncUnit.unit.assertOK(true, message) } success && success.apply(this, arguments); }, error : function(){ var msg = errorMessage; if(ret){ msg += ", actual value: "+ret; } FuncUnit.unit.assertOK(false, msg); }, timeout : timeout, bind: this, type: "wait" }) return this; }else{ // throw a warning if user tries to use a getter after the start of the test (if there are other async methods) if(!FuncUnit._ignoreGetterError && !FuncUnit._incallback && FuncUnit._haveAsyncQueries()){ console && console.error("You can't run getters after actions and waits. Please put your getters in a callback or at the beginning of the test.") } // just call the original jQ method var methodArgs = []; if(argIndex > 0){ methodArgs.push(args[3]); } return orig.apply(this, methodArgs); } } } for (var prop in FuncUnit.funcs) { FuncUnit.makeFunc(prop, FuncUnit.funcs[prop]); } return FuncUnit; })(jQuery, __m3); // ## browser/traversers.js var __m12 = (function($, FuncUnit){ /** * @add FuncUnit */ // prototype //do traversers var traversers = [ /** * @function FuncUnit.prototype.closest .closest() * @parent traversal * @signature `closest()` * Asynchronous version of jQuery's closest. Performs the exact same functionality as the jQuery method * but adds itself to the queue. * * @codestart * // after the click, filter the collection, then wait for result to be visible *F(".foo").click().closest(".bar").visible(); * @codeend * @param {string} selector */ "closest", /** * @function FuncUnit.prototype.next .next() * @parent traversal * @signature `next()` * Asynchronous version of next. Performs the exact same functionality as the jQuery method * but adds itself to the queue. * * @codestart * // after the click, filter the collection, then wait for result to be visible *F(".foo").click().next().visible(); * @codeend */ "next", /** * @function FuncUnit.prototype.prev .prev() * @parent traversal * @signature `prev()` * Asynchronous version of prev. Performs the exact same functionality as the jQuery method * but adds itself to the queue. * * @codestart * // after the click, filter the collection, then wait for result to be visible *F(".foo").click().prev().visible(); * @codeend */ "prev", /** * @function FuncUnit.prototype.siblings .siblings() * @parent traversal * @signature `siblings()` * Asynchronous version of siblings. Performs the exact same functionality as the jQuery method * but adds itself to the queue. * * @codestart * // after the click, filter the collection, then wait for result to be visible *F(".foo").click().siblings().visible(); * @codeend */ "siblings", /** * @function FuncUnit.prototype.last .last() * @parent traversal * @signature `last()` * Asynchronous version of last. Performs the exact same functionality as the jQuery method * but adds itself to the queue. * * @codestart * // after the click, filter the collection, then wait for result to be visible *F(".foo").click().last().visible(); * @codeend */ "last", /** * @function FuncUnit.prototype.first .first() * @parent traversal * @signature `first()` * Asynchronous version of first. Performs the exact same functionality as the jQuery method * but adds itself to the queue. * * @codestart * // after the click, filter the collection, then wait for result to be visible *F(".foo").click().first().visible(); * @codeend */ "first", /** * @function FuncUnit.prototype.find .find() * @parent traversal * @signature `find()` * Asynchronous version of find. Performs the exact same functionality as the jQuery method * but adds itself to the queue. * * @codestart * // after the click, filter the collection, then wait for result to be visible *F(".foo").click().find(".bar").visible(); * @codeend * @param {string} selector */ "find" ], makeTraverser = function(name){ var orig = FuncUnit.prototype[name]; FuncUnit.prototype[name] = function(selector){ var args = arguments; // find is called (with "this" as document) from FuncUnit.fn.init, so in this case don't queue it up, just run the regular find if (FuncUnit.win && this[0] && this[0].parentNode && this[0].parentNode.nodeType !== 9) { // document nodes are 9 FuncUnit.add({ method: function(success, error){ // adjust the collection by using the real traverser method var newBind = orig.apply(this.bind, args); newBind.prevTraverser = name; newBind.prevTraverserSelector = selector; success(newBind) }, error: "Could not traverse: " + name + " " + selector, bind: this }); } return orig.apply(this, arguments); } }; for(var i =0; i < traversers.length; i++){ makeTraverser(traversers[i]); } return FuncUnit; })(jQuery, __m3); // ## browser/queue.js var __m13 = (function(FuncUnit) { /** * @add FuncUnit */ /** * True when we are in a callback function (something we pass to a FuncUnit plugin). */ FuncUnit._incallback = false; //where we should add things in a callback var currentPosition = 0, startedQueue = false; /** * @property FuncUnit.speed F.speed() * @parent utilities * A global speed setting for actions. Defaults to 0 milliseconds. */ FuncUnit.speed = 0; /** * @property FuncUnit.timeout F.timeout() * @parent utilities * A global timeout value for wait commands. Defaults to 10 seconds. */ FuncUnit.timeout = 10000; /** * @hide * @property FuncUnit._queue _queue * @parent utilities * A queue of methods. Each method in the queue are run in order. After the method is complete, it * calls FuncUnit._done, which pops the next method off the queue and runs it. */ FuncUnit._queue = []; /** * @hide * Logic that determines if this next query needs to be sync, or if we can optimize it. * Returns false if there are actual actions in the queue, returns true if the only queued methods are * S methods. If the only method is an S query, remove it from the queue. */ FuncUnit._needSyncQuery = function(){ // if only method is query, need sync if(FuncUnit._queue.length === 1){ if(FuncUnit._queue[0].type === "query"){ FuncUnit._queue = []; return true; } } // if empty queue, need sync if(FuncUnit._queue.length === 0){ return true; } return false } /** * @hide * Return last item in the queue. */ FuncUnit._lastQueuedItem = function(){ if(!FuncUnit._queue.length){ return null; } return FuncUnit._queue[FuncUnit._queue.length-1]; } /** * @hide * Return true if there are already async methods queued. If true, getters need throw errors. */ FuncUnit._haveAsyncQueries = function(){ for(var i=0; i < FuncUnit._queue.length; i++){ if(FuncUnit._queue[i].type === "action" || FuncUnit._queue[i].type === "wait") return true; } return false; } FuncUnit. /** * @parent utilities * @function FuncUnit.add F.add() * @signature `add(handler)` * Adds a function to the queue. * @param {Object} handler An object that contains the method to run along with other properties: - method : the method to be called. It will be provided a success and error function to call - success : an optional callback to be called after the function is done - error : an error message if the command fails - timeout : the time until success should be called - bind : an object that will be 'this' of the success - type: the type of method (optional) */ add = function(handler){ //if we are in a callback, add to the current position if (FuncUnit._incallback) { FuncUnit._queue.splice(currentPosition, 0, handler); currentPosition++; } else { //add to the end FuncUnit._queue.push(handler); } //if our queue has just started, stop qunit //call done to call the next command if (FuncUnit._queue.length == 1 && ! FuncUnit._incallback) { FuncUnit.unit.pauseTest(); setTimeout(FuncUnit._done, 13) } } var currentEl; /** * @hide * @parent utilities * @function FuncUnit._done _done * @signature `_done(handler)` * * Every queued method calls this when its complete. It gets the next function from the queue and calls it. * @param {Object} el the current jQuery collection * @param {Object} selector */ FuncUnit._done = function(el, selector){ var next, timer, speed = FuncUnit.speed || 0; // TODO: we need to clarify the easing api if(FuncUnit.speed === 'slow'){ speed = 500; } if (FuncUnit._queue.length > 0) { next = FuncUnit._queue.shift(); currentPosition = 0; // set a timer that will error //call next method setTimeout(function(){ timer = setTimeout(function(){ next.stop && next.stop(); if(typeof next.error === "function"){ next.error(); } else { FuncUnit.unit.assertOK(false, next.error); } FuncUnit._done(); }, (next.timeout || FuncUnit.timeout) + speed) // if the last successful method had a collection, save it if(el && el.jquery){ currentEl = el; } // make the new collection the last successful collection if(currentEl){ next.bind = currentEl; } next.selector = selector; next.method( //success function(el){ if(el && el.jquery){ next.bind = el; } //make sure we don't create an error clearTimeout(timer); //mark in callback so the next set of add get added to the front FuncUnit._incallback = true; if (next.success) { // callback's "this" is the collection next.success.apply(next.bind, arguments); } FuncUnit._incallback = false; FuncUnit._done(next.bind, next.selector); }, //error function(message){ clearTimeout(timer); FuncUnit.unit.assertOK(false, message); FuncUnit._done(); }) }, speed); } else { FuncUnit.unit.resumeTest(); } } return FuncUnit; })(__m3); // ## browser/waits.js var __m14 = (function($, FuncUnit) { /** * @add FuncUnit */ FuncUnit. /** * * @function FuncUnit.wait F.wait() * @parent waits * @signature `wait(time, success)` * Waits a timeout before running the next command. Wait is an action and gets * added to the queue. * @codestart * F.wait(100, function(){ * equals(F('#foo').innerWidth(), 100, "innerWidth is 100"); * }) * @codeend * @param {Number} [time] The timeout in milliseconds. Defaults to 5000. * @param {Function} [success] A callback that will run * after the wait has completed, * but before any more queued actions. */ wait = function(time, success){ if(typeof time == 'function'){ success = time; time = undefined; } time = time != null ? time : 5000 FuncUnit.add({ method : function(success, error){ setTimeout(success, time) }, success : success, error : "Couldn't wait!", timeout : time + 1000 }); return this; } FuncUnit. /** * @function FuncUnit.branch F.branch() * @parent waits * @signature `branch(check1, success1, check2, success2)` * Uses 2 checker methods to see which success function to call. This is a way to conditionally * run one method if you're unsure about the conditions of your page, without causing a test * failure. For example, this is useful for login steps, if you're not sure whether the app * is logged in. * * @codestart * F.branch(function(){ * return (F("#exists").size() > 0); * }, function(){ * ok(true, "found exists") * }, function(){ * return (F("#notexists").size() > 0); * }, function(){ * ok(false, "found notexists") * }); * @codeend * * @param {Function} check1 a checker function that, if it returns true, causes success1 to be called * @param {Function} success1 a function that runs when check1 returns true * @param {Function} check2 a checker function that, if it returns true, causes success2 to be called * @param {Function} success2 a function that runs when check2 returns true * @param {Number} timeout if neither checker returns true before this timeout, the test fails */ branch = function(check1, success1, check2, success2, timeout){ FuncUnit.repeat({ method : function(print){ print("Running a branch statement") if(check1()){ success1(); return true; } if(check2()){ success2(); return true; } }, error : "no branch condition was ever true", timeout : timeout, type: "branch" }) } /** * * @function FuncUnit.repeat F.repeat() * @parent waits * @signature `repeat()` * Takes a function that will be called over and over until it is successful. * method : function(){}, success : success, error : errorMessage, timeout : timeout, bind: this */ FuncUnit.repeat = function(options){ var interval, stopped = false , stop = function(){ clearTimeout(interval) stopped = true; }; FuncUnit.add({ method : function(success, error){ options.bind = this.bind; options.selector = this.selector; var printed = false, print = function(msg){ if(!printed){ printed = true; } } interval = setTimeout(function(){ var result = null; try { result = options.method(print) } catch (e) { //should we throw this too error? } if (result) { success(options.bind); }else if(!stopped){ interval = setTimeout(arguments.callee, 10) } }, 10); }, success : options.success, error : options.error, timeout : options.timeout, stop : stop, bind : options.bind, type: options.type }); } /** * * @function FuncUnit.animationEnd F.animationEnd() * @parent waits * @signature `animationEnd()` * Waits until all animations in the page have completed. Only works * if the tested page has jQuery present. */ FuncUnit.animationEnd = function(){ F("body").wait(200).size(function(){ return F.win.$(':animated').length === 0; }); }; FuncUnit.animationsDone = FuncUnit.animationEnd; $.extend(FuncUnit.prototype, { /** * @function FuncUnit.prototype.exists .exists() * @parent waits * @signature `exists([timeout] [,success] [,message])` * Waits until an element exists before running the next action. * @codestart * //waits until #foo exists before clicking it. *F("#foo").exists().click() * @codeend * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a success that is run after the selector exists, but before the next action. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {FuncUnit} returns the funcUnit for chaining. */ exists: function( timeout, success, message ) { var logMessage = "Waiting for '"+this.selector+"' to exist"; if(timeout === false){ // pass false to suppress this wait (make it not print any logMessage) logMessage = false } return this.size({ condition: function(size){ return size > 0; }, timeout: timeout, success: success, message: message, errorMessage: "Exist failed: element with selector '"+this.selector+"' not found", logMessage: logMessage }) }, /** * @function FuncUnit.prototype.missing .missing() * @parent waits * @signature `missing([timeout] [,success] [,message])` * Waits until no elements are matched by the selector. Missing is equivalent to calling * .size(0, success); * @codestart * //waits until #foo leaves before continuing to the next action. *F("#foo").missing() * @codeend * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that is run after the selector exists, but before the next action * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return {FuncUnit} returns the funcUnit for chaining. */ missing: function( timeout,success, message ) { return this.size(0, timeout, success, message) }, /** * @function FuncUnit.prototype.visible .visible() * @parent waits * @signature `visible([timeout] [,success] [,message])` * Waits until the funcUnit selector is visible. * @codestart * //waits until #foo is visible. *F("#foo").visible() * @codeend * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that runs after the funcUnit is visible, but before the next action. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return [funcUnit] returns the funcUnit for chaining. */ visible: function( timeout, success, message ) { var self = this, sel = this.selector, ret; return this.size(function(size){ return this.is(":visible") === true; }, timeout, success, message) }, /** * @function FuncUnit.prototype.invisible .invisible() * @parent waits * @signature `invisible([timeout] [,success] [,message])` * Waits until the selector is invisible. * @codestart * //waits until #foo is invisible. *F("#foo").invisible() * @codeend * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that runs after the selector is invisible, but before the next action. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully * @return [funcUnit] returns the funcUnit selector for chaining. */ invisible: function( timeout, success, message ) { var self = this, sel = this.selector, ret; return this.size(function(size){ return this.is(":visible") === false; }, timeout, success, message) }, /** * @function FuncUnit.prototype.wait .wait() * @parent waits * @signature `wait([checker] [,timeout] [,success] [,message])` * * Waits until some condition is true before calling the next action. Or if no checker function is provided, waits a * timeout before calling the next queued method. This can be used as a flexible wait condition to check various things in the tested page: * @codestart *F('#testData').wait(function(){ * return F.win.$(this).data('idval') === 1000; * }, "Data value matched"); * @codeend * @param {Number|Function} [checker] a checking function. It runs repeatedly until the condition becomes true or the timeout period passes. * If a number is provided, a time in milliseconds to wait before running the next queued method. * @param {Number} [timeout] overrides FuncUnit.timeout. If provided, the wait will fail if not completed before this timeout. * @param {Function} [success] a callback that will run after this action completes. * @param {String} [message] if provided, an assertion will be passed when this wait condition completes successfully */ wait: function( checker, timeout, success, message ) { if(typeof checker === "number"){ timeout = checker; FuncUnit.wait(timeout, success) return this; } else { return this.size(checker, timeout, success, message) } }, /** * @function FuncUnit.prototype.then .then() * @parent waits * @signature `then(success)` * Calls the success function after all previous asynchronous actions have completed. Then * is called with the funcunit object. * @param {Function} success */ then : function(success){ var self = this; FuncUnit.wait(0, function(){ success.call(this, this); }); return this; } }) return FuncUnit; })(jQuery, __m3); // ## funcunit.js var __m1 = (function(Syn, FuncUnit) { window.FuncUnit = window.S = window.F = FuncUnit; return FuncUnit; })(Syn, __m3, __m6, __m9, __m10, __m11, __m12, __m13, __m14); }(window);