PageRenderTime 65ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/projects/htmlunit-2.8/src/test/resources/libraries/mootools/1.2.1/Specs/Assets/Scripts/JSSpec.js

https://gitlab.com/essere.lab.public/qualitas.class-corpus
JavaScript | 1552 lines | 1187 code | 264 blank | 101 comment | 294 complexity | e060cf6b1cb88408f5099b0fb746cbb9 MD5 | raw file
  1. /**
  2. * JSSpec
  3. *
  4. * Copyright 2007 Alan Kang
  5. * - mailto:jania902@gmail.com
  6. * - http://jania.pe.kr
  7. *
  8. * http://jania.pe.kr/aw/moin.cgi/JSSpec
  9. *
  10. * Dependencies:
  11. * - diff_match_patch.js ( http://code.google.com/p/google-diff-match-patch )
  12. *
  13. * This library is free software; you can redistribute it and/or
  14. * modify it under the terms of the GNU Lesser General Public
  15. * License as published by the Free Software Foundation; either
  16. * version 2.1 of the License, or (at your option) any later version.
  17. *
  18. * This library is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  21. * Lesser General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Lesser General Public
  24. * License along with this library; if not, write to the Free Software
  25. * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  26. */
  27. /**
  28. * Namespace
  29. */
  30. var JSSpec = {
  31. specs: [],
  32. EMPTY_FUNCTION: function() {},
  33. Browser: {
  34. // By Rendering Engines
  35. Trident: navigator.appName === "Microsoft Internet Explorer",
  36. Webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
  37. Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
  38. KHTML: navigator.userAgent.indexOf('KHTML') !== -1,
  39. Presto: navigator.appName === "Opera",
  40. // By Platforms
  41. Mac: navigator.userAgent.indexOf("Macintosh") !== -1,
  42. Ubuntu: navigator.userAgent.indexOf('Ubuntu') !== -1,
  43. Win: navigator.userAgent.indexOf('Windows') !== -1,
  44. // By Browsers
  45. IE: navigator.appName === "Microsoft Internet Explorer",
  46. IE6: navigator.userAgent.indexOf('MSIE 6') !== -1,
  47. IE7: navigator.userAgent.indexOf('MSIE 7') !== -1,
  48. IE8: navigator.userAgent.indexOf('MSIE 8') !== -1,
  49. FF: navigator.userAgent.indexOf('Firefox') !== -1,
  50. FF2: navigator.userAgent.indexOf('Firefox/2') !== -1,
  51. FF3: navigator.userAgent.indexOf('Firefox/3') !== -1,
  52. Safari: navigator.userAgent.indexOf('Safari') !== -1
  53. }
  54. };
  55. /**
  56. * Executor
  57. */
  58. JSSpec.Executor = function(target, onSuccess, onException) {
  59. this.target = target;
  60. this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
  61. this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
  62. if(JSSpec.Browser.Trident) {
  63. // Exception handler for Trident. It helps to collect exact line number where exception occured.
  64. window.onerror = function(message, fileName, lineNumber) {
  65. var self = window._curExecutor;
  66. var ex = {message:message, fileName:fileName, lineNumber:lineNumber};
  67. if(JSSpec._secondPass) {
  68. ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
  69. delete JSSpec._secondPass;
  70. delete JSSpec._assertionFailure;
  71. ex.type = "failure";
  72. self.onException(self, ex);
  73. } else if(JSSpec._assertionFailure) {
  74. JSSpec._secondPass = true;
  75. self.run();
  76. } else {
  77. self.onException(self, ex);
  78. }
  79. return true;
  80. };
  81. }
  82. };
  83. JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException) {
  84. var merged = {
  85. message:assertionFailure.message,
  86. fileName:normalException.fileName,
  87. lineNumber:normalException.lineNumber
  88. };
  89. return merged;
  90. };
  91. JSSpec.Executor.prototype.run = function() {
  92. var self = this;
  93. var target = this.target;
  94. var onSuccess = this.onSuccess;
  95. var onException = this.onException;
  96. window.setTimeout(
  97. function() {
  98. var result;
  99. if(JSSpec.Browser.Trident) {
  100. try{
  101. window._curExecutor = self;
  102. result = self.target();
  103. self.onSuccess(self, result);
  104. }catch(ex){
  105. self.onException(self, ex);
  106. };
  107. } else {
  108. try {
  109. result = self.target();
  110. self.onSuccess(self, result);
  111. } catch(ex) {
  112. if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line};
  113. if(JSSpec._secondPass) {
  114. ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
  115. delete JSSpec._secondPass;
  116. delete JSSpec._assertionFailure;
  117. ex.type = "failure";
  118. self.onException(self, ex);
  119. } else if(JSSpec._assertionFailure) {
  120. JSSpec._secondPass = true;
  121. self.run();
  122. } else {
  123. self.onException(self, ex);
  124. }
  125. }
  126. }
  127. },
  128. 0
  129. );
  130. };
  131. /**
  132. * CompositeExecutor composites one or more executors and execute them sequencially.
  133. */
  134. JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException) {
  135. this.queue = [];
  136. this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
  137. this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
  138. this.continueOnException = !!continueOnException;
  139. };
  140. JSSpec.CompositeExecutor.prototype.addFunction = function(func) {
  141. this.addExecutor(new JSSpec.Executor(func));
  142. };
  143. JSSpec.CompositeExecutor.prototype.addExecutor = function(executor) {
  144. var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1];
  145. if(last) {
  146. last.next = executor;
  147. }
  148. executor.parent = this;
  149. executor.onSuccessBackup = executor.onSuccess;
  150. executor.onSuccess = function(result) {
  151. this.onSuccessBackup(result);
  152. if(this.next) {
  153. this.next.run();
  154. } else {
  155. this.parent.onSuccess();
  156. }
  157. };
  158. executor.onExceptionBackup = executor.onException;
  159. executor.onException = function(executor, ex) {
  160. this.onExceptionBackup(executor, ex);
  161. if(this.parent.continueOnException) {
  162. if(this.next) {
  163. this.next.run();
  164. } else {
  165. this.parent.onSuccess();
  166. }
  167. } else {
  168. this.parent.onException(executor, ex);
  169. }
  170. };
  171. this.queue.push(executor);
  172. };
  173. JSSpec.CompositeExecutor.prototype.run = function() {
  174. if(this.queue.length > 0) {
  175. this.queue[0].run();
  176. }
  177. };
  178. /**
  179. * Spec is a set of Examples in a specific context
  180. */
  181. JSSpec.Spec = function(context, entries) {
  182. this.id = JSSpec.Spec.id++;
  183. this.context = context;
  184. this.url = location.href;
  185. this.filterEntriesByEmbeddedExpressions(entries);
  186. this.extractOutSpecialEntries(entries);
  187. this.examples = this.makeExamplesFromEntries(entries);
  188. this.examplesMap = this.makeMapFromExamples(this.examples);
  189. };
  190. JSSpec.Spec.id = 0;
  191. JSSpec.Spec.prototype.getExamples = function() {
  192. return this.examples;
  193. };
  194. JSSpec.Spec.prototype.hasException = function() {
  195. return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
  196. };
  197. JSSpec.Spec.prototype.getTotalFailures = function() {
  198. var examples = this.examples;
  199. var failures = 0;
  200. for(var i = 0; i < examples.length; i++) {
  201. if(examples[i].isFailure()) failures++;
  202. }
  203. return failures;
  204. };
  205. JSSpec.Spec.prototype.getTotalErrors = function() {
  206. var examples = this.examples;
  207. var errors = 0;
  208. for(var i = 0; i < examples.length; i++) {
  209. if(examples[i].isError()) errors++;
  210. }
  211. return errors;
  212. };
  213. JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries) {
  214. var isTrue;
  215. for(name in entries) if(entries.hasOwnProperty(name)) {
  216. var m = name.match(/\[\[(.+)\]\]/);
  217. if(m && m[1]) {
  218. eval("isTrue = (" + m[1] + ")");
  219. if(!isTrue) delete entries[name];
  220. }
  221. }
  222. };
  223. JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) {
  224. this.beforeEach = JSSpec.EMPTY_FUNCTION;
  225. this.beforeAll = JSSpec.EMPTY_FUNCTION;
  226. this.afterEach = JSSpec.EMPTY_FUNCTION;
  227. this.afterAll = JSSpec.EMPTY_FUNCTION;
  228. for(name in entries) if(entries.hasOwnProperty(name)) {
  229. if(name == 'before' || name == 'before each' || name == 'before_each') {
  230. this.beforeEach = entries[name];
  231. } else if(name == 'before all' || name == 'before_all') {
  232. this.beforeAll = entries[name];
  233. } else if(name == 'after' || name == 'after each' || name == 'after_each') {
  234. this.afterEach = entries[name];
  235. } else if(name == 'after all' || name == 'after_all') {
  236. this.afterAll = entries[name];
  237. }
  238. }
  239. delete entries['before'];
  240. delete entries['before each'];
  241. delete entries['before_each'];
  242. delete entries['before all'];
  243. delete entries['before_all'];
  244. delete entries['after'];
  245. delete entries['after each'];
  246. delete entries['after_each'];
  247. delete entries['after all'];
  248. delete entries['after_all'];
  249. };
  250. JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) {
  251. var examples = [];
  252. for(name in entries) if(entries.hasOwnProperty(name)) {
  253. examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
  254. }
  255. return examples;
  256. };
  257. JSSpec.Spec.prototype.makeMapFromExamples = function(examples) {
  258. var map = {};
  259. for(var i = 0; i < examples.length; i++) {
  260. var example = examples[i];
  261. map[example.id] = examples[i];
  262. }
  263. return map;
  264. };
  265. JSSpec.Spec.prototype.getExampleById = function(id) {
  266. return this.examplesMap[id];
  267. };
  268. JSSpec.Spec.prototype.getExecutor = function() {
  269. var self = this;
  270. var onException = function(executor, ex) {
  271. self.exception = ex;
  272. };
  273. var composite = new JSSpec.CompositeExecutor();
  274. composite.addFunction(function() {JSSpec.log.onSpecStart(self);});
  275. composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex) {
  276. self.exception = ex;
  277. JSSpec.log.onSpecEnd(self);
  278. }));
  279. var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true);
  280. for(var i = 0; i < this.examples.length; i++) {
  281. exampleAndAfter.addExecutor(this.examples[i].getExecutor());
  282. }
  283. exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException));
  284. exampleAndAfter.addFunction(function() {JSSpec.log.onSpecEnd(self);});
  285. composite.addExecutor(exampleAndAfter);
  286. return composite;
  287. };
  288. /**
  289. * Example
  290. */
  291. JSSpec.Example = function(name, target, before, after) {
  292. this.id = JSSpec.Example.id++;
  293. this.name = name;
  294. this.target = target;
  295. this.before = before;
  296. this.after = after;
  297. };
  298. JSSpec.Example.id = 0;
  299. JSSpec.Example.prototype.isFailure = function() {
  300. return this.exception && this.exception.type == "failure";
  301. };
  302. JSSpec.Example.prototype.isError = function() {
  303. return this.exception && !this.exception.type;
  304. };
  305. JSSpec.Example.prototype.getExecutor = function() {
  306. var self = this;
  307. var onException = function(executor, ex) {
  308. self.exception = ex;
  309. };
  310. var composite = new JSSpec.CompositeExecutor();
  311. composite.addFunction(function() {JSSpec.log.onExampleStart(self);});
  312. composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex) {
  313. self.exception = ex;
  314. JSSpec.log.onExampleEnd(self);
  315. }));
  316. var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true);
  317. targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException));
  318. targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException));
  319. targetAndAfter.addFunction(function() {JSSpec.log.onExampleEnd(self);});
  320. composite.addExecutor(targetAndAfter);
  321. return composite;
  322. };
  323. /**
  324. * Runner
  325. */
  326. JSSpec.Runner = function(specs, logger) {
  327. JSSpec.log = logger;
  328. this.totalExamples = 0;
  329. this.specs = [];
  330. this.specsMap = {};
  331. this.addAllSpecs(specs);
  332. };
  333. JSSpec.Runner.prototype.addAllSpecs = function(specs) {
  334. for(var i = 0; i < specs.length; i++) {
  335. this.addSpec(specs[i]);
  336. }
  337. };
  338. JSSpec.Runner.prototype.addSpec = function(spec) {
  339. this.specs.push(spec);
  340. this.specsMap[spec.id] = spec;
  341. this.totalExamples += spec.getExamples().length;
  342. };
  343. JSSpec.Runner.prototype.getSpecById = function(id) {
  344. return this.specsMap[id];
  345. };
  346. JSSpec.Runner.prototype.getSpecByContext = function(context) {
  347. for(var i = 0; i < this.specs.length; i++) {
  348. if(this.specs[i].context == context) return this.specs[i];
  349. }
  350. return null;
  351. };
  352. JSSpec.Runner.prototype.getSpecs = function() {
  353. return this.specs;
  354. };
  355. JSSpec.Runner.prototype.hasException = function() {
  356. return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
  357. };
  358. JSSpec.Runner.prototype.getTotalFailures = function() {
  359. var specs = this.specs;
  360. var failures = 0;
  361. for(var i = 0; i < specs.length; i++) {
  362. failures += specs[i].getTotalFailures();
  363. }
  364. return failures;
  365. };
  366. JSSpec.Runner.prototype.getTotalErrors = function() {
  367. var specs = this.specs;
  368. var errors = 0;
  369. for(var i = 0; i < specs.length; i++) {
  370. errors += specs[i].getTotalErrors();
  371. }
  372. return errors;
  373. };
  374. JSSpec.Runner.prototype.run = function() {
  375. JSSpec.log.onRunnerStart();
  376. var executor = new JSSpec.CompositeExecutor(function() {JSSpec.log.onRunnerEnd()},null,true);
  377. for(var i = 0; i < this.specs.length; i++) {
  378. executor.addExecutor(this.specs[i].getExecutor());
  379. }
  380. executor.run();
  381. };
  382. JSSpec.Runner.prototype.rerun = function(context) {
  383. JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log);
  384. JSSpec.runner.run();
  385. };
  386. /**
  387. * Logger
  388. */
  389. JSSpec.Logger = function() {
  390. this.finishedExamples = 0;
  391. this.startedAt = null;
  392. };
  393. JSSpec.Logger.prototype.onRunnerStart = function() {
  394. this._title = document.title;
  395. this.startedAt = new Date();
  396. var container = document.getElementById('jsspec_container');
  397. if(container) {
  398. container.innerHTML = "";
  399. } else {
  400. container = document.createElement("DIV");
  401. container.id = "jsspec_container";
  402. document.body.appendChild(container);
  403. }
  404. var title = document.createElement("DIV");
  405. title.id = "title";
  406. title.innerHTML = [
  407. '<h1>JSSpec</h1>',
  408. '<ul>',
  409. JSSpec.options.rerun ? '<li>[<a href="?" title="rerun all specs">X</a>] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '</li>' : '',
  410. ' <li><span id="total_examples">' + JSSpec.runner.totalExamples + '</span> examples</li>',
  411. ' <li><span id="total_failures">0</span> failures</li>',
  412. ' <li><span id="total_errors">0</span> errors</li>',
  413. ' <li><span id="progress">0</span>% done</li>',
  414. ' <li><span id="total_elapsed">0</span> secs</li>',
  415. '</ul>',
  416. '<p><a href="http://jania.pe.kr/aw/moin.cgi/JSSpec">JSSpec homepage</a></p>',
  417. ].join("");
  418. container.appendChild(title);
  419. var list = document.createElement("DIV");
  420. list.id = "list";
  421. list.innerHTML = [
  422. '<h2>List</h2>',
  423. '<ul class="specs">',
  424. function() {
  425. var specs = JSSpec.runner.getSpecs();
  426. var sb = [];
  427. for(var i = 0; i < specs.length; i++) {
  428. var spec = specs[i];
  429. sb.push('<li id="spec_' + specs[i].id + '_list"><h3><a href="#spec_' + specs[i].id + '">' + JSSpec.util.escapeTags(specs[i].context) + '</a> [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3> </li>');
  430. }
  431. return sb.join("");
  432. }(),
  433. '</ul>'
  434. ].join("");
  435. container.appendChild(list);
  436. var log = document.createElement("DIV");
  437. log.id = "log";
  438. log.innerHTML = [
  439. '<h2>Log</h2>',
  440. '<ul class="specs">',
  441. function() {
  442. var specs = JSSpec.runner.getSpecs();
  443. var sb = [];
  444. for(var i = 0; i < specs.length; i++) {
  445. var spec = specs[i];
  446. sb.push(' <li id="spec_' + specs[i].id + '">');
  447. sb.push(' <h3>' + JSSpec.util.escapeTags(specs[i].context) + ' [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3>');
  448. sb.push(' <ul id="spec_' + specs[i].id + '_examples" class="examples">');
  449. for(var j = 0; j < spec.examples.length; j++) {
  450. var example = spec.examples[j];
  451. sb.push(' <li id="example_' + example.id + '">');
  452. sb.push(' <h4>' + JSSpec.util.escapeTags(example.name) + '</h4>');
  453. sb.push(' <pre class="examples-code"><code>'+JSSpec.util.escapeTags(example.target.toString())+'</code></pre>');
  454. sb.push(' </li>');
  455. }
  456. sb.push(' </ul>');
  457. sb.push(' </li>');
  458. }
  459. return sb.join("");
  460. }(),
  461. '</ul>'
  462. ].join("");
  463. container.appendChild(log);
  464. // add event handler for toggling
  465. var specs = JSSpec.runner.getSpecs();
  466. var sb = [];
  467. for(var i = 0; i < specs.length; i++) {
  468. var spec = document.getElementById("spec_" + specs[i].id);
  469. var title = spec.getElementsByTagName("H3")[0];
  470. title.onclick = function(e) {
  471. var target = document.getElementById(this.parentNode.id + "_examples");
  472. target.style.display = target.style.display == "none" ? "block" : "none";
  473. return true;
  474. }
  475. }
  476. };
  477. JSSpec.Logger.prototype.onRunnerEnd = function() {
  478. if(JSSpec.runner.hasException()) {
  479. var times = 4;
  480. var title1 = "*" + this._title;
  481. var title2 = "*F" + JSSpec.runner.getTotalFailures() + " E" + JSSpec.runner.getTotalErrors() + "* " + this._title;
  482. } else {
  483. var times = 2;
  484. var title1 = this._title;
  485. var title2 = "Success";
  486. }
  487. this.blinkTitle(times,title1,title2);
  488. };
  489. JSSpec.Logger.prototype.blinkTitle = function(times, title1, title2) {
  490. var times = times * 2;
  491. var mode = true;
  492. var f = function() {
  493. if(times > 0) {
  494. document.title = mode ? title1 : title2;
  495. mode = !mode;
  496. times--;
  497. window.setTimeout(f, 500);
  498. } else {
  499. document.title = title1;
  500. }
  501. };
  502. f();
  503. };
  504. JSSpec.Logger.prototype.onSpecStart = function(spec) {
  505. var spec_list = document.getElementById("spec_" + spec.id + "_list");
  506. var spec_log = document.getElementById("spec_" + spec.id);
  507. spec_list.className = "ongoing";
  508. spec_log.className = "ongoing";
  509. };
  510. JSSpec.Logger.prototype.onSpecEnd = function(spec) {
  511. var spec_list = document.getElementById("spec_" + spec.id + "_list");
  512. var spec_log = document.getElementById("spec_" + spec.id);
  513. var examples = document.getElementById("spec_" + spec.id + "_examples");
  514. var className = spec.hasException() ? "exception" : "success";
  515. spec_list.className = className;
  516. spec_log.className = className;
  517. if(JSSpec.options.autocollapse && !spec.hasException()) examples.style.display = "none";
  518. if(spec.exception) {
  519. spec_log.appendChild(document.createTextNode(" - " + spec.exception.message));
  520. }
  521. };
  522. JSSpec.Logger.prototype.onExampleStart = function(example) {
  523. var li = document.getElementById("example_" + example.id);
  524. li.className = "ongoing";
  525. };
  526. JSSpec.Logger.prototype.onExampleEnd = function(example) {
  527. var li = document.getElementById("example_" + example.id);
  528. li.className = example.exception ? "exception" : "success";
  529. if(example.exception) {
  530. var div = document.createElement("DIV");
  531. div.innerHTML = example.exception.message + "<p><br />" + " at " + example.exception.fileName + ", line " + example.exception.lineNumber + "</p>";
  532. li.appendChild(div);
  533. }
  534. var title = document.getElementById("title");
  535. var runner = JSSpec.runner;
  536. title.className = runner.hasException() ? "exception" : "success";
  537. this.finishedExamples++;
  538. document.getElementById("total_failures").innerHTML = runner.getTotalFailures();
  539. document.getElementById("total_errors").innerHTML = runner.getTotalErrors();
  540. var progress = parseInt(this.finishedExamples / runner.totalExamples * 100);
  541. document.getElementById("progress").innerHTML = progress;
  542. document.getElementById("total_elapsed").innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000;
  543. document.title = progress + "%: " + this._title;
  544. };
  545. /**
  546. * IncludeMatcher
  547. */
  548. JSSpec.IncludeMatcher = function(actual, expected, condition) {
  549. this.actual = actual;
  550. this.expected = expected;
  551. this.condition = condition;
  552. this.match = false;
  553. this.explaination = this.makeExplain();
  554. };
  555. JSSpec.IncludeMatcher.createInstance = function(actual, expected, condition) {
  556. return new JSSpec.IncludeMatcher(actual, expected, condition);
  557. };
  558. JSSpec.IncludeMatcher.prototype.matches = function() {
  559. return this.match;
  560. };
  561. JSSpec.IncludeMatcher.prototype.explain = function() {
  562. return this.explaination;
  563. };
  564. JSSpec.IncludeMatcher.prototype.makeExplain = function() {
  565. if(typeof this.actual.length == 'undefined') {
  566. return this.makeExplainForNotArray();
  567. } else {
  568. return this.makeExplainForArray();
  569. }
  570. };
  571. JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function() {
  572. if(this.condition) {
  573. this.match = !!this.actual[this.expected];
  574. } else {
  575. this.match = !this.actual[this.expected];
  576. }
  577. var sb = [];
  578. sb.push('<p>actual value:</p>');
  579. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, this.expected) + '</p>');
  580. sb.push('<p>should ' + (this.condition ? '' : 'not') + ' include:</p>');
  581. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
  582. return sb.join("");
  583. };
  584. JSSpec.IncludeMatcher.prototype.makeExplainForArray = function() {
  585. var matches;
  586. if(this.condition) {
  587. for(var i = 0; i < this.actual.length; i++) {
  588. matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
  589. if(matches) {
  590. this.match = true;
  591. break;
  592. }
  593. }
  594. } else {
  595. for(var i = 0; i < this.actual.length; i++) {
  596. matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
  597. if(matches) {
  598. this.match = false;
  599. break;
  600. }
  601. }
  602. }
  603. if(this.match) return "";
  604. var sb = [];
  605. sb.push('<p>actual value:</p>');
  606. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, this.condition ? null : i) + '</p>');
  607. sb.push('<p>should ' + (this.condition ? '' : 'not') + ' include:</p>');
  608. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
  609. return sb.join("");
  610. };
  611. /**
  612. * PropertyLengthMatcher
  613. */
  614. JSSpec.PropertyLengthMatcher = function(num, property, o, condition) {
  615. this.num = num;
  616. this.o = o;
  617. this.property = property;
  618. if((property == 'characters' || property == 'items') && typeof o.length != 'undefined') {
  619. this.property = 'length';
  620. }
  621. this.condition = condition;
  622. this.conditionMet = function(x) {
  623. if(condition == 'exactly') return x.length == num;
  624. if(condition == 'at least') return x.length >= num;
  625. if(condition == 'at most') return x.length <= num;
  626. throw "Unknown condition '" + condition + "'";
  627. };
  628. this.match = false;
  629. this.explaination = this.makeExplain();
  630. };
  631. JSSpec.PropertyLengthMatcher.prototype.makeExplain = function() {
  632. if(this.o._type == 'String' && this.property == 'length') {
  633. this.match = this.conditionMet(this.o);
  634. return this.match ? '' : this.makeExplainForString();
  635. } else if(typeof this.o.length != 'undefined' && this.property == "length") {
  636. this.match = this.conditionMet(this.o);
  637. return this.match ? '' : this.makeExplainForArray();
  638. } else if(typeof this.o[this.property] != 'undefined' && this.o[this.property] != null) {
  639. this.match = this.conditionMet(this.o[this.property]);
  640. return this.match ? '' : this.makeExplainForObject();
  641. } else if(typeof this.o[this.property] == 'undefined' || this.o[this.property] == null) {
  642. this.match = false;
  643. return this.makeExplainForNoProperty();
  644. }
  645. this.match = true;
  646. };
  647. JSSpec.PropertyLengthMatcher.prototype.makeExplainForString = function() {
  648. var sb = [];
  649. var exp = this.num == 0 ?
  650. 'be an <strong>empty string</strong>' :
  651. 'have <strong>' + this.condition + ' ' + this.num + ' characters</strong>';
  652. sb.push('<p>actual value has <strong>' + this.o.length + ' characters</strong>:</p>');
  653. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
  654. sb.push('<p>but it should ' + exp + '.</p>');
  655. return sb.join("");
  656. };
  657. JSSpec.PropertyLengthMatcher.prototype.makeExplainForArray = function() {
  658. var sb = [];
  659. var exp = this.num == 0 ?
  660. 'be an <strong>empty array</strong>' :
  661. 'have <strong>' + this.condition + ' ' + this.num + ' items</strong>';
  662. sb.push('<p>actual value has <strong>' + this.o.length + ' items</strong>:</p>');
  663. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
  664. sb.push('<p>but it should ' + exp + '.</p>');
  665. return sb.join("");
  666. };
  667. JSSpec.PropertyLengthMatcher.prototype.makeExplainForObject = function() {
  668. var sb = [];
  669. var exp = this.num == 0 ?
  670. 'be <strong>empty</strong>' :
  671. 'have <strong>' + this.condition + ' ' + this.num + ' ' + this.property + '.</strong>';
  672. sb.push('<p>actual value has <strong>' + this.o[this.property].length + ' ' + this.property + '</strong>:</p>');
  673. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o, false, this.property) + '</p>');
  674. sb.push('<p>but it should ' + exp + '.</p>');
  675. return sb.join("");
  676. };
  677. JSSpec.PropertyLengthMatcher.prototype.makeExplainForNoProperty = function() {
  678. var sb = [];
  679. sb.push('<p>actual value:</p>');
  680. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.o) + '</p>');
  681. sb.push('<p>should have <strong>' + this.condition + ' ' + this.num + ' ' + this.property + '</strong> but there\'s no such property.</p>');
  682. return sb.join("");
  683. };
  684. JSSpec.PropertyLengthMatcher.prototype.matches = function() {
  685. return this.match;
  686. };
  687. JSSpec.PropertyLengthMatcher.prototype.explain = function() {
  688. return this.explaination;
  689. };
  690. JSSpec.PropertyLengthMatcher.createInstance = function(num, property, o, condition) {
  691. return new JSSpec.PropertyLengthMatcher(num, property, o, condition);
  692. };
  693. /**
  694. * EqualityMatcher
  695. */
  696. JSSpec.EqualityMatcher = {};
  697. JSSpec.EqualityMatcher.createInstance = function(expected, actual) {
  698. if(expected == null || actual == null) {
  699. return new JSSpec.NullEqualityMatcher(expected, actual);
  700. } else if(expected._type && expected._type == actual._type) {
  701. if(expected._type == "String") {
  702. return new JSSpec.StringEqualityMatcher(expected, actual);
  703. } else if(expected._type == "Date") {
  704. return new JSSpec.DateEqualityMatcher(expected, actual);
  705. } else if(expected._type == "Number") {
  706. return new JSSpec.NumberEqualityMatcher(expected, actual);
  707. } else if(expected._type == "Array") {
  708. return new JSSpec.ArrayEqualityMatcher(expected, actual);
  709. } else if(expected._type == "Boolean") {
  710. return new JSSpec.BooleanEqualityMatcher(expected, actual);
  711. }
  712. }
  713. return new JSSpec.ObjectEqualityMatcher(expected, actual);
  714. };
  715. JSSpec.EqualityMatcher.basicExplain = function(expected, actual, expectedDesc, actualDesc) {
  716. var sb = [];
  717. sb.push(actualDesc || '<p>actual value:</p>');
  718. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(actual) + '</p>');
  719. sb.push(expectedDesc || '<p>should be:</p>');
  720. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(expected) + '</p>');
  721. return sb.join("");
  722. };
  723. JSSpec.EqualityMatcher.diffExplain = function(expected, actual) {
  724. var sb = [];
  725. sb.push('<p>diff:</p>');
  726. sb.push('<p style="margin-left:2em;">');
  727. var dmp = new diff_match_patch();
  728. var diff = dmp.diff_main(expected, actual);
  729. dmp.diff_cleanupEfficiency(diff);
  730. sb.push(JSSpec.util.inspect(dmp.diff_prettyHtml(diff), true));
  731. sb.push('</p>');
  732. return sb.join("");
  733. };
  734. /**
  735. * BooleanEqualityMatcher
  736. */
  737. JSSpec.BooleanEqualityMatcher = function(expected, actual) {
  738. this.expected = expected;
  739. this.actual = actual;
  740. };
  741. JSSpec.BooleanEqualityMatcher.prototype.explain = function() {
  742. var sb = [];
  743. sb.push('<p>actual value:</p>');
  744. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
  745. sb.push('<p>should be:</p>');
  746. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected) + '</p>');
  747. return sb.join("");
  748. };
  749. JSSpec.BooleanEqualityMatcher.prototype.matches = function() {
  750. return this.expected == this.actual;
  751. };
  752. /**
  753. * NullEqualityMatcher
  754. */
  755. JSSpec.NullEqualityMatcher = function(expected, actual) {
  756. this.expected = expected;
  757. this.actual = actual;
  758. };
  759. JSSpec.NullEqualityMatcher.prototype.matches = function() {
  760. return this.expected == this.actual && typeof this.expected == typeof this.actual;
  761. };
  762. JSSpec.NullEqualityMatcher.prototype.explain = function() {
  763. return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
  764. };
  765. JSSpec.DateEqualityMatcher = function(expected, actual) {
  766. this.expected = expected;
  767. this.actual = actual;
  768. };
  769. JSSpec.DateEqualityMatcher.prototype.matches = function() {
  770. return this.expected.getTime() == this.actual.getTime();
  771. };
  772. JSSpec.DateEqualityMatcher.prototype.explain = function() {
  773. var sb = [];
  774. sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
  775. sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected.toString(), this.actual.toString()));
  776. return sb.join("");
  777. };
  778. /**
  779. * ObjectEqualityMatcher
  780. */
  781. JSSpec.ObjectEqualityMatcher = function(expected, actual) {
  782. this.expected = expected;
  783. this.actual = actual;
  784. this.match = this.expected == this.actual;
  785. this.explaination = this.makeExplain();
  786. };
  787. JSSpec.ObjectEqualityMatcher.prototype.matches = function() {return this.match};
  788. JSSpec.ObjectEqualityMatcher.prototype.explain = function() {return this.explaination};
  789. JSSpec.ObjectEqualityMatcher.prototype.makeExplain = function() {
  790. if(this.expected == this.actual) {
  791. this.match = true;
  792. return "";
  793. }
  794. if(JSSpec.util.isDomNode(this.expected)) {
  795. return this.makeExplainForDomNode();
  796. }
  797. var key, expectedHasItem, actualHasItem;
  798. for(key in this.expected) {
  799. expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
  800. actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
  801. if(expectedHasItem && !actualHasItem) return this.makeExplainForMissingItem(key);
  802. }
  803. for(key in this.actual) {
  804. expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
  805. actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
  806. if(actualHasItem && !expectedHasItem) return this.makeExplainForUnknownItem(key);
  807. }
  808. for(key in this.expected) {
  809. var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[key], this.actual[key]);
  810. if(!matcher.matches()) return this.makeExplainForItemMismatch(key);
  811. }
  812. this.match = true;
  813. };
  814. JSSpec.ObjectEqualityMatcher.prototype.makeExplainForDomNode = function(key) {
  815. var sb = [];
  816. sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
  817. return sb.join("");
  818. };
  819. JSSpec.ObjectEqualityMatcher.prototype.makeExplainForMissingItem = function(key) {
  820. var sb = [];
  821. sb.push('<p>actual value has no item named <strong>' + JSSpec.util.inspect(key) + '</strong></p>');
  822. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
  823. sb.push('<p>but it should have the item whose value is <strong>' + JSSpec.util.inspect(this.expected[key]) + '</strong></p>');
  824. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
  825. return sb.join("");
  826. };
  827. JSSpec.ObjectEqualityMatcher.prototype.makeExplainForUnknownItem = function(key) {
  828. var sb = [];
  829. sb.push('<p>actual value has item named <strong>' + JSSpec.util.inspect(key) + '</strong></p>');
  830. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
  831. sb.push('<p>but there should be no such item</p>');
  832. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
  833. return sb.join("");
  834. };
  835. JSSpec.ObjectEqualityMatcher.prototype.makeExplainForItemMismatch = function(key) {
  836. var sb = [];
  837. sb.push('<p>actual value has an item named <strong>' + JSSpec.util.inspect(key) + '</strong> whose value is <strong>' + JSSpec.util.inspect(this.actual[key]) + '</strong></p>');
  838. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, key) + '</p>');
  839. sb.push('<p>but it\'s value should be <strong>' + JSSpec.util.inspect(this.expected[key]) + '</strong></p>');
  840. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, key) + '</p>');
  841. return sb.join("");
  842. };
  843. /**
  844. * ArrayEqualityMatcher
  845. */
  846. JSSpec.ArrayEqualityMatcher = function(expected, actual) {
  847. this.expected = expected;
  848. this.actual = actual;
  849. this.match = this.expected == this.actual;
  850. this.explaination = this.makeExplain();
  851. };
  852. JSSpec.ArrayEqualityMatcher.prototype.matches = function() {return this.match};
  853. JSSpec.ArrayEqualityMatcher.prototype.explain = function() {return this.explaination};
  854. JSSpec.ArrayEqualityMatcher.prototype.makeExplain = function() {
  855. if(this.expected.length != this.actual.length) return this.makeExplainForLengthMismatch();
  856. for(var i = 0; i < this.expected.length; i++) {
  857. var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[i], this.actual[i]);
  858. if(!matcher.matches()) return this.makeExplainForItemMismatch(i);
  859. }
  860. this.match = true;
  861. };
  862. JSSpec.ArrayEqualityMatcher.prototype.makeExplainForLengthMismatch = function() {
  863. return JSSpec.EqualityMatcher.basicExplain(
  864. this.expected,
  865. this.actual,
  866. '<p>but it should be <strong>' + this.expected.length + '</strong></p>',
  867. '<p>actual value has <strong>' + this.actual.length + '</strong> items</p>'
  868. );
  869. };
  870. JSSpec.ArrayEqualityMatcher.prototype.makeExplainForItemMismatch = function(index) {
  871. var postfix = ["th", "st", "nd", "rd", "th"][Math.min((index + 1) % 10,4)];
  872. var sb = [];
  873. sb.push('<p>' + (index + 1) + postfix + ' item (index ' + index + ') of actual value is <strong>' + JSSpec.util.inspect(this.actual[index]) + '</strong>:</p>');
  874. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual, false, index) + '</p>');
  875. sb.push('<p>but it should be <strong>' + JSSpec.util.inspect(this.expected[index]) + '</strong>:</p>');
  876. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.expected, false, index) + '</p>');
  877. return sb.join("");
  878. };
  879. /**
  880. * NumberEqualityMatcher
  881. */
  882. JSSpec.NumberEqualityMatcher = function(expected, actual) {
  883. this.expected = expected;
  884. this.actual = actual;
  885. };
  886. JSSpec.NumberEqualityMatcher.prototype.matches = function() {
  887. if(this.expected == this.actual) return true;
  888. };
  889. JSSpec.NumberEqualityMatcher.prototype.explain = function() {
  890. return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
  891. };
  892. /**
  893. * StringEqualityMatcher
  894. */
  895. JSSpec.StringEqualityMatcher = function(expected, actual) {
  896. this.expected = expected;
  897. this.actual = actual;
  898. };
  899. JSSpec.StringEqualityMatcher.prototype.matches = function() {
  900. return this.expected == this.actual;
  901. };
  902. JSSpec.StringEqualityMatcher.prototype.explain = function() {
  903. var sb = [];
  904. sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
  905. sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected, this.actual));
  906. return sb.join("");
  907. };
  908. /**
  909. * PatternMatcher
  910. */
  911. JSSpec.PatternMatcher = function(actual, pattern, condition) {
  912. this.actual = actual;
  913. this.pattern = pattern;
  914. this.condition = condition;
  915. this.match = false;
  916. this.explaination = this.makeExplain();
  917. };
  918. JSSpec.PatternMatcher.createInstance = function(actual, pattern, condition) {
  919. return new JSSpec.PatternMatcher(actual, pattern, condition);
  920. };
  921. JSSpec.PatternMatcher.prototype.makeExplain = function() {
  922. var sb;
  923. if(this.actual == null || this.actual._type != 'String') {
  924. sb = [];
  925. sb.push('<p>actual value:</p>');
  926. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
  927. sb.push('<p>should ' + (this.condition ? '' : 'not') + ' match with pattern:</p>');
  928. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.pattern) + '</p>');
  929. sb.push('<p>but pattern matching cannot be performed.</p>');
  930. return sb.join("");
  931. } else {
  932. this.match = this.condition == !!this.actual.match(this.pattern);
  933. if(this.match) return "";
  934. sb = [];
  935. sb.push('<p>actual value:</p>');
  936. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.actual) + '</p>');
  937. sb.push('<p>should ' + (this.condition ? '' : 'not') + ' match with pattern:</p>');
  938. sb.push('<p style="margin-left:2em;">' + JSSpec.util.inspect(this.pattern) + '</p>');
  939. return sb.join("");
  940. }
  941. };
  942. JSSpec.PatternMatcher.prototype.matches = function() {
  943. return this.match;
  944. };
  945. JSSpec.PatternMatcher.prototype.explain = function() {
  946. return this.explaination;
  947. };
  948. /**
  949. * Domain Specific Languages
  950. */
  951. JSSpec.DSL = {};
  952. JSSpec.DSL.forString = {
  953. normalizeHtml: function() {
  954. var html = this;
  955. // Uniformize quotation, turn tag names and attribute names into lower case
  956. html = html.replace(/<(\/?)(\w+)([^>]*?)>/img, function(str, closingMark, tagName, attrs) {
  957. var sortedAttrs = JSSpec.util.sortHtmlAttrs(JSSpec.util.correctHtmlAttrQuotation(attrs).toLowerCase())
  958. return "<" + closingMark + tagName.toLowerCase() + sortedAttrs + ">"
  959. });
  960. // validation self-closing tags
  961. html = html.replace(/<(br|hr|img)([^>]*?)>/mg, function(str, tag, attrs) {
  962. return "<" + tag + attrs + " />";
  963. });
  964. // append semi-colon at the end of style value
  965. html = html.replace(/style="(.*?)"/mg, function(str, styleStr) {
  966. styleStr = JSSpec.util.sortStyleEntries(styleStr.strip()); // for Safari
  967. if(styleStr.charAt(styleStr.length - 1) != ';') styleStr += ";"
  968. return 'style="' + styleStr + '"'
  969. });
  970. // sort style entries
  971. // remove empty style attributes
  972. html = html.replace(/ style=";"/mg, "");
  973. // remove new-lines
  974. html = html.replace(/\r/mg, '');
  975. html = html.replace(/\n/mg, '');
  976. return html;
  977. }
  978. };
  979. JSSpec.DSL.describe = function(context, entries, base) {
  980. if(base) {
  981. for(var i = 0; i < JSSpec.specs.length; i++) {
  982. if(JSSpec.specs[i].context === base) {
  983. base = JSSpec.specs[i];
  984. break;
  985. }
  986. }
  987. for(var i = 0; i < base.examples.length; i++) {
  988. var example = base.examples[i];
  989. if(!entries[example.name]) entries[example.name] = example.target;
  990. }
  991. }
  992. JSSpec.specs.push(new JSSpec.Spec(context, entries));
  993. };
  994. JSSpec.DSL.value_of = function(target) {
  995. if(JSSpec._secondPass) return {};
  996. var subject = new JSSpec.DSL.Subject(target);
  997. return subject;
  998. };
  999. JSSpec.DSL.Subject = function(target) {
  1000. this.target = target;
  1001. };
  1002. JSSpec.DSL.Subject.prototype._type = 'Subject';
  1003. JSSpec.DSL.Subject.prototype.should_fail = function(message) {
  1004. JSSpec._assertionFailure = {message:message};
  1005. throw JSSpec._assertionFailure;
  1006. };
  1007. JSSpec.DSL.Subject.prototype.should_be = function(expected) {
  1008. var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
  1009. if(!matcher.matches()) {
  1010. JSSpec._assertionFailure = {message:matcher.explain()};
  1011. throw JSSpec._assertionFailure;
  1012. }
  1013. };
  1014. JSSpec.DSL.Subject.prototype.should_not_be = function(expected) {
  1015. // TODO JSSpec.EqualityMatcher should support 'condition'
  1016. var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
  1017. if(matcher.matches()) {
  1018. JSSpec._assertionFailure = {message:"'" + this.target + "' should not be '" + expected + "'"};
  1019. throw JSSpec._assertionFailure;
  1020. }
  1021. };
  1022. JSSpec.DSL.Subject.prototype.should_be_empty = function() {
  1023. this.should_have(0, this.getType() == 'String' ? 'characters' : 'items');
  1024. };
  1025. JSSpec.DSL.Subject.prototype.should_not_be_empty = function() {
  1026. this.should_have_at_least(1, this.getType() == 'String' ? 'characters' : 'items');
  1027. };
  1028. JSSpec.DSL.Subject.prototype.should_be_true = function() {
  1029. this.should_be(true);
  1030. };
  1031. JSSpec.DSL.Subject.prototype.should_be_false = function() {
  1032. this.should_be(false);
  1033. };
  1034. JSSpec.DSL.Subject.prototype.should_be_null = function() {
  1035. this.should_be(null);
  1036. };
  1037. JSSpec.DSL.Subject.prototype.should_be_undefined = function() {
  1038. this.should_be(undefined);
  1039. };
  1040. JSSpec.DSL.Subject.prototype.should_not_be_null = function() {
  1041. this.should_not_be(null);
  1042. };
  1043. JSSpec.DSL.Subject.prototype.should_not_be_undefined = function() {
  1044. this.should_not_be(undefined);
  1045. };
  1046. JSSpec.DSL.Subject.prototype._should_have = function(num, property, condition) {
  1047. var matcher = JSSpec.PropertyLengthMatcher.createInstance(num, property, this.target, condition);
  1048. if(!matcher.matches()) {
  1049. JSSpec._assertionFailure = {message:matcher.explain()};
  1050. throw JSSpec._assertionFailure;
  1051. }
  1052. };
  1053. JSSpec.DSL.Subject.prototype.should_have = function(num, property) {
  1054. this._should_have(num, property, "exactly");
  1055. };
  1056. JSSpec.DSL.Subject.prototype.should_have_exactly = function(num, property) {
  1057. this._should_have(num, property, "exactly");
  1058. };
  1059. JSSpec.DSL.Subject.prototype.should_have_at_least = function(num, property) {
  1060. this._should_have(num, property, "at least");
  1061. };
  1062. JSSpec.DSL.Subject.prototype.should_have_at_most = function(num, property) {
  1063. this._should_have(num, property, "at most");
  1064. };
  1065. JSSpec.DSL.Subject.prototype.should_include = function(expected) {
  1066. var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, true);
  1067. if(!matcher.matches()) {
  1068. JSSpec._assertionFailure = {message:matcher.explain()};
  1069. throw JSSpec._assertionFailure;
  1070. }
  1071. };
  1072. JSSpec.DSL.Subject.prototype.should_not_include = function(expected) {
  1073. var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, false);
  1074. if(!matcher.matches()) {
  1075. JSSpec._assertionFailure = {message:matcher.explain()};
  1076. throw JSSpec._assertionFailure;
  1077. }
  1078. };
  1079. JSSpec.DSL.Subject.prototype.should_match = function(pattern) {
  1080. var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, true);
  1081. if(!matcher.matches()) {
  1082. JSSpec._assertionFailure = {message:matcher.explain()};
  1083. throw JSSpec._assertionFailure;
  1084. }
  1085. }
  1086. JSSpec.DSL.Subject.prototype.should_not_match = function(pattern) {
  1087. var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, false);
  1088. if(!matcher.matches()) {
  1089. JSSpec._assertionFailure = {message:matcher.explain()};
  1090. throw JSSpec._assertionFailure;
  1091. }
  1092. };
  1093. JSSpec.DSL.Subject.prototype.getType = function() {
  1094. if(typeof this.target == 'undefined') {
  1095. return 'undefined';
  1096. } else if(this.target == null) {
  1097. return 'null';
  1098. } else if(this.target._type) {
  1099. return this.target._type;
  1100. } else if(JSSpec.util.isDomNode(this.target)) {
  1101. return 'DomNode';
  1102. } else {
  1103. return 'object';
  1104. }
  1105. };
  1106. /**
  1107. * Utilities
  1108. */
  1109. JSSpec.util = {
  1110. escapeTags: function(string) {
  1111. return string.replace(/</img, '&lt;').replace(/>/img, '&gt;');
  1112. },
  1113. escapeMetastring: function(string) {
  1114. return string.replace(/\r/img, '\\r').replace(/\n/img, '\\n').replace(/\&para\;\<BR\>/img, '\\n').replace(/\t/img, '\\t');
  1115. },
  1116. parseOptions: function(defaults) {
  1117. var options = defaults;
  1118. var url = location.href;
  1119. var queryIndex = url.indexOf('?');
  1120. if(queryIndex == -1) return options;
  1121. var query = url.substring(queryIndex + 1).split('#')[0];
  1122. var pairs = query.split('&');
  1123. for(var i = 0; i < pairs.length; i++) {
  1124. var tokens = pairs[i].split('=');
  1125. options[tokens[0]] = tokens[1];
  1126. }
  1127. return options;
  1128. },
  1129. correctHtmlAttrQuotation: function(html) {
  1130. html = html.replace(/(\w+)=['"]([^'"]+)['"]/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
  1131. html = html.replace(/(\w+)=([^ '"]+)/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
  1132. html = html.replace(/'/mg, '"');
  1133. return html;
  1134. },
  1135. sortHtmlAttrs: function(html) {
  1136. var attrs = [];
  1137. html.replace(/((\w+)="[^"]+")/mg, function(str, matched) {
  1138. attrs.push(matched);
  1139. });
  1140. return attrs.length == 0 ? "" : " " + attrs.sort().join(" ");
  1141. },
  1142. sortStyleEntries: function(styleText) {
  1143. var entries = styleText.split(/; /);
  1144. return entries.sort().join("; ");
  1145. },
  1146. escapeHtml: function(str) {
  1147. if(!this._div) {
  1148. this._div = document.createElement("DIV");
  1149. this._text = document.createTextNode('');
  1150. this._div.appendChild(this._text);
  1151. }
  1152. this._text.data = str;
  1153. return this._div.innerHTML;
  1154. },
  1155. isDomNode: function(o) {
  1156. // TODO: make it more stricter
  1157. return (typeof o.nodeName == 'string') && (typeof o.nodeType == 'number');
  1158. },
  1159. inspectDomPath: function(o) {
  1160. var sb = [];
  1161. while(o && o.nodeName != '#document' && o.parent) {
  1162. var siblings = o.parentNode.childNodes;
  1163. for(var i = 0; i < siblings.length; i++) {
  1164. if(siblings[i] == o) {
  1165. sb.push(o.nodeName + (i == 0 ? '' : '[' + i + ']'));
  1166. break;
  1167. }
  1168. }
  1169. o = o.parentNode;
  1170. }
  1171. return sb.join(" &gt; ");
  1172. },
  1173. inspectDomNode: function(o) {
  1174. if(o.nodeType == 1) {
  1175. var nodeName = o.nodeName.toLowerCase();
  1176. var sb = [];
  1177. sb.push('<span class="dom_value">');
  1178. sb.push("&lt;");
  1179. sb.push(nodeName);
  1180. var attrs = o.attributes;
  1181. for(var i = 0; i < attrs.length; i++) {
  1182. if(
  1183. attrs[i].nodeValue &&
  1184. attrs[i].nodeName != 'contentEditable' &&
  1185. attrs[i].nodeName != 'style' &&
  1186. typeof attrs[i].nodeValue != 'function'
  1187. ) sb.push(' <span class="dom_attr_name">' + attrs[i].nodeName.toLowerCase() + '</span>=<span class="dom_attr_value">"' + attrs[i].nodeValue + '"</span>');
  1188. }
  1189. if(o.style && o.style.cssText) {
  1190. sb.push(' <span class="dom_attr_name">style</span>=<span class="dom_attr_value">"' + o.style.cssText + '"</span>');
  1191. }
  1192. sb.push('&gt;');
  1193. sb.push(JSSpec.util.escapeHtml(o.innerHTML));
  1194. sb.push('&lt;/' + nodeName + '&gt;');
  1195. sb.push(' <span class="dom_path">(' + JSSpec.util.inspectDomPath(o) + ')</span>' );
  1196. sb.push('</span>');
  1197. return sb.join("");
  1198. } else if(o.nodeType == 3) {
  1199. return '<span class="dom_value">#text ' + o.nodeValue + '</span>';
  1200. } else {
  1201. return '<span class="dom_value">UnknownDomNode</span>';
  1202. }
  1203. },
  1204. inspect: function(o, dontEscape, emphasisKey) {
  1205. var sb, inspected;
  1206. if(typeof o == 'undefined') return '<span class="undefined_value">undefined</span>';
  1207. if(o == null) return '<span class="null_value">null</span>';
  1208. if(o._type == 'String') return '<span class="string_value">"' + (dontEscape ? JSSpec.util.escapeMetastring(o) : JSSpec.util.escapeHtml(JSSpec.util.escapeMetastring(o))) + '"</span>';
  1209. if(o._type == 'Date') {
  1210. return '<span class="date_value">"' + o.toString() + '"</span>';
  1211. }
  1212. if(o._type == 'Number') return '<span class="number_value">' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '</span>';
  1213. if(o._type == 'Boolean') return '<span class="boolean_value">' + o + '</span>';
  1214. if(o._type == 'RegExp') return '<span class="regexp_value">' + JSSpec.util.escapeHtml(o.toString()) + '</span>';
  1215. if(JSSpec.util.isDomNode(o)) return JSSpec.util.inspectDomNode(o);
  1216. if(o._type == 'Array' || typeof o.length != 'undefined') {
  1217. sb = [];
  1218. for(var i = 0; i < o.length; i++) {
  1219. inspected = JSSpec.util.inspect(o[i]);
  1220. sb.push(i == emphasisKey ? ('<strong>' + inspected + '</strong>') : inspected);
  1221. }
  1222. return '<span class="array_value">[' + sb.join(', ') + ']</span>';
  1223. }
  1224. // object
  1225. sb = [];
  1226. for(var key in o) {
  1227. if(key == 'should') continue;
  1228. inspected = JSSpec.util.inspect(key) + ":" + JSSpec.util.inspect(o[key]);
  1229. sb.push(key == emphasisKey ? ('<strong>' + inspected + '</strong>') : inspected);
  1230. }
  1231. return '<span class="object_value">{' + sb.join(', ') + '}</span>';
  1232. }
  1233. };
  1234. describe = JSSpec.DSL.describe;
  1235. behavior_of = JSSpec.DSL.describe;
  1236. value_of = JSSpec.DSL.value_of;
  1237. expect = JSSpec.DSL.value_of; // @deprecated
  1238. String.prototype._type = "String";
  1239. Number.prototype._type = "Number";
  1240. Date.prototype._type = "Date";
  1241. Array.prototype._type = "Array";
  1242. Boolean.prototype._type = "Boolean";
  1243. RegExp.prototype._type = "RegExp";
  1244. var targets = [Array.prototype, Date.prototype, Number.prototype, String.prototype, Boolean.prototype, RegExp.prototype];
  1245. String.prototype.normalizeHtml = JSSpec.DSL.forString.normalizeHtml;
  1246. String.prototype.asHtml = String.prototype.normalizeHtml; //@deprecated
  1247. String.prototype.strip = function() {return this.replace(/^\s+/, '').replace(/\s+$/, '');}
  1248. /**
  1249. * Main
  1250. */
  1251. JSSpec.defaultOptions = {
  1252. autorun: 1,
  1253. specIdBeginsWith: 0,
  1254. exampleIdBeginsWith: 0,
  1255. autocollapse: 1
  1256. };
  1257. JSSpec.options = JSSpec.util.parseOptions(JSSpec.defaultOptions);
  1258. JSSpec.Spec.id = JSSpec.options.specIdBeginsWith;
  1259. JSSpec.Example.id = JSSpec.options.exampleIdBeginsWith;
  1260. window.onload = function() {
  1261. if(JSSpec.specs.length > 0) {
  1262. if(!JSSpec.options.inSuite) {
  1263. JSSpec.runner = new JSSpec.Runner(JSSpec.specs, new JSSpec.Logger());
  1264. if(JSSpec.options.rerun) {
  1265. JSSpec.runner.rerun(decodeURIComponent(JSSpec.options.rerun));
  1266. } else {
  1267. JSSpec.runner.run();
  1268. }
  1269. } else {
  1270. // in suite, send all specs to parent
  1271. var parentWindow = window.frames.parent.window;
  1272. for(var i = 0; i < JSSpec.specs.length; i++) {
  1273. parentWindow.JSSpec.specs.push(JSSpec.specs[i]);
  1274. }
  1275. }
  1276. } else {
  1277. var links = document.getElementById('list').getElementsByTagName('A');
  1278. var frameContainer = document.createElement('DIV');
  1279. frameContainer.style.display = 'none';
  1280. document.body.appendChild(frameContainer);
  1281. for(var i = 0; i < links.length; i++) {
  1282. var frame = document.createElement('IFRAME');
  1283. frame.src = links[i].href + '?inSuite=0&specIdBeginsWith=' + (i * 10000) + '&exampleIdBeginsWith=' + (i * 10000);
  1284. frameContainer.appendChild(frame);
  1285. }
  1286. }
  1287. }