diff --git a/ajax/libs/FuncUnit/2.0.4/funcunit.js b/ajax/libs/FuncUnit/2.0.4/funcunit.js new file mode 100644 index 000000000..3a43f5e85 --- /dev/null +++ b/ajax/libs/FuncUnit/2.0.4/funcunit.js @@ -0,0 +1,4603 @@ +/* + * 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); \ No newline at end of file diff --git a/ajax/libs/FuncUnit/2.0.4/funcunit.min.js b/ajax/libs/FuncUnit/2.0.4/funcunit.min.js new file mode 100644 index 000000000..9caabcc28 --- /dev/null +++ b/ajax/libs/FuncUnit/2.0.4/funcunit.min.js @@ -0,0 +1,12 @@ +/* + * 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){var __m2=function(){var e=window.Syn?window.Syn:{},t=function(e,t){var n;for(n in t)e[n]=t[n];return e},n={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/)&&!0},r=function(e,n,r){var i=r.ownerDocument.createEventObject();return t(i,n)},i={},s=1,o="_synthetic"+(new Date).getTime(),u,a,f=/keypress|keyup|keydown/,l=/load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/,c,h=function(e,t,n,r){return new h.init(e,t,n,r)};h.config=e,u=function(e,t,n){return e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent("on"+t,n)},a=function(e,t,n){return e.addEventListener?e.removeEventListener(t,n,!1):e.detachEvent("on"+t,n)},t(h,{init:function(e,t,n,r){var i=h.args(t,n,r),s=this;this.queue=[],this.element=i.element,typeof this[e]=="function"?this[e](i.options,i.element,function(e,t){i.callback&&i.callback.apply(s,arguments),s.done.apply(s,arguments)}):(this.result=h.trigger(e,i.options,i.element),i.callback&&i.callback.call(this,i.element,this.result))},jquery:function(e,t){return window.FuncUnit&&window.FuncUnit.jQuery?window.FuncUnit.jQuery:e?h.helpers.getWindow(e).jQuery||window.jQuery:window.jQuery},args:function(){var e={},t=0;for(;t0&&i.apply(this,[])},t.dispatchEvent(e),s<=0}try{window.event=e}catch(o){}return t.sourceIndex<=0||t.fireEvent&&t.fireEvent("on"+n,e)},create:{page:{event:function(e,t,n){var i=h.helpers.getWindow(n).document||document,s;if(i.createEvent)return s=i.createEvent("Events"),s.initEvent(e,!0,!0),s;try{s=r(e,t,n)}catch(o){}return s}},focus:{event:function(e,t,n){return h.onParents(n,function(e){if(h.isFocusable(e)){if(e.nodeName.toLowerCase()!=="html")e.focus(),c=e;else if(c){var t=h.helpers.getWindow(n).document;if(t!==window.document)return!1;t.activeElement?(t.activeElement.blur(),c=null):(c.blur(),c=null)}return!1}}),!0}}},support:{clickChanges:!1,clickSubmits:!1,keypressSubmits:!1,mouseupSubmits:!1,radioClickChanges:!1,focusChanges:!1,linkHrefJS:!1,keyCharacters:!1,backspaceWorks:!1,mouseDownUpClicks:!1,tabKeyTabs:!1,keypressOnAnchorClicks:!1,optionClickBubbles:!1,ready:0},trigger:function(e,t,n){t||(t={});var r=h.create,i=r[e]&&r[e].setup,s=f.test(e)?"key":l.test(e)?"page":"mouse",o=r[e]||{},u=r[s],a,c,p,d=n;return h.support.ready===2&&i&&i(e,t,n),p=t._autoPrevent,delete t._autoPrevent,o.event?c=o.event(e,t,n):(t=u.options?u.options(e,t,n):t,!h.support.changeBubbles&&/option/i.test(n.nodeName)&&(d=n.parentNode),a=u.event(e,t,d),c=h.dispatch(a,d,e,p)),c&&h.support.ready===2&&h.defaults[e]&&h.defaults[e].call(n,t,p),c},eventSupported:function(e){var t=document.createElement("div");e="on"+e;var n=e in t;return n||(t.setAttribute(e,"return;"),n=typeof t[e]=="function"),t=null,n}}),t(h.init.prototype,{then:function(e,t,n,r){h.autoDelay&&this.delay();var i=h.args(t,n,r),s=this;return this.queue.unshift(function(t,n){if(typeof this[e]!="function")return this.result=h.trigger(e,i.options,i.element),i.callback&&i.callback.call(this,i.element,this.result),this;this.element=i.element||t,this[e](i.options,this.element,function(e,t){i.callback&&i.callback.apply(s,arguments),s.done.apply(s,arguments)})}),this},delay:function(e,t){typeof e=="function"&&(t=e,e=null),e=e||600;var n=this;return this.queue.unshift(function(){setTimeout(function(){t&&t.apply(n,[]),n.done.apply(n,arguments)},e)}),this},done:function(e,t){t&&(this.element=t),this.queue.length&&this.queue.pop().call(this,this.element,e)},_click:function(e,t,n,r){h.helpers.addOffset(e,t),h.trigger("mousedown",e,t),setTimeout(function(){h.trigger("mouseup",e,t),!h.support.mouseDownUpClicks||r?(h.trigger("click",e,t),n(!0)):(h.create.click.setup("click",e,t),h.defaults.click.call(t),setTimeout(function(){n(!0)},1))},1)},_rightClick:function(e,n,r){h.helpers.addOffset(e,n);var i=t(t({},h.mouse.browser.right.mouseup),e);h.trigger("mousedown",i,n),setTimeout(function(){h.trigger("mouseup",i,n),h.mouse.browser.right.contextmenu&&h.trigger("contextmenu",t(t({},h.mouse.browser.right.contextmenu),e),n),r(!0)},1)},_dblclick:function(e,t,n){h.helpers.addOffset(e,t);var r=this;this._click(e,t,function(){setTimeout(function(){r._click(e,t,function(){h.trigger("dblclick",e,t),n(!0)},!0)},2)})}});var p=["click","dblclick","move","drag","key","type","rightClick"],d=function(e){h[e]=function(t,n,r){return h("_"+e,t,n,r)},h.init.prototype[e]=function(t,n,r){return this.then("_"+e,t,n,r)}},v=0;for(;v",document.documentElement.appendChild(t),i=t.firstChild,n=i.childNodes[0],r=i.childNodes[2],o=i.getElementsByTagName("select")[0],n.checked=!1,n.onchange=function(){Syn.support.clickChanges=!0},Syn.trigger("click",{},n),Syn.support.clickChecks=n.checked,n.checked=!1,Syn.trigger("change",{},n),Syn.support.changeChecks=n.checked,i.onsubmit=function(e){return e.preventDefault&&e.preventDefault(),Syn.support.clickSubmits=!0,!1},Syn.trigger("click",{},r),i.childNodes[1].onchange=function(){Syn.support.radioClickChanges=!0},Syn.trigger("click",{},i.childNodes[1]),Syn.bind(t,"click",function(){Syn.support.optionClickBubbles=!0,Syn.unbind(t,"click",arguments.callee)}),Syn.trigger("click",{},o.firstChild),Syn.support.changeBubbles=Syn.eventSupported("change");var u=0;t.onclick=function(){Syn.support.mouseDownUpClicks=!0},Syn.trigger("mousedown",{},t),Syn.trigger("mouseup",{},t),document.documentElement.removeChild(t),window.__synthTest=e,Syn.support.ready++}(),Syn}(__m2),__m4=function(e){return e.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"]}," ":{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"]}}},e.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}}}},e.key.browser=function(){if(e.key.browsers[window.navigator.userAgent])return e.key.browsers[window.navigator.userAgent];for(var t in e.browser)if(e.browser[t]&&e.key.browsers[t])return e.key.browsers[t];return e.key.browsers.gecko}(),e.mouse.browser=function(){if(e.mouse.browsers[window.navigator.userAgent])return e.mouse.browsers[window.navigator.userAgent];for(var t in e.browser)if(e.browser[t]&&e.mouse.browsers[t])return e.mouse.browsers[t];return e.mouse.browsers.gecko}(),e}(__m2,__m3),__m5=function(e){var t=e.helpers,n=function(e){if(e.selectionStart!==undefined)return document.activeElement&&document.activeElement!=e&&e.selectionStart==e.selectionEnd&&e.selectionStart==0?{start:e.value.length,end:e.value.length}:{start:e.selectionStart,end:e.selectionEnd};try{if(e.nodeName.toLowerCase()=="input"){var n=t.getWindow(e).document.selection.createRange(),r=e.createTextRange();r.setEndPoint("EndToStart",n);var i=r.text.length;return{start:i,end:i+n.text.length}}var n=t.getWindow(e).document.selection.createRange(),r=n.duplicate(),s=n.duplicate(),o=n.duplicate();s.collapse(),o.collapse(!1),s.moveStart("character",-1),o.moveStart("character",-1),r.moveToElementText(e),r.setEndPoint("EndToEnd",n);var i=r.text.length-n.text.length,u=r.text.length;return i!=0&&s.text==""&&(i+=2),u!=0&&o.text==""&&(u+=2),{start:i,end:u}}catch(a){return{start:e.value.length,end:e.value.length}}},r=function(n){var r=t.getWindow(n).document,i=[],s=r.getElementsByTagName("*"),o=s.length;for(var u=0;u-1)return e.key.browser[r];return e.key.browser.character},isSpecial:function(t){var n=e.key.kinds.special;for(var r=0;r-1&&e.key.defaults[r])return e.key.defaults[r];return e.key.defaults.character},defaults:{character:function(t,n,r,i,s){/num\d+/.test(r)&&(r=r.match(/\d+/)[0]);if(i||!e.support.keyCharacters&&e.typeable.test(this.nodeName)){var o=this.value,u=o.substr(0,s.start),a=o.substr(s.end),f=r;this.value=u+f+a;var l=f=="\n"&&e.support.textareaCarriage?2:f.length;e.selectText(this,u.length+l)}},c:function(t,n,r,i,s){e.key.ctrlKey?e.key.clipboard=e.getText(this):e.key.defaults.character.apply(this,arguments)},v:function(t,n,r,i,s){e.key.ctrlKey?e.key.defaults.character.call(this,t,n,e.key.clipboard,!0,s):e.key.defaults.character.apply(this,arguments)},a:function(t,n,r,i,s){e.key.ctrlKey?e.selectText(this,0,this.value.length):e.key.defaults.character.apply(this,arguments)},home:function(){e.onParents(this,function(e){if(e.scrollHeight!=e.clientHeight)return e.scrollTop=0,!1})},end:function(){e.onParents(this,function(e){if(e.scrollHeight!=e.clientHeight)return e.scrollTop=e.scrollHeight,!1})},"page-down":function(){e.onParents(this,function(e){if(e.scrollHeight!=e.clientHeight){var t=e.clientHeight;return e.scrollTop+=t,!1}})},"page-up":function(){e.onParents(this,function(e){if(e.scrollHeight!=e.clientHeight){var t=e.clientHeight;return e.scrollTop-=t,!1}})},"\b":function(t,n,r,i,s){if(!e.support.backspaceWorks&&e.typeable.test(this.nodeName)){var o=this.value,u=o.substr(0,s.start),a=o.substr(s.end);s.start==s.end&&s.start>0?(this.value=u.substring(0,u.length-1)+a,e.selectText(this,s.start-1)):(this.value=u+a,e.selectText(this,s.start))}},"delete":function(t,n,r,i,s){if(!e.support.backspaceWorks&&e.typeable.test(this.nodeName)){var o=this.value,u=o.substr(0,s.start),a=o.substr(s.end);s.start==s.end&&s.start<=this.value.length-1?this.value=u+a.substring(1):this.value=u+a,e.selectText(this,s.start)}},"\r":function(t,n,r,i,s){var o=this.nodeName.toLowerCase();o=="input"&&e.trigger("change",{},this);if(!e.support.keypressSubmits&&o=="input"){var u=e.closest(this,"form");u&&e.trigger("submit",{},u)}!e.support.keyCharacters&&o=="textarea"&&e.key.defaults.character.call(this,t,n,"\n",undefined,s),!e.support.keypressOnAnchorClicks&&o=="a"&&e.trigger("click",{},this)}," ":function(t,n){var i=r(this),s=e.tabIndex(this),o=null,u=1e9,a=!1,f=0,l,c,h,p;orders=[];for(;fthis.value.length?this.value.length:s.end+1):e.selectText(this,s.end+1>this.value.length?this.value.length:s.end+1))},up:function(){/select/i.test(this.nodeName)&&(this.selectedIndex=this.selectedIndex?this.selectedIndex-1:0)},down:function(){/select/i.test(this.nodeName)&&(e.changeOnBlur(this,"selectedIndex",this.selectedIndex),this.selectedIndex=this.selectedIndex+1)},shift:function(){return null},ctrl:function(){return null}}}),t.extend(e.create,{keydown:{setup:function(n,r,i){t.inArray(r,e.key.kinds.special)!=-1&&(e.key[r+"Key"]=i)}},keypress:{setup:function(t,n,r){e.support.keyCharacters&&!e.support.keysOnNotFocused&&r.focus()}},keyup:{setup:function(n,r,i){t.inArray(r,e.key.kinds.special)!=-1&&(e.key[r+"Key"]=null)}},key:{options:function(n,r,i){return r=typeof r!="object"?{character:r}:r,r=t.extend({},r),r.character&&(t.extend(r,e.key.options(r.character,n)),delete r.character),r=t.extend({ctrlKey:!!e.key.ctrlKey,altKey:!!e.key.altKey,shiftKey:!!e.key.shiftKey,metaKey:!!e.key.metaKey},r),r},event:function(e,n,r){var i=t.getWindow(r).document||document;if(i.createEvent){var s;try{s=i.createEvent("KeyEvents"),s.initKeyEvent(e,!0,!0,window,n.ctrlKey,n.altKey,n.shiftKey,n.metaKey,n.keyCode,n.charCode)}catch(o){s=t.createBasicStandardEvent(e,n,i)}return s.synthetic=!0,s}var s;try{s=t.createEventObject.apply(this,arguments),t.extend(s,n)}catch(o){}return s}}});var i={enter:"\r",backspace:"\b",tab:" ",space:" "};return t.extend(e.init.prototype,{_key:function(r,s,o){if(/-up$/.test(r)&&t.inArray(r.replace("-up",""),e.key.kinds.special)!=-1){e.trigger("keyup",r.replace("-up",""),s),o(!0,s);return}var u=t.getWindow(s).document.activeElement,a=e.typeable.test(s.nodeName)&&n(s),f=i[r]||r,l=e.trigger("keydown",f,s),c=e.key.getDefault,p=e.key.browser.prevent,d,v=e.key.options(f,"keypress");return l?v?(u!==t.getWindow(s).document.activeElement&&(s=t.getWindow(s).document.activeElement),l=e.trigger("keypress",v,s),l&&(d=c(f).call(s,v,t.getWindow(s),f,undefined,a))):d=c(f).call(s,v,t.getWindow(s),f,undefined,a):v&&t.inArray("keypress",p.keydown)==-1&&(u!==t.getWindow(s).document.activeElement&&(s=t.getWindow(s).document.activeElement),e.trigger("keypress",v,s)),d&&d.nodeName&&(s=d),d!==null?setTimeout(function(){e.trigger("keyup",e.key.options(f,"keyup"),s),o(l,s)},1):o(l,s),s},_type:function(e,t,n){var r=e.match(/(\[[^\]]+\])|([^\[])/g),i=this,s=function(e,o){var u=r.shift();if(!u){n(e,o);return}o=o||t,u.length>1&&(u=u.substr(1,u.length-2)),i._key(u,o,s)};s()}}),e.config.support?e.helpers.extend(e.support,e.config.support):!function(){if(!document.body){setTimeout(arguments.callee,1);return}var t=document.createElement("div"),n,r,i,s,o=!1,u,a,f;t.innerHTML="
",document.documentElement.appendChild(t),i=t.firstChild,n=i.childNodes[0],r=i.childNodes[2],u=i.getElementsByTagName("a")[0],a=i.getElementsByTagName("textarea")[0],f=i.childNodes[3],i.onsubmit=function(t){return t.preventDefault&&t.preventDefault(),e.support.keypressSubmits=!0,t.returnValue=!1,!1},f.focus(),e.trigger("keypress","\r",f),e.trigger("keypress","a",f),e.support.keyCharacters=f.value=="a",f.value="a",e.trigger("keypress","\b",f),e.support.backspaceWorks=f.value=="",f.onchange=function(){e.support.focusChanges=!0},f.focus(),e.trigger("keypress","a",f),i.childNodes[5].focus(),e.trigger("keypress","b",f),e.support.keysOnNotFocused=f.value=="ab",e.bind(u,"click",function(t){return t.preventDefault&&t.preventDefault(),e.support.keypressOnAnchorClicks=!0,t.returnValue=!1,!1}),e.trigger("keypress","\r",u),e.support.textareaCarriage=a.value.length==4,document.documentElement.removeChild(t),e.support.ready++}(),e}(__m2,__m4),__m6=function(e){(function(){if(!document.body){setTimeout(arguments.callee,1);return}var t=document.createElement("div");document.body.appendChild(t),e.helpers.extend(t.style,{width:"100px",height:"10000px",backgroundColor:"blue",position:"absolute",top:"10px",left:"0px",zIndex:19999}),document.body.scrollTop=11;if(!document.elementFromPoint)return;var n=document.elementFromPoint(3,1);n==t?e.support.elementFromClient=!0:e.support.elementFromPage=!0,document.body.removeChild(t),document.body.scrollTop=0})();var t=function(t,n){var r=t.clientX,i=t.clientY,s=e.helpers.getWindow(n),o;if(e.support.elementFromPage){var u=e.helpers.scrollOffset(s);r+=u.left,i+=u.top}return o=s.document.elementFromPoint?s.document.elementFromPoint(r,i):n,o===s.document.documentElement&&(t.clientY<0||t.clientX<0)?n:o},n=function(n,r,i){var s=t(r,i);return e.trigger(n,r,s||i),s},r=function(n,r,i){var s=t(n,r);if(i!=s&&s&&i){var o=e.helpers.extend({},n);o.relatedTarget=s,e.trigger("mouseout",o,i),o.relatedTarget=i,e.trigger("mouseover",o,s)}return e.trigger("mousemove",n,s||r),s},i=function(n,i,s,o,u){var a=new Date,f=i.clientX-n.clientX,l=i.clientY-n.clientY,c=e.helpers.getWindow(o),h=t(n,o),p=c.document.createElement("div"),d=0;move=function(){var t=new Date,v=e.helpers.scrollOffset(c),m=(d==0?0:t-a)/s,g={clientX:f*m+n.clientX,clientY:l*m+n.clientY};d++,m<1?(e.helpers.extend(p.style,{left:g.clientX+v.left+2+"px",top:g.clientY+v.top+2+"px"}),h=r(g,o,h),setTimeout(arguments.callee,15)):(h=r(i,o,h),c.document.body.removeChild(p),u())},e.helpers.extend(p.style,{height:"5px",width:"5px",backgroundColor:"red",position:"absolute",zIndex:19999,fontSize:"1px"}),c.document.body.appendChild(p),move()},s=function(e,t,r,s,o){n("mousedown",e,s),i(e,t,r,s,function(){n("mouseup",t,s),o()})},o=function(t){var n=e.jquery()(t),r=n.offset();return{pageX:r.left+n.outerWidth()/2,pageY:r.top+n.outerHeight()/2}},u=function(t,n,r){var i=/(\d+)[x ](\d+)/,s=/(\d+)X(\d+)/,u=/([+-]\d+)[xX ]([+-]\d+)/;if(typeof t=="string"&&u.test(t)&&r){var a=o(r),f=t.match(u);t={pageX:a.pageX+parseInt(f[1]),pageY:a.pageY+parseInt(f[2])}}if(typeof t=="string"&&i.test(t)){var f=t.match(i);t={pageX:parseInt(f[1]),pageY:parseInt(f[2])}}if(typeof t=="string"&&s.test(t)){var f=t.match(s);t={clientX:parseInt(f[1]),clientY:parseInt(f[2])}}typeof t=="string"&&(t=e.jquery()(t,n.document)[0]),t.nodeName&&(t=o(t));if(t.pageX){var l=e.helpers.scrollOffset(n);t={clientX:t.pageX-l.left,clientY:t.pageY-l.top}}return t},a=function(t,n,r){if(t.clientY<0){var i=e.helpers.scrollOffset(r),s=e.helpers.scrollDimensions(r),o=i.top+t.clientY-100,u=o-i.top;o>0||(o=0,u=-i.top),t.clientY=t.clientY-u,n.clientY=n.clientY-u,e.helpers.scrollOffset(r,{top:o,left:i.left})}};return e.helpers.extend(e.init.prototype,{_move:function(t,n,r){var s=e.helpers.getWindow(n),o=u(t.from||n,s,n),f=u(t.to||t,s,n);t.adjust!==!1&&a(o,f,s),i(o,f,t.duration||500,n,r)},_drag:function(t,n,r){var i=e.helpers.getWindow(n),o=u(t.from||n,i,n),f=u(t.to||t,i,n);t.adjust!==!1&&a(o,f,i),s(o,f,t.duration||500,n,r)}}),e}(__m2),__m1=function(e){return window.Syn=e,e}(__m2,__m3,__m4,__m5,__m6)}(window),!function(e){var t=function(t){var n=e.FuncUnit||{};return t.sub=function(){function e(t,n){return new e.fn.init(t,n)}t.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(i,s){return s&&s instanceof t&&!(s instanceof e)&&(s=e(s)),t.fn.init.call(this,i,s,n)},e.fn.init.prototype=e.fn;var n=e(document);return e},n.jQuery=t,n}(jQuery),n=function(e,t){var n=t.jQuery.sub(),r=n;n=function(e,t){var t,r,i=!1;t&&t.forceSync&&(r=t.forceSync),t&&typeof t.frame!="undefined"&&(t=t.frame),i=typeof r=="boolean"?r:i;if(typeof e=="function")return n.wait(0,e);this.selector=e;if(i===!0){var u=o(e,t);return u}s(e,t,this);var u=o(e,t);return u};var i=function(e){if(typeof e=="number"||typeof e=="string")var t=typeof e=="number"?"iframe:eq("+e+")":"iframe[name='"+e+"']",i=new r.fn.init(t,n.win.document.documentElement,!0),s=(i.length?i.get(0).contentWindow:n.win).document.documentElement;else s=n.win.document.documentElement;return s},s=function(e,t,s){n.add({method:function(s,o){return this.frame=t,n.win&&(t=i(t)),this.selector=e,this.bind=new r.fn.init(e,t,!0),s(),this},error:"selector failed: "+e,type:"query"})},o=function(e,t){var s=t;n.win&&(t=i(t));var o=new r.fn.init(e,t,!0);return o.frame=s,o};return t.jQuery.extend(n,t,r),n.prototype=r.prototype,n}(jQuery,t),r=function(t){if(e.jasmine){var n=!1;return t.unit={pauseTest:function(){n=!0,waitsFor(function(){return n===!1},6e4)},resumeTest:function(){n=!1},assertOK:function(e,t){expect(e).toBeTruthy()},equiv:function(e,t){return jasmine.getEnv().equals_(e,t)}},t}}(n),i=function(t){e.QUnit&&(t.unit={pauseTest:function(){stop()},resumeTest:function(){start()},assertOK:function(e,t){ok(e,t)},equiv:function(e,t){return QUnit.equiv(e,t)}})}(n),s=function(){}(r,i),o=function(t,n){if(n.frameMode){var r=document.createElement("iframe");r.id="funcunit_app",document.body.insertBefore(r,document.body.firstChild)}var i=[],s=[],o,u,a,f=!1,l=function(e){return e.replace(/\#.*$/,"")},c=function(e){var t=l(n.win.location.pathname),r=l(n.win.location.href),e=l(e);return t===e||r===e?!0:!1};t.extend(n,{open:function(e,t,r){typeof t!="function"&&(r=t,t=undefined),n.add({method:function(t,r){if(typeof e=="string"){var i=n.getAbsolutePath(e);n._open(i,r),n._onload(function(){t()},r)}else n.win=e,t()},success:t,error:"Page "+e+" not loaded in time!" +,timeout:r||3e4})},_open:function(r){n.win=a,p=!1,n.frame=t("#funcunit_app").length?t("#funcunit_app")[0]:null;if(h){if(n.frame)n.win=n.frame.contentWindow,n.win.location=r;else{var i=t(e).width();n.win=e.open(r,"funcunit","height=1000,toolbar=yes,status=yes,width="+i/2+",left="+i/2),n.win.___FUNCUNIT_OPENED&&(n.win.close(),n.win=e.open(r,"funcunit","height=1000,toolbar=yes,status=yes,left="+i/2));if(!n.win)throw"Could not open a popup window. Your popup blocker is probably on. Please turn it off and try again"}a=n.win}else f=!0,c(r)?(n.win.document.body.parentNode.removeChild(n.win.document.body),n.win.location.hash=r.split("#")[1]||"",n.win.location.reload(!0)):n.win.location=r,o=null;f=!0},confirm:function(e){i.push(!!e)},prompt:function(e){s.push(e)},_opened:function(){this._isOverridden("alert")||(n.win.alert=function(){}),this._isOverridden("confirm")||(n.win.confirm=function(){var e=i.shift();return e}),this._isOverridden("prompt")||(n.win.prompt=function(){return s.shift()})},_isOverridden:function(e){return!/(native code)|(source code not available)/.test(n.win[e])},_onload:function(e,t){v=function(){n.win.steal&&(p=!0),n.win.steal&&n.win.steal.isReady||!p?e():setTimeout(arguments.callee,200)};if(!h)return;h=!1,n.support.readystate?E():d()},getAbsolutePath:function(e){return/^\/\//.test(e)&&(e=e.substr(2)),e},win:e,support:{readystate:"readyState"in document},eval:function(e){return n.win.eval(e)},documentLoaded:function(){var e=n.win.document.readyState==="complete"&&n.win.location.href!="about:blank"&&n.win.document.body;return e},checkForNewDocument:function(){var e=!1;try{e=(n.win.document!==o&&!n.win.___FUNCUNIT_OPENED||u!=n.win.location.href)&&n.documentLoaded()}catch(t){}return e&&(f=!1,o=n.win.document,u=n.win.location.href,n.win.___FUNCUNIT_OPENED=!0,n._opened()),e}});if(navigator.userAgent.match(/Rhino/))return;var h=!0,p=!1,d,v,m=!0,g=function(){n.win.document.documentElement.tabIndex=0,setTimeout(function(){n.win.focus();var e=v;v=null,e&&e()},0),Syn.unbind(n.win,"load",g)},y=function(){n.stop=!0,b(),setTimeout(d,0)},b=function(){Syn.unbind(n.win,"unload",y),Syn.unbind(n.win,"load",g)};d=function(){m||b(),Syn.bind(n.win,"load",g),Syn.bind(n.win,"unload",y)};var w=!1,E=function(){var e;f&&n.checkForNewDocument()&&(e=v,v=null,e&&(n.win.focus(),n.win.document.documentElement.tabIndex=0,e())),setTimeout(arguments.callee,500)};return t(e).unload(function(){n.win&&n.win!==e.top&&n.win.close()}),n}(jQuery,n),u=function(t,n,r){e.Syn=r;var i=["click","dblclick","rightClick"],s=function(e){n.prototype[e]=function(t,i){this._addExists(),typeof t=="function"&&(i=t,t={});var s=this.selector;return n.add({method:function(n,i){t=t||{},r("_"+e,t,this.bind[0],n)},success:i,error:"Could not "+e+" '"+this.selector+"'",bind:this,type:"action"}),this}};for(var o=0;on,o,u=this;i.unshift(this.selector,this.frame,e);if(s){var a=i[n+3],f=i[n+4],o=i[n+5],l=i[n+6],c=a,h="waiting for "+e+" on "+this.selector,p=this.frame,d="Checking "+e+" on '"+this.selector+"'",v;return typeof a=="object"&&!(a instanceof RegExp)&&(f=a.timeout,o=a.success,l=a.message,a.errorMessage&&(h=a.errorMessage),typeof a.logMessage!="undefined"&&(d=a.logMessage),a=a.condition),typeof f=="function"&&(o=f,l=o,f=undefined),typeof f=="string"&&(l=f,f=undefined,o=undefined),typeof l!="string"&&(l=undefined),i.splice(n+3,i.length-n-3),typeof a!="function"&&(h+=" !== "+c,a=function(e){return t.unit.equiv(e,c)||c instanceof RegExp&&c.test(e)}),l&&(h=l),t.repeat({method:function(r){if(this.bind.prevObject&&this.bind.prevTraverser){var s=this.bind;this.bind=this.bind.prevObject[this.bind.prevTraverser](this.bind.prevTraverserSelector),this.bind.prevTraverser=s.prevTraverser,this.bind.prevTraverserSelector=s.prevTraverserSelector}else this.bind=F(this.selector,{frame:p,forceSync:!0});d&&r(d);var o=[];n>0&&o.push(i[3]),t._ignoreGetterError=!0,v=this.bind[e].apply(this.bind,o),t._ignoreGetterError=!1;var u=a.call(this.bind,v);return this.bind.length===0&&e!=="size"&&(u=!1),u&&(t.documentLoaded()?t.checkForNewDocument():u=!1),u},success:function(){l&&t.unit.assertOK(!0,l),o&&o.apply(this,arguments)},error:function(){var e=h;v&&(e+=", actual value: "+v),t.unit.assertOK(!1,e)},timeout:f,bind:this,type:"wait"}),this}!t._ignoreGetterError&&!t._incallback&&t._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.");var m=[];return n>0&&m.push(i[3]),r.apply(this,m)}};for(var n in t.funcs)t.makeFunc(n,t.funcs[n]);return t}(jQuery,n),f=function(e,t){var n=["closest","next","prev","siblings","last","first","find"],r=function(e){var n=t.prototype[e];t.prototype[e]=function(r){var i=arguments;return t.win&&this[0]&&this[0].parentNode&&this[0].parentNode.nodeType!==9&&t.add({method:function(t,s){var o=n.apply(this.bind,i);o.prevTraverser=e,o.prevTraverserSelector=r,t(o)},error:"Could not traverse: "+e+" "+r,bind:this}),n.apply(this,arguments)}};for(var i=0;i0?(s=e._queue.shift(),t=0,setTimeout(function(){o=setTimeout(function(){s.stop&&s.stop(),typeof s.error=="function"?s.error():e.unit.assertOK(!1,s.error),e._done()},(s.timeout||e.timeout)+u),n&&n.jquery&&(r=n),r&&(s.bind=r),s.selector=i,s.method(function(t){t&&t.jquery&&(s.bind=t),clearTimeout(o),e._incallback=!0,s.success&&s.success.apply(s.bind,arguments),e._incallback=!1,e._done(s.bind,s.selector)},function(t){clearTimeout(o),e.unit.assertOK(!1,t),e._done()})},u)):e.unit.resumeTest()},e}(n),c=function(e,t){return t.wait=function(e,n){return typeof e=="function"&&(n=e,e=undefined),e=e!=null?e:5e3,t.add({method:function(t,n){setTimeout(t,e)},success:n,error:"Couldn't wait!",timeout:e+1e3}),this},t.branch=function(e,n,r,i,s){t.repeat({method:function(t){t("Running a branch statement");if(e())return n(),!0;if(r())return i(),!0},error:"no branch condition was ever true",timeout:s,type:"branch"})},t.repeat=function(e){var n,r=!1,i=function(){clearTimeout(n),r=!0};t.add({method:function(t,i){e.bind=this.bind,e.selector=this.selector;var s=!1,o=function(e){s||(s=!0)};n=setTimeout(function(){var i=null;try{i=e.method(o)}catch(s){}i?t(e.bind):r||(n=setTimeout(arguments.callee,10))},10)},success:e.success,error:e.error,timeout:e.timeout,stop:i,bind:e.bind,type:e.type})},t.animationEnd=function(){F("body").wait(200).size(function(){return F.win.$(":animated").length===0})},t.animationsDone=t.animationEnd,e.extend(t.prototype,{exists:function(e,t,n){var r="Waiting for '"+this.selector+"' to exist";return e===!1&&(r=!1),this.size({condition:function(e){return e>0},timeout:e,success:t,message:n,errorMessage:"Exist failed: element with selector '"+this.selector+"' not found",logMessage:r})},missing:function(e,t,n){return this.size(0,e,t,n)},visible:function(e,t,n){var r=this,i=this.selector,s;return this.size(function(e){return this.is(":visible")===!0},e,t,n)},invisible:function(e,t,n){var r=this,i=this.selector,s;return this.size(function(e){return this.is(":visible")===!1},e,t,n)},wait:function(e,n,r,i){return typeof e=="number"?(n=e,t.wait(n,r),this):this.size(e,n,r,i)},then:function(e){var n=this;return t.wait(0,function(){e.call(this,this)}),this}}),t}(jQuery,n),h=function(t,n){return e.FuncUnit=e.S=e.F=n,n}(Syn,n,s,o,u,a,f,l,c)}(window); \ No newline at end of file diff --git a/ajax/libs/FuncUnit/package.json b/ajax/libs/FuncUnit/package.json new file mode 100644 index 000000000..93150c2c4 --- /dev/null +++ b/ajax/libs/FuncUnit/package.json @@ -0,0 +1,23 @@ +{ + "name": "FuncUnit", + "filename": "funcunit.min.js", + "version": "2.0.4", + "description": "A functional test suite based on jQuery.", + "homepage": "http://funcunit.com/", + "keywords": [ + "framework", + "functional testing" + ], + "maintainers": [ + { + "name": "Bitovi", + "web": "https://github.com/bitovi" + } + ], + "repositories": [ + { + "type": "git", + "url": "https://github.com/bitovi/funcunit.git" + } + ] +}