├── .gitignore ├── MIT-LICENSE ├── README.md ├── emile.js ├── emile.min.js ├── examples └── slide.html ├── index.html ├── test.js └── test ├── emile.html └── evidence.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Thomas Fuchs http://script.aculo.us/thomas 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Émile 2 | ===== 3 | 4 | #### Stand-alone CSS animation JavaScript mini-framework #### 5 | 6 | * Doesn't need a JavaScript framework 7 | * Full set of CSS properties for animation (length-based and colors) 8 | * Easing and callbacks 9 | * Less than 50 lines of code 10 | 11 | Get updates on Twitter: 12 | 13 | Also see the video of my presentation at Fronteers 2009: 14 | 15 | 16 | ### Targeted platforms ### 17 | 18 | Émile currently targets the following platforms: 19 | 20 | * Microsoft Internet Explorer for Windows, version 6.0 and higher 21 | * Mozilla Firefox 1.5 and higher 22 | * Apple Safari 2.0.4 and higher 23 | * Opera 9.25 and higher 24 | * Chrome 1.0 and higher 25 | 26 | ### Documentation ### 27 | 28 | One method: 29 | 30 | emile(element, style, options, after) 31 | 32 | **Parameters** 33 | 34 | * element (id | element) - element to which the animation will be applied 35 | * style (String) - style which will be applied after the animation is finished 36 | * for some properties you'll need to define defaults on your page's css 37 | * options (Object) - optional; the following options are available 38 | * duration (Number) - duration of the animation in milliseconds 39 | * after (Function) - a function which will be executed after the animation is finished 40 | * easing (Function) - easing function for the animation. Receives one argument pos which indicates position in time between animation's start and end 41 | * after (Function) - optional; a callback that will be excuted after everything is done (in addition to options.after) 42 | 43 | ### License ### 44 | 45 | Émile is is licensed under the terms of the MIT License, see the included MIT-LICENSE file. 46 | Émile borrows its name from . 47 | -------------------------------------------------------------------------------- /emile.js: -------------------------------------------------------------------------------- 1 | // emile.js (c) 2009 Thomas Fuchs 2 | // Licensed under the terms of the MIT license. 3 | 4 | (function(emile, container){ 5 | var parseEl = document.createElement('div'), 6 | props = ('backgroundColor borderBottomColor borderBottomWidth borderLeftColor borderLeftWidth '+ 7 | 'borderRightColor borderRightWidth borderSpacing borderTopColor borderTopWidth bottom color fontSize '+ 8 | 'fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop maxHeight '+ 9 | 'maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft '+ 10 | 'paddingRight paddingTop right textIndent top width wordSpacing zIndex').split(' '); 11 | 12 | function interpolate(source,target,pos){ return (source+(target-source)*pos).toFixed(3); } 13 | function s(str, p, c){ return str.substr(p,c||1); } 14 | function color(source,target,pos){ 15 | var i = 2, j, c, tmp, v = [], r = []; 16 | while(j=3,c=arguments[i-1],i--) 17 | if(s(c,0)=='r') { c = c.match(/\d+/g); while(j--) v.push(~~c[j]); } else { 18 | if(c.length==4) c='#'+s(c,1)+s(c,1)+s(c,2)+s(c,2)+s(c,3)+s(c,3); 19 | while(j--) v.push(parseInt(s(c,1+j*2,2), 16)); } 20 | while(j--) { tmp = ~~(v[j+3]+(v[j]-v[j+3])*pos); r.push(tmp<0?0:tmp>255?255:tmp); } 21 | return 'rgb('+r.join(',')+')'; 22 | } 23 | 24 | function parse(prop){ 25 | var p = parseFloat(prop), q = prop.replace(/^[\-\d\.]+/,''); 26 | return isNaN(p) ? { v: q, f: color, u: ''} : { v: p, f: interpolate, u: q }; 27 | } 28 | 29 | function normalize(style){ 30 | var css, rules = {}, i = props.length, v; 31 | parseEl.innerHTML = '
'; 32 | css = parseEl.childNodes[0].style; 33 | while(i--) if(v = css[props[i]]) rules[props[i]] = parse(v); 34 | return rules; 35 | } 36 | 37 | container[emile] = function(el, style, opts, after){ 38 | el = typeof el == 'string' ? document.getElementById(el) : el; 39 | opts = opts || {}; 40 | var target = normalize(style), comp = el.currentStyle ? el.currentStyle : getComputedStyle(el, null), 41 | prop, current = {}, start = +new Date, dur = opts.duration||200, finish = start+dur, interval, 42 | easing = opts.easing || function(pos){ return (-Math.cos(pos*Math.PI)/2) + 0.5; }; 43 | for(prop in target) current[prop] = parse(comp[prop]); 44 | interval = setInterval(function(){ 45 | var time = +new Date, pos = time>finish ? 1 : (time-start)/dur; 46 | for(prop in target) 47 | el.style[prop] = target[prop].f(current[prop].v,target[prop].v,easing(pos)) + target[prop].u; 48 | if(time>finish) { clearInterval(interval); opts.after && opts.after(); after && setTimeout(after,1); } 49 | },10); 50 | } 51 | })('emile', this); -------------------------------------------------------------------------------- /emile.min.js: -------------------------------------------------------------------------------- 1 | (function(f,a){var h=document.createElement("div"),g=("backgroundColor borderBottomColor borderBottomWidth borderLeftColor borderLeftWidth borderRightColor borderRightWidth borderSpacing borderTopColor borderTopWidth bottom color fontSize fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop maxHeight maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft paddingRight paddingTop right textIndent top width wordSpacing zIndex").split(" ");function e(j,k,l){return(j+(k-j)*l).toFixed(3)}function i(k,j,l){return k.substr(j,l||1)}function c(l,p,s){var n=2,m,q,o,t=[],k=[];while(m=3,q=arguments[n-1],n--){if(i(q,0)=="r"){q=q.match(/\d+/g);while(m--){t.push(~~q[m])}}else{if(q.length==4){q="#"+i(q,1)+i(q,1)+i(q,2)+i(q,2)+i(q,3)+i(q,3)}while(m--){t.push(parseInt(i(q,1+m*2,2),16))}}}while(m--){o=~~(t[m+3]+(t[m]-t[m+3])*s);k.push(o<0?0:o>255?255:o)}return"rgb("+k.join(",")+")"}function b(l){var k=parseFloat(l),j=l.replace(/^[\-\d\.]+/,"");return isNaN(k)?{v:j,f:c,u:""}:{v:k,f:e,u:j}}function d(m){var l,n={},k=g.length,j;h.innerHTML='
';l=h.childNodes[0].style;while(k--){if(j=l[g[k]]){n[g[k]]=b(j)}}return n}a[f]=function(p,m,j){p=typeof p=="string"?document.getElementById(p):p;j=j||{};var r=d(m),q=p.currentStyle?p.currentStyle:getComputedStyle(p,null),l,s={},n=+new Date,k=j.duration||200,u=n+k,o,t=j.easing||function(v){return(-Math.cos(v*Math.PI)/2)+0.5};for(l in r){s[l]=b(q[l])}o=setInterval(function(){var v=+new Date,w=v>u?1:(v-n)/k;for(l in r){p.style[l]=r[l].f(s[l].v,r[l].v,t(w))+r[l].u}if(v>u){clearInterval(o);j.after&&j.after()}},10)}})("emile",this); -------------------------------------------------------------------------------- /examples/slide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Slide Example 5 | 6 | 38 | 39 | 40 |
41 |
42 |

Slip'n'Slide Demo!

43 | 44 | 45 | 46 | 47 |
48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
test
4 |
test
5 | 6 | 28 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | (function(emile,container){var parseEl=document.createElement("div"),props=("backgroundColor borderBottomColor borderBottomWidth borderLeftColor borderLeftWidth borderRightColor borderRightWidth borderSpacing borderTopColor borderTopWidth bottom color fontSize fontWeight height left letterSpacing lineHeight marginBottom marginLeft marginRight marginTop maxHeight maxWidth minHeight minWidth opacity outlineColor outlineOffset outlineWidth paddingBottom paddingLeft paddingRight paddingTop right textIndent top width wordSpacing zIndex").split(" ");function interpolate(source,target,pos){return(source+(target-source)*pos).toFixed(3)}function s(str,p,c){return str.substr(p,c||1)}function color(source,target,pos){var i=2,j,c,tmp,v=[],r=[];while(j=3,c=arguments[i-1],i--){if(s(c,0)=="r"){c=c.match(/\d+/g);while(j--){v.push(~~c[j])}}else{if(c.length==4){c="#"+s(c,1)+s(c,1)+s(c,2)+s(c,2)+s(c,3)+s(c,3)}while(j--){v.push(parseInt(s(c,1+j*2,2),16))}}}while(j--){tmp=~~(v[j+3]+(v[j]-v[j+3])*pos);r.push(tmp<0?0:tmp>255?255:tmp)}return"rgb("+r.join(",")+")"}function parse(prop){var p=parseFloat(prop),q=prop.replace(/^[\-\d\.]+/,"");return isNaN(p)?{v:q,f:color,u:""}:{v:p,f:interpolate,u:q}}function normalize(style){var css,rules={},i=props.length,v;parseEl.innerHTML='
';css=parseEl.childNodes[0].style;while(i--){if(v=css[props[i]]){rules[props[i]]=parse(v)}}return rules}container[emile]=function(el,style,opts){el=typeof el=="string"?document.getElementById(el):el;opts=opts||{};var target=normalize(style),comp=el.currentStyle?el.currentStyle:getComputedStyle(el,null),prop,current={},start=+new Date,dur=opts.duration||200,finish=start+dur,interval,easing=opts.easing||function(pos){return(-Math.cos(pos*Math.PI)/2)+0.5};for(prop in target){current[prop]=parse(comp[prop])}interval=setInterval(function(){var time=+new Date,pos=time>finish?1:(time-start)/dur;for(prop in target){el.style[prop]=target[prop].f(current[prop].v,target[prop].v,easing(pos))+target[prop].u}if(time>finish){clearInterval(interval);opts.after&&opts.after()}},10)}})("emile",this); -------------------------------------------------------------------------------- /test/emile.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Émile unit tests 6 | 7 | 8 | 9 | 10 | 11 |
test1
12 |
test2
13 |
test3
14 | 68 | 69 | -------------------------------------------------------------------------------- /test/evidence.js: -------------------------------------------------------------------------------- 1 | /* evidence.js, version 0.6 2 | * 3 | * Copyright (c) 2009 Tobie Langel (http://tobielangel.com) 4 | * 5 | * evidence.js is freely distributable under the terms of an MIT-style license. 6 | *--------------------------------------------------------------------------*/ 7 | 8 | (function(global) { 9 | var originalEvidence = global.Evidence, 10 | originalOnload = global.onload; 11 | 12 | function Evidence() { 13 | TestCase.extend.apply(TestCase, arguments); 14 | } 15 | 16 | function noConflict() { 17 | global.Evidence = originalEvidence; 18 | return Evidence; 19 | } 20 | 21 | Evidence.noConflict = noConflict; 22 | Evidence.VERSION = '0.6'; 23 | 24 | var FILE_REGEXP = /.*?\/(\w+\.html)(.*)/; 25 | 26 | function getNameFromFile() { 27 | return (global.location || '').toString().replace(FILE_REGEXP, '$1'); 28 | } 29 | 30 | function chain(subclass, superclass) { 31 | function Subclass() {} 32 | Subclass.prototype = superclass.prototype; 33 | subclass.prototype = new Subclass(); 34 | subclass.prototype.constructor = subclass; 35 | return subclass; 36 | } 37 | 38 | function defer(block, context) { 39 | if ('setTimeout' in global) { 40 | window.setTimeout(function() { 41 | block.call(context); 42 | }, 10); 43 | } else { 44 | block.call(context); 45 | } 46 | } 47 | function AssertionSkippedError(message) { 48 | this.message = message; 49 | } 50 | 51 | AssertionSkippedError.displayName = 'AssertionSkippedError'; 52 | 53 | (function(p) { 54 | p.name = 'AssertionSkippedError'; 55 | })(AssertionSkippedError.prototype); 56 | Evidence.AssertionSkippedError = AssertionSkippedError; 57 | function AssertionFailedError(message, template, args) { 58 | this.message = message; 59 | this.template = template || ''; 60 | this.args = args; 61 | } 62 | 63 | AssertionFailedError.displayName = 'AssertionFailedError'; 64 | 65 | (function(p) { 66 | p.name = 'AssertionFailedError'; 67 | })(AssertionFailedError.prototype); 68 | Evidence.AssertionFailedError = AssertionFailedError; 69 | function AssertionMessage(message, template, args) { 70 | this.message = message.replace(/%/g, '%%'); 71 | this.template = template || ''; 72 | this.args = args; 73 | } 74 | 75 | AssertionMessage.displayName = 'AssertionMessage'; 76 | 77 | (function(p) { 78 | function toString() { 79 | return UI.printf(this.message + this.template, this.args); 80 | } 81 | p.toString = toString; 82 | })(AssertionMessage.prototype); 83 | Evidence.AssertionMessage = AssertionMessage; 84 | 85 | var Assertions = (function() { 86 | function _assertExpression(expression, message, template) { 87 | /*for (var i=0; i < 100000; i++) { 88 | (function(){})() 89 | }*/ 90 | if (expression) { 91 | this.addAssertion(); 92 | } else { 93 | var args = Array.prototype.slice.call(arguments, 3); 94 | throw new AssertionFailedError(message, template, args); 95 | } 96 | } 97 | 98 | function skip(message) { 99 | throw new AssertionSkippedError(message || 'Skipped!'); 100 | } 101 | 102 | function fail(message) { 103 | this._assertExpression(false, message || 'Flunked!'); 104 | } 105 | 106 | function assert(test, message) { 107 | this._assertExpression( 108 | !!test, 109 | message || 'Failed assertion.', 110 | 'Expected %o to evaluate to true.', test 111 | ); 112 | } 113 | 114 | function refute(test, message) { 115 | this._assertExpression( 116 | !test, 117 | message || 'Failed refutation.', 118 | 'Expected %o to evaluate to false.', test 119 | ); 120 | } 121 | 122 | function assertTrue(test, message) { 123 | this._assertExpression( 124 | (test === true), 125 | message || 'Failed assertion.', 126 | 'Expected %o to be true.', test 127 | ); 128 | } 129 | 130 | function refuteTrue(test, message) { 131 | this._assertExpression( 132 | (test !== true), 133 | message || 'Failed refutation.', 134 | 'Expected %o to not be true.', test 135 | ); 136 | } 137 | 138 | function assertNull(test, message) { 139 | this._assertExpression( 140 | (test === null), 141 | message || 'Failed assertion.', 142 | 'Expected %o to be null.', test 143 | ); 144 | } 145 | 146 | function refuteNull(test, message) { 147 | this._assertExpression( 148 | (test !== null), 149 | message || 'Failed refutation.', 150 | 'Expected %o to not be null.', test 151 | ); 152 | } 153 | 154 | function assertUndefined(test, message) { 155 | this._assertExpression( 156 | (typeof test === 'undefined'), 157 | message || 'Failed assertion.', 158 | 'Expected %o to be undefined.', test 159 | ); 160 | } 161 | 162 | function refuteUndefined(test, message) { 163 | this._assertExpression( 164 | (typeof test !== 'undefined'), 165 | message || 'Failed refutation.', 166 | 'Expected %o to not be undefined.', test 167 | ); 168 | } 169 | 170 | function assertFalse(test, message) { 171 | this._assertExpression( 172 | (test === false), 173 | message || 'Failed assertion.', 174 | 'Expected %o to be false.', test 175 | ); 176 | } 177 | 178 | function refuteFalse(test, message) { 179 | this._assertExpression( 180 | (test !== false), 181 | message || 'Failed refutation.', 182 | 'Expected %o to not be false.', test 183 | ); 184 | } 185 | 186 | function assertEqual(expected, actual, message) { 187 | this._assertExpression( 188 | (expected == actual), 189 | message || 'Failed assertion.', 190 | 'Expected %o to be == to %o.', actual, expected 191 | ); 192 | } 193 | 194 | function refuteEqual(expected, actual, message) { 195 | this._assertExpression( 196 | (expected != actual), 197 | message || 'Failed refutation.', 198 | 'Expected %o to be != to %o.', actual, expected 199 | ); 200 | } 201 | 202 | function assertIdentical(expected, actual, message) { 203 | this._assertExpression( 204 | (expected === actual), 205 | message || 'Failed assertion.', 206 | 'Expected %o to be === to %o.', actual, expected 207 | ); 208 | } 209 | 210 | function refuteIdentical(expected, actual, message) { 211 | this._assertExpression( 212 | (expected !== actual), 213 | message || 'Failed refutation.', 214 | 'Expected %o to be !== to %o.', actual, expected 215 | ); 216 | } 217 | 218 | function assertIn(property, object, message) { 219 | this._assertExpression( 220 | (property in object), 221 | message || 'Failed assertion.', 222 | 'Expected "%s" to be a property of %o.', property, object 223 | ); 224 | } 225 | 226 | function refuteIn(property, object, message) { 227 | this._assertExpression( 228 | !(property in object), 229 | message || 'Failed refutation.', 230 | 'Expected "%s" to not be a property of %o.', property, object 231 | ); 232 | } 233 | 234 | return { 235 | _assertExpression: _assertExpression, 236 | skip: skip, 237 | assert: assert, 238 | refute: refute, 239 | assertNot: refute, 240 | assertTrue: assertTrue, 241 | assertNull: assertNull, 242 | assertUndefined: assertUndefined, 243 | assertFalse: assertFalse, 244 | assertIdentical: assertIdentical, 245 | refuteIdentical: refuteIdentical, 246 | assertEqual: assertEqual, 247 | refuteEqual: refuteEqual, 248 | assertIn: assertIn, 249 | refuteIn: refuteIn, 250 | fail: fail, 251 | flunk: fail 252 | }; 253 | })(); 254 | Evidence.Assertions = Assertions; 255 | function TestCase(methodName) { 256 | this._methodName = methodName; 257 | this.name = methodName; 258 | } 259 | 260 | (function() { 261 | function extend(name, methods) { 262 | function TestCaseSubclass(methodName) { 263 | TestCase.call(this, methodName); 264 | } 265 | 266 | if (!methods) { 267 | methods = name; 268 | name = getNameFromFile(); 269 | } 270 | 271 | chain(TestCaseSubclass, this); 272 | TestCaseSubclass.displayName = name; 273 | TestCaseSubclass.extend = extend; 274 | 275 | for(var prop in methods) { 276 | TestCaseSubclass.prototype[prop] = methods[prop]; 277 | } 278 | TestCase.subclasses.push(TestCaseSubclass); 279 | return TestCaseSubclass; 280 | } 281 | 282 | function AssertionsMixin() {} 283 | AssertionsMixin.prototype = Assertions; 284 | TestCase.prototype = new AssertionsMixin(); 285 | TestCase.constructor = TestCase; 286 | 287 | TestCase.displayName = 'TestCase'; 288 | TestCase.extend = extend; 289 | TestCase.subclasses = []; 290 | TestCase.defaultTimeout = 10000; 291 | })(); 292 | 293 | (function(p) { 294 | function run(result) { 295 | if (result) { this._result = result; } 296 | try { 297 | if (this._nextAssertions) { 298 | this._result.restartTest(this); 299 | this._nextAssertions(this); 300 | } else { 301 | /*this._globalProperties = objectKeys(global);*/ 302 | this._result.startTest(this); 303 | this.setUp(this); 304 | this[this._methodName](this); 305 | } 306 | } catch(e) { 307 | this._filterException(e); 308 | } finally { 309 | if (this._paused) { 310 | this._result.pauseTest(this); 311 | } else { 312 | try { 313 | this.tearDown(this); 314 | } catch(e) { 315 | this._filterException(e); 316 | } finally { 317 | this._nextAssertions = null; 318 | this._result.stopTest(this); 319 | defer(function() { 320 | this.parent.next(); 321 | }, this); 322 | } 323 | } 324 | } 325 | } 326 | 327 | function _filterException(e) { 328 | var name = e.name; 329 | switch(name) { 330 | case 'AssertionFailedError': 331 | this._result.addFailure(this, e); 332 | break; 333 | case 'AssertionSkippedError': 334 | this._result.addSkip(this, e); 335 | break; 336 | default: 337 | this._result.addError(this, e); 338 | } 339 | } 340 | 341 | function pause(assertions) { 342 | this._paused = true; 343 | var self = this; 344 | if (assertions) { this._nextAssertions = assertions; } 345 | self._timeoutId = global.setTimeout(function() { 346 | self.resume(function() { 347 | self.fail('Test timed out. Testing was not resumed after being paused.'); 348 | }); 349 | }, TestCase.defaultTimeout); 350 | } 351 | 352 | function resume(assertions) { 353 | if (this._paused) { // avoid race conditions 354 | this._paused = false; 355 | global.clearTimeout(this._timeoutId); 356 | if (assertions) { this._nextAssertions = assertions; } 357 | this.run(); 358 | } 359 | } 360 | 361 | function size() { 362 | return 1; 363 | } 364 | 365 | function toString() { 366 | return this.constructor.displayName + '#' + this.name; 367 | } 368 | 369 | function addAssertion() { 370 | this._result.addAssertion(); 371 | } 372 | 373 | p.run = run; 374 | p.addAssertion = addAssertion; 375 | p._filterException = _filterException; 376 | p.pause = pause; 377 | p.resume = resume; 378 | p.size = size; 379 | p.toString = toString; 380 | p.setUp = function() {}; 381 | p.tearDown = function() {}; 382 | })(TestCase.prototype); 383 | Evidence.TestCase = TestCase; 384 | function TestSuite(name, tests) { 385 | this.name = name; 386 | this._tests = []; 387 | if (tests) { 388 | this.push.apply(this, tests); 389 | } 390 | } 391 | 392 | TestSuite.displayName = 'TestSuite'; 393 | 394 | (function(p) { 395 | function run(result) { 396 | this._index = 0; 397 | this._result = result; 398 | result.startSuite(this); 399 | this.next(); 400 | return result; 401 | } 402 | 403 | function next() { 404 | var next = this._tests[this._index]; 405 | if (next) { 406 | this._index++; 407 | next.run(this._result); 408 | } else { 409 | this._result.stopSuite(this); 410 | if (this.parent) { 411 | this.parent.next(); 412 | } else { 413 | this._result.stop(new Date()); 414 | } 415 | } 416 | } 417 | 418 | function push() { 419 | for (var i = 0, length = arguments.length; i < length; i++) { 420 | var test = arguments[i]; 421 | test.parent = this; 422 | this._tests.push(test); 423 | } 424 | } 425 | 426 | function addTest(test) { 427 | test.parent = this; 428 | this._tests.push(test); 429 | } 430 | 431 | function addTests(tests) { 432 | for (var i = 0, length = tests.length; i < length; i++) { 433 | this.addTest(tests[i]); 434 | } 435 | } 436 | 437 | function size() { 438 | var tests = this._tests, 439 | length = tests.length, 440 | sum = 0; 441 | 442 | for (var i = 0; i < length; i++) { 443 | sum += tests[i].size(); 444 | } 445 | return sum; 446 | } 447 | 448 | function isEmpty() { 449 | return this.size() === 0; 450 | } 451 | 452 | function toString() { 453 | return this.name; 454 | } 455 | p.run = run; 456 | p.next = next; 457 | p.push = push; 458 | p.size = size; 459 | p.isEmpty = isEmpty; 460 | p.toString = toString; 461 | })(TestSuite.prototype); 462 | Evidence.TestSuite = TestSuite; 463 | function TestRunner() { 464 | } 465 | 466 | TestRunner.displayName = 'TestRunner'; 467 | 468 | (function(p) { 469 | function run(suite) { 470 | suite.parent = null; 471 | var result = this._makeResult(); 472 | result.start(new Date()); 473 | suite.run(result); 474 | return result; 475 | } 476 | 477 | function _makeResult() { 478 | return new TestResult(); 479 | } 480 | 481 | p.run = run; 482 | p._makeResult = _makeResult; 483 | })(TestRunner.prototype); 484 | Evidence.TestRunner = TestRunner; 485 | function TestLoader() { 486 | } 487 | 488 | TestLoader.displayName = 'TestLoader'; 489 | 490 | (function(p) { 491 | function loadTestsFromTestCase(testcaseClass) { 492 | var suite = new TestSuite(testcaseClass.displayName), 493 | props = this.getTestCaseNames(testcaseClass); 494 | for (var i=0; i < props.length; i++) { 495 | suite.push(new testcaseClass(props[i])); 496 | } 497 | return suite; 498 | } 499 | 500 | function loadTestsFromTestCases(testcases) { 501 | var suite = new TestSuite(getNameFromFile()); 502 | for (var i = 0; i < testcases.length; i++) { 503 | var testcase = testcases[i]; 504 | var subSuite = defaultLoader.loadTestsFromTestCase(testcase); 505 | if (!subSuite.isEmpty()) { suite.push(subSuite); } 506 | } 507 | return suite; 508 | } 509 | 510 | function getTestCaseNames(testcaseClass) { 511 | var results = [], 512 | proto = testcaseClass.prototype, 513 | prefix = this.testMethodPrefix; 514 | 515 | for (var property in proto) { 516 | if (property.indexOf(prefix) === 0) { 517 | results.push(property); 518 | } 519 | } 520 | return results.sort(); 521 | } 522 | 523 | function loadRegisteredTestCases() { 524 | return loadTestsFromTestCases(TestCase.subclasses); 525 | } 526 | 527 | p.loadTestsFromTestCase = loadTestsFromTestCase; 528 | p.loadRegisteredTestCases = loadRegisteredTestCases; 529 | p.loadTestsFromTestCases = loadTestsFromTestCases; 530 | p.testMethodPrefix = 'test'; 531 | p.getTestCaseNames = getTestCaseNames; 532 | 533 | })(TestLoader.prototype); 534 | Evidence.TestLoader = TestLoader; 535 | function AutoRunner() { 536 | if (global.console && global.console.log) { 537 | this.logger = Logger; 538 | } else if (Object.prototype.toString.call(global.environment) === '[object Environment]' && global.print) { 539 | this.logger = CommandLineLogger; 540 | } else { 541 | this.logger = PopupLogger; 542 | } 543 | this.autoRun = true; 544 | this.verbosity = Logger.INFO; 545 | this.runner = ConsoleTestRunner; 546 | } 547 | 548 | (function() { 549 | function run(options) { 550 | var autoRunner = new this(); 551 | options = options || autoRunner.retrieveOptions(); 552 | autoRunner.processOptions(options); 553 | if (autoRunner.autoRun) { autoRunner.run() }; 554 | } 555 | 556 | AutoRunner.run = run; 557 | AutoRunner.displayName = 'AutoRunner'; 558 | AutoRunner.LOGGERS = { 559 | console: Logger, 560 | popup: PopupLogger, 561 | command_line: CommandLineLogger 562 | }; 563 | 564 | AutoRunner.RUNNERS = { 565 | console: ConsoleTestRunner 566 | }; 567 | })(); 568 | 569 | (function(p) { 570 | function run() { 571 | var logger = new this.logger(this.verbosity), 572 | runner = new this.runner(logger), 573 | suite = defaultLoader.loadRegisteredTestCases(); 574 | if (suite._tests.length <= 1) { 575 | suite = suite._tests[0]; 576 | } 577 | return runner.run(suite); 578 | } 579 | 580 | function processQueryString(str) { 581 | var results = {}; 582 | str = (str + '').match(/^(?:[^?#]*\?)([^#]+?)(?:#.*)?$/); 583 | str = str && str[1]; 584 | 585 | if (!str) { return results; } 586 | 587 | var pairs = str.split('&'), 588 | length = pairs.length; 589 | if (!length) { return results; } 590 | 591 | for (var i = 0; i < length; i++) { 592 | var pair = pairs[i].split('='), 593 | key = decodeURIComponent(pair[0]), 594 | value = pair[1]; 595 | value = value ? decodeURIComponent(value) : true; 596 | results[key] = value; 597 | } 598 | return results; 599 | } 600 | 601 | function processArguments(args) { // RHINO 602 | var results = {}; 603 | 604 | for (var i = 0; i < args.length; i++) { 605 | var arg = args[i]; 606 | if (arg.indexOf('-') === 0) { 607 | var value = args[i + 1]; 608 | if (value && value.indexOf('-') !== 0) { 609 | i++; 610 | } else { 611 | value = true; 612 | } 613 | results[arg.substr(1)] = value; 614 | } 615 | } 616 | return results; 617 | } 618 | 619 | function retrieveOptions() { 620 | if (global.location) { 621 | return this.processQueryString(global.location); 622 | } 623 | if (global.arguments) { 624 | return this.processArguments(global.arguments); 625 | } 626 | return {}; 627 | } 628 | 629 | function processOptions(options) { 630 | for(var key in options) { 631 | var value = options[key]; 632 | switch(key) { 633 | case 'timeout': 634 | TestCase.defaultTimeout = global.parseFloat(value) * 1000; 635 | break; 636 | case 'run': 637 | this.autoRun = value === 'false' ? false : true; 638 | break; 639 | case 'logger': 640 | this.logger = AutoRunner.LOGGERS[value]; 641 | break; 642 | case 'verbosity': 643 | var i = global.parseInt(value); 644 | this.verbosity = global.isNaN(i) ? Logger[value] : i; 645 | break; 646 | case 'runner': 647 | this.runner = AutoRunner.RUNNERS[value]; 648 | break; 649 | } 650 | } 651 | } 652 | 653 | p.run = run; 654 | p.processQueryString = processQueryString; 655 | p.processArguments = processArguments; 656 | p.retrieveOptions = retrieveOptions; 657 | p.processOptions = processOptions; 658 | })(AutoRunner.prototype); 659 | Evidence.AutoRunner = AutoRunner; 660 | function TestResult() { 661 | this.testCount = 0; 662 | this.assertionCount = 0; 663 | this.skipCount = 0; 664 | this.skips = []; 665 | this.failureCount = 0; 666 | this.failures = []; 667 | this.errors = []; 668 | this.errorCount = 0; 669 | this.testCount = 0; 670 | } 671 | 672 | TestResult.displayName = 'TestResult'; 673 | 674 | (function(p) { 675 | function addAssertion() { 676 | this.assertionCount++; 677 | } 678 | 679 | function addSkip(testcase, reason) { 680 | this.skipCount++; 681 | this.skips.push(reason); 682 | } 683 | 684 | function addFailure(testcase, reason) { 685 | this.failureCount++; 686 | this.failures.push(reason); 687 | } 688 | 689 | function addError(testcase, error) { 690 | this.errorCount++; 691 | this.errors.push(error); 692 | } 693 | 694 | function startTest(testcase) { 695 | this.testCount++; 696 | } 697 | 698 | function stopTest(testcase) {} 699 | 700 | function pauseTest(testcase) {} 701 | 702 | function restartTest(testcase) {} 703 | 704 | function startSuite(suite) {} 705 | 706 | function stopSuite(suite) {} 707 | 708 | function start(t0) { 709 | this.t0 = t0; 710 | } 711 | 712 | function stop(t1) { 713 | this.t1 = t1; 714 | } 715 | 716 | function toString() { 717 | return this.testCount + ' tests, ' + 718 | this.assertionCount + ' assertions, ' + 719 | this.failureCount + ' failures, ' + 720 | this.errorCount + ' errors, ' + 721 | this.skipCount + ' skips'; 722 | } 723 | 724 | p.addAssertion = addAssertion; 725 | p.addSkip = addSkip; 726 | p.addFailure = addFailure; 727 | p.addError = addError; 728 | p.startTest = startTest; 729 | p.stopTest = stopTest; 730 | p.pauseTest = pauseTest; 731 | p.restartTest = restartTest; 732 | p.startSuite = startSuite; 733 | p.stopSuite = stopSuite; 734 | p.start = start; 735 | p.stop = stop; 736 | p.toString = toString; 737 | })(TestResult.prototype); 738 | Evidence.TestResult = TestResult; 739 | var Console = {}; 740 | 741 | function Logger(level) { 742 | if (typeof level !== 'undefined') { 743 | this.level = level; 744 | } 745 | } 746 | 747 | Logger.displayName = 'Logger'; 748 | Logger.LEVELS = ['NOTSET', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL']; 749 | Logger.CRITICAL = 5; 750 | Logger.ERROR = 4; 751 | Logger.WARN = 3; 752 | Logger.INFO = 2; 753 | Logger.DEBUG = 1; 754 | Logger.NOTSET = 0; 755 | 756 | (function(p) { 757 | function critical(template, params) { 758 | this.log(Logger.CRITICAL, template, params); 759 | } 760 | 761 | function error(template, params) { 762 | this.log(Logger.ERROR, template, params); 763 | } 764 | 765 | function warn(template, params) { 766 | this.log(Logger.WARN, template, params); 767 | } 768 | 769 | function info(template, params) { 770 | this.log(Logger.INFO, template, params); 771 | } 772 | 773 | function debug(template, params) { 774 | this.log(Logger.DEBUG, template, params); 775 | } 776 | 777 | function log(level, template, params) { 778 | level = level || Logger.NOTSET; 779 | var c = global.console; 780 | 781 | var method = Logger.LEVELS[level].toLowerCase(); 782 | if (method === 'critical') { method = 'error'; } 783 | method = (method in c) ? method : 'log'; 784 | 785 | if (level >= this.level) { 786 | if (params) { 787 | params = params.slice(0); 788 | params.unshift(template); 789 | c[method].apply(c, params); 790 | } else { 791 | c[method](template); 792 | } 793 | } 794 | } 795 | 796 | p.log = log; 797 | p.critical = critical; 798 | p.error = error; 799 | p.warn = warn; 800 | p.info = info; 801 | p.debug = debug; 802 | p.level = 0; 803 | })(Logger.prototype); 804 | Console.Logger = Logger; 805 | function PopupLogger(level) { 806 | Logger.call(this, level); 807 | } 808 | 809 | chain(PopupLogger, Logger); 810 | PopupLogger.displayName = 'PopupLogger'; 811 | 812 | (function(p) { 813 | var BASIC_STYLES = 'color: #333; background-color: #fff; font-family: monospace; border-bottom: 1px solid #ccc;'; 814 | var STYLES = { 815 | WARN: 'color: #000; background-color: #fc6;', 816 | ERROR: 'color: #f00; background-color: #fcc;', 817 | CRITICAL: 'color: #fff; background-color: #000;' 818 | }; 819 | 820 | function _cleanup(html) { 821 | return html.replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&').replace(/[\n\r]+/, '
'); 822 | } 823 | 824 | function _makePopup() { 825 | var popup = global.open('','popup','height=400,width=400'); 826 | var doc = popup.document; 827 | doc.write('\ 828 | \ 829 | \ 830 | \ 831 | Console\ 832 | \ 833 |
\ 834 | '); 835 | doc.close(); 836 | popup.focus(); 837 | return popup; 838 | } 839 | 840 | function _appendLine(level, msg) { 841 | this.popup = this.popup || this._makePopup(); 842 | var levelName = Logger.LEVELS[level]; 843 | 844 | var html = '
'; 848 | if (level > Logger.INFO) { 849 | html += ''; 850 | html += levelName; 851 | html += ': '; 852 | } 853 | html += _cleanup(msg); 854 | html += '
'; 855 | var doc = this.popup.document, 856 | div = doc.createElement('div'); 857 | div.innerHTML = html; 858 | html = div.firstChild; 859 | div = null; 860 | doc.getElementById('evidence_console').appendChild(html); 861 | } 862 | 863 | function log(level, msg, params) { 864 | level = level || Logger.NOTSET; 865 | if (level >= this.level) { 866 | if (params) { 867 | msg = UI.printf(msg, params); 868 | } 869 | this._appendLine(level, msg); 870 | } 871 | } 872 | 873 | p.log = log; 874 | p._makePopup = _makePopup; 875 | p._appendLine = _appendLine; 876 | })(PopupLogger.prototype); 877 | Console.PopupLogger = PopupLogger; 878 | function CommandLineLogger(level) { 879 | Logger.call(this, level); 880 | } 881 | 882 | chain(CommandLineLogger, Logger); 883 | CommandLineLogger.displayName = 'CommandLineLogger'; 884 | 885 | (function(p) { 886 | 887 | function log(level, msg, params) { 888 | level = level || Logger.NOTSET; 889 | if (level >= this.level) { 890 | var prefix = ''; 891 | if (level > Logger.INFO) { 892 | prefix = Logger.LEVELS[level]+ ': '; 893 | } 894 | if (params) { 895 | msg = UI.printf(msg, params); 896 | } 897 | global.print(prefix + msg); 898 | } 899 | } 900 | 901 | p.log = log; 902 | })(CommandLineLogger.prototype); 903 | Console.CommandLineLogger = CommandLineLogger; 904 | function ConsoleTestRunner(logger) { 905 | TestRunner.call(this); 906 | this.logger = logger; 907 | } 908 | 909 | chain(ConsoleTestRunner, TestRunner); 910 | ConsoleTestRunner.displayName = 'ConsoleTestRunner'; 911 | 912 | (function(p) { 913 | function _makeResult() { 914 | return new ConsoleTestResult(this.logger); 915 | } 916 | 917 | p._makeResult = _makeResult; 918 | })(ConsoleTestRunner.prototype); 919 | Console.TestRunner = ConsoleTestRunner; 920 | function ConsoleTestResult(logger) { 921 | TestResult.call(this); 922 | this.logger = logger; 923 | } 924 | 925 | chain(ConsoleTestResult, TestResult); 926 | ConsoleTestResult.displayName = 'ConsoleTestResult'; 927 | 928 | (function(p) { 929 | var _super = TestResult.prototype; 930 | 931 | function addAssertion() { 932 | this.assertionCount++; 933 | } 934 | 935 | function addSkip(testcase, msg) { 936 | _super.addSkip.call(this, testcase, msg); 937 | this.logger.warn('Skipping testcase ' + testcase + ': ' + msg.message); 938 | } 939 | 940 | function addFailure(testcase, msg) { 941 | _super.addFailure.call(this, testcase, msg); 942 | this.logger.error(testcase + ': ' + msg.message + ' ' + msg.template, msg.args); 943 | } 944 | 945 | function addError(testcase, error) { 946 | _super.addError.call(this, testcase, error); 947 | this.logger.error(testcase + ' threw an error. ' + error); 948 | } 949 | 950 | function startTest(testcase) { 951 | _super.startTest.call(this, testcase); 952 | this.logger.debug('Started testcase ' + testcase + '.'); 953 | } 954 | 955 | function stopTest(testcase) { 956 | this.logger.debug('Completed testcase ' + testcase + '.'); 957 | } 958 | 959 | function pauseTest(testcase) { 960 | this.logger.info('Paused testcase ' + testcase + '.'); 961 | } 962 | 963 | function restartTest(testcase) { 964 | this.logger.info('Restarted testcase ' + testcase + '.'); 965 | } 966 | 967 | function startSuite(suite) { 968 | this.logger.info('Started suite ' + suite + '.'); 969 | } 970 | 971 | function stopSuite(suite) { 972 | this.logger.info('Completed suite ' + suite + '.'); 973 | } 974 | 975 | function start(t0) { 976 | _super.start.call(this, t0); 977 | this.logger.info('Started tests.'); 978 | } 979 | 980 | function stop(t1) { 981 | _super.stop.call(this, t1); 982 | this.logger.info('Completed tests in ' + ((t1 - this.t0)/1000) + 's.'); 983 | this.logger.info(this.toString() + '.'); 984 | } 985 | 986 | p.addAssertion = addAssertion; 987 | p.addSkip = addSkip; 988 | p.addFailure = addFailure; 989 | p.addError = addError; 990 | p.startTest = startTest; 991 | p.stopTest = stopTest; 992 | p.pauseTest = pauseTest; 993 | p.restartTest = restartTest; 994 | p.startSuite = startSuite; 995 | p.stopSuite = stopSuite; 996 | p.start = start; 997 | p.stop = stop; 998 | })(ConsoleTestResult.prototype); 999 | 1000 | 1001 | Console.TestResult = ConsoleTestResult; 1002 | var UI = (function() { 1003 | function printf(template, args, inspector) { 1004 | var parts = [], 1005 | regexp = /(^%|.%)([a-zA-Z])/, 1006 | args = args.splice(0); // clone args 1007 | 1008 | inspector = inspector || String; 1009 | 1010 | if (template.length <= 0) { 1011 | return ''; 1012 | } 1013 | while (m = regexp.exec(template)) { 1014 | var match = m[0], index = m.index, type, arg; 1015 | 1016 | if (match.indexOf('%%') === 0) { 1017 | parts.push(template.substr(0, index)); 1018 | parts.push(match.substr(1)); 1019 | } else { 1020 | parts.push(template.substr(0, match.indexOf('%' === 0) ? index + 1 : index)); 1021 | type = m[2]; 1022 | arg = args.shift(); 1023 | arg = inspector(arg, type); 1024 | parts.push(arg); 1025 | } 1026 | template = template.substr(index + match.length); 1027 | } 1028 | parts.push(template); 1029 | return parts.join(''); 1030 | } 1031 | 1032 | return { 1033 | printf: printf, 1034 | Console: Console 1035 | }; 1036 | })(); 1037 | Evidence.UI = UI; 1038 | 1039 | var defaultLoader = new TestLoader(); 1040 | Evidence.defaultLoader = defaultLoader; 1041 | 1042 | global.Evidence = Evidence; 1043 | 1044 | if (global.location) { 1045 | global.onload = function() { 1046 | if (typeof originalOnload === 'function') { 1047 | originalOnload.call(global); 1048 | } 1049 | AutoRunner.run(); 1050 | }; 1051 | } else if (global.arguments) { 1052 | var runtime = java.lang.Runtime.getRuntime(); 1053 | var thread = new java.lang.Thread(function() { 1054 | AutoRunner.run(); 1055 | }); 1056 | runtime.addShutdownHook(thread); 1057 | } 1058 | 1059 | })(this); 1060 | --------------------------------------------------------------------------------