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:
+ *
+ *
+ *
[Syn.prototype.click click] - a mousedown, focus, mouseup, and click.
+ *
[Syn.prototype.dblclick dblclick] - two click! events followed by a dblclick.
+ *
[Syn.prototype.key key] - types a single character (keydown, keypress, keyup).
+ *
[Syn.prototype.type type] - types multiple characters into an element.
+ *
[Syn.prototype.move move] - moves the mouse from one position to another (triggering mouseover / mouseouts).
+ *
[Syn.prototype.drag drag] - a mousedown, followed by mousemoves, and a mouseup.
+ *
+ * All actions run asynchronously.
+ * Click on the links above for more
+ * information on how to use the specific action.
+ *
Asynchronous Callbacks
+ * Actions don't complete immediately. This is almost
+ * entirely because focus()
+ * doesn't run immediately in IE.
+ * If you provide a callback function to Syn, it will
+ * be called after the action is completed.
+ * The following checks that "Hello World" was entered correctly:
+ @codestart
+ Syn.click({},'description')
+ .type("Hello World", function(){
+
+ ok("Hello World" == document.getElementById('description').value)
+ })
+ @codeend
+
Asynchronous Chaining
+
You might have noticed the [Syn.prototype.then then] method. It provides chaining
+ so you can do a sequence of events with a single (final) callback.
+
+ If an element isn't provided to then, it uses the previous Syn's element.
+
+ The following does a lot of stuff before checking the result:
+ @codestart
+ Syn.type('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.
+ *
+ * Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+.
+ * With FF 1+, drag / move events are only partially supported. They will
+ * not trigger mouseover / mouseout events.
+ * Safari crashes when a mousedown is triggered on a select. Syn will not
+ * create this event.
+ *
Contributing to Syn
+ * Have we missed something? We happily accept patches. The following are
+ * important objects and properties of Syn:
+ *
+ *
Syn.create - contains methods to setup, convert options, and create an event of a specific type.
+ *
Syn.defaults - default behavior by event type (except for keys).
+ *
Syn.key.defaults - default behavior by key.
+ *
Syn.keycodes - supported keys you can type.
+ *
+ *
Roll Your Own Functional Test Framework
+ *
Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit].
+ * But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or
+ * testing solutions can use it as well.
+ *
+ * @constructor
+ * 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:
+ *
+ *
mousedown
+ *
focus - if the element is focusable
+ *
mouseup
+ *
click
+ *
+ * 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"
+ }
+ ]
+}