qunit.js 115 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437
  1. /*!
  2. * QUnit 2.0.1
  3. * https://qunitjs.com/
  4. *
  5. * Copyright jQuery Foundation and other contributors
  6. * Released under the MIT license
  7. * https://jquery.org/license
  8. *
  9. * Date: 2016-07-23T19:39Z
  10. */
  11. ( function( global ) {
  12. var QUnit = {};
  13. var Date = global.Date;
  14. var now = Date.now || function() {
  15. return new Date().getTime();
  16. };
  17. var setTimeout = global.setTimeout;
  18. var clearTimeout = global.clearTimeout;
  19. // Store a local window from the global to allow direct references.
  20. var window = global.window;
  21. var defined = {
  22. document: window && window.document !== undefined,
  23. setTimeout: setTimeout !== undefined,
  24. sessionStorage: ( function() {
  25. var x = "qunit-test-string";
  26. try {
  27. sessionStorage.setItem( x, x );
  28. sessionStorage.removeItem( x );
  29. return true;
  30. } catch ( e ) {
  31. return false;
  32. }
  33. }() )
  34. };
  35. var fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" );
  36. var globalStartCalled = false;
  37. var runStarted = false;
  38. var autorun = false;
  39. var toString = Object.prototype.toString,
  40. hasOwn = Object.prototype.hasOwnProperty;
  41. // Returns a new Array with the elements that are in a but not in b
  42. function diff( a, b ) {
  43. var i, j,
  44. result = a.slice();
  45. for ( i = 0; i < result.length; i++ ) {
  46. for ( j = 0; j < b.length; j++ ) {
  47. if ( result[ i ] === b[ j ] ) {
  48. result.splice( i, 1 );
  49. i--;
  50. break;
  51. }
  52. }
  53. }
  54. return result;
  55. }
  56. // From jquery.js
  57. function inArray( elem, array ) {
  58. if ( array.indexOf ) {
  59. return array.indexOf( elem );
  60. }
  61. for ( var i = 0, length = array.length; i < length; i++ ) {
  62. if ( array[ i ] === elem ) {
  63. return i;
  64. }
  65. }
  66. return -1;
  67. }
  68. /**
  69. * Makes a clone of an object using only Array or Object as base,
  70. * and copies over the own enumerable properties.
  71. *
  72. * @param {Object} obj
  73. * @return {Object} New object with only the own properties (recursively).
  74. */
  75. function objectValues ( obj ) {
  76. var key, val,
  77. vals = QUnit.is( "array", obj ) ? [] : {};
  78. for ( key in obj ) {
  79. if ( hasOwn.call( obj, key ) ) {
  80. val = obj[ key ];
  81. vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
  82. }
  83. }
  84. return vals;
  85. }
  86. function extend( a, b, undefOnly ) {
  87. for ( var prop in b ) {
  88. if ( hasOwn.call( b, prop ) ) {
  89. if ( b[ prop ] === undefined ) {
  90. delete a[ prop ];
  91. } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
  92. a[ prop ] = b[ prop ];
  93. }
  94. }
  95. }
  96. return a;
  97. }
  98. function objectType( obj ) {
  99. if ( typeof obj === "undefined" ) {
  100. return "undefined";
  101. }
  102. // Consider: typeof null === object
  103. if ( obj === null ) {
  104. return "null";
  105. }
  106. var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
  107. type = match && match[ 1 ];
  108. switch ( type ) {
  109. case "Number":
  110. if ( isNaN( obj ) ) {
  111. return "nan";
  112. }
  113. return "number";
  114. case "String":
  115. case "Boolean":
  116. case "Array":
  117. case "Set":
  118. case "Map":
  119. case "Date":
  120. case "RegExp":
  121. case "Function":
  122. case "Symbol":
  123. return type.toLowerCase();
  124. }
  125. if ( typeof obj === "object" ) {
  126. return "object";
  127. }
  128. }
  129. // Safe object type checking
  130. function is( type, obj ) {
  131. return QUnit.objectType( obj ) === type;
  132. }
  133. // Doesn't support IE9, it will return undefined on these browsers
  134. // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
  135. function extractStacktrace( e, offset ) {
  136. offset = offset === undefined ? 4 : offset;
  137. var stack, include, i;
  138. if ( e.stack ) {
  139. stack = e.stack.split( "\n" );
  140. if ( /^error$/i.test( stack[ 0 ] ) ) {
  141. stack.shift();
  142. }
  143. if ( fileName ) {
  144. include = [];
  145. for ( i = offset; i < stack.length; i++ ) {
  146. if ( stack[ i ].indexOf( fileName ) !== -1 ) {
  147. break;
  148. }
  149. include.push( stack[ i ] );
  150. }
  151. if ( include.length ) {
  152. return include.join( "\n" );
  153. }
  154. }
  155. return stack[ offset ];
  156. }
  157. }
  158. function sourceFromStacktrace( offset ) {
  159. var error = new Error();
  160. // Support: Safari <=7 only, IE <=10 - 11 only
  161. // Not all browsers generate the `stack` property for `new Error()`, see also #636
  162. if ( !error.stack ) {
  163. try {
  164. throw error;
  165. } catch ( err ) {
  166. error = err;
  167. }
  168. }
  169. return extractStacktrace( error, offset );
  170. }
  171. /**
  172. * Config object: Maintain internal state
  173. * Later exposed as QUnit.config
  174. * `config` initialized at top of scope
  175. */
  176. var config = {
  177. // The queue of tests to run
  178. queue: [],
  179. // Block until document ready
  180. blocking: true,
  181. // By default, run previously failed tests first
  182. // very useful in combination with "Hide passed tests" checked
  183. reorder: true,
  184. // By default, modify document.title when suite is done
  185. altertitle: true,
  186. // HTML Reporter: collapse every test except the first failing test
  187. // If false, all failing tests will be expanded
  188. collapse: true,
  189. // By default, scroll to top of the page when suite is done
  190. scrolltop: true,
  191. // Depth up-to which object will be dumped
  192. maxDepth: 5,
  193. // When enabled, all tests must call expect()
  194. requireExpects: false,
  195. // Placeholder for user-configurable form-exposed URL parameters
  196. urlConfig: [],
  197. // Set of all modules.
  198. modules: [],
  199. // Stack of nested modules
  200. moduleStack: [],
  201. // The first unnamed module
  202. currentModule: {
  203. name: "",
  204. tests: []
  205. },
  206. callbacks: {}
  207. };
  208. // Push a loose unnamed module to the modules collection
  209. config.modules.push( config.currentModule );
  210. // Register logging callbacks
  211. function registerLoggingCallbacks( obj ) {
  212. var i, l, key,
  213. callbackNames = [ "begin", "done", "log", "testStart", "testDone",
  214. "moduleStart", "moduleDone" ];
  215. function registerLoggingCallback( key ) {
  216. var loggingCallback = function( callback ) {
  217. if ( objectType( callback ) !== "function" ) {
  218. throw new Error(
  219. "QUnit logging methods require a callback function as their first parameters."
  220. );
  221. }
  222. config.callbacks[ key ].push( callback );
  223. };
  224. return loggingCallback;
  225. }
  226. for ( i = 0, l = callbackNames.length; i < l; i++ ) {
  227. key = callbackNames[ i ];
  228. // Initialize key collection of logging callback
  229. if ( objectType( config.callbacks[ key ] ) === "undefined" ) {
  230. config.callbacks[ key ] = [];
  231. }
  232. obj[ key ] = registerLoggingCallback( key );
  233. }
  234. }
  235. function runLoggingCallbacks( key, args ) {
  236. var i, l, callbacks;
  237. callbacks = config.callbacks[ key ];
  238. for ( i = 0, l = callbacks.length; i < l; i++ ) {
  239. callbacks[ i ]( args );
  240. }
  241. }
  242. ( function() {
  243. if ( !defined.document ) {
  244. return;
  245. }
  246. // `onErrorFnPrev` initialized at top of scope
  247. // Preserve other handlers
  248. var onErrorFnPrev = window.onerror;
  249. // Cover uncaught exceptions
  250. // Returning true will suppress the default browser handler,
  251. // returning false will let it run.
  252. window.onerror = function( error, filePath, linerNr ) {
  253. var ret = false;
  254. if ( onErrorFnPrev ) {
  255. ret = onErrorFnPrev( error, filePath, linerNr );
  256. }
  257. // Treat return value as window.onerror itself does,
  258. // Only do our handling if not suppressed.
  259. if ( ret !== true ) {
  260. if ( QUnit.config.current ) {
  261. if ( QUnit.config.current.ignoreGlobalErrors ) {
  262. return true;
  263. }
  264. QUnit.pushFailure( error, filePath + ":" + linerNr );
  265. } else {
  266. QUnit.test( "global failure", extend( function() {
  267. QUnit.pushFailure( error, filePath + ":" + linerNr );
  268. }, { validTest: true } ) );
  269. }
  270. return false;
  271. }
  272. return ret;
  273. };
  274. }() );
  275. // Figure out if we're running the tests from a server or not
  276. QUnit.isLocal = !( defined.document && window.location.protocol !== "file:" );
  277. // Expose the current QUnit version
  278. QUnit.version = "2.0.1";
  279. extend( QUnit, {
  280. // Call on start of module test to prepend name to all tests
  281. module: function( name, testEnvironment, executeNow ) {
  282. var module, moduleFns;
  283. var currentModule = config.currentModule;
  284. if ( arguments.length === 2 ) {
  285. if ( objectType( testEnvironment ) === "function" ) {
  286. executeNow = testEnvironment;
  287. testEnvironment = undefined;
  288. }
  289. }
  290. module = createModule();
  291. if ( testEnvironment && ( testEnvironment.setup || testEnvironment.teardown ) ) {
  292. console.warn(
  293. "Module's `setup` and `teardown` are not hooks anymore on QUnit 2.0, use " +
  294. "`beforeEach` and `afterEach` instead\n" +
  295. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  296. );
  297. }
  298. moduleFns = {
  299. before: setHook( module, "before" ),
  300. beforeEach: setHook( module, "beforeEach" ),
  301. afterEach: setHook( module, "afterEach" ),
  302. after: setHook( module, "after" )
  303. };
  304. if ( objectType( executeNow ) === "function" ) {
  305. config.moduleStack.push( module );
  306. setCurrentModule( module );
  307. executeNow.call( module.testEnvironment, moduleFns );
  308. config.moduleStack.pop();
  309. module = module.parentModule || currentModule;
  310. }
  311. setCurrentModule( module );
  312. function createModule() {
  313. var parentModule = config.moduleStack.length ?
  314. config.moduleStack.slice( -1 )[ 0 ] : null;
  315. var moduleName = parentModule !== null ?
  316. [ parentModule.name, name ].join( " > " ) : name;
  317. var module = {
  318. name: moduleName,
  319. parentModule: parentModule,
  320. tests: [],
  321. moduleId: generateHash( moduleName ),
  322. testsRun: 0
  323. };
  324. var env = {};
  325. if ( parentModule ) {
  326. parentModule.childModule = module;
  327. extend( env, parentModule.testEnvironment );
  328. delete env.beforeEach;
  329. delete env.afterEach;
  330. }
  331. extend( env, testEnvironment );
  332. module.testEnvironment = env;
  333. config.modules.push( module );
  334. return module;
  335. }
  336. function setCurrentModule( module ) {
  337. config.currentModule = module;
  338. }
  339. },
  340. test: test,
  341. skip: skip,
  342. only: only,
  343. start: function( count ) {
  344. var globalStartAlreadyCalled = globalStartCalled;
  345. if ( !config.current ) {
  346. globalStartCalled = true;
  347. if ( runStarted ) {
  348. throw new Error( "Called start() while test already started running" );
  349. } else if ( globalStartAlreadyCalled || count > 1 ) {
  350. throw new Error( "Called start() outside of a test context too many times" );
  351. } else if ( config.autostart ) {
  352. throw new Error( "Called start() outside of a test context when " +
  353. "QUnit.config.autostart was true" );
  354. } else if ( !config.pageLoaded ) {
  355. // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
  356. config.autostart = true;
  357. return;
  358. }
  359. } else {
  360. throw new Error(
  361. "QUnit.start cannot be called inside a test context. This feature is removed in " +
  362. "QUnit 2.0. For async tests, use QUnit.test() with assert.async() instead.\n" +
  363. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  364. );
  365. }
  366. scheduleBegin();
  367. },
  368. config: config,
  369. is: is,
  370. objectType: objectType,
  371. extend: extend,
  372. load: function() {
  373. config.pageLoaded = true;
  374. // Initialize the configuration options
  375. extend( config, {
  376. stats: { all: 0, bad: 0 },
  377. moduleStats: { all: 0, bad: 0 },
  378. started: 0,
  379. updateRate: 1000,
  380. autostart: true,
  381. filter: ""
  382. }, true );
  383. if ( !runStarted ) {
  384. config.blocking = false;
  385. if ( config.autostart ) {
  386. scheduleBegin();
  387. }
  388. }
  389. },
  390. stack: function( offset ) {
  391. offset = ( offset || 0 ) + 2;
  392. return sourceFromStacktrace( offset );
  393. }
  394. } );
  395. registerLoggingCallbacks( QUnit );
  396. function scheduleBegin() {
  397. runStarted = true;
  398. // Add a slight delay to allow definition of more modules and tests.
  399. if ( defined.setTimeout ) {
  400. setTimeout( function() {
  401. begin();
  402. }, 13 );
  403. } else {
  404. begin();
  405. }
  406. }
  407. function begin() {
  408. var i, l,
  409. modulesLog = [];
  410. // If the test run hasn't officially begun yet
  411. if ( !config.started ) {
  412. // Record the time of the test run's beginning
  413. config.started = now();
  414. // Delete the loose unnamed module if unused.
  415. if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
  416. config.modules.shift();
  417. }
  418. // Avoid unnecessary information by not logging modules' test environments
  419. for ( i = 0, l = config.modules.length; i < l; i++ ) {
  420. modulesLog.push( {
  421. name: config.modules[ i ].name,
  422. tests: config.modules[ i ].tests
  423. } );
  424. }
  425. // The test run is officially beginning now
  426. runLoggingCallbacks( "begin", {
  427. totalTests: Test.count,
  428. modules: modulesLog
  429. } );
  430. }
  431. config.blocking = false;
  432. process( true );
  433. }
  434. function process( last ) {
  435. function next() {
  436. process( last );
  437. }
  438. var start = now();
  439. config.depth = ( config.depth || 0 ) + 1;
  440. while ( config.queue.length && !config.blocking ) {
  441. if ( !defined.setTimeout || config.updateRate <= 0 ||
  442. ( ( now() - start ) < config.updateRate ) ) {
  443. if ( config.current ) {
  444. // Reset async tracking for each phase of the Test lifecycle
  445. config.current.usedAsync = false;
  446. }
  447. config.queue.shift()();
  448. } else {
  449. setTimeout( next, 13 );
  450. break;
  451. }
  452. }
  453. config.depth--;
  454. if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
  455. done();
  456. }
  457. }
  458. function done() {
  459. var runtime, passed;
  460. autorun = true;
  461. // Log the last module results
  462. if ( config.previousModule ) {
  463. runLoggingCallbacks( "moduleDone", {
  464. name: config.previousModule.name,
  465. tests: config.previousModule.tests,
  466. failed: config.moduleStats.bad,
  467. passed: config.moduleStats.all - config.moduleStats.bad,
  468. total: config.moduleStats.all,
  469. runtime: now() - config.moduleStats.started
  470. } );
  471. }
  472. delete config.previousModule;
  473. runtime = now() - config.started;
  474. passed = config.stats.all - config.stats.bad;
  475. runLoggingCallbacks( "done", {
  476. failed: config.stats.bad,
  477. passed: passed,
  478. total: config.stats.all,
  479. runtime: runtime
  480. } );
  481. }
  482. function setHook( module, hookName ) {
  483. if ( module.testEnvironment === undefined ) {
  484. module.testEnvironment = {};
  485. }
  486. return function( callback ) {
  487. module.testEnvironment[ hookName ] = callback;
  488. };
  489. }
  490. var unitSampler,
  491. focused = false,
  492. priorityCount = 0;
  493. function Test( settings ) {
  494. var i, l;
  495. ++Test.count;
  496. this.expected = null;
  497. extend( this, settings );
  498. this.assertions = [];
  499. this.semaphore = 0;
  500. this.usedAsync = false;
  501. this.module = config.currentModule;
  502. this.stack = sourceFromStacktrace( 3 );
  503. // Register unique strings
  504. for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
  505. if ( this.module.tests[ i ].name === this.testName ) {
  506. this.testName += " ";
  507. }
  508. }
  509. this.testId = generateHash( this.module.name, this.testName );
  510. this.module.tests.push( {
  511. name: this.testName,
  512. testId: this.testId
  513. } );
  514. if ( settings.skip ) {
  515. // Skipped tests will fully ignore any sent callback
  516. this.callback = function() {};
  517. this.async = false;
  518. this.expected = 0;
  519. } else {
  520. this.assert = new Assert( this );
  521. }
  522. }
  523. Test.count = 0;
  524. Test.prototype = {
  525. before: function() {
  526. if (
  527. // Emit moduleStart when we're switching from one module to another
  528. this.module !== config.previousModule ||
  529. // They could be equal (both undefined) but if the previousModule property doesn't
  530. // yet exist it means this is the first test in a suite that isn't wrapped in a
  531. // module, in which case we'll just emit a moduleStart event for 'undefined'.
  532. // Without this, reporters can get testStart before moduleStart which is a problem.
  533. !hasOwn.call( config, "previousModule" )
  534. ) {
  535. if ( hasOwn.call( config, "previousModule" ) ) {
  536. runLoggingCallbacks( "moduleDone", {
  537. name: config.previousModule.name,
  538. tests: config.previousModule.tests,
  539. failed: config.moduleStats.bad,
  540. passed: config.moduleStats.all - config.moduleStats.bad,
  541. total: config.moduleStats.all,
  542. runtime: now() - config.moduleStats.started
  543. } );
  544. }
  545. config.previousModule = this.module;
  546. config.moduleStats = { all: 0, bad: 0, started: now() };
  547. runLoggingCallbacks( "moduleStart", {
  548. name: this.module.name,
  549. tests: this.module.tests
  550. } );
  551. }
  552. config.current = this;
  553. if ( this.module.testEnvironment ) {
  554. delete this.module.testEnvironment.before;
  555. delete this.module.testEnvironment.beforeEach;
  556. delete this.module.testEnvironment.afterEach;
  557. delete this.module.testEnvironment.after;
  558. }
  559. this.testEnvironment = extend( {}, this.module.testEnvironment );
  560. this.started = now();
  561. runLoggingCallbacks( "testStart", {
  562. name: this.testName,
  563. module: this.module.name,
  564. testId: this.testId
  565. } );
  566. if ( !config.pollution ) {
  567. saveGlobal();
  568. }
  569. },
  570. run: function() {
  571. var promise;
  572. config.current = this;
  573. this.callbackStarted = now();
  574. if ( config.notrycatch ) {
  575. runTest( this );
  576. return;
  577. }
  578. try {
  579. runTest( this );
  580. } catch ( e ) {
  581. this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
  582. this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
  583. // Else next test will carry the responsibility
  584. saveGlobal();
  585. // Restart the tests if they're blocking
  586. if ( config.blocking ) {
  587. internalRecover( this );
  588. }
  589. }
  590. function runTest( test ) {
  591. promise = test.callback.call( test.testEnvironment, test.assert );
  592. test.resolvePromise( promise );
  593. }
  594. },
  595. after: function() {
  596. checkPollution();
  597. },
  598. queueHook: function( hook, hookName, hookOwner ) {
  599. var promise,
  600. test = this;
  601. return function runHook() {
  602. if ( hookName === "before" ) {
  603. if ( hookOwner.testsRun !== 0 ) {
  604. return;
  605. }
  606. test.preserveEnvironment = true;
  607. }
  608. if ( hookName === "after" && hookOwner.testsRun !== numberOfTests( hookOwner ) - 1 ) {
  609. return;
  610. }
  611. config.current = test;
  612. if ( config.notrycatch ) {
  613. callHook();
  614. return;
  615. }
  616. try {
  617. callHook();
  618. } catch ( error ) {
  619. test.pushFailure( hookName + " failed on " + test.testName + ": " +
  620. ( error.message || error ), extractStacktrace( error, 0 ) );
  621. }
  622. function callHook() {
  623. promise = hook.call( test.testEnvironment, test.assert );
  624. test.resolvePromise( promise, hookName );
  625. }
  626. };
  627. },
  628. // Currently only used for module level hooks, can be used to add global level ones
  629. hooks: function( handler ) {
  630. var hooks = [];
  631. function processHooks( test, module ) {
  632. if ( module.parentModule ) {
  633. processHooks( test, module.parentModule );
  634. }
  635. if ( module.testEnvironment &&
  636. QUnit.objectType( module.testEnvironment[ handler ] ) === "function" ) {
  637. hooks.push( test.queueHook( module.testEnvironment[ handler ], handler, module ) );
  638. }
  639. }
  640. // Hooks are ignored on skipped tests
  641. if ( !this.skip ) {
  642. processHooks( this, this.module );
  643. }
  644. return hooks;
  645. },
  646. finish: function() {
  647. config.current = this;
  648. if ( config.requireExpects && this.expected === null ) {
  649. this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
  650. "not called.", this.stack );
  651. } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
  652. this.pushFailure( "Expected " + this.expected + " assertions, but " +
  653. this.assertions.length + " were run", this.stack );
  654. } else if ( this.expected === null && !this.assertions.length ) {
  655. this.pushFailure( "Expected at least one assertion, but none were run - call " +
  656. "expect(0) to accept zero assertions.", this.stack );
  657. }
  658. var i,
  659. skipped = !!this.skip,
  660. bad = 0;
  661. this.runtime = now() - this.started;
  662. config.stats.all += this.assertions.length;
  663. config.moduleStats.all += this.assertions.length;
  664. for ( i = 0; i < this.assertions.length; i++ ) {
  665. if ( !this.assertions[ i ].result ) {
  666. bad++;
  667. config.stats.bad++;
  668. config.moduleStats.bad++;
  669. }
  670. }
  671. notifyTestsRan( this.module );
  672. runLoggingCallbacks( "testDone", {
  673. name: this.testName,
  674. module: this.module.name,
  675. skipped: skipped,
  676. failed: bad,
  677. passed: this.assertions.length - bad,
  678. total: this.assertions.length,
  679. runtime: skipped ? 0 : this.runtime,
  680. // HTML Reporter use
  681. assertions: this.assertions,
  682. testId: this.testId,
  683. // Source of Test
  684. source: this.stack
  685. } );
  686. config.current = undefined;
  687. },
  688. preserveTestEnvironment: function() {
  689. if ( this.preserveEnvironment ) {
  690. this.module.testEnvironment = this.testEnvironment;
  691. this.testEnvironment = extend( {}, this.module.testEnvironment );
  692. }
  693. },
  694. queue: function() {
  695. var priority,
  696. test = this;
  697. if ( !this.valid() ) {
  698. return;
  699. }
  700. function run() {
  701. // Each of these can by async
  702. synchronize( [
  703. function() {
  704. test.before();
  705. },
  706. test.hooks( "before" ),
  707. function() {
  708. test.preserveTestEnvironment();
  709. },
  710. test.hooks( "beforeEach" ),
  711. function() {
  712. test.run();
  713. },
  714. test.hooks( "afterEach" ).reverse(),
  715. test.hooks( "after" ).reverse(),
  716. function() {
  717. test.after();
  718. },
  719. function() {
  720. test.finish();
  721. }
  722. ] );
  723. }
  724. // Prioritize previously failed tests, detected from sessionStorage
  725. priority = QUnit.config.reorder && defined.sessionStorage &&
  726. +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
  727. return synchronize( run, priority, config.seed );
  728. },
  729. pushResult: function( resultInfo ) {
  730. // Destructure of resultInfo = { result, actual, expected, message, negative }
  731. var source,
  732. details = {
  733. module: this.module.name,
  734. name: this.testName,
  735. result: resultInfo.result,
  736. message: resultInfo.message,
  737. actual: resultInfo.actual,
  738. expected: resultInfo.expected,
  739. testId: this.testId,
  740. negative: resultInfo.negative || false,
  741. runtime: now() - this.started
  742. };
  743. if ( !resultInfo.result ) {
  744. source = sourceFromStacktrace();
  745. if ( source ) {
  746. details.source = source;
  747. }
  748. }
  749. runLoggingCallbacks( "log", details );
  750. this.assertions.push( {
  751. result: !!resultInfo.result,
  752. message: resultInfo.message
  753. } );
  754. },
  755. pushFailure: function( message, source, actual ) {
  756. if ( !( this instanceof Test ) ) {
  757. throw new Error( "pushFailure() assertion outside test context, was " +
  758. sourceFromStacktrace( 2 ) );
  759. }
  760. var details = {
  761. module: this.module.name,
  762. name: this.testName,
  763. result: false,
  764. message: message || "error",
  765. actual: actual || null,
  766. testId: this.testId,
  767. runtime: now() - this.started
  768. };
  769. if ( source ) {
  770. details.source = source;
  771. }
  772. runLoggingCallbacks( "log", details );
  773. this.assertions.push( {
  774. result: false,
  775. message: message
  776. } );
  777. },
  778. resolvePromise: function( promise, phase ) {
  779. var then, resume, message,
  780. test = this;
  781. if ( promise != null ) {
  782. then = promise.then;
  783. if ( QUnit.objectType( then ) === "function" ) {
  784. resume = internalStop( test );
  785. then.call(
  786. promise,
  787. function() { resume(); },
  788. function( error ) {
  789. message = "Promise rejected " +
  790. ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
  791. " " + test.testName + ": " + ( error.message || error );
  792. test.pushFailure( message, extractStacktrace( error, 0 ) );
  793. // Else next test will carry the responsibility
  794. saveGlobal();
  795. // Unblock
  796. resume();
  797. }
  798. );
  799. }
  800. }
  801. },
  802. valid: function() {
  803. var filter = config.filter,
  804. regexFilter = /^(!?)\/([\w\W]*)\/(i?$)/.exec( filter ),
  805. module = config.module && config.module.toLowerCase(),
  806. fullName = ( this.module.name + ": " + this.testName );
  807. function moduleChainNameMatch( testModule ) {
  808. var testModuleName = testModule.name ? testModule.name.toLowerCase() : null;
  809. if ( testModuleName === module ) {
  810. return true;
  811. } else if ( testModule.parentModule ) {
  812. return moduleChainNameMatch( testModule.parentModule );
  813. } else {
  814. return false;
  815. }
  816. }
  817. function moduleChainIdMatch( testModule ) {
  818. return inArray( testModule.moduleId, config.moduleId ) > -1 ||
  819. testModule.parentModule && moduleChainIdMatch( testModule.parentModule );
  820. }
  821. // Internally-generated tests are always valid
  822. if ( this.callback && this.callback.validTest ) {
  823. return true;
  824. }
  825. if ( config.moduleId && config.moduleId.length > 0 &&
  826. !moduleChainIdMatch( this.module ) ) {
  827. return false;
  828. }
  829. if ( config.testId && config.testId.length > 0 &&
  830. inArray( this.testId, config.testId ) < 0 ) {
  831. return false;
  832. }
  833. if ( module && !moduleChainNameMatch( this.module ) ) {
  834. return false;
  835. }
  836. if ( !filter ) {
  837. return true;
  838. }
  839. return regexFilter ?
  840. this.regexFilter( !!regexFilter[ 1 ], regexFilter[ 2 ], regexFilter[ 3 ], fullName ) :
  841. this.stringFilter( filter, fullName );
  842. },
  843. regexFilter: function( exclude, pattern, flags, fullName ) {
  844. var regex = new RegExp( pattern, flags );
  845. var match = regex.test( fullName );
  846. return match !== exclude;
  847. },
  848. stringFilter: function( filter, fullName ) {
  849. filter = filter.toLowerCase();
  850. fullName = fullName.toLowerCase();
  851. var include = filter.charAt( 0 ) !== "!";
  852. if ( !include ) {
  853. filter = filter.slice( 1 );
  854. }
  855. // If the filter matches, we need to honour include
  856. if ( fullName.indexOf( filter ) !== -1 ) {
  857. return include;
  858. }
  859. // Otherwise, do the opposite
  860. return !include;
  861. }
  862. };
  863. QUnit.pushFailure = function() {
  864. if ( !QUnit.config.current ) {
  865. throw new Error( "pushFailure() assertion outside test context, in " +
  866. sourceFromStacktrace( 2 ) );
  867. }
  868. // Gets current test obj
  869. var currentTest = QUnit.config.current;
  870. return currentTest.pushFailure.apply( currentTest, arguments );
  871. };
  872. // Based on Java's String.hashCode, a simple but not
  873. // rigorously collision resistant hashing function
  874. function generateHash( module, testName ) {
  875. var hex,
  876. i = 0,
  877. hash = 0,
  878. str = module + "\x1C" + testName,
  879. len = str.length;
  880. for ( ; i < len; i++ ) {
  881. hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
  882. hash |= 0;
  883. }
  884. // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
  885. // strictly necessary but increases user understanding that the id is a SHA-like hash
  886. hex = ( 0x100000000 + hash ).toString( 16 );
  887. if ( hex.length < 8 ) {
  888. hex = "0000000" + hex;
  889. }
  890. return hex.slice( -8 );
  891. }
  892. function synchronize( callback, priority, seed ) {
  893. var last = !priority,
  894. index;
  895. if ( QUnit.objectType( callback ) === "array" ) {
  896. while ( callback.length ) {
  897. synchronize( callback.shift() );
  898. }
  899. return;
  900. }
  901. if ( priority ) {
  902. config.queue.splice( priorityCount++, 0, callback );
  903. } else if ( seed ) {
  904. if ( !unitSampler ) {
  905. unitSampler = unitSamplerGenerator( seed );
  906. }
  907. // Insert into a random position after all priority items
  908. index = Math.floor( unitSampler() * ( config.queue.length - priorityCount + 1 ) );
  909. config.queue.splice( priorityCount + index, 0, callback );
  910. } else {
  911. config.queue.push( callback );
  912. }
  913. if ( autorun && !config.blocking ) {
  914. process( last );
  915. }
  916. }
  917. function unitSamplerGenerator( seed ) {
  918. // 32-bit xorshift, requires only a nonzero seed
  919. // http://excamera.com/sphinx/article-xorshift.html
  920. var sample = parseInt( generateHash( seed ), 16 ) || -1;
  921. return function() {
  922. sample ^= sample << 13;
  923. sample ^= sample >>> 17;
  924. sample ^= sample << 5;
  925. // ECMAScript has no unsigned number type
  926. if ( sample < 0 ) {
  927. sample += 0x100000000;
  928. }
  929. return sample / 0x100000000;
  930. };
  931. }
  932. function saveGlobal() {
  933. config.pollution = [];
  934. if ( config.noglobals ) {
  935. for ( var key in global ) {
  936. if ( hasOwn.call( global, key ) ) {
  937. // In Opera sometimes DOM element ids show up here, ignore them
  938. if ( /^qunit-test-output/.test( key ) ) {
  939. continue;
  940. }
  941. config.pollution.push( key );
  942. }
  943. }
  944. }
  945. }
  946. function checkPollution() {
  947. var newGlobals,
  948. deletedGlobals,
  949. old = config.pollution;
  950. saveGlobal();
  951. newGlobals = diff( config.pollution, old );
  952. if ( newGlobals.length > 0 ) {
  953. QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
  954. }
  955. deletedGlobals = diff( old, config.pollution );
  956. if ( deletedGlobals.length > 0 ) {
  957. QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
  958. }
  959. }
  960. // Will be exposed as QUnit.test
  961. function test( testName, callback ) {
  962. if ( focused ) { return; }
  963. var newTest;
  964. newTest = new Test( {
  965. testName: testName,
  966. callback: callback
  967. } );
  968. newTest.queue();
  969. }
  970. // Will be exposed as QUnit.skip
  971. function skip( testName ) {
  972. if ( focused ) { return; }
  973. var test = new Test( {
  974. testName: testName,
  975. skip: true
  976. } );
  977. test.queue();
  978. }
  979. // Will be exposed as QUnit.only
  980. function only( testName, callback ) {
  981. var newTest;
  982. if ( focused ) { return; }
  983. QUnit.config.queue.length = 0;
  984. focused = true;
  985. newTest = new Test( {
  986. testName: testName,
  987. callback: callback
  988. } );
  989. newTest.queue();
  990. }
  991. // Put a hold on processing and return a function that will release it.
  992. function internalStop( test ) {
  993. var released = false;
  994. test.semaphore += 1;
  995. config.blocking = true;
  996. // Set a recovery timeout, if so configured.
  997. if ( config.testTimeout && defined.setTimeout ) {
  998. clearTimeout( config.timeout );
  999. config.timeout = setTimeout( function() {
  1000. QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
  1001. internalRecover( test );
  1002. }, config.testTimeout );
  1003. }
  1004. return function resume() {
  1005. if ( released ) {
  1006. return;
  1007. }
  1008. released = true;
  1009. test.semaphore -= 1;
  1010. internalStart( test );
  1011. };
  1012. }
  1013. // Forcefully release all processing holds.
  1014. function internalRecover( test ) {
  1015. test.semaphore = 0;
  1016. internalStart( test );
  1017. }
  1018. // Release a processing hold, scheduling a resumption attempt if no holds remain.
  1019. function internalStart( test ) {
  1020. // If semaphore is non-numeric, throw error
  1021. if ( isNaN( test.semaphore ) ) {
  1022. test.semaphore = 0;
  1023. QUnit.pushFailure(
  1024. "Invalid value on test.semaphore",
  1025. sourceFromStacktrace( 2 )
  1026. );
  1027. return;
  1028. }
  1029. // Don't start until equal number of stop-calls
  1030. if ( test.semaphore > 0 ) {
  1031. return;
  1032. }
  1033. // Throw an Error if start is called more often than stop
  1034. if ( test.semaphore < 0 ) {
  1035. test.semaphore = 0;
  1036. QUnit.pushFailure(
  1037. "Tried to restart test while already started (test's semaphore was 0 already)",
  1038. sourceFromStacktrace( 2 )
  1039. );
  1040. return;
  1041. }
  1042. // Add a slight delay to allow more assertions etc.
  1043. if ( defined.setTimeout ) {
  1044. if ( config.timeout ) {
  1045. clearTimeout( config.timeout );
  1046. }
  1047. config.timeout = setTimeout( function() {
  1048. if ( test.semaphore > 0 ) {
  1049. return;
  1050. }
  1051. if ( config.timeout ) {
  1052. clearTimeout( config.timeout );
  1053. }
  1054. begin();
  1055. }, 13 );
  1056. } else {
  1057. begin();
  1058. }
  1059. }
  1060. function numberOfTests( module ) {
  1061. var count = module.tests.length;
  1062. while ( module = module.childModule ) {
  1063. count += module.tests.length;
  1064. }
  1065. return count;
  1066. }
  1067. function notifyTestsRan( module ) {
  1068. module.testsRun++;
  1069. while ( module = module.parentModule ) {
  1070. module.testsRun++;
  1071. }
  1072. }
  1073. function Assert( testContext ) {
  1074. this.test = testContext;
  1075. }
  1076. // Assert helpers
  1077. QUnit.assert = Assert.prototype = {
  1078. // Specify the number of expected assertions to guarantee that failed test
  1079. // (no assertions are run at all) don't slip through.
  1080. expect: function( asserts ) {
  1081. if ( arguments.length === 1 ) {
  1082. this.test.expected = asserts;
  1083. } else {
  1084. return this.test.expected;
  1085. }
  1086. },
  1087. // Put a hold on processing and return a function that will release it a maximum of once.
  1088. async: function( count ) {
  1089. var resume,
  1090. test = this.test,
  1091. popped = false,
  1092. acceptCallCount = count;
  1093. if ( typeof acceptCallCount === "undefined" ) {
  1094. acceptCallCount = 1;
  1095. }
  1096. test.usedAsync = true;
  1097. resume = internalStop( test );
  1098. return function done() {
  1099. if ( popped ) {
  1100. test.pushFailure( "Too many calls to the `assert.async` callback",
  1101. sourceFromStacktrace( 2 ) );
  1102. return;
  1103. }
  1104. acceptCallCount -= 1;
  1105. if ( acceptCallCount > 0 ) {
  1106. return;
  1107. }
  1108. popped = true;
  1109. resume();
  1110. };
  1111. },
  1112. // Exports test.push() to the user API
  1113. // Alias of pushResult.
  1114. push: function( result, actual, expected, message, negative ) {
  1115. var currentAssert = this instanceof Assert ? this : QUnit.config.current.assert;
  1116. return currentAssert.pushResult( {
  1117. result: result,
  1118. actual: actual,
  1119. expected: expected,
  1120. message: message,
  1121. negative: negative
  1122. } );
  1123. },
  1124. pushResult: function( resultInfo ) {
  1125. // Destructure of resultInfo = { result, actual, expected, message, negative }
  1126. var assert = this,
  1127. currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
  1128. // Backwards compatibility fix.
  1129. // Allows the direct use of global exported assertions and QUnit.assert.*
  1130. // Although, it's use is not recommended as it can leak assertions
  1131. // to other tests from async tests, because we only get a reference to the current test,
  1132. // not exactly the test where assertion were intended to be called.
  1133. if ( !currentTest ) {
  1134. throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
  1135. }
  1136. if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
  1137. currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
  1138. sourceFromStacktrace( 2 ) );
  1139. // Allow this assertion to continue running anyway...
  1140. }
  1141. if ( !( assert instanceof Assert ) ) {
  1142. assert = currentTest.assert;
  1143. }
  1144. return assert.test.pushResult( resultInfo );
  1145. },
  1146. ok: function( result, message ) {
  1147. message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
  1148. QUnit.dump.parse( result ) );
  1149. this.pushResult( {
  1150. result: !!result,
  1151. actual: result,
  1152. expected: true,
  1153. message: message
  1154. } );
  1155. },
  1156. notOk: function( result, message ) {
  1157. message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
  1158. QUnit.dump.parse( result ) );
  1159. this.pushResult( {
  1160. result: !result,
  1161. actual: result,
  1162. expected: false,
  1163. message: message
  1164. } );
  1165. },
  1166. equal: function( actual, expected, message ) {
  1167. /*jshint eqeqeq:false */
  1168. this.pushResult( {
  1169. result: expected == actual,
  1170. actual: actual,
  1171. expected: expected,
  1172. message: message
  1173. } );
  1174. },
  1175. notEqual: function( actual, expected, message ) {
  1176. /*jshint eqeqeq:false */
  1177. this.pushResult( {
  1178. result: expected != actual,
  1179. actual: actual,
  1180. expected: expected,
  1181. message: message,
  1182. negative: true
  1183. } );
  1184. },
  1185. propEqual: function( actual, expected, message ) {
  1186. actual = objectValues( actual );
  1187. expected = objectValues( expected );
  1188. this.pushResult( {
  1189. result: QUnit.equiv( actual, expected ),
  1190. actual: actual,
  1191. expected: expected,
  1192. message: message
  1193. } );
  1194. },
  1195. notPropEqual: function( actual, expected, message ) {
  1196. actual = objectValues( actual );
  1197. expected = objectValues( expected );
  1198. this.pushResult( {
  1199. result: !QUnit.equiv( actual, expected ),
  1200. actual: actual,
  1201. expected: expected,
  1202. message: message,
  1203. negative: true
  1204. } );
  1205. },
  1206. deepEqual: function( actual, expected, message ) {
  1207. this.pushResult( {
  1208. result: QUnit.equiv( actual, expected ),
  1209. actual: actual,
  1210. expected: expected,
  1211. message: message
  1212. } );
  1213. },
  1214. notDeepEqual: function( actual, expected, message ) {
  1215. this.pushResult( {
  1216. result: !QUnit.equiv( actual, expected ),
  1217. actual: actual,
  1218. expected: expected,
  1219. message: message,
  1220. negative: true
  1221. } );
  1222. },
  1223. strictEqual: function( actual, expected, message ) {
  1224. this.pushResult( {
  1225. result: expected === actual,
  1226. actual: actual,
  1227. expected: expected,
  1228. message: message
  1229. } );
  1230. },
  1231. notStrictEqual: function( actual, expected, message ) {
  1232. this.pushResult( {
  1233. result: expected !== actual,
  1234. actual: actual,
  1235. expected: expected,
  1236. message: message,
  1237. negative: true
  1238. } );
  1239. },
  1240. "throws": function( block, expected, message ) {
  1241. var actual, expectedType,
  1242. expectedOutput = expected,
  1243. ok = false,
  1244. currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
  1245. // 'expected' is optional unless doing string comparison
  1246. if ( QUnit.objectType( expected ) === "string" ) {
  1247. if ( message == null ) {
  1248. message = expected;
  1249. expected = null;
  1250. } else {
  1251. throw new Error(
  1252. "throws/raises does not accept a string value for the expected argument.\n" +
  1253. "Use a non-string object value (e.g. regExp) instead if it's necessary." +
  1254. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  1255. );
  1256. }
  1257. }
  1258. currentTest.ignoreGlobalErrors = true;
  1259. try {
  1260. block.call( currentTest.testEnvironment );
  1261. } catch ( e ) {
  1262. actual = e;
  1263. }
  1264. currentTest.ignoreGlobalErrors = false;
  1265. if ( actual ) {
  1266. expectedType = QUnit.objectType( expected );
  1267. // We don't want to validate thrown error
  1268. if ( !expected ) {
  1269. ok = true;
  1270. expectedOutput = null;
  1271. // Expected is a regexp
  1272. } else if ( expectedType === "regexp" ) {
  1273. ok = expected.test( errorString( actual ) );
  1274. // Expected is a constructor, maybe an Error constructor
  1275. } else if ( expectedType === "function" && actual instanceof expected ) {
  1276. ok = true;
  1277. // Expected is an Error object
  1278. } else if ( expectedType === "object" ) {
  1279. ok = actual instanceof expected.constructor &&
  1280. actual.name === expected.name &&
  1281. actual.message === expected.message;
  1282. // Expected is a validation function which returns true if validation passed
  1283. } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
  1284. expectedOutput = null;
  1285. ok = true;
  1286. }
  1287. }
  1288. currentTest.assert.pushResult( {
  1289. result: ok,
  1290. actual: actual,
  1291. expected: expectedOutput,
  1292. message: message
  1293. } );
  1294. }
  1295. };
  1296. // Provide an alternative to assert.throws(), for environments that consider throws a reserved word
  1297. // Known to us are: Closure Compiler, Narwhal
  1298. ( function() {
  1299. /*jshint sub:true */
  1300. Assert.prototype.raises = Assert.prototype [ "throws" ]; //jscs:ignore requireDotNotation
  1301. }() );
  1302. function errorString( error ) {
  1303. var name, message,
  1304. resultErrorString = error.toString();
  1305. if ( resultErrorString.substring( 0, 7 ) === "[object" ) {
  1306. name = error.name ? error.name.toString() : "Error";
  1307. message = error.message ? error.message.toString() : "";
  1308. if ( name && message ) {
  1309. return name + ": " + message;
  1310. } else if ( name ) {
  1311. return name;
  1312. } else if ( message ) {
  1313. return message;
  1314. } else {
  1315. return "Error";
  1316. }
  1317. } else {
  1318. return resultErrorString;
  1319. }
  1320. }
  1321. // Test for equality any JavaScript type.
  1322. // Author: Philippe Rathé <prathe@gmail.com>
  1323. QUnit.equiv = ( function() {
  1324. // Stack to decide between skip/abort functions
  1325. var callers = [];
  1326. // Stack to avoiding loops from circular referencing
  1327. var parents = [];
  1328. var parentsB = [];
  1329. var getProto = Object.getPrototypeOf || function( obj ) {
  1330. /*jshint proto: true */
  1331. return obj.__proto__;
  1332. };
  1333. function useStrictEquality( b, a ) {
  1334. // To catch short annotation VS 'new' annotation of a declaration. e.g.:
  1335. // `var i = 1;`
  1336. // `var j = new Number(1);`
  1337. if ( typeof a === "object" ) {
  1338. a = a.valueOf();
  1339. }
  1340. if ( typeof b === "object" ) {
  1341. b = b.valueOf();
  1342. }
  1343. return a === b;
  1344. }
  1345. function compareConstructors( a, b ) {
  1346. var protoA = getProto( a );
  1347. var protoB = getProto( b );
  1348. // Comparing constructors is more strict than using `instanceof`
  1349. if ( a.constructor === b.constructor ) {
  1350. return true;
  1351. }
  1352. // Ref #851
  1353. // If the obj prototype descends from a null constructor, treat it
  1354. // as a null prototype.
  1355. if ( protoA && protoA.constructor === null ) {
  1356. protoA = null;
  1357. }
  1358. if ( protoB && protoB.constructor === null ) {
  1359. protoB = null;
  1360. }
  1361. // Allow objects with no prototype to be equivalent to
  1362. // objects with Object as their constructor.
  1363. if ( ( protoA === null && protoB === Object.prototype ) ||
  1364. ( protoB === null && protoA === Object.prototype ) ) {
  1365. return true;
  1366. }
  1367. return false;
  1368. }
  1369. function getRegExpFlags( regexp ) {
  1370. return "flags" in regexp ? regexp.flags : regexp.toString().match( /[gimuy]*$/ )[ 0 ];
  1371. }
  1372. var callbacks = {
  1373. "string": useStrictEquality,
  1374. "boolean": useStrictEquality,
  1375. "number": useStrictEquality,
  1376. "null": useStrictEquality,
  1377. "undefined": useStrictEquality,
  1378. "symbol": useStrictEquality,
  1379. "date": useStrictEquality,
  1380. "nan": function() {
  1381. return true;
  1382. },
  1383. "regexp": function( b, a ) {
  1384. return a.source === b.source &&
  1385. // Include flags in the comparison
  1386. getRegExpFlags( a ) === getRegExpFlags( b );
  1387. },
  1388. // - skip when the property is a method of an instance (OOP)
  1389. // - abort otherwise,
  1390. // initial === would have catch identical references anyway
  1391. "function": function() {
  1392. var caller = callers[ callers.length - 1 ];
  1393. return caller !== Object && typeof caller !== "undefined";
  1394. },
  1395. "array": function( b, a ) {
  1396. var i, j, len, loop, aCircular, bCircular;
  1397. len = a.length;
  1398. if ( len !== b.length ) {
  1399. // Safe and faster
  1400. return false;
  1401. }
  1402. // Track reference to avoid circular references
  1403. parents.push( a );
  1404. parentsB.push( b );
  1405. for ( i = 0; i < len; i++ ) {
  1406. loop = false;
  1407. for ( j = 0; j < parents.length; j++ ) {
  1408. aCircular = parents[ j ] === a[ i ];
  1409. bCircular = parentsB[ j ] === b[ i ];
  1410. if ( aCircular || bCircular ) {
  1411. if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
  1412. loop = true;
  1413. } else {
  1414. parents.pop();
  1415. parentsB.pop();
  1416. return false;
  1417. }
  1418. }
  1419. }
  1420. if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
  1421. parents.pop();
  1422. parentsB.pop();
  1423. return false;
  1424. }
  1425. }
  1426. parents.pop();
  1427. parentsB.pop();
  1428. return true;
  1429. },
  1430. "set": function( b, a ) {
  1431. var innerEq,
  1432. outerEq = true;
  1433. if ( a.size !== b.size ) {
  1434. return false;
  1435. }
  1436. a.forEach( function( aVal ) {
  1437. innerEq = false;
  1438. b.forEach( function( bVal ) {
  1439. if ( innerEquiv( bVal, aVal ) ) {
  1440. innerEq = true;
  1441. }
  1442. } );
  1443. if ( !innerEq ) {
  1444. outerEq = false;
  1445. }
  1446. } );
  1447. return outerEq;
  1448. },
  1449. "map": function( b, a ) {
  1450. var innerEq,
  1451. outerEq = true;
  1452. if ( a.size !== b.size ) {
  1453. return false;
  1454. }
  1455. a.forEach( function( aVal, aKey ) {
  1456. innerEq = false;
  1457. b.forEach( function( bVal, bKey ) {
  1458. if ( innerEquiv( [ bVal, bKey ], [ aVal, aKey ] ) ) {
  1459. innerEq = true;
  1460. }
  1461. } );
  1462. if ( !innerEq ) {
  1463. outerEq = false;
  1464. }
  1465. } );
  1466. return outerEq;
  1467. },
  1468. "object": function( b, a ) {
  1469. var i, j, loop, aCircular, bCircular;
  1470. // Default to true
  1471. var eq = true;
  1472. var aProperties = [];
  1473. var bProperties = [];
  1474. if ( compareConstructors( a, b ) === false ) {
  1475. return false;
  1476. }
  1477. // Stack constructor before traversing properties
  1478. callers.push( a.constructor );
  1479. // Track reference to avoid circular references
  1480. parents.push( a );
  1481. parentsB.push( b );
  1482. // Be strict: don't ensure hasOwnProperty and go deep
  1483. for ( i in a ) {
  1484. loop = false;
  1485. for ( j = 0; j < parents.length; j++ ) {
  1486. aCircular = parents[ j ] === a[ i ];
  1487. bCircular = parentsB[ j ] === b[ i ];
  1488. if ( aCircular || bCircular ) {
  1489. if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
  1490. loop = true;
  1491. } else {
  1492. eq = false;
  1493. break;
  1494. }
  1495. }
  1496. }
  1497. aProperties.push( i );
  1498. if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
  1499. eq = false;
  1500. break;
  1501. }
  1502. }
  1503. parents.pop();
  1504. parentsB.pop();
  1505. // Unstack, we are done
  1506. callers.pop();
  1507. for ( i in b ) {
  1508. // Collect b's properties
  1509. bProperties.push( i );
  1510. }
  1511. // Ensures identical properties name
  1512. return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
  1513. }
  1514. };
  1515. function typeEquiv( a, b ) {
  1516. var type = QUnit.objectType( a );
  1517. return QUnit.objectType( b ) === type && callbacks[ type ]( b, a );
  1518. }
  1519. // The real equiv function
  1520. function innerEquiv( a, b ) {
  1521. // We're done when there's nothing more to compare
  1522. if ( arguments.length < 2 ) {
  1523. return true;
  1524. }
  1525. // Require type-specific equality
  1526. return ( a === b || typeEquiv( a, b ) ) &&
  1527. // ...across all consecutive argument pairs
  1528. ( arguments.length === 2 || innerEquiv.apply( this, [].slice.call( arguments, 1 ) ) );
  1529. }
  1530. return innerEquiv;
  1531. }() );
  1532. // Based on jsDump by Ariel Flesler
  1533. // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
  1534. QUnit.dump = ( function() {
  1535. function quote( str ) {
  1536. return "\"" + str.toString().replace( /\\/g, "\\\\" ).replace( /"/g, "\\\"" ) + "\"";
  1537. }
  1538. function literal( o ) {
  1539. return o + "";
  1540. }
  1541. function join( pre, arr, post ) {
  1542. var s = dump.separator(),
  1543. base = dump.indent(),
  1544. inner = dump.indent( 1 );
  1545. if ( arr.join ) {
  1546. arr = arr.join( "," + s + inner );
  1547. }
  1548. if ( !arr ) {
  1549. return pre + post;
  1550. }
  1551. return [ pre, inner + arr, base + post ].join( s );
  1552. }
  1553. function array( arr, stack ) {
  1554. var i = arr.length,
  1555. ret = new Array( i );
  1556. if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
  1557. return "[object Array]";
  1558. }
  1559. this.up();
  1560. while ( i-- ) {
  1561. ret[ i ] = this.parse( arr[ i ], undefined, stack );
  1562. }
  1563. this.down();
  1564. return join( "[", ret, "]" );
  1565. }
  1566. function isArray( obj ) {
  1567. return (
  1568. //Native Arrays
  1569. toString.call( obj ) === "[object Array]" ||
  1570. // NodeList objects
  1571. ( typeof obj.length === "number" && obj.item !== undefined ) &&
  1572. ( obj.length ?
  1573. obj.item( 0 ) === obj[ 0 ] :
  1574. ( obj.item( 0 ) === null && obj[ 0 ] === undefined )
  1575. )
  1576. );
  1577. }
  1578. var reName = /^function (\w+)/,
  1579. dump = {
  1580. // The objType is used mostly internally, you can fix a (custom) type in advance
  1581. parse: function( obj, objType, stack ) {
  1582. stack = stack || [];
  1583. var res, parser, parserType,
  1584. inStack = inArray( obj, stack );
  1585. if ( inStack !== -1 ) {
  1586. return "recursion(" + ( inStack - stack.length ) + ")";
  1587. }
  1588. objType = objType || this.typeOf( obj );
  1589. parser = this.parsers[ objType ];
  1590. parserType = typeof parser;
  1591. if ( parserType === "function" ) {
  1592. stack.push( obj );
  1593. res = parser.call( this, obj, stack );
  1594. stack.pop();
  1595. return res;
  1596. }
  1597. return ( parserType === "string" ) ? parser : this.parsers.error;
  1598. },
  1599. typeOf: function( obj ) {
  1600. var type;
  1601. if ( obj === null ) {
  1602. type = "null";
  1603. } else if ( typeof obj === "undefined" ) {
  1604. type = "undefined";
  1605. } else if ( QUnit.is( "regexp", obj ) ) {
  1606. type = "regexp";
  1607. } else if ( QUnit.is( "date", obj ) ) {
  1608. type = "date";
  1609. } else if ( QUnit.is( "function", obj ) ) {
  1610. type = "function";
  1611. } else if ( obj.setInterval !== undefined &&
  1612. obj.document !== undefined &&
  1613. obj.nodeType === undefined ) {
  1614. type = "window";
  1615. } else if ( obj.nodeType === 9 ) {
  1616. type = "document";
  1617. } else if ( obj.nodeType ) {
  1618. type = "node";
  1619. } else if ( isArray( obj ) ) {
  1620. type = "array";
  1621. } else if ( obj.constructor === Error.prototype.constructor ) {
  1622. type = "error";
  1623. } else {
  1624. type = typeof obj;
  1625. }
  1626. return type;
  1627. },
  1628. separator: function() {
  1629. return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
  1630. },
  1631. // Extra can be a number, shortcut for increasing-calling-decreasing
  1632. indent: function( extra ) {
  1633. if ( !this.multiline ) {
  1634. return "";
  1635. }
  1636. var chr = this.indentChar;
  1637. if ( this.HTML ) {
  1638. chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
  1639. }
  1640. return new Array( this.depth + ( extra || 0 ) ).join( chr );
  1641. },
  1642. up: function( a ) {
  1643. this.depth += a || 1;
  1644. },
  1645. down: function( a ) {
  1646. this.depth -= a || 1;
  1647. },
  1648. setParser: function( name, parser ) {
  1649. this.parsers[ name ] = parser;
  1650. },
  1651. // The next 3 are exposed so you can use them
  1652. quote: quote,
  1653. literal: literal,
  1654. join: join,
  1655. depth: 1,
  1656. maxDepth: QUnit.config.maxDepth,
  1657. // This is the list of parsers, to modify them, use dump.setParser
  1658. parsers: {
  1659. window: "[Window]",
  1660. document: "[Document]",
  1661. error: function( error ) {
  1662. return "Error(\"" + error.message + "\")";
  1663. },
  1664. unknown: "[Unknown]",
  1665. "null": "null",
  1666. "undefined": "undefined",
  1667. "function": function( fn ) {
  1668. var ret = "function",
  1669. // Functions never have name in IE
  1670. name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
  1671. if ( name ) {
  1672. ret += " " + name;
  1673. }
  1674. ret += "(";
  1675. ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
  1676. return join( ret, dump.parse( fn, "functionCode" ), "}" );
  1677. },
  1678. array: array,
  1679. nodelist: array,
  1680. "arguments": array,
  1681. object: function( map, stack ) {
  1682. var keys, key, val, i, nonEnumerableProperties,
  1683. ret = [];
  1684. if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
  1685. return "[object Object]";
  1686. }
  1687. dump.up();
  1688. keys = [];
  1689. for ( key in map ) {
  1690. keys.push( key );
  1691. }
  1692. // Some properties are not always enumerable on Error objects.
  1693. nonEnumerableProperties = [ "message", "name" ];
  1694. for ( i in nonEnumerableProperties ) {
  1695. key = nonEnumerableProperties[ i ];
  1696. if ( key in map && inArray( key, keys ) < 0 ) {
  1697. keys.push( key );
  1698. }
  1699. }
  1700. keys.sort();
  1701. for ( i = 0; i < keys.length; i++ ) {
  1702. key = keys[ i ];
  1703. val = map[ key ];
  1704. ret.push( dump.parse( key, "key" ) + ": " +
  1705. dump.parse( val, undefined, stack ) );
  1706. }
  1707. dump.down();
  1708. return join( "{", ret, "}" );
  1709. },
  1710. node: function( node ) {
  1711. var len, i, val,
  1712. open = dump.HTML ? "&lt;" : "<",
  1713. close = dump.HTML ? "&gt;" : ">",
  1714. tag = node.nodeName.toLowerCase(),
  1715. ret = open + tag,
  1716. attrs = node.attributes;
  1717. if ( attrs ) {
  1718. for ( i = 0, len = attrs.length; i < len; i++ ) {
  1719. val = attrs[ i ].nodeValue;
  1720. // IE6 includes all attributes in .attributes, even ones not explicitly
  1721. // set. Those have values like undefined, null, 0, false, "" or
  1722. // "inherit".
  1723. if ( val && val !== "inherit" ) {
  1724. ret += " " + attrs[ i ].nodeName + "=" +
  1725. dump.parse( val, "attribute" );
  1726. }
  1727. }
  1728. }
  1729. ret += close;
  1730. // Show content of TextNode or CDATASection
  1731. if ( node.nodeType === 3 || node.nodeType === 4 ) {
  1732. ret += node.nodeValue;
  1733. }
  1734. return ret + open + "/" + tag + close;
  1735. },
  1736. // Function calls it internally, it's the arguments part of the function
  1737. functionArgs: function( fn ) {
  1738. var args,
  1739. l = fn.length;
  1740. if ( !l ) {
  1741. return "";
  1742. }
  1743. args = new Array( l );
  1744. while ( l-- ) {
  1745. // 97 is 'a'
  1746. args[ l ] = String.fromCharCode( 97 + l );
  1747. }
  1748. return " " + args.join( ", " ) + " ";
  1749. },
  1750. // Object calls it internally, the key part of an item in a map
  1751. key: quote,
  1752. // Function calls it internally, it's the content of the function
  1753. functionCode: "[code]",
  1754. // Node calls it internally, it's a html attribute value
  1755. attribute: quote,
  1756. string: quote,
  1757. date: quote,
  1758. regexp: literal,
  1759. number: literal,
  1760. "boolean": literal,
  1761. symbol: function( sym ) {
  1762. return sym.toString();
  1763. }
  1764. },
  1765. // If true, entities are escaped ( <, >, \t, space and \n )
  1766. HTML: false,
  1767. // Indentation unit
  1768. indentChar: " ",
  1769. // If true, items in a collection, are separated by a \n, else just a space.
  1770. multiline: true
  1771. };
  1772. return dump;
  1773. }() );
  1774. // Back compat
  1775. QUnit.jsDump = QUnit.dump;
  1776. function applyDeprecated( name ) {
  1777. return function() {
  1778. throw new Error(
  1779. name + " is removed in QUnit 2.0.\n" +
  1780. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  1781. );
  1782. };
  1783. }
  1784. Object.keys( Assert.prototype ).forEach( function( key ) {
  1785. QUnit[ key ] = applyDeprecated( "`QUnit." + key + "`" );
  1786. } );
  1787. QUnit.asyncTest = function() {
  1788. throw new Error(
  1789. "asyncTest is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" +
  1790. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  1791. );
  1792. };
  1793. QUnit.stop = function() {
  1794. throw new Error(
  1795. "QUnit.stop is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" +
  1796. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  1797. );
  1798. };
  1799. function resetThrower() {
  1800. throw new Error(
  1801. "QUnit.reset is removed in QUnit 2.0 without replacement.\n" +
  1802. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  1803. );
  1804. }
  1805. Object.defineProperty( QUnit, "reset", {
  1806. get: function() {
  1807. return resetThrower;
  1808. },
  1809. set: resetThrower
  1810. } );
  1811. if ( defined.document ) {
  1812. if ( window.QUnit ) {
  1813. throw new Error( "QUnit has already been defined." );
  1814. }
  1815. [
  1816. "test",
  1817. "module",
  1818. "expect",
  1819. "start",
  1820. "ok",
  1821. "notOk",
  1822. "equal",
  1823. "notEqual",
  1824. "propEqual",
  1825. "notPropEqual",
  1826. "deepEqual",
  1827. "notDeepEqual",
  1828. "strictEqual",
  1829. "notStrictEqual",
  1830. "throws",
  1831. "raises"
  1832. ].forEach( function( key ) {
  1833. window[ key ] = applyDeprecated( "The global `" + key + "`" );
  1834. } );
  1835. window.QUnit = QUnit;
  1836. }
  1837. // For nodejs
  1838. if ( typeof module !== "undefined" && module && module.exports ) {
  1839. module.exports = QUnit;
  1840. // For consistency with CommonJS environments' exports
  1841. module.exports.QUnit = QUnit;
  1842. }
  1843. // For CommonJS with exports, but without module.exports, like Rhino
  1844. if ( typeof exports !== "undefined" && exports ) {
  1845. exports.QUnit = QUnit;
  1846. }
  1847. if ( typeof define === "function" && define.amd ) {
  1848. define( function() {
  1849. return QUnit;
  1850. } );
  1851. QUnit.config.autostart = false;
  1852. }
  1853. // Get a reference to the global object, like window in browsers
  1854. }( ( function() {
  1855. return this;
  1856. }() ) ) );
  1857. ( function() {
  1858. if ( typeof window === "undefined" || !window.document ) {
  1859. return;
  1860. }
  1861. var config = QUnit.config,
  1862. hasOwn = Object.prototype.hasOwnProperty;
  1863. // Stores fixture HTML for resetting later
  1864. function storeFixture() {
  1865. // Avoid overwriting user-defined values
  1866. if ( hasOwn.call( config, "fixture" ) ) {
  1867. return;
  1868. }
  1869. var fixture = document.getElementById( "qunit-fixture" );
  1870. if ( fixture ) {
  1871. config.fixture = fixture.innerHTML;
  1872. }
  1873. }
  1874. QUnit.begin( storeFixture );
  1875. // Resets the fixture DOM element if available.
  1876. function resetFixture() {
  1877. if ( config.fixture == null ) {
  1878. return;
  1879. }
  1880. var fixture = document.getElementById( "qunit-fixture" );
  1881. if ( fixture ) {
  1882. fixture.innerHTML = config.fixture;
  1883. }
  1884. }
  1885. QUnit.testStart( resetFixture );
  1886. }() );
  1887. ( function() {
  1888. // Only interact with URLs via window.location
  1889. var location = typeof window !== "undefined" && window.location;
  1890. if ( !location ) {
  1891. return;
  1892. }
  1893. var urlParams = getUrlParams();
  1894. QUnit.urlParams = urlParams;
  1895. // Match module/test by inclusion in an array
  1896. QUnit.config.moduleId = [].concat( urlParams.moduleId || [] );
  1897. QUnit.config.testId = [].concat( urlParams.testId || [] );
  1898. // Exact case-insensitive match of the module name
  1899. QUnit.config.module = urlParams.module;
  1900. // Regular expression or case-insensitive substring match against "moduleName: testName"
  1901. QUnit.config.filter = urlParams.filter;
  1902. // Test order randomization
  1903. if ( urlParams.seed === true ) {
  1904. // Generate a random seed if the option is specified without a value
  1905. QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
  1906. } else if ( urlParams.seed ) {
  1907. QUnit.config.seed = urlParams.seed;
  1908. }
  1909. // Add URL-parameter-mapped config values with UI form rendering data
  1910. QUnit.config.urlConfig.push(
  1911. {
  1912. id: "hidepassed",
  1913. label: "Hide passed tests",
  1914. tooltip: "Only show tests and assertions that fail. Stored as query-strings."
  1915. },
  1916. {
  1917. id: "noglobals",
  1918. label: "Check for Globals",
  1919. tooltip: "Enabling this will test if any test introduces new properties on the " +
  1920. "global object (`window` in Browsers). Stored as query-strings."
  1921. },
  1922. {
  1923. id: "notrycatch",
  1924. label: "No try-catch",
  1925. tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
  1926. "exceptions in IE reasonable. Stored as query-strings."
  1927. }
  1928. );
  1929. QUnit.begin( function() {
  1930. var i, option,
  1931. urlConfig = QUnit.config.urlConfig;
  1932. for ( i = 0; i < urlConfig.length; i++ ) {
  1933. // Options can be either strings or objects with nonempty "id" properties
  1934. option = QUnit.config.urlConfig[ i ];
  1935. if ( typeof option !== "string" ) {
  1936. option = option.id;
  1937. }
  1938. if ( QUnit.config[ option ] === undefined ) {
  1939. QUnit.config[ option ] = urlParams[ option ];
  1940. }
  1941. }
  1942. } );
  1943. function getUrlParams() {
  1944. var i, param, name, value;
  1945. var urlParams = {};
  1946. var params = location.search.slice( 1 ).split( "&" );
  1947. var length = params.length;
  1948. for ( i = 0; i < length; i++ ) {
  1949. if ( params[ i ] ) {
  1950. param = params[ i ].split( "=" );
  1951. name = decodeQueryParam( param[ 0 ] );
  1952. // Allow just a key to turn on a flag, e.g., test.html?noglobals
  1953. value = param.length === 1 ||
  1954. decodeQueryParam( param.slice( 1 ).join( "=" ) ) ;
  1955. if ( urlParams[ name ] ) {
  1956. urlParams[ name ] = [].concat( urlParams[ name ], value );
  1957. } else {
  1958. urlParams[ name ] = value;
  1959. }
  1960. }
  1961. }
  1962. return urlParams;
  1963. }
  1964. function decodeQueryParam( param ) {
  1965. return decodeURIComponent( param.replace( /\+/g, "%20" ) );
  1966. }
  1967. // Don't load the HTML Reporter on non-browser environments
  1968. if ( typeof window === "undefined" || !window.document ) {
  1969. return;
  1970. }
  1971. QUnit.init = function() {
  1972. throw new Error(
  1973. "QUnit.init is removed in QUnit 2.0, use QUnit.test() with assert.async() instead.\n" +
  1974. "Details in our upgrade guide at https://qunitjs.com/upgrade-guide-2.x/"
  1975. );
  1976. };
  1977. var config = QUnit.config,
  1978. document = window.document,
  1979. collapseNext = false,
  1980. hasOwn = Object.prototype.hasOwnProperty,
  1981. unfilteredUrl = setUrl( { filter: undefined, module: undefined,
  1982. moduleId: undefined, testId: undefined } ),
  1983. defined = {
  1984. sessionStorage: ( function() {
  1985. var x = "qunit-test-string";
  1986. try {
  1987. sessionStorage.setItem( x, x );
  1988. sessionStorage.removeItem( x );
  1989. return true;
  1990. } catch ( e ) {
  1991. return false;
  1992. }
  1993. }() )
  1994. },
  1995. modulesList = [];
  1996. // Escape text for attribute or text content.
  1997. function escapeText( s ) {
  1998. if ( !s ) {
  1999. return "";
  2000. }
  2001. s = s + "";
  2002. // Both single quotes and double quotes (for attributes)
  2003. return s.replace( /['"<>&]/g, function( s ) {
  2004. switch ( s ) {
  2005. case "'":
  2006. return "&#039;";
  2007. case "\"":
  2008. return "&quot;";
  2009. case "<":
  2010. return "&lt;";
  2011. case ">":
  2012. return "&gt;";
  2013. case "&":
  2014. return "&amp;";
  2015. }
  2016. } );
  2017. }
  2018. function addEvent( elem, type, fn ) {
  2019. elem.addEventListener( type, fn, false );
  2020. }
  2021. function removeEvent( elem, type, fn ) {
  2022. elem.removeEventListener( type, fn, false );
  2023. }
  2024. function addEvents( elems, type, fn ) {
  2025. var i = elems.length;
  2026. while ( i-- ) {
  2027. addEvent( elems[ i ], type, fn );
  2028. }
  2029. }
  2030. function hasClass( elem, name ) {
  2031. return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
  2032. }
  2033. function addClass( elem, name ) {
  2034. if ( !hasClass( elem, name ) ) {
  2035. elem.className += ( elem.className ? " " : "" ) + name;
  2036. }
  2037. }
  2038. function toggleClass( elem, name, force ) {
  2039. if ( force || typeof force === "undefined" && !hasClass( elem, name ) ) {
  2040. addClass( elem, name );
  2041. } else {
  2042. removeClass( elem, name );
  2043. }
  2044. }
  2045. function removeClass( elem, name ) {
  2046. var set = " " + elem.className + " ";
  2047. // Class name may appear multiple times
  2048. while ( set.indexOf( " " + name + " " ) >= 0 ) {
  2049. set = set.replace( " " + name + " ", " " );
  2050. }
  2051. // Trim for prettiness
  2052. elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
  2053. }
  2054. function id( name ) {
  2055. return document.getElementById && document.getElementById( name );
  2056. }
  2057. function interceptNavigation( ev ) {
  2058. applyUrlParams();
  2059. if ( ev && ev.preventDefault ) {
  2060. ev.preventDefault();
  2061. }
  2062. return false;
  2063. }
  2064. function getUrlConfigHtml() {
  2065. var i, j, val,
  2066. escaped, escapedTooltip,
  2067. selection = false,
  2068. urlConfig = config.urlConfig,
  2069. urlConfigHtml = "";
  2070. for ( i = 0; i < urlConfig.length; i++ ) {
  2071. // Options can be either strings or objects with nonempty "id" properties
  2072. val = config.urlConfig[ i ];
  2073. if ( typeof val === "string" ) {
  2074. val = {
  2075. id: val,
  2076. label: val
  2077. };
  2078. }
  2079. escaped = escapeText( val.id );
  2080. escapedTooltip = escapeText( val.tooltip );
  2081. if ( !val.value || typeof val.value === "string" ) {
  2082. urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
  2083. "' title='" + escapedTooltip + "'><input id='qunit-urlconfig-" + escaped +
  2084. "' name='" + escaped + "' type='checkbox'" +
  2085. ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
  2086. ( config[ val.id ] ? " checked='checked'" : "" ) +
  2087. " title='" + escapedTooltip + "' />" + escapeText( val.label ) + "</label>";
  2088. } else {
  2089. urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
  2090. "' title='" + escapedTooltip + "'>" + val.label +
  2091. ": </label><select id='qunit-urlconfig-" + escaped +
  2092. "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
  2093. if ( QUnit.is( "array", val.value ) ) {
  2094. for ( j = 0; j < val.value.length; j++ ) {
  2095. escaped = escapeText( val.value[ j ] );
  2096. urlConfigHtml += "<option value='" + escaped + "'" +
  2097. ( config[ val.id ] === val.value[ j ] ?
  2098. ( selection = true ) && " selected='selected'" : "" ) +
  2099. ">" + escaped + "</option>";
  2100. }
  2101. } else {
  2102. for ( j in val.value ) {
  2103. if ( hasOwn.call( val.value, j ) ) {
  2104. urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
  2105. ( config[ val.id ] === j ?
  2106. ( selection = true ) && " selected='selected'" : "" ) +
  2107. ">" + escapeText( val.value[ j ] ) + "</option>";
  2108. }
  2109. }
  2110. }
  2111. if ( config[ val.id ] && !selection ) {
  2112. escaped = escapeText( config[ val.id ] );
  2113. urlConfigHtml += "<option value='" + escaped +
  2114. "' selected='selected' disabled='disabled'>" + escaped + "</option>";
  2115. }
  2116. urlConfigHtml += "</select>";
  2117. }
  2118. }
  2119. return urlConfigHtml;
  2120. }
  2121. // Handle "click" events on toolbar checkboxes and "change" for select menus.
  2122. // Updates the URL with the new state of `config.urlConfig` values.
  2123. function toolbarChanged() {
  2124. var updatedUrl, value, tests,
  2125. field = this,
  2126. params = {};
  2127. // Detect if field is a select menu or a checkbox
  2128. if ( "selectedIndex" in field ) {
  2129. value = field.options[ field.selectedIndex ].value || undefined;
  2130. } else {
  2131. value = field.checked ? ( field.defaultValue || true ) : undefined;
  2132. }
  2133. params[ field.name ] = value;
  2134. updatedUrl = setUrl( params );
  2135. // Check if we can apply the change without a page refresh
  2136. if ( "hidepassed" === field.name && "replaceState" in window.history ) {
  2137. QUnit.urlParams[ field.name ] = value;
  2138. config[ field.name ] = value || false;
  2139. tests = id( "qunit-tests" );
  2140. if ( tests ) {
  2141. toggleClass( tests, "hidepass", value || false );
  2142. }
  2143. window.history.replaceState( null, "", updatedUrl );
  2144. } else {
  2145. window.location = updatedUrl;
  2146. }
  2147. }
  2148. function setUrl( params ) {
  2149. var key, arrValue, i,
  2150. querystring = "?",
  2151. location = window.location;
  2152. params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
  2153. for ( key in params ) {
  2154. // Skip inherited or undefined properties
  2155. if ( hasOwn.call( params, key ) && params[ key ] !== undefined ) {
  2156. // Output a parameter for each value of this key (but usually just one)
  2157. arrValue = [].concat( params[ key ] );
  2158. for ( i = 0; i < arrValue.length; i++ ) {
  2159. querystring += encodeURIComponent( key );
  2160. if ( arrValue[ i ] !== true ) {
  2161. querystring += "=" + encodeURIComponent( arrValue[ i ] );
  2162. }
  2163. querystring += "&";
  2164. }
  2165. }
  2166. }
  2167. return location.protocol + "//" + location.host +
  2168. location.pathname + querystring.slice( 0, -1 );
  2169. }
  2170. function applyUrlParams() {
  2171. var i,
  2172. selectedModules = [],
  2173. modulesList = id( "qunit-modulefilter-dropdown-list" ).getElementsByTagName( "input" ),
  2174. filter = id( "qunit-filter-input" ).value;
  2175. for ( i = 0; i < modulesList.length; i++ ) {
  2176. if ( modulesList[ i ].checked ) {
  2177. selectedModules.push( modulesList[ i ].value );
  2178. }
  2179. }
  2180. window.location = setUrl( {
  2181. filter: ( filter === "" ) ? undefined : filter,
  2182. moduleId: ( selectedModules.length === 0 ) ? undefined : selectedModules,
  2183. // Remove module and testId filter
  2184. module: undefined,
  2185. testId: undefined
  2186. } );
  2187. }
  2188. function toolbarUrlConfigContainer() {
  2189. var urlConfigContainer = document.createElement( "span" );
  2190. urlConfigContainer.innerHTML = getUrlConfigHtml();
  2191. addClass( urlConfigContainer, "qunit-url-config" );
  2192. addEvents( urlConfigContainer.getElementsByTagName( "input" ), "change", toolbarChanged );
  2193. addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
  2194. return urlConfigContainer;
  2195. }
  2196. function toolbarLooseFilter() {
  2197. var filter = document.createElement( "form" ),
  2198. label = document.createElement( "label" ),
  2199. input = document.createElement( "input" ),
  2200. button = document.createElement( "button" );
  2201. addClass( filter, "qunit-filter" );
  2202. label.innerHTML = "Filter: ";
  2203. input.type = "text";
  2204. input.value = config.filter || "";
  2205. input.name = "filter";
  2206. input.id = "qunit-filter-input";
  2207. button.innerHTML = "Go";
  2208. label.appendChild( input );
  2209. filter.appendChild( label );
  2210. filter.appendChild( document.createTextNode( " " ) );
  2211. filter.appendChild( button );
  2212. addEvent( filter, "submit", interceptNavigation );
  2213. return filter;
  2214. }
  2215. function moduleListHtml () {
  2216. var i, checked,
  2217. html = "";
  2218. for ( i = 0; i < config.modules.length; i++ ) {
  2219. if ( config.modules[ i ].name !== "" ) {
  2220. checked = config.moduleId.indexOf( config.modules[ i ].moduleId ) > -1;
  2221. html += "<li><label class='clickable" + ( checked ? " checked" : "" ) +
  2222. "'><input type='checkbox' " + "value='" + config.modules[ i ].moduleId + "'" +
  2223. ( checked ? " checked='checked'" : "" ) + " />" +
  2224. escapeText( config.modules[ i ].name ) + "</label></li>";
  2225. }
  2226. }
  2227. return html;
  2228. }
  2229. function toolbarModuleFilter () {
  2230. var allCheckbox, commit, reset,
  2231. moduleFilter = document.createElement( "form" ),
  2232. label = document.createElement( "label" ),
  2233. moduleSearch = document.createElement( "input" ),
  2234. dropDown = document.createElement( "div" ),
  2235. actions = document.createElement( "span" ),
  2236. dropDownList = document.createElement( "ul" ),
  2237. dirty = false;
  2238. moduleSearch.id = "qunit-modulefilter-search";
  2239. addEvent( moduleSearch, "input", searchInput );
  2240. addEvent( moduleSearch, "input", searchFocus );
  2241. addEvent( moduleSearch, "focus", searchFocus );
  2242. addEvent( moduleSearch, "click", searchFocus );
  2243. label.id = "qunit-modulefilter-search-container";
  2244. label.innerHTML = "Module: ";
  2245. label.appendChild( moduleSearch );
  2246. actions.id = "qunit-modulefilter-actions";
  2247. actions.innerHTML =
  2248. "<button style='display:none'>Apply</button>" +
  2249. "<button type='reset' style='display:none'>Reset</button>" +
  2250. "<label class='clickable" +
  2251. ( config.moduleId.length ? "" : " checked" ) +
  2252. "'><input type='checkbox'" + ( config.moduleId.length ? "" : " checked='checked'" ) +
  2253. ">All modules</label>";
  2254. allCheckbox = actions.lastChild.firstChild;
  2255. commit = actions.firstChild;
  2256. reset = commit.nextSibling;
  2257. addEvent( commit, "click", applyUrlParams );
  2258. dropDownList.id = "qunit-modulefilter-dropdown-list";
  2259. dropDownList.innerHTML = moduleListHtml();
  2260. dropDown.id = "qunit-modulefilter-dropdown";
  2261. dropDown.style.display = "none";
  2262. dropDown.appendChild( actions );
  2263. dropDown.appendChild( dropDownList );
  2264. addEvent( dropDown, "change", selectionChange );
  2265. selectionChange();
  2266. moduleFilter.id = "qunit-modulefilter";
  2267. moduleFilter.appendChild( label );
  2268. moduleFilter.appendChild( dropDown ) ;
  2269. addEvent( moduleFilter, "submit", interceptNavigation );
  2270. addEvent( moduleFilter, "reset", function() {
  2271. // Let the reset happen, then update styles
  2272. window.setTimeout( selectionChange );
  2273. } );
  2274. // Enables show/hide for the dropdown
  2275. function searchFocus() {
  2276. if ( dropDown.style.display !== "none" ) {
  2277. return;
  2278. }
  2279. dropDown.style.display = "block";
  2280. addEvent( document, "click", hideHandler );
  2281. addEvent( document, "keydown", hideHandler );
  2282. // Hide on Escape keydown or outside-container click
  2283. function hideHandler( e ) {
  2284. var inContainer = moduleFilter.contains( e.target );
  2285. if ( e.keyCode === 27 || !inContainer ) {
  2286. if ( e.keyCode === 27 && inContainer ) {
  2287. moduleSearch.focus();
  2288. }
  2289. dropDown.style.display = "none";
  2290. removeEvent( document, "click", hideHandler );
  2291. removeEvent( document, "keydown", hideHandler );
  2292. moduleSearch.value = "";
  2293. searchInput();
  2294. }
  2295. }
  2296. }
  2297. // Processes module search box input
  2298. function searchInput() {
  2299. var i, item,
  2300. searchText = moduleSearch.value.toLowerCase(),
  2301. listItems = dropDownList.children;
  2302. for ( i = 0; i < listItems.length; i++ ) {
  2303. item = listItems[ i ];
  2304. if ( !searchText || item.textContent.toLowerCase().indexOf( searchText ) > -1 ) {
  2305. item.style.display = "";
  2306. } else {
  2307. item.style.display = "none";
  2308. }
  2309. }
  2310. }
  2311. // Processes selection changes
  2312. function selectionChange( evt ) {
  2313. var i, item,
  2314. checkbox = evt && evt.target || allCheckbox,
  2315. modulesList = dropDownList.getElementsByTagName( "input" ),
  2316. selectedNames = [];
  2317. toggleClass( checkbox.parentNode, "checked", checkbox.checked );
  2318. dirty = false;
  2319. if ( checkbox.checked && checkbox !== allCheckbox ) {
  2320. allCheckbox.checked = false;
  2321. removeClass( allCheckbox.parentNode, "checked" );
  2322. }
  2323. for ( i = 0; i < modulesList.length; i++ ) {
  2324. item = modulesList[ i ];
  2325. if ( !evt ) {
  2326. toggleClass( item.parentNode, "checked", item.checked );
  2327. } else if ( checkbox === allCheckbox && checkbox.checked ) {
  2328. item.checked = false;
  2329. removeClass( item.parentNode, "checked" );
  2330. }
  2331. dirty = dirty || ( item.checked !== item.defaultChecked );
  2332. if ( item.checked ) {
  2333. selectedNames.push( item.parentNode.textContent );
  2334. }
  2335. }
  2336. commit.style.display = reset.style.display = dirty ? "" : "none";
  2337. moduleSearch.placeholder = selectedNames.join( ", " ) || allCheckbox.parentNode.textContent;
  2338. moduleSearch.title = "Type to filter list. Current selection:\n" +
  2339. ( selectedNames.join( "\n" ) || allCheckbox.parentNode.textContent );
  2340. }
  2341. return moduleFilter;
  2342. }
  2343. function appendToolbar() {
  2344. var toolbar = id( "qunit-testrunner-toolbar" );
  2345. if ( toolbar ) {
  2346. toolbar.appendChild( toolbarUrlConfigContainer() );
  2347. toolbar.appendChild( toolbarModuleFilter() );
  2348. toolbar.appendChild( toolbarLooseFilter() );
  2349. toolbar.appendChild( document.createElement( "div" ) ).className = "clearfix";
  2350. }
  2351. }
  2352. function appendHeader() {
  2353. var header = id( "qunit-header" );
  2354. if ( header ) {
  2355. header.innerHTML = "<a href='" + escapeText( unfilteredUrl ) + "'>" + header.innerHTML +
  2356. "</a> ";
  2357. }
  2358. }
  2359. function appendBanner() {
  2360. var banner = id( "qunit-banner" );
  2361. if ( banner ) {
  2362. banner.className = "";
  2363. }
  2364. }
  2365. function appendTestResults() {
  2366. var tests = id( "qunit-tests" ),
  2367. result = id( "qunit-testresult" );
  2368. if ( result ) {
  2369. result.parentNode.removeChild( result );
  2370. }
  2371. if ( tests ) {
  2372. tests.innerHTML = "";
  2373. result = document.createElement( "p" );
  2374. result.id = "qunit-testresult";
  2375. result.className = "result";
  2376. tests.parentNode.insertBefore( result, tests );
  2377. result.innerHTML = "Running...<br />&#160;";
  2378. }
  2379. }
  2380. function appendFilteredTest() {
  2381. var testId = QUnit.config.testId;
  2382. if ( !testId || testId.length <= 0 ) {
  2383. return "";
  2384. }
  2385. return "<div id='qunit-filteredTest'>Rerunning selected tests: " +
  2386. escapeText( testId.join( ", " ) ) +
  2387. " <a id='qunit-clearFilter' href='" +
  2388. escapeText( unfilteredUrl ) +
  2389. "'>Run all tests</a></div>";
  2390. }
  2391. function appendUserAgent() {
  2392. var userAgent = id( "qunit-userAgent" );
  2393. if ( userAgent ) {
  2394. userAgent.innerHTML = "";
  2395. userAgent.appendChild(
  2396. document.createTextNode(
  2397. "QUnit " + QUnit.version + "; " + navigator.userAgent
  2398. )
  2399. );
  2400. }
  2401. }
  2402. function appendInterface() {
  2403. var qunit = id( "qunit" );
  2404. if ( qunit ) {
  2405. qunit.innerHTML =
  2406. "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
  2407. "<h2 id='qunit-banner'></h2>" +
  2408. "<div id='qunit-testrunner-toolbar'></div>" +
  2409. appendFilteredTest() +
  2410. "<h2 id='qunit-userAgent'></h2>" +
  2411. "<ol id='qunit-tests'></ol>";
  2412. }
  2413. appendHeader();
  2414. appendBanner();
  2415. appendTestResults();
  2416. appendUserAgent();
  2417. appendToolbar();
  2418. }
  2419. function appendTestsList( modules ) {
  2420. var i, l, x, z, test, moduleObj;
  2421. for ( i = 0, l = modules.length; i < l; i++ ) {
  2422. moduleObj = modules[ i ];
  2423. for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
  2424. test = moduleObj.tests[ x ];
  2425. appendTest( test.name, test.testId, moduleObj.name );
  2426. }
  2427. }
  2428. }
  2429. function appendTest( name, testId, moduleName ) {
  2430. var title, rerunTrigger, testBlock, assertList,
  2431. tests = id( "qunit-tests" );
  2432. if ( !tests ) {
  2433. return;
  2434. }
  2435. title = document.createElement( "strong" );
  2436. title.innerHTML = getNameHtml( name, moduleName );
  2437. rerunTrigger = document.createElement( "a" );
  2438. rerunTrigger.innerHTML = "Rerun";
  2439. rerunTrigger.href = setUrl( { testId: testId } );
  2440. testBlock = document.createElement( "li" );
  2441. testBlock.appendChild( title );
  2442. testBlock.appendChild( rerunTrigger );
  2443. testBlock.id = "qunit-test-output-" + testId;
  2444. assertList = document.createElement( "ol" );
  2445. assertList.className = "qunit-assert-list";
  2446. testBlock.appendChild( assertList );
  2447. tests.appendChild( testBlock );
  2448. }
  2449. // HTML Reporter initialization and load
  2450. QUnit.begin( function( details ) {
  2451. var i, moduleObj, tests;
  2452. // Sort modules by name for the picker
  2453. for ( i = 0; i < details.modules.length; i++ ) {
  2454. moduleObj = details.modules[ i ];
  2455. if ( moduleObj.name ) {
  2456. modulesList.push( moduleObj.name );
  2457. }
  2458. }
  2459. modulesList.sort( function( a, b ) {
  2460. return a.localeCompare( b );
  2461. } );
  2462. // Initialize QUnit elements
  2463. appendInterface();
  2464. appendTestsList( details.modules );
  2465. tests = id( "qunit-tests" );
  2466. if ( tests && config.hidepassed ) {
  2467. addClass( tests, "hidepass" );
  2468. }
  2469. } );
  2470. QUnit.done( function( details ) {
  2471. var i, key,
  2472. banner = id( "qunit-banner" ),
  2473. tests = id( "qunit-tests" ),
  2474. html = [
  2475. "Tests completed in ",
  2476. details.runtime,
  2477. " milliseconds.<br />",
  2478. "<span class='passed'>",
  2479. details.passed,
  2480. "</span> assertions of <span class='total'>",
  2481. details.total,
  2482. "</span> passed, <span class='failed'>",
  2483. details.failed,
  2484. "</span> failed."
  2485. ].join( "" );
  2486. if ( banner ) {
  2487. banner.className = details.failed ? "qunit-fail" : "qunit-pass";
  2488. }
  2489. if ( tests ) {
  2490. id( "qunit-testresult" ).innerHTML = html;
  2491. }
  2492. if ( config.altertitle && document.title ) {
  2493. // Show ✖ for good, ✔ for bad suite result in title
  2494. // use escape sequences in case file gets loaded with non-utf-8-charset
  2495. document.title = [
  2496. ( details.failed ? "\u2716" : "\u2714" ),
  2497. document.title.replace( /^[\u2714\u2716] /i, "" )
  2498. ].join( " " );
  2499. }
  2500. // Clear own sessionStorage items if all tests passed
  2501. if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
  2502. for ( i = 0; i < sessionStorage.length; i++ ) {
  2503. key = sessionStorage.key( i++ );
  2504. if ( key.indexOf( "qunit-test-" ) === 0 ) {
  2505. sessionStorage.removeItem( key );
  2506. }
  2507. }
  2508. }
  2509. // Scroll back to top to show results
  2510. if ( config.scrolltop && window.scrollTo ) {
  2511. window.scrollTo( 0, 0 );
  2512. }
  2513. } );
  2514. function getNameHtml( name, module ) {
  2515. var nameHtml = "";
  2516. if ( module ) {
  2517. nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
  2518. }
  2519. nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
  2520. return nameHtml;
  2521. }
  2522. QUnit.testStart( function( details ) {
  2523. var running, testBlock, bad;
  2524. testBlock = id( "qunit-test-output-" + details.testId );
  2525. if ( testBlock ) {
  2526. testBlock.className = "running";
  2527. } else {
  2528. // Report later registered tests
  2529. appendTest( details.name, details.testId, details.module );
  2530. }
  2531. running = id( "qunit-testresult" );
  2532. if ( running ) {
  2533. bad = QUnit.config.reorder && defined.sessionStorage &&
  2534. +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
  2535. running.innerHTML = ( bad ?
  2536. "Rerunning previously failed test: <br />" :
  2537. "Running: <br />" ) +
  2538. getNameHtml( details.name, details.module );
  2539. }
  2540. } );
  2541. function stripHtml( string ) {
  2542. // Strip tags, html entity and whitespaces
  2543. return string.replace( /<\/?[^>]+(>|$)/g, "" ).replace( /\&quot;/g, "" ).replace( /\s+/g, "" );
  2544. }
  2545. QUnit.log( function( details ) {
  2546. var assertList, assertLi,
  2547. message, expected, actual, diff,
  2548. showDiff = false,
  2549. testItem = id( "qunit-test-output-" + details.testId );
  2550. if ( !testItem ) {
  2551. return;
  2552. }
  2553. message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
  2554. message = "<span class='test-message'>" + message + "</span>";
  2555. message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
  2556. // The pushFailure doesn't provide details.expected
  2557. // when it calls, it's implicit to also not show expected and diff stuff
  2558. // Also, we need to check details.expected existence, as it can exist and be undefined
  2559. if ( !details.result && hasOwn.call( details, "expected" ) ) {
  2560. if ( details.negative ) {
  2561. expected = "NOT " + QUnit.dump.parse( details.expected );
  2562. } else {
  2563. expected = QUnit.dump.parse( details.expected );
  2564. }
  2565. actual = QUnit.dump.parse( details.actual );
  2566. message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
  2567. escapeText( expected ) +
  2568. "</pre></td></tr>";
  2569. if ( actual !== expected ) {
  2570. message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
  2571. escapeText( actual ) + "</pre></td></tr>";
  2572. // Don't show diff if actual or expected are booleans
  2573. if ( !( /^(true|false)$/.test( actual ) ) &&
  2574. !( /^(true|false)$/.test( expected ) ) ) {
  2575. diff = QUnit.diff( expected, actual );
  2576. showDiff = stripHtml( diff ).length !==
  2577. stripHtml( expected ).length +
  2578. stripHtml( actual ).length;
  2579. }
  2580. // Don't show diff if expected and actual are totally different
  2581. if ( showDiff ) {
  2582. message += "<tr class='test-diff'><th>Diff: </th><td><pre>" +
  2583. diff + "</pre></td></tr>";
  2584. }
  2585. } else if ( expected.indexOf( "[object Array]" ) !== -1 ||
  2586. expected.indexOf( "[object Object]" ) !== -1 ) {
  2587. message += "<tr class='test-message'><th>Message: </th><td>" +
  2588. "Diff suppressed as the depth of object is more than current max depth (" +
  2589. QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
  2590. " run with a higher max depth or <a href='" +
  2591. escapeText( setUrl( { maxDepth: -1 } ) ) + "'>" +
  2592. "Rerun</a> without max depth.</p></td></tr>";
  2593. } else {
  2594. message += "<tr class='test-message'><th>Message: </th><td>" +
  2595. "Diff suppressed as the expected and actual results have an equivalent" +
  2596. " serialization</td></tr>";
  2597. }
  2598. if ( details.source ) {
  2599. message += "<tr class='test-source'><th>Source: </th><td><pre>" +
  2600. escapeText( details.source ) + "</pre></td></tr>";
  2601. }
  2602. message += "</table>";
  2603. // This occurs when pushFailure is set and we have an extracted stack trace
  2604. } else if ( !details.result && details.source ) {
  2605. message += "<table>" +
  2606. "<tr class='test-source'><th>Source: </th><td><pre>" +
  2607. escapeText( details.source ) + "</pre></td></tr>" +
  2608. "</table>";
  2609. }
  2610. assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
  2611. assertLi = document.createElement( "li" );
  2612. assertLi.className = details.result ? "pass" : "fail";
  2613. assertLi.innerHTML = message;
  2614. assertList.appendChild( assertLi );
  2615. } );
  2616. QUnit.testDone( function( details ) {
  2617. var testTitle, time, testItem, assertList,
  2618. good, bad, testCounts, skipped, sourceName,
  2619. tests = id( "qunit-tests" );
  2620. if ( !tests ) {
  2621. return;
  2622. }
  2623. testItem = id( "qunit-test-output-" + details.testId );
  2624. assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
  2625. good = details.passed;
  2626. bad = details.failed;
  2627. // Store result when possible
  2628. if ( config.reorder && defined.sessionStorage ) {
  2629. if ( bad ) {
  2630. sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
  2631. } else {
  2632. sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
  2633. }
  2634. }
  2635. if ( bad === 0 ) {
  2636. // Collapse the passing tests
  2637. addClass( assertList, "qunit-collapsed" );
  2638. } else if ( bad && config.collapse && !collapseNext ) {
  2639. // Skip collapsing the first failing test
  2640. collapseNext = true;
  2641. } else {
  2642. // Collapse remaining tests
  2643. addClass( assertList, "qunit-collapsed" );
  2644. }
  2645. // The testItem.firstChild is the test name
  2646. testTitle = testItem.firstChild;
  2647. testCounts = bad ?
  2648. "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
  2649. "";
  2650. testTitle.innerHTML += " <b class='counts'>(" + testCounts +
  2651. details.assertions.length + ")</b>";
  2652. if ( details.skipped ) {
  2653. testItem.className = "skipped";
  2654. skipped = document.createElement( "em" );
  2655. skipped.className = "qunit-skipped-label";
  2656. skipped.innerHTML = "skipped";
  2657. testItem.insertBefore( skipped, testTitle );
  2658. } else {
  2659. addEvent( testTitle, "click", function() {
  2660. toggleClass( assertList, "qunit-collapsed" );
  2661. } );
  2662. testItem.className = bad ? "fail" : "pass";
  2663. time = document.createElement( "span" );
  2664. time.className = "runtime";
  2665. time.innerHTML = details.runtime + " ms";
  2666. testItem.insertBefore( time, assertList );
  2667. }
  2668. // Show the source of the test when showing assertions
  2669. if ( details.source ) {
  2670. sourceName = document.createElement( "p" );
  2671. sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
  2672. addClass( sourceName, "qunit-source" );
  2673. if ( bad === 0 ) {
  2674. addClass( sourceName, "qunit-collapsed" );
  2675. }
  2676. addEvent( testTitle, "click", function() {
  2677. toggleClass( sourceName, "qunit-collapsed" );
  2678. } );
  2679. testItem.appendChild( sourceName );
  2680. }
  2681. } );
  2682. // Avoid readyState issue with phantomjs
  2683. // Ref: #818
  2684. var notPhantom = ( function( p ) {
  2685. return !( p && p.version && p.version.major > 0 );
  2686. } )( window.phantom );
  2687. if ( notPhantom && document.readyState === "complete" ) {
  2688. QUnit.load();
  2689. } else {
  2690. addEvent( window, "load", QUnit.load );
  2691. }
  2692. /*
  2693. * This file is a modified version of google-diff-match-patch's JavaScript implementation
  2694. * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
  2695. * modifications are licensed as more fully set forth in LICENSE.txt.
  2696. *
  2697. * The original source of google-diff-match-patch is attributable and licensed as follows:
  2698. *
  2699. * Copyright 2006 Google Inc.
  2700. * https://code.google.com/p/google-diff-match-patch/
  2701. *
  2702. * Licensed under the Apache License, Version 2.0 (the "License");
  2703. * you may not use this file except in compliance with the License.
  2704. * You may obtain a copy of the License at
  2705. *
  2706. * https://www.apache.org/licenses/LICENSE-2.0
  2707. *
  2708. * Unless required by applicable law or agreed to in writing, software
  2709. * distributed under the License is distributed on an "AS IS" BASIS,
  2710. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  2711. * See the License for the specific language governing permissions and
  2712. * limitations under the License.
  2713. *
  2714. * More Info:
  2715. * https://code.google.com/p/google-diff-match-patch/
  2716. *
  2717. * Usage: QUnit.diff(expected, actual)
  2718. *
  2719. */
  2720. QUnit.diff = ( function() {
  2721. function DiffMatchPatch() {
  2722. }
  2723. // DIFF FUNCTIONS
  2724. /**
  2725. * The data structure representing a diff is an array of tuples:
  2726. * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
  2727. * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
  2728. */
  2729. var DIFF_DELETE = -1,
  2730. DIFF_INSERT = 1,
  2731. DIFF_EQUAL = 0;
  2732. /**
  2733. * Find the differences between two texts. Simplifies the problem by stripping
  2734. * any common prefix or suffix off the texts before diffing.
  2735. * @param {string} text1 Old string to be diffed.
  2736. * @param {string} text2 New string to be diffed.
  2737. * @param {boolean=} optChecklines Optional speedup flag. If present and false,
  2738. * then don't run a line-level diff first to identify the changed areas.
  2739. * Defaults to true, which does a faster, slightly less optimal diff.
  2740. * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
  2741. */
  2742. DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines ) {
  2743. var deadline, checklines, commonlength,
  2744. commonprefix, commonsuffix, diffs;
  2745. // The diff must be complete in up to 1 second.
  2746. deadline = ( new Date() ).getTime() + 1000;
  2747. // Check for null inputs.
  2748. if ( text1 === null || text2 === null ) {
  2749. throw new Error( "Null input. (DiffMain)" );
  2750. }
  2751. // Check for equality (speedup).
  2752. if ( text1 === text2 ) {
  2753. if ( text1 ) {
  2754. return [
  2755. [ DIFF_EQUAL, text1 ]
  2756. ];
  2757. }
  2758. return [];
  2759. }
  2760. if ( typeof optChecklines === "undefined" ) {
  2761. optChecklines = true;
  2762. }
  2763. checklines = optChecklines;
  2764. // Trim off common prefix (speedup).
  2765. commonlength = this.diffCommonPrefix( text1, text2 );
  2766. commonprefix = text1.substring( 0, commonlength );
  2767. text1 = text1.substring( commonlength );
  2768. text2 = text2.substring( commonlength );
  2769. // Trim off common suffix (speedup).
  2770. commonlength = this.diffCommonSuffix( text1, text2 );
  2771. commonsuffix = text1.substring( text1.length - commonlength );
  2772. text1 = text1.substring( 0, text1.length - commonlength );
  2773. text2 = text2.substring( 0, text2.length - commonlength );
  2774. // Compute the diff on the middle block.
  2775. diffs = this.diffCompute( text1, text2, checklines, deadline );
  2776. // Restore the prefix and suffix.
  2777. if ( commonprefix ) {
  2778. diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
  2779. }
  2780. if ( commonsuffix ) {
  2781. diffs.push( [ DIFF_EQUAL, commonsuffix ] );
  2782. }
  2783. this.diffCleanupMerge( diffs );
  2784. return diffs;
  2785. };
  2786. /**
  2787. * Reduce the number of edits by eliminating operationally trivial equalities.
  2788. * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
  2789. */
  2790. DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
  2791. var changes, equalities, equalitiesLength, lastequality,
  2792. pointer, preIns, preDel, postIns, postDel;
  2793. changes = false;
  2794. equalities = []; // Stack of indices where equalities are found.
  2795. equalitiesLength = 0; // Keeping our own length var is faster in JS.
  2796. /** @type {?string} */
  2797. lastequality = null;
  2798. // Always equal to diffs[equalities[equalitiesLength - 1]][1]
  2799. pointer = 0; // Index of current position.
  2800. // Is there an insertion operation before the last equality.
  2801. preIns = false;
  2802. // Is there a deletion operation before the last equality.
  2803. preDel = false;
  2804. // Is there an insertion operation after the last equality.
  2805. postIns = false;
  2806. // Is there a deletion operation after the last equality.
  2807. postDel = false;
  2808. while ( pointer < diffs.length ) {
  2809. // Equality found.
  2810. if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) {
  2811. if ( diffs[ pointer ][ 1 ].length < 4 && ( postIns || postDel ) ) {
  2812. // Candidate found.
  2813. equalities[ equalitiesLength++ ] = pointer;
  2814. preIns = postIns;
  2815. preDel = postDel;
  2816. lastequality = diffs[ pointer ][ 1 ];
  2817. } else {
  2818. // Not a candidate, and can never become one.
  2819. equalitiesLength = 0;
  2820. lastequality = null;
  2821. }
  2822. postIns = postDel = false;
  2823. // An insertion or deletion.
  2824. } else {
  2825. if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
  2826. postDel = true;
  2827. } else {
  2828. postIns = true;
  2829. }
  2830. /*
  2831. * Five types to be split:
  2832. * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
  2833. * <ins>A</ins>X<ins>C</ins><del>D</del>
  2834. * <ins>A</ins><del>B</del>X<ins>C</ins>
  2835. * <ins>A</del>X<ins>C</ins><del>D</del>
  2836. * <ins>A</ins><del>B</del>X<del>C</del>
  2837. */
  2838. if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
  2839. ( ( lastequality.length < 2 ) &&
  2840. ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
  2841. // Duplicate record.
  2842. diffs.splice(
  2843. equalities[ equalitiesLength - 1 ],
  2844. 0,
  2845. [ DIFF_DELETE, lastequality ]
  2846. );
  2847. // Change second copy to insert.
  2848. diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
  2849. equalitiesLength--; // Throw away the equality we just deleted;
  2850. lastequality = null;
  2851. if ( preIns && preDel ) {
  2852. // No changes made which could affect previous entry, keep going.
  2853. postIns = postDel = true;
  2854. equalitiesLength = 0;
  2855. } else {
  2856. equalitiesLength--; // Throw away the previous equality.
  2857. pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
  2858. postIns = postDel = false;
  2859. }
  2860. changes = true;
  2861. }
  2862. }
  2863. pointer++;
  2864. }
  2865. if ( changes ) {
  2866. this.diffCleanupMerge( diffs );
  2867. }
  2868. };
  2869. /**
  2870. * Convert a diff array into a pretty HTML report.
  2871. * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
  2872. * @param {integer} string to be beautified.
  2873. * @return {string} HTML representation.
  2874. */
  2875. DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
  2876. var op, data, x,
  2877. html = [];
  2878. for ( x = 0; x < diffs.length; x++ ) {
  2879. op = diffs[ x ][ 0 ]; // Operation (insert, delete, equal)
  2880. data = diffs[ x ][ 1 ]; // Text of change.
  2881. switch ( op ) {
  2882. case DIFF_INSERT:
  2883. html[ x ] = "<ins>" + escapeText( data ) + "</ins>";
  2884. break;
  2885. case DIFF_DELETE:
  2886. html[ x ] = "<del>" + escapeText( data ) + "</del>";
  2887. break;
  2888. case DIFF_EQUAL:
  2889. html[ x ] = "<span>" + escapeText( data ) + "</span>";
  2890. break;
  2891. }
  2892. }
  2893. return html.join( "" );
  2894. };
  2895. /**
  2896. * Determine the common prefix of two strings.
  2897. * @param {string} text1 First string.
  2898. * @param {string} text2 Second string.
  2899. * @return {number} The number of characters common to the start of each
  2900. * string.
  2901. */
  2902. DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
  2903. var pointermid, pointermax, pointermin, pointerstart;
  2904. // Quick check for common null cases.
  2905. if ( !text1 || !text2 || text1.charAt( 0 ) !== text2.charAt( 0 ) ) {
  2906. return 0;
  2907. }
  2908. // Binary search.
  2909. // Performance analysis: https://neil.fraser.name/news/2007/10/09/
  2910. pointermin = 0;
  2911. pointermax = Math.min( text1.length, text2.length );
  2912. pointermid = pointermax;
  2913. pointerstart = 0;
  2914. while ( pointermin < pointermid ) {
  2915. if ( text1.substring( pointerstart, pointermid ) ===
  2916. text2.substring( pointerstart, pointermid ) ) {
  2917. pointermin = pointermid;
  2918. pointerstart = pointermin;
  2919. } else {
  2920. pointermax = pointermid;
  2921. }
  2922. pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
  2923. }
  2924. return pointermid;
  2925. };
  2926. /**
  2927. * Determine the common suffix of two strings.
  2928. * @param {string} text1 First string.
  2929. * @param {string} text2 Second string.
  2930. * @return {number} The number of characters common to the end of each string.
  2931. */
  2932. DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
  2933. var pointermid, pointermax, pointermin, pointerend;
  2934. // Quick check for common null cases.
  2935. if ( !text1 ||
  2936. !text2 ||
  2937. text1.charAt( text1.length - 1 ) !== text2.charAt( text2.length - 1 ) ) {
  2938. return 0;
  2939. }
  2940. // Binary search.
  2941. // Performance analysis: https://neil.fraser.name/news/2007/10/09/
  2942. pointermin = 0;
  2943. pointermax = Math.min( text1.length, text2.length );
  2944. pointermid = pointermax;
  2945. pointerend = 0;
  2946. while ( pointermin < pointermid ) {
  2947. if ( text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
  2948. text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
  2949. pointermin = pointermid;
  2950. pointerend = pointermin;
  2951. } else {
  2952. pointermax = pointermid;
  2953. }
  2954. pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
  2955. }
  2956. return pointermid;
  2957. };
  2958. /**
  2959. * Find the differences between two texts. Assumes that the texts do not
  2960. * have any common prefix or suffix.
  2961. * @param {string} text1 Old string to be diffed.
  2962. * @param {string} text2 New string to be diffed.
  2963. * @param {boolean} checklines Speedup flag. If false, then don't run a
  2964. * line-level diff first to identify the changed areas.
  2965. * If true, then run a faster, slightly less optimal diff.
  2966. * @param {number} deadline Time when the diff should be complete by.
  2967. * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
  2968. * @private
  2969. */
  2970. DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
  2971. var diffs, longtext, shorttext, i, hm,
  2972. text1A, text2A, text1B, text2B,
  2973. midCommon, diffsA, diffsB;
  2974. if ( !text1 ) {
  2975. // Just add some text (speedup).
  2976. return [
  2977. [ DIFF_INSERT, text2 ]
  2978. ];
  2979. }
  2980. if ( !text2 ) {
  2981. // Just delete some text (speedup).
  2982. return [
  2983. [ DIFF_DELETE, text1 ]
  2984. ];
  2985. }
  2986. longtext = text1.length > text2.length ? text1 : text2;
  2987. shorttext = text1.length > text2.length ? text2 : text1;
  2988. i = longtext.indexOf( shorttext );
  2989. if ( i !== -1 ) {
  2990. // Shorter text is inside the longer text (speedup).
  2991. diffs = [
  2992. [ DIFF_INSERT, longtext.substring( 0, i ) ],
  2993. [ DIFF_EQUAL, shorttext ],
  2994. [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
  2995. ];
  2996. // Swap insertions for deletions if diff is reversed.
  2997. if ( text1.length > text2.length ) {
  2998. diffs[ 0 ][ 0 ] = diffs[ 2 ][ 0 ] = DIFF_DELETE;
  2999. }
  3000. return diffs;
  3001. }
  3002. if ( shorttext.length === 1 ) {
  3003. // Single character string.
  3004. // After the previous speedup, the character can't be an equality.
  3005. return [
  3006. [ DIFF_DELETE, text1 ],
  3007. [ DIFF_INSERT, text2 ]
  3008. ];
  3009. }
  3010. // Check to see if the problem can be split in two.
  3011. hm = this.diffHalfMatch( text1, text2 );
  3012. if ( hm ) {
  3013. // A half-match was found, sort out the return data.
  3014. text1A = hm[ 0 ];
  3015. text1B = hm[ 1 ];
  3016. text2A = hm[ 2 ];
  3017. text2B = hm[ 3 ];
  3018. midCommon = hm[ 4 ];
  3019. // Send both pairs off for separate processing.
  3020. diffsA = this.DiffMain( text1A, text2A, checklines, deadline );
  3021. diffsB = this.DiffMain( text1B, text2B, checklines, deadline );
  3022. // Merge the results.
  3023. return diffsA.concat( [
  3024. [ DIFF_EQUAL, midCommon ]
  3025. ], diffsB );
  3026. }
  3027. if ( checklines && text1.length > 100 && text2.length > 100 ) {
  3028. return this.diffLineMode( text1, text2, deadline );
  3029. }
  3030. return this.diffBisect( text1, text2, deadline );
  3031. };
  3032. /**
  3033. * Do the two texts share a substring which is at least half the length of the
  3034. * longer text?
  3035. * This speedup can produce non-minimal diffs.
  3036. * @param {string} text1 First string.
  3037. * @param {string} text2 Second string.
  3038. * @return {Array.<string>} Five element Array, containing the prefix of
  3039. * text1, the suffix of text1, the prefix of text2, the suffix of
  3040. * text2 and the common middle. Or null if there was no match.
  3041. * @private
  3042. */
  3043. DiffMatchPatch.prototype.diffHalfMatch = function( text1, text2 ) {
  3044. var longtext, shorttext, dmp,
  3045. text1A, text2B, text2A, text1B, midCommon,
  3046. hm1, hm2, hm;
  3047. longtext = text1.length > text2.length ? text1 : text2;
  3048. shorttext = text1.length > text2.length ? text2 : text1;
  3049. if ( longtext.length < 4 || shorttext.length * 2 < longtext.length ) {
  3050. return null; // Pointless.
  3051. }
  3052. dmp = this; // 'this' becomes 'window' in a closure.
  3053. /**
  3054. * Does a substring of shorttext exist within longtext such that the substring
  3055. * is at least half the length of longtext?
  3056. * Closure, but does not reference any external variables.
  3057. * @param {string} longtext Longer string.
  3058. * @param {string} shorttext Shorter string.
  3059. * @param {number} i Start index of quarter length substring within longtext.
  3060. * @return {Array.<string>} Five element Array, containing the prefix of
  3061. * longtext, the suffix of longtext, the prefix of shorttext, the suffix
  3062. * of shorttext and the common middle. Or null if there was no match.
  3063. * @private
  3064. */
  3065. function diffHalfMatchI( longtext, shorttext, i ) {
  3066. var seed, j, bestCommon, prefixLength, suffixLength,
  3067. bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
  3068. // Start with a 1/4 length substring at position i as a seed.
  3069. seed = longtext.substring( i, i + Math.floor( longtext.length / 4 ) );
  3070. j = -1;
  3071. bestCommon = "";
  3072. while ( ( j = shorttext.indexOf( seed, j + 1 ) ) !== -1 ) {
  3073. prefixLength = dmp.diffCommonPrefix( longtext.substring( i ),
  3074. shorttext.substring( j ) );
  3075. suffixLength = dmp.diffCommonSuffix( longtext.substring( 0, i ),
  3076. shorttext.substring( 0, j ) );
  3077. if ( bestCommon.length < suffixLength + prefixLength ) {
  3078. bestCommon = shorttext.substring( j - suffixLength, j ) +
  3079. shorttext.substring( j, j + prefixLength );
  3080. bestLongtextA = longtext.substring( 0, i - suffixLength );
  3081. bestLongtextB = longtext.substring( i + prefixLength );
  3082. bestShorttextA = shorttext.substring( 0, j - suffixLength );
  3083. bestShorttextB = shorttext.substring( j + prefixLength );
  3084. }
  3085. }
  3086. if ( bestCommon.length * 2 >= longtext.length ) {
  3087. return [ bestLongtextA, bestLongtextB,
  3088. bestShorttextA, bestShorttextB, bestCommon
  3089. ];
  3090. } else {
  3091. return null;
  3092. }
  3093. }
  3094. // First check if the second quarter is the seed for a half-match.
  3095. hm1 = diffHalfMatchI( longtext, shorttext,
  3096. Math.ceil( longtext.length / 4 ) );
  3097. // Check again based on the third quarter.
  3098. hm2 = diffHalfMatchI( longtext, shorttext,
  3099. Math.ceil( longtext.length / 2 ) );
  3100. if ( !hm1 && !hm2 ) {
  3101. return null;
  3102. } else if ( !hm2 ) {
  3103. hm = hm1;
  3104. } else if ( !hm1 ) {
  3105. hm = hm2;
  3106. } else {
  3107. // Both matched. Select the longest.
  3108. hm = hm1[ 4 ].length > hm2[ 4 ].length ? hm1 : hm2;
  3109. }
  3110. // A half-match was found, sort out the return data.
  3111. text1A, text1B, text2A, text2B;
  3112. if ( text1.length > text2.length ) {
  3113. text1A = hm[ 0 ];
  3114. text1B = hm[ 1 ];
  3115. text2A = hm[ 2 ];
  3116. text2B = hm[ 3 ];
  3117. } else {
  3118. text2A = hm[ 0 ];
  3119. text2B = hm[ 1 ];
  3120. text1A = hm[ 2 ];
  3121. text1B = hm[ 3 ];
  3122. }
  3123. midCommon = hm[ 4 ];
  3124. return [ text1A, text1B, text2A, text2B, midCommon ];
  3125. };
  3126. /**
  3127. * Do a quick line-level diff on both strings, then rediff the parts for
  3128. * greater accuracy.
  3129. * This speedup can produce non-minimal diffs.
  3130. * @param {string} text1 Old string to be diffed.
  3131. * @param {string} text2 New string to be diffed.
  3132. * @param {number} deadline Time when the diff should be complete by.
  3133. * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
  3134. * @private
  3135. */
  3136. DiffMatchPatch.prototype.diffLineMode = function( text1, text2, deadline ) {
  3137. var a, diffs, linearray, pointer, countInsert,
  3138. countDelete, textInsert, textDelete, j;
  3139. // Scan the text on a line-by-line basis first.
  3140. a = this.diffLinesToChars( text1, text2 );
  3141. text1 = a.chars1;
  3142. text2 = a.chars2;
  3143. linearray = a.lineArray;
  3144. diffs = this.DiffMain( text1, text2, false, deadline );
  3145. // Convert the diff back to original text.
  3146. this.diffCharsToLines( diffs, linearray );
  3147. // Eliminate freak matches (e.g. blank lines)
  3148. this.diffCleanupSemantic( diffs );
  3149. // Rediff any replacement blocks, this time character-by-character.
  3150. // Add a dummy entry at the end.
  3151. diffs.push( [ DIFF_EQUAL, "" ] );
  3152. pointer = 0;
  3153. countDelete = 0;
  3154. countInsert = 0;
  3155. textDelete = "";
  3156. textInsert = "";
  3157. while ( pointer < diffs.length ) {
  3158. switch ( diffs[ pointer ][ 0 ] ) {
  3159. case DIFF_INSERT:
  3160. countInsert++;
  3161. textInsert += diffs[ pointer ][ 1 ];
  3162. break;
  3163. case DIFF_DELETE:
  3164. countDelete++;
  3165. textDelete += diffs[ pointer ][ 1 ];
  3166. break;
  3167. case DIFF_EQUAL:
  3168. // Upon reaching an equality, check for prior redundancies.
  3169. if ( countDelete >= 1 && countInsert >= 1 ) {
  3170. // Delete the offending records and add the merged ones.
  3171. diffs.splice( pointer - countDelete - countInsert,
  3172. countDelete + countInsert );
  3173. pointer = pointer - countDelete - countInsert;
  3174. a = this.DiffMain( textDelete, textInsert, false, deadline );
  3175. for ( j = a.length - 1; j >= 0; j-- ) {
  3176. diffs.splice( pointer, 0, a[ j ] );
  3177. }
  3178. pointer = pointer + a.length;
  3179. }
  3180. countInsert = 0;
  3181. countDelete = 0;
  3182. textDelete = "";
  3183. textInsert = "";
  3184. break;
  3185. }
  3186. pointer++;
  3187. }
  3188. diffs.pop(); // Remove the dummy entry at the end.
  3189. return diffs;
  3190. };
  3191. /**
  3192. * Find the 'middle snake' of a diff, split the problem in two
  3193. * and return the recursively constructed diff.
  3194. * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
  3195. * @param {string} text1 Old string to be diffed.
  3196. * @param {string} text2 New string to be diffed.
  3197. * @param {number} deadline Time at which to bail if not yet complete.
  3198. * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
  3199. * @private
  3200. */
  3201. DiffMatchPatch.prototype.diffBisect = function( text1, text2, deadline ) {
  3202. var text1Length, text2Length, maxD, vOffset, vLength,
  3203. v1, v2, x, delta, front, k1start, k1end, k2start,
  3204. k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
  3205. // Cache the text lengths to prevent multiple calls.
  3206. text1Length = text1.length;
  3207. text2Length = text2.length;
  3208. maxD = Math.ceil( ( text1Length + text2Length ) / 2 );
  3209. vOffset = maxD;
  3210. vLength = 2 * maxD;
  3211. v1 = new Array( vLength );
  3212. v2 = new Array( vLength );
  3213. // Setting all elements to -1 is faster in Chrome & Firefox than mixing
  3214. // integers and undefined.
  3215. for ( x = 0; x < vLength; x++ ) {
  3216. v1[ x ] = -1;
  3217. v2[ x ] = -1;
  3218. }
  3219. v1[ vOffset + 1 ] = 0;
  3220. v2[ vOffset + 1 ] = 0;
  3221. delta = text1Length - text2Length;
  3222. // If the total number of characters is odd, then the front path will collide
  3223. // with the reverse path.
  3224. front = ( delta % 2 !== 0 );
  3225. // Offsets for start and end of k loop.
  3226. // Prevents mapping of space beyond the grid.
  3227. k1start = 0;
  3228. k1end = 0;
  3229. k2start = 0;
  3230. k2end = 0;
  3231. for ( d = 0; d < maxD; d++ ) {
  3232. // Bail out if deadline is reached.
  3233. if ( ( new Date() ).getTime() > deadline ) {
  3234. break;
  3235. }
  3236. // Walk the front path one step.
  3237. for ( k1 = -d + k1start; k1 <= d - k1end; k1 += 2 ) {
  3238. k1Offset = vOffset + k1;
  3239. if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
  3240. x1 = v1[ k1Offset + 1 ];
  3241. } else {
  3242. x1 = v1[ k1Offset - 1 ] + 1;
  3243. }
  3244. y1 = x1 - k1;
  3245. while ( x1 < text1Length && y1 < text2Length &&
  3246. text1.charAt( x1 ) === text2.charAt( y1 ) ) {
  3247. x1++;
  3248. y1++;
  3249. }
  3250. v1[ k1Offset ] = x1;
  3251. if ( x1 > text1Length ) {
  3252. // Ran off the right of the graph.
  3253. k1end += 2;
  3254. } else if ( y1 > text2Length ) {
  3255. // Ran off the bottom of the graph.
  3256. k1start += 2;
  3257. } else if ( front ) {
  3258. k2Offset = vOffset + delta - k1;
  3259. if ( k2Offset >= 0 && k2Offset < vLength && v2[ k2Offset ] !== -1 ) {
  3260. // Mirror x2 onto top-left coordinate system.
  3261. x2 = text1Length - v2[ k2Offset ];
  3262. if ( x1 >= x2 ) {
  3263. // Overlap detected.
  3264. return this.diffBisectSplit( text1, text2, x1, y1, deadline );
  3265. }
  3266. }
  3267. }
  3268. }
  3269. // Walk the reverse path one step.
  3270. for ( k2 = -d + k2start; k2 <= d - k2end; k2 += 2 ) {
  3271. k2Offset = vOffset + k2;
  3272. if ( k2 === -d || ( k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
  3273. x2 = v2[ k2Offset + 1 ];
  3274. } else {
  3275. x2 = v2[ k2Offset - 1 ] + 1;
  3276. }
  3277. y2 = x2 - k2;
  3278. while ( x2 < text1Length && y2 < text2Length &&
  3279. text1.charAt( text1Length - x2 - 1 ) ===
  3280. text2.charAt( text2Length - y2 - 1 ) ) {
  3281. x2++;
  3282. y2++;
  3283. }
  3284. v2[ k2Offset ] = x2;
  3285. if ( x2 > text1Length ) {
  3286. // Ran off the left of the graph.
  3287. k2end += 2;
  3288. } else if ( y2 > text2Length ) {
  3289. // Ran off the top of the graph.
  3290. k2start += 2;
  3291. } else if ( !front ) {
  3292. k1Offset = vOffset + delta - k2;
  3293. if ( k1Offset >= 0 && k1Offset < vLength && v1[ k1Offset ] !== -1 ) {
  3294. x1 = v1[ k1Offset ];
  3295. y1 = vOffset + x1 - k1Offset;
  3296. // Mirror x2 onto top-left coordinate system.
  3297. x2 = text1Length - x2;
  3298. if ( x1 >= x2 ) {
  3299. // Overlap detected.
  3300. return this.diffBisectSplit( text1, text2, x1, y1, deadline );
  3301. }
  3302. }
  3303. }
  3304. }
  3305. }
  3306. // Diff took too long and hit the deadline or
  3307. // number of diffs equals number of characters, no commonality at all.
  3308. return [
  3309. [ DIFF_DELETE, text1 ],
  3310. [ DIFF_INSERT, text2 ]
  3311. ];
  3312. };
  3313. /**
  3314. * Given the location of the 'middle snake', split the diff in two parts
  3315. * and recurse.
  3316. * @param {string} text1 Old string to be diffed.
  3317. * @param {string} text2 New string to be diffed.
  3318. * @param {number} x Index of split point in text1.
  3319. * @param {number} y Index of split point in text2.
  3320. * @param {number} deadline Time at which to bail if not yet complete.
  3321. * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
  3322. * @private
  3323. */
  3324. DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
  3325. var text1a, text1b, text2a, text2b, diffs, diffsb;
  3326. text1a = text1.substring( 0, x );
  3327. text2a = text2.substring( 0, y );
  3328. text1b = text1.substring( x );
  3329. text2b = text2.substring( y );
  3330. // Compute both diffs serially.
  3331. diffs = this.DiffMain( text1a, text2a, false, deadline );
  3332. diffsb = this.DiffMain( text1b, text2b, false, deadline );
  3333. return diffs.concat( diffsb );
  3334. };
  3335. /**
  3336. * Reduce the number of edits by eliminating semantically trivial equalities.
  3337. * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
  3338. */
  3339. DiffMatchPatch.prototype.diffCleanupSemantic = function( diffs ) {
  3340. var changes, equalities, equalitiesLength, lastequality,
  3341. pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
  3342. lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
  3343. changes = false;
  3344. equalities = []; // Stack of indices where equalities are found.
  3345. equalitiesLength = 0; // Keeping our own length var is faster in JS.
  3346. /** @type {?string} */
  3347. lastequality = null;
  3348. // Always equal to diffs[equalities[equalitiesLength - 1]][1]
  3349. pointer = 0; // Index of current position.
  3350. // Number of characters that changed prior to the equality.
  3351. lengthInsertions1 = 0;
  3352. lengthDeletions1 = 0;
  3353. // Number of characters that changed after the equality.
  3354. lengthInsertions2 = 0;
  3355. lengthDeletions2 = 0;
  3356. while ( pointer < diffs.length ) {
  3357. if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
  3358. equalities[ equalitiesLength++ ] = pointer;
  3359. lengthInsertions1 = lengthInsertions2;
  3360. lengthDeletions1 = lengthDeletions2;
  3361. lengthInsertions2 = 0;
  3362. lengthDeletions2 = 0;
  3363. lastequality = diffs[ pointer ][ 1 ];
  3364. } else { // An insertion or deletion.
  3365. if ( diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
  3366. lengthInsertions2 += diffs[ pointer ][ 1 ].length;
  3367. } else {
  3368. lengthDeletions2 += diffs[ pointer ][ 1 ].length;
  3369. }
  3370. // Eliminate an equality that is smaller or equal to the edits on both
  3371. // sides of it.
  3372. if ( lastequality && ( lastequality.length <=
  3373. Math.max( lengthInsertions1, lengthDeletions1 ) ) &&
  3374. ( lastequality.length <= Math.max( lengthInsertions2,
  3375. lengthDeletions2 ) ) ) {
  3376. // Duplicate record.
  3377. diffs.splice(
  3378. equalities[ equalitiesLength - 1 ],
  3379. 0,
  3380. [ DIFF_DELETE, lastequality ]
  3381. );
  3382. // Change second copy to insert.
  3383. diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
  3384. // Throw away the equality we just deleted.
  3385. equalitiesLength--;
  3386. // Throw away the previous equality (it needs to be reevaluated).
  3387. equalitiesLength--;
  3388. pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
  3389. // Reset the counters.
  3390. lengthInsertions1 = 0;
  3391. lengthDeletions1 = 0;
  3392. lengthInsertions2 = 0;
  3393. lengthDeletions2 = 0;
  3394. lastequality = null;
  3395. changes = true;
  3396. }
  3397. }
  3398. pointer++;
  3399. }
  3400. // Normalize the diff.
  3401. if ( changes ) {
  3402. this.diffCleanupMerge( diffs );
  3403. }
  3404. // Find any overlaps between deletions and insertions.
  3405. // e.g: <del>abcxxx</del><ins>xxxdef</ins>
  3406. // -> <del>abc</del>xxx<ins>def</ins>
  3407. // e.g: <del>xxxabc</del><ins>defxxx</ins>
  3408. // -> <ins>def</ins>xxx<del>abc</del>
  3409. // Only extract an overlap if it is as big as the edit ahead or behind it.
  3410. pointer = 1;
  3411. while ( pointer < diffs.length ) {
  3412. if ( diffs[ pointer - 1 ][ 0 ] === DIFF_DELETE &&
  3413. diffs[ pointer ][ 0 ] === DIFF_INSERT ) {
  3414. deletion = diffs[ pointer - 1 ][ 1 ];
  3415. insertion = diffs[ pointer ][ 1 ];
  3416. overlapLength1 = this.diffCommonOverlap( deletion, insertion );
  3417. overlapLength2 = this.diffCommonOverlap( insertion, deletion );
  3418. if ( overlapLength1 >= overlapLength2 ) {
  3419. if ( overlapLength1 >= deletion.length / 2 ||
  3420. overlapLength1 >= insertion.length / 2 ) {
  3421. // Overlap found. Insert an equality and trim the surrounding edits.
  3422. diffs.splice(
  3423. pointer,
  3424. 0,
  3425. [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ]
  3426. );
  3427. diffs[ pointer - 1 ][ 1 ] =
  3428. deletion.substring( 0, deletion.length - overlapLength1 );
  3429. diffs[ pointer + 1 ][ 1 ] = insertion.substring( overlapLength1 );
  3430. pointer++;
  3431. }
  3432. } else {
  3433. if ( overlapLength2 >= deletion.length / 2 ||
  3434. overlapLength2 >= insertion.length / 2 ) {
  3435. // Reverse overlap found.
  3436. // Insert an equality and swap and trim the surrounding edits.
  3437. diffs.splice(
  3438. pointer,
  3439. 0,
  3440. [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ]
  3441. );
  3442. diffs[ pointer - 1 ][ 0 ] = DIFF_INSERT;
  3443. diffs[ pointer - 1 ][ 1 ] =
  3444. insertion.substring( 0, insertion.length - overlapLength2 );
  3445. diffs[ pointer + 1 ][ 0 ] = DIFF_DELETE;
  3446. diffs[ pointer + 1 ][ 1 ] =
  3447. deletion.substring( overlapLength2 );
  3448. pointer++;
  3449. }
  3450. }
  3451. pointer++;
  3452. }
  3453. pointer++;
  3454. }
  3455. };
  3456. /**
  3457. * Determine if the suffix of one string is the prefix of another.
  3458. * @param {string} text1 First string.
  3459. * @param {string} text2 Second string.
  3460. * @return {number} The number of characters common to the end of the first
  3461. * string and the start of the second string.
  3462. * @private
  3463. */
  3464. DiffMatchPatch.prototype.diffCommonOverlap = function( text1, text2 ) {
  3465. var text1Length, text2Length, textLength,
  3466. best, length, pattern, found;
  3467. // Cache the text lengths to prevent multiple calls.
  3468. text1Length = text1.length;
  3469. text2Length = text2.length;
  3470. // Eliminate the null case.
  3471. if ( text1Length === 0 || text2Length === 0 ) {
  3472. return 0;
  3473. }
  3474. // Truncate the longer string.
  3475. if ( text1Length > text2Length ) {
  3476. text1 = text1.substring( text1Length - text2Length );
  3477. } else if ( text1Length < text2Length ) {
  3478. text2 = text2.substring( 0, text1Length );
  3479. }
  3480. textLength = Math.min( text1Length, text2Length );
  3481. // Quick check for the worst case.
  3482. if ( text1 === text2 ) {
  3483. return textLength;
  3484. }
  3485. // Start by looking for a single character match
  3486. // and increase length until no match is found.
  3487. // Performance analysis: https://neil.fraser.name/news/2010/11/04/
  3488. best = 0;
  3489. length = 1;
  3490. while ( true ) {
  3491. pattern = text1.substring( textLength - length );
  3492. found = text2.indexOf( pattern );
  3493. if ( found === -1 ) {
  3494. return best;
  3495. }
  3496. length += found;
  3497. if ( found === 0 || text1.substring( textLength - length ) ===
  3498. text2.substring( 0, length ) ) {
  3499. best = length;
  3500. length++;
  3501. }
  3502. }
  3503. };
  3504. /**
  3505. * Split two texts into an array of strings. Reduce the texts to a string of
  3506. * hashes where each Unicode character represents one line.
  3507. * @param {string} text1 First string.
  3508. * @param {string} text2 Second string.
  3509. * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
  3510. * An object containing the encoded text1, the encoded text2 and
  3511. * the array of unique strings.
  3512. * The zeroth element of the array of unique strings is intentionally blank.
  3513. * @private
  3514. */
  3515. DiffMatchPatch.prototype.diffLinesToChars = function( text1, text2 ) {
  3516. var lineArray, lineHash, chars1, chars2;
  3517. lineArray = []; // E.g. lineArray[4] === 'Hello\n'
  3518. lineHash = {}; // E.g. lineHash['Hello\n'] === 4
  3519. // '\x00' is a valid character, but various debuggers don't like it.
  3520. // So we'll insert a junk entry to avoid generating a null character.
  3521. lineArray[ 0 ] = "";
  3522. /**
  3523. * Split a text into an array of strings. Reduce the texts to a string of
  3524. * hashes where each Unicode character represents one line.
  3525. * Modifies linearray and linehash through being a closure.
  3526. * @param {string} text String to encode.
  3527. * @return {string} Encoded string.
  3528. * @private
  3529. */
  3530. function diffLinesToCharsMunge( text ) {
  3531. var chars, lineStart, lineEnd, lineArrayLength, line;
  3532. chars = "";
  3533. // Walk the text, pulling out a substring for each line.
  3534. // text.split('\n') would would temporarily double our memory footprint.
  3535. // Modifying text would create many large strings to garbage collect.
  3536. lineStart = 0;
  3537. lineEnd = -1;
  3538. // Keeping our own length variable is faster than looking it up.
  3539. lineArrayLength = lineArray.length;
  3540. while ( lineEnd < text.length - 1 ) {
  3541. lineEnd = text.indexOf( "\n", lineStart );
  3542. if ( lineEnd === -1 ) {
  3543. lineEnd = text.length - 1;
  3544. }
  3545. line = text.substring( lineStart, lineEnd + 1 );
  3546. lineStart = lineEnd + 1;
  3547. if ( lineHash.hasOwnProperty ? lineHash.hasOwnProperty( line ) :
  3548. ( lineHash[ line ] !== undefined ) ) {
  3549. chars += String.fromCharCode( lineHash[ line ] );
  3550. } else {
  3551. chars += String.fromCharCode( lineArrayLength );
  3552. lineHash[ line ] = lineArrayLength;
  3553. lineArray[ lineArrayLength++ ] = line;
  3554. }
  3555. }
  3556. return chars;
  3557. }
  3558. chars1 = diffLinesToCharsMunge( text1 );
  3559. chars2 = diffLinesToCharsMunge( text2 );
  3560. return {
  3561. chars1: chars1,
  3562. chars2: chars2,
  3563. lineArray: lineArray
  3564. };
  3565. };
  3566. /**
  3567. * Rehydrate the text in a diff from a string of line hashes to real lines of
  3568. * text.
  3569. * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
  3570. * @param {!Array.<string>} lineArray Array of unique strings.
  3571. * @private
  3572. */
  3573. DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
  3574. var x, chars, text, y;
  3575. for ( x = 0; x < diffs.length; x++ ) {
  3576. chars = diffs[ x ][ 1 ];
  3577. text = [];
  3578. for ( y = 0; y < chars.length; y++ ) {
  3579. text[ y ] = lineArray[ chars.charCodeAt( y ) ];
  3580. }
  3581. diffs[ x ][ 1 ] = text.join( "" );
  3582. }
  3583. };
  3584. /**
  3585. * Reorder and merge like edit sections. Merge equalities.
  3586. * Any edit section can move as long as it doesn't cross an equality.
  3587. * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
  3588. */
  3589. DiffMatchPatch.prototype.diffCleanupMerge = function( diffs ) {
  3590. var pointer, countDelete, countInsert, textInsert, textDelete,
  3591. commonlength, changes, diffPointer, position;
  3592. diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
  3593. pointer = 0;
  3594. countDelete = 0;
  3595. countInsert = 0;
  3596. textDelete = "";
  3597. textInsert = "";
  3598. commonlength;
  3599. while ( pointer < diffs.length ) {
  3600. switch ( diffs[ pointer ][ 0 ] ) {
  3601. case DIFF_INSERT:
  3602. countInsert++;
  3603. textInsert += diffs[ pointer ][ 1 ];
  3604. pointer++;
  3605. break;
  3606. case DIFF_DELETE:
  3607. countDelete++;
  3608. textDelete += diffs[ pointer ][ 1 ];
  3609. pointer++;
  3610. break;
  3611. case DIFF_EQUAL:
  3612. // Upon reaching an equality, check for prior redundancies.
  3613. if ( countDelete + countInsert > 1 ) {
  3614. if ( countDelete !== 0 && countInsert !== 0 ) {
  3615. // Factor out any common prefixes.
  3616. commonlength = this.diffCommonPrefix( textInsert, textDelete );
  3617. if ( commonlength !== 0 ) {
  3618. if ( ( pointer - countDelete - countInsert ) > 0 &&
  3619. diffs[ pointer - countDelete - countInsert - 1 ][ 0 ] ===
  3620. DIFF_EQUAL ) {
  3621. diffs[ pointer - countDelete - countInsert - 1 ][ 1 ] +=
  3622. textInsert.substring( 0, commonlength );
  3623. } else {
  3624. diffs.splice( 0, 0, [ DIFF_EQUAL,
  3625. textInsert.substring( 0, commonlength )
  3626. ] );
  3627. pointer++;
  3628. }
  3629. textInsert = textInsert.substring( commonlength );
  3630. textDelete = textDelete.substring( commonlength );
  3631. }
  3632. // Factor out any common suffixies.
  3633. commonlength = this.diffCommonSuffix( textInsert, textDelete );
  3634. if ( commonlength !== 0 ) {
  3635. diffs[ pointer ][ 1 ] = textInsert.substring( textInsert.length -
  3636. commonlength ) + diffs[ pointer ][ 1 ];
  3637. textInsert = textInsert.substring( 0, textInsert.length -
  3638. commonlength );
  3639. textDelete = textDelete.substring( 0, textDelete.length -
  3640. commonlength );
  3641. }
  3642. }
  3643. // Delete the offending records and add the merged ones.
  3644. if ( countDelete === 0 ) {
  3645. diffs.splice( pointer - countInsert,
  3646. countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
  3647. } else if ( countInsert === 0 ) {
  3648. diffs.splice( pointer - countDelete,
  3649. countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
  3650. } else {
  3651. diffs.splice(
  3652. pointer - countDelete - countInsert,
  3653. countDelete + countInsert,
  3654. [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ]
  3655. );
  3656. }
  3657. pointer = pointer - countDelete - countInsert +
  3658. ( countDelete ? 1 : 0 ) + ( countInsert ? 1 : 0 ) + 1;
  3659. } else if ( pointer !== 0 && diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL ) {
  3660. // Merge this equality with the previous one.
  3661. diffs[ pointer - 1 ][ 1 ] += diffs[ pointer ][ 1 ];
  3662. diffs.splice( pointer, 1 );
  3663. } else {
  3664. pointer++;
  3665. }
  3666. countInsert = 0;
  3667. countDelete = 0;
  3668. textDelete = "";
  3669. textInsert = "";
  3670. break;
  3671. }
  3672. }
  3673. if ( diffs[ diffs.length - 1 ][ 1 ] === "" ) {
  3674. diffs.pop(); // Remove the dummy entry at the end.
  3675. }
  3676. // Second pass: look for single edits surrounded on both sides by equalities
  3677. // which can be shifted sideways to eliminate an equality.
  3678. // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
  3679. changes = false;
  3680. pointer = 1;
  3681. // Intentionally ignore the first and last element (don't need checking).
  3682. while ( pointer < diffs.length - 1 ) {
  3683. if ( diffs[ pointer - 1 ][ 0 ] === DIFF_EQUAL &&
  3684. diffs[ pointer + 1 ][ 0 ] === DIFF_EQUAL ) {
  3685. diffPointer = diffs[ pointer ][ 1 ];
  3686. position = diffPointer.substring(
  3687. diffPointer.length - diffs[ pointer - 1 ][ 1 ].length
  3688. );
  3689. // This is a single edit surrounded by equalities.
  3690. if ( position === diffs[ pointer - 1 ][ 1 ] ) {
  3691. // Shift the edit over the previous equality.
  3692. diffs[ pointer ][ 1 ] = diffs[ pointer - 1 ][ 1 ] +
  3693. diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer ][ 1 ].length -
  3694. diffs[ pointer - 1 ][ 1 ].length );
  3695. diffs[ pointer + 1 ][ 1 ] =
  3696. diffs[ pointer - 1 ][ 1 ] + diffs[ pointer + 1 ][ 1 ];
  3697. diffs.splice( pointer - 1, 1 );
  3698. changes = true;
  3699. } else if ( diffPointer.substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
  3700. diffs[ pointer + 1 ][ 1 ] ) {
  3701. // Shift the edit over the next equality.
  3702. diffs[ pointer - 1 ][ 1 ] += diffs[ pointer + 1 ][ 1 ];
  3703. diffs[ pointer ][ 1 ] =
  3704. diffs[ pointer ][ 1 ].substring( diffs[ pointer + 1 ][ 1 ].length ) +
  3705. diffs[ pointer + 1 ][ 1 ];
  3706. diffs.splice( pointer + 1, 1 );
  3707. changes = true;
  3708. }
  3709. }
  3710. pointer++;
  3711. }
  3712. // If shifts were made, the diff needs reordering and another shift sweep.
  3713. if ( changes ) {
  3714. this.diffCleanupMerge( diffs );
  3715. }
  3716. };
  3717. return function( o, n ) {
  3718. var diff, output, text;
  3719. diff = new DiffMatchPatch();
  3720. output = diff.DiffMain( o, n );
  3721. diff.diffCleanupEfficiency( output );
  3722. text = diff.diffPrettyHtml( output );
  3723. return text;
  3724. };
  3725. }() );
  3726. }() );