├── .gitignore ├── README.md ├── demos ├── application.css ├── application.js ├── bootstrap.min.css ├── female.png ├── jquery.animate-color.js ├── jquery.event.drag.js └── male.png ├── index.html ├── switchy.css ├── switchy.jquery.json ├── switchy.js └── test ├── SpecRunner.html ├── lib ├── jasmine-1.2.0 │ ├── MIT.LICENSE │ ├── jasmine-html.js │ ├── jasmine.css │ └── jasmine.js └── jasmine-jquery.js └── spec ├── SpecHelper.js └── SwitchySpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Welcome to Switchy 2 | I am a tiny jQuery plugin that firstly aims to transform a standard <select> in a beautiful switchy element. 3 | Please have a look to what I am about at [http://lou.github.com/switchy/](http://lou.github.com/switchy/ "That is what I am about") 4 | 5 | But wait! I can do a lot more if you play nice with me. 6 | 7 | 8 | ### Basic Example 9 | 10 | You put your standard <select> in your HTML with as many <option> as you want. 11 | ```html 12 | 16 | ``` 17 | 18 | In your Javascript you can invoke the magic switchy method 19 | ```javascript 20 | $('#switch-me').switchy(); 21 | ``` 22 | -------------------------------------------------------------------------------- /demos/application.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: Arial; 3 | color: #333; 4 | background: #f6f6f6; 5 | } 6 | 7 | .female{ 8 | text-align: right; 9 | } 10 | 11 | .container{ 12 | margin-top: 10%; 13 | } 14 | 15 | #console{ 16 | padding: 40px 0px; 17 | text-align: center; 18 | color: #aaa; 19 | text-shadow: 1px 2px #fff; 20 | display: none; 21 | } -------------------------------------------------------------------------------- /demos/application.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $('#switch-me').switchy(); 4 | 5 | $('.gender').on('click', function(){ 6 | $('#switch-me').val($(this).attr('gender')).change(); 7 | }); 8 | 9 | $('#switch-me').on('change', function(){ 10 | 11 | // Animate Switchy Bar background color 12 | var bgColor = '#ccb3dc'; 13 | 14 | if ($(this).val() == 'female'){ 15 | bgColor = '#ed7ab0'; 16 | } else if ($(this).val() == 'male'){ 17 | bgColor = '#7fcbea'; 18 | } 19 | 20 | $('.switchy-bar').animate({ 21 | backgroundColor: bgColor 22 | }); 23 | 24 | // Display action in console 25 | var log = 'Selected value is "'+$(this).val()+'"'; 26 | $('#console').html(log).hide().fadeIn(); 27 | }); 28 | }); -------------------------------------------------------------------------------- /demos/female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lou/switchy/4dd7a9fd771b0f06bbfe4610438a84ea11dbf0ac/demos/female.png -------------------------------------------------------------------------------- /demos/jquery.animate-color.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * @preserve Color animation 20120928 3 | * http://www.bitstorm.org/jquery/color-animation/ 4 | * Copyright 2011, 2012 Edwin Martin 5 | * Released under the MIT and GPL licenses. 6 | */ 7 | 8 | (function($) { 9 | /** 10 | * Check whether the browser supports RGBA color mode. 11 | * 12 | * Author Mehdi Kabab 13 | * @return {boolean} True if the browser support RGBA. False otherwise. 14 | */ 15 | function isRGBACapable() { 16 | var $script = $('script:first'), 17 | color = $script.css('color'), 18 | result = false; 19 | if (/^rgba/.test(color)) { 20 | result = true; 21 | } else { 22 | try { 23 | result = ( color != $script.css('color', 'rgba(0, 0, 0, 0.5)').css('color') ); 24 | $script.css('color', color); 25 | } catch (e) { 26 | } 27 | } 28 | 29 | return result; 30 | } 31 | 32 | $.extend(true, $, { 33 | support: { 34 | 'rgba': isRGBACapable() 35 | } 36 | }); 37 | 38 | var properties = ['color', 'backgroundColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor', 'outlineColor']; 39 | $.each(properties, function(i, property) { 40 | $.Tween.propHooks[ property ] = { 41 | get: function(tween) { 42 | return $(tween.elem).css(property); 43 | }, 44 | set: function(tween) { 45 | var style = tween.elem.style; 46 | var p_begin = parseColor($(tween.elem).css(property)); 47 | var p_end = parseColor(tween.end); 48 | tween.run = function(progress) { 49 | style[property] = calculateColor(p_begin, p_end, progress); 50 | } 51 | } 52 | } 53 | }); 54 | 55 | // borderColor doesn't fit in standard fx.step above. 56 | $.Tween.propHooks.borderColor = { 57 | set: function(tween) { 58 | var style = tween.elem.style; 59 | var p_begin = []; 60 | var borders = properties.slice(2, 6); // All four border properties 61 | $.each(borders, function(i, property) { 62 | p_begin[property] = parseColor($(tween.elem).css(property)); 63 | }); 64 | var p_end = parseColor(tween.end); 65 | tween.run = function(progress) { 66 | $.each(borders, function(i, property) { 67 | style[property] = calculateColor(p_begin[property], p_end, progress); 68 | }); 69 | } 70 | } 71 | } 72 | 73 | // Calculate an in-between color. Returns "#aabbcc"-like string. 74 | function calculateColor(begin, end, pos) { 75 | var color = 'rgb' + ($.support['rgba'] ? 'a' : '') + '(' 76 | + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ',' 77 | + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ',' 78 | + parseInt((begin[2] + pos * (end[2] - begin[2])), 10); 79 | if ($.support['rgba']) { 80 | color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); 81 | } 82 | color += ')'; 83 | return color; 84 | } 85 | 86 | // Parse an CSS-syntax color. Outputs an array [r, g, b] 87 | function parseColor(color) { 88 | var match, triplet; 89 | 90 | // Match #aabbcc 91 | if (match = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/.exec(color)) { 92 | triplet = [parseInt(match[1], 16), parseInt(match[2], 16), parseInt(match[3], 16), 1]; 93 | 94 | // Match #abc 95 | } else if (match = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/.exec(color)) { 96 | triplet = [parseInt(match[1], 16) * 17, parseInt(match[2], 16) * 17, parseInt(match[3], 16) * 17, 1]; 97 | 98 | // Match rgb(n, n, n) 99 | } else if (match = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)) { 100 | triplet = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), 1]; 101 | 102 | } else if (match = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9\.]*)\s*\)/.exec(color)) { 103 | triplet = [parseInt(match[1], 10), parseInt(match[2], 10), parseInt(match[3], 10),parseFloat(match[4])]; 104 | 105 | // No browser returns rgb(n%, n%, n%), so little reason to support this format. 106 | } 107 | return triplet; 108 | } 109 | })(jQuery); -------------------------------------------------------------------------------- /demos/jquery.event.drag.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jquery.event.drag - v 2.2 3 | * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com 4 | * Open Source MIT License - http://threedubmedia.com/code/license 5 | */ 6 | // Created: 2008-06-04 7 | // Updated: 2012-05-21 8 | // REQUIRES: jquery 1.7.x 9 | 10 | ;(function( $ ){ 11 | 12 | // add the jquery instance method 13 | $.fn.drag = function( str, arg, opts ){ 14 | // figure out the event type 15 | var type = typeof str == "string" ? str : "", 16 | // figure out the event handler... 17 | fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null; 18 | // fix the event type 19 | if ( type.indexOf("drag") !== 0 ) 20 | type = "drag"+ type; 21 | // were options passed 22 | opts = ( str == fn ? arg : opts ) || {}; 23 | // trigger or bind event handler 24 | return fn ? this.bind( type, opts, fn ) : this.trigger( type ); 25 | }; 26 | 27 | // local refs (increase compression) 28 | var $event = $.event, 29 | $special = $event.special, 30 | // configure the drag special event 31 | drag = $special.drag = { 32 | 33 | // these are the default settings 34 | defaults: { 35 | which: 1, // mouse button pressed to start drag sequence 36 | distance: 0, // distance dragged before dragstart 37 | not: ':input', // selector to suppress dragging on target elements 38 | handle: null, // selector to match handle target elements 39 | relative: false, // true to use "position", false to use "offset" 40 | drop: true, // false to suppress drop events, true or selector to allow 41 | click: false // false to suppress click events after dragend (no proxy) 42 | }, 43 | 44 | // the key name for stored drag data 45 | datakey: "dragdata", 46 | 47 | // prevent bubbling for better performance 48 | noBubble: true, 49 | 50 | // count bound related events 51 | add: function( obj ){ 52 | // read the interaction data 53 | var data = $.data( this, drag.datakey ), 54 | // read any passed options 55 | opts = obj.data || {}; 56 | // count another realted event 57 | data.related += 1; 58 | // extend data options bound with this event 59 | // don't iterate "opts" in case it is a node 60 | $.each( drag.defaults, function( key, def ){ 61 | if ( opts[ key ] !== undefined ) 62 | data[ key ] = opts[ key ]; 63 | }); 64 | }, 65 | 66 | // forget unbound related events 67 | remove: function(){ 68 | $.data( this, drag.datakey ).related -= 1; 69 | }, 70 | 71 | // configure interaction, capture settings 72 | setup: function(){ 73 | // check for related events 74 | if ( $.data( this, drag.datakey ) ) 75 | return; 76 | // initialize the drag data with copied defaults 77 | var data = $.extend({ related:0 }, drag.defaults ); 78 | // store the interaction data 79 | $.data( this, drag.datakey, data ); 80 | // bind the mousedown event, which starts drag interactions 81 | $event.add( this, "touchstart mousedown", drag.init, data ); 82 | // prevent image dragging in IE... 83 | if ( this.attachEvent ) 84 | this.attachEvent("ondragstart", drag.dontstart ); 85 | }, 86 | 87 | // destroy configured interaction 88 | teardown: function(){ 89 | var data = $.data( this, drag.datakey ) || {}; 90 | // check for related events 91 | if ( data.related ) 92 | return; 93 | // remove the stored data 94 | $.removeData( this, drag.datakey ); 95 | // remove the mousedown event 96 | $event.remove( this, "touchstart mousedown", drag.init ); 97 | // enable text selection 98 | drag.textselect( true ); 99 | // un-prevent image dragging in IE... 100 | if ( this.detachEvent ) 101 | this.detachEvent("ondragstart", drag.dontstart ); 102 | }, 103 | 104 | // initialize the interaction 105 | init: function( event ){ 106 | // sorry, only one touch at a time 107 | if ( drag.touched ) 108 | return; 109 | // the drag/drop interaction data 110 | var dd = event.data, results; 111 | // check the which directive 112 | if ( event.which != 0 && dd.which > 0 && event.which != dd.which ) 113 | return; 114 | // check for suppressed selector 115 | if ( $( event.target ).is( dd.not ) ) 116 | return; 117 | // check for handle selector 118 | if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length ) 119 | return; 120 | 121 | drag.touched = event.type == 'touchstart' ? this : null; 122 | dd.propagates = 1; 123 | dd.mousedown = this; 124 | dd.interactions = [ drag.interaction( this, dd ) ]; 125 | dd.target = event.target; 126 | dd.pageX = event.pageX; 127 | dd.pageY = event.pageY; 128 | dd.dragging = null; 129 | // handle draginit event... 130 | results = drag.hijack( event, "draginit", dd ); 131 | // early cancel 132 | if ( !dd.propagates ) 133 | return; 134 | // flatten the result set 135 | results = drag.flatten( results ); 136 | // insert new interaction elements 137 | if ( results && results.length ){ 138 | dd.interactions = []; 139 | $.each( results, function(){ 140 | dd.interactions.push( drag.interaction( this, dd ) ); 141 | }); 142 | } 143 | // remember how many interactions are propagating 144 | dd.propagates = dd.interactions.length; 145 | // locate and init the drop targets 146 | if ( dd.drop !== false && $special.drop ) 147 | $special.drop.handler( event, dd ); 148 | // disable text selection 149 | drag.textselect( false ); 150 | // bind additional events... 151 | if ( drag.touched ) 152 | $event.add( drag.touched, "touchmove touchend", drag.handler, dd ); 153 | else 154 | $event.add( document, "mousemove mouseup", drag.handler, dd ); 155 | // helps prevent text selection or scrolling 156 | if ( !drag.touched || dd.live ) 157 | return false; 158 | }, 159 | 160 | // returns an interaction object 161 | interaction: function( elem, dd ){ 162 | var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 }; 163 | return { 164 | drag: elem, 165 | callback: new drag.callback(), 166 | droppable: [], 167 | offset: offset 168 | }; 169 | }, 170 | 171 | // handle drag-releatd DOM events 172 | handler: function( event ){ 173 | // read the data before hijacking anything 174 | var dd = event.data; 175 | // handle various events 176 | switch ( event.type ){ 177 | // mousemove, check distance, start dragging 178 | case !dd.dragging && 'touchmove': 179 | event.preventDefault(); 180 | case !dd.dragging && 'mousemove': 181 | // drag tolerance, x² + y² = distance² 182 | if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) ) 183 | break; // distance tolerance not reached 184 | event.target = dd.target; // force target from "mousedown" event (fix distance issue) 185 | drag.hijack( event, "dragstart", dd ); // trigger "dragstart" 186 | if ( dd.propagates ) // "dragstart" not rejected 187 | dd.dragging = true; // activate interaction 188 | // mousemove, dragging 189 | case 'touchmove': 190 | event.preventDefault(); 191 | case 'mousemove': 192 | if ( dd.dragging ){ 193 | // trigger "drag" 194 | drag.hijack( event, "drag", dd ); 195 | if ( dd.propagates ){ 196 | // manage drop events 197 | if ( dd.drop !== false && $special.drop ) 198 | $special.drop.handler( event, dd ); // "dropstart", "dropend" 199 | break; // "drag" not rejected, stop 200 | } 201 | event.type = "mouseup"; // helps "drop" handler behave 202 | } 203 | // mouseup, stop dragging 204 | case 'touchend': 205 | case 'mouseup': 206 | default: 207 | if ( drag.touched ) 208 | $event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events 209 | else 210 | $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events 211 | if ( dd.dragging ){ 212 | if ( dd.drop !== false && $special.drop ) 213 | $special.drop.handler( event, dd ); // "drop" 214 | drag.hijack( event, "dragend", dd ); // trigger "dragend" 215 | } 216 | drag.textselect( true ); // enable text selection 217 | // if suppressing click events... 218 | if ( dd.click === false && dd.dragging ) 219 | $.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 ); 220 | dd.dragging = drag.touched = false; // deactivate element 221 | break; 222 | } 223 | }, 224 | 225 | // re-use event object for custom events 226 | hijack: function( event, type, dd, x, elem ){ 227 | // not configured 228 | if ( !dd ) 229 | return; 230 | // remember the original event and type 231 | var orig = { event:event.originalEvent, type:event.type }, 232 | // is the event drag related or drog related? 233 | mode = type.indexOf("drop") ? "drag" : "drop", 234 | // iteration vars 235 | result, i = x || 0, ia, $elems, callback, 236 | len = !isNaN( x ) ? x : dd.interactions.length; 237 | // modify the event type 238 | event.type = type; 239 | // remove the original event 240 | event.originalEvent = null; 241 | // initialize the results 242 | dd.results = []; 243 | // handle each interacted element 244 | do if ( ia = dd.interactions[ i ] ){ 245 | // validate the interaction 246 | if ( type !== "dragend" && ia.cancelled ) 247 | continue; 248 | // set the dragdrop properties on the event object 249 | callback = drag.properties( event, dd, ia ); 250 | // prepare for more results 251 | ia.results = []; 252 | // handle each element 253 | $( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){ 254 | // identify drag or drop targets individually 255 | callback.target = subject; 256 | // force propagtion of the custom event 257 | event.isPropagationStopped = function(){ return false; }; 258 | // handle the event 259 | result = subject ? $event.dispatch.call( subject, event, callback ) : null; 260 | // stop the drag interaction for this element 261 | if ( result === false ){ 262 | if ( mode == "drag" ){ 263 | ia.cancelled = true; 264 | dd.propagates -= 1; 265 | } 266 | if ( type == "drop" ){ 267 | ia[ mode ][p] = null; 268 | } 269 | } 270 | // assign any dropinit elements 271 | else if ( type == "dropinit" ) 272 | ia.droppable.push( drag.element( result ) || subject ); 273 | // accept a returned proxy element 274 | if ( type == "dragstart" ) 275 | ia.proxy = $( drag.element( result ) || ia.drag )[0]; 276 | // remember this result 277 | ia.results.push( result ); 278 | // forget the event result, for recycling 279 | delete event.result; 280 | // break on cancelled handler 281 | if ( type !== "dropinit" ) 282 | return result; 283 | }); 284 | // flatten the results 285 | dd.results[ i ] = drag.flatten( ia.results ); 286 | // accept a set of valid drop targets 287 | if ( type == "dropinit" ) 288 | ia.droppable = drag.flatten( ia.droppable ); 289 | // locate drop targets 290 | if ( type == "dragstart" && !ia.cancelled ) 291 | callback.update(); 292 | } 293 | while ( ++i < len ) 294 | // restore the original event & type 295 | event.type = orig.type; 296 | event.originalEvent = orig.event; 297 | // return all handler results 298 | return drag.flatten( dd.results ); 299 | }, 300 | 301 | // extend the callback object with drag/drop properties... 302 | properties: function( event, dd, ia ){ 303 | var obj = ia.callback; 304 | // elements 305 | obj.drag = ia.drag; 306 | obj.proxy = ia.proxy || ia.drag; 307 | // starting mouse position 308 | obj.startX = dd.pageX; 309 | obj.startY = dd.pageY; 310 | // current distance dragged 311 | obj.deltaX = event.pageX - dd.pageX; 312 | obj.deltaY = event.pageY - dd.pageY; 313 | // original element position 314 | obj.originalX = ia.offset.left; 315 | obj.originalY = ia.offset.top; 316 | // adjusted element position 317 | obj.offsetX = obj.originalX + obj.deltaX; 318 | obj.offsetY = obj.originalY + obj.deltaY; 319 | // assign the drop targets information 320 | obj.drop = drag.flatten( ( ia.drop || [] ).slice() ); 321 | obj.available = drag.flatten( ( ia.droppable || [] ).slice() ); 322 | return obj; 323 | }, 324 | 325 | // determine is the argument is an element or jquery instance 326 | element: function( arg ){ 327 | if ( arg && ( arg.jquery || arg.nodeType == 1 ) ) 328 | return arg; 329 | }, 330 | 331 | // flatten nested jquery objects and arrays into a single dimension array 332 | flatten: function( arr ){ 333 | return $.map( arr, function( member ){ 334 | return member && member.jquery ? $.makeArray( member ) : 335 | member && member.length ? drag.flatten( member ) : member; 336 | }); 337 | }, 338 | 339 | // toggles text selection attributes ON (true) or OFF (false) 340 | textselect: function( bool ){ 341 | $( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart ) 342 | .css("MozUserSelect", bool ? "" : "none" ); 343 | // .attr("unselectable", bool ? "off" : "on" ) 344 | document.unselectable = bool ? "off" : "on"; 345 | }, 346 | 347 | // suppress "selectstart" and "ondragstart" events 348 | dontstart: function(){ 349 | return false; 350 | }, 351 | 352 | // a callback instance contructor 353 | callback: function(){} 354 | 355 | }; 356 | 357 | // callback methods 358 | drag.callback.prototype = { 359 | update: function(){ 360 | if ( $special.drop && this.available.length ) 361 | $.each( this.available, function( i ){ 362 | $special.drop.locate( this, i ); 363 | }); 364 | } 365 | }; 366 | 367 | // patch $.event.$dispatch to allow suppressing clicks 368 | var $dispatch = $event.dispatch; 369 | $event.dispatch = function( event ){ 370 | if ( $.data( this, "suppress."+ event.type ) - new Date().getTime() > 0 ){ 371 | $.removeData( this, "suppress."+ event.type ); 372 | return; 373 | } 374 | return $dispatch.apply( this, arguments ); 375 | }; 376 | 377 | // event fix hooks for touch events... 378 | var touchHooks = 379 | $event.fixHooks.touchstart = 380 | $event.fixHooks.touchmove = 381 | $event.fixHooks.touchend = 382 | $event.fixHooks.touchcancel = { 383 | props: "clientX clientY pageX pageY screenX screenY".split( " " ), 384 | filter: function( event, orig ) { 385 | if ( orig ){ 386 | var touched = ( orig.touches && orig.touches[0] ) 387 | || ( orig.changedTouches && orig.changedTouches[0] ) 388 | || null; 389 | // iOS webkit: touchstart, touchmove, touchend 390 | if ( touched ) 391 | $.each( touchHooks.props, function( i, prop ){ 392 | event[ prop ] = touched[ prop ]; 393 | }); 394 | } 395 | return event; 396 | } 397 | }; 398 | 399 | // share the same special event configuration with related events... 400 | $special.draginit = $special.dragstart = $special.dragend = drag; 401 | 402 | })( jQuery ); -------------------------------------------------------------------------------- /demos/male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lou/switchy/4dd7a9fd771b0f06bbfe4610438a84ea11dbf0ac/demos/male.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |
14 |
15 | 20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 51 | 52 | 53 | 54 | 55 | 56 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /switchy.css: -------------------------------------------------------------------------------- 1 | .switchy-container{ 2 | position: relative; 3 | margin: 0 auto; 4 | width: 70px; 5 | height: 20px; 6 | background: #fff; 7 | border-radius: 16px; 8 | padding: 4px 15px; 9 | box-shadow: 0px 1px 1px #aaa; 10 | } 11 | 12 | .switchy-bar{ 13 | position: relative; 14 | background: #ccb3dc; 15 | border-radius: 16px; 16 | box-shadow: 0px 2px 6px #756381 inset; 17 | width: 100%; 18 | height: 100%; 19 | cursor: pointer; 20 | } 21 | 22 | .switchy-slider{ 23 | position: absolute; 24 | width: 16px; 25 | height: 16px; 26 | background: #fff; 27 | left: 0px; 28 | cursor: pointer; 29 | border-radius: 9px; 30 | box-shadow: 0px 2px 4px 0px #777; 31 | margin: 2px; 32 | } -------------------------------------------------------------------------------- /switchy.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "switchy", 3 | "title": "Switchy", 4 | "description": "This is a tiny jQuery plugin which can be used as both a switch or a slider.", 5 | "keywords": [ 6 | "switch", 7 | "slider", 8 | "select", 9 | "form" 10 | ], 11 | "version": "0.1.1", 12 | "author": { 13 | "name": "lou", 14 | "email" : "louiscuny@gmail.com", 15 | "url": "https://github.com/lou" 16 | }, 17 | "maintainers": [ 18 | { 19 | "name": "Louis CUNY", 20 | "email": "louiscuny@gmail.com", 21 | "url": "https://github.com/lou" 22 | } 23 | ], 24 | "licenses": [ 25 | { 26 | "type": "wtfpl", 27 | "url": "http://sam.zoy.org/wtfpl/COPYING" 28 | } 29 | ], 30 | "bugs": "https://github.com/lou/switchy/issues", 31 | "homepage": "http://lou.github.com/switchy/", 32 | "download": "https://github.com/lou/switchy/archive/master.zip", 33 | "dependencies": { 34 | "jquery": ">= 1.8" 35 | } 36 | } -------------------------------------------------------------------------------- /switchy.js: -------------------------------------------------------------------------------- 1 | !function ($) { 2 | 3 | "use strict"; // jshint ;_; 4 | 5 | 6 | /* SWITCHY CLASS DEFINITION 7 | * ====================== */ 8 | 9 | var Switchy = function (element, options) { 10 | this.options = options; 11 | this.$element = $(element); 12 | this.$container = $("
"); 13 | this.$bar = $("
"); 14 | this.$slider = $("
"); 15 | this.$options = $(element).children('option'); 16 | this.numberOfOptions = this.$options.length; 17 | this.initialOptionIndex = this.$options.filter('[value="'+$(element).val()+'"]').index(); 18 | this.init(); 19 | } 20 | 21 | Switchy.prototype = { 22 | constructor: Switchy, 23 | 24 | lastSliderPosition: null, 25 | 26 | init: function(){ 27 | var that = this; 28 | // hide original select 29 | this.$element.css({ position: 'absolute', left: '-9999px' }) 30 | // Prepare the slider for the DOM 31 | this.$container.append(this.$bar.append(this.$slider)); 32 | // Append the slider to the DOM 33 | this.$element.after(this.$container); 34 | 35 | this.lastSliderPosition = this.initialOptionIndex; 36 | var barGrid = this.$bar.innerWidth() / (this.numberOfOptions - 1); 37 | 38 | // Position slider to initial value 39 | this.$slider.css({ 40 | left: that.sliderPosition(barGrid, this.initialOptionIndex) 41 | }); 42 | 43 | // When original select is updated 44 | this.$element.on('change', function(e){ 45 | var nextOptionIndex = that.$options.filter('[value="'+that.$element.val()+'"]').index(); 46 | 47 | if (that.lastSliderPosition != nextOptionIndex){ 48 | that.moveSliderTo(barGrid, nextOptionIndex, false); 49 | } 50 | }); 51 | 52 | if (this.$slider.drag != undefined && this.options.draggable == true){ 53 | this.$slider. 54 | drag('end', function(ev, dd){ 55 | var currentSliderPosition = that.$slider.position().left + (that.$slider.outerWidth(true) / 2), 56 | currentOptionIndex = Math.round(currentSliderPosition / barGrid); 57 | 58 | that.moveSliderTo(barGrid, currentOptionIndex, true); 59 | }). 60 | drag(function(ev, dd){ 61 | var limit = { 62 | left: 0, 63 | right: that.$bar.innerWidth() - that.$slider.outerWidth(true) 64 | } 65 | $(this).css({ 66 | left: Math.min(limit.right, Math.max(limit.left, dd.offsetX)) 67 | }); 68 | }, { relative: true }); 69 | } 70 | 71 | this.$bar.on('click', function(e){ 72 | var currentSliderPosition = that.$slider.position().left, 73 | currentOptionIndex = Math.ceil(currentSliderPosition / barGrid), 74 | clickPosition = e.pageX - that.$bar.offset().left, 75 | nextOptionIndex = Math.round(clickPosition / barGrid); 76 | 77 | if (currentOptionIndex != nextOptionIndex){ 78 | // move slider position 79 | that.moveSliderTo(barGrid, nextOptionIndex, true); 80 | } 81 | }); 82 | }, 83 | 84 | sliderPosition: function(barGrid, optionIndex){ 85 | var add = null; 86 | 87 | if (optionIndex == 0){ 88 | add = 0; 89 | } else if (optionIndex == this.numberOfOptions - 1){ 90 | add = -(this.$slider.outerWidth(true)); 91 | } else { 92 | add = -(this.$slider.outerWidth(true) / 2); 93 | } 94 | return (barGrid * optionIndex) + add; 95 | }, 96 | 97 | moveSliderTo: function(barGrid, nextOptionIndex, triggerChange){ 98 | var leftPosition = this.sliderPosition(barGrid, nextOptionIndex) 99 | // move slider position 100 | if (leftPosition != null){ 101 | this.$slider.animate({ 102 | left: leftPosition 103 | }, "fast"); 104 | } 105 | // update original select value 106 | this.$options.removeAttr('selected'); 107 | this.$options.eq(nextOptionIndex).prop('selected', 'selected'); 108 | if (triggerChange == true) 109 | this.$element.trigger('change'); 110 | this.lastSliderPosition = nextOptionIndex; 111 | } 112 | } 113 | 114 | /* SWITCHY PLUGIN DEFINITION 115 | * ======================= */ 116 | 117 | $.fn.switchy = function (option) { 118 | return this.each(function () { 119 | var $this = $(this), 120 | options = $.extend({}, $.fn.switchy.defaults, typeof option == 'object' && option); 121 | new Switchy(this, options); 122 | }) 123 | } 124 | 125 | $.fn.switchy.defaults = { 126 | draggable: true 127 | } 128 | 129 | $.fn.switchy.Constructor = Switchy 130 | }(window.jQuery); -------------------------------------------------------------------------------- /test/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/lib/jasmine-1.2.0/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/lib/jasmine-1.2.0/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | 82 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 83 | reporterView.addSpecs(specs, self.specFilter); 84 | }; 85 | 86 | self.reportRunnerResults = function(runner) { 87 | reporterView && reporterView.complete(); 88 | }; 89 | 90 | self.reportSuiteResults = function(suite) { 91 | reporterView.suiteComplete(suite); 92 | }; 93 | 94 | self.reportSpecStarting = function(spec) { 95 | if (self.logRunningSpecs) { 96 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 97 | } 98 | }; 99 | 100 | self.reportSpecResults = function(spec) { 101 | reporterView.specComplete(spec); 102 | }; 103 | 104 | self.log = function() { 105 | var console = jasmine.getGlobal().console; 106 | if (console && console.log) { 107 | if (console.log.apply) { 108 | console.log.apply(console, arguments); 109 | } else { 110 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 111 | } 112 | } 113 | }; 114 | 115 | self.specFilter = function(spec) { 116 | if (!focusedSpecName()) { 117 | return true; 118 | } 119 | 120 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 121 | }; 122 | 123 | return self; 124 | 125 | function focusedSpecName() { 126 | var specName; 127 | 128 | (function memoizeFocusedSpec() { 129 | if (specName) { 130 | return; 131 | } 132 | 133 | var paramMap = []; 134 | var params = doc.location.search.substring(1).split('&'); 135 | 136 | for (var i = 0; i < params.length; i++) { 137 | var p = params[i].split('='); 138 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 139 | } 140 | 141 | specName = paramMap.spec; 142 | })(); 143 | 144 | return specName; 145 | } 146 | 147 | function createReporterDom(version) { 148 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 149 | dom.banner = self.createDom('div', { className: 'banner' }, 150 | self.createDom('span', { className: 'title' }, "Jasmine "), 151 | self.createDom('span', { className: 'version' }, version)), 152 | 153 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 154 | dom.alert = self.createDom('div', {className: 'alert'}), 155 | dom.results = self.createDom('div', {className: 'results'}, 156 | dom.summary = self.createDom('div', { className: 'summary' }), 157 | dom.details = self.createDom('div', { id: 'details' })) 158 | ); 159 | } 160 | }; 161 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporter.ReporterView = function(dom) { 162 | this.startedAt = new Date(); 163 | this.runningSpecCount = 0; 164 | this.completeSpecCount = 0; 165 | this.passedCount = 0; 166 | this.failedCount = 0; 167 | this.skippedCount = 0; 168 | 169 | this.createResultsMenu = function() { 170 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 171 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 172 | ' | ', 173 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 174 | 175 | this.summaryMenuItem.onclick = function() { 176 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 177 | }; 178 | 179 | this.detailsMenuItem.onclick = function() { 180 | showDetails(); 181 | }; 182 | }; 183 | 184 | this.addSpecs = function(specs, specFilter) { 185 | this.totalSpecCount = specs.length; 186 | 187 | this.views = { 188 | specs: {}, 189 | suites: {} 190 | }; 191 | 192 | for (var i = 0; i < specs.length; i++) { 193 | var spec = specs[i]; 194 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 195 | if (specFilter(spec)) { 196 | this.runningSpecCount++; 197 | } 198 | } 199 | }; 200 | 201 | this.specComplete = function(spec) { 202 | this.completeSpecCount++; 203 | 204 | if (isUndefined(this.views.specs[spec.id])) { 205 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 206 | } 207 | 208 | var specView = this.views.specs[spec.id]; 209 | 210 | switch (specView.status()) { 211 | case 'passed': 212 | this.passedCount++; 213 | break; 214 | 215 | case 'failed': 216 | this.failedCount++; 217 | break; 218 | 219 | case 'skipped': 220 | this.skippedCount++; 221 | break; 222 | } 223 | 224 | specView.refresh(); 225 | this.refresh(); 226 | }; 227 | 228 | this.suiteComplete = function(suite) { 229 | var suiteView = this.views.suites[suite.id]; 230 | if (isUndefined(suiteView)) { 231 | return; 232 | } 233 | suiteView.refresh(); 234 | }; 235 | 236 | this.refresh = function() { 237 | 238 | if (isUndefined(this.resultsMenu)) { 239 | this.createResultsMenu(); 240 | } 241 | 242 | // currently running UI 243 | if (isUndefined(this.runningAlert)) { 244 | this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); 245 | dom.alert.appendChild(this.runningAlert); 246 | } 247 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 248 | 249 | // skipped specs UI 250 | if (isUndefined(this.skippedAlert)) { 251 | this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); 252 | } 253 | 254 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 255 | 256 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 257 | dom.alert.appendChild(this.skippedAlert); 258 | } 259 | 260 | // passing specs UI 261 | if (isUndefined(this.passedAlert)) { 262 | this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); 263 | } 264 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 265 | 266 | // failing specs UI 267 | if (isUndefined(this.failedAlert)) { 268 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 269 | } 270 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 271 | 272 | if (this.failedCount === 1 && isDefined(dom.alert)) { 273 | dom.alert.appendChild(this.failedAlert); 274 | dom.alert.appendChild(this.resultsMenu); 275 | } 276 | 277 | // summary info 278 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 279 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 280 | }; 281 | 282 | this.complete = function() { 283 | dom.alert.removeChild(this.runningAlert); 284 | 285 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 286 | 287 | if (this.failedCount === 0) { 288 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 289 | } else { 290 | showDetails(); 291 | } 292 | 293 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 294 | }; 295 | 296 | return this; 297 | 298 | function showDetails() { 299 | if (dom.reporter.className.search(/showDetails/) === -1) { 300 | dom.reporter.className += " showDetails"; 301 | } 302 | } 303 | 304 | function isUndefined(obj) { 305 | return typeof obj === 'undefined'; 306 | } 307 | 308 | function isDefined(obj) { 309 | return !isUndefined(obj); 310 | } 311 | 312 | function specPluralizedFor(count) { 313 | var str = count + " spec"; 314 | if (count > 1) { 315 | str += "s" 316 | } 317 | return str; 318 | } 319 | 320 | }; 321 | 322 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 323 | 324 | 325 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 326 | this.spec = spec; 327 | this.dom = dom; 328 | this.views = views; 329 | 330 | this.symbol = this.createDom('li', { className: 'pending' }); 331 | this.dom.symbolSummary.appendChild(this.symbol); 332 | 333 | this.summary = this.createDom('div', { className: 'specSummary' }, 334 | this.createDom('a', { 335 | className: 'description', 336 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 337 | title: this.spec.getFullName() 338 | }, this.spec.description) 339 | ); 340 | 341 | this.detail = this.createDom('div', { className: 'specDetail' }, 342 | this.createDom('a', { 343 | className: 'description', 344 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 345 | title: this.spec.getFullName() 346 | }, this.spec.getFullName()) 347 | ); 348 | }; 349 | 350 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 351 | return this.getSpecStatus(this.spec); 352 | }; 353 | 354 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 355 | this.symbol.className = this.status(); 356 | 357 | switch (this.status()) { 358 | case 'skipped': 359 | break; 360 | 361 | case 'passed': 362 | this.appendSummaryToSuiteDiv(); 363 | break; 364 | 365 | case 'failed': 366 | this.appendSummaryToSuiteDiv(); 367 | this.appendFailureDetail(); 368 | break; 369 | } 370 | }; 371 | 372 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 373 | this.summary.className += ' ' + this.status(); 374 | this.appendToSummary(this.spec, this.summary); 375 | }; 376 | 377 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 378 | this.detail.className += ' ' + this.status(); 379 | 380 | var resultItems = this.spec.results().getItems(); 381 | var messagesDiv = this.createDom('div', { className: 'messages' }); 382 | 383 | for (var i = 0; i < resultItems.length; i++) { 384 | var result = resultItems[i]; 385 | 386 | if (result.type == 'log') { 387 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 388 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 389 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 390 | 391 | if (result.trace.stack) { 392 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 393 | } 394 | } 395 | } 396 | 397 | if (messagesDiv.childNodes.length > 0) { 398 | this.detail.appendChild(messagesDiv); 399 | this.dom.details.appendChild(this.detail); 400 | } 401 | }; 402 | 403 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 404 | this.suite = suite; 405 | this.dom = dom; 406 | this.views = views; 407 | 408 | this.element = this.createDom('div', { className: 'suite' }, 409 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) 410 | ); 411 | 412 | this.appendToSummary(this.suite, this.element); 413 | }; 414 | 415 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 416 | return this.getSpecStatus(this.suite); 417 | }; 418 | 419 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 420 | this.element.className += " " + this.status(); 421 | }; 422 | 423 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 424 | 425 | /* @deprecated Use jasmine.HtmlReporter instead 426 | */ 427 | jasmine.TrivialReporter = function(doc) { 428 | this.document = doc || document; 429 | this.suiteDivs = {}; 430 | this.logRunningSpecs = false; 431 | }; 432 | 433 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 434 | var el = document.createElement(type); 435 | 436 | for (var i = 2; i < arguments.length; i++) { 437 | var child = arguments[i]; 438 | 439 | if (typeof child === 'string') { 440 | el.appendChild(document.createTextNode(child)); 441 | } else { 442 | if (child) { el.appendChild(child); } 443 | } 444 | } 445 | 446 | for (var attr in attrs) { 447 | if (attr == "className") { 448 | el[attr] = attrs[attr]; 449 | } else { 450 | el.setAttribute(attr, attrs[attr]); 451 | } 452 | } 453 | 454 | return el; 455 | }; 456 | 457 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 458 | var showPassed, showSkipped; 459 | 460 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 461 | this.createDom('div', { className: 'banner' }, 462 | this.createDom('div', { className: 'logo' }, 463 | this.createDom('span', { className: 'title' }, "Jasmine"), 464 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 465 | this.createDom('div', { className: 'options' }, 466 | "Show ", 467 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 468 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 469 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 470 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 471 | ) 472 | ), 473 | 474 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 475 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 476 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 477 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 478 | ); 479 | 480 | this.document.body.appendChild(this.outerDiv); 481 | 482 | var suites = runner.suites(); 483 | for (var i = 0; i < suites.length; i++) { 484 | var suite = suites[i]; 485 | var suiteDiv = this.createDom('div', { className: 'suite' }, 486 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 487 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 488 | this.suiteDivs[suite.id] = suiteDiv; 489 | var parentDiv = this.outerDiv; 490 | if (suite.parentSuite) { 491 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 492 | } 493 | parentDiv.appendChild(suiteDiv); 494 | } 495 | 496 | this.startedAt = new Date(); 497 | 498 | var self = this; 499 | showPassed.onclick = function(evt) { 500 | if (showPassed.checked) { 501 | self.outerDiv.className += ' show-passed'; 502 | } else { 503 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 504 | } 505 | }; 506 | 507 | showSkipped.onclick = function(evt) { 508 | if (showSkipped.checked) { 509 | self.outerDiv.className += ' show-skipped'; 510 | } else { 511 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 512 | } 513 | }; 514 | }; 515 | 516 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 517 | var results = runner.results(); 518 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 519 | this.runnerDiv.setAttribute("class", className); 520 | //do it twice for IE 521 | this.runnerDiv.setAttribute("className", className); 522 | var specs = runner.specs(); 523 | var specCount = 0; 524 | for (var i = 0; i < specs.length; i++) { 525 | if (this.specFilter(specs[i])) { 526 | specCount++; 527 | } 528 | } 529 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 530 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 531 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 532 | 533 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 534 | }; 535 | 536 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 537 | var results = suite.results(); 538 | var status = results.passed() ? 'passed' : 'failed'; 539 | if (results.totalCount === 0) { // todo: change this to check results.skipped 540 | status = 'skipped'; 541 | } 542 | this.suiteDivs[suite.id].className += " " + status; 543 | }; 544 | 545 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 546 | if (this.logRunningSpecs) { 547 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 548 | } 549 | }; 550 | 551 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 552 | var results = spec.results(); 553 | var status = results.passed() ? 'passed' : 'failed'; 554 | if (results.skipped) { 555 | status = 'skipped'; 556 | } 557 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 558 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 559 | this.createDom('a', { 560 | className: 'description', 561 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 562 | title: spec.getFullName() 563 | }, spec.description)); 564 | 565 | 566 | var resultItems = results.getItems(); 567 | var messagesDiv = this.createDom('div', { className: 'messages' }); 568 | for (var i = 0; i < resultItems.length; i++) { 569 | var result = resultItems[i]; 570 | 571 | if (result.type == 'log') { 572 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 573 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 574 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 575 | 576 | if (result.trace.stack) { 577 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 578 | } 579 | } 580 | } 581 | 582 | if (messagesDiv.childNodes.length > 0) { 583 | specDiv.appendChild(messagesDiv); 584 | } 585 | 586 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 587 | }; 588 | 589 | jasmine.TrivialReporter.prototype.log = function() { 590 | var console = jasmine.getGlobal().console; 591 | if (console && console.log) { 592 | if (console.log.apply) { 593 | console.log.apply(console, arguments); 594 | } else { 595 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 596 | } 597 | } 598 | }; 599 | 600 | jasmine.TrivialReporter.prototype.getLocation = function() { 601 | return this.document.location; 602 | }; 603 | 604 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 605 | var paramMap = {}; 606 | var params = this.getLocation().search.substring(1).split('&'); 607 | for (var i = 0; i < params.length; i++) { 608 | var p = params[i].split('='); 609 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 610 | } 611 | 612 | if (!paramMap.spec) { 613 | return true; 614 | } 615 | return spec.getFullName().indexOf(paramMap.spec) === 0; 616 | }; 617 | -------------------------------------------------------------------------------- /test/lib/jasmine-1.2.0/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 23 | #HTMLReporter .runningAlert { background-color: #666666; } 24 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 25 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 26 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 27 | #HTMLReporter .passingAlert { background-color: #a6b779; } 28 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 29 | #HTMLReporter .failingAlert { background-color: #cf867e; } 30 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 31 | #HTMLReporter .results { margin-top: 14px; } 32 | #HTMLReporter #details { display: none; } 33 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 34 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 35 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 36 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 37 | #HTMLReporter.showDetails .summary { display: none; } 38 | #HTMLReporter.showDetails #details { display: block; } 39 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 40 | #HTMLReporter .summary { margin-top: 14px; } 41 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 42 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 43 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 44 | #HTMLReporter .description + .suite { margin-top: 0; } 45 | #HTMLReporter .suite { margin-top: 14px; } 46 | #HTMLReporter .suite a { color: #333333; } 47 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 48 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 49 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 50 | #HTMLReporter .resultMessage span.result { display: block; } 51 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 52 | 53 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 54 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 55 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 56 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 57 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 58 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 59 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 60 | #TrivialReporter .runner.running { background-color: yellow; } 61 | #TrivialReporter .options { text-align: right; font-size: .8em; } 62 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 63 | #TrivialReporter .suite .suite { margin: 5px; } 64 | #TrivialReporter .suite.passed { background-color: #dfd; } 65 | #TrivialReporter .suite.failed { background-color: #fdd; } 66 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 67 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 68 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 69 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 70 | #TrivialReporter .spec.skipped { background-color: #bbb; } 71 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 72 | #TrivialReporter .passed { background-color: #cfc; display: none; } 73 | #TrivialReporter .failed { background-color: #fbb; } 74 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 75 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 76 | #TrivialReporter .resultMessage .mismatch { color: black; } 77 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 78 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 79 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 80 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 81 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 82 | -------------------------------------------------------------------------------- /test/lib/jasmine-1.2.0/jasmine.js: -------------------------------------------------------------------------------- 1 | var isCommonJS = typeof window == "undefined"; 2 | 3 | /** 4 | * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. 5 | * 6 | * @namespace 7 | */ 8 | var jasmine = {}; 9 | if (isCommonJS) exports.jasmine = jasmine; 10 | /** 11 | * @private 12 | */ 13 | jasmine.unimplementedMethod_ = function() { 14 | throw new Error("unimplemented method"); 15 | }; 16 | 17 | /** 18 | * Use jasmine.undefined instead of undefined, since undefined is just 19 | * a plain old variable and may be redefined by somebody else. 20 | * 21 | * @private 22 | */ 23 | jasmine.undefined = jasmine.___undefined___; 24 | 25 | /** 26 | * Show diagnostic messages in the console if set to true 27 | * 28 | */ 29 | jasmine.VERBOSE = false; 30 | 31 | /** 32 | * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. 33 | * 34 | */ 35 | jasmine.DEFAULT_UPDATE_INTERVAL = 250; 36 | 37 | /** 38 | * Default timeout interval in milliseconds for waitsFor() blocks. 39 | */ 40 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; 41 | 42 | jasmine.getGlobal = function() { 43 | function getGlobal() { 44 | return this; 45 | } 46 | 47 | return getGlobal(); 48 | }; 49 | 50 | /** 51 | * Allows for bound functions to be compared. Internal use only. 52 | * 53 | * @ignore 54 | * @private 55 | * @param base {Object} bound 'this' for the function 56 | * @param name {Function} function to find 57 | */ 58 | jasmine.bindOriginal_ = function(base, name) { 59 | var original = base[name]; 60 | if (original.apply) { 61 | return function() { 62 | return original.apply(base, arguments); 63 | }; 64 | } else { 65 | // IE support 66 | return jasmine.getGlobal()[name]; 67 | } 68 | }; 69 | 70 | jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); 71 | jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); 72 | jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); 73 | jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); 74 | 75 | jasmine.MessageResult = function(values) { 76 | this.type = 'log'; 77 | this.values = values; 78 | this.trace = new Error(); // todo: test better 79 | }; 80 | 81 | jasmine.MessageResult.prototype.toString = function() { 82 | var text = ""; 83 | for (var i = 0; i < this.values.length; i++) { 84 | if (i > 0) text += " "; 85 | if (jasmine.isString_(this.values[i])) { 86 | text += this.values[i]; 87 | } else { 88 | text += jasmine.pp(this.values[i]); 89 | } 90 | } 91 | return text; 92 | }; 93 | 94 | jasmine.ExpectationResult = function(params) { 95 | this.type = 'expect'; 96 | this.matcherName = params.matcherName; 97 | this.passed_ = params.passed; 98 | this.expected = params.expected; 99 | this.actual = params.actual; 100 | this.message = this.passed_ ? 'Passed.' : params.message; 101 | 102 | var trace = (params.trace || new Error(this.message)); 103 | this.trace = this.passed_ ? '' : trace; 104 | }; 105 | 106 | jasmine.ExpectationResult.prototype.toString = function () { 107 | return this.message; 108 | }; 109 | 110 | jasmine.ExpectationResult.prototype.passed = function () { 111 | return this.passed_; 112 | }; 113 | 114 | /** 115 | * Getter for the Jasmine environment. Ensures one gets created 116 | */ 117 | jasmine.getEnv = function() { 118 | var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); 119 | return env; 120 | }; 121 | 122 | /** 123 | * @ignore 124 | * @private 125 | * @param value 126 | * @returns {Boolean} 127 | */ 128 | jasmine.isArray_ = function(value) { 129 | return jasmine.isA_("Array", value); 130 | }; 131 | 132 | /** 133 | * @ignore 134 | * @private 135 | * @param value 136 | * @returns {Boolean} 137 | */ 138 | jasmine.isString_ = function(value) { 139 | return jasmine.isA_("String", value); 140 | }; 141 | 142 | /** 143 | * @ignore 144 | * @private 145 | * @param value 146 | * @returns {Boolean} 147 | */ 148 | jasmine.isNumber_ = function(value) { 149 | return jasmine.isA_("Number", value); 150 | }; 151 | 152 | /** 153 | * @ignore 154 | * @private 155 | * @param {String} typeName 156 | * @param value 157 | * @returns {Boolean} 158 | */ 159 | jasmine.isA_ = function(typeName, value) { 160 | return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; 161 | }; 162 | 163 | /** 164 | * Pretty printer for expecations. Takes any object and turns it into a human-readable string. 165 | * 166 | * @param value {Object} an object to be outputted 167 | * @returns {String} 168 | */ 169 | jasmine.pp = function(value) { 170 | var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); 171 | stringPrettyPrinter.format(value); 172 | return stringPrettyPrinter.string; 173 | }; 174 | 175 | /** 176 | * Returns true if the object is a DOM Node. 177 | * 178 | * @param {Object} obj object to check 179 | * @returns {Boolean} 180 | */ 181 | jasmine.isDomNode = function(obj) { 182 | return obj.nodeType > 0; 183 | }; 184 | 185 | /** 186 | * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. 187 | * 188 | * @example 189 | * // don't care about which function is passed in, as long as it's a function 190 | * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); 191 | * 192 | * @param {Class} clazz 193 | * @returns matchable object of the type clazz 194 | */ 195 | jasmine.any = function(clazz) { 196 | return new jasmine.Matchers.Any(clazz); 197 | }; 198 | 199 | /** 200 | * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the 201 | * attributes on the object. 202 | * 203 | * @example 204 | * // don't care about any other attributes than foo. 205 | * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); 206 | * 207 | * @param sample {Object} sample 208 | * @returns matchable object for the sample 209 | */ 210 | jasmine.objectContaining = function (sample) { 211 | return new jasmine.Matchers.ObjectContaining(sample); 212 | }; 213 | 214 | /** 215 | * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. 216 | * 217 | * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine 218 | * expectation syntax. Spies can be checked if they were called or not and what the calling params were. 219 | * 220 | * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). 221 | * 222 | * Spies are torn down at the end of every spec. 223 | * 224 | * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. 225 | * 226 | * @example 227 | * // a stub 228 | * var myStub = jasmine.createSpy('myStub'); // can be used anywhere 229 | * 230 | * // spy example 231 | * var foo = { 232 | * not: function(bool) { return !bool; } 233 | * } 234 | * 235 | * // actual foo.not will not be called, execution stops 236 | * spyOn(foo, 'not'); 237 | 238 | // foo.not spied upon, execution will continue to implementation 239 | * spyOn(foo, 'not').andCallThrough(); 240 | * 241 | * // fake example 242 | * var foo = { 243 | * not: function(bool) { return !bool; } 244 | * } 245 | * 246 | * // foo.not(val) will return val 247 | * spyOn(foo, 'not').andCallFake(function(value) {return value;}); 248 | * 249 | * // mock example 250 | * foo.not(7 == 7); 251 | * expect(foo.not).toHaveBeenCalled(); 252 | * expect(foo.not).toHaveBeenCalledWith(true); 253 | * 254 | * @constructor 255 | * @see spyOn, jasmine.createSpy, jasmine.createSpyObj 256 | * @param {String} name 257 | */ 258 | jasmine.Spy = function(name) { 259 | /** 260 | * The name of the spy, if provided. 261 | */ 262 | this.identity = name || 'unknown'; 263 | /** 264 | * Is this Object a spy? 265 | */ 266 | this.isSpy = true; 267 | /** 268 | * The actual function this spy stubs. 269 | */ 270 | this.plan = function() { 271 | }; 272 | /** 273 | * Tracking of the most recent call to the spy. 274 | * @example 275 | * var mySpy = jasmine.createSpy('foo'); 276 | * mySpy(1, 2); 277 | * mySpy.mostRecentCall.args = [1, 2]; 278 | */ 279 | this.mostRecentCall = {}; 280 | 281 | /** 282 | * Holds arguments for each call to the spy, indexed by call count 283 | * @example 284 | * var mySpy = jasmine.createSpy('foo'); 285 | * mySpy(1, 2); 286 | * mySpy(7, 8); 287 | * mySpy.mostRecentCall.args = [7, 8]; 288 | * mySpy.argsForCall[0] = [1, 2]; 289 | * mySpy.argsForCall[1] = [7, 8]; 290 | */ 291 | this.argsForCall = []; 292 | this.calls = []; 293 | }; 294 | 295 | /** 296 | * Tells a spy to call through to the actual implemenatation. 297 | * 298 | * @example 299 | * var foo = { 300 | * bar: function() { // do some stuff } 301 | * } 302 | * 303 | * // defining a spy on an existing property: foo.bar 304 | * spyOn(foo, 'bar').andCallThrough(); 305 | */ 306 | jasmine.Spy.prototype.andCallThrough = function() { 307 | this.plan = this.originalValue; 308 | return this; 309 | }; 310 | 311 | /** 312 | * For setting the return value of a spy. 313 | * 314 | * @example 315 | * // defining a spy from scratch: foo() returns 'baz' 316 | * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); 317 | * 318 | * // defining a spy on an existing property: foo.bar() returns 'baz' 319 | * spyOn(foo, 'bar').andReturn('baz'); 320 | * 321 | * @param {Object} value 322 | */ 323 | jasmine.Spy.prototype.andReturn = function(value) { 324 | this.plan = function() { 325 | return value; 326 | }; 327 | return this; 328 | }; 329 | 330 | /** 331 | * For throwing an exception when a spy is called. 332 | * 333 | * @example 334 | * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' 335 | * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); 336 | * 337 | * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' 338 | * spyOn(foo, 'bar').andThrow('baz'); 339 | * 340 | * @param {String} exceptionMsg 341 | */ 342 | jasmine.Spy.prototype.andThrow = function(exceptionMsg) { 343 | this.plan = function() { 344 | throw exceptionMsg; 345 | }; 346 | return this; 347 | }; 348 | 349 | /** 350 | * Calls an alternate implementation when a spy is called. 351 | * 352 | * @example 353 | * var baz = function() { 354 | * // do some stuff, return something 355 | * } 356 | * // defining a spy from scratch: foo() calls the function baz 357 | * var foo = jasmine.createSpy('spy on foo').andCall(baz); 358 | * 359 | * // defining a spy on an existing property: foo.bar() calls an anonymnous function 360 | * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); 361 | * 362 | * @param {Function} fakeFunc 363 | */ 364 | jasmine.Spy.prototype.andCallFake = function(fakeFunc) { 365 | this.plan = fakeFunc; 366 | return this; 367 | }; 368 | 369 | /** 370 | * Resets all of a spy's the tracking variables so that it can be used again. 371 | * 372 | * @example 373 | * spyOn(foo, 'bar'); 374 | * 375 | * foo.bar(); 376 | * 377 | * expect(foo.bar.callCount).toEqual(1); 378 | * 379 | * foo.bar.reset(); 380 | * 381 | * expect(foo.bar.callCount).toEqual(0); 382 | */ 383 | jasmine.Spy.prototype.reset = function() { 384 | this.wasCalled = false; 385 | this.callCount = 0; 386 | this.argsForCall = []; 387 | this.calls = []; 388 | this.mostRecentCall = {}; 389 | }; 390 | 391 | jasmine.createSpy = function(name) { 392 | 393 | var spyObj = function() { 394 | spyObj.wasCalled = true; 395 | spyObj.callCount++; 396 | var args = jasmine.util.argsToArray(arguments); 397 | spyObj.mostRecentCall.object = this; 398 | spyObj.mostRecentCall.args = args; 399 | spyObj.argsForCall.push(args); 400 | spyObj.calls.push({object: this, args: args}); 401 | return spyObj.plan.apply(this, arguments); 402 | }; 403 | 404 | var spy = new jasmine.Spy(name); 405 | 406 | for (var prop in spy) { 407 | spyObj[prop] = spy[prop]; 408 | } 409 | 410 | spyObj.reset(); 411 | 412 | return spyObj; 413 | }; 414 | 415 | /** 416 | * Determines whether an object is a spy. 417 | * 418 | * @param {jasmine.Spy|Object} putativeSpy 419 | * @returns {Boolean} 420 | */ 421 | jasmine.isSpy = function(putativeSpy) { 422 | return putativeSpy && putativeSpy.isSpy; 423 | }; 424 | 425 | /** 426 | * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something 427 | * large in one call. 428 | * 429 | * @param {String} baseName name of spy class 430 | * @param {Array} methodNames array of names of methods to make spies 431 | */ 432 | jasmine.createSpyObj = function(baseName, methodNames) { 433 | if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { 434 | throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); 435 | } 436 | var obj = {}; 437 | for (var i = 0; i < methodNames.length; i++) { 438 | obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); 439 | } 440 | return obj; 441 | }; 442 | 443 | /** 444 | * All parameters are pretty-printed and concatenated together, then written to the current spec's output. 445 | * 446 | * Be careful not to leave calls to jasmine.log in production code. 447 | */ 448 | jasmine.log = function() { 449 | var spec = jasmine.getEnv().currentSpec; 450 | spec.log.apply(spec, arguments); 451 | }; 452 | 453 | /** 454 | * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. 455 | * 456 | * @example 457 | * // spy example 458 | * var foo = { 459 | * not: function(bool) { return !bool; } 460 | * } 461 | * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops 462 | * 463 | * @see jasmine.createSpy 464 | * @param obj 465 | * @param methodName 466 | * @returns a Jasmine spy that can be chained with all spy methods 467 | */ 468 | var spyOn = function(obj, methodName) { 469 | return jasmine.getEnv().currentSpec.spyOn(obj, methodName); 470 | }; 471 | if (isCommonJS) exports.spyOn = spyOn; 472 | 473 | /** 474 | * Creates a Jasmine spec that will be added to the current suite. 475 | * 476 | * // TODO: pending tests 477 | * 478 | * @example 479 | * it('should be true', function() { 480 | * expect(true).toEqual(true); 481 | * }); 482 | * 483 | * @param {String} desc description of this specification 484 | * @param {Function} func defines the preconditions and expectations of the spec 485 | */ 486 | var it = function(desc, func) { 487 | return jasmine.getEnv().it(desc, func); 488 | }; 489 | if (isCommonJS) exports.it = it; 490 | 491 | /** 492 | * Creates a disabled Jasmine spec. 493 | * 494 | * A convenience method that allows existing specs to be disabled temporarily during development. 495 | * 496 | * @param {String} desc description of this specification 497 | * @param {Function} func defines the preconditions and expectations of the spec 498 | */ 499 | var xit = function(desc, func) { 500 | return jasmine.getEnv().xit(desc, func); 501 | }; 502 | if (isCommonJS) exports.xit = xit; 503 | 504 | /** 505 | * Starts a chain for a Jasmine expectation. 506 | * 507 | * It is passed an Object that is the actual value and should chain to one of the many 508 | * jasmine.Matchers functions. 509 | * 510 | * @param {Object} actual Actual value to test against and expected value 511 | */ 512 | var expect = function(actual) { 513 | return jasmine.getEnv().currentSpec.expect(actual); 514 | }; 515 | if (isCommonJS) exports.expect = expect; 516 | 517 | /** 518 | * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. 519 | * 520 | * @param {Function} func Function that defines part of a jasmine spec. 521 | */ 522 | var runs = function(func) { 523 | jasmine.getEnv().currentSpec.runs(func); 524 | }; 525 | if (isCommonJS) exports.runs = runs; 526 | 527 | /** 528 | * Waits a fixed time period before moving to the next block. 529 | * 530 | * @deprecated Use waitsFor() instead 531 | * @param {Number} timeout milliseconds to wait 532 | */ 533 | var waits = function(timeout) { 534 | jasmine.getEnv().currentSpec.waits(timeout); 535 | }; 536 | if (isCommonJS) exports.waits = waits; 537 | 538 | /** 539 | * Waits for the latchFunction to return true before proceeding to the next block. 540 | * 541 | * @param {Function} latchFunction 542 | * @param {String} optional_timeoutMessage 543 | * @param {Number} optional_timeout 544 | */ 545 | var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 546 | jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); 547 | }; 548 | if (isCommonJS) exports.waitsFor = waitsFor; 549 | 550 | /** 551 | * A function that is called before each spec in a suite. 552 | * 553 | * Used for spec setup, including validating assumptions. 554 | * 555 | * @param {Function} beforeEachFunction 556 | */ 557 | var beforeEach = function(beforeEachFunction) { 558 | jasmine.getEnv().beforeEach(beforeEachFunction); 559 | }; 560 | if (isCommonJS) exports.beforeEach = beforeEach; 561 | 562 | /** 563 | * A function that is called after each spec in a suite. 564 | * 565 | * Used for restoring any state that is hijacked during spec execution. 566 | * 567 | * @param {Function} afterEachFunction 568 | */ 569 | var afterEach = function(afterEachFunction) { 570 | jasmine.getEnv().afterEach(afterEachFunction); 571 | }; 572 | if (isCommonJS) exports.afterEach = afterEach; 573 | 574 | /** 575 | * Defines a suite of specifications. 576 | * 577 | * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared 578 | * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization 579 | * of setup in some tests. 580 | * 581 | * @example 582 | * // TODO: a simple suite 583 | * 584 | * // TODO: a simple suite with a nested describe block 585 | * 586 | * @param {String} description A string, usually the class under test. 587 | * @param {Function} specDefinitions function that defines several specs. 588 | */ 589 | var describe = function(description, specDefinitions) { 590 | return jasmine.getEnv().describe(description, specDefinitions); 591 | }; 592 | if (isCommonJS) exports.describe = describe; 593 | 594 | /** 595 | * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. 596 | * 597 | * @param {String} description A string, usually the class under test. 598 | * @param {Function} specDefinitions function that defines several specs. 599 | */ 600 | var xdescribe = function(description, specDefinitions) { 601 | return jasmine.getEnv().xdescribe(description, specDefinitions); 602 | }; 603 | if (isCommonJS) exports.xdescribe = xdescribe; 604 | 605 | 606 | // Provide the XMLHttpRequest class for IE 5.x-6.x: 607 | jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { 608 | function tryIt(f) { 609 | try { 610 | return f(); 611 | } catch(e) { 612 | } 613 | return null; 614 | } 615 | 616 | var xhr = tryIt(function() { 617 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 618 | }) || 619 | tryIt(function() { 620 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 621 | }) || 622 | tryIt(function() { 623 | return new ActiveXObject("Msxml2.XMLHTTP"); 624 | }) || 625 | tryIt(function() { 626 | return new ActiveXObject("Microsoft.XMLHTTP"); 627 | }); 628 | 629 | if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); 630 | 631 | return xhr; 632 | } : XMLHttpRequest; 633 | /** 634 | * @namespace 635 | */ 636 | jasmine.util = {}; 637 | 638 | /** 639 | * Declare that a child class inherit it's prototype from the parent class. 640 | * 641 | * @private 642 | * @param {Function} childClass 643 | * @param {Function} parentClass 644 | */ 645 | jasmine.util.inherit = function(childClass, parentClass) { 646 | /** 647 | * @private 648 | */ 649 | var subclass = function() { 650 | }; 651 | subclass.prototype = parentClass.prototype; 652 | childClass.prototype = new subclass(); 653 | }; 654 | 655 | jasmine.util.formatException = function(e) { 656 | var lineNumber; 657 | if (e.line) { 658 | lineNumber = e.line; 659 | } 660 | else if (e.lineNumber) { 661 | lineNumber = e.lineNumber; 662 | } 663 | 664 | var file; 665 | 666 | if (e.sourceURL) { 667 | file = e.sourceURL; 668 | } 669 | else if (e.fileName) { 670 | file = e.fileName; 671 | } 672 | 673 | var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 674 | 675 | if (file && lineNumber) { 676 | message += ' in ' + file + ' (line ' + lineNumber + ')'; 677 | } 678 | 679 | return message; 680 | }; 681 | 682 | jasmine.util.htmlEscape = function(str) { 683 | if (!str) return str; 684 | return str.replace(/&/g, '&') 685 | .replace(//g, '>'); 687 | }; 688 | 689 | jasmine.util.argsToArray = function(args) { 690 | var arrayOfArgs = []; 691 | for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 692 | return arrayOfArgs; 693 | }; 694 | 695 | jasmine.util.extend = function(destination, source) { 696 | for (var property in source) destination[property] = source[property]; 697 | return destination; 698 | }; 699 | 700 | /** 701 | * Environment for Jasmine 702 | * 703 | * @constructor 704 | */ 705 | jasmine.Env = function() { 706 | this.currentSpec = null; 707 | this.currentSuite = null; 708 | this.currentRunner_ = new jasmine.Runner(this); 709 | 710 | this.reporter = new jasmine.MultiReporter(); 711 | 712 | this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; 713 | this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; 714 | this.lastUpdate = 0; 715 | this.specFilter = function() { 716 | return true; 717 | }; 718 | 719 | this.nextSpecId_ = 0; 720 | this.nextSuiteId_ = 0; 721 | this.equalityTesters_ = []; 722 | 723 | // wrap matchers 724 | this.matchersClass = function() { 725 | jasmine.Matchers.apply(this, arguments); 726 | }; 727 | jasmine.util.inherit(this.matchersClass, jasmine.Matchers); 728 | 729 | jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); 730 | }; 731 | 732 | 733 | jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 734 | jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 735 | jasmine.Env.prototype.setInterval = jasmine.setInterval; 736 | jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 737 | 738 | /** 739 | * @returns an object containing jasmine version build info, if set. 740 | */ 741 | jasmine.Env.prototype.version = function () { 742 | if (jasmine.version_) { 743 | return jasmine.version_; 744 | } else { 745 | throw new Error('Version not set'); 746 | } 747 | }; 748 | 749 | /** 750 | * @returns string containing jasmine version build info, if set. 751 | */ 752 | jasmine.Env.prototype.versionString = function() { 753 | if (!jasmine.version_) { 754 | return "version unknown"; 755 | } 756 | 757 | var version = this.version(); 758 | var versionString = version.major + "." + version.minor + "." + version.build; 759 | if (version.release_candidate) { 760 | versionString += ".rc" + version.release_candidate; 761 | } 762 | versionString += " revision " + version.revision; 763 | return versionString; 764 | }; 765 | 766 | /** 767 | * @returns a sequential integer starting at 0 768 | */ 769 | jasmine.Env.prototype.nextSpecId = function () { 770 | return this.nextSpecId_++; 771 | }; 772 | 773 | /** 774 | * @returns a sequential integer starting at 0 775 | */ 776 | jasmine.Env.prototype.nextSuiteId = function () { 777 | return this.nextSuiteId_++; 778 | }; 779 | 780 | /** 781 | * Register a reporter to receive status updates from Jasmine. 782 | * @param {jasmine.Reporter} reporter An object which will receive status updates. 783 | */ 784 | jasmine.Env.prototype.addReporter = function(reporter) { 785 | this.reporter.addReporter(reporter); 786 | }; 787 | 788 | jasmine.Env.prototype.execute = function() { 789 | this.currentRunner_.execute(); 790 | }; 791 | 792 | jasmine.Env.prototype.describe = function(description, specDefinitions) { 793 | var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 794 | 795 | var parentSuite = this.currentSuite; 796 | if (parentSuite) { 797 | parentSuite.add(suite); 798 | } else { 799 | this.currentRunner_.add(suite); 800 | } 801 | 802 | this.currentSuite = suite; 803 | 804 | var declarationError = null; 805 | try { 806 | specDefinitions.call(suite); 807 | } catch(e) { 808 | declarationError = e; 809 | } 810 | 811 | if (declarationError) { 812 | this.it("encountered a declaration exception", function() { 813 | throw declarationError; 814 | }); 815 | } 816 | 817 | this.currentSuite = parentSuite; 818 | 819 | return suite; 820 | }; 821 | 822 | jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 823 | if (this.currentSuite) { 824 | this.currentSuite.beforeEach(beforeEachFunction); 825 | } else { 826 | this.currentRunner_.beforeEach(beforeEachFunction); 827 | } 828 | }; 829 | 830 | jasmine.Env.prototype.currentRunner = function () { 831 | return this.currentRunner_; 832 | }; 833 | 834 | jasmine.Env.prototype.afterEach = function(afterEachFunction) { 835 | if (this.currentSuite) { 836 | this.currentSuite.afterEach(afterEachFunction); 837 | } else { 838 | this.currentRunner_.afterEach(afterEachFunction); 839 | } 840 | 841 | }; 842 | 843 | jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 844 | return { 845 | execute: function() { 846 | } 847 | }; 848 | }; 849 | 850 | jasmine.Env.prototype.it = function(description, func) { 851 | var spec = new jasmine.Spec(this, this.currentSuite, description); 852 | this.currentSuite.add(spec); 853 | this.currentSpec = spec; 854 | 855 | if (func) { 856 | spec.runs(func); 857 | } 858 | 859 | return spec; 860 | }; 861 | 862 | jasmine.Env.prototype.xit = function(desc, func) { 863 | return { 864 | id: this.nextSpecId(), 865 | runs: function() { 866 | } 867 | }; 868 | }; 869 | 870 | jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 871 | if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 872 | return true; 873 | } 874 | 875 | a.__Jasmine_been_here_before__ = b; 876 | b.__Jasmine_been_here_before__ = a; 877 | 878 | var hasKey = function(obj, keyName) { 879 | return obj !== null && obj[keyName] !== jasmine.undefined; 880 | }; 881 | 882 | for (var property in b) { 883 | if (!hasKey(a, property) && hasKey(b, property)) { 884 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 885 | } 886 | } 887 | for (property in a) { 888 | if (!hasKey(b, property) && hasKey(a, property)) { 889 | mismatchKeys.push("expected missing key '" + property + "', but present in actual."); 890 | } 891 | } 892 | for (property in b) { 893 | if (property == '__Jasmine_been_here_before__') continue; 894 | if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 895 | mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); 896 | } 897 | } 898 | 899 | if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 900 | mismatchValues.push("arrays were not the same length"); 901 | } 902 | 903 | delete a.__Jasmine_been_here_before__; 904 | delete b.__Jasmine_been_here_before__; 905 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 906 | }; 907 | 908 | jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 909 | mismatchKeys = mismatchKeys || []; 910 | mismatchValues = mismatchValues || []; 911 | 912 | for (var i = 0; i < this.equalityTesters_.length; i++) { 913 | var equalityTester = this.equalityTesters_[i]; 914 | var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 915 | if (result !== jasmine.undefined) return result; 916 | } 917 | 918 | if (a === b) return true; 919 | 920 | if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { 921 | return (a == jasmine.undefined && b == jasmine.undefined); 922 | } 923 | 924 | if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 925 | return a === b; 926 | } 927 | 928 | if (a instanceof Date && b instanceof Date) { 929 | return a.getTime() == b.getTime(); 930 | } 931 | 932 | if (a.jasmineMatches) { 933 | return a.jasmineMatches(b); 934 | } 935 | 936 | if (b.jasmineMatches) { 937 | return b.jasmineMatches(a); 938 | } 939 | 940 | if (a instanceof jasmine.Matchers.ObjectContaining) { 941 | return a.matches(b); 942 | } 943 | 944 | if (b instanceof jasmine.Matchers.ObjectContaining) { 945 | return b.matches(a); 946 | } 947 | 948 | if (jasmine.isString_(a) && jasmine.isString_(b)) { 949 | return (a == b); 950 | } 951 | 952 | if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { 953 | return (a == b); 954 | } 955 | 956 | if (typeof a === "object" && typeof b === "object") { 957 | return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 958 | } 959 | 960 | //Straight check 961 | return (a === b); 962 | }; 963 | 964 | jasmine.Env.prototype.contains_ = function(haystack, needle) { 965 | if (jasmine.isArray_(haystack)) { 966 | for (var i = 0; i < haystack.length; i++) { 967 | if (this.equals_(haystack[i], needle)) return true; 968 | } 969 | return false; 970 | } 971 | return haystack.indexOf(needle) >= 0; 972 | }; 973 | 974 | jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 975 | this.equalityTesters_.push(equalityTester); 976 | }; 977 | /** No-op base class for Jasmine reporters. 978 | * 979 | * @constructor 980 | */ 981 | jasmine.Reporter = function() { 982 | }; 983 | 984 | //noinspection JSUnusedLocalSymbols 985 | jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 986 | }; 987 | 988 | //noinspection JSUnusedLocalSymbols 989 | jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 990 | }; 991 | 992 | //noinspection JSUnusedLocalSymbols 993 | jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 994 | }; 995 | 996 | //noinspection JSUnusedLocalSymbols 997 | jasmine.Reporter.prototype.reportSpecStarting = function(spec) { 998 | }; 999 | 1000 | //noinspection JSUnusedLocalSymbols 1001 | jasmine.Reporter.prototype.reportSpecResults = function(spec) { 1002 | }; 1003 | 1004 | //noinspection JSUnusedLocalSymbols 1005 | jasmine.Reporter.prototype.log = function(str) { 1006 | }; 1007 | 1008 | /** 1009 | * Blocks are functions with executable code that make up a spec. 1010 | * 1011 | * @constructor 1012 | * @param {jasmine.Env} env 1013 | * @param {Function} func 1014 | * @param {jasmine.Spec} spec 1015 | */ 1016 | jasmine.Block = function(env, func, spec) { 1017 | this.env = env; 1018 | this.func = func; 1019 | this.spec = spec; 1020 | }; 1021 | 1022 | jasmine.Block.prototype.execute = function(onComplete) { 1023 | try { 1024 | this.func.apply(this.spec); 1025 | } catch (e) { 1026 | this.spec.fail(e); 1027 | } 1028 | onComplete(); 1029 | }; 1030 | /** JavaScript API reporter. 1031 | * 1032 | * @constructor 1033 | */ 1034 | jasmine.JsApiReporter = function() { 1035 | this.started = false; 1036 | this.finished = false; 1037 | this.suites_ = []; 1038 | this.results_ = {}; 1039 | }; 1040 | 1041 | jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 1042 | this.started = true; 1043 | var suites = runner.topLevelSuites(); 1044 | for (var i = 0; i < suites.length; i++) { 1045 | var suite = suites[i]; 1046 | this.suites_.push(this.summarize_(suite)); 1047 | } 1048 | }; 1049 | 1050 | jasmine.JsApiReporter.prototype.suites = function() { 1051 | return this.suites_; 1052 | }; 1053 | 1054 | jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 1055 | var isSuite = suiteOrSpec instanceof jasmine.Suite; 1056 | var summary = { 1057 | id: suiteOrSpec.id, 1058 | name: suiteOrSpec.description, 1059 | type: isSuite ? 'suite' : 'spec', 1060 | children: [] 1061 | }; 1062 | 1063 | if (isSuite) { 1064 | var children = suiteOrSpec.children(); 1065 | for (var i = 0; i < children.length; i++) { 1066 | summary.children.push(this.summarize_(children[i])); 1067 | } 1068 | } 1069 | return summary; 1070 | }; 1071 | 1072 | jasmine.JsApiReporter.prototype.results = function() { 1073 | return this.results_; 1074 | }; 1075 | 1076 | jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 1077 | return this.results_[specId]; 1078 | }; 1079 | 1080 | //noinspection JSUnusedLocalSymbols 1081 | jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 1082 | this.finished = true; 1083 | }; 1084 | 1085 | //noinspection JSUnusedLocalSymbols 1086 | jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 1087 | }; 1088 | 1089 | //noinspection JSUnusedLocalSymbols 1090 | jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 1091 | this.results_[spec.id] = { 1092 | messages: spec.results().getItems(), 1093 | result: spec.results().failedCount > 0 ? "failed" : "passed" 1094 | }; 1095 | }; 1096 | 1097 | //noinspection JSUnusedLocalSymbols 1098 | jasmine.JsApiReporter.prototype.log = function(str) { 1099 | }; 1100 | 1101 | jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 1102 | var results = {}; 1103 | for (var i = 0; i < specIds.length; i++) { 1104 | var specId = specIds[i]; 1105 | results[specId] = this.summarizeResult_(this.results_[specId]); 1106 | } 1107 | return results; 1108 | }; 1109 | 1110 | jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 1111 | var summaryMessages = []; 1112 | var messagesLength = result.messages.length; 1113 | for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { 1114 | var resultMessage = result.messages[messageIndex]; 1115 | summaryMessages.push({ 1116 | text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, 1117 | passed: resultMessage.passed ? resultMessage.passed() : true, 1118 | type: resultMessage.type, 1119 | message: resultMessage.message, 1120 | trace: { 1121 | stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined 1122 | } 1123 | }); 1124 | } 1125 | 1126 | return { 1127 | result : result.result, 1128 | messages : summaryMessages 1129 | }; 1130 | }; 1131 | 1132 | /** 1133 | * @constructor 1134 | * @param {jasmine.Env} env 1135 | * @param actual 1136 | * @param {jasmine.Spec} spec 1137 | */ 1138 | jasmine.Matchers = function(env, actual, spec, opt_isNot) { 1139 | this.env = env; 1140 | this.actual = actual; 1141 | this.spec = spec; 1142 | this.isNot = opt_isNot || false; 1143 | this.reportWasCalled_ = false; 1144 | }; 1145 | 1146 | // todo: @deprecated as of Jasmine 0.11, remove soon [xw] 1147 | jasmine.Matchers.pp = function(str) { 1148 | throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); 1149 | }; 1150 | 1151 | // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] 1152 | jasmine.Matchers.prototype.report = function(result, failing_message, details) { 1153 | throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); 1154 | }; 1155 | 1156 | jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { 1157 | for (var methodName in prototype) { 1158 | if (methodName == 'report') continue; 1159 | var orig = prototype[methodName]; 1160 | matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); 1161 | } 1162 | }; 1163 | 1164 | jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { 1165 | return function() { 1166 | var matcherArgs = jasmine.util.argsToArray(arguments); 1167 | var result = matcherFunction.apply(this, arguments); 1168 | 1169 | if (this.isNot) { 1170 | result = !result; 1171 | } 1172 | 1173 | if (this.reportWasCalled_) return result; 1174 | 1175 | var message; 1176 | if (!result) { 1177 | if (this.message) { 1178 | message = this.message.apply(this, arguments); 1179 | if (jasmine.isArray_(message)) { 1180 | message = message[this.isNot ? 1 : 0]; 1181 | } 1182 | } else { 1183 | var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); 1184 | message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; 1185 | if (matcherArgs.length > 0) { 1186 | for (var i = 0; i < matcherArgs.length; i++) { 1187 | if (i > 0) message += ","; 1188 | message += " " + jasmine.pp(matcherArgs[i]); 1189 | } 1190 | } 1191 | message += "."; 1192 | } 1193 | } 1194 | var expectationResult = new jasmine.ExpectationResult({ 1195 | matcherName: matcherName, 1196 | passed: result, 1197 | expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], 1198 | actual: this.actual, 1199 | message: message 1200 | }); 1201 | this.spec.addMatcherResult(expectationResult); 1202 | return jasmine.undefined; 1203 | }; 1204 | }; 1205 | 1206 | 1207 | 1208 | 1209 | /** 1210 | * toBe: compares the actual to the expected using === 1211 | * @param expected 1212 | */ 1213 | jasmine.Matchers.prototype.toBe = function(expected) { 1214 | return this.actual === expected; 1215 | }; 1216 | 1217 | /** 1218 | * toNotBe: compares the actual to the expected using !== 1219 | * @param expected 1220 | * @deprecated as of 1.0. Use not.toBe() instead. 1221 | */ 1222 | jasmine.Matchers.prototype.toNotBe = function(expected) { 1223 | return this.actual !== expected; 1224 | }; 1225 | 1226 | /** 1227 | * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1228 | * 1229 | * @param expected 1230 | */ 1231 | jasmine.Matchers.prototype.toEqual = function(expected) { 1232 | return this.env.equals_(this.actual, expected); 1233 | }; 1234 | 1235 | /** 1236 | * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1237 | * @param expected 1238 | * @deprecated as of 1.0. Use not.toEqual() instead. 1239 | */ 1240 | jasmine.Matchers.prototype.toNotEqual = function(expected) { 1241 | return !this.env.equals_(this.actual, expected); 1242 | }; 1243 | 1244 | /** 1245 | * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1246 | * a pattern or a String. 1247 | * 1248 | * @param expected 1249 | */ 1250 | jasmine.Matchers.prototype.toMatch = function(expected) { 1251 | return new RegExp(expected).test(this.actual); 1252 | }; 1253 | 1254 | /** 1255 | * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1256 | * @param expected 1257 | * @deprecated as of 1.0. Use not.toMatch() instead. 1258 | */ 1259 | jasmine.Matchers.prototype.toNotMatch = function(expected) { 1260 | return !(new RegExp(expected).test(this.actual)); 1261 | }; 1262 | 1263 | /** 1264 | * Matcher that compares the actual to jasmine.undefined. 1265 | */ 1266 | jasmine.Matchers.prototype.toBeDefined = function() { 1267 | return (this.actual !== jasmine.undefined); 1268 | }; 1269 | 1270 | /** 1271 | * Matcher that compares the actual to jasmine.undefined. 1272 | */ 1273 | jasmine.Matchers.prototype.toBeUndefined = function() { 1274 | return (this.actual === jasmine.undefined); 1275 | }; 1276 | 1277 | /** 1278 | * Matcher that compares the actual to null. 1279 | */ 1280 | jasmine.Matchers.prototype.toBeNull = function() { 1281 | return (this.actual === null); 1282 | }; 1283 | 1284 | /** 1285 | * Matcher that boolean not-nots the actual. 1286 | */ 1287 | jasmine.Matchers.prototype.toBeTruthy = function() { 1288 | return !!this.actual; 1289 | }; 1290 | 1291 | 1292 | /** 1293 | * Matcher that boolean nots the actual. 1294 | */ 1295 | jasmine.Matchers.prototype.toBeFalsy = function() { 1296 | return !this.actual; 1297 | }; 1298 | 1299 | 1300 | /** 1301 | * Matcher that checks to see if the actual, a Jasmine spy, was called. 1302 | */ 1303 | jasmine.Matchers.prototype.toHaveBeenCalled = function() { 1304 | if (arguments.length > 0) { 1305 | throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); 1306 | } 1307 | 1308 | if (!jasmine.isSpy(this.actual)) { 1309 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1310 | } 1311 | 1312 | this.message = function() { 1313 | return [ 1314 | "Expected spy " + this.actual.identity + " to have been called.", 1315 | "Expected spy " + this.actual.identity + " not to have been called." 1316 | ]; 1317 | }; 1318 | 1319 | return this.actual.wasCalled; 1320 | }; 1321 | 1322 | /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ 1323 | jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; 1324 | 1325 | /** 1326 | * Matcher that checks to see if the actual, a Jasmine spy, was not called. 1327 | * 1328 | * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead 1329 | */ 1330 | jasmine.Matchers.prototype.wasNotCalled = function() { 1331 | if (arguments.length > 0) { 1332 | throw new Error('wasNotCalled does not take arguments'); 1333 | } 1334 | 1335 | if (!jasmine.isSpy(this.actual)) { 1336 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1337 | } 1338 | 1339 | this.message = function() { 1340 | return [ 1341 | "Expected spy " + this.actual.identity + " to not have been called.", 1342 | "Expected spy " + this.actual.identity + " to have been called." 1343 | ]; 1344 | }; 1345 | 1346 | return !this.actual.wasCalled; 1347 | }; 1348 | 1349 | /** 1350 | * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. 1351 | * 1352 | * @example 1353 | * 1354 | */ 1355 | jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { 1356 | var expectedArgs = jasmine.util.argsToArray(arguments); 1357 | if (!jasmine.isSpy(this.actual)) { 1358 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1359 | } 1360 | this.message = function() { 1361 | if (this.actual.callCount === 0) { 1362 | // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] 1363 | return [ 1364 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", 1365 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." 1366 | ]; 1367 | } else { 1368 | return [ 1369 | "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), 1370 | "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) 1371 | ]; 1372 | } 1373 | }; 1374 | 1375 | return this.env.contains_(this.actual.argsForCall, expectedArgs); 1376 | }; 1377 | 1378 | /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ 1379 | jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; 1380 | 1381 | /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ 1382 | jasmine.Matchers.prototype.wasNotCalledWith = function() { 1383 | var expectedArgs = jasmine.util.argsToArray(arguments); 1384 | if (!jasmine.isSpy(this.actual)) { 1385 | throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); 1386 | } 1387 | 1388 | this.message = function() { 1389 | return [ 1390 | "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", 1391 | "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" 1392 | ]; 1393 | }; 1394 | 1395 | return !this.env.contains_(this.actual.argsForCall, expectedArgs); 1396 | }; 1397 | 1398 | /** 1399 | * Matcher that checks that the expected item is an element in the actual Array. 1400 | * 1401 | * @param {Object} expected 1402 | */ 1403 | jasmine.Matchers.prototype.toContain = function(expected) { 1404 | return this.env.contains_(this.actual, expected); 1405 | }; 1406 | 1407 | /** 1408 | * Matcher that checks that the expected item is NOT an element in the actual Array. 1409 | * 1410 | * @param {Object} expected 1411 | * @deprecated as of 1.0. Use not.toContain() instead. 1412 | */ 1413 | jasmine.Matchers.prototype.toNotContain = function(expected) { 1414 | return !this.env.contains_(this.actual, expected); 1415 | }; 1416 | 1417 | jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1418 | return this.actual < expected; 1419 | }; 1420 | 1421 | jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1422 | return this.actual > expected; 1423 | }; 1424 | 1425 | /** 1426 | * Matcher that checks that the expected item is equal to the actual item 1427 | * up to a given level of decimal precision (default 2). 1428 | * 1429 | * @param {Number} expected 1430 | * @param {Number} precision 1431 | */ 1432 | jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { 1433 | if (!(precision === 0)) { 1434 | precision = precision || 2; 1435 | } 1436 | var multiplier = Math.pow(10, precision); 1437 | var actual = Math.round(this.actual * multiplier); 1438 | expected = Math.round(expected * multiplier); 1439 | return expected == actual; 1440 | }; 1441 | 1442 | /** 1443 | * Matcher that checks that the expected exception was thrown by the actual. 1444 | * 1445 | * @param {String} expected 1446 | */ 1447 | jasmine.Matchers.prototype.toThrow = function(expected) { 1448 | var result = false; 1449 | var exception; 1450 | if (typeof this.actual != 'function') { 1451 | throw new Error('Actual is not a function'); 1452 | } 1453 | try { 1454 | this.actual(); 1455 | } catch (e) { 1456 | exception = e; 1457 | } 1458 | if (exception) { 1459 | result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); 1460 | } 1461 | 1462 | var not = this.isNot ? "not " : ""; 1463 | 1464 | this.message = function() { 1465 | if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { 1466 | return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); 1467 | } else { 1468 | return "Expected function to throw an exception."; 1469 | } 1470 | }; 1471 | 1472 | return result; 1473 | }; 1474 | 1475 | jasmine.Matchers.Any = function(expectedClass) { 1476 | this.expectedClass = expectedClass; 1477 | }; 1478 | 1479 | jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { 1480 | if (this.expectedClass == String) { 1481 | return typeof other == 'string' || other instanceof String; 1482 | } 1483 | 1484 | if (this.expectedClass == Number) { 1485 | return typeof other == 'number' || other instanceof Number; 1486 | } 1487 | 1488 | if (this.expectedClass == Function) { 1489 | return typeof other == 'function' || other instanceof Function; 1490 | } 1491 | 1492 | if (this.expectedClass == Object) { 1493 | return typeof other == 'object'; 1494 | } 1495 | 1496 | return other instanceof this.expectedClass; 1497 | }; 1498 | 1499 | jasmine.Matchers.Any.prototype.jasmineToString = function() { 1500 | return ''; 1501 | }; 1502 | 1503 | jasmine.Matchers.ObjectContaining = function (sample) { 1504 | this.sample = sample; 1505 | }; 1506 | 1507 | jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { 1508 | mismatchKeys = mismatchKeys || []; 1509 | mismatchValues = mismatchValues || []; 1510 | 1511 | var env = jasmine.getEnv(); 1512 | 1513 | var hasKey = function(obj, keyName) { 1514 | return obj != null && obj[keyName] !== jasmine.undefined; 1515 | }; 1516 | 1517 | for (var property in this.sample) { 1518 | if (!hasKey(other, property) && hasKey(this.sample, property)) { 1519 | mismatchKeys.push("expected has key '" + property + "', but missing from actual."); 1520 | } 1521 | else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { 1522 | mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); 1523 | } 1524 | } 1525 | 1526 | return (mismatchKeys.length === 0 && mismatchValues.length === 0); 1527 | }; 1528 | 1529 | jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { 1530 | return ""; 1531 | }; 1532 | // Mock setTimeout, clearTimeout 1533 | // Contributed by Pivotal Computer Systems, www.pivotalsf.com 1534 | 1535 | jasmine.FakeTimer = function() { 1536 | this.reset(); 1537 | 1538 | var self = this; 1539 | self.setTimeout = function(funcToCall, millis) { 1540 | self.timeoutsMade++; 1541 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 1542 | return self.timeoutsMade; 1543 | }; 1544 | 1545 | self.setInterval = function(funcToCall, millis) { 1546 | self.timeoutsMade++; 1547 | self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 1548 | return self.timeoutsMade; 1549 | }; 1550 | 1551 | self.clearTimeout = function(timeoutKey) { 1552 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 1553 | }; 1554 | 1555 | self.clearInterval = function(timeoutKey) { 1556 | self.scheduledFunctions[timeoutKey] = jasmine.undefined; 1557 | }; 1558 | 1559 | }; 1560 | 1561 | jasmine.FakeTimer.prototype.reset = function() { 1562 | this.timeoutsMade = 0; 1563 | this.scheduledFunctions = {}; 1564 | this.nowMillis = 0; 1565 | }; 1566 | 1567 | jasmine.FakeTimer.prototype.tick = function(millis) { 1568 | var oldMillis = this.nowMillis; 1569 | var newMillis = oldMillis + millis; 1570 | this.runFunctionsWithinRange(oldMillis, newMillis); 1571 | this.nowMillis = newMillis; 1572 | }; 1573 | 1574 | jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 1575 | var scheduledFunc; 1576 | var funcsToRun = []; 1577 | for (var timeoutKey in this.scheduledFunctions) { 1578 | scheduledFunc = this.scheduledFunctions[timeoutKey]; 1579 | if (scheduledFunc != jasmine.undefined && 1580 | scheduledFunc.runAtMillis >= oldMillis && 1581 | scheduledFunc.runAtMillis <= nowMillis) { 1582 | funcsToRun.push(scheduledFunc); 1583 | this.scheduledFunctions[timeoutKey] = jasmine.undefined; 1584 | } 1585 | } 1586 | 1587 | if (funcsToRun.length > 0) { 1588 | funcsToRun.sort(function(a, b) { 1589 | return a.runAtMillis - b.runAtMillis; 1590 | }); 1591 | for (var i = 0; i < funcsToRun.length; ++i) { 1592 | try { 1593 | var funcToRun = funcsToRun[i]; 1594 | this.nowMillis = funcToRun.runAtMillis; 1595 | funcToRun.funcToCall(); 1596 | if (funcToRun.recurring) { 1597 | this.scheduleFunction(funcToRun.timeoutKey, 1598 | funcToRun.funcToCall, 1599 | funcToRun.millis, 1600 | true); 1601 | } 1602 | } catch(e) { 1603 | } 1604 | } 1605 | this.runFunctionsWithinRange(oldMillis, nowMillis); 1606 | } 1607 | }; 1608 | 1609 | jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 1610 | this.scheduledFunctions[timeoutKey] = { 1611 | runAtMillis: this.nowMillis + millis, 1612 | funcToCall: funcToCall, 1613 | recurring: recurring, 1614 | timeoutKey: timeoutKey, 1615 | millis: millis 1616 | }; 1617 | }; 1618 | 1619 | /** 1620 | * @namespace 1621 | */ 1622 | jasmine.Clock = { 1623 | defaultFakeTimer: new jasmine.FakeTimer(), 1624 | 1625 | reset: function() { 1626 | jasmine.Clock.assertInstalled(); 1627 | jasmine.Clock.defaultFakeTimer.reset(); 1628 | }, 1629 | 1630 | tick: function(millis) { 1631 | jasmine.Clock.assertInstalled(); 1632 | jasmine.Clock.defaultFakeTimer.tick(millis); 1633 | }, 1634 | 1635 | runFunctionsWithinRange: function(oldMillis, nowMillis) { 1636 | jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 1637 | }, 1638 | 1639 | scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 1640 | jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 1641 | }, 1642 | 1643 | useMock: function() { 1644 | if (!jasmine.Clock.isInstalled()) { 1645 | var spec = jasmine.getEnv().currentSpec; 1646 | spec.after(jasmine.Clock.uninstallMock); 1647 | 1648 | jasmine.Clock.installMock(); 1649 | } 1650 | }, 1651 | 1652 | installMock: function() { 1653 | jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 1654 | }, 1655 | 1656 | uninstallMock: function() { 1657 | jasmine.Clock.assertInstalled(); 1658 | jasmine.Clock.installed = jasmine.Clock.real; 1659 | }, 1660 | 1661 | real: { 1662 | setTimeout: jasmine.getGlobal().setTimeout, 1663 | clearTimeout: jasmine.getGlobal().clearTimeout, 1664 | setInterval: jasmine.getGlobal().setInterval, 1665 | clearInterval: jasmine.getGlobal().clearInterval 1666 | }, 1667 | 1668 | assertInstalled: function() { 1669 | if (!jasmine.Clock.isInstalled()) { 1670 | throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 1671 | } 1672 | }, 1673 | 1674 | isInstalled: function() { 1675 | return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; 1676 | }, 1677 | 1678 | installed: null 1679 | }; 1680 | jasmine.Clock.installed = jasmine.Clock.real; 1681 | 1682 | //else for IE support 1683 | jasmine.getGlobal().setTimeout = function(funcToCall, millis) { 1684 | if (jasmine.Clock.installed.setTimeout.apply) { 1685 | return jasmine.Clock.installed.setTimeout.apply(this, arguments); 1686 | } else { 1687 | return jasmine.Clock.installed.setTimeout(funcToCall, millis); 1688 | } 1689 | }; 1690 | 1691 | jasmine.getGlobal().setInterval = function(funcToCall, millis) { 1692 | if (jasmine.Clock.installed.setInterval.apply) { 1693 | return jasmine.Clock.installed.setInterval.apply(this, arguments); 1694 | } else { 1695 | return jasmine.Clock.installed.setInterval(funcToCall, millis); 1696 | } 1697 | }; 1698 | 1699 | jasmine.getGlobal().clearTimeout = function(timeoutKey) { 1700 | if (jasmine.Clock.installed.clearTimeout.apply) { 1701 | return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 1702 | } else { 1703 | return jasmine.Clock.installed.clearTimeout(timeoutKey); 1704 | } 1705 | }; 1706 | 1707 | jasmine.getGlobal().clearInterval = function(timeoutKey) { 1708 | if (jasmine.Clock.installed.clearTimeout.apply) { 1709 | return jasmine.Clock.installed.clearInterval.apply(this, arguments); 1710 | } else { 1711 | return jasmine.Clock.installed.clearInterval(timeoutKey); 1712 | } 1713 | }; 1714 | 1715 | /** 1716 | * @constructor 1717 | */ 1718 | jasmine.MultiReporter = function() { 1719 | this.subReporters_ = []; 1720 | }; 1721 | jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1722 | 1723 | jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1724 | this.subReporters_.push(reporter); 1725 | }; 1726 | 1727 | (function() { 1728 | var functionNames = [ 1729 | "reportRunnerStarting", 1730 | "reportRunnerResults", 1731 | "reportSuiteResults", 1732 | "reportSpecStarting", 1733 | "reportSpecResults", 1734 | "log" 1735 | ]; 1736 | for (var i = 0; i < functionNames.length; i++) { 1737 | var functionName = functionNames[i]; 1738 | jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1739 | return function() { 1740 | for (var j = 0; j < this.subReporters_.length; j++) { 1741 | var subReporter = this.subReporters_[j]; 1742 | if (subReporter[functionName]) { 1743 | subReporter[functionName].apply(subReporter, arguments); 1744 | } 1745 | } 1746 | }; 1747 | })(functionName); 1748 | } 1749 | })(); 1750 | /** 1751 | * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1752 | * 1753 | * @constructor 1754 | */ 1755 | jasmine.NestedResults = function() { 1756 | /** 1757 | * The total count of results 1758 | */ 1759 | this.totalCount = 0; 1760 | /** 1761 | * Number of passed results 1762 | */ 1763 | this.passedCount = 0; 1764 | /** 1765 | * Number of failed results 1766 | */ 1767 | this.failedCount = 0; 1768 | /** 1769 | * Was this suite/spec skipped? 1770 | */ 1771 | this.skipped = false; 1772 | /** 1773 | * @ignore 1774 | */ 1775 | this.items_ = []; 1776 | }; 1777 | 1778 | /** 1779 | * Roll up the result counts. 1780 | * 1781 | * @param result 1782 | */ 1783 | jasmine.NestedResults.prototype.rollupCounts = function(result) { 1784 | this.totalCount += result.totalCount; 1785 | this.passedCount += result.passedCount; 1786 | this.failedCount += result.failedCount; 1787 | }; 1788 | 1789 | /** 1790 | * Adds a log message. 1791 | * @param values Array of message parts which will be concatenated later. 1792 | */ 1793 | jasmine.NestedResults.prototype.log = function(values) { 1794 | this.items_.push(new jasmine.MessageResult(values)); 1795 | }; 1796 | 1797 | /** 1798 | * Getter for the results: message & results. 1799 | */ 1800 | jasmine.NestedResults.prototype.getItems = function() { 1801 | return this.items_; 1802 | }; 1803 | 1804 | /** 1805 | * Adds a result, tracking counts (total, passed, & failed) 1806 | * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1807 | */ 1808 | jasmine.NestedResults.prototype.addResult = function(result) { 1809 | if (result.type != 'log') { 1810 | if (result.items_) { 1811 | this.rollupCounts(result); 1812 | } else { 1813 | this.totalCount++; 1814 | if (result.passed()) { 1815 | this.passedCount++; 1816 | } else { 1817 | this.failedCount++; 1818 | } 1819 | } 1820 | } 1821 | this.items_.push(result); 1822 | }; 1823 | 1824 | /** 1825 | * @returns {Boolean} True if everything below passed 1826 | */ 1827 | jasmine.NestedResults.prototype.passed = function() { 1828 | return this.passedCount === this.totalCount; 1829 | }; 1830 | /** 1831 | * Base class for pretty printing for expectation results. 1832 | */ 1833 | jasmine.PrettyPrinter = function() { 1834 | this.ppNestLevel_ = 0; 1835 | }; 1836 | 1837 | /** 1838 | * Formats a value in a nice, human-readable string. 1839 | * 1840 | * @param value 1841 | */ 1842 | jasmine.PrettyPrinter.prototype.format = function(value) { 1843 | if (this.ppNestLevel_ > 40) { 1844 | throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1845 | } 1846 | 1847 | this.ppNestLevel_++; 1848 | try { 1849 | if (value === jasmine.undefined) { 1850 | this.emitScalar('undefined'); 1851 | } else if (value === null) { 1852 | this.emitScalar('null'); 1853 | } else if (value === jasmine.getGlobal()) { 1854 | this.emitScalar(''); 1855 | } else if (value.jasmineToString) { 1856 | this.emitScalar(value.jasmineToString()); 1857 | } else if (typeof value === 'string') { 1858 | this.emitString(value); 1859 | } else if (jasmine.isSpy(value)) { 1860 | this.emitScalar("spy on " + value.identity); 1861 | } else if (value instanceof RegExp) { 1862 | this.emitScalar(value.toString()); 1863 | } else if (typeof value === 'function') { 1864 | this.emitScalar('Function'); 1865 | } else if (typeof value.nodeType === 'number') { 1866 | this.emitScalar('HTMLNode'); 1867 | } else if (value instanceof Date) { 1868 | this.emitScalar('Date(' + value + ')'); 1869 | } else if (value.__Jasmine_been_here_before__) { 1870 | this.emitScalar(''); 1871 | } else if (jasmine.isArray_(value) || typeof value == 'object') { 1872 | value.__Jasmine_been_here_before__ = true; 1873 | if (jasmine.isArray_(value)) { 1874 | this.emitArray(value); 1875 | } else { 1876 | this.emitObject(value); 1877 | } 1878 | delete value.__Jasmine_been_here_before__; 1879 | } else { 1880 | this.emitScalar(value.toString()); 1881 | } 1882 | } finally { 1883 | this.ppNestLevel_--; 1884 | } 1885 | }; 1886 | 1887 | jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1888 | for (var property in obj) { 1889 | if (property == '__Jasmine_been_here_before__') continue; 1890 | fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && 1891 | obj.__lookupGetter__(property) !== null) : false); 1892 | } 1893 | }; 1894 | 1895 | jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1896 | jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1897 | jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1898 | jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1899 | 1900 | jasmine.StringPrettyPrinter = function() { 1901 | jasmine.PrettyPrinter.call(this); 1902 | 1903 | this.string = ''; 1904 | }; 1905 | jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1906 | 1907 | jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1908 | this.append(value); 1909 | }; 1910 | 1911 | jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1912 | this.append("'" + value + "'"); 1913 | }; 1914 | 1915 | jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1916 | this.append('[ '); 1917 | for (var i = 0; i < array.length; i++) { 1918 | if (i > 0) { 1919 | this.append(', '); 1920 | } 1921 | this.format(array[i]); 1922 | } 1923 | this.append(' ]'); 1924 | }; 1925 | 1926 | jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1927 | var self = this; 1928 | this.append('{ '); 1929 | var first = true; 1930 | 1931 | this.iterateObject(obj, function(property, isGetter) { 1932 | if (first) { 1933 | first = false; 1934 | } else { 1935 | self.append(', '); 1936 | } 1937 | 1938 | self.append(property); 1939 | self.append(' : '); 1940 | if (isGetter) { 1941 | self.append(''); 1942 | } else { 1943 | self.format(obj[property]); 1944 | } 1945 | }); 1946 | 1947 | this.append(' }'); 1948 | }; 1949 | 1950 | jasmine.StringPrettyPrinter.prototype.append = function(value) { 1951 | this.string += value; 1952 | }; 1953 | jasmine.Queue = function(env) { 1954 | this.env = env; 1955 | this.blocks = []; 1956 | this.running = false; 1957 | this.index = 0; 1958 | this.offset = 0; 1959 | this.abort = false; 1960 | }; 1961 | 1962 | jasmine.Queue.prototype.addBefore = function(block) { 1963 | this.blocks.unshift(block); 1964 | }; 1965 | 1966 | jasmine.Queue.prototype.add = function(block) { 1967 | this.blocks.push(block); 1968 | }; 1969 | 1970 | jasmine.Queue.prototype.insertNext = function(block) { 1971 | this.blocks.splice((this.index + this.offset + 1), 0, block); 1972 | this.offset++; 1973 | }; 1974 | 1975 | jasmine.Queue.prototype.start = function(onComplete) { 1976 | this.running = true; 1977 | this.onComplete = onComplete; 1978 | this.next_(); 1979 | }; 1980 | 1981 | jasmine.Queue.prototype.isRunning = function() { 1982 | return this.running; 1983 | }; 1984 | 1985 | jasmine.Queue.LOOP_DONT_RECURSE = true; 1986 | 1987 | jasmine.Queue.prototype.next_ = function() { 1988 | var self = this; 1989 | var goAgain = true; 1990 | 1991 | while (goAgain) { 1992 | goAgain = false; 1993 | 1994 | if (self.index < self.blocks.length && !this.abort) { 1995 | var calledSynchronously = true; 1996 | var completedSynchronously = false; 1997 | 1998 | var onComplete = function () { 1999 | if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 2000 | completedSynchronously = true; 2001 | return; 2002 | } 2003 | 2004 | if (self.blocks[self.index].abort) { 2005 | self.abort = true; 2006 | } 2007 | 2008 | self.offset = 0; 2009 | self.index++; 2010 | 2011 | var now = new Date().getTime(); 2012 | if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 2013 | self.env.lastUpdate = now; 2014 | self.env.setTimeout(function() { 2015 | self.next_(); 2016 | }, 0); 2017 | } else { 2018 | if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 2019 | goAgain = true; 2020 | } else { 2021 | self.next_(); 2022 | } 2023 | } 2024 | }; 2025 | self.blocks[self.index].execute(onComplete); 2026 | 2027 | calledSynchronously = false; 2028 | if (completedSynchronously) { 2029 | onComplete(); 2030 | } 2031 | 2032 | } else { 2033 | self.running = false; 2034 | if (self.onComplete) { 2035 | self.onComplete(); 2036 | } 2037 | } 2038 | } 2039 | }; 2040 | 2041 | jasmine.Queue.prototype.results = function() { 2042 | var results = new jasmine.NestedResults(); 2043 | for (var i = 0; i < this.blocks.length; i++) { 2044 | if (this.blocks[i].results) { 2045 | results.addResult(this.blocks[i].results()); 2046 | } 2047 | } 2048 | return results; 2049 | }; 2050 | 2051 | 2052 | /** 2053 | * Runner 2054 | * 2055 | * @constructor 2056 | * @param {jasmine.Env} env 2057 | */ 2058 | jasmine.Runner = function(env) { 2059 | var self = this; 2060 | self.env = env; 2061 | self.queue = new jasmine.Queue(env); 2062 | self.before_ = []; 2063 | self.after_ = []; 2064 | self.suites_ = []; 2065 | }; 2066 | 2067 | jasmine.Runner.prototype.execute = function() { 2068 | var self = this; 2069 | if (self.env.reporter.reportRunnerStarting) { 2070 | self.env.reporter.reportRunnerStarting(this); 2071 | } 2072 | self.queue.start(function () { 2073 | self.finishCallback(); 2074 | }); 2075 | }; 2076 | 2077 | jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 2078 | beforeEachFunction.typeName = 'beforeEach'; 2079 | this.before_.splice(0,0,beforeEachFunction); 2080 | }; 2081 | 2082 | jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 2083 | afterEachFunction.typeName = 'afterEach'; 2084 | this.after_.splice(0,0,afterEachFunction); 2085 | }; 2086 | 2087 | 2088 | jasmine.Runner.prototype.finishCallback = function() { 2089 | this.env.reporter.reportRunnerResults(this); 2090 | }; 2091 | 2092 | jasmine.Runner.prototype.addSuite = function(suite) { 2093 | this.suites_.push(suite); 2094 | }; 2095 | 2096 | jasmine.Runner.prototype.add = function(block) { 2097 | if (block instanceof jasmine.Suite) { 2098 | this.addSuite(block); 2099 | } 2100 | this.queue.add(block); 2101 | }; 2102 | 2103 | jasmine.Runner.prototype.specs = function () { 2104 | var suites = this.suites(); 2105 | var specs = []; 2106 | for (var i = 0; i < suites.length; i++) { 2107 | specs = specs.concat(suites[i].specs()); 2108 | } 2109 | return specs; 2110 | }; 2111 | 2112 | jasmine.Runner.prototype.suites = function() { 2113 | return this.suites_; 2114 | }; 2115 | 2116 | jasmine.Runner.prototype.topLevelSuites = function() { 2117 | var topLevelSuites = []; 2118 | for (var i = 0; i < this.suites_.length; i++) { 2119 | if (!this.suites_[i].parentSuite) { 2120 | topLevelSuites.push(this.suites_[i]); 2121 | } 2122 | } 2123 | return topLevelSuites; 2124 | }; 2125 | 2126 | jasmine.Runner.prototype.results = function() { 2127 | return this.queue.results(); 2128 | }; 2129 | /** 2130 | * Internal representation of a Jasmine specification, or test. 2131 | * 2132 | * @constructor 2133 | * @param {jasmine.Env} env 2134 | * @param {jasmine.Suite} suite 2135 | * @param {String} description 2136 | */ 2137 | jasmine.Spec = function(env, suite, description) { 2138 | if (!env) { 2139 | throw new Error('jasmine.Env() required'); 2140 | } 2141 | if (!suite) { 2142 | throw new Error('jasmine.Suite() required'); 2143 | } 2144 | var spec = this; 2145 | spec.id = env.nextSpecId ? env.nextSpecId() : null; 2146 | spec.env = env; 2147 | spec.suite = suite; 2148 | spec.description = description; 2149 | spec.queue = new jasmine.Queue(env); 2150 | 2151 | spec.afterCallbacks = []; 2152 | spec.spies_ = []; 2153 | 2154 | spec.results_ = new jasmine.NestedResults(); 2155 | spec.results_.description = description; 2156 | spec.matchersClass = null; 2157 | }; 2158 | 2159 | jasmine.Spec.prototype.getFullName = function() { 2160 | return this.suite.getFullName() + ' ' + this.description + '.'; 2161 | }; 2162 | 2163 | 2164 | jasmine.Spec.prototype.results = function() { 2165 | return this.results_; 2166 | }; 2167 | 2168 | /** 2169 | * All parameters are pretty-printed and concatenated together, then written to the spec's output. 2170 | * 2171 | * Be careful not to leave calls to jasmine.log in production code. 2172 | */ 2173 | jasmine.Spec.prototype.log = function() { 2174 | return this.results_.log(arguments); 2175 | }; 2176 | 2177 | jasmine.Spec.prototype.runs = function (func) { 2178 | var block = new jasmine.Block(this.env, func, this); 2179 | this.addToQueue(block); 2180 | return this; 2181 | }; 2182 | 2183 | jasmine.Spec.prototype.addToQueue = function (block) { 2184 | if (this.queue.isRunning()) { 2185 | this.queue.insertNext(block); 2186 | } else { 2187 | this.queue.add(block); 2188 | } 2189 | }; 2190 | 2191 | /** 2192 | * @param {jasmine.ExpectationResult} result 2193 | */ 2194 | jasmine.Spec.prototype.addMatcherResult = function(result) { 2195 | this.results_.addResult(result); 2196 | }; 2197 | 2198 | jasmine.Spec.prototype.expect = function(actual) { 2199 | var positive = new (this.getMatchersClass_())(this.env, actual, this); 2200 | positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); 2201 | return positive; 2202 | }; 2203 | 2204 | /** 2205 | * Waits a fixed time period before moving to the next block. 2206 | * 2207 | * @deprecated Use waitsFor() instead 2208 | * @param {Number} timeout milliseconds to wait 2209 | */ 2210 | jasmine.Spec.prototype.waits = function(timeout) { 2211 | var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 2212 | this.addToQueue(waitsFunc); 2213 | return this; 2214 | }; 2215 | 2216 | /** 2217 | * Waits for the latchFunction to return true before proceeding to the next block. 2218 | * 2219 | * @param {Function} latchFunction 2220 | * @param {String} optional_timeoutMessage 2221 | * @param {Number} optional_timeout 2222 | */ 2223 | jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { 2224 | var latchFunction_ = null; 2225 | var optional_timeoutMessage_ = null; 2226 | var optional_timeout_ = null; 2227 | 2228 | for (var i = 0; i < arguments.length; i++) { 2229 | var arg = arguments[i]; 2230 | switch (typeof arg) { 2231 | case 'function': 2232 | latchFunction_ = arg; 2233 | break; 2234 | case 'string': 2235 | optional_timeoutMessage_ = arg; 2236 | break; 2237 | case 'number': 2238 | optional_timeout_ = arg; 2239 | break; 2240 | } 2241 | } 2242 | 2243 | var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); 2244 | this.addToQueue(waitsForFunc); 2245 | return this; 2246 | }; 2247 | 2248 | jasmine.Spec.prototype.fail = function (e) { 2249 | var expectationResult = new jasmine.ExpectationResult({ 2250 | passed: false, 2251 | message: e ? jasmine.util.formatException(e) : 'Exception', 2252 | trace: { stack: e.stack } 2253 | }); 2254 | this.results_.addResult(expectationResult); 2255 | }; 2256 | 2257 | jasmine.Spec.prototype.getMatchersClass_ = function() { 2258 | return this.matchersClass || this.env.matchersClass; 2259 | }; 2260 | 2261 | jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 2262 | var parent = this.getMatchersClass_(); 2263 | var newMatchersClass = function() { 2264 | parent.apply(this, arguments); 2265 | }; 2266 | jasmine.util.inherit(newMatchersClass, parent); 2267 | jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); 2268 | this.matchersClass = newMatchersClass; 2269 | }; 2270 | 2271 | jasmine.Spec.prototype.finishCallback = function() { 2272 | this.env.reporter.reportSpecResults(this); 2273 | }; 2274 | 2275 | jasmine.Spec.prototype.finish = function(onComplete) { 2276 | this.removeAllSpies(); 2277 | this.finishCallback(); 2278 | if (onComplete) { 2279 | onComplete(); 2280 | } 2281 | }; 2282 | 2283 | jasmine.Spec.prototype.after = function(doAfter) { 2284 | if (this.queue.isRunning()) { 2285 | this.queue.add(new jasmine.Block(this.env, doAfter, this)); 2286 | } else { 2287 | this.afterCallbacks.unshift(doAfter); 2288 | } 2289 | }; 2290 | 2291 | jasmine.Spec.prototype.execute = function(onComplete) { 2292 | var spec = this; 2293 | if (!spec.env.specFilter(spec)) { 2294 | spec.results_.skipped = true; 2295 | spec.finish(onComplete); 2296 | return; 2297 | } 2298 | 2299 | this.env.reporter.reportSpecStarting(this); 2300 | 2301 | spec.env.currentSpec = spec; 2302 | 2303 | spec.addBeforesAndAftersToQueue(); 2304 | 2305 | spec.queue.start(function () { 2306 | spec.finish(onComplete); 2307 | }); 2308 | }; 2309 | 2310 | jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 2311 | var runner = this.env.currentRunner(); 2312 | var i; 2313 | 2314 | for (var suite = this.suite; suite; suite = suite.parentSuite) { 2315 | for (i = 0; i < suite.before_.length; i++) { 2316 | this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 2317 | } 2318 | } 2319 | for (i = 0; i < runner.before_.length; i++) { 2320 | this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 2321 | } 2322 | for (i = 0; i < this.afterCallbacks.length; i++) { 2323 | this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 2324 | } 2325 | for (suite = this.suite; suite; suite = suite.parentSuite) { 2326 | for (i = 0; i < suite.after_.length; i++) { 2327 | this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 2328 | } 2329 | } 2330 | for (i = 0; i < runner.after_.length; i++) { 2331 | this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 2332 | } 2333 | }; 2334 | 2335 | jasmine.Spec.prototype.explodes = function() { 2336 | throw 'explodes function should not have been called'; 2337 | }; 2338 | 2339 | jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 2340 | if (obj == jasmine.undefined) { 2341 | throw "spyOn could not find an object to spy upon for " + methodName + "()"; 2342 | } 2343 | 2344 | if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { 2345 | throw methodName + '() method does not exist'; 2346 | } 2347 | 2348 | if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 2349 | throw new Error(methodName + ' has already been spied upon'); 2350 | } 2351 | 2352 | var spyObj = jasmine.createSpy(methodName); 2353 | 2354 | this.spies_.push(spyObj); 2355 | spyObj.baseObj = obj; 2356 | spyObj.methodName = methodName; 2357 | spyObj.originalValue = obj[methodName]; 2358 | 2359 | obj[methodName] = spyObj; 2360 | 2361 | return spyObj; 2362 | }; 2363 | 2364 | jasmine.Spec.prototype.removeAllSpies = function() { 2365 | for (var i = 0; i < this.spies_.length; i++) { 2366 | var spy = this.spies_[i]; 2367 | spy.baseObj[spy.methodName] = spy.originalValue; 2368 | } 2369 | this.spies_ = []; 2370 | }; 2371 | 2372 | /** 2373 | * Internal representation of a Jasmine suite. 2374 | * 2375 | * @constructor 2376 | * @param {jasmine.Env} env 2377 | * @param {String} description 2378 | * @param {Function} specDefinitions 2379 | * @param {jasmine.Suite} parentSuite 2380 | */ 2381 | jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 2382 | var self = this; 2383 | self.id = env.nextSuiteId ? env.nextSuiteId() : null; 2384 | self.description = description; 2385 | self.queue = new jasmine.Queue(env); 2386 | self.parentSuite = parentSuite; 2387 | self.env = env; 2388 | self.before_ = []; 2389 | self.after_ = []; 2390 | self.children_ = []; 2391 | self.suites_ = []; 2392 | self.specs_ = []; 2393 | }; 2394 | 2395 | jasmine.Suite.prototype.getFullName = function() { 2396 | var fullName = this.description; 2397 | for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 2398 | fullName = parentSuite.description + ' ' + fullName; 2399 | } 2400 | return fullName; 2401 | }; 2402 | 2403 | jasmine.Suite.prototype.finish = function(onComplete) { 2404 | this.env.reporter.reportSuiteResults(this); 2405 | this.finished = true; 2406 | if (typeof(onComplete) == 'function') { 2407 | onComplete(); 2408 | } 2409 | }; 2410 | 2411 | jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 2412 | beforeEachFunction.typeName = 'beforeEach'; 2413 | this.before_.unshift(beforeEachFunction); 2414 | }; 2415 | 2416 | jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 2417 | afterEachFunction.typeName = 'afterEach'; 2418 | this.after_.unshift(afterEachFunction); 2419 | }; 2420 | 2421 | jasmine.Suite.prototype.results = function() { 2422 | return this.queue.results(); 2423 | }; 2424 | 2425 | jasmine.Suite.prototype.add = function(suiteOrSpec) { 2426 | this.children_.push(suiteOrSpec); 2427 | if (suiteOrSpec instanceof jasmine.Suite) { 2428 | this.suites_.push(suiteOrSpec); 2429 | this.env.currentRunner().addSuite(suiteOrSpec); 2430 | } else { 2431 | this.specs_.push(suiteOrSpec); 2432 | } 2433 | this.queue.add(suiteOrSpec); 2434 | }; 2435 | 2436 | jasmine.Suite.prototype.specs = function() { 2437 | return this.specs_; 2438 | }; 2439 | 2440 | jasmine.Suite.prototype.suites = function() { 2441 | return this.suites_; 2442 | }; 2443 | 2444 | jasmine.Suite.prototype.children = function() { 2445 | return this.children_; 2446 | }; 2447 | 2448 | jasmine.Suite.prototype.execute = function(onComplete) { 2449 | var self = this; 2450 | this.queue.start(function () { 2451 | self.finish(onComplete); 2452 | }); 2453 | }; 2454 | jasmine.WaitsBlock = function(env, timeout, spec) { 2455 | this.timeout = timeout; 2456 | jasmine.Block.call(this, env, null, spec); 2457 | }; 2458 | 2459 | jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 2460 | 2461 | jasmine.WaitsBlock.prototype.execute = function (onComplete) { 2462 | if (jasmine.VERBOSE) { 2463 | this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 2464 | } 2465 | this.env.setTimeout(function () { 2466 | onComplete(); 2467 | }, this.timeout); 2468 | }; 2469 | /** 2470 | * A block which waits for some condition to become true, with timeout. 2471 | * 2472 | * @constructor 2473 | * @extends jasmine.Block 2474 | * @param {jasmine.Env} env The Jasmine environment. 2475 | * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. 2476 | * @param {Function} latchFunction A function which returns true when the desired condition has been met. 2477 | * @param {String} message The message to display if the desired condition hasn't been met within the given time period. 2478 | * @param {jasmine.Spec} spec The Jasmine spec. 2479 | */ 2480 | jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 2481 | this.timeout = timeout || env.defaultTimeoutInterval; 2482 | this.latchFunction = latchFunction; 2483 | this.message = message; 2484 | this.totalTimeSpentWaitingForLatch = 0; 2485 | jasmine.Block.call(this, env, null, spec); 2486 | }; 2487 | jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 2488 | 2489 | jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; 2490 | 2491 | jasmine.WaitsForBlock.prototype.execute = function(onComplete) { 2492 | if (jasmine.VERBOSE) { 2493 | this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); 2494 | } 2495 | var latchFunctionResult; 2496 | try { 2497 | latchFunctionResult = this.latchFunction.apply(this.spec); 2498 | } catch (e) { 2499 | this.spec.fail(e); 2500 | onComplete(); 2501 | return; 2502 | } 2503 | 2504 | if (latchFunctionResult) { 2505 | onComplete(); 2506 | } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { 2507 | var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); 2508 | this.spec.fail({ 2509 | name: 'timeout', 2510 | message: message 2511 | }); 2512 | 2513 | this.abort = true; 2514 | onComplete(); 2515 | } else { 2516 | this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 2517 | var self = this; 2518 | this.env.setTimeout(function() { 2519 | self.execute(onComplete); 2520 | }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 2521 | } 2522 | }; 2523 | 2524 | jasmine.version_= { 2525 | "major": 1, 2526 | "minor": 2, 2527 | "build": 0, 2528 | "revision": 1337005947 2529 | }; 2530 | -------------------------------------------------------------------------------- /test/lib/jasmine-jquery.js: -------------------------------------------------------------------------------- 1 | var readFixtures = function() { 2 | return jasmine.getFixtures().proxyCallTo_('read', arguments) 3 | } 4 | 5 | var preloadFixtures = function() { 6 | jasmine.getFixtures().proxyCallTo_('preload', arguments) 7 | } 8 | 9 | var loadFixtures = function() { 10 | jasmine.getFixtures().proxyCallTo_('load', arguments) 11 | } 12 | 13 | var appendLoadFixtures = function() { 14 | jasmine.getFixtures().proxyCallTo_('appendLoad', arguments) 15 | } 16 | 17 | var setFixtures = function(html) { 18 | jasmine.getFixtures().proxyCallTo_('set', arguments) 19 | } 20 | 21 | var appendSetFixtures = function() { 22 | jasmine.getFixtures().proxyCallTo_('appendSet', arguments) 23 | } 24 | 25 | var sandbox = function(attributes) { 26 | return jasmine.getFixtures().sandbox(attributes) 27 | } 28 | 29 | var spyOnEvent = function(selector, eventName) { 30 | return jasmine.JQuery.events.spyOn(selector, eventName) 31 | } 32 | 33 | var preloadStyleFixtures = function() { 34 | jasmine.getStyleFixtures().proxyCallTo_('preload', arguments) 35 | } 36 | 37 | var loadStyleFixtures = function() { 38 | jasmine.getStyleFixtures().proxyCallTo_('load', arguments) 39 | } 40 | 41 | var appendLoadStyleFixtures = function() { 42 | jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments) 43 | } 44 | 45 | var setStyleFixtures = function(html) { 46 | jasmine.getStyleFixtures().proxyCallTo_('set', arguments) 47 | } 48 | 49 | var appendSetStyleFixtures = function(html) { 50 | jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments) 51 | } 52 | 53 | var loadJSONFixtures = function() { 54 | return jasmine.getJSONFixtures().proxyCallTo_('load', arguments) 55 | } 56 | 57 | var getJSONFixture = function(url) { 58 | return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url] 59 | } 60 | 61 | jasmine.spiedEventsKey = function (selector, eventName) { 62 | return [$(selector).selector, eventName].toString() 63 | } 64 | 65 | jasmine.getFixtures = function() { 66 | return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures() 67 | } 68 | 69 | jasmine.getStyleFixtures = function() { 70 | return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures() 71 | } 72 | 73 | jasmine.Fixtures = function() { 74 | this.containerId = 'jasmine-fixtures' 75 | this.fixturesCache_ = {} 76 | this.fixturesPath = 'spec/javascripts/fixtures' 77 | } 78 | 79 | jasmine.Fixtures.prototype.set = function(html) { 80 | this.cleanUp() 81 | this.createContainer_(html) 82 | } 83 | 84 | jasmine.Fixtures.prototype.appendSet= function(html) { 85 | this.addToContainer_(html) 86 | } 87 | 88 | jasmine.Fixtures.prototype.preload = function() { 89 | this.read.apply(this, arguments) 90 | } 91 | 92 | jasmine.Fixtures.prototype.load = function() { 93 | this.cleanUp() 94 | this.createContainer_(this.read.apply(this, arguments)) 95 | } 96 | 97 | jasmine.Fixtures.prototype.appendLoad = function() { 98 | this.addToContainer_(this.read.apply(this, arguments)) 99 | } 100 | 101 | jasmine.Fixtures.prototype.read = function() { 102 | var htmlChunks = [] 103 | 104 | var fixtureUrls = arguments 105 | for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { 106 | htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex])) 107 | } 108 | 109 | return htmlChunks.join('') 110 | } 111 | 112 | jasmine.Fixtures.prototype.clearCache = function() { 113 | this.fixturesCache_ = {} 114 | } 115 | 116 | jasmine.Fixtures.prototype.cleanUp = function() { 117 | $('#' + this.containerId).remove() 118 | } 119 | 120 | jasmine.Fixtures.prototype.sandbox = function(attributes) { 121 | var attributesToSet = attributes || {} 122 | return $('
').attr(attributesToSet) 123 | } 124 | 125 | jasmine.Fixtures.prototype.createContainer_ = function(html) { 126 | var container 127 | if(html instanceof $) { 128 | container = $('
') 129 | container.html(html) 130 | } else { 131 | container = '
' + html + '
' 132 | } 133 | $('body').append(container) 134 | } 135 | 136 | jasmine.Fixtures.prototype.addToContainer_ = function(html){ 137 | var container = $('body').find('#'+this.containerId).append(html) 138 | if(!container.length){ 139 | this.createContainer_(html) 140 | } 141 | } 142 | 143 | jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) { 144 | if (typeof this.fixturesCache_[url] === 'undefined') { 145 | this.loadFixtureIntoCache_(url) 146 | } 147 | return this.fixturesCache_[url] 148 | } 149 | 150 | jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) { 151 | var url = this.makeFixtureUrl_(relativeUrl) 152 | var request = $.ajax({ 153 | type: "GET", 154 | url: url + "?" + new Date().getTime(), 155 | async: false 156 | }) 157 | this.fixturesCache_[relativeUrl] = request.responseText 158 | } 159 | 160 | jasmine.Fixtures.prototype.makeFixtureUrl_ = function(relativeUrl){ 161 | return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl 162 | } 163 | 164 | jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) { 165 | return this[methodName].apply(this, passedArguments) 166 | } 167 | 168 | 169 | jasmine.StyleFixtures = function() { 170 | this.fixturesCache_ = {} 171 | this.fixturesNodes_ = [] 172 | this.fixturesPath = 'spec/javascripts/fixtures' 173 | } 174 | 175 | jasmine.StyleFixtures.prototype.set = function(css) { 176 | this.cleanUp() 177 | this.createStyle_(css) 178 | } 179 | 180 | jasmine.StyleFixtures.prototype.appendSet = function(css) { 181 | this.createStyle_(css) 182 | } 183 | 184 | jasmine.StyleFixtures.prototype.preload = function() { 185 | this.read_.apply(this, arguments) 186 | } 187 | 188 | jasmine.StyleFixtures.prototype.load = function() { 189 | this.cleanUp() 190 | this.createStyle_(this.read_.apply(this, arguments)) 191 | } 192 | 193 | jasmine.StyleFixtures.prototype.appendLoad = function() { 194 | this.createStyle_(this.read_.apply(this, arguments)) 195 | } 196 | 197 | jasmine.StyleFixtures.prototype.cleanUp = function() { 198 | while(this.fixturesNodes_.length) { 199 | this.fixturesNodes_.pop().remove() 200 | } 201 | } 202 | 203 | jasmine.StyleFixtures.prototype.createStyle_ = function(html) { 204 | var styleText = $('
').html(html).text(), 205 | style = $('') 206 | 207 | this.fixturesNodes_.push(style) 208 | 209 | $('head').append(style) 210 | } 211 | 212 | jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache 213 | 214 | jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read 215 | 216 | jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_ 217 | 218 | jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_ 219 | 220 | jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_ 221 | 222 | jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_ 223 | 224 | jasmine.getJSONFixtures = function() { 225 | return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures() 226 | } 227 | 228 | jasmine.JSONFixtures = function() { 229 | this.fixturesCache_ = {} 230 | this.fixturesPath = 'spec/javascripts/fixtures/json' 231 | } 232 | 233 | jasmine.JSONFixtures.prototype.load = function() { 234 | this.read.apply(this, arguments) 235 | return this.fixturesCache_ 236 | } 237 | 238 | jasmine.JSONFixtures.prototype.read = function() { 239 | var fixtureUrls = arguments 240 | for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { 241 | this.getFixtureData_(fixtureUrls[urlIndex]) 242 | } 243 | return this.fixturesCache_ 244 | } 245 | 246 | jasmine.JSONFixtures.prototype.clearCache = function() { 247 | this.fixturesCache_ = {} 248 | } 249 | 250 | jasmine.JSONFixtures.prototype.getFixtureData_ = function(url) { 251 | this.loadFixtureIntoCache_(url) 252 | return this.fixturesCache_[url] 253 | } 254 | 255 | jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) { 256 | var self = this 257 | var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl 258 | $.ajax({ 259 | async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded 260 | cache: false, 261 | dataType: 'json', 262 | url: url, 263 | success: function(data) { 264 | self.fixturesCache_[relativeUrl] = data 265 | }, 266 | error: function(jqXHR, status, errorThrown) { 267 | throw Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')') 268 | } 269 | }) 270 | } 271 | 272 | jasmine.JSONFixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) { 273 | return this[methodName].apply(this, passedArguments) 274 | } 275 | 276 | jasmine.JQuery = function() {} 277 | 278 | jasmine.JQuery.browserTagCaseIndependentHtml = function(html) { 279 | return $('
').append(html).html() 280 | } 281 | 282 | jasmine.JQuery.elementToString = function(element) { 283 | var domEl = $(element).get(0) 284 | if (domEl == undefined || domEl.cloneNode) 285 | return $('
').append($(element).clone()).html() 286 | else 287 | return element.toString() 288 | } 289 | 290 | jasmine.JQuery.matchersClass = {} 291 | 292 | !function(namespace) { 293 | var data = { 294 | spiedEvents: {}, 295 | handlers: [] 296 | } 297 | 298 | namespace.events = { 299 | spyOn: function(selector, eventName) { 300 | var handler = function(e) { 301 | data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = e 302 | } 303 | $(selector).bind(eventName, handler) 304 | data.handlers.push(handler) 305 | return { 306 | selector: selector, 307 | eventName: eventName, 308 | handler: handler, 309 | reset: function(){ 310 | delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] 311 | } 312 | } 313 | }, 314 | 315 | wasTriggered: function(selector, eventName) { 316 | return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) 317 | }, 318 | 319 | wasPrevented: function(selector, eventName) { 320 | return data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)].isDefaultPrevented() 321 | }, 322 | 323 | cleanUp: function() { 324 | data.spiedEvents = {} 325 | data.handlers = [] 326 | } 327 | } 328 | }(jasmine.JQuery) 329 | 330 | !function(){ 331 | var jQueryMatchers = { 332 | toHaveClass: function(className) { 333 | return this.actual.hasClass(className) 334 | }, 335 | 336 | toHaveCss: function(css){ 337 | for (var prop in css){ 338 | if (this.actual.css(prop) !== css[prop]) return false 339 | } 340 | return true 341 | }, 342 | 343 | toBeVisible: function() { 344 | return this.actual.is(':visible') 345 | }, 346 | 347 | toBeHidden: function() { 348 | return this.actual.is(':hidden') 349 | }, 350 | 351 | toBeSelected: function() { 352 | return this.actual.is(':selected') 353 | }, 354 | 355 | toBeChecked: function() { 356 | return this.actual.is(':checked') 357 | }, 358 | 359 | toBeEmpty: function() { 360 | return this.actual.is(':empty') 361 | }, 362 | 363 | toExist: function() { 364 | return $(document).find(this.actual).length 365 | }, 366 | 367 | toHaveAttr: function(attributeName, expectedAttributeValue) { 368 | return hasProperty(this.actual.attr(attributeName), expectedAttributeValue) 369 | }, 370 | 371 | toHaveProp: function(propertyName, expectedPropertyValue) { 372 | return hasProperty(this.actual.prop(propertyName), expectedPropertyValue) 373 | }, 374 | 375 | toHaveId: function(id) { 376 | return this.actual.attr('id') == id 377 | }, 378 | 379 | toHaveHtml: function(html) { 380 | return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html) 381 | }, 382 | 383 | toContainHtml: function(html){ 384 | var actualHtml = this.actual.html() 385 | var expectedHtml = jasmine.JQuery.browserTagCaseIndependentHtml(html) 386 | return (actualHtml.indexOf(expectedHtml) >= 0) 387 | }, 388 | 389 | toHaveText: function(text) { 390 | var trimmedText = $.trim(this.actual.text()) 391 | if (text && $.isFunction(text.test)) { 392 | return text.test(trimmedText) 393 | } else { 394 | return trimmedText == text 395 | } 396 | }, 397 | 398 | toHaveValue: function(value) { 399 | return this.actual.val() == value 400 | }, 401 | 402 | toHaveData: function(key, expectedValue) { 403 | return hasProperty(this.actual.data(key), expectedValue) 404 | }, 405 | 406 | toBe: function(selector) { 407 | return this.actual.is(selector) 408 | }, 409 | 410 | toContain: function(selector) { 411 | return this.actual.find(selector).length 412 | }, 413 | 414 | toBeDisabled: function(selector){ 415 | return this.actual.is(':disabled') 416 | }, 417 | 418 | toBeFocused: function(selector) { 419 | return this.actual.is(':focus') 420 | }, 421 | 422 | toHandle: function(event) { 423 | 424 | var events = $._data(this.actual.get(0), "events") 425 | 426 | if(!events || !event || typeof event !== "string") { 427 | return false 428 | } 429 | 430 | var namespaces = event.split(".") 431 | var eventType = namespaces.shift() 432 | var sortedNamespaces = namespaces.slice(0).sort() 433 | var namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") 434 | 435 | if(events[eventType] && namespaces.length) { 436 | for(var i = 0; i < events[eventType].length; i++) { 437 | var namespace = events[eventType][i].namespace 438 | if(namespaceRegExp.test(namespace)) { 439 | return true 440 | } 441 | } 442 | } else { 443 | return events[eventType] && events[eventType].length > 0 444 | } 445 | }, 446 | 447 | // tests the existence of a specific event binding + handler 448 | toHandleWith: function(eventName, eventHandler) { 449 | var stack = $._data(this.actual.get(0), "events")[eventName] 450 | for (var i = 0; i < stack.length; i++) { 451 | if (stack[i].handler == eventHandler) return true 452 | } 453 | return false 454 | } 455 | } 456 | 457 | var hasProperty = function(actualValue, expectedValue) { 458 | if (expectedValue === undefined) return actualValue !== undefined 459 | return actualValue == expectedValue 460 | } 461 | 462 | var bindMatcher = function(methodName) { 463 | var builtInMatcher = jasmine.Matchers.prototype[methodName] 464 | 465 | jasmine.JQuery.matchersClass[methodName] = function() { 466 | if (this.actual 467 | && (this.actual instanceof $ 468 | || jasmine.isDomNode(this.actual))) { 469 | this.actual = $(this.actual) 470 | var result = jQueryMatchers[methodName].apply(this, arguments) 471 | var element 472 | if (this.actual.get && (element = this.actual.get()[0]) && !$.isWindow(element) && element.tagName !== "HTML") 473 | this.actual = jasmine.JQuery.elementToString(this.actual) 474 | return result 475 | } 476 | 477 | if (builtInMatcher) { 478 | return builtInMatcher.apply(this, arguments) 479 | } 480 | 481 | return false 482 | } 483 | } 484 | 485 | for(var methodName in jQueryMatchers) { 486 | bindMatcher(methodName) 487 | } 488 | }() 489 | 490 | beforeEach(function() { 491 | this.addMatchers(jasmine.JQuery.matchersClass) 492 | this.addMatchers({ 493 | toHaveBeenTriggeredOn: function(selector) { 494 | this.message = function() { 495 | return [ 496 | "Expected event " + this.actual + " to have been triggered on " + selector, 497 | "Expected event " + this.actual + " not to have been triggered on " + selector 498 | ] 499 | } 500 | return jasmine.JQuery.events.wasTriggered(selector, this.actual) 501 | } 502 | }) 503 | this.addMatchers({ 504 | toHaveBeenTriggered: function(){ 505 | var eventName = this.actual.eventName, 506 | selector = this.actual.selector 507 | this.message = function() { 508 | return [ 509 | "Expected event " + eventName + " to have been triggered on " + selector, 510 | "Expected event " + eventName + " not to have been triggered on " + selector 511 | ] 512 | } 513 | return jasmine.JQuery.events.wasTriggered(selector, eventName) 514 | } 515 | }) 516 | this.addMatchers({ 517 | toHaveBeenPreventedOn: function(selector) { 518 | this.message = function() { 519 | return [ 520 | "Expected event " + this.actual + " to have been prevented on " + selector, 521 | "Expected event " + this.actual + " not to have been prevented on " + selector 522 | ] 523 | } 524 | return jasmine.JQuery.events.wasPrevented(selector, this.actual) 525 | } 526 | }) 527 | this.addMatchers({ 528 | toHaveBeenPrevented: function() { 529 | var eventName = this.actual.eventName, 530 | selector = this.actual.selector 531 | this.message = function() { 532 | return [ 533 | "Expected event " + eventName + " to have been prevented on " + selector, 534 | "Expected event " + eventName + " not to have been prevented on " + selector 535 | ] 536 | } 537 | return jasmine.JQuery.events.wasPrevented(selector, eventName) 538 | } 539 | }) 540 | }) 541 | 542 | afterEach(function() { 543 | jasmine.getFixtures().cleanUp() 544 | jasmine.getStyleFixtures().cleanUp() 545 | jasmine.JQuery.events.cleanUp() 546 | }) -------------------------------------------------------------------------------- /test/spec/SpecHelper.js: -------------------------------------------------------------------------------- 1 | var originalSelect; 2 | var switchyContainer; 3 | var switchyBar; 4 | var switchySlider; 5 | 6 | beforeEach(function() { 7 | $('').appendTo('body'); 8 | for (var i=1; i <= 3; i++) { 9 | $('').appendTo($("#original-select")); 10 | }; 11 | originalSelect = $("#original-select"); 12 | }); 13 | 14 | afterEach(function () { 15 | $("#original-select, .switchy-container").remove(); 16 | }); 17 | -------------------------------------------------------------------------------- /test/spec/SwitchySpec.js: -------------------------------------------------------------------------------- 1 | describe("Switchy", function() { 2 | 3 | describe('init', function(){ 4 | 5 | it ('should be chainable', function(){ 6 | originalSelect.switchy().addClass('chainable'); 7 | expect(originalSelect.hasClass('chainable')).toBeTruthy(); 8 | }); 9 | 10 | describe('without options', function(){ 11 | 12 | beforeEach(function() { 13 | originalSelect.switchy(); 14 | switchyContainer = originalSelect.next(); 15 | switchyBar = switchyContainer.children(); 16 | switchySlider = switchyBar.children(); 17 | }); 18 | 19 | it('should hide the original select', function(){ 20 | expect(originalSelect.css('position')).toBe('absolute'); 21 | expect(originalSelect.css('left')).toBe('-9999px'); 22 | }); 23 | 24 | it('should create a container', function(){ 25 | expect(switchyContainer).toBe('div.switchy-container'); 26 | }); 27 | 28 | it ('should create a bar', function(){ 29 | expect(switchyContainer).toContain('div.switchy-bar'); 30 | }); 31 | 32 | it ('should create a slider', function(){ 33 | expect(switchyContainer).toContain('div.switchy-slider'); 34 | }); 35 | 36 | }); 37 | 38 | describe('without a selected option', function(){ 39 | it ('should init the slider position to CSS value', function(){ 40 | expect(switchySlider.position().left).toBe(0); 41 | }); 42 | }); 43 | 44 | describe('with a selected option', function(){ 45 | it ('should init the slider position to the right one', function(){ 46 | originalSelect.children('option').eq(1).prop('selected', true); 47 | originalSelect.switchy(); 48 | switchyContainer = originalSelect.next(); 49 | switchyBar = switchyContainer.children(); 50 | switchySlider = switchyBar.children(); 51 | 52 | var sliderPosition = (switchyBar.innerWidth() / (originalSelect.children('option').length - 1)) - (switchySlider.outerWidth(true) / 2); 53 | expect(switchySlider.position().left).toBe(sliderPosition); 54 | }); 55 | }); 56 | 57 | }); 58 | }); --------------------------------------------------------------------------------