ÿØÿà JFIF    ÿÛ „  ( %"1!%)+...383,7(-.+  -+++--++++---+-+-----+---------------+---+-++7-----ÿÀ  ß â" ÿÄ     ÿÄ H    !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ   ÿÄ %  !1AQa"23‘ÿÚ   ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6  öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ "Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷󲍷˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%ÌÁ²h´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ǍýʏTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆѪQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»& î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$˝Úsäÿ ÷Û #°xŸëí(l »ý3—¥5m! rt`†0~'j2(]S¦¦kv,ÚÇ l¦øJA£Šƒ J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉ䢍mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ­‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U  Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD é©¤&‡ïDbàÁôMÁ." ¤‡ú*õ'VŽ|¼´Úgllº¼klz[Æüï÷Aób‡Eÿ dÑ»Xx9ÃÜ£ÁT/`¼¸vI±Ýµ·Ë‚“G³þ*Ÿû´r|*}<¨îºœ @¦mÄ’M¹”.œ«Y–|6ÏU¤jç¥ÕÞqO ˜kDÆÁ¨5ÿ š;ÐЦ¦€GÙk \ –Þ=â¼=SͧµªS°ÚÍpÜãQűÀõ¬?ÃÁ1Ñ•õZà?hóœ€ L¦l{Y*K˜Ù›zc˜–ˆâ ø+¾ ­-Ök¥%ùEÜA'}ˆ><ÊIè“bpÍ/qÞâvoX€w,\úªò6Z[XdÒæ­@Ö—€$òJí#é>'°Ú ôª˜<)4ryÙ£|óAÅn5žêŸyÒäMÝ2{"}‰–¤l÷ûWX\l¾Á¸góÉOÔ /óñB¤f¸çñ[.P˜ZsÊË*ßT܈§QN¢’¡¨§V¼(Üù*eÕ“”5T¨‹Âê¥FŒã½Dü[8'Ò¥a…Ú¶k7a *•›¼'Ò·\8¨ª\@\õ¢¦íq+DÙrmÎ…_ªæ»ŠÓœ¡¯’Ré9MÅ×D™lælffc+ŒÑ,ý™ÿ ¯þǤ=Å’Á7µ÷ÚÛ/“Ü€ñýã¼àí¾ÕÑ+ƒ,uµMâÀÄbm:ÒÎPæ{˜Gz[ƒ¯«® KHà`ߨŠéí¯P8Aq.C‰ à€kòpj´kN¶qô€…Õ,ÜNŠª-­{Zö’æû44‰sŽè‰îVíRœÕm" 6?³D9¡ÇTíÅꋇ`4«¸ÝÁô ï’ýorqКÇZ«x4Žâéþuïf¹µö[P ,Q£éaX±`PÉÍZ ¸äYúg üAx ’6Lê‚xÝÓ*äQ  Ï’¨hÍ =²,6ï#rÃ<¯–£»ƒ‹,–ê•€ aÛsñ'%Æ"®ÛüìBᝠHÚ3ß°©$“XnœÖ’î2ËTeûìxîß ¦å¿çÉ ðK§þ{‘t‚Ϋ¬jéîZ[ ”š7L¥4VÚCE×]m¤Øy”ä4-dz£œ§¸x.*ãÊÊ b÷•h:©‡¦s`BTÁRû¾g⻩‹jø sF¢àJøFl‘È•Xᓁà~*j¯ +(ÚÕ6-£¯÷GŠØy‚<Ç’.F‹Hœw(+)ÜÜâÈzÄäT§FߘãÏ;DmVœ3Àu@mÚüXÝü•3B¨òÌÁÛ<·ÃÜ z,Ì@õÅ·d2]ü8s÷IôÞ¯^Ç9¢u„~ëAŸï4«M? K]­ÅàPl@s_ p:°¬ZR”´›JC[CS.h‹ƒïËœ«Æ]–÷ó‚wR×k7X‰k›‘´ù¦=¡«‰¨¨Â')—71ó’c‡Ðúµ `é.{§p¹ój\Ž{1h{o±Ý=áUÊïGÖŒõ–-BÄm+AZX¶¡ ïHðæ¥JmÙ;…䡟ˆ¦ ° äšiÉg«$üMk5¤L“’çÊvïâï ,=f“"íἊ5ô¬x6{ɏžID0e¸vçmi'︧ºð9$ò¹÷*£’9ÿ ²TÔ…×>JV¥}Œ}$p[bÔ®*[jzS*8 ”·T›Í–ñUîƒwo$áè=LT™ç—~ô·¤ÈÚ$榍q‰„+´kFm)ž‹©i–ËqÞŠ‰à¶ü( ‚•§ •°ò·‡#5ª•µÊ﯅¡X¨šÁ*F#TXJÊ ušJVÍ&=iÄs1‚3•'fý§5Ñ<=[íÞ­ PÚ;ѱÌ_~Ä££8rÞ ²w;’hDT°>ÈG¬8Á²ÚzŽ®ò®qZcqJêäÞ-ö[ܘbň±çb“ж31²n×iƒðÕ;1¶þÉ ªX‰,ßqÏ$>•î íZ¥Z 1{ç൵+ƒÕµ¥°T$§K]á»Ûï*·¤tMI’ÂZbŽÕiÒ˜}bÓ0£ª5›¨ [5Ž^ÝœWøÂÝh° ¢OWun£¤5 a2Z.G2³YL]jåtì”ä ÁÓ‘%"©<Ôúʰsº UZvä‡ÄiÆÒM .÷V·™ø#kèýiíÌ–ª)µT[)BˆõÑ xB¾B€ÖT¨.¥~ð@VĶr#¸ü*åZNDŽH;âi ],©£öØpù(šºãö¼T.uCê•4@ÿ GÕÛ)Cx›®0ø#:ÏðFÒbR\(€€Ä®fã4Þ‰Fä¯HXƒÅ,†öEÑÔÜ]Öv²?tLÃvBY£ú6Êu5ÅAQ³1‘’¬x–HŒÐ‡ ^ ¸KwJôÖŽ5×CÚ¨vÜ«/B0$×k°=ðbÇ(Ï)w±A†Á† 11Í=èQšµ626ŒÜ/`G«µ<}—-Ö7KEHÈÉðóȤmݱû±·ø«Snmá=“䫚mݱŸ¡¶~ó·“äUóJæúòB|E LêŽy´jDÔ$G¢þÐñ7óR8ýÒ…Ç› WVe#·Ÿ p·Fx~•ݤF÷0Èÿ K¯æS<6’¡WШ; ´ÿ ¥Êø\Òuî†åÝ–VNœkÒ7oòX¨Á­Ø÷FÎÑä±g÷ÿ M~Çî=p,X´ ÝÌÚÅ‹’ÃjÖ.ØöÏñ qïQ¤ÓZE†° =6·]܈ s¸>v•Ž^Ý\wq9r‰Î\¸¡kURÒ$­*‹Nq?Þª*!sŠÆ:TU_u±T+øX¡ ®¹¡,ÄâÃBTsÜ$Ø›4m椴zÜK]’’›Pƒ @€#â˜`é¹=I‡fiV•Ôî“nRm+µFPOhÍ0B£ €+¬5c v•:P'ÒyÎ ‰V~‚Ó†ÖuókDoh$å\*ö%Ю=£«…aȼ½÷Û.-½VŒŠ¼'lyî±1¬3ó#ÞE¿ÔS¤gV£m›=§\û"—WU¤ÚǼÿ ÂnÁGŒÃ ‚õN D³õNÚíŒÕ;HôyÄÈ©P¹Ä{:?R‘Ô¨âF÷ø£bÅó® JS|‚R÷ivýáâ€Æé¡è³´IئÑT!§˜•ت‚¬â@q€wnïCWÄ@JU€ê¯m6]Ï:£âx'+ÒðXvÓ¦Úm=–´7œ $ì“B£~p%ÕŸUþ« N@¼üï~w˜ñø5®—'Ôe»¤5ã//€ž~‰Tþ›Å7•#¤× Íö pÄ$ùeåì*«ÓŠEØWEÈsßg ¦ûvžSsLpºÊW–âµEWöˬH; ™!CYõZ ÃÄf æ#1W. \uWâ\,\Çf j’<qTbên›Î[vxx£ë 'ö¨1›˜ÀM¼Pÿ H)ƒêêŒA7s,|F“ 꺸k³9Ìö*ç®;Ö!Ö$Eiž•¹ÒÚ†ýóéÝû¾ÕS®ó$’NÝäŸz¤5r¦ãÄÃD÷Üø!°ø‡Ô&@m™Ì^Ãä­d q5Lnÿ N;.6½·N|#ä"1Nƒx“ã<3('&ñßt  ~ªu”1Tb㫨9ê–›–bìd$ߣ=#ÕãÒmU¯eí$EFù5ýYô櫨æì™Ç—±ssM]·á¿0ÕåJRÓªîiƒ+O58ÖñªŠÒx" \µâá¨i’¤i —Ö ” M+M¤ë9‚‰A¦°Qõ¾ßøK~¼Ã‘g…Ö´~÷Ï[3GUœÒ½#…kàÔ®Ò”‰³·dWV‰IP‰Ú8u¹”E ÖqLj¾êÕCBš{A^Âß;–¨`¯¬ìö ˼ ×tìø.tƐm*n¨y4o&Àx¥n¦×î‡aupáÛj8¿m›è¶ã!o½;ß0y^ý×^EÑ¿ÒjzŒ­)vÚÑnÄL …^ªô× ‡—‚3k Îý­hï]içå–îÏ*÷ñþ»Ô CÒjøjÍznˆ´ ¹#b'Fô‹ ‰v¥'’à'T´ƒHýÍ%M‰ ƒ&ÆÇŒï1 ‘ –Þ ‰i¬s žR-Ÿ kЬá¬7:þ 0ŒÅÒÕ/aÙ¬ÃÝ#Úøœ ©aiVc‰. ¹¦ãµ” ›Yg¦›ÆÎýº°f³7ƒhá·¸­}&D9¡ÂsÉÙÞèŠõØàC™¨ñbFC|´Ü(ŸƒÚÒ-%»'a Ì¿)ËÇn¿úÿ ÞŽX…4ÊÅH^ôΑí@ù¹Eh¶“L8Çjù ¼ÎåVªóR©Ï5uà V4lZß®=€xÖŸ–ÑÈ ÷”¨°¾__yM1tÉ?uÆþIkÄgæ@þ[¢†°XÃJ£j·:nkÅ¢u ‘}âGzö­/IµèЬ¼48q¦F°ŽR¼=ûì{´¯RýicS ÕÛ íNtÍÙï£,w4rêì®»~x(©Uñ§#Ñ&œÕ¤>ÎåÍÓ9’Ö{9eV­[Öjâ²ãu]˜å2›qÑšÕJç0€sÄ|Êëè0튔bÁ>“{×_F`Ø©ºê:µä,v¤ðfc1±"«ÔÍän1#=· Âøv~H½ÐßA¾¿Ü€Óš]Õ; I¾÷ç‚Qi†î¹9ywÔKG˜áñ zQY—§ÃÕZ07§X‚ Áh;ÁM)iÌCH-¯T‘ë|A0{Ò½LÚ–TâÖkÜ’dÀ“rmm»”جPF³ÖcbE§T€ÒxKºû’Ó®7±²(\4ŽÃ¸Uu@j™yĵ;³µ!Á¢b.W¤=mõ´êµK k ¸K^ÜÛ#p*Ü14qkZç5ïë †°5Ï%ÍÛ<Õ¤×Ô¥ê†C Õ´¼ú$ƒÖ“”]Ù¬qÞÚ[4©ý!ûÏ—Áb쳐XµA¬â~`›Çr¸8ìùÝ䫦<>ä÷«?xs´ÇÑ /á;¹øüÊÈÙà{"@Žïzâ¬[âß‚ U_<ÇŸ½4èN˜ú61®qŠu ¦þF£»äJ_ˆÙÎ~ ÞAã–݄ϗrŠD;xTž‘ô`É«…suãO`?³à™ô Lý#Íc5öoæØ‚y´´÷«ZR§<&JÇ+éâô´€i!Àˆ0æAoàðLèÖ-2ŸõW.’t^–(KÁmHµV@xÜÇy®Ñø­â^:Ú3w· 7½¹°ñ¸â¹®:',«Mœ—n­Á+Ãbš LÈ‘ÄnRÓÅœ%¦²‰¨ùQ:¤f‚ "PÕtô¸…cæl…&˜Ú˜Ôkv‹ž+vŠ,=¢v­6—Xy*¥t£«<™:“aîϲ=¦6rO]XI¿Œ÷¤zÚ­›¶ 6÷”w\d ü~v®ˆÌk«^m<ÿ ¢‰Õ\)ùºŽ;… lîÙÅEŠ®cѾ@vnMÏ,¼“ñ•ŽBxðÃzãÇç%3ˆ"}Ù•Åî> BÉú;Ò]V+P˜F_´ßé> Øše|ï‡ÄOmFæÇ ãqÞ$/xÐx­z`ï9"œÜij‚!7.\Td…9M‡•iŽ‹¾‘50ÞŽn¥ß4ÉôO ¹*í^QêËÜÇÌ8=ާs‰'ÂëÙ«á%Pú[O †ÅP¯Vsް.‰,kc¶ ¬A9n˜XÎ-ÞšN["¹QÕ‰ƒMýÁߺXJæÍaLj¾×Ãmã¾ãÚ uñÒþåQô¦¥ /ÄUx:‚ÍÜ’ Đ©ØÝ3V¨‰ÕnÐ6ó*óúK­«…c ¯U òhsý­jóÔj#,ímŒRµ«lbïUTŒÑ8†Ä0œÏr`ð¡¬É Ї ë"À² ™ 6¥ f¶ ¢ÚoܱԷ-<Àî)†a¶ž'Ú»¨TXqØæ¶÷YÄHy˜9ÈIW­YÀuMFë ºÏ’AqÌ4·/Ú †ô'i$øä­=Ä Ý|öK×40è|È6p‘0§)o¥ctî§H+CA-“ xØ|ÐXАç l8íºð3Ø:³¤¬KX¯UÿÙ /* * QUnit - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * * Copyright (c) 2009 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. */ (function(window) { var QUnit = { // Initialize the configuration options init: function() { config = { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, started: +new Date, blocking: false, autorun: false, assertions: [], filters: [], queue: [] }; var tests = id("qunit-tests"), banner = id("qunit-banner"), result = id("qunit-testresult"); if ( tests ) { tests.innerHTML = ""; } if ( banner ) { banner.className = ""; } if ( result ) { result.parentNode.removeChild( result ); } }, // call on start of module test to prepend name to all tests module: function(name, testEnvironment) { config.currentModule = name; synchronize(function() { if ( config.currentModule ) { QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); } config.currentModule = name; config.moduleTestEnvironment = testEnvironment; config.moduleStats = { all: 0, bad: 0 }; QUnit.moduleStart( name, testEnvironment ); }); }, asyncTest: function(testName, expected, callback) { if ( arguments.length === 2 ) { callback = expected; expected = 0; } QUnit.test(testName, expected, callback, true); }, test: function(testName, expected, callback, async) { var name = testName, testEnvironment, testEnvironmentArg; if ( arguments.length === 2 ) { callback = expected; expected = null; } // is 2nd argument a testEnvironment? if ( expected && typeof expected === 'object') { testEnvironmentArg = expected; expected = null; } if ( config.currentModule ) { name = config.currentModule + " module: " + name; } if ( !validTest(name) ) { return; } synchronize(function() { QUnit.testStart( testName ); testEnvironment = extend({ setup: function() {}, teardown: function() {} }, config.moduleTestEnvironment); if (testEnvironmentArg) { extend(testEnvironment,testEnvironmentArg); } // allow utility functions to access the current test environment QUnit.current_testEnvironment = testEnvironment; config.assertions = []; config.expected = expected; try { if ( !config.pollution ) { saveGlobal(); } testEnvironment.setup.call(testEnvironment); } catch(e) { QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); } if ( async ) { QUnit.stop(); } try { callback.call(testEnvironment); } catch(e) { fail("Test " + name + " died, exception and test follows", e, callback); QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); // else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking if ( config.blocking ) { start(); } } }); synchronize(function() { try { checkPollution(); testEnvironment.teardown.call(testEnvironment); } catch(e) { QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); } try { QUnit.reset(); } catch(e) { fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); } if ( config.expected && config.expected != config.assertions.length ) { QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); } var good = 0, bad = 0, tests = id("qunit-tests"); config.stats.all += config.assertions.length; config.moduleStats.all += config.assertions.length; if ( tests ) { var ol = document.createElement("ol"); ol.style.display = "none"; for ( var i = 0; i < config.assertions.length; i++ ) { var assertion = config.assertions[i]; var li = document.createElement("li"); li.className = assertion.result ? "pass" : "fail"; li.appendChild(document.createTextNode(assertion.message || "(no message)")); ol.appendChild( li ); if ( assertion.result ) { good++; } else { bad++; config.stats.bad++; config.moduleStats.bad++; } } var b = document.createElement("strong"); b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; addEvent(b, "click", function() { var next = b.nextSibling, display = next.style.display; next.style.display = display === "none" ? "block" : "none"; }); addEvent(b, "dblclick", function(e) { var target = e && e.target ? e.target : window.event.srcElement; if ( target.nodeName.toLowerCase() === "strong" ) { var text = "", node = target.firstChild; while ( node.nodeType === 3 ) { text += node.nodeValue; node = node.nextSibling; } text = text.replace(/(^\s*|\s*$)/g, ""); if ( window.location ) { window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); } } }); var li = document.createElement("li"); li.className = bad ? "fail" : "pass"; li.appendChild( b ); li.appendChild( ol ); tests.appendChild( li ); if ( bad ) { var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { toolbar.style.display = "block"; id("qunit-filter-pass").disabled = null; id("qunit-filter-missing").disabled = null; } } } else { for ( var i = 0; i < config.assertions.length; i++ ) { if ( !config.assertions[i].result ) { bad++; config.stats.bad++; config.moduleStats.bad++; } } } QUnit.testDone( testName, bad, config.assertions.length ); if ( !window.setTimeout && !config.queue.length ) { done(); } }); if ( window.setTimeout && !config.doneTimer ) { config.doneTimer = window.setTimeout(function(){ if ( !config.queue.length ) { done(); } else { synchronize( done ); } }, 13); } }, /** * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. */ expect: function(asserts) { config.expected = asserts; }, /** * Asserts true. * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function(a, msg) { QUnit.log(a, msg); config.assertions.push({ result: !!a, message: msg }); }, /** * Checks that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. * * Prefered to ok( actual == expected, message ) * * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); * * @param Object actual * @param Object expected * @param String message (optional) */ equal: function(actual, expected, message) { push(expected == actual, actual, expected, message); }, notEqual: function(actual, expected, message) { push(expected != actual, actual, expected, message); }, deepEqual: function(a, b, message) { push(QUnit.equiv(a, b), a, b, message); }, notDeepEqual: function(a, b, message) { push(!QUnit.equiv(a, b), a, b, message); }, strictEqual: function(actual, expected, message) { push(expected === actual, actual, expected, message); }, notStrictEqual: function(actual, expected, message) { push(expected !== actual, actual, expected, message); }, start: function() { // A slight delay, to avoid any current callbacks if ( window.setTimeout ) { window.setTimeout(function() { if ( config.timeout ) { clearTimeout(config.timeout); } config.blocking = false; process(); }, 13); } else { config.blocking = false; process(); } }, stop: function(timeout) { config.blocking = true; if ( timeout && window.setTimeout ) { config.timeout = window.setTimeout(function() { QUnit.ok( false, "Test timed out" ); QUnit.start(); }, timeout); } }, /** * Resets the test setup. Useful for tests that modify the DOM. */ reset: function() { if ( window.jQuery ) { jQuery("#main").html( config.fixture ); jQuery.event.global = {}; jQuery.ajaxSettings = extend({}, config.ajaxSettings); } }, /** * Trigger an event on an element. * * @example triggerEvent( document.body, "click" ); * * @param DOMElement elem * @param String type */ triggerEvent: function( elem, type, event ) { if ( document.createEvent ) { event = document.createEvent("MouseEvents"); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); elem.dispatchEvent( event ); } else if ( elem.fireEvent ) { elem.fireEvent("on"+type); } }, // Safe object type checking is: function( type, obj ) { return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; }, // Logging callbacks done: function(failures, total) {}, log: function(result, message) {}, testStart: function(name) {}, testDone: function(name, failures, total) {}, moduleStart: function(name, testEnvironment) {}, moduleDone: function(name, failures, total) {} }; // Backwards compatibility, deprecated QUnit.equals = QUnit.equal; QUnit.same = QUnit.deepEqual; // Maintain internal state var config = { // The queue of tests to run queue: [], // block until document ready blocking: true }; // Load paramaters (function() { var location = window.location || { search: "", protocol: "file:" }, GETParams = location.search.slice(1).split('&'); for ( var i = 0; i < GETParams.length; i++ ) { GETParams[i] = decodeURIComponent( GETParams[i] ); if ( GETParams[i] === "noglobals" ) { GETParams.splice( i, 1 ); i--; config.noglobals = true; } else if ( GETParams[i].search('=') > -1 ) { GETParams.splice( i, 1 ); i--; } } // restrict modules/tests by get parameters config.filters = GETParams; // Figure out if we're running the tests from a server or not QUnit.isLocal = !!(location.protocol === 'file:'); })(); // Expose the API as global variables, unless an 'exports' // object exists, in that case we assume we're in CommonJS if ( typeof exports === "undefined" || typeof require === "undefined" ) { extend(window, QUnit); window.QUnit = QUnit; } else { extend(exports, QUnit); exports.QUnit = QUnit; } if ( typeof document === "undefined" || document.readyState === "complete" ) { config.autorun = true; } addEvent(window, "load", function() { // Initialize the config, saving the execution queue var oldconfig = extend({}, config); QUnit.init(); extend(config, oldconfig); config.blocking = false; var userAgent = id("qunit-userAgent"); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { toolbar.style.display = "none"; var filter = document.createElement("input"); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; filter.disabled = true; addEvent( filter, "click", function() { var li = document.getElementsByTagName("li"); for ( var i = 0; i < li.length; i++ ) { if ( li[i].className.indexOf("pass") > -1 ) { li[i].style.display = filter.checked ? "none" : ""; } } }); toolbar.appendChild( filter ); var label = document.createElement("label"); label.setAttribute("for", "qunit-filter-pass"); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); var missing = document.createElement("input"); missing.type = "checkbox"; missing.id = "qunit-filter-missing"; missing.disabled = true; addEvent( missing, "click", function() { var li = document.getElementsByTagName("li"); for ( var i = 0; i < li.length; i++ ) { if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; } } }); toolbar.appendChild( missing ); label = document.createElement("label"); label.setAttribute("for", "qunit-filter-missing"); label.innerHTML = "Hide missing tests (untested code is broken code)"; toolbar.appendChild( label ); } var main = id('main'); if ( main ) { config.fixture = main.innerHTML; } if ( window.jQuery ) { config.ajaxSettings = window.jQuery.ajaxSettings; } QUnit.start(); }); function done() { if ( config.doneTimer && window.clearTimeout ) { window.clearTimeout( config.doneTimer ); config.doneTimer = null; } if ( config.queue.length ) { config.doneTimer = window.setTimeout(function(){ if ( !config.queue.length ) { done(); } else { synchronize( done ); } }, 13); return; } config.autorun = true; // Log the last module results if ( config.currentModule ) { QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); } var banner = id("qunit-banner"), tests = id("qunit-tests"), html = ['Tests completed in ', +new Date - config.started, ' milliseconds.
', '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); if ( banner ) { banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); } if ( tests ) { var result = id("qunit-testresult"); if ( !result ) { result = document.createElement("p"); result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests.nextSibling ); } result.innerHTML = html; } QUnit.done( config.stats.bad, config.stats.all ); } function validTest( name ) { var i = config.filters.length, run = false; if ( !i ) { return true; } while ( i-- ) { var filter = config.filters[i], not = filter.charAt(0) == '!'; if ( not ) { filter = filter.slice(1); } if ( name.indexOf(filter) !== -1 ) { return !not; } if ( not ) { run = true; } } return run; } function push(result, actual, expected, message) { message = message || (result ? "okay" : "failed"); QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); } function synchronize( callback ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { process(); } } function process() { while ( config.queue.length && !config.blocking ) { config.queue.shift()(); } } function saveGlobal() { config.pollution = []; if ( config.noglobals ) { for ( var key in window ) { config.pollution.push( key ); } } } function checkPollution( name ) { var old = config.pollution; saveGlobal(); var newGlobals = diff( old, config.pollution ); if ( newGlobals.length > 0 ) { ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); config.expected++; } var deletedGlobals = diff( config.pollution, old ); if ( deletedGlobals.length > 0 ) { ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); config.expected++; } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { var result = a.slice(); for ( var i = 0; i < result.length; i++ ) { for ( var j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { result.splice(i, 1); i--; break; } } } return result; } function fail(message, exception, callback) { if ( typeof console !== "undefined" && console.error && console.warn ) { console.error(message); console.error(exception); console.warn(callback.toString()); } else if ( window.opera && opera.postError ) { opera.postError(message, exception, callback.toString); } } function extend(a, b) { for ( var prop in b ) { a[prop] = b[prop]; } return a; } function addEvent(elem, type, fn) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } } function id(name) { return !!(typeof document !== "undefined" && document && document.getElementById) && document.getElementById( name ); } // Test for equality any JavaScript type. // Discussions and reference: http://philrathe.com/articles/equiv // Test suites: http://philrathe.com/tests/equiv // Author: Philippe Rathé QUnit.equiv = function () { var innerEquiv; // the real equiv function var callers = []; // stack to decide between skip/abort functions // Determine what is o. function hoozit(o) { if (QUnit.is("String", o)) { return "string"; } else if (QUnit.is("Boolean", o)) { return "boolean"; } else if (QUnit.is("Number", o)) { if (isNaN(o)) { return "nan"; } else { return "number"; } } else if (typeof o === "undefined") { return "undefined"; // consider: typeof null === object } else if (o === null) { return "null"; // consider: typeof [] === object } else if (QUnit.is( "Array", o)) { return "array"; // consider: typeof new Date() === object } else if (QUnit.is( "Date", o)) { return "date"; // consider: /./ instanceof Object; // /./ instanceof RegExp; // typeof /./ === "function"; // => false in IE and Opera, // true in FF and Safari } else if (QUnit.is( "RegExp", o)) { return "regexp"; } else if (typeof o === "object") { return "object"; } else if (QUnit.is( "Function", o)) { return "function"; } else { return undefined; } } // Call the o related callback with the given arguments. function bindCallbacks(o, callbacks, args) { var prop = hoozit(o); if (prop) { if (hoozit(callbacks[prop]) === "function") { return callbacks[prop].apply(callbacks, args); } else { return callbacks[prop]; // or undefined } } } var callbacks = function () { // for string, boolean, number and null function useStrictEquality(b, a) { if (b instanceof a.constructor || a instanceof b.constructor) { // to catch short annotaion VS 'new' annotation of a declaration // e.g. var i = 1; // var j = new Number(1); return a == b; } else { return a === b; } } return { "string": useStrictEquality, "boolean": useStrictEquality, "number": useStrictEquality, "null": useStrictEquality, "undefined": useStrictEquality, "nan": function (b) { return isNaN(b); }, "date": function (b, a) { return hoozit(b) === "date" && a.valueOf() === b.valueOf(); }, "regexp": function (b, a) { return hoozit(b) === "regexp" && a.source === b.source && // the regex itself a.global === b.global && // and its modifers (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; }, // - skip when the property is a method of an instance (OOP) // - abort otherwise, // initial === would have catch identical references anyway "function": function () { var caller = callers[callers.length - 1]; return caller !== Object && typeof caller !== "undefined"; }, "array": function (b, a) { var i; var len; // b could be an object literal here if ( ! (hoozit(b) === "array")) { return false; } len = a.length; if (len !== b.length) { // safe and faster return false; } for (i = 0; i < len; i++) { if ( ! innerEquiv(a[i], b[i])) { return false; } } return true; }, "object": function (b, a) { var i; var eq = true; // unless we can proove it var aProperties = [], bProperties = []; // collection of strings // comparing constructors is more strict than using instanceof if ( a.constructor !== b.constructor) { return false; } // stack constructor before traversing properties callers.push(a.constructor); for (i in a) { // be strict: don't ensures hasOwnProperty and go deep aProperties.push(i); // collect a's properties if ( ! innerEquiv(a[i], b[i])) { eq = false; } } callers.pop(); // unstack, we are done for (i in b) { bProperties.push(i); // collect b's properties } // Ensures identical properties name return eq && innerEquiv(aProperties.sort(), bProperties.sort()); } }; }(); innerEquiv = function () { // can take multiple arguments var args = Array.prototype.slice.apply(arguments); if (args.length < 2) { return true; // end transition } return (function (a, b) { if (a === b) { return true; // catch the most you can } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [b, a]); } // apply transition with (1..n) arguments })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); }; return innerEquiv; }(); /** * jsDump * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) * Date: 5/15/2008 * @projectDescription Advanced and extensible data dumping for Javascript. * @version 1.0.0 * @author Ariel Flesler * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} */ QUnit.jsDump = (function() { function quote( str ) { return '"' + str.toString().replace(/"/g, '\\"') + '"'; }; function literal( o ) { return o + ''; }; function join( pre, arr, post ) { var s = jsDump.separator(), base = jsDump.indent(), inner = jsDump.indent(1); if ( arr.join ) arr = arr.join( ',' + s + inner ); if ( !arr ) return pre + post; return [ pre, inner + arr, base + post ].join(s); }; function array( arr ) { var i = arr.length, ret = Array(i); this.up(); while ( i-- ) ret[i] = this.parse( arr[i] ); this.down(); return join( '[', ret, ']' ); }; var reName = /^function (\w+)/; var jsDump = { parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance var parser = this.parsers[ type || this.typeOf(obj) ]; type = typeof parser; return type == 'function' ? parser.call( this, obj ) : type == 'string' ? parser : this.parsers.error; }, typeOf:function( obj ) { var type; if ( obj === null ) { type = "null"; } else if (typeof obj === "undefined") { type = "undefined"; } else if (QUnit.is("RegExp", obj)) { type = "regexp"; } else if (QUnit.is("Date", obj)) { type = "date"; } else if (QUnit.is("Function", obj)) { type = "function"; } else if (QUnit.is("Array", obj)) { type = "array"; } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) { type = "window"; } else if (QUnit.is("HTMLDocument", obj)) { type = "document"; } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) { type = "nodelist"; } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) { type = "node"; } else { type = typeof obj; } return type; }, separator:function() { return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; }, indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing if ( !this.multiline ) return ''; var chr = this.indentChar; if ( this.HTML ) chr = chr.replace(/\t/g,' ').replace(/ /g,' '); return Array( this._depth_ + (extra||0) ).join(chr); }, up:function( a ) { this._depth_ += a || 1; }, down:function( a ) { this._depth_ -= a || 1; }, setParser:function( name, parser ) { this.parsers[name] = parser; }, // The next 3 are exposed so you can use them quote:quote, literal:literal, join:join, // _depth_: 1, // This is the list of parsers, to modify them, use jsDump.setParser parsers:{ window: '[Window]', document: '[Document]', error:'[ERROR]', //when no parser is found, shouldn't happen unknown: '[Unknown]', 'null':'null', undefined:'undefined', 'function':function( fn ) { var ret = 'function', name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE if ( name ) ret += ' ' + name; ret += '('; ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); return join( ret, this.parse(fn,'functionCode'), '}' ); }, array: array, nodelist: array, arguments: array, object:function( map ) { var ret = [ ]; this.up(); for ( var key in map ) ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); this.down(); return join( '{', ret, '}' ); }, node:function( node ) { var open = this.HTML ? '<' : '<', close = this.HTML ? '>' : '>'; var tag = node.nodeName.toLowerCase(), ret = open + tag; for ( var a in this.DOMAttrs ) { var val = node[this.DOMAttrs[a]]; if ( val ) ret += ' ' + a + '=' + this.parse( val, 'attribute' ); } return ret + close + open + '/' + tag + close; }, functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function var l = fn.length; if ( !l ) return ''; var args = Array(l); while ( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a' return ' ' + args.join(', ') + ' '; }, key:quote, //object calls it internally, the key part of an item in a map functionCode:'[code]', //function calls it internally, it's the content of the function attribute:quote, //node calls it internally, it's an html attribute value string:quote, date:quote, regexp:literal, //regex number:literal, 'boolean':literal }, DOMAttrs:{//attributes to dump from nodes, name=>realName id:'id', name:'name', 'class':'className' }, HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) indentChar:' ',//indentation unit multiline:true //if true, items in a collection, are separated by a \n, else just a space. }; return jsDump; })(); })(this);