/*
* 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.
*
@codestart
Syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = {
'prevent':
{"keyup":[],"keydown":["char","keypress"],"keypress":["char"]},
'character':
{ ... }
}
@codeend
* Limitations
* Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+.
* With FF 1+, drag / move events are only partially supported. They will
* not trigger mouseover / mouseout events.
* Safari crashes when a mousedown is triggered on a select. Syn will not
* create this event.
* Contributing to Syn
* Have we missed something? We happily accept patches. The following are
* important objects and properties of Syn:
*
* Syn.create
- contains methods to setup, convert options, and create an event of a specific type.
* Syn.defaults
- default behavior by event type (except for keys).
* Syn.key.defaults
- default behavior by key.
* Syn.keycodes
- supported keys you can type.
*
* Roll Your Own Functional Test Framework
* Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit].
* But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or
* testing solutions can use it as well.
*
* @constructor
* 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);