reporter.js 99 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622
  1. var Test = {};var isCommonJS = typeof window == "undefined" && typeof exports == "object";
  2. /**
  3. * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
  4. *
  5. * @namespace
  6. */
  7. var jasmine = {};
  8. if (isCommonJS) exports.jasmine = jasmine;
  9. /**
  10. * @private
  11. */
  12. jasmine.unimplementedMethod_ = function() {
  13. throw new Error("unimplemented method");
  14. };
  15. /**
  16. * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
  17. * a plain old variable and may be redefined by somebody else.
  18. *
  19. * @private
  20. */
  21. jasmine.undefined = jasmine.___undefined___;
  22. /**
  23. * Show diagnostic messages in the console if set to true
  24. *
  25. */
  26. jasmine.VERBOSE = false;
  27. /**
  28. * 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.
  29. *
  30. */
  31. jasmine.DEFAULT_UPDATE_INTERVAL = 250;
  32. /**
  33. * Maximum levels of nesting that will be included when an object is pretty-printed
  34. */
  35. jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
  36. /**
  37. * Default timeout interval in milliseconds for waitsFor() blocks.
  38. */
  39. jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
  40. /**
  41. * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
  42. * Set to false to let the exception bubble up in the browser.
  43. *
  44. */
  45. jasmine.CATCH_EXCEPTIONS = true;
  46. jasmine.getGlobal = function() {
  47. function getGlobal() {
  48. return this;
  49. }
  50. return getGlobal();
  51. };
  52. /**
  53. * Allows for bound functions to be compared. Internal use only.
  54. *
  55. * @ignore
  56. * @private
  57. * @param base {Object} bound 'this' for the function
  58. * @param name {Function} function to find
  59. */
  60. jasmine.bindOriginal_ = function(base, name) {
  61. var original = base[name];
  62. if (original.apply) {
  63. return function() {
  64. return original.apply(base, arguments);
  65. };
  66. } else {
  67. // IE support
  68. return jasmine.getGlobal()[name];
  69. }
  70. };
  71. jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
  72. jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
  73. jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
  74. jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
  75. jasmine.MessageResult = function(values) {
  76. this.type = 'log';
  77. this.values = values;
  78. this.trace = new Error(); // todo: test better
  79. };
  80. jasmine.MessageResult.prototype.toString = function() {
  81. var text = "";
  82. for (var i = 0; i < this.values.length; i++) {
  83. if (i > 0) text += " ";
  84. if (jasmine.isString_(this.values[i])) {
  85. text += this.values[i];
  86. } else {
  87. text += jasmine.pp(this.values[i]);
  88. }
  89. }
  90. return text;
  91. };
  92. jasmine.ExpectationResult = function(params) {
  93. this.type = 'expect';
  94. this.matcherName = params.matcherName;
  95. this.passed_ = params.passed;
  96. this.expected = params.expected;
  97. this.actual = params.actual;
  98. this.message = this.passed_ ? 'Passed.' : params.message;
  99. var trace = (params.trace || new Error(this.message));
  100. this.trace = this.passed_ ? '' : trace;
  101. };
  102. jasmine.ExpectationResult.prototype.toString = function () {
  103. return this.message;
  104. };
  105. jasmine.ExpectationResult.prototype.passed = function () {
  106. return this.passed_;
  107. };
  108. /**
  109. * Getter for the Jasmine environment. Ensures one gets created
  110. */
  111. jasmine.getEnv = function() {
  112. var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
  113. return env;
  114. };
  115. /**
  116. * @ignore
  117. * @private
  118. * @param value
  119. * @returns {Boolean}
  120. */
  121. jasmine.isArray_ = function(value) {
  122. return jasmine.isA_("Array", value);
  123. };
  124. /**
  125. * @ignore
  126. * @private
  127. * @param value
  128. * @returns {Boolean}
  129. */
  130. jasmine.isString_ = function(value) {
  131. return jasmine.isA_("String", value);
  132. };
  133. /**
  134. * @ignore
  135. * @private
  136. * @param value
  137. * @returns {Boolean}
  138. */
  139. jasmine.isNumber_ = function(value) {
  140. return jasmine.isA_("Number", value);
  141. };
  142. /**
  143. * @ignore
  144. * @private
  145. * @param {String} typeName
  146. * @param value
  147. * @returns {Boolean}
  148. */
  149. jasmine.isA_ = function(typeName, value) {
  150. return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
  151. };
  152. /**
  153. * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
  154. *
  155. * @param value {Object} an object to be outputted
  156. * @returns {String}
  157. */
  158. jasmine.pp = function(value) {
  159. var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
  160. stringPrettyPrinter.format(value);
  161. return stringPrettyPrinter.string;
  162. };
  163. /**
  164. * Returns true if the object is a DOM Node.
  165. *
  166. * @param {Object} obj object to check
  167. * @returns {Boolean}
  168. */
  169. jasmine.isDomNode = function(obj) {
  170. return obj.nodeType > 0;
  171. };
  172. /**
  173. * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
  174. *
  175. * @example
  176. * // don't care about which function is passed in, as long as it's a function
  177. * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
  178. *
  179. * @param {Class} clazz
  180. * @returns matchable object of the type clazz
  181. */
  182. jasmine.any = function(clazz) {
  183. return new jasmine.Matchers.Any(clazz);
  184. };
  185. /**
  186. * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
  187. * attributes on the object.
  188. *
  189. * @example
  190. * // don't care about any other attributes than foo.
  191. * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
  192. *
  193. * @param sample {Object} sample
  194. * @returns matchable object for the sample
  195. */
  196. jasmine.objectContaining = function (sample) {
  197. return new jasmine.Matchers.ObjectContaining(sample);
  198. };
  199. /**
  200. * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
  201. *
  202. * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
  203. * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
  204. *
  205. * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
  206. *
  207. * Spies are torn down at the end of every spec.
  208. *
  209. * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
  210. *
  211. * @example
  212. * // a stub
  213. * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
  214. *
  215. * // spy example
  216. * var foo = {
  217. * not: function(bool) { return !bool; }
  218. * }
  219. *
  220. * // actual foo.not will not be called, execution stops
  221. * spyOn(foo, 'not');
  222. // foo.not spied upon, execution will continue to implementation
  223. * spyOn(foo, 'not').andCallThrough();
  224. *
  225. * // fake example
  226. * var foo = {
  227. * not: function(bool) { return !bool; }
  228. * }
  229. *
  230. * // foo.not(val) will return val
  231. * spyOn(foo, 'not').andCallFake(function(value) {return value;});
  232. *
  233. * // mock example
  234. * foo.not(7 == 7);
  235. * expect(foo.not).toHaveBeenCalled();
  236. * expect(foo.not).toHaveBeenCalledWith(true);
  237. *
  238. * @constructor
  239. * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
  240. * @param {String} name
  241. */
  242. jasmine.Spy = function(name) {
  243. /**
  244. * The name of the spy, if provided.
  245. */
  246. this.identity = name || 'unknown';
  247. /**
  248. * Is this Object a spy?
  249. */
  250. this.isSpy = true;
  251. /**
  252. * The actual function this spy stubs.
  253. */
  254. this.plan = function() {
  255. };
  256. /**
  257. * Tracking of the most recent call to the spy.
  258. * @example
  259. * var mySpy = jasmine.createSpy('foo');
  260. * mySpy(1, 2);
  261. * mySpy.mostRecentCall.args = [1, 2];
  262. */
  263. this.mostRecentCall = {};
  264. /**
  265. * Holds arguments for each call to the spy, indexed by call count
  266. * @example
  267. * var mySpy = jasmine.createSpy('foo');
  268. * mySpy(1, 2);
  269. * mySpy(7, 8);
  270. * mySpy.mostRecentCall.args = [7, 8];
  271. * mySpy.argsForCall[0] = [1, 2];
  272. * mySpy.argsForCall[1] = [7, 8];
  273. */
  274. this.argsForCall = [];
  275. this.calls = [];
  276. };
  277. /**
  278. * Tells a spy to call through to the actual implemenatation.
  279. *
  280. * @example
  281. * var foo = {
  282. * bar: function() { // do some stuff }
  283. * }
  284. *
  285. * // defining a spy on an existing property: foo.bar
  286. * spyOn(foo, 'bar').andCallThrough();
  287. */
  288. jasmine.Spy.prototype.andCallThrough = function() {
  289. this.plan = this.originalValue;
  290. return this;
  291. };
  292. /**
  293. * For setting the return value of a spy.
  294. *
  295. * @example
  296. * // defining a spy from scratch: foo() returns 'baz'
  297. * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
  298. *
  299. * // defining a spy on an existing property: foo.bar() returns 'baz'
  300. * spyOn(foo, 'bar').andReturn('baz');
  301. *
  302. * @param {Object} value
  303. */
  304. jasmine.Spy.prototype.andReturn = function(value) {
  305. this.plan = function() {
  306. return value;
  307. };
  308. return this;
  309. };
  310. /**
  311. * For throwing an exception when a spy is called.
  312. *
  313. * @example
  314. * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
  315. * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
  316. *
  317. * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
  318. * spyOn(foo, 'bar').andThrow('baz');
  319. *
  320. * @param {String} exceptionMsg
  321. */
  322. jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
  323. this.plan = function() {
  324. throw exceptionMsg;
  325. };
  326. return this;
  327. };
  328. /**
  329. * Calls an alternate implementation when a spy is called.
  330. *
  331. * @example
  332. * var baz = function() {
  333. * // do some stuff, return something
  334. * }
  335. * // defining a spy from scratch: foo() calls the function baz
  336. * var foo = jasmine.createSpy('spy on foo').andCall(baz);
  337. *
  338. * // defining a spy on an existing property: foo.bar() calls an anonymnous function
  339. * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
  340. *
  341. * @param {Function} fakeFunc
  342. */
  343. jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
  344. this.plan = fakeFunc;
  345. return this;
  346. };
  347. /**
  348. * Resets all of a spy's the tracking variables so that it can be used again.
  349. *
  350. * @example
  351. * spyOn(foo, 'bar');
  352. *
  353. * foo.bar();
  354. *
  355. * expect(foo.bar.callCount).toEqual(1);
  356. *
  357. * foo.bar.reset();
  358. *
  359. * expect(foo.bar.callCount).toEqual(0);
  360. */
  361. jasmine.Spy.prototype.reset = function() {
  362. this.wasCalled = false;
  363. this.callCount = 0;
  364. this.argsForCall = [];
  365. this.calls = [];
  366. this.mostRecentCall = {};
  367. };
  368. jasmine.createSpy = function(name) {
  369. var spyObj = function() {
  370. spyObj.wasCalled = true;
  371. spyObj.callCount++;
  372. var args = jasmine.util.argsToArray(arguments);
  373. spyObj.mostRecentCall.object = this;
  374. spyObj.mostRecentCall.args = args;
  375. spyObj.argsForCall.push(args);
  376. spyObj.calls.push({object: this, args: args});
  377. return spyObj.plan.apply(this, arguments);
  378. };
  379. var spy = new jasmine.Spy(name);
  380. for (var prop in spy) {
  381. spyObj[prop] = spy[prop];
  382. }
  383. spyObj.reset();
  384. return spyObj;
  385. };
  386. /**
  387. * Determines whether an object is a spy.
  388. *
  389. * @param {jasmine.Spy|Object} putativeSpy
  390. * @returns {Boolean}
  391. */
  392. jasmine.isSpy = function(putativeSpy) {
  393. return putativeSpy && putativeSpy.isSpy;
  394. };
  395. /**
  396. * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
  397. * large in one call.
  398. *
  399. * @param {String} baseName name of spy class
  400. * @param {Array} methodNames array of names of methods to make spies
  401. */
  402. jasmine.createSpyObj = function(baseName, methodNames) {
  403. if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
  404. throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
  405. }
  406. var obj = {};
  407. for (var i = 0; i < methodNames.length; i++) {
  408. obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
  409. }
  410. return obj;
  411. };
  412. /**
  413. * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
  414. *
  415. * Be careful not to leave calls to <code>jasmine.log</code> in production code.
  416. */
  417. jasmine.log = function() {
  418. var spec = jasmine.getEnv().currentSpec;
  419. spec.log.apply(spec, arguments);
  420. };
  421. /**
  422. * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
  423. *
  424. * @example
  425. * // spy example
  426. * var foo = {
  427. * not: function(bool) { return !bool; }
  428. * }
  429. * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
  430. *
  431. * @see jasmine.createSpy
  432. * @param obj
  433. * @param methodName
  434. * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
  435. */
  436. var spyOn = function(obj, methodName) {
  437. return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
  438. };
  439. if (isCommonJS) exports.spyOn = spyOn;
  440. /**
  441. * Creates a Jasmine spec that will be added to the current suite.
  442. *
  443. * // TODO: pending tests
  444. *
  445. * @example
  446. * it('should be true', function() {
  447. * expect(true).toEqual(true);
  448. * });
  449. *
  450. * @param {String} desc description of this specification
  451. * @param {Function} func defines the preconditions and expectations of the spec
  452. */
  453. var it = function(desc, func) {
  454. return jasmine.getEnv().it(desc, func);
  455. };
  456. if (isCommonJS) exports.it = it;
  457. /**
  458. * Creates a <em>disabled</em> Jasmine spec.
  459. *
  460. * A convenience method that allows existing specs to be disabled temporarily during development.
  461. *
  462. * @param {String} desc description of this specification
  463. * @param {Function} func defines the preconditions and expectations of the spec
  464. */
  465. var xit = function(desc, func) {
  466. return jasmine.getEnv().xit(desc, func);
  467. };
  468. if (isCommonJS) exports.xit = xit;
  469. /**
  470. * Starts a chain for a Jasmine expectation.
  471. *
  472. * It is passed an Object that is the actual value and should chain to one of the many
  473. * jasmine.Matchers functions.
  474. *
  475. * @param {Object} actual Actual value to test against and expected value
  476. * @return {jasmine.Matchers}
  477. */
  478. var expect = function(actual) {
  479. return jasmine.getEnv().currentSpec.expect(actual);
  480. };
  481. if (isCommonJS) exports.expect = expect;
  482. /**
  483. * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
  484. *
  485. * @param {Function} func Function that defines part of a jasmine spec.
  486. */
  487. var runs = function(func) {
  488. jasmine.getEnv().currentSpec.runs(func);
  489. };
  490. if (isCommonJS) exports.runs = runs;
  491. /**
  492. * Waits a fixed time period before moving to the next block.
  493. *
  494. * @deprecated Use waitsFor() instead
  495. * @param {Number} timeout milliseconds to wait
  496. */
  497. var waits = function(timeout) {
  498. jasmine.getEnv().currentSpec.waits(timeout);
  499. };
  500. if (isCommonJS) exports.waits = waits;
  501. /**
  502. * Waits for the latchFunction to return true before proceeding to the next block.
  503. *
  504. * @param {Function} latchFunction
  505. * @param {String} optional_timeoutMessage
  506. * @param {Number} optional_timeout
  507. */
  508. var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
  509. jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
  510. };
  511. if (isCommonJS) exports.waitsFor = waitsFor;
  512. /**
  513. * A function that is called before each spec in a suite.
  514. *
  515. * Used for spec setup, including validating assumptions.
  516. *
  517. * @param {Function} beforeEachFunction
  518. */
  519. var beforeEach = function(beforeEachFunction) {
  520. jasmine.getEnv().beforeEach(beforeEachFunction);
  521. };
  522. if (isCommonJS) exports.beforeEach = beforeEach;
  523. /**
  524. * A function that is called after each spec in a suite.
  525. *
  526. * Used for restoring any state that is hijacked during spec execution.
  527. *
  528. * @param {Function} afterEachFunction
  529. */
  530. var afterEach = function(afterEachFunction) {
  531. jasmine.getEnv().afterEach(afterEachFunction);
  532. };
  533. if (isCommonJS) exports.afterEach = afterEach;
  534. /**
  535. * Defines a suite of specifications.
  536. *
  537. * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
  538. * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
  539. * of setup in some tests.
  540. *
  541. * @example
  542. * // TODO: a simple suite
  543. *
  544. * // TODO: a simple suite with a nested describe block
  545. *
  546. * @param {String} description A string, usually the class under test.
  547. * @param {Function} specDefinitions function that defines several specs.
  548. */
  549. var describe = function(description, specDefinitions) {
  550. return jasmine.getEnv().describe(description, specDefinitions);
  551. };
  552. if (isCommonJS) exports.describe = describe;
  553. /**
  554. * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
  555. *
  556. * @param {String} description A string, usually the class under test.
  557. * @param {Function} specDefinitions function that defines several specs.
  558. */
  559. var xdescribe = function(description, specDefinitions) {
  560. return jasmine.getEnv().xdescribe(description, specDefinitions);
  561. };
  562. if (isCommonJS) exports.xdescribe = xdescribe;
  563. // Provide the XMLHttpRequest class for IE 5.x-6.x:
  564. jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
  565. function tryIt(f) {
  566. try {
  567. return f();
  568. } catch(e) {
  569. }
  570. return null;
  571. }
  572. var xhr = tryIt(function() {
  573. return new ActiveXObject("Msxml2.XMLHTTP.6.0");
  574. }) ||
  575. tryIt(function() {
  576. return new ActiveXObject("Msxml2.XMLHTTP.3.0");
  577. }) ||
  578. tryIt(function() {
  579. return new ActiveXObject("Msxml2.XMLHTTP");
  580. }) ||
  581. tryIt(function() {
  582. return new ActiveXObject("Microsoft.XMLHTTP");
  583. });
  584. if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
  585. return xhr;
  586. } : XMLHttpRequest;
  587. /**
  588. * @namespace
  589. */
  590. jasmine.util = {};
  591. /**
  592. * Declare that a child class inherit it's prototype from the parent class.
  593. *
  594. * @private
  595. * @param {Function} childClass
  596. * @param {Function} parentClass
  597. */
  598. jasmine.util.inherit = function(childClass, parentClass) {
  599. /**
  600. * @private
  601. */
  602. var subclass = function() {
  603. };
  604. subclass.prototype = parentClass.prototype;
  605. childClass.prototype = new subclass();
  606. };
  607. jasmine.util.formatException = function(e) {
  608. var lineNumber;
  609. if (e.line) {
  610. lineNumber = e.line;
  611. }
  612. else if (e.lineNumber) {
  613. lineNumber = e.lineNumber;
  614. }
  615. var file;
  616. if (e.sourceURL) {
  617. file = e.sourceURL;
  618. }
  619. else if (e.fileName) {
  620. file = e.fileName;
  621. }
  622. var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
  623. if (file && lineNumber) {
  624. message += ' in ' + file + ' (line ' + lineNumber + ')';
  625. }
  626. return message;
  627. };
  628. jasmine.util.htmlEscape = function(str) {
  629. if (!str) return str;
  630. return str.replace(/&/g, '&amp;')
  631. .replace(/</g, '&lt;')
  632. .replace(/>/g, '&gt;');
  633. };
  634. jasmine.util.argsToArray = function(args) {
  635. var arrayOfArgs = [];
  636. for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
  637. return arrayOfArgs;
  638. };
  639. jasmine.util.extend = function(destination, source) {
  640. for (var property in source) destination[property] = source[property];
  641. return destination;
  642. };
  643. /**
  644. * Base class for pretty printing for expectation results.
  645. */
  646. jasmine.PrettyPrinter = function() {
  647. this.ppNestLevel_ = 0;
  648. };
  649. /**
  650. * Formats a value in a nice, human-readable string.
  651. *
  652. * @param value
  653. */
  654. jasmine.PrettyPrinter.prototype.format = function(value) {
  655. this.ppNestLevel_++;
  656. try {
  657. if (value === jasmine.undefined) {
  658. this.emitScalar('undefined');
  659. } else if (value === null) {
  660. this.emitScalar('null');
  661. } else if (value === jasmine.getGlobal()) {
  662. this.emitScalar('<global>');
  663. } else if (value.jasmineToString) {
  664. this.emitScalar(value.jasmineToString());
  665. } else if (typeof value === 'string') {
  666. this.emitString(value);
  667. } else if (jasmine.isSpy(value)) {
  668. this.emitScalar("spy on " + value.identity);
  669. } else if (value instanceof RegExp) {
  670. this.emitScalar(value.toString());
  671. } else if (typeof value === 'function') {
  672. this.emitScalar('Function');
  673. } else if (typeof value.nodeType === 'number') {
  674. this.emitScalar('HTMLNode');
  675. } else if (value instanceof Date) {
  676. this.emitScalar('Date(' + value + ')');
  677. } else if (value.__Jasmine_been_here_before__) {
  678. this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
  679. } else if (jasmine.isArray_(value) || typeof value == 'object') {
  680. value.__Jasmine_been_here_before__ = true;
  681. if (jasmine.isArray_(value)) {
  682. this.emitArray(value);
  683. } else {
  684. this.emitObject(value);
  685. }
  686. delete value.__Jasmine_been_here_before__;
  687. } else {
  688. this.emitScalar(value.toString());
  689. }
  690. } finally {
  691. this.ppNestLevel_--;
  692. }
  693. };
  694. jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
  695. for (var property in obj) {
  696. if (!obj.hasOwnProperty(property)) continue;
  697. if (property == '__Jasmine_been_here_before__') continue;
  698. fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
  699. obj.__lookupGetter__(property) !== null) : false);
  700. }
  701. };
  702. jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
  703. jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
  704. jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
  705. jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
  706. jasmine.StringPrettyPrinter = function() {
  707. jasmine.PrettyPrinter.call(this);
  708. this.string = '';
  709. };
  710. jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
  711. jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
  712. this.append(value);
  713. };
  714. jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
  715. this.append("'" + value + "'");
  716. };
  717. jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
  718. if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
  719. this.append("Array");
  720. return;
  721. }
  722. this.append('[ ');
  723. for (var i = 0; i < array.length; i++) {
  724. if (i > 0) {
  725. this.append(', ');
  726. }
  727. this.format(array[i]);
  728. }
  729. this.append(' ]');
  730. };
  731. jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
  732. if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
  733. this.append("Object");
  734. return;
  735. }
  736. var self = this;
  737. this.append('{ ');
  738. var first = true;
  739. this.iterateObject(obj, function(property, isGetter) {
  740. if (first) {
  741. first = false;
  742. } else {
  743. self.append(', ');
  744. }
  745. self.append(property);
  746. self.append(' : ');
  747. if (isGetter) {
  748. self.append('<getter>');
  749. } else {
  750. self.format(obj[property]);
  751. }
  752. });
  753. this.append(' }');
  754. };
  755. jasmine.StringPrettyPrinter.prototype.append = function(value) {
  756. this.string += value;
  757. };
  758. /**
  759. * Formats a value in a nice, human-readable string.
  760. *
  761. * @param value
  762. */
  763. jasmine.PrettyPrinter.prototype.format = function(value) {
  764. if (this.ppNestLevel_ > 40) {
  765. throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
  766. }
  767. this.ppNestLevel_++;
  768. try {
  769. if (value === jasmine.undefined) {
  770. this.emitScalar('undefined');
  771. } else if (value === null) {
  772. this.emitScalar('null');
  773. } else if (value === jasmine.getGlobal()) {
  774. this.emitScalar('<global>');
  775. } else if (value.expectedClass) { //override of value instanceof jasmine.Matchers.Any
  776. this.emitScalar(value.toString());
  777. } else if (typeof value === 'string') {
  778. this.emitString(value);
  779. } else if (jasmine.isSpy(value)) {
  780. this.emitScalar("spy on " + value.identity);
  781. } else if (value instanceof RegExp) {
  782. this.emitScalar(value.toString());
  783. } else if (typeof value === 'function') {
  784. this.emitScalar('Function');
  785. } else if (typeof value.nodeType === 'number') {
  786. this.emitScalar('HTMLNode');
  787. } else if (value instanceof Date) {
  788. this.emitScalar('Date(' + value + ')');
  789. } else if (value.__Jasmine_been_here_before__) {
  790. this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
  791. } else if (jasmine.isArray_(value) || typeof value == 'object') {
  792. value.__Jasmine_been_here_before__ = true;
  793. if (jasmine.isArray_(value)) {
  794. this.emitArray(value);
  795. } else {
  796. this.emitObject(value);
  797. }
  798. delete value.__Jasmine_been_here_before__;
  799. } else {
  800. this.emitScalar(value.toString());
  801. }
  802. } catch (e) {
  803. } finally {
  804. this.ppNestLevel_--;
  805. }
  806. };
  807. // Extend: creates whitespaces indent
  808. jasmine.StringPrettyPrinter.prototype.getIndent = function () {
  809. var whiteSpaces = "",
  810. i;
  811. for (i = 0; i < this.ws; i++) {
  812. whiteSpaces += " ";
  813. }
  814. return whiteSpaces;
  815. };
  816. // Override: pre-format object
  817. jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
  818. var self = this,
  819. first = true,
  820. indent;
  821. this.append('{\n');
  822. if(!this.ws) {
  823. this.ws = 0;
  824. }
  825. this.ws += 4;
  826. indent = this.getIndent();
  827. var i = 0;
  828. this.iterateObject(obj, function(property, isGetter) {
  829. if (first) {
  830. first = false;
  831. } else {
  832. self.append(',\n');
  833. }
  834. self.append(indent + property);
  835. self.append(' : ');
  836. if (isGetter) {
  837. self.append('<getter>');
  838. } else {
  839. if (typeof obj[property] !== "object") {
  840. self.format(obj[property]);
  841. } else {
  842. self.append("<Object>");
  843. }
  844. }
  845. });
  846. this.ws -= 4;
  847. indent = this.getIndent();
  848. this.append(indent + '\n'+ indent +'}');
  849. };
  850. /**
  851. * Basic browsers detection.
  852. */
  853. jasmine.browser = {};
  854. jasmine.browser.isIE = !!window.ActiveXObject;
  855. jasmine.browser.isIE6 = jasmine.browser.isIE && !window.XMLHttpRequest;
  856. jasmine.browser.isIE7 = jasmine.browser.isIE && !!window.XMLHttpRequest && !document.documentMode;
  857. jasmine.browser.isIE8 = jasmine.browser.isIE && !!window.XMLHttpRequest && !!document.documentMode && !window.performance;
  858. jasmine.browser.isIE9 = jasmine.browser.isIE && !!window.performance;
  859. jasmine.browser.isSafari3 = /safari/.test(navigator.userAgent.toLowerCase()) && /version\/3/.test(navigator.userAgent.toLowerCase());
  860. jasmine.browser.isOpera = !!window.opera;
  861. jasmine.browser.isOpera11 = jasmine.browser.isOpera && parseInt(window.opera.version(), 10) > 10;
  862. jasmine.array = {};
  863. /**
  864. * Checks whether or not the specified item exists in the array.
  865. * Array.prototype.indexOf is missing in Internet Explorer, unfortunately.
  866. * We always have to use this static method instead for consistency
  867. * @param {Array} array The array to check
  868. * @param {Mixed} item The item to look for
  869. * @param {Number} from (Optional) The index at which to begin the search
  870. * @return {Number} The index of item in the array (or -1 if it is not found)
  871. */
  872. jasmine.array.indexOf = function(array, item, from){
  873. if (array.indexOf) {
  874. return array.indexOf(item, from);
  875. }
  876. var i, length = array.length;
  877. for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){
  878. if (array[i] === item) {
  879. return i;
  880. }
  881. }
  882. return -1;
  883. };
  884. /**
  885. * Removes the specified item from the array. If the item is not found nothing happens.
  886. * @param {Array} array The array
  887. * @param {Mixed} item The item to remove
  888. * @return {Array} The passed array itself
  889. */
  890. jasmine.array.remove = function(array, item) {
  891. var index = this.indexOf(array, item);
  892. if (index !== -1) {
  893. array.splice(index, 1);
  894. }
  895. return array;
  896. };/**
  897. * Creates an HTMLElement.
  898. * @param {Object/HTMLElement} config Ext DomHelper style element config object.
  899. * If no tag is specified (e.g., {tag:'input'}) then a div will be automatically generated with the specified attributes.
  900. * @return {HTMLElement} The created HTMLElement
  901. */
  902. jasmine.Dom = function(config) {
  903. var element, children, length, child, i, property;
  904. config = config || {};
  905. if (config.tagName) {
  906. return config;
  907. }
  908. element = document.createElement(config.tag || "div");
  909. children = config.children || [];
  910. length = children.length;
  911. delete config.tag;
  912. for (i = 0; i < length; i++) {
  913. child = children[i];
  914. element.appendChild(new jasmine.Dom(child));
  915. }
  916. delete config.children;
  917. if (config.cls) {
  918. jasmine.Dom.setCls(element, config.cls);
  919. delete config.cls;
  920. }
  921. if (config.html) {
  922. jasmine.Dom.setHTML(element, config.html);
  923. delete config.html;
  924. }
  925. if (config.style) {
  926. jasmine.Dom.setStyle(element, config.style);
  927. delete config.style;
  928. }
  929. for (property in config) {
  930. if (!config.hasOwnProperty(property)) {
  931. continue;
  932. }
  933. element[property] = config[property];
  934. }
  935. return element;
  936. };
  937. /**
  938. * Adds className to an HTMLElement.
  939. * @param {HTMLElement} element The HTMLElement
  940. * @param {String} cls The className string
  941. */
  942. jasmine.Dom.addCls = function (element, cls) {
  943. var split, length, i;
  944. if (!element.className) {
  945. jasmine.Dom.setCls(element, cls);
  946. return;
  947. }
  948. split = element.className.split(" ");
  949. length = split.length;
  950. for (i = 0; i < length; i++) {
  951. if (split[i] == cls) {
  952. return;
  953. }
  954. }
  955. element.className = element.className + " " + cls;
  956. };
  957. /**
  958. * Removes className to HTMLElement.
  959. * @param {HTMLElement} element The HTMLElement
  960. * @param {String} cls The className string
  961. */
  962. jasmine.Dom.removeCls = function(element, cls) {
  963. var split, length, classArray, i;
  964. if (!element.className) {
  965. return;
  966. }
  967. classArray = [];
  968. split = element.className.split(" ");
  969. length = split.length;
  970. for (i = 0; i < length; i++) {
  971. if (split[i] !== cls) {
  972. classArray.push(split[i]);
  973. }
  974. }
  975. element.className = classArray.join(" ");
  976. };
  977. /**
  978. * Checks if a dom element has a className.
  979. * @param {HTMLElement} element The HTMLElement
  980. * @param {String} cls The className string
  981. * @return {Boolean}
  982. */
  983. jasmine.Dom.hasCls = function(element, cls) {
  984. var split, length, classArray, i;
  985. if (!element.className) {
  986. return;
  987. }
  988. split = element.className.split(" ");
  989. length = split.length;
  990. for (i = 0; i < length; i++) {
  991. if (split[i] === cls) {
  992. return true;
  993. }
  994. }
  995. return false;
  996. };
  997. /**
  998. * Sets HTMLElement className.
  999. * @param {HTMLElement} element The HTMLElement
  1000. * @param {String} cls The className string
  1001. */
  1002. jasmine.Dom.setCls = function(element, cls) {
  1003. element.className = cls;
  1004. };
  1005. /**
  1006. * Sets HTMLElement innerHTML
  1007. * @param {HTMLElement} element The HTMLElement
  1008. * @param {String} html The innerHTML text
  1009. */
  1010. jasmine.Dom.setHTML = function(element, html) {
  1011. element.innerHTML = html;
  1012. };
  1013. /**
  1014. * Sets HTMLElement style
  1015. * @param {HTMLElement} element The HTMLElement
  1016. * @param {String} style The style property to set
  1017. */
  1018. jasmine.Dom.setStyle = function(element, style) {
  1019. var property;
  1020. for (property in style) {
  1021. if (style.hasOwnProperty(property)) {
  1022. element.style[property] = style[property];
  1023. }
  1024. }
  1025. };
  1026. Test.OptionsImpl = function() {
  1027. this.optionCheckBoxesEl = {};
  1028. this.options = this.urlDecode(window.location.search.substring(1));
  1029. this.options.remote = window.location.toString().search("http:") !== -1;
  1030. this.startAutoReloadTask();
  1031. };
  1032. Test.OptionsImpl.prototype.get = function() {
  1033. return this.options;
  1034. };
  1035. /**
  1036. * Takes an object and converts it to an encoded URL.
  1037. * @param {Object} o The object to encode
  1038. * @return {String}
  1039. */
  1040. Test.OptionsImpl.prototype.urlEncode = function(object) {
  1041. var buf = [],
  1042. e = encodeURIComponent,
  1043. value, property, length, i;
  1044. for (property in object) {
  1045. if(!object.hasOwnProperty(property)) {
  1046. continue;
  1047. }
  1048. value = object[property];
  1049. if (jasmine.isArray_(value)) {
  1050. length = value.length;
  1051. for (i = 0; i < length; i++) {
  1052. buf.push(property + '=' + e(value[i]));
  1053. }
  1054. } else {
  1055. buf.push(property + '=' + e(value));
  1056. }
  1057. }
  1058. return buf.join('&');
  1059. };
  1060. Test.hashString = function (s, hash) {
  1061. hash = hash || 0;
  1062. // see http://www.cse.yorku.ca/~oz/hash.html
  1063. for (var c, i = 0, n = s.length; i < n; ++i) {
  1064. c = s.charCodeAt(i);
  1065. hash = c + (hash << 6) + (hash << 16) - hash;
  1066. }
  1067. return hash;
  1068. };
  1069. /**
  1070. * Takes an encoded URL and and converts it to an object. Example:
  1071. * @param {String} string
  1072. * @return {Object} A literal with members
  1073. */
  1074. Test.OptionsImpl.prototype.urlDecode = function(string) {
  1075. var obj = {},
  1076. pairs, d, name, value, pair, i, length;
  1077. if (string != "") {
  1078. pairs = string.split('&');
  1079. d = decodeURIComponent;
  1080. length = pairs.length;
  1081. for (i = 0; i < length; i++) {
  1082. pair = pairs[i].split('=');
  1083. name = d(pair[0]);
  1084. value = d(pair[1]);
  1085. obj[name] = !obj[name] ? value : [].concat(obj[name]).concat(value);
  1086. }
  1087. }
  1088. function parseStringOrId (str) {
  1089. var id = parseInt(str, 10);
  1090. if (String(id) !== str) {
  1091. id = Test.hashString(str);
  1092. }
  1093. return id;
  1094. }
  1095. if (obj.specs) {
  1096. obj.specs = jasmine.isArray_(obj.specs) ? obj.specs : [obj.specs];
  1097. length = obj.specs.length;
  1098. for (i = 0; i < length; i++) {
  1099. obj.specs[i] = parseStringOrId(obj.specs[i]);
  1100. }
  1101. } else {
  1102. obj.specs = [];
  1103. }
  1104. if (obj.suites) {
  1105. obj.suites = jasmine.isArray_(obj.suites) ? obj.suites : [obj.suites];
  1106. length = obj.suites.length;
  1107. for (i = 0; i < length; i++) {
  1108. obj.suites[i] = parseStringOrId(obj.suites[i]);
  1109. }
  1110. } else {
  1111. obj.suites = [];
  1112. }
  1113. return obj;
  1114. };
  1115. /**
  1116. * Renders option checkbox and label.
  1117. * @param {String} name The option name.
  1118. * @param {String} labelText The label text.
  1119. * @return {HTMLElement} The option HTMLElement
  1120. */
  1121. Test.OptionsImpl.prototype.renderCheckbox = function(name, labelText) {
  1122. var me = this,
  1123. checkbox = new jasmine.Dom({
  1124. tag: "input",
  1125. cls: "option " + name,
  1126. type: "checkbox",
  1127. onclick: function() {
  1128. me.onCheckboxClick.apply(me, arguments);
  1129. }
  1130. });
  1131. me.optionCheckBoxesEl[name] = checkbox;
  1132. return new jasmine.Dom({
  1133. tag: "span",
  1134. cls: "show",
  1135. children: [checkbox,{
  1136. tag: "label",
  1137. html: labelText
  1138. }]
  1139. });
  1140. };
  1141. /**
  1142. * Checks options checkboxs if needed.
  1143. */
  1144. Test.OptionsImpl.prototype.check = function() {
  1145. var property, checkbox;
  1146. for (property in this.options) {
  1147. if (!this.options.hasOwnProperty(property)) {
  1148. continue;
  1149. }
  1150. checkbox = this.optionCheckBoxesEl[property];
  1151. if (checkbox) {
  1152. checkbox.checked = this.options[property];
  1153. }
  1154. }
  1155. };
  1156. /**
  1157. * Options checkbox check/uncked handler.
  1158. * @param {HTMLElement} el The checkbox HTMLElement
  1159. */
  1160. Test.OptionsImpl.prototype.onCheckboxClick = function(event) {
  1161. var el, opt, row, length, i;
  1162. event = event || window.event;
  1163. el = event.target || event.srcElement;
  1164. opt = el.className.split(" ")[1];
  1165. if (el.checked) {
  1166. this.options[opt] = true;
  1167. } else {
  1168. delete this.options[opt];
  1169. }
  1170. };
  1171. /**
  1172. * Reloads current page with reporter options.
  1173. */
  1174. Test.OptionsImpl.prototype.reloadWindow = function(reset) {
  1175. if (reset) {
  1176. this.options.specs = [];
  1177. this.options.suites = [];
  1178. }
  1179. window.location.search = this.urlEncode(this.options);
  1180. };
  1181. /**
  1182. * Starts autoReload task.
  1183. */
  1184. Test.OptionsImpl.prototype.startAutoReloadTask = function() {
  1185. var me = this;
  1186. if (me.options.autoReload) {
  1187. var interval = setInterval(function() {
  1188. if (Test.SandBox.isRunning()) {
  1189. clearInterval(interval);
  1190. setTimeout(function() {
  1191. me.reloadWindow();
  1192. }, 2000);
  1193. }
  1194. }, 1500);
  1195. }
  1196. };
  1197. Test.OptionsImpl.prototype.isChecked = function(o) {
  1198. var specs = this.options.specs,
  1199. suites = this.options.suites,
  1200. id = o.id;
  1201. if (o.suite) {
  1202. return specs && jasmine.array.indexOf(specs, id) !== -1;
  1203. } else {
  1204. return suites && jasmine.array.indexOf(suites, id) !== -1;
  1205. }
  1206. return false;
  1207. };
  1208. Test.Options = new Test.OptionsImpl();Test.SandBoxImpl = function(){};
  1209. Test.SandBoxImpl.prototype.domReady = function(fn) {
  1210. if (document.addEventListener) {
  1211. window.addEventListener('load', fn, false);
  1212. } else {
  1213. window.attachEvent('onload', fn, false);
  1214. }
  1215. };
  1216. Test.SandBoxImpl.prototype.setup = function(config) {
  1217. var me = this;
  1218. me.requires = config.requires;
  1219. me.domReady(function() {
  1220. me.reporter = new Test.Reporter();
  1221. me.createIframe();
  1222. });
  1223. };
  1224. Test.SandBoxImpl.prototype.createIframe = function() {
  1225. var me = this,
  1226. iframe,
  1227. win,
  1228. doc;
  1229. me.options = Test.Options.get();
  1230. var src = me.options.quirksMode ? 'iframe-quirks.html?loadSpecs=true' : 'iframe.html?loadSpecs=true';
  1231. src += '&compiled=' + !!me.options.compiled;
  1232. if (me.options.specsset) {
  1233. src += '&specsset=' + me.options.specsset;
  1234. }
  1235. iframe = new jasmine.Dom({
  1236. tag: "iframe",
  1237. cls: "sandboxIframe",
  1238. name: "sandbox",
  1239. frameBorder: 0,
  1240. src: src
  1241. });
  1242. me.reporter.getIframeContainer().appendChild(iframe);
  1243. win = iframe.contentWindow || window.frames[iframe.name];
  1244. doc = iframe.contentDocument || win.document;
  1245. this.iframe = iframe;
  1246. this.win = win;
  1247. this.doc = doc;
  1248. };
  1249. Test.SandBoxImpl.prototype.getIframe = function() {
  1250. return this.iframe;
  1251. };
  1252. Test.SandBoxImpl.prototype.getWin = function() {
  1253. return this.win;
  1254. };
  1255. Test.SandBoxImpl.prototype.getDoc = function() {
  1256. return this.doc;
  1257. };
  1258. Test.SandBoxImpl.prototype.getBody = function() {
  1259. return this.getDoc().body;
  1260. };
  1261. Test.SandBoxImpl.prototype.getHead = function() {
  1262. return this.getDoc().getElementsByTagName("head")[0];
  1263. };
  1264. Test.SandBoxImpl.prototype.save = function(spec) {
  1265. var doc = this.getDoc(),
  1266. sb = doc.createElement("div"),
  1267. body = this.getBody(),
  1268. children = body && body.childNodes || [],
  1269. length = children.length,
  1270. i = 0,
  1271. child,
  1272. lwas = this.lengthWas || (this.lengthWas = 0);
  1273. if (!this.options || !this.options.disableBodyClean) {
  1274. //this.clearComponents();
  1275. //this.clearDomElements();
  1276. }
  1277. if (length != lwas) {
  1278. if (!window.headless) {
  1279. this.reporter.log(">> Warning the document.body dom element contains childNodes after spec execution !<br/>" +
  1280. "Spec : " + jasmine.util.htmlEscape(spec.getFullName()) + ' <a href="?' +
  1281. Test.Options.urlEncode({specs: [spec.id], suites:[], disableBodyClean: true}) + '">Load this spec only and disable body autoclean</a><br/>',
  1282. "warning");
  1283. } else {
  1284. this.reporter.log("Warning: " + spec.getFullName() + "doesn't clean properly the document.body.");
  1285. }
  1286. this.lengthWas = length;
  1287. }
  1288. };
  1289. Test.SandBoxImpl.prototype.clearDomElements = function() {
  1290. var doc = this.getDoc(),
  1291. bd = this.getBody(),
  1292. children = bd.childNodes,
  1293. length = children.length,
  1294. i, child;
  1295. if (!this.options.disableBodyClean) {
  1296. for (i = 0; i < length; i++) {
  1297. child = children[i];
  1298. if (child) {
  1299. bd.removeChild(child);
  1300. }
  1301. }
  1302. }
  1303. };
  1304. Test.SandBoxImpl.prototype.clearComponents = function() {
  1305. var me = this,
  1306. win = me.getWin(),
  1307. comps, c, len, i;
  1308. if(win.Ext && win.Ext.ComponentManager) {
  1309. comps = win.Ext.ComponentManager.all.getArray();
  1310. len = comps.length;
  1311. for(i=0; i<len; i++) {
  1312. c = comps[i];
  1313. c.destroy();
  1314. }
  1315. }
  1316. };
  1317. Test.SandBoxImpl.prototype.isRunning = function() {
  1318. return !this.getWin().jasmine.getEnv().currentRunner_.queue.isRunning();
  1319. };
  1320. Test.SandBoxImpl.prototype.iScope = function(o) {
  1321. if (typeof o === "function") {
  1322. o = "(" + o.toString() + ")();";
  1323. }
  1324. return Test.SandBox.getWin().eval(o);
  1325. };
  1326. Test.SandBox = new Test.SandBoxImpl();
  1327. var iScope = Test.SandBox.iScope; /**
  1328. * @class Test.CodeHighLighter
  1329. * A javascript simple source code higlighter and beautifier (optional).
  1330. */
  1331. Test.CodeHighLighter = function(config) {
  1332. /**
  1333. * @cfg {String} source The source string to process.
  1334. */
  1335. this.source = config.source;
  1336. this.lineNumber = config.lineNumber;
  1337. this.linesFromJsCoverage = config.linesFromJsCoverage;
  1338. this.beautify = config.beautify || this.lineNumber === undefined;
  1339. this.highLightCode = config.highLightCode === false ? false : true;
  1340. this.matchedComments = [];
  1341. this.matchedStrings = [];
  1342. };
  1343. /**
  1344. * Regular expressions.
  1345. */
  1346. Test.CodeHighLighter.prototype.regExps = {
  1347. strings: /"([^\\"\n]|\\.)*"|'([^\\'\n]|\\.)*'|"([^\\"\n]|\\\n)*"|'([^\\'\n]|\\\n)*'/gm,
  1348. comments: /\/\/.*$|\/\*[\s\S]*?\*\//gm,
  1349. operators: /([\+\-\*\/=\?!]{1,3}|[\-\+]{1,2})/g,
  1350. numbers: /\b([0-9]+)\b/g,
  1351. keywords: [/\b(break)\b/g, /\b(case)\b/g, /\b(catch)\b/g, /\b(continue)\b/g, /\b(default)\b/g,
  1352. /\b(delete)\b/g, /\b(do)\b/g, /\b(else)\b/g, /\b(false)\b/g, /\b(for)\b/g, /\b(function)\b/g,
  1353. /\b(if)\b/g, /\b(in)\b/g, /\b(instanceof)\b/g, /\b(new)\b/g, /\b(null)\b/g,
  1354. /\b(return)\b/g, /\b(switch)\b/g, /\b(this)\b/g, /\b(throw)\b/g, /\b(true)\b/g,
  1355. /\b(try)\b/g,/\b(typeof)\b/g, /\b(var)\b/g, /\b(while)\b/g, /\b(with)\b/g],
  1356. commasInsideParenthesis: /\(([^\(\)\{\}])+\)/g,
  1357. arrayWithOneElement: /\[\n([^,\]]*)\n\]/g,
  1358. commaBracket: /,\n\s*\{/g,
  1359. multipleWhiteSpaces: /(\s+)/g,
  1360. semiColon: /;/g,
  1361. comma: /,/g,
  1362. openedBrackets: /([\{\[])/g,
  1363. closedBrackets: /([\}\]])/g,
  1364. emptyObject: /\{\n\s*\n\}/g,
  1365. openedBracketsWithNewLine: /[\{\[]$/g,
  1366. closedBracketsWithNewLine: /^\s*[\}\]]/g,
  1367. unwantedNewLines: /\n([\n,;\)])/g,
  1368. newLine: /\n/g,
  1369. firstSpaces: /^(\s)+/
  1370. };
  1371. /**
  1372. * Populates an array of matched objects.
  1373. * @param {String} value The match result.
  1374. * @param {Number} index The index of the match.
  1375. * @param {Array} matchedObjects The array of matches to populate.
  1376. * @param {String} css The css to apply to the match.
  1377. * @return {Boolean} Returns <tt>true</tt> is the match is inside another.
  1378. */
  1379. Test.CodeHighLighter.prototype.matchObjects = function(value, index, matchedObjects, css) {
  1380. matchedObjects.push({
  1381. origValue: value,
  1382. value: '<span class="jsHl'+ css +'">' + jasmine.util.htmlEscape(value).replace("$","$\b") + '</span>',
  1383. start: index,
  1384. end: index + value.length
  1385. });
  1386. };
  1387. /**
  1388. * Checks if a match is inside another matches.
  1389. * @param {Object} matchedObject The checked match.
  1390. * @param {Array} matchedOthers The array that contains other matches.
  1391. * @return {Boolean} Returns <tt>true</tt> is the match is inside another.
  1392. */
  1393. Test.CodeHighLighter.prototype.isInside = function(matchedObject, matchedOthers) {
  1394. var start = matchedObject.start,
  1395. end = matchedObject.end,
  1396. length = matchedOthers.length,
  1397. matchedOther, i;
  1398. for (i = 0; i < length; i++) {
  1399. matchedOther = matchedOthers[i];
  1400. if (matchedOther.start < start && start < matchedOther.end) {
  1401. return true;
  1402. }
  1403. }
  1404. return false;
  1405. };
  1406. /**
  1407. * This function get rid of any matches that are inside of other matches.
  1408. * If a match isn't inside another it is replaced by a string in {@link #source}
  1409. * in order to protect it from {@link #processOperatorsNumbersKeywords} replace tricks.
  1410. * @param {Array} matchedObjects The array of matches to check.
  1411. * @param {Array} matchedOthers The array that contains other matches.
  1412. * @param {String} protect The replacement string
  1413. */
  1414. Test.CodeHighLighter.prototype.fixOverlaps = function(matchedObjects, matchedOthers, protect) {
  1415. var result = [],
  1416. length = matchedObjects.length,
  1417. matchedObject,
  1418. i;
  1419. for (i = 0; i < length; i++) {
  1420. matchedObject = matchedObjects[i];
  1421. if (!this.isInside(matchedObject, matchedOthers)) {
  1422. this.source = this.source.replace(matchedObject.origValue, protect);
  1423. result.push(matchedObject);
  1424. }
  1425. }
  1426. return result;
  1427. };
  1428. /**
  1429. * Replaces Strings and Comments in javascript source code.
  1430. */
  1431. Test.CodeHighLighter.prototype.saveStringsAndComments = function() {
  1432. var commentsRe = this.regExps.comments,
  1433. stringsRe = this.regExps.strings,
  1434. exec;
  1435. while((exec = commentsRe.exec(this.source))) {
  1436. this.matchObjects(exec[0], exec.index, this.matchedComments, "Comment");
  1437. }
  1438. while((exec = stringsRe.exec(this.source))) {
  1439. this.matchObjects(exec[0], exec.index, this.matchedStrings, "String");
  1440. }
  1441. this.matchedComments = this.fixOverlaps(this.matchedComments, this.matchedStrings, "%%%%comment%%%%");
  1442. this.matchedStrings = this.fixOverlaps(this.matchedStrings, this.matchedComments, '%%%%string%%%%');
  1443. };
  1444. /**
  1445. * Process strings and comments saved by {@link #saveStringsAndComments}.
  1446. */
  1447. Test.CodeHighLighter.prototype.processStringsAndComments = function() {
  1448. var matches = this.matchedComments,
  1449. length = matches ? matches.length : 0,
  1450. value, i;
  1451. for (i = 0; i < length; i++) {
  1452. value = matches[i].value;
  1453. this.source = this.source.replace("%%%%comment%%%%", value);
  1454. }
  1455. matches = this.matchedStrings;
  1456. length = matches ? matches.length : 0;
  1457. for (i = 0; i < length; i++) {
  1458. value = matches[i].value;
  1459. this.source = this.source.replace('%%%%string%%%%', value);
  1460. }
  1461. };
  1462. /**
  1463. * Highlight operators, numbers and keywords.
  1464. */
  1465. Test.CodeHighLighter.prototype.processOperatorsNumbersKeywords = function() {
  1466. var regexps = this.regExps,
  1467. keywords = regexps.keywords,
  1468. length = keywords.length,
  1469. i;
  1470. this.source = jasmine.util.htmlEscape(this.source).replace(
  1471. regexps.operators, '<span class="jsHlOperator">$1</span>').replace(
  1472. regexps.numbers, '<span class="jsHlNumber">$1</span>');
  1473. for (i = 0; i < length; i++) {
  1474. this.source = this.source.replace(keywords[i], '<span class="jsHlKeyword">$1</span>');
  1475. }
  1476. };
  1477. /**
  1478. * Format and highligth javascript sources.
  1479. * @return The HTML formatted and highlighted code
  1480. */
  1481. Test.CodeHighLighter.prototype.process = function() {
  1482. this.saveStringsAndComments();
  1483. if (this.beautify) {
  1484. this.prepareIndent();
  1485. this.doIndent();
  1486. }
  1487. this.processOperatorsNumbersKeywords();
  1488. this.processStringsAndComments();
  1489. return this.source;
  1490. };
  1491. /**
  1492. * Render sources with line numbers.
  1493. * @return The HTML formatted and highlighted code
  1494. */
  1495. Test.CodeHighLighter.prototype.renderJsSources = function() {
  1496. var result = 'No code found.',
  1497. linesFromJsCoverage = this.linesFromJsCoverage,
  1498. lineNumber = this.lineNumber,
  1499. source = this.source,
  1500. lines, line, i, errorCls, length, lineNumberCls;
  1501. if (source) {
  1502. source = this.highLightCode ? this.process() : source;
  1503. lines = source.split("\n");
  1504. length = lines.length;
  1505. result = '<table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td class="lineNumbers">';
  1506. for (i = 0; i < length; i++) {
  1507. errorCls = "";
  1508. lineNumberCls = "";
  1509. if (lineNumber) {
  1510. errorCls = i === (lineNumber - 1) ? " error" : "";
  1511. }
  1512. if (linesFromJsCoverage) {
  1513. lineNumberCls = !isNaN(linesFromJsCoverage[i + 1]) ? " lineNumberGreen" : "";
  1514. lineNumberCls = linesFromJsCoverage[i + 1] === 0 ? " lineNumberRed" : lineNumberCls;
  1515. }
  1516. result += '<div class="lineNumber' + errorCls + lineNumberCls + '">' + (i + 1) +'</div>';
  1517. }
  1518. result += '</td><td><pre class="code">'+ source +'</pre></td></tr></tbody></table>';
  1519. }
  1520. this.source = result;
  1521. return this.source;
  1522. };
  1523. /**
  1524. * Prepares source code. It crops double whitespace and append new lines.
  1525. * This function is used generally to preformat the code that come from a
  1526. * Function.prototype.toString.
  1527. */
  1528. Test.CodeHighLighter.prototype.prepareIndent = function() {
  1529. var regexps = this.regExps,
  1530. matches, length, i, m;
  1531. this.source = this.source.replace(
  1532. regexps.multipleWhiteSpaces, " ").replace(
  1533. regexps.semiColon, ";\n").replace(
  1534. regexps.comma, ",\n").replace(
  1535. regexps.openedBrackets, "$1\n").replace(
  1536. regexps.closedBrackets, "\n$1\n");
  1537. // remove newline after commas inside code parenthesis
  1538. matches = this.source.match(regexps.commasInsideParenthesis);
  1539. length = matches ? matches.length : 0;
  1540. for (i = 0; i < length; i++) {
  1541. m = matches[i];
  1542. this.source = this.source.replace(m, m.replace(regexps.newLine, ""));
  1543. }
  1544. // fixes various bad formatting
  1545. this.source = this.source.replace(regexps.arrayWithOneElement, "[$1]").replace(
  1546. regexps.emptyObject, "{}").replace(
  1547. regexps.commaBracket, ", {").replace(
  1548. regexps.unwantedNewLines, "$1");
  1549. };
  1550. /**
  1551. * Creates a string composed of n whitespaces
  1552. * @param {Number} number The number of white spaces.
  1553. * @return {String} A multiple whitespace string.
  1554. */
  1555. Test.CodeHighLighter.prototype.addWhiteSpaces = function (number) {
  1556. var whiteSpaces = "",
  1557. i;
  1558. for (i = 0; i < number; i++) {
  1559. whiteSpaces += " ";
  1560. }
  1561. return whiteSpaces;
  1562. };
  1563. /**
  1564. * Indents pre-formatted source code.
  1565. */
  1566. Test.CodeHighLighter.prototype.doIndent = function() {
  1567. var regexps = this.regExps,
  1568. results = [],
  1569. indent = 0,
  1570. sources = this.source.split("\n"),
  1571. length = sources.length,
  1572. whiteSpaces = "",
  1573. source, i;
  1574. for (i = 0; i < length; i++) {
  1575. source = sources[i].replace(regexps.firstSpaces, '');
  1576. if (source !== "") {
  1577. if (source.search(regexps.closedBracketsWithNewLine) !== -1) {
  1578. indent = Math.max(indent - 4, 0);
  1579. whiteSpaces = this.addWhiteSpaces(indent);
  1580. }
  1581. results.push(whiteSpaces + source);
  1582. if (source.search(regexps.openedBracketsWithNewLine) !== -1) {
  1583. indent += 4;
  1584. whiteSpaces = this.addWhiteSpaces(indent);
  1585. }
  1586. }
  1587. }
  1588. this.source = results.join("\n");
  1589. };
  1590. /**
  1591. * Init allowedGlobals array.
  1592. */
  1593. Test.BadGlobalsImpl = function(reporter) {
  1594. this.results = [];
  1595. };
  1596. Test.BadGlobalsImpl.prototype.setup = function() {
  1597. var me = this,
  1598. win = Test.SandBox.getWin(),
  1599. property;
  1600. // whitelist support
  1601. win.addGlobal = function() {
  1602. me.addGlobal.apply(me, arguments);
  1603. };
  1604. me.allowedGlobals = {};
  1605. for (property in win) {
  1606. me.allowedGlobals[property] = true;
  1607. }
  1608. // add firebug globals variables to white list
  1609. me.allowedGlobals._firebug = true;
  1610. me.allowedGlobals._createFirebugConsole = true;
  1611. me.allowedGlobals.loadFirebugConsole = true;
  1612. me.allowedGlobals.console = true;
  1613. };
  1614. /**
  1615. * Append to suite HTMLElement warning messages if improper global variables are found.
  1616. * @param {HTMLElement} suiteEl The suite HTMLElement.
  1617. */
  1618. Test.BadGlobalsImpl.prototype.report = function(info, suite) {
  1619. var allowedGlobals = this.allowedGlobals,
  1620. win = Test.SandBox.getWin(),
  1621. property, message, value;
  1622. for (property in win) {
  1623. if (!allowedGlobals[property]) {
  1624. value = jasmine.pp(win[property]);
  1625. message = ">> Bad global variable found in " + (suite ? suite.description : "global scope") + "<br/>" + property + " = " + value;
  1626. info.log(message, "warning");
  1627. this.results[property] = {
  1628. where: (suite ? ('in suite' + suite.description) : "global scope"),
  1629. value: value
  1630. };
  1631. allowedGlobals[property] = true;
  1632. }
  1633. }
  1634. };
  1635. Test.BadGlobalsImpl.prototype.addGlobal = function(property) {
  1636. this.allowedGlobals[property] = true;
  1637. };
  1638. if (!jasmine.browser.isIE && !jasmine.browser.isOpera) {
  1639. Test.BadGlobals = new Test.BadGlobalsImpl();
  1640. }/**
  1641. * @singleton Test.jsCoverage
  1642. * The jscoverage manager.
  1643. */
  1644. Test.jsCoverage = {
  1645. executed: 0,
  1646. coverage: {},
  1647. isEnabled: function() {
  1648. return !!Test.SandBox.getWin()._$jscoverage;
  1649. },
  1650. getCoverage: function() {
  1651. return this.coverage;
  1652. },
  1653. getSandBoxCoverage: function() {
  1654. return Test.SandBox.getWin()._$jscoverage;
  1655. },
  1656. /**
  1657. * Adds suite to the jscoverage manager.
  1658. * @param {jasmine.Suite} The jasmine suite.
  1659. */
  1660. add: function(suite) {
  1661. var coverage = this.getSandBoxCoverage(),
  1662. filename, file, property, statement;
  1663. if (!coverage) {
  1664. return;
  1665. }
  1666. filename = this.getFileName(suite.coverageFile);
  1667. file = coverage[filename];
  1668. if (coverage && file) {
  1669. for (property in file) {
  1670. if (!file.hasOwnProperty(property)) {
  1671. continue;
  1672. }
  1673. statement = file[property];
  1674. }
  1675. }
  1676. },
  1677. /**
  1678. * This methods try to find the corresponding javascript source file.
  1679. * @param {String} The filename.
  1680. */
  1681. getFileName: function(filename) {
  1682. var coverage = this.getSandBoxCoverage(),
  1683. property;
  1684. if (!coverage || !filename) {
  1685. return;
  1686. }
  1687. if (coverage[filename]) {
  1688. return filename;
  1689. }
  1690. for (property in coverage) {
  1691. if (property.search(filename) !== -1) {
  1692. return property;
  1693. }
  1694. }
  1695. },
  1696. /**
  1697. * Updates suite coverage results after execution.
  1698. * @param {jasmine.Suite} The jasmine suite.
  1699. */
  1700. update: function(suite) {
  1701. var coverage = this.getSandBoxCoverage(),
  1702. statements = 0,
  1703. executed = 0,
  1704. property, statement, filename, file;
  1705. if (!coverage) {
  1706. return;
  1707. }
  1708. filename = this.getFileName(suite.coverageFile);
  1709. file = coverage[filename];
  1710. if (file) {
  1711. suite.jscoverage = {
  1712. file: []
  1713. };
  1714. for (property in file) {
  1715. if (!file.hasOwnProperty(property)) {
  1716. continue;
  1717. }
  1718. statement = file[property];
  1719. suite.jscoverage.file[property] = statement;
  1720. if (!isNaN(property) && statement !== undefined) {
  1721. statements = statements + 1;
  1722. if (statement !== 0) {
  1723. this.executed = this.executed + 1;
  1724. executed = executed + 1;
  1725. }
  1726. }
  1727. }
  1728. suite.jscoverage.percentage = ((executed/statements) * 100).toFixed(2);
  1729. suite.jscoverage.statements = statements;
  1730. suite.jscoverage.executed = executed;
  1731. this.coverage[filename] = suite.jscoverage.file;
  1732. this.coverage[filename].percentage = suite.jscoverage.percentage;
  1733. this.coverage[filename].statements = suite.jscoverage.statements;
  1734. this.coverage[filename].executed = suite.jscoverage.executed;
  1735. }
  1736. },
  1737. /**
  1738. * Returns suite coverage text.
  1739. * @param {jasmine.Suite} The jasmine suite.
  1740. * @return {String} The Code coverage text<
  1741. */
  1742. getSuiteCoverage: function(suite) {
  1743. if (suite.jscoverage) {
  1744. return " - Code coverage: " + suite.jscoverage.percentage + "%";
  1745. }
  1746. return '';
  1747. },
  1748. /**
  1749. * Gets total code coverage.
  1750. * @return {String} A string with total code coverage.
  1751. */
  1752. getTotal: function() {
  1753. if (this.percentage) {
  1754. return " - Code coverage: " + this.percentage + "%";
  1755. }
  1756. return '';
  1757. },
  1758. updateTotal: function() {
  1759. var coverage = this.getSandBoxCoverage(),
  1760. statements = 0,
  1761. file, filename, statement, property, fstatements, fexecuted, create;
  1762. if(!coverage) {
  1763. return "";
  1764. }
  1765. for (filename in coverage) {
  1766. if (!coverage.hasOwnProperty(filename)) {
  1767. continue;
  1768. }
  1769. file = coverage[filename];
  1770. fstatements = 0;
  1771. fexecuted = 0;
  1772. create = !this.coverage[filename];
  1773. if (create) {
  1774. this.coverage[filename] = [];
  1775. }
  1776. for (property in file) {
  1777. if (!file.hasOwnProperty(property)) {
  1778. continue;
  1779. }
  1780. statement = file[property];
  1781. if (!isNaN(property)) {
  1782. if (statement !== undefined) {
  1783. statements = statements + 1;
  1784. fstatements = fstatements + 1;
  1785. }
  1786. if (create) {
  1787. this.coverage[filename][property] = 0;
  1788. }
  1789. }
  1790. }
  1791. if (create) {
  1792. this.coverage[filename].source = file.source;
  1793. this.coverage[filename].statements = fstatements;
  1794. this.coverage[filename].executed = fexecuted;
  1795. this.coverage[filename].percentage = ((fexecuted/fstatements) * 100).toFixed(2);
  1796. }
  1797. }
  1798. this.statements = statements;
  1799. this.percentage = ((this.executed/statements) * 100).toFixed(2);
  1800. }
  1801. };Test.panel = {};
  1802. /**
  1803. * Renders Jasmine Blocks executed by spec.
  1804. * @param {Jasmine.spec} spec The spec.
  1805. * @param {HTMLElement} panelsEl The HTMLElement which encapsulate the tools panels.
  1806. */
  1807. Test.panel.Blocks = function(config) {
  1808. var blocks = config.spec.queue.blocks,
  1809. length = blocks.length,
  1810. cls = "panel blocks",
  1811. children = [],
  1812. i, block, codeHighLighter;
  1813. for (i = 0; i < length; i++) {
  1814. block = blocks[i];
  1815. if (block.func) {
  1816. children.push({
  1817. cls: "blockTitle " + (block.func.typeName || "specSources"),
  1818. html: block.func.typeName || 'it("' + jasmine.util.htmlEscape(config.spec.description) + '")'
  1819. });
  1820. codeHighLighter = new Test.CodeHighLighter({
  1821. source: block.func.toString()
  1822. });
  1823. children.push({
  1824. cls: "sources",
  1825. html: codeHighLighter.renderJsSources()
  1826. });
  1827. }
  1828. }
  1829. this.el = new jasmine.Dom({
  1830. cls: cls,
  1831. children: children
  1832. });
  1833. return this;
  1834. };
  1835. Test.panel.Blocks.prototype.remove = function() {
  1836. this.el.parentNode.removeChild(this.el);
  1837. };/**
  1838. * Renders spec dom sandbox tool.
  1839. * @param {Jasmine.spec} spec The spec.
  1840. * @param {HTMLElement} panelsEl The HTMLElement which encapsulate the tools panels.
  1841. */
  1842. Test.panel.Sandbox = function(config) {
  1843. this.persist = true;
  1844. this.render();
  1845. return this;
  1846. };
  1847. /**
  1848. * Renders spec dom sandbox innerHTML.
  1849. * @return {HTMElement} The formatted dom sandbox innerHTML.
  1850. */
  1851. Test.panel.Sandbox.prototype.render = function() {
  1852. this.el = new jasmine.Dom({
  1853. cls: "panel sandbox hideMe"
  1854. });
  1855. };/**
  1856. * Renders infos panel.
  1857. */
  1858. Test.panel.Infos = function() {
  1859. this.el = new jasmine.Dom({
  1860. tag: "div",
  1861. cls: "panel infos",
  1862. children: [{
  1863. cls: "logs"
  1864. }]
  1865. });
  1866. this.logs = this.el.childNodes[0];
  1867. this.persist = true;
  1868. return this;
  1869. };
  1870. /**
  1871. * Print a message into console.
  1872. * @param {String} message The message.
  1873. * @param {String} cls (optional) an extra cls to add to the message.
  1874. */
  1875. Test.panel.Infos.prototype.log = function(message, cls) {
  1876. var log = this.logs.appendChild(new jasmine.Dom({
  1877. cls: "infoMessage",
  1878. html: message
  1879. }));
  1880. if (cls) {
  1881. jasmine.Dom.addCls(log, cls);
  1882. }
  1883. };/**
  1884. * @class jasmine.panel.jsCoverage
  1885. * Creates and renders a per spec jscoverage panel.
  1886. * @param {Object} config The configuration object.
  1887. */
  1888. Test.panel.jsCoverage = function(config) {
  1889. this.el = new jasmine.Dom({
  1890. tag: "div",
  1891. cls: "panel jsCoverage",
  1892. children: [{
  1893. cls: "sources",
  1894. html: new Test.CodeHighLighter({
  1895. source: config.suite.jscoverage.file.source.join("\n"),
  1896. linesFromJsCoverage: config.suite.jscoverage.file,
  1897. highLightCode: false
  1898. }).renderJsSources()
  1899. }]
  1900. });
  1901. return this;
  1902. };
  1903. Test.panel.jsCoverage.prototype.remove = function() {
  1904. this.el.parentNode.removeChild(this.el);
  1905. };/**
  1906. * @class jasmine.panel.jsCoverageSummary
  1907. * Creates and renders the persistant jscoverage summary panel.
  1908. * @param {Object} config The configuration object.
  1909. */
  1910. Test.panel.jsCoverageSummary = function(config) {
  1911. var me = this;
  1912. me.el = new jasmine.Dom({
  1913. tag: "div",
  1914. cls: "panel jsCoverageSummary hideMe",
  1915. onclick: function() {
  1916. me.onClick.apply(me, arguments);
  1917. },
  1918. children: [{
  1919. cls: "sbody"
  1920. }]
  1921. });
  1922. me.body = me.el.childNodes[0];
  1923. me.persist = true;
  1924. this.renderSummary();
  1925. return me;
  1926. };
  1927. /**
  1928. * Renders summary view.
  1929. */
  1930. Test.panel.jsCoverageSummary.prototype.renderSummary = function() {
  1931. var coverage = Test.jsCoverage.getCoverage(),
  1932. filename, result;
  1933. if (!this.summary) {
  1934. result = '<table class="summary" border="0" cellpadding="0" cellspacing="0"><tbody>';
  1935. result += '<tr class="line header"><td class="fileName">File</td><td class="statements">Statements</td><td class="executed">Executed</td><td class="percentage">Percentage</td></tr>';
  1936. result += '<tr class="line total">';
  1937. result += '<td class="fileName">Total</td>';
  1938. result += '<td class="statements">' + Test.jsCoverage.statements + "</td>";
  1939. result += '<td class="executed">' + Test.jsCoverage.executed + "</td>";
  1940. result += '<td class="percentage">' + this.renderPercentage(Test.jsCoverage.percentage) + "</td>";
  1941. result += '</tr>';
  1942. for (filename in coverage) {
  1943. if (!coverage.hasOwnProperty(filename)) {
  1944. continue;
  1945. }
  1946. result += '<tr class="line">';
  1947. result += '<td class="fileName"><a>' + filename + "</a></td>";
  1948. result += '<td class="statements">' + coverage[filename].statements + "</td>";
  1949. result += '<td class="executed">' + coverage[filename].executed + "</td>";
  1950. result += '<td class="percentage">' + this.renderPercentage(coverage[filename].percentage) + "</td>";
  1951. result += '</tr>';
  1952. }
  1953. result += '</tbody></table>';
  1954. this.summary = result;
  1955. }
  1956. this.body.innerHTML = this.summary;
  1957. };
  1958. /**
  1959. * Renders percentage progress bar.
  1960. * @return {String} The progressbar html.
  1961. */
  1962. Test.panel.jsCoverageSummary.prototype.renderPercentage = function(percent) {
  1963. var result = percent + '%<div class="limit" style="width:300px;">';
  1964. result += '<div class="result" style="width:' + 3 * percent + 'px;"></div>';
  1965. result += '</div>';
  1966. return result;
  1967. };
  1968. /**
  1969. * Renders percentage progress bar.
  1970. * @return {String} The progressbar html.
  1971. */
  1972. Test.panel.jsCoverageSummary.prototype.onClick = function(event) {
  1973. var el;
  1974. event = event || window.event;
  1975. el = event.target || event.srcElement;
  1976. if (el.tagName === "A") {
  1977. this.renderSource(Test.jsCoverage.getCoverage()[el.innerHTML]);
  1978. }
  1979. if (jasmine.Dom.hasCls(el,"back")) {
  1980. this.renderSummary();
  1981. }
  1982. };
  1983. /**
  1984. * Renders file source.
  1985. */
  1986. Test.panel.jsCoverageSummary.prototype.renderSource = function(coverage) {
  1987. this.body.innerHTML = "";
  1988. this.body.appendChild(new jasmine.Dom({
  1989. cls: "back",
  1990. html: "Back"
  1991. }));
  1992. this.body.appendChild(new jasmine.Dom({
  1993. cls: "sources",
  1994. html: new Test.CodeHighLighter({
  1995. source: coverage.source.join("\n"),
  1996. linesFromJsCoverage: coverage,
  1997. highLightCode: false
  1998. }).renderJsSources()
  1999. }));
  2000. };/**
  2001. * Renders stack trace tool.
  2002. * @param {Jasmine.spec} The jasmine spec.
  2003. * @return {HTMLElement} The created HTMLElement.
  2004. */
  2005. Test.panel.StackTrace = function(config) {
  2006. this.spec = config.spec;
  2007. this.badLinesEls = [];
  2008. var resultItems = this.spec.results().getItems(),
  2009. length = resultItems.length,
  2010. result,
  2011. error,
  2012. lines,
  2013. i;
  2014. if (jasmine.browser.isIE || !this.spec.hasError) {
  2015. return this;
  2016. }
  2017. for (i = 0; i < length; i++) {
  2018. result = resultItems[i];
  2019. if (result.type == "expect" && result.passed && !result.passed()) {
  2020. if (result.error) {
  2021. error = result.error;
  2022. break;
  2023. }
  2024. }
  2025. }
  2026. if (error) {
  2027. lines = this.extractStackTrace(error);
  2028. this.el = new jasmine.Dom({
  2029. tag: "div",
  2030. cls: "panel stackTrace",
  2031. children: this.renderStackLines(lines)
  2032. });
  2033. }
  2034. return this;
  2035. };
  2036. /**
  2037. * Extracts error stack trace.
  2038. * @param {Error} e The javascript error object.
  2039. * @return {Array} An array which contains all stack trace files and lineNumbers.
  2040. */
  2041. Test.panel.StackTrace.prototype.extractStackTrace = function(error) {
  2042. var stack = error.stack || error.stackTrace,
  2043. results = [],
  2044. lines, line, length, i, extract, file, lineNumber;
  2045. if (stack) {
  2046. lines = stack.split("\n");
  2047. length = lines.length;
  2048. for(i = 0; i < length; i++) {
  2049. line = lines[i];
  2050. if (line.search("jasmine.js") === -1) {
  2051. extract = this.extractFileAndLine(line);
  2052. if (extract) {
  2053. results.push(extract);
  2054. }
  2055. }
  2056. }
  2057. } else {
  2058. file = error.sourceURL || error.fileName;
  2059. lineNumber = error.line || error.lineNumber;
  2060. if (file && lineNumber) {
  2061. results.push({
  2062. file: file,
  2063. lineNumber: lineNumber
  2064. });
  2065. }
  2066. }
  2067. return results;
  2068. };
  2069. /**
  2070. * Extracts filename and line number from a stack trace line.
  2071. * @param {String} line The stack trace line.
  2072. * @return {Object} An object containing the filename and the line number or null.
  2073. */
  2074. Test.panel.StackTrace.prototype.extractRe = /((http:\/\/|file:\/\/\/).*\.js)[^:]*:(\d*)/;
  2075. Test.panel.StackTrace.prototype.extractFileAndLine = function(line) {
  2076. var result = line.match(this.extractRe);
  2077. if (!result) {
  2078. return null;
  2079. }
  2080. return {
  2081. file: result[1],
  2082. lineNumber: result[3]
  2083. };
  2084. };
  2085. /**
  2086. * Render stack trace lines.
  2087. * @param {String} file The filename.
  2088. * @param {String/Number} lineNumber The line number.
  2089. * @return {Array} An array containing all strace trace HTMLElements.
  2090. */
  2091. Test.panel.StackTrace.prototype.renderStackLines = function(lines) {
  2092. var els = [],
  2093. length = lines.length,
  2094. el, line, i, file, lineNumber;
  2095. for (i = 0; i < length; i++) {
  2096. line = lines[i];
  2097. file = line.file;
  2098. lineNumber = parseInt(line.lineNumber, 0);
  2099. el = new jasmine.Dom({
  2100. cls: "stackTraceLine",
  2101. children: [{
  2102. cls: "fileName",
  2103. html: "File: "+ file + " (line " + lineNumber + ")"
  2104. },{
  2105. cls: "sources",
  2106. html: this.renderTraceFileSource(file, lineNumber)
  2107. }]
  2108. });
  2109. this.badLinesEls.push({
  2110. el: el.childNodes[1],
  2111. line: lineNumber
  2112. });
  2113. els.push(el);
  2114. }
  2115. return els;
  2116. };
  2117. /**
  2118. * Downloads source file.
  2119. * @param {String} url The filename url.
  2120. * @return {String} The file source or null.
  2121. */
  2122. Test.panel.StackTrace.prototype.getFile = function(file) {
  2123. var request;
  2124. if (jasmine.browser.isIE || Test.Options.remote) {
  2125. return null;
  2126. }
  2127. this.downloadedFiles = this.downloadedFiles || {};
  2128. if (!this.downloadedFiles[file]) {
  2129. request = new XMLHttpRequest();
  2130. if (!request) {
  2131. return null;
  2132. }
  2133. request.open("GET", file + "?" + (new Date()).getTime(), false);
  2134. request.send("");
  2135. this.downloadedFiles[file] = request.responseText;
  2136. }
  2137. return this.downloadedFiles[file];
  2138. };
  2139. /**
  2140. * Renders stack trace source file.
  2141. * @param {String} file The filename.
  2142. * @param {String/Number} lineNumber The line number.
  2143. * @return {HTMLElement} The javascript source file HTMLElement.
  2144. */
  2145. Test.panel.StackTrace.prototype.jscoverageFileRe = /(http:\/\/|file:\/\/\/)[^\/]*/;
  2146. Test.panel.StackTrace.prototype.renderTraceFileSource = function (file, lineNumber) {
  2147. var highLightCode = true,
  2148. source, instrumented_file, i, length, line;
  2149. if (Test.SandBox.getWin()._$jscoverage) {
  2150. instrumented_file = SandBox.getWin()._$jscoverage[file.replace(this.jscoverageFileRe, "")];
  2151. if (instrumented_file) {
  2152. highLightCode = false;
  2153. source = instrumented_file.source.join("\n");
  2154. linesFromJsCoverage = {};
  2155. length = instrumented_file.length;
  2156. for (i = 0; i < length; i++) {
  2157. line = instrumented_file[i];
  2158. if (line === 0) {
  2159. linesFromJsCoverage[i-1] = true;
  2160. }
  2161. }
  2162. }
  2163. }
  2164. source = source || this.getFile(file);
  2165. return new Test.CodeHighLighter({
  2166. source: source,
  2167. highLightCode: highLightCode,
  2168. lineNumber: lineNumber
  2169. }).renderJsSources();
  2170. };
  2171. /**
  2172. * Ensure that line which contains the error is visible without scroll.
  2173. */
  2174. Test.panel.StackTrace.prototype.afterRender = function() {
  2175. var length = this.badLinesEls.length,
  2176. badLine, firstChild, el, i, lineHeigth, visiblesLines;
  2177. for (i = 0; i < length; i++) {
  2178. badLine = this.badLinesEls[i];
  2179. el = badLine.el;
  2180. lineHeigth = 16;
  2181. visiblesLines = el.clientHeight/lineHeigth;
  2182. el.scrollTop = Math.max(badLine.line - visiblesLines/2, 0) * lineHeigth;
  2183. }
  2184. this.badLinesEls = [];
  2185. };
  2186. Test.panel.StackTrace.prototype.remove = function() {
  2187. this.el.parentNode.removeChild(this.el);
  2188. };/**
  2189. * @class Test.panel.TabPanel
  2190. * Renders inspection tools htmlElement.
  2191. * @param {Object} config The configuration object.
  2192. */
  2193. Test.panel.TabPanel = function(config) {
  2194. var me = this;
  2195. me.options = Test.Options.get();
  2196. me.spec = config.spec;
  2197. me.container = config.container;
  2198. me.el = new jasmine.Dom({
  2199. cls: "tabpanel",
  2200. onclick: function() {
  2201. me.onTabPanelClick.apply(me, arguments);
  2202. },
  2203. children: [{
  2204. cls: "toolBar"
  2205. },{
  2206. cls: "panels"
  2207. }]
  2208. });
  2209. me.toolbar = me.el.childNodes[0];
  2210. me.body = me.el.childNodes[1];
  2211. me.children = [];
  2212. me.tabs = [];
  2213. me.container.appendChild(me.el);
  2214. me.renderToolBar();
  2215. me.add(new Test.panel.Infos({}));
  2216. me.add(new Test.panel.Sandbox({}));
  2217. if (me.options.panel) {
  2218. me.activatePanel(me.options.panel);
  2219. }
  2220. return me;
  2221. };
  2222. /**
  2223. * Adds a panel.
  2224. * @param {Object} panel the panel to be added to this tabPanel.
  2225. */
  2226. Test.panel.TabPanel.prototype.add = function(panel) {
  2227. if (panel.el) {
  2228. this.body.appendChild(panel.el);
  2229. }
  2230. if (panel.afterRender) {
  2231. panel.afterRender();
  2232. }
  2233. this.children.push(panel);
  2234. if (panel.afterRender) {
  2235. panel.afterRender();
  2236. }
  2237. };
  2238. /**
  2239. * Adds a tab
  2240. * @param {Object} panel the panel to be added to this tabPanel.
  2241. */
  2242. Test.panel.TabPanel.prototype.addTab = function(cls, name, persist) {
  2243. var el = this.toolbar.appendChild(new jasmine.Dom({
  2244. tag: "span",
  2245. cls: "toolbarTab " + cls,
  2246. html: name
  2247. }));
  2248. this.tabs.push({
  2249. el: el,
  2250. persist: persist
  2251. });
  2252. };
  2253. /**
  2254. * Activate a tool panel and render it if needed.
  2255. * @param {String} cls The panel className.
  2256. */
  2257. Test.panel.TabPanel.prototype.activatePanel = function(cls) {
  2258. var children = this.children,
  2259. length = children.length,
  2260. rendered = false,
  2261. child, i;
  2262. for(i = 0; i < length; i++) {
  2263. child = children[i].el;
  2264. jasmine.Dom.addCls(child, "hideMe");
  2265. if (jasmine.Dom.hasCls(child, cls)) {
  2266. jasmine.Dom.removeCls(child, "hideMe");
  2267. if (children[i].persist && cls !== "jsCoverageSummary") {
  2268. this.options.panel = cls;
  2269. } else {
  2270. delete this.options.panel;
  2271. }
  2272. rendered = true;
  2273. }
  2274. }
  2275. if (rendered) {
  2276. return;
  2277. }
  2278. if (this.spec) {
  2279. if (cls === "blocks") {
  2280. this.add(new Test.panel.Blocks({
  2281. spec: this.spec
  2282. }));
  2283. }
  2284. if (cls === "stackTrace") {
  2285. this.add(new Test.panel.StackTrace({
  2286. spec: this.spec
  2287. }));
  2288. }
  2289. }
  2290. if (this.suite && this.suite.jscoverage) {
  2291. if (cls === "jsCoverage") {
  2292. this.add(new Test.panel.jsCoverage({
  2293. suite: this.suite
  2294. }));
  2295. }
  2296. }
  2297. };
  2298. /**
  2299. * Reporter HTMLElement click dispatcher.
  2300. * @param {Event} event The event
  2301. */
  2302. Test.panel.TabPanel.prototype.onTabPanelClick = function(event) {
  2303. var el;
  2304. event = event || window.event;
  2305. el = event.target || event.srcElement;
  2306. if (jasmine.Dom.hasCls(el, "toolbarTab")) {
  2307. this.onTabClick(el);
  2308. }
  2309. };
  2310. /**
  2311. * Handle spec tools tab click.
  2312. * @param {HTMLElement} el The tab HTMLElement.
  2313. */
  2314. Test.panel.TabPanel.prototype.onTabClick = function(el) {
  2315. var tools, panels, length, child, i;
  2316. jasmine.Dom.addCls(el, "selected");
  2317. tools = this.toolbar.childNodes;
  2318. panels = this.body.childNodes;
  2319. length = tools.length;
  2320. for(i = 0; i < length; i++) {
  2321. child = tools[i];
  2322. if (child != el) {
  2323. jasmine.Dom.removeCls(child, "selected");
  2324. }
  2325. }
  2326. this.activatePanel(el.className.split(" ")[1]);
  2327. };
  2328. /**
  2329. * Renders inspection tabpanel toolbar which contain tabs.
  2330. * @param {jasmine.Spec} spec The jasmine spec.
  2331. * @param {HTMLElement} toolBarEl The toolbar HTMLElement
  2332. */
  2333. Test.panel.TabPanel.prototype.renderToolBar = function() {
  2334. var spec = this.spec,
  2335. suite = this.suite,
  2336. toolbar = this.toolbar;
  2337. if (this.tabs.length === 0) {
  2338. this.addTab("infos selected", "Console", true);
  2339. this.addTab("sandbox", "Iframe", true);
  2340. } else {
  2341. jasmine.Dom.addCls(this.tabs[0].el, "selected");
  2342. }
  2343. if (spec) {
  2344. this.addTab("blocks", "Blocks");
  2345. if (!jasmine.browser.isIE && !jasmine.browser.isOpera && this.spec.hasError) {
  2346. this.addTab("stackTrace", "Stack Trace");
  2347. }
  2348. }
  2349. if (suite && suite.jscoverage) {
  2350. this.addTab("jsCoverage", "Suite Coverage");
  2351. }
  2352. };
  2353. /**
  2354. * Removes all non-persistant tabs.
  2355. */
  2356. Test.panel.TabPanel.prototype.resetToolBar = function() {
  2357. var children = this.tabs,
  2358. length = children.length,
  2359. child, i;
  2360. for (i = length - 1; i >= 0; i--) {
  2361. child = children[i];
  2362. if (!child.persist) {
  2363. this.toolbar.removeChild(child.el);
  2364. jasmine.array.remove(children, child);
  2365. }
  2366. jasmine.Dom.removeCls(child.el, "selected");
  2367. }
  2368. this.renderToolBar();
  2369. };
  2370. /**
  2371. * Removes all non-persistant panels.
  2372. */
  2373. Test.panel.TabPanel.prototype.resetPanels = function() {
  2374. var children = this.children,
  2375. length = children.length,
  2376. child, i;
  2377. for (i = length - 1; i >= 0; i--) {
  2378. child = children[i];
  2379. if (!child.persist) {
  2380. child.remove();
  2381. jasmine.array.remove(children, child);
  2382. }
  2383. jasmine.Dom.addCls(child.el, "hideMe");
  2384. }
  2385. if (children[0]) {
  2386. jasmine.Dom.removeCls(children[0].el, "hideMe");
  2387. }
  2388. };
  2389. /**
  2390. * Sets TabPanel current spec.
  2391. */
  2392. Test.panel.TabPanel.prototype.setSpec = function(spec) {
  2393. this.spec = spec;
  2394. delete this.suite;
  2395. this.resetToolBar();
  2396. this.resetPanels();
  2397. };
  2398. /**
  2399. * Sets TabPanel current suite.
  2400. */
  2401. Test.panel.TabPanel.prototype.setSuite = function(suite) {
  2402. this.suite = suite;
  2403. delete this.spec;
  2404. this.resetToolBar();
  2405. this.resetPanels();
  2406. };
  2407. /**
  2408. * Resize TabPanel dom element.
  2409. */
  2410. Test.panel.TabPanel.prototype.resize = function(val) {
  2411. this.el.style.height = val + "px";
  2412. this.body.style.height = val - 40 + "px";
  2413. };
  2414. /**
  2415. * Adds jscoverage persistant panel.
  2416. */
  2417. Test.panel.TabPanel.prototype.addCoverageSummary = function() {
  2418. this.addTab("jsCoverageSummary", "Coverage Summary", true);
  2419. this.add(new Test.panel.jsCoverageSummary({}));
  2420. };/**
  2421. * @class Test.panel.TreeGrid
  2422. * Creates and renders reporter treegrid.
  2423. * @param {Object} config The configuration object.
  2424. */
  2425. Test.panel.TreeGrid = function(config) {
  2426. var me = this;
  2427. me.options = Test.Options.get();
  2428. me.el = document.body.appendChild(new jasmine.Dom({
  2429. tag: "div",
  2430. cls: "treegrid",
  2431. onmousedown: function() {
  2432. me.onMouseDown.apply(me, arguments);
  2433. },
  2434. onmouseup: function() {
  2435. me.onMouseUp.apply(me, arguments);
  2436. },
  2437. onmousemove: function() {
  2438. me.onMouseMove.apply(me, arguments);
  2439. },
  2440. children: [{
  2441. cls: "header",
  2442. children: [{
  2443. cls: "logo",
  2444. html: "Sencha"
  2445. },{
  2446. cls: "statusMessage"
  2447. },{
  2448. cls: "toolBar",
  2449. children: [{
  2450. tag: "span",
  2451. cls: "options",
  2452. children: [
  2453. Test.Options.renderCheckbox("showPassed", "Show passed"),
  2454. Test.Options.renderCheckbox("showDisabled", "Show disabled"),
  2455. Test.Options.renderCheckbox("collapseAll", "Collapse all"),
  2456. Test.Options.renderCheckbox("disableBodyClean", "Disable Body Autoclean"),
  2457. Test.Options.renderCheckbox("disableCacheBuster", "Disable CacheBuster"),
  2458. Test.Options.renderCheckbox("showTimings", "Show Timings"),
  2459. Test.Options.renderCheckbox("verbose", "Show jasmine logs"),
  2460. Test.Options.renderCheckbox("autoReload", "Automatic reload"),
  2461. Test.Options.renderCheckbox("quirksMode", "Quirks Mode")
  2462. ]
  2463. },{
  2464. tag: "a",
  2465. cls: "actionLink",
  2466. html: "Run checked",
  2467. onclick: function() {
  2468. Test.Options.reloadWindow();
  2469. }
  2470. },{
  2471. tag: "a",
  2472. cls: "actionLink",
  2473. html: "Run all",
  2474. onclick: function() {
  2475. Test.Options.reloadWindow(true);
  2476. }
  2477. }]
  2478. }]
  2479. },{
  2480. tag: "div",
  2481. cls: "tbody",
  2482. onclick: function() {
  2483. me.onBodyClick.apply(me, arguments);
  2484. }
  2485. }, {
  2486. cls: "resizer",
  2487. html: "......"
  2488. }]
  2489. }));
  2490. me.tabPanel = new Test.panel.TabPanel({
  2491. container: me.el
  2492. });
  2493. Test.Options.check();
  2494. me.header = me.el.childNodes[0];
  2495. me.statusMessage = me.header.childNodes[1];
  2496. me.toolBar = me.header.childNodes[2];
  2497. me.body = me.el.childNodes[1];
  2498. me.resizer = me.el.childNodes[2];
  2499. me.suites = {};
  2500. me.specs = {};
  2501. me.suitesEls = {};
  2502. me.specsEls = {};
  2503. if (me.options.resizer) {
  2504. me.tabPanel.resize(parseInt(me.options.resizer, 10));
  2505. }
  2506. me.resizeBody();
  2507. window.onresize = function() {
  2508. me.resizeBody();
  2509. };
  2510. };
  2511. /**
  2512. * Renders suite htmlElement.
  2513. * @param {jasmine.Suite} suite The jasmine suite.
  2514. * @return {HTMLElement} The suite HTMLElement
  2515. */
  2516. Test.panel.TreeGrid.prototype.addSuite = function(suite) {
  2517. var options = {},
  2518. parent = suite.parentSuite,
  2519. padding = 18,
  2520. prefix = suite.isDisabled() ? "xdescribe :" : "describe: ",
  2521. cls = "noexpand",
  2522. row, property;
  2523. if (suite.children_.length !== 0) {
  2524. cls = this.options.collapseAll ? "expand" : "collapse";
  2525. }
  2526. if (parent) {
  2527. this.suitesEls[parent.id] || this.addSuite(parent);
  2528. while(parent) {
  2529. padding += 18;
  2530. parent = parent.parentSuite;
  2531. }
  2532. }
  2533. row = this.createRow(this.options.collapseAll && suite.parentSuite, suite);
  2534. for (property in this.options) {
  2535. if (!this.options.hasOwnProperty(property)) {
  2536. continue;
  2537. }
  2538. options[property] = this.options[property];
  2539. }
  2540. options.suite = suite.id;
  2541. delete options.spec;
  2542. this.suitesEls[suite.id] = new jasmine.Dom({
  2543. tag: "div",
  2544. id: "suite-" + suite.id,
  2545. cls: "suite " + (suite.isDisabled() ? "disabled" : ""),
  2546. style: {
  2547. "paddingLeft": padding + "px"
  2548. },
  2549. children: [{
  2550. cls: cls
  2551. },{
  2552. tag: "span",
  2553. cls: "description",
  2554. html: prefix + suite.description
  2555. }]
  2556. });
  2557. row.appendChild(this.suitesEls[suite.id]);
  2558. var clear = new jasmine.Dom({ tag: 'div' });
  2559. clear.style.clear = 'both';
  2560. row.appendChild(clear);
  2561. this.suites[suite.id] = suite;
  2562. return this.suitesEls[suite.id];
  2563. };
  2564. /**
  2565. * Updates suite dom element by adding a code coverage percentage to it's description.
  2566. * @param {HTMLElement} The suite dom element.
  2567. * @param {jasmine.Suite} The jasmine suite.
  2568. */
  2569. Test.panel.TreeGrid.prototype.updateSuiteEl = function(suite, text) {
  2570. var description = this.suitesEls[suite.id].childNodes[1];
  2571. jasmine.Dom.setHTML(description, description.innerHTML + text);
  2572. };
  2573. /**
  2574. * Renders spec htmlElement.
  2575. * @param {jasmine.Spec} spec The jasmine spec.
  2576. * @return {HTMLElement} The spec HTMLElement
  2577. */
  2578. Test.panel.TreeGrid.prototype.addSpec = function(spec) {
  2579. var options = {},
  2580. padding = 18,
  2581. suite = spec.suite,
  2582. suffix = spec.time ? " (" + spec.time + "s)" : "",
  2583. row, prefix, status, property, specEl, resultPanel;
  2584. if (spec.isEnabled()) {
  2585. prefix = "it ";
  2586. status = spec.results().passed() ? "passed" : "failed";
  2587. } else {
  2588. prefix = "xit ";
  2589. status = "disabled";
  2590. }
  2591. if (suite) {
  2592. this.suitesEls[suite.id] || this.addSuite(suite);
  2593. while(suite) {
  2594. padding += 18;
  2595. suite = suite.parentSuite;
  2596. }
  2597. }
  2598. row = this.createRow(this.options.collapseAll, spec);
  2599. for (property in this.options) {
  2600. if (this.options.hasOwnProperty(property)) {
  2601. options[property] = this.options[property];
  2602. }
  2603. }
  2604. options.spec = spec.id;
  2605. delete options.suite;
  2606. specEl = {
  2607. id: "spec-" + spec.id,
  2608. cls: "spec " + status,
  2609. style: {
  2610. "paddingLeft": padding + "px"
  2611. },
  2612. children: [{
  2613. cls: this.options.collapseAll ? "expand" : "collapse"
  2614. },{
  2615. tag: "span",
  2616. cls: "description",
  2617. html: prefix + spec.description + suffix
  2618. }]
  2619. };
  2620. resultPanel = this.renderSpecResults(spec);
  2621. if (this.options.collapseAll) {
  2622. resultPanel.style.display = "none";
  2623. }
  2624. if (resultPanel.innerHTML === "") {
  2625. specEl.children[0].cls = "noexpand";
  2626. }
  2627. specEl.children.push(resultPanel);
  2628. specEl = new jasmine.Dom(specEl);
  2629. this.specsEls[spec.id] = specEl;
  2630. this.specs[spec.id] = spec;
  2631. row.appendChild(specEl);
  2632. jasmine.Dom.addCls(row, status);
  2633. var clear = new jasmine.Dom({ tag: 'div' });
  2634. clear.style.clear = 'both';
  2635. row.appendChild(clear);
  2636. };
  2637. /**
  2638. * Returns a suite by id.
  2639. * @param {String/Number} id The suite id.
  2640. * @return {jasmine.Suite} The jasmine suite.
  2641. */
  2642. Test.panel.TreeGrid.prototype.getSuite = function(id) {
  2643. return this.suites[parseInt(id, 10)];
  2644. };
  2645. /**
  2646. * Returns a spec by id.
  2647. * @param {String/Number} id The spec id.
  2648. * @return {jasmine.Spec} The jasmine spec.
  2649. */
  2650. Test.panel.TreeGrid.prototype.getSpec = function(id) {
  2651. return this.specs[parseInt(id, 10)];
  2652. };
  2653. /**
  2654. * Body elements click event dispatcher.
  2655. */
  2656. Test.panel.TreeGrid.prototype.onBodyClick = function(event) {
  2657. event = event || window.event;
  2658. var el = event.target || event.srcElement,
  2659. cls = el.className,
  2660. i;
  2661. if (cls) {
  2662. if (jasmine.Dom.hasCls(el, "collapse")) {
  2663. this.onCollapse(el);
  2664. return;
  2665. }
  2666. if (jasmine.Dom.hasCls(el, "expand")) {
  2667. this.onExpand(el);
  2668. return;
  2669. }
  2670. if (jasmine.Dom.hasCls(el, "select-checkbox")) {
  2671. this.onCheck(el);
  2672. return;
  2673. }
  2674. for (i = 0; i < 6; i++) {
  2675. if (cls && jasmine.Dom.hasCls(el, "row")) {
  2676. this.onRowClick(el);
  2677. return;
  2678. }
  2679. el = el.parentNode;
  2680. if (!el) {
  2681. break;
  2682. }
  2683. cls = el.className;
  2684. }
  2685. }
  2686. };
  2687. /**
  2688. * Checkboxes listener.
  2689. */
  2690. Test.panel.TreeGrid.prototype.onCheck = function(el) {
  2691. var next = el.parentNode.nextSibling,
  2692. id;
  2693. if (jasmine.Dom.hasCls(next,"spec")) {
  2694. id = parseInt(next.id.replace("spec-", ""), 10);
  2695. if (el.checked) {
  2696. if (jasmine.array.indexOf(this.options.specs, id) === -1) {
  2697. this.options.specs.push(id);
  2698. }
  2699. } else {
  2700. jasmine.array.remove(this.options.specs, id);
  2701. }
  2702. } else {
  2703. id = parseInt(next.id.replace("suite-", ""), 10);
  2704. if (el.checked) {
  2705. if (jasmine.array.indexOf(this.options.suites, id) === -1) {
  2706. this.options.suites.push(id);
  2707. }
  2708. } else {
  2709. jasmine.array.remove(this.options.suites, id);
  2710. }
  2711. }
  2712. };
  2713. /**
  2714. * Returns row dom element by spec or suite.
  2715. * @param {jasmine.Suite/jasmine.Spec} o A suite or a spec.
  2716. * @return {HTMLElement} The row dom element.
  2717. */
  2718. Test.panel.TreeGrid.prototype.getRow = function(o) {
  2719. if (!o.suite && this.suitesEls[o.id]) {
  2720. return this.suitesEls[o.id].parentNode;
  2721. } else if (this.specsEls[o.id]) {
  2722. return this.specsEls[o.id].parentNode;
  2723. }
  2724. };
  2725. /**
  2726. * Iterates nested rows calling the supplied function.
  2727. * @param {HTMLElement} row The row.
  2728. * @param {Function} fn The function.
  2729. * @param {Boolean} recursive recurse in all children suite (default to true)
  2730. */
  2731. Test.panel.TreeGrid.prototype.onEachRow = function(row, fn, recursive) {
  2732. var me = this,
  2733. id = row.childNodes[1].id,
  2734. traverse = function(s, func) {
  2735. var children = s.children_,
  2736. i, child, length, r;
  2737. if (children) {
  2738. length = children.length;
  2739. for (i = 0; i < length; i++) {
  2740. child = children[i];
  2741. r = me.getRow(child);
  2742. if (r) {
  2743. func.call(me, r, child);
  2744. if (child.children_ && recursive !== false) {
  2745. traverse(child, func);
  2746. }
  2747. }
  2748. }
  2749. }
  2750. },
  2751. spec, suite;
  2752. if (id.search("suite") !== -1) {
  2753. suite = this.getSuite(id.replace("suite-", ""));
  2754. traverse(suite, fn);
  2755. } else {
  2756. spec = this.getSpec(id.replace("spec-", ""));
  2757. traverse(spec, fn);
  2758. }
  2759. };
  2760. /**
  2761. * Collapse click handler.
  2762. */
  2763. Test.panel.TreeGrid.prototype.onCollapse = function(el) {
  2764. el = el.parentNode;
  2765. jasmine.Dom.setCls(el.childNodes[0], "expand");
  2766. if (jasmine.Dom.hasCls(el, "suite")) {
  2767. this.onEachRow(el.parentNode, function(row, o) {
  2768. var childNode = row.childNodes[1],
  2769. icon = childNode.childNodes[0],
  2770. content = childNode.childNodes[2];
  2771. row.style.display = "none";
  2772. if (jasmine.Dom.hasCls(icon, "collapse")) {
  2773. jasmine.Dom.setCls(icon, "expand");
  2774. }
  2775. if (o.suite) {
  2776. content.style.display = "none";
  2777. }
  2778. });
  2779. } else {
  2780. el.childNodes[2].style.display = "none";
  2781. }
  2782. };
  2783. /**
  2784. * Expand click handler.
  2785. */
  2786. Test.panel.TreeGrid.prototype.onExpand = function(el) {
  2787. el = el.parentNode;
  2788. jasmine.Dom.setCls(el.childNodes[0], "collapse");
  2789. if (jasmine.Dom.hasCls(el, "suite")) {
  2790. this.onEachRow(el.parentNode, function(row, o) {
  2791. row.style.display = "block";
  2792. }, false);
  2793. } else {
  2794. el.childNodes[2].style.display = "block";
  2795. }
  2796. };
  2797. /**
  2798. * Row click click handler.
  2799. */
  2800. Test.panel.TreeGrid.prototype.onRowClick = function(el) {
  2801. var rows = el.parentNode.childNodes,
  2802. length = rows.length,
  2803. id, i;
  2804. for (i = 0; i < length; i++) {
  2805. jasmine.Dom.removeCls(rows[i], "selected");
  2806. }
  2807. jasmine.Dom.addCls(el, "row selected");
  2808. id = el.childNodes[1].id;
  2809. if (id.search("spec") !== -1) {
  2810. this.tabPanel.setSpec(this.getSpec(id.replace("spec-", "")));
  2811. }
  2812. if (id.search("suite") !== -1) {
  2813. this.tabPanel.setSuite(this.getSuite(id.replace("suite-", "")));
  2814. }
  2815. };
  2816. /**
  2817. * Creates row dom element.
  2818. * @param {Boolean} hide Sets the row visibility.
  2819. * @param {jasmine.Suite/jasmine.Spec} The suite or the spec.
  2820. * @return {HTMLElement} The row.
  2821. */
  2822. Test.panel.TreeGrid.prototype.createRow = function(hide, o) {
  2823. var row = this.body.appendChild(new jasmine.Dom({
  2824. tag: "div",
  2825. cls: "row",
  2826. style: {
  2827. display: hide ? "none" : "block"
  2828. },
  2829. children: [{
  2830. cls: "checkbox-col",
  2831. children: [{
  2832. tag: "input",
  2833. cls: "select-checkbox",
  2834. type: "checkbox"
  2835. }]
  2836. }]
  2837. }));
  2838. if (Test.Options.isChecked(o)) {
  2839. row.childNodes[0].childNodes[0].checked = true;
  2840. }
  2841. return row;
  2842. };
  2843. /**
  2844. * Resizer
  2845. */
  2846. /**
  2847. * MouseDown event listener. (resizing starts)
  2848. */
  2849. Test.panel.TreeGrid.prototype.onMouseDown = function(event) {
  2850. var el;
  2851. event = event || window.event;
  2852. el = event.target || event.srcElement;
  2853. if (jasmine.Dom.hasCls(el, "resizer")) {
  2854. if (event.preventDefault) {
  2855. event.preventDefault();
  2856. } else {
  2857. event.returnValue = false;
  2858. }
  2859. this.pageY = event.pageY || event.clientY;
  2860. this.startHeight = this.tabPanel.el.clientHeight;
  2861. document.body.style.cursor = "row-resize";
  2862. }
  2863. };
  2864. /**
  2865. * MouseDown event listener. (resize in progress)
  2866. */
  2867. Test.panel.TreeGrid.prototype.onMouseMove = function(event) {
  2868. var el, diff;
  2869. if (this.pageY) {
  2870. event = event || window.event;
  2871. el = event.target || event.srcElement;
  2872. diff = Math.max(200, this.startHeight - ((event.pageY || event.clientY)- this.pageY));
  2873. diff = Math.min(diff, document.body.clientHeight - 200);
  2874. this.tabPanel.resize(diff);
  2875. this.options.resizer = diff;
  2876. this.resizeBody();
  2877. }
  2878. };
  2879. /**
  2880. * MouseUp event listener. (resize ends)
  2881. */
  2882. Test.panel.TreeGrid.prototype.onMouseUp = function(event) {
  2883. document.body.style.cursor = "auto";
  2884. delete this.pageY;
  2885. };
  2886. /**
  2887. * Returns treegrid innerHeight.
  2888. * @return {Number} The innerHeight.
  2889. */
  2890. Test.panel.TreeGrid.prototype.getInnerHeight = function() {
  2891. return (window.innerHeight || document.documentElement.clientHeight) - this.header.offsetTop * 2;
  2892. };
  2893. /**
  2894. * Resizes treegrid.
  2895. */
  2896. Test.panel.TreeGrid.prototype.resizeBody = function() {
  2897. var height = this.getInnerHeight();
  2898. height -= this.resizer.offsetHeight + this.tabPanel.el.offsetHeight + this.header.offsetHeight;
  2899. height -= 2;
  2900. height = Math.max(30, height);
  2901. this.body.style.height = height + 'px';
  2902. };
  2903. /**
  2904. * End of Resizer
  2905. */
  2906. /**
  2907. * Renders specs results.
  2908. * @param {jasmine.Spec} spec The spec.
  2909. * @return {HTMLElement} The spec results dom element.
  2910. */
  2911. Test.panel.TreeGrid.prototype.renderSpecResults = function(spec) {
  2912. var resultItems = spec.results().getItems(),
  2913. length = resultItems.length,
  2914. resultsEl,
  2915. resultEl,
  2916. result,
  2917. i;
  2918. resultsEl = new jasmine.Dom({
  2919. cls: "results"
  2920. });
  2921. for (i = 0; i < length; i++) {
  2922. result = resultItems[i];
  2923. if (result.type === "expect" && result.passed) {
  2924. if (!result.passed()) {
  2925. resultEl = this.renderFailedResult(result);
  2926. } else {
  2927. resultEl = this.renderPassedResult(result);
  2928. }
  2929. if (i === 0) {
  2930. jasmine.Dom.addCls(resultEl, "first");
  2931. }
  2932. resultsEl.appendChild(resultEl);
  2933. if (result.error) {
  2934. break;
  2935. }
  2936. }
  2937. }
  2938. return resultsEl;
  2939. };
  2940. /**
  2941. * Renders failed spec result.
  2942. * @param {Object} result The spec result.
  2943. * @return {HTMLElement} The spec result message HTMLElement
  2944. */
  2945. Test.panel.TreeGrid.prototype.renderFailedResult = function(result) {
  2946. var message = result.message,
  2947. children;
  2948. children = [{
  2949. cls: "prettyPrint",
  2950. html: jasmine.util.htmlEscape(message)
  2951. }];
  2952. return new jasmine.Dom({
  2953. cls: "resultMessage fail",
  2954. children: children
  2955. });
  2956. };
  2957. /**
  2958. * Renders failed spec result.
  2959. * @param {Object} result The spec result.
  2960. * @return {HTMLElement} The spec result message HTMLElement
  2961. */
  2962. Test.panel.TreeGrid.prototype.renderPassedResult = function(result) {
  2963. var children = [{
  2964. cls: "prettyPrint",
  2965. html: "Actual: " + jasmine.pp(result.actual) + "\nExpected: " + jasmine.pp(result.expected) + "\nMatcher: " + result.matcherName + "."
  2966. }];
  2967. return new jasmine.Dom({
  2968. cls: "resultMessage pass",
  2969. children: children
  2970. });
  2971. };
  2972. /**
  2973. * Returns tabPanel console.
  2974. */
  2975. Test.panel.TreeGrid.prototype.getInfoPanel = function() {
  2976. return this.tabPanel.children[0];
  2977. };
  2978. /**
  2979. * Print a message into info console.
  2980. * @param {String} message The message.
  2981. * @param {String} cls (optional) an extra cls to add to the message.
  2982. */
  2983. Test.panel.TreeGrid.prototype.log = function(message, cls) {
  2984. this.getInfoPanel().log(message, cls);
  2985. };
  2986. /**
  2987. * Sets statubar message, this method can also add a className.
  2988. * @param {String} message The message.
  2989. * @param {String} cls The className (optional).
  2990. */
  2991. Test.panel.TreeGrid.prototype.setStatus = function(message, cls) {
  2992. jasmine.Dom.setHTML(this.statusMessage, message);
  2993. if (cls) {
  2994. jasmine.Dom.addCls(this.statusMessage, cls);
  2995. }
  2996. };/**
  2997. * @class Test.Reporter
  2998. * The Sencha Unit Tests Reporter
  2999. */
  3000. Test.Reporter = function(config) {
  3001. config = config || {};
  3002. this.options = Test.Options.get();
  3003. this.runnedSpecsCount = 0;
  3004. this.failedSpecsCount = 0;
  3005. this.disabledSpecsCount = 0;
  3006. this.optionCheckBoxesEl = {};
  3007. this.treeGrid = new Test.panel.TreeGrid({});
  3008. };
  3009. /**
  3010. * Called before runner execution.
  3011. * @param {jasmine.Runner} runner The Jasmine Runner
  3012. */
  3013. Test.Reporter.prototype.reportRunnerStarting = function(runner) {
  3014. this.runner = runner;
  3015. this.startedAt = new Date();
  3016. if (Test.BadGlobals) {
  3017. Test.BadGlobals.setup();
  3018. }
  3019. this.logger = this.treeGrid;
  3020. this.log(">> Started at " + this.startedAt.toString(), "info");
  3021. if (!this.options.remote) {
  3022. this.log(">> Warning! Because you access TestReporter locally, stack trace report isn't available.", "warning");
  3023. }
  3024. this.runner.filter(this.options.suites, this.options.specs);
  3025. if (Test.BadGlobals) {
  3026. Test.BadGlobals.report(this.logger);
  3027. }
  3028. };
  3029. /**
  3030. * Called after Jasmine runner execution ends.
  3031. * @param {jasmine.Runner} runner The Jasmine Runner
  3032. */
  3033. Test.Reporter.prototype.reportRunnerResults = function(runner) {
  3034. Test.jsCoverage.updateTotal();
  3035. this.renderResults(runner);
  3036. };
  3037. /**
  3038. * Called before spec execution.
  3039. * @param {jasmine.Runner} suite The Jasmine spec
  3040. */
  3041. Test.Reporter.prototype.reportSuiteStarting = function(suite) {
  3042. if (this.options.showTimings) {
  3043. suite.startedAt = new Date();
  3044. }
  3045. if (Test.jsCoverage.isEnabled()) {
  3046. Test.jsCoverage.add(suite);
  3047. }
  3048. };
  3049. /**
  3050. * Called after suite execution ends.
  3051. * @param {jasmine.Runner} suite A Jasmine suite
  3052. */
  3053. Test.Reporter.prototype.reportSuiteResults = function(suite) {
  3054. var suiteEl = this.treeGrid ? this.treeGrid.suitesEls[suite.id] : undefined,
  3055. status;
  3056. if (suite.isEnabled()) {
  3057. if (this.options.showTimings) {
  3058. suite.time = (((new Date()).getTime() - suite.startedAt.getTime())/ 1000).toFixed(3);
  3059. }
  3060. Test.jsCoverage.update(suite);
  3061. if (!suite.parentSuite && Test.BadGlobals) {
  3062. Test.BadGlobals.report(this.logger, suite);
  3063. }
  3064. if (this.treeGrid && this.options.showPassed && !suiteEl) {
  3065. suiteEl = this.treeGrid.addSuite(suite);
  3066. }
  3067. if (suiteEl) {
  3068. status = suite.results().passed() ? "passed" : "failed";
  3069. jasmine.Dom.addCls(suiteEl, status);
  3070. jasmine.Dom.addCls(suiteEl.parentNode, status);
  3071. if (Test.jsCoverage.isEnabled()) {
  3072. this.treeGrid.updateSuiteEl(suite, Test.jsCoverage.getSuiteCoverage(suite));
  3073. }
  3074. if (suite.time) {
  3075. this.treeGrid.updateSuiteEl(suite, " (" + suite.time + "s)");
  3076. }
  3077. }
  3078. } else if (this.treeGrid && this.options.showDisabled && !suiteEl) {
  3079. this.treeGrid.addSuite(suite);
  3080. }
  3081. };
  3082. /**
  3083. * Called before spec execution.
  3084. * @param {jasmine.Runner} suite The Jasmine spec
  3085. */
  3086. Test.Reporter.prototype.reportSpecStarting = function(spec) {
  3087. this.currentSpec = spec;
  3088. if (spec.isEnabled()) {
  3089. if (this.options.showTimings) {
  3090. spec.startedAt = new Date();
  3091. }
  3092. this.treeGrid.setStatus("Running: " + jasmine.util.htmlEscape(spec.getFullName()));
  3093. }
  3094. };
  3095. /**
  3096. * Called after spec execution.
  3097. * @param {jasmine.Runner} suite The Jasmine spec
  3098. */
  3099. Test.Reporter.prototype.reportSpecResults = function(spec) {
  3100. var results, status;
  3101. if (spec.isEnabled()) {
  3102. if (this.options.showTimings) {
  3103. spec.time = (((new Date()).getTime() - spec.startedAt.getTime())/ 1000).toFixed(3);
  3104. }
  3105. results = spec.results();
  3106. status = results.passed() ? "passed" : "failed";
  3107. if(status === "failed") {
  3108. this.failedSpecsCount = this.failedSpecsCount + 1;
  3109. }
  3110. if ((status === "failed" || this.options.showPassed) && spec.isEnabled() && this.treeGrid) {
  3111. this.treeGrid.addSpec(spec);
  3112. }
  3113. Test.SandBox.save(spec);
  3114. this.runnedSpecsCount = this.runnedSpecsCount + 1;
  3115. } else {
  3116. this.disabledSpecsCount = this.disabledSpecsCount + 1;
  3117. if (this.treeGrid && this.options.showDisabled) {
  3118. this.treeGrid.addSpec(spec);
  3119. }
  3120. }
  3121. };
  3122. /**
  3123. * Updates runner message with failed and passed specs
  3124. * @param {jasmine.Runner} runner The jasmine runner.
  3125. */
  3126. Test.Reporter.prototype.renderResults = function(runner) {
  3127. var cls = (this.failedSpecsCount > 0) ? "failed" : "passed",
  3128. runTime,
  3129. message;
  3130. runTime = (new Date().getTime() - this.startedAt.getTime()) / 1000;
  3131. message = this.runnedSpecsCount + " spec" +
  3132. (this.runnedSpecsCount === 1 ? "" : "s" ) + " ran, " +
  3133. this.failedSpecsCount + " failure" +
  3134. (this.failedSpecsCount === 1 ? "" : "s") +
  3135. " and " + this.disabledSpecsCount + " disabled";
  3136. message += " in " + runTime + "s";
  3137. message += Test.jsCoverage.getTotal() + ".";
  3138. if (this.treeGrid) {
  3139. if (Test.SandBox.getWin()._$jscoverage) {
  3140. this.treeGrid.tabPanel.addCoverageSummary();
  3141. }
  3142. this.treeGrid.setStatus(message, cls);
  3143. }
  3144. this.log(">> Finished at " + new Date().toString(), "info");
  3145. };
  3146. Test.Reporter.prototype.log = function() {
  3147. if (this.options.verbose || arguments.length === 2) {
  3148. this.logger.log.apply(this.logger, arguments);
  3149. }
  3150. };
  3151. Test.Reporter.prototype.getIframeContainer = function() {
  3152. if (this.treeGrid) {
  3153. return this.treeGrid.tabPanel.children[1].el;
  3154. }
  3155. return document.body;
  3156. };