Commit 93dcfbd4 93dcfbd4ef31e52e86ba4a0bd164ec295f027f23 by Nicolas Perriault

jshint configuration, code cleaning

1 parent bcaf4e30
1 {
2 "asi": true,
3 "browser": true,
4 "debug": true,
5 "devel": true,
6 "eqeqeq": true,
7 "evil": true,
8 "maxparams": 5,
9 "maxdepth": 3,
10 "maxstatements": 15,
11 "maxcomplexity": 7,
12 "regexdash": true,
13 "strict": true,
14 "sub": true,
15 "trailing": true,
16 "undef": true,
17
18 "predef" : [
19 "exports",
20 "phantom",
21 "require",
22 "window"
23 ]
24 }
1 docs
2 modules/vendors
3 modules/events.js
4 modules/querystring.js
5 samples
6 tests/site
7 tests/testdir
8 tests/suites
...@@ -208,12 +208,6 @@ function bootstrap(global) { ...@@ -208,12 +208,6 @@ function bootstrap(global) {
208 // custom global CasperError 208 // custom global CasperError
209 global.CasperError = function CasperError(msg) { 209 global.CasperError = function CasperError(msg) {
210 Error.call(this); 210 Error.call(this);
211 try {
212 // let's get where this error has been thrown from, if we can
213 this._from = arguments.callee.caller.name;
214 } catch (e) {
215 this._from = "anonymous";
216 }
217 this.message = msg; 211 this.message = msg;
218 this.name = 'CasperError'; 212 this.name = 'CasperError';
219 }; 213 };
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
33 var colorizer = require('colorizer'); 33 var colorizer = require('colorizer');
34 var events = require('events'); 34 var events = require('events');
35 var fs = require('fs'); 35 var fs = require('fs');
36 var http = require('http');
36 var mouse = require('mouse'); 37 var mouse = require('mouse');
37 var qs = require('querystring'); 38 var qs = require('querystring');
38 var tester = require('tester'); 39 var tester = require('tester');
...@@ -192,6 +193,7 @@ utils.inherits(Casper, events.EventEmitter); ...@@ -192,6 +193,7 @@ utils.inherits(Casper, events.EventEmitter);
192 */ 193 */
193 Casper.prototype.back = function back() { 194 Casper.prototype.back = function back() {
194 "use strict"; 195 "use strict";
196 this.checkStarted();
195 return this.then(function _step() { 197 return this.then(function _step() {
196 this.emit('back'); 198 this.emit('back');
197 this.evaluate(function _evaluate() { 199 this.evaluate(function _evaluate() {
...@@ -230,9 +232,8 @@ Casper.prototype.base64encode = function base64encode(url, method, data) { ...@@ -230,9 +232,8 @@ Casper.prototype.base64encode = function base64encode(url, method, data) {
230 */ 232 */
231 Casper.prototype.capture = function capture(targetFile, clipRect) { 233 Casper.prototype.capture = function capture(targetFile, clipRect) {
232 "use strict"; 234 "use strict";
233 if (!this.started) { 235 /*jshint maxstatements:20*/
234 throw new CasperError("Casper not started, can't capture()"); 236 this.checkStarted();
235 }
236 var previousClipRect; 237 var previousClipRect;
237 targetFile = fs.absolute(targetFile); 238 targetFile = fs.absolute(targetFile);
238 if (clipRect) { 239 if (clipRect) {
...@@ -270,16 +271,9 @@ Casper.prototype.capture = function capture(targetFile, clipRect) { ...@@ -270,16 +271,9 @@ Casper.prototype.capture = function capture(targetFile, clipRect) {
270 */ 271 */
271 Casper.prototype.captureBase64 = function captureBase64(format, area) { 272 Casper.prototype.captureBase64 = function captureBase64(format, area) {
272 "use strict"; 273 "use strict";
273 if (!this.started) { 274 /*jshint maxstatements:20*/
274 throw new CasperError("Casper not started, can't captureBase64()"); 275 this.checkStarted();
275 } 276 var base64, previousClipRect, formats = ['bmp', 'jpg', 'jpeg', 'png', 'ppm', 'tiff', 'xbm', 'xpm'];
276 if (!('renderBase64' in this.page)) {
277 this.warn('captureBase64() requires PhantomJS >= 1.6');
278 return;
279 }
280 var base64;
281 var previousClipRect;
282 var formats = ['bmp', 'jpg', 'jpeg', 'png', 'ppm', 'tiff', 'xbm', 'xpm'];
283 if (formats.indexOf(format.toLowerCase()) === -1) { 277 if (formats.indexOf(format.toLowerCase()) === -1) {
284 throw new CasperError(f('Unsupported format "%s"', format)); 278 throw new CasperError(f('Unsupported format "%s"', format));
285 } 279 }
...@@ -346,6 +340,20 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) { ...@@ -346,6 +340,20 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) {
346 }; 340 };
347 341
348 /** 342 /**
343 * Checks if this instance is started.
344 *
345 * @return Boolean
346 * @throws CasperError
347 */
348 Casper.prototype.checkStarted = function checkStarted() {
349 "use strict";
350 if (!this.started) {
351 throw new CasperError(f("Casper is not started, can't execute `%s()`",
352 checkStarted.caller.name));
353 }
354 };
355
356 /**
349 * Clears the current page execution environment context. Useful to avoid 357 * Clears the current page execution environment context. Useful to avoid
350 * having previously loaded DOM contents being still active (refs #34). 358 * having previously loaded DOM contents being still active (refs #34).
351 * 359 *
...@@ -356,6 +364,7 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) { ...@@ -356,6 +364,7 @@ Casper.prototype.checkStep = function checkStep(self, onComplete) {
356 */ 364 */
357 Casper.prototype.clear = function clear() { 365 Casper.prototype.clear = function clear() {
358 "use strict"; 366 "use strict";
367 this.checkStarted();
359 this.page.content = ''; 368 this.page.content = '';
360 return this; 369 return this;
361 }; 370 };
...@@ -371,6 +380,7 @@ Casper.prototype.clear = function clear() { ...@@ -371,6 +380,7 @@ Casper.prototype.clear = function clear() {
371 */ 380 */
372 Casper.prototype.click = function click(selector) { 381 Casper.prototype.click = function click(selector) {
373 "use strict"; 382 "use strict";
383 this.checkStarted();
374 return this.mouseEvent('click', selector); 384 return this.mouseEvent('click', selector);
375 }; 385 };
376 386
...@@ -384,6 +394,7 @@ Casper.prototype.click = function click(selector) { ...@@ -384,6 +394,7 @@ Casper.prototype.click = function click(selector) {
384 */ 394 */
385 Casper.prototype.clickLabel = function clickLabel(label, tag) { 395 Casper.prototype.clickLabel = function clickLabel(label, tag) {
386 "use strict"; 396 "use strict";
397 this.checkStarted();
387 tag = tag || "*"; 398 tag = tag || "*";
388 var escapedLabel = label.toString().replace(/"/g, '\\"'); 399 var escapedLabel = label.toString().replace(/"/g, '\\"');
389 var selector = selectXPath(f('//%s[text()="%s"]', tag, escapedLabel)); 400 var selector = selectXPath(f('//%s[text()="%s"]', tag, escapedLabel));
...@@ -416,6 +427,7 @@ Casper.prototype.createStep = function createStep(fn, options) { ...@@ -416,6 +427,7 @@ Casper.prototype.createStep = function createStep(fn, options) {
416 */ 427 */
417 Casper.prototype.debugHTML = function debugHTML(selector, outer) { 428 Casper.prototype.debugHTML = function debugHTML(selector, outer) {
418 "use strict"; 429 "use strict";
430 this.checkStarted();
419 return this.echo(this.getHTML(selector, outer)); 431 return this.echo(this.getHTML(selector, outer));
420 }; 432 };
421 433
...@@ -426,6 +438,7 @@ Casper.prototype.debugHTML = function debugHTML(selector, outer) { ...@@ -426,6 +438,7 @@ Casper.prototype.debugHTML = function debugHTML(selector, outer) {
426 */ 438 */
427 Casper.prototype.debugPage = function debugPage() { 439 Casper.prototype.debugPage = function debugPage() {
428 "use strict"; 440 "use strict";
441 this.checkStarted();
429 this.echo(this.evaluate(function _evaluate() { 442 this.echo(this.evaluate(function _evaluate() {
430 return document.body.textContent || document.body.innerText; 443 return document.body.textContent || document.body.innerText;
431 })); 444 }));
...@@ -441,6 +454,7 @@ Casper.prototype.debugPage = function debugPage() { ...@@ -441,6 +454,7 @@ Casper.prototype.debugPage = function debugPage() {
441 */ 454 */
442 Casper.prototype.die = function die(message, status) { 455 Casper.prototype.die = function die(message, status) {
443 "use strict"; 456 "use strict";
457 this.checkStarted();
444 this.result.status = "error"; 458 this.result.status = "error";
445 this.result.time = new Date().getTime() - this.startTime; 459 this.result.time = new Date().getTime() - this.startTime;
446 if (!utils.isString(message) || !message.length) { 460 if (!utils.isString(message) || !message.length) {
...@@ -465,6 +479,7 @@ Casper.prototype.die = function die(message, status) { ...@@ -465,6 +479,7 @@ Casper.prototype.die = function die(message, status) {
465 */ 479 */
466 Casper.prototype.download = function download(url, targetPath, method, data) { 480 Casper.prototype.download = function download(url, targetPath, method, data) {
467 "use strict"; 481 "use strict";
482 this.checkStarted();
468 var cu = require('clientutils').create(utils.mergeObjects({}, this.options)); 483 var cu = require('clientutils').create(utils.mergeObjects({}, this.options));
469 try { 484 try {
470 fs.write(targetPath, cu.decode(this.base64encode(url, method, data)), 'wb'); 485 fs.write(targetPath, cu.decode(this.base64encode(url, method, data)), 'wb');
...@@ -548,6 +563,7 @@ Casper.prototype.echo = function echo(text, style, pad) { ...@@ -548,6 +563,7 @@ Casper.prototype.echo = function echo(text, style, pad) {
548 */ 563 */
549 Casper.prototype.evaluate = function evaluate(fn, context) { 564 Casper.prototype.evaluate = function evaluate(fn, context) {
550 "use strict"; 565 "use strict";
566 this.checkStarted();
551 // ensure client utils are always injected 567 // ensure client utils are always injected
552 this.injectClientUtils(); 568 this.injectClientUtils();
553 // function context 569 // function context
...@@ -571,6 +587,7 @@ Casper.prototype.evaluate = function evaluate(fn, context) { ...@@ -571,6 +587,7 @@ Casper.prototype.evaluate = function evaluate(fn, context) {
571 */ 587 */
572 Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message, status) { 588 Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message, status) {
573 "use strict"; 589 "use strict";
590 this.checkStarted();
574 if (!this.evaluate(fn)) { 591 if (!this.evaluate(fn)) {
575 return this.die(message, status); 592 return this.die(message, status);
576 } 593 }
...@@ -586,6 +603,7 @@ Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message, status) { ...@@ -586,6 +603,7 @@ Casper.prototype.evaluateOrDie = function evaluateOrDie(fn, message, status) {
586 */ 603 */
587 Casper.prototype.exists = function exists(selector) { 604 Casper.prototype.exists = function exists(selector) {
588 "use strict"; 605 "use strict";
606 this.checkStarted();
589 return this.evaluate(function _evaluate(selector) { 607 return this.evaluate(function _evaluate(selector) {
590 return window.__utils__.exists(selector); 608 return window.__utils__.exists(selector);
591 }, { selector: selector }); 609 }, { selector: selector });
...@@ -612,9 +630,7 @@ Casper.prototype.exit = function exit(status) { ...@@ -612,9 +630,7 @@ Casper.prototype.exit = function exit(status) {
612 */ 630 */
613 Casper.prototype.fetchText = function fetchText(selector) { 631 Casper.prototype.fetchText = function fetchText(selector) {
614 "use strict"; 632 "use strict";
615 if (!this.started) { 633 this.checkStarted();
616 throw new CasperError("Casper not started, can't fetchText()");
617 }
618 return this.evaluate(function _evaluate(selector) { 634 return this.evaluate(function _evaluate(selector) {
619 return window.__utils__.fetchText(selector); 635 return window.__utils__.fetchText(selector);
620 }, { selector: selector }); 636 }, { selector: selector });
...@@ -629,6 +645,7 @@ Casper.prototype.fetchText = function fetchText(selector) { ...@@ -629,6 +645,7 @@ Casper.prototype.fetchText = function fetchText(selector) {
629 */ 645 */
630 Casper.prototype.fill = function fill(selector, vals, submit) { 646 Casper.prototype.fill = function fill(selector, vals, submit) {
631 "use strict"; 647 "use strict";
648 this.checkStarted();
632 submit = submit === true ? submit : false; 649 submit = submit === true ? submit : false;
633 if (!utils.isObject(vals)) { 650 if (!utils.isObject(vals)) {
634 throw new CasperError("Form values must be provided as an object"); 651 throw new CasperError("Form values must be provided as an object");
...@@ -643,15 +660,8 @@ Casper.prototype.fill = function fill(selector, vals, submit) { ...@@ -643,15 +660,8 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
643 if (!fillResults) { 660 if (!fillResults) {
644 throw new CasperError("Unable to fill form"); 661 throw new CasperError("Unable to fill form");
645 } else if (fillResults.errors.length > 0) { 662 } else if (fillResults.errors.length > 0) {
646 (function _each(self){ 663 throw new CasperError(f('Errors encountered while filling form: %s',
647 fillResults.errors.forEach(function _forEach(error) { 664 fillResults.errors.join('; ')));
648 throw new CasperError(error);
649 });
650 })(this);
651 if (submit) {
652 this.warn("Errors encountered while filling form; submission aborted");
653 submit = false;
654 }
655 } 665 }
656 // File uploads 666 // File uploads
657 if (fillResults.files && fillResults.files.length > 0) { 667 if (fillResults.files && fillResults.files.length > 0) {
...@@ -691,6 +701,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) { ...@@ -691,6 +701,7 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
691 */ 701 */
692 Casper.prototype.forward = function forward(then) { 702 Casper.prototype.forward = function forward(then) {
693 "use strict"; 703 "use strict";
704 this.checkStarted();
694 return this.then(function _step() { 705 return this.then(function _step() {
695 this.emit('forward'); 706 this.emit('forward');
696 this.evaluate(function _evaluate() { 707 this.evaluate(function _evaluate() {
...@@ -717,9 +728,7 @@ Casper.prototype.getColorizer = function getColorizer() { ...@@ -717,9 +728,7 @@ Casper.prototype.getColorizer = function getColorizer() {
717 */ 728 */
718 Casper.prototype.getPageContent = function getPageContent() { 729 Casper.prototype.getPageContent = function getPageContent() {
719 "use strict"; 730 "use strict";
720 if (!this.started) { 731 this.checkStarted();
721 throw new CasperError("Casper not started, can't getPageContent()");
722 }
723 var contentType = utils.getPropertyPath(this, 'currentResponse.contentType'); 732 var contentType = utils.getPropertyPath(this, 'currentResponse.contentType');
724 if (!utils.isString(contentType)) { 733 if (!utils.isString(contentType)) {
725 return this.page.content; 734 return this.page.content;
...@@ -742,6 +751,7 @@ Casper.prototype.getPageContent = function getPageContent() { ...@@ -742,6 +751,7 @@ Casper.prototype.getPageContent = function getPageContent() {
742 */ 751 */
743 Casper.prototype.getCurrentUrl = function getCurrentUrl() { 752 Casper.prototype.getCurrentUrl = function getCurrentUrl() {
744 "use strict"; 753 "use strict";
754 this.checkStarted();
745 var url = this.evaluate(function _evaluate() { 755 var url = this.evaluate(function _evaluate() {
746 return document.location.href; 756 return document.location.href;
747 }); 757 });
...@@ -762,6 +772,7 @@ Casper.prototype.getCurrentUrl = function getCurrentUrl() { ...@@ -762,6 +772,7 @@ Casper.prototype.getCurrentUrl = function getCurrentUrl() {
762 */ 772 */
763 Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = function getElementAttr(selector, attribute) { 773 Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = function getElementAttr(selector, attribute) {
764 "use strict"; 774 "use strict";
775 this.checkStarted();
765 return this.evaluate(function _evaluate(selector, attribute) { 776 return this.evaluate(function _evaluate(selector, attribute) {
766 return document.querySelector(selector).getAttribute(attribute); 777 return document.querySelector(selector).getAttribute(attribute);
767 }, { selector: selector, attribute: attribute }); 778 }, { selector: selector, attribute: attribute });
...@@ -775,6 +786,7 @@ Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = functio ...@@ -775,6 +786,7 @@ Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = functio
775 */ 786 */
776 Casper.prototype.getElementBounds = function getElementBounds(selector) { 787 Casper.prototype.getElementBounds = function getElementBounds(selector) {
777 "use strict"; 788 "use strict";
789 this.checkStarted();
778 if (!this.exists(selector)) { 790 if (!this.exists(selector)) {
779 throw new CasperError("No element matching selector found: " + selector); 791 throw new CasperError("No element matching selector found: " + selector);
780 } 792 }
...@@ -795,6 +807,7 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) { ...@@ -795,6 +807,7 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) {
795 */ 807 */
796 Casper.prototype.getElementsBounds = function getElementBounds(selector) { 808 Casper.prototype.getElementsBounds = function getElementBounds(selector) {
797 "use strict"; 809 "use strict";
810 this.checkStarted();
798 if (!this.exists(selector)) { 811 if (!this.exists(selector)) {
799 throw new CasperError("No element matching selector found: " + selector); 812 throw new CasperError("No element matching selector found: " + selector);
800 } 813 }
...@@ -811,6 +824,7 @@ Casper.prototype.getElementsBounds = function getElementBounds(selector) { ...@@ -811,6 +824,7 @@ Casper.prototype.getElementsBounds = function getElementBounds(selector) {
811 */ 824 */
812 Casper.prototype.getGlobal = function getGlobal(name) { 825 Casper.prototype.getGlobal = function getGlobal(name) {
813 "use strict"; 826 "use strict";
827 this.checkStarted();
814 var result = this.evaluate(function _evaluate(name) { 828 var result = this.evaluate(function _evaluate(name) {
815 var result = {}; 829 var result = {};
816 try { 830 try {
...@@ -843,9 +857,7 @@ Casper.prototype.getGlobal = function getGlobal(name) { ...@@ -843,9 +857,7 @@ Casper.prototype.getGlobal = function getGlobal(name) {
843 */ 857 */
844 Casper.prototype.getHTML = function getHTML(selector, outer) { 858 Casper.prototype.getHTML = function getHTML(selector, outer) {
845 "use strict"; 859 "use strict";
846 if (!this.started) { 860 this.checkStarted();
847 throw new CasperError("Casper not started, can't getHTML()");
848 }
849 if (!selector) { 861 if (!selector) {
850 return this.page.content; 862 return this.page.content;
851 } 863 }
...@@ -865,12 +877,43 @@ Casper.prototype.getHTML = function getHTML(selector, outer) { ...@@ -865,12 +877,43 @@ Casper.prototype.getHTML = function getHTML(selector, outer) {
865 */ 877 */
866 Casper.prototype.getTitle = function getTitle() { 878 Casper.prototype.getTitle = function getTitle() {
867 "use strict"; 879 "use strict";
880 this.checkStarted();
868 return this.evaluate(function _evaluate() { 881 return this.evaluate(function _evaluate() {
869 return document.title; 882 return document.title;
870 }); 883 });
871 }; 884 };
872 885
873 /** 886 /**
887 * Handles received HTTP resource.
888 *
889 * @param Object resource PhantomJS HTTP resource
890 */
891 Casper.prototype.handleReceivedResource = function(resource) {
892 "use strict";
893 if (resource.stage !== "end") {
894 return;
895 }
896 this.resources.push(resource);
897 if (resource.url !== this.requestUrl) {
898 return;
899 }
900 this.currentHTTPStatus = null;
901 this.currentResponse = undefined;
902 if (utils.isHTTPResource(resource)) {
903 this.currentResponse = resource;
904 this.currentHTTPStatus = resource.status;
905 this.emit('http.status.' + resource.status, resource);
906 if (utils.isObject(this.options.httpStatusHandlers) &&
907 resource.status in this.options.httpStatusHandlers &&
908 utils.isFunction(this.options.httpStatusHandlers[resource.status])) {
909 this.options.httpStatusHandlers[resource.status].call(this, this, resource);
910 }
911 }
912 this.currentUrl = resource.url;
913 this.emit('location.changed', resource.url);
914 };
915
916 /**
874 * Initializes PhantomJS error handler. 917 * Initializes PhantomJS error handler.
875 * 918 *
876 */ 919 */
...@@ -886,11 +929,39 @@ Casper.prototype.initErrorHandler = function initErrorHandler() { ...@@ -886,11 +929,39 @@ Casper.prototype.initErrorHandler = function initErrorHandler() {
886 }; 929 };
887 930
888 /** 931 /**
932 * Injects configured client scripts.
933 *
934 * @return Casper
935 */
936 Casper.prototype.injectClientScripts = function injectClientScripts() {
937 "use strict";
938 this.checkStarted();
939 if (!this.options.clientScripts) {
940 return;
941 }
942 if (utils.isString(this.options.clientScripts)) {
943 this.options.clientScripts = [this.options.clientScripts];
944 }
945 if (!utils.isArray(this.options.clientScripts)) {
946 throw new CasperError("The clientScripts option must be an array");
947 }
948 this.options.clientScripts.forEach(function _forEach(script) {
949 if (this.page.injectJs(script)) {
950 this.log(f('Automatically injected %s client side', script), "debug");
951 } else {
952 this.warn('Failed injecting %s client side', script);
953 }
954 });
955 return this;
956 };
957
958 /**
889 * Injects Client-side utilities in current page context. 959 * Injects Client-side utilities in current page context.
890 * 960 *
891 */ 961 */
892 Casper.prototype.injectClientUtils = function injectClientUtils() { 962 Casper.prototype.injectClientUtils = function injectClientUtils() {
893 "use strict"; 963 "use strict";
964 this.checkStarted();
894 var clientUtilsInjected = this.page.evaluate(function() { 965 var clientUtilsInjected = this.page.evaluate(function() {
895 return typeof window.__utils__ === "object"; 966 return typeof window.__utils__ === "object";
896 }); 967 });
...@@ -938,8 +1009,10 @@ Casper.prototype.log = function log(message, level, space) { ...@@ -938,8 +1009,10 @@ Casper.prototype.log = function log(message, level, space) {
938 if (level in this.logFormats && utils.isFunction(this.logFormats[level])) { 1009 if (level in this.logFormats && utils.isFunction(this.logFormats[level])) {
939 message = this.logFormats[level](message, level, space); 1010 message = this.logFormats[level](message, level, space);
940 } else { 1011 } else {
941 var levelStr = this.colorizer.colorize(f('[%s]', level), this.logStyles[level]); 1012 message = f('%s [%s] %s',
942 message = f('%s [%s] %s', levelStr, space, message); 1013 this.colorizer.colorize(f('[%s]', level), this.logStyles[level]),
1014 space,
1015 message);
943 } 1016 }
944 if (this.options.verbose) { 1017 if (this.options.verbose) {
945 this.echo(this.filter('log.message', message) || message); // direct output 1018 this.echo(this.filter('log.message', message) || message); // direct output
...@@ -961,6 +1034,7 @@ Casper.prototype.log = function log(message, level, space) { ...@@ -961,6 +1034,7 @@ Casper.prototype.log = function log(message, level, space) {
961 */ 1034 */
962 Casper.prototype.mouseEvent = function mouseEvent(type, selector) { 1035 Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
963 "use strict"; 1036 "use strict";
1037 this.checkStarted();
964 this.log("Mouse event '" + type + "' on selector: " + selector, "debug"); 1038 this.log("Mouse event '" + type + "' on selector: " + selector, "debug");
965 if (!this.exists(selector)) { 1039 if (!this.exists(selector)) {
966 throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector)); 1040 throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector));
...@@ -998,6 +1072,7 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) { ...@@ -998,6 +1072,7 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
998 */ 1072 */
999 Casper.prototype.open = function open(location, settings) { 1073 Casper.prototype.open = function open(location, settings) {
1000 "use strict"; 1074 "use strict";
1075 this.checkStarted();
1001 // settings validation 1076 // settings validation
1002 if (!settings) { 1077 if (!settings) {
1003 settings = { 1078 settings = {
...@@ -1040,9 +1115,6 @@ Casper.prototype.open = function open(location, settings) { ...@@ -1040,9 +1115,6 @@ Casper.prototype.open = function open(location, settings) {
1040 } 1115 }
1041 this.emit('open', this.requestUrl, settings); 1116 this.emit('open', this.requestUrl, settings);
1042 this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug"); 1117 this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug");
1043 if ('headers' in settings && phantom.version.minor < 6) {
1044 this.warn('Custom headers in outgoing requests are supported in PhantomJS >= 1.6');
1045 }
1046 this.page.openUrl(this.requestUrl, { 1118 this.page.openUrl(this.requestUrl, {
1047 operation: settings.method, 1119 operation: settings.method,
1048 data: settings.data, 1120 data: settings.data,
...@@ -1060,9 +1132,7 @@ Casper.prototype.open = function open(location, settings) { ...@@ -1060,9 +1132,7 @@ Casper.prototype.open = function open(location, settings) {
1060 */ 1132 */
1061 Casper.prototype.reload = function reload(then) { 1133 Casper.prototype.reload = function reload(then) {
1062 "use strict"; 1134 "use strict";
1063 if (!this.started) { 1135 this.checkStarted();
1064 throw new CasperError("Casper not started, can't reload()");
1065 }
1066 this.evaluate(function() { 1136 this.evaluate(function() {
1067 window.location.reload(); 1137 window.location.reload();
1068 }); 1138 });
...@@ -1097,6 +1167,7 @@ Casper.prototype.repeat = function repeat(times, then) { ...@@ -1097,6 +1167,7 @@ Casper.prototype.repeat = function repeat(times, then) {
1097 */ 1167 */
1098 Casper.prototype.resourceExists = function resourceExists(test) { 1168 Casper.prototype.resourceExists = function resourceExists(test) {
1099 "use strict"; 1169 "use strict";
1170 this.checkStarted();
1100 var testFn; 1171 var testFn;
1101 switch (utils.betterTypeOf(test)) { 1172 switch (utils.betterTypeOf(test)) {
1102 case "string": 1173 case "string":
...@@ -1128,6 +1199,7 @@ Casper.prototype.resourceExists = function resourceExists(test) { ...@@ -1128,6 +1199,7 @@ Casper.prototype.resourceExists = function resourceExists(test) {
1128 */ 1199 */
1129 Casper.prototype.run = function run(onComplete, time) { 1200 Casper.prototype.run = function run(onComplete, time) {
1130 "use strict"; 1201 "use strict";
1202 this.checkStarted();
1131 if (!this.steps || this.steps.length < 1) { 1203 if (!this.steps || this.steps.length < 1) {
1132 this.log("No steps defined, aborting", "error"); 1204 this.log("No steps defined, aborting", "error");
1133 return this; 1205 return this;
...@@ -1145,6 +1217,7 @@ Casper.prototype.run = function run(onComplete, time) { ...@@ -1145,6 +1217,7 @@ Casper.prototype.run = function run(onComplete, time) {
1145 */ 1217 */
1146 Casper.prototype.runStep = function runStep(step) { 1218 Casper.prototype.runStep = function runStep(step) {
1147 "use strict"; 1219 "use strict";
1220 this.checkStarted();
1148 var skipLog = utils.isObject(step.options) && step.options.skipLog === true; 1221 var skipLog = utils.isObject(step.options) && step.options.skipLog === true;
1149 var stepInfo = f("Step %d/%d", this.step, this.steps.length); 1222 var stepInfo = f("Step %d/%d", this.step, this.steps.length);
1150 var stepResult; 1223 var stepResult;
...@@ -1184,9 +1257,7 @@ Casper.prototype.runStep = function runStep(step) { ...@@ -1184,9 +1257,7 @@ Casper.prototype.runStep = function runStep(step) {
1184 */ 1257 */
1185 Casper.prototype.setHttpAuth = function setHttpAuth(username, password) { 1258 Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
1186 "use strict"; 1259 "use strict";
1187 if (!this.started) { 1260 this.checkStarted();
1188 throw new CasperError("Casper must be started in order to use the setHttpAuth() method");
1189 }
1190 if (!utils.isString(username) || !utils.isString(password)) { 1261 if (!utils.isString(username) || !utils.isString(password)) {
1191 throw new CasperError("Both username and password must be strings"); 1262 throw new CasperError("Both username and password must be strings");
1192 } 1263 }
...@@ -1258,6 +1329,7 @@ Casper.prototype.start = function start(location, then) { ...@@ -1258,6 +1329,7 @@ Casper.prototype.start = function start(location, then) {
1258 * @return Object 1329 * @return Object
1259 */ 1330 */
1260 Casper.prototype.status = function status(asString) { 1331 Casper.prototype.status = function status(asString) {
1332 "use strict";
1261 var properties = ['currentHTTPStatus', 'loadInProgress', 'navigationRequested', 1333 var properties = ['currentHTTPStatus', 'loadInProgress', 'navigationRequested',
1262 'options', 'pendingWait', 'requestUrl', 'started', 'step', 'url']; 1334 'options', 'pendingWait', 'requestUrl', 'started', 'step', 'url'];
1263 var currentStatus = {}; 1335 var currentStatus = {};
...@@ -1275,9 +1347,7 @@ Casper.prototype.status = function status(asString) { ...@@ -1275,9 +1347,7 @@ Casper.prototype.status = function status(asString) {
1275 */ 1347 */
1276 Casper.prototype.then = function then(step) { 1348 Casper.prototype.then = function then(step) {
1277 "use strict"; 1349 "use strict";
1278 if (!this.started) { 1350 this.checkStarted();
1279 throw new CasperError("Casper not started; please use Casper#start");
1280 }
1281 if (!utils.isFunction(step)) { 1351 if (!utils.isFunction(step)) {
1282 throw new CasperError("You can only define a step as a function"); 1352 throw new CasperError("You can only define a step as a function");
1283 } 1353 }
...@@ -1315,6 +1385,7 @@ Casper.prototype.then = function then(step) { ...@@ -1315,6 +1385,7 @@ Casper.prototype.then = function then(step) {
1315 */ 1385 */
1316 Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) { 1386 Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) {
1317 "use strict"; 1387 "use strict";
1388 this.checkStarted();
1318 if (arguments.length > 2) { 1389 if (arguments.length > 2) {
1319 this.emit("deprecated", "The thenClick() method does not process the fallbackToHref argument since 0.6"); 1390 this.emit("deprecated", "The thenClick() method does not process the fallbackToHref argument since 0.6");
1320 } 1391 }
...@@ -1335,6 +1406,7 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref) ...@@ -1335,6 +1406,7 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref)
1335 */ 1406 */
1336 Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) { 1407 Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
1337 "use strict"; 1408 "use strict";
1409 this.checkStarted();
1338 return this.then(function _step() { 1410 return this.then(function _step() {
1339 this.evaluate(fn, context); 1411 this.evaluate(fn, context);
1340 }); 1412 });
...@@ -1350,6 +1422,7 @@ Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) { ...@@ -1350,6 +1422,7 @@ Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
1350 */ 1422 */
1351 Casper.prototype.thenOpen = function thenOpen(location, settings, then) { 1423 Casper.prototype.thenOpen = function thenOpen(location, settings, then) {
1352 "use strict"; 1424 "use strict";
1425 this.checkStarted();
1353 if (!(settings && !utils.isFunction(settings))) { 1426 if (!(settings && !utils.isFunction(settings))) {
1354 then = settings; 1427 then = settings;
1355 settings = null; 1428 settings = null;
...@@ -1375,6 +1448,7 @@ Casper.prototype.thenOpen = function thenOpen(location, settings, then) { ...@@ -1375,6 +1448,7 @@ Casper.prototype.thenOpen = function thenOpen(location, settings, then) {
1375 */ 1448 */
1376 Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn, context) { 1449 Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn, context) {
1377 "use strict"; 1450 "use strict";
1451 this.checkStarted();
1378 return this.thenOpen(location).thenEvaluate(fn, context); 1452 return this.thenOpen(location).thenEvaluate(fn, context);
1379 }; 1453 };
1380 1454
...@@ -1384,6 +1458,7 @@ Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn ...@@ -1384,6 +1458,7 @@ Casper.prototype.thenOpenAndEvaluate = function thenOpenAndEvaluate(location, fn
1384 * @return String 1458 * @return String
1385 */ 1459 */
1386 Casper.prototype.toString = function toString() { 1460 Casper.prototype.toString = function toString() {
1461 "use strict";
1387 return '[object Casper], currently at ' + this.getCurrentUrl(); 1462 return '[object Casper], currently at ' + this.getCurrentUrl();
1388 }; 1463 };
1389 1464
...@@ -1395,9 +1470,7 @@ Casper.prototype.toString = function toString() { ...@@ -1395,9 +1470,7 @@ Casper.prototype.toString = function toString() {
1395 */ 1470 */
1396 Casper.prototype.userAgent = function userAgent(agent) { 1471 Casper.prototype.userAgent = function userAgent(agent) {
1397 "use strict"; 1472 "use strict";
1398 if (!this.started) { 1473 this.checkStarted();
1399 throw new CasperError("Casper not started, can't set userAgent");
1400 }
1401 this.options.pageSettings.userAgent = this.page.settings.userAgent = agent; 1474 this.options.pageSettings.userAgent = this.page.settings.userAgent = agent;
1402 return this; 1475 return this;
1403 }; 1476 };
...@@ -1411,9 +1484,7 @@ Casper.prototype.userAgent = function userAgent(agent) { ...@@ -1411,9 +1484,7 @@ Casper.prototype.userAgent = function userAgent(agent) {
1411 */ 1484 */
1412 Casper.prototype.viewport = function viewport(width, height) { 1485 Casper.prototype.viewport = function viewport(width, height) {
1413 "use strict"; 1486 "use strict";
1414 if (!this.started) { 1487 this.checkStarted();
1415 throw new CasperError("Casper must be started in order to set viewport at runtime");
1416 }
1417 if (!utils.isNumber(width) || !utils.isNumber(height) || width <= 0 || height <= 0) { 1488 if (!utils.isNumber(width) || !utils.isNumber(height) || width <= 0 || height <= 0) {
1418 throw new CasperError(f("Invalid viewport: %dx%d", width, height)); 1489 throw new CasperError(f("Invalid viewport: %dx%d", width, height));
1419 } 1490 }
...@@ -1435,6 +1506,7 @@ Casper.prototype.viewport = function viewport(width, height) { ...@@ -1435,6 +1506,7 @@ Casper.prototype.viewport = function viewport(width, height) {
1435 */ 1506 */
1436 Casper.prototype.visible = function visible(selector) { 1507 Casper.prototype.visible = function visible(selector) {
1437 "use strict"; 1508 "use strict";
1509 this.checkStarted();
1438 return this.evaluate(function _evaluate(selector) { 1510 return this.evaluate(function _evaluate(selector) {
1439 return window.__utils__.visible(selector); 1511 return window.__utils__.visible(selector);
1440 }, { selector: selector }); 1512 }, { selector: selector });
...@@ -1463,6 +1535,7 @@ Casper.prototype.warn = function warn(message) { ...@@ -1463,6 +1535,7 @@ Casper.prototype.warn = function warn(message) {
1463 */ 1535 */
1464 Casper.prototype.wait = function wait(timeout, then) { 1536 Casper.prototype.wait = function wait(timeout, then) {
1465 "use strict"; 1537 "use strict";
1538 this.checkStarted();
1466 timeout = ~~timeout; 1539 timeout = ~~timeout;
1467 if (timeout < 1) { 1540 if (timeout < 1) {
1468 this.die("wait() only accepts a positive integer > 0 as a timeout value"); 1541 this.die("wait() only accepts a positive integer > 0 as a timeout value");
...@@ -1505,6 +1578,7 @@ Casper.prototype.waitDone = function waitDone() { ...@@ -1505,6 +1578,7 @@ Casper.prototype.waitDone = function waitDone() {
1505 */ 1578 */
1506 Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { 1579 Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
1507 "use strict"; 1580 "use strict";
1581 this.checkStarted();
1508 timeout = timeout ? timeout : this.options.waitTimeout; 1582 timeout = timeout ? timeout : this.options.waitTimeout;
1509 if (!utils.isFunction(testFx)) { 1583 if (!utils.isFunction(testFx)) {
1510 this.die("waitFor() needs a test function"); 1584 this.die("waitFor() needs a test function");
...@@ -1553,6 +1627,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) { ...@@ -1553,6 +1627,7 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
1553 */ 1627 */
1554 Casper.prototype.waitForResource = function waitForResource(test, then, onTimeout, timeout) { 1628 Casper.prototype.waitForResource = function waitForResource(test, then, onTimeout, timeout) {
1555 "use strict"; 1629 "use strict";
1630 this.checkStarted();
1556 timeout = timeout ? timeout : this.options.waitTimeout; 1631 timeout = timeout ? timeout : this.options.waitTimeout;
1557 return this.waitFor(function _check() { 1632 return this.waitFor(function _check() {
1558 return this.resourceExists(test); 1633 return this.resourceExists(test);
...@@ -1571,6 +1646,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou ...@@ -1571,6 +1646,7 @@ Casper.prototype.waitForResource = function waitForResource(test, then, onTimeou
1571 */ 1646 */
1572 Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTimeout, timeout) { 1647 Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTimeout, timeout) {
1573 "use strict"; 1648 "use strict";
1649 this.checkStarted();
1574 timeout = timeout ? timeout : this.options.waitTimeout; 1650 timeout = timeout ? timeout : this.options.waitTimeout;
1575 return this.waitFor(function _check() { 1651 return this.waitFor(function _check() {
1576 return this.exists(selector); 1652 return this.exists(selector);
...@@ -1589,6 +1665,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi ...@@ -1589,6 +1665,7 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi
1589 */ 1665 */
1590 Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, onTimeout, timeout) { 1666 Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, onTimeout, timeout) {
1591 "use strict"; 1667 "use strict";
1668 this.checkStarted();
1592 timeout = timeout ? timeout : this.options.waitTimeout; 1669 timeout = timeout ? timeout : this.options.waitTimeout;
1593 return this.waitFor(function _check() { 1670 return this.waitFor(function _check() {
1594 return !this.exists(selector); 1671 return !this.exists(selector);
...@@ -1607,6 +1684,7 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then, ...@@ -1607,6 +1684,7 @@ Casper.prototype.waitWhileSelector = function waitWhileSelector(selector, then,
1607 */ 1684 */
1608 Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, onTimeout, timeout) { 1685 Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, onTimeout, timeout) {
1609 "use strict"; 1686 "use strict";
1687 this.checkStarted();
1610 timeout = timeout ? timeout : this.options.waitTimeout; 1688 timeout = timeout ? timeout : this.options.waitTimeout;
1611 return this.waitFor(function _check() { 1689 return this.waitFor(function _check() {
1612 return this.visible(selector); 1690 return this.visible(selector);
...@@ -1625,6 +1703,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on ...@@ -1625,6 +1703,7 @@ Casper.prototype.waitUntilVisible = function waitUntilVisible(selector, then, on
1625 */ 1703 */
1626 Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, onTimeout, timeout) { 1704 Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, onTimeout, timeout) {
1627 "use strict"; 1705 "use strict";
1706 this.checkStarted();
1628 timeout = timeout ? timeout : this.options.waitTimeout; 1707 timeout = timeout ? timeout : this.options.waitTimeout;
1629 return this.waitFor(function _check() { 1708 return this.waitFor(function _check() {
1630 return !this.visible(selector); 1709 return !this.visible(selector);
...@@ -1639,17 +1718,11 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on ...@@ -1639,17 +1718,11 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
1639 */ 1718 */
1640 Casper.prototype.zoom = function zoom(factor) { 1719 Casper.prototype.zoom = function zoom(factor) {
1641 "use strict"; 1720 "use strict";
1642 if (!this.started) { 1721 this.checkStarted();
1643 throw new CasperError("Casper has not been started, can't set zoom factor");
1644 }
1645 if (!utils.isNumber(factor) || factor <= 0) { 1722 if (!utils.isNumber(factor) || factor <= 0) {
1646 throw new CasperError("Invalid zoom factor: " + factor); 1723 throw new CasperError("Invalid zoom factor: " + factor);
1647 } 1724 }
1648 if ('zoomFactor' in this.page) { 1725 this.page.zoomFactor = factor;
1649 this.page.zoomFactor = factor;
1650 } else {
1651 this.warn("zoom() requires PhantomJS >= 1.6");
1652 }
1653 return this; 1726 return this;
1654 }; 1727 };
1655 1728
...@@ -1662,7 +1735,7 @@ Casper.prototype.zoom = function zoom(factor) { ...@@ -1662,7 +1735,7 @@ Casper.prototype.zoom = function zoom(factor) {
1662 */ 1735 */
1663 Casper.extend = function(proto) { 1736 Casper.extend = function(proto) {
1664 "use strict"; 1737 "use strict";
1665 this.warn('Casper.extend() has been deprecated since 0.6; check the docs'); 1738 this.emit("deprecated", "Casper.extend() has been deprecated since 0.6; check the docs")
1666 if (!utils.isObject(proto)) { 1739 if (!utils.isObject(proto)) {
1667 throw new CasperError("extends() only accept objects as prototypes"); 1740 throw new CasperError("extends() only accept objects as prototypes");
1668 } 1741 }
...@@ -1731,21 +1804,7 @@ function createPage(casper) { ...@@ -1731,21 +1804,7 @@ function createPage(casper) {
1731 casper.options.onLoadError.call(casper, casper, casper.requestUrl, status); 1804 casper.options.onLoadError.call(casper, casper, casper.requestUrl, status);
1732 } 1805 }
1733 } 1806 }
1734 if (casper.options.clientScripts) { 1807 casper.injectClientScripts();
1735 if (utils.isString(casper.options.clientScripts)) {
1736 casper.options.clientScripts = [casper.options.clientScripts];
1737 }
1738 if (!utils.isArray(casper.options.clientScripts)) {
1739 throw new CasperError("The clientScripts option must be an array");
1740 }
1741 casper.options.clientScripts.forEach(function _forEach(script) {
1742 if (casper.page.injectJs(script)) {
1743 casper.log(f('Automatically injected %s client side', script), "debug");
1744 } else {
1745 casper.warn('Failed injecting %s client side', script);
1746 }
1747 });
1748 }
1749 // Client-side utils injection 1808 // Client-side utils injection
1750 casper.injectClientUtils(); 1809 casper.injectClientUtils();
1751 // history 1810 // history
...@@ -1765,34 +1824,12 @@ function createPage(casper) { ...@@ -1765,34 +1824,12 @@ function createPage(casper) {
1765 return casper.filter('page.prompt', message, value); 1824 return casper.filter('page.prompt', message, value);
1766 }; 1825 };
1767 page.onResourceReceived = function onResourceReceived(resource) { 1826 page.onResourceReceived = function onResourceReceived(resource) {
1768 if (utils.isHTTPResource(resource)) { 1827 http.augmentResponse(resource);
1769 require('http').augmentResponse(resource);
1770 } else {
1771 casper.log(f('Non-HTTP resource received from %s', resource.url), "debug");
1772 }
1773 casper.emit('resource.received', resource); 1828 casper.emit('resource.received', resource);
1774 if (utils.isFunction(casper.options.onResourceReceived)) { 1829 if (utils.isFunction(casper.options.onResourceReceived)) {
1775 casper.options.onResourceReceived.call(casper, casper, resource); 1830 casper.options.onResourceReceived.call(casper, casper, resource);
1776 } 1831 }
1777 if (resource.stage === "end") { 1832 casper.handleReceivedResource(resource);
1778 casper.resources.push(resource);
1779 }
1780 if (resource.url === casper.requestUrl && resource.stage === "end") {
1781 casper.currentHTTPStatus = null;
1782 casper.currentResponse = undefined;
1783 if (utils.isHTTPResource(resource)) {
1784 casper.currentResponse = resource;
1785 casper.currentHTTPStatus = resource.status;
1786 casper.emit('http.status.' + resource.status, resource);
1787 if (utils.isObject(casper.options.httpStatusHandlers) &&
1788 resource.status in casper.options.httpStatusHandlers &&
1789 utils.isFunction(casper.options.httpStatusHandlers[resource.status])) {
1790 casper.options.httpStatusHandlers[resource.status].call(casper, casper, resource);
1791 }
1792 }
1793 casper.currentUrl = resource.url;
1794 casper.emit('location.changed', resource.url);
1795 }
1796 }; 1833 };
1797 page.onResourceRequested = function onResourceRequested(request) { 1834 page.onResourceRequested = function onResourceRequested(request) {
1798 casper.emit('resource.requested', request); 1835 casper.emit('resource.requested', request);
......
...@@ -75,6 +75,7 @@ ...@@ -75,6 +75,7 @@
75 * @return string 75 * @return string
76 */ 76 */
77 this.decode = function decode(str) { 77 this.decode = function decode(str) {
78 /*jshint maxstatements:30 maxcomplexity:30 */
78 var c1, c2, c3, c4, i = 0, len = str.length, out = ""; 79 var c1, c2, c3, c4, i = 0, len = str.length, out = "";
79 while (i < len) { 80 while (i < len) {
80 do { 81 do {
...@@ -123,6 +124,7 @@ ...@@ -123,6 +124,7 @@
123 * @return string 124 * @return string
124 */ 125 */
125 this.encode = function encode(str) { 126 this.encode = function encode(str) {
127 /*jshint maxstatements:30 */
126 var out = "", i = 0, len = str.length, c1, c2, c3; 128 var out = "", i = 0, len = str.length, c1, c2, c3;
127 while (i < len) { 129 while (i < len) {
128 c1 = str.charCodeAt(i++) & 0xff; 130 c1 = str.charCodeAt(i++) & 0xff;
...@@ -183,9 +185,9 @@ ...@@ -183,9 +185,9 @@
183 /** 185 /**
184 * Fills a form with provided field values, and optionnaly submits it. 186 * Fills a form with provided field values, and optionnaly submits it.
185 * 187 *
186 * @param HTMLElement|String form A form element, or a CSS3 selector to a form element 188 * @param HTMLElement|String form A form element, or a CSS3 selector to a form element
187 * @param Object vals Field values 189 * @param Object vals Field values
188 * @return Object An object containing setting result for each field, including file uploads 190 * @return Object An object containing setting result for each field, including file uploads
189 */ 191 */
190 this.fill = function fill(form, vals) { 192 this.fill = function fill(form, vals) {
191 var out = { 193 var out = {
...@@ -295,37 +297,14 @@ ...@@ -295,37 +297,14 @@
295 * Retrieves string contents from a binary file behind an url. Silently 297 * Retrieves string contents from a binary file behind an url. Silently
296 * fails but log errors. 298 * fails but log errors.
297 * 299 *
298 * @param String url 300 * @param String url Url.
299 * @param String method 301 * @param String method HTTP method.
300 * @param Object data 302 * @param Object data Request parameters.
301 * @return string 303 * @return String
302 */ 304 */
303 this.getBinary = function getBinary(url, method, data) { 305 this.getBinary = function getBinary(url, method, data) {
304 try { 306 try {
305 var xhr = new XMLHttpRequest(), dataString = ""; 307 return this.sendAJAX(url, method, data, false);
306 if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) {
307 method = "GET";
308 } else {
309 method = method.toUpperCase();
310 }
311 xhr.open(method, url, false);
312 this.log("getBinary(): Using HTTP method: '" + method + "'", "debug");
313 xhr.overrideMimeType("text/plain; charset=x-user-defined");
314 if (method === "POST") {
315 if (typeof data === "object") {
316 var dataList = [];
317 for (var k in data) {
318 dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString()));
319 }
320 dataString = dataList.join('&');
321 this.log("getBinary(): Using request data: '" + dataString + "'", "debug");
322 } else if (typeof data === "string") {
323 dataString = data;
324 }
325 xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
326 }
327 xhr.send(method === "POST" ? dataString : null);
328 return xhr.responseText;
329 } catch (e) { 308 } catch (e) {
330 if (e.name === "NETWORK_ERR" && e.code === 101) { 309 if (e.name === "NETWORK_ERR" && e.code === 101) {
331 this.log("getBinary(): Unfortunately, casperjs cannot make cross domain ajax requests", "warning"); 310 this.log("getBinary(): Unfortunately, casperjs cannot make cross domain ajax requests", "warning");
...@@ -529,6 +508,42 @@ ...@@ -529,6 +508,42 @@
529 }; 508 };
530 509
531 /** 510 /**
511 * Performs an AJAX request.
512 *
513 * @param String url Url.
514 * @param String method HTTP method.
515 * @param Object data Request parameters.
516 * @param Boolean async Asynchroneous request? (default: false)
517 * @return String Response text.
518 */
519 this.sendAJAX = function sendAJAX(url, method, data, async) {
520 var xhr = new XMLHttpRequest(), dataString = "";
521 if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) {
522 method = "GET";
523 } else {
524 method = method.toUpperCase();
525 }
526 xhr.open(method, url, !!async);
527 this.log("getBinary(): Using HTTP method: '" + method + "'", "debug");
528 xhr.overrideMimeType("text/plain; charset=x-user-defined");
529 if (method === "POST") {
530 if (typeof data === "object") {
531 var dataList = [];
532 for (var k in data) {
533 dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString()));
534 }
535 dataString = dataList.join('&');
536 this.log("sendAJAX(): Using request data: '" + dataString + "'", "debug");
537 } else if (typeof data === "string") {
538 dataString = data;
539 }
540 xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
541 }
542 xhr.send(method === "POST" ? dataString : null);
543 return xhr.responseText;
544 };
545
546 /**
532 * Sets a field (or a set of fields) value. Fails silently, but log 547 * Sets a field (or a set of fields) value. Fails silently, but log
533 * error messages. 548 * error messages.
534 * 549 *
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
28 * 28 *
29 */ 29 */
30 30
31 var utils = require('utils');
32
31 /* 33 /*
32 * Building an Array subclass 34 * Building an Array subclass
33 */ 35 */
...@@ -59,7 +61,8 @@ responseHeaders.prototype.get = function get(name){ ...@@ -59,7 +61,8 @@ responseHeaders.prototype.get = function get(name){
59 * @param mixed response Phantom response or undefined (generally with local files) 61 * @param mixed response Phantom response or undefined (generally with local files)
60 */ 62 */
61 exports.augmentResponse = function(response) { 63 exports.augmentResponse = function(response) {
62 if (response === undefined) { 64 "use strict";
65 if (!utils.isHTTPResource(response)) {
63 return; 66 return;
64 } 67 }
65 response.headers.__proto__ = responseHeaders.prototype; 68 response.headers.__proto__ = responseHeaders.prototype;
......
...@@ -53,6 +53,8 @@ var Tester = function Tester(casper, options) { ...@@ -53,6 +53,8 @@ var Tester = function Tester(casper, options) {
53 throw new CasperError("Tester needs a Casper instance"); 53 throw new CasperError("Tester needs a Casper instance");
54 } 54 }
55 55
56 this.casper = casper;
57
56 this.currentTestFile = null; 58 this.currentTestFile = null;
57 this.currentSuiteNum = 0; 59 this.currentSuiteNum = 0;
58 this.exporter = require('xunit').create(); 60 this.exporter = require('xunit').create();
...@@ -132,7 +134,7 @@ var Tester = function Tester(casper, options) { ...@@ -132,7 +134,7 @@ var Tester = function Tester(casper, options) {
132 } catch (e) { 134 } catch (e) {
133 try { 135 try {
134 comment += utils.serialize(failure.values[name].toString()); 136 comment += utils.serialize(failure.values[name].toString());
135 } catch (e) { 137 } catch (e2) {
136 comment += '(unserializable value)'; 138 comment += '(unserializable value)';
137 } 139 }
138 } 140 }
...@@ -140,789 +142,829 @@ var Tester = function Tester(casper, options) { ...@@ -140,789 +142,829 @@ var Tester = function Tester(casper, options) {
140 } 142 }
141 } 143 }
142 }); 144 });
145 };
143 146
144 // methods 147 // Tester class is an EventEmitter
145 /** 148 utils.inherits(Tester, events.EventEmitter);
146 * Asserts that a condition strictly resolves to true. Also returns an 149 exports.Tester = Tester;
147 * "assertion object" containing useful informations about the test case
148 * results.
149 *
150 * This method is also used as the base one used for all other `assert*`
151 * family methods; supplementary informations are then passed using the
152 * `context` argument.
153 *
154 * @param Boolean subject The condition to test
155 * @param String message Test description
156 * @param Object|null context Assertion context object (Optional)
157 * @return Object An assertion result object
158 */
159 this.assert = this.assertTrue = function assert(subject, message, context) {
160 return this.processAssertionResult(utils.mergeObjects({
161 success: subject === true,
162 type: "assert",
163 standard: "Subject is strictly true",
164 message: message,
165 file: this.currentTestFile,
166 values: {
167 subject: utils.getPropertyPath(context, 'values.subject') || subject
168 }
169 }, context || {}));
170 };
171 150
172 /** 151 /**
173 * Asserts that two values are strictly equals. 152 * Asserts that a condition strictly resolves to true. Also returns an
174 * 153 * "assertion object" containing useful informations about the test case
175 * @param Mixed subject The value to test 154 * results.
176 * @param Mixed expected The expected value 155 *
177 * @param String message Test description (Optional) 156 * This method is also used as the base one used for all other `assert*`
178 * @return Object An assertion result object 157 * family methods; supplementary informations are then passed using the
179 */ 158 * `context` argument.
180 this.assertEquals = this.assertEqual = function assertEquals(subject, expected, message) { 159 *
181 return this.assert(this.testEquals(subject, expected), message, { 160 * @param Boolean subject The condition to test
182 type: "assertEquals", 161 * @param String message Test description
183 standard: "Subject equals the expected value", 162 * @param Object|null context Assertion context object (Optional)
184 values: { 163 * @return Object An assertion result object
185 subject: subject, 164 */
186 expected: expected 165 Tester.prototype.assert = Tester.prototype.assertTrue = function assert(subject, message, context) {
187 } 166 "use strict";
188 }); 167 return this.processAssertionResult(utils.mergeObjects({
189 }; 168 success: subject === true,
169 type: "assert",
170 standard: "Subject is strictly true",
171 message: message,
172 file: this.currentTestFile,
173 values: {
174 subject: utils.getPropertyPath(context, 'values.subject') || subject
175 }
176 }, context || {}));
177 };
190 178
191 /** 179 /**
192 * Asserts that two values are strictly not equals. 180 * Asserts that two values are strictly equals.
193 * 181 *
194 * @param Mixed subject The value to test 182 * @param Mixed subject The value to test
195 * @param Mixed expected The unwanted value 183 * @param Mixed expected The expected value
196 * @param String|null message Test description (Optional) 184 * @param String message Test description (Optional)
197 * @return Object An assertion result object 185 * @return Object An assertion result object
198 */ 186 */
199 this.assertNotEquals = function assertNotEquals(subject, shouldnt, message) { 187 Tester.prototype.assertEquals = Tester.prototype.assertEqual = function assertEquals(subject, expected, message) {
200 return this.assert(!this.testEquals(subject, shouldnt), message, { 188 "use strict";
201 type: "assertNotEquals", 189 return this.assert(this.testEquals(subject, expected), message, {
202 standard: "Subject doesn't equal what it shouldn't be", 190 type: "assertEquals",
203 values: { 191 standard: "Subject equals the expected value",
204 subject: subject, 192 values: {
205 shouldnt: shouldnt 193 subject: subject,
206 } 194 expected: expected
207 }); 195 }
208 }; 196 });
197 };
209 198
210 /** 199 /**
211 * Asserts that a code evaluation in remote DOM resolves to true. 200 * Asserts that two values are strictly not equals.
212 * 201 *
213 * @param Function fn A function to be evaluated in remote DOM 202 * @param Mixed subject The value to test
214 * @param String message Test description 203 * @param Mixed expected The unwanted value
215 * @param Object params Object containing the parameters to inject into the function (optional) 204 * @param String|null message Test description (Optional)
216 * @return Object An assertion result object 205 * @return Object An assertion result object
217 */ 206 */
218 this.assertEval = this.assertEvaluate = function assertEval(fn, message, params) { 207 Tester.prototype.assertNotEquals = function assertNotEquals(subject, shouldnt, message) {
219 return this.assert(casper.evaluate(fn, params), message, { 208 "use strict";
220 type: "assertEval", 209 return this.assert(!this.testEquals(subject, shouldnt), message, {
221 standard: "Evaluated function returns true", 210 type: "assertNotEquals",
222 values: { 211 standard: "Subject doesn't equal what it shouldn't be",
223 fn: fn, 212 values: {
224 params: params 213 subject: subject,
225 } 214 shouldnt: shouldnt
226 }); 215 }
227 }; 216 });
217 };
228 218
229 /** 219 /**
230 * Asserts that the result of a code evaluation in remote DOM equals 220 * Asserts that a code evaluation in remote DOM resolves to true.
231 * an expected value. 221 *
232 * 222 * @param Function fn A function to be evaluated in remote DOM
233 * @param Function fn The function to be evaluated in remote DOM 223 * @param String message Test description
234 * @param Boolean expected The expected value 224 * @param Object params Object containing the parameters to inject into the function (optional)
235 * @param String|null message Test description 225 * @return Object An assertion result object
236 * @param Object|null params Object containing the parameters to inject into the function (optional) 226 */
237 * @return Object An assertion result object 227 Tester.prototype.assertEval = Tester.prototype.assertEvaluate = function assertEval(fn, message, params) {
238 */ 228 "use strict";
239 this.assertEvalEquals = this.assertEvalEqual = function assertEvalEquals(fn, expected, message, params) { 229 return this.assert(this.casper.evaluate(fn, params), message, {
240 var subject = casper.evaluate(fn, params); 230 type: "assertEval",
241 return this.assert(this.testEquals(subject, expected), message, { 231 standard: "Evaluated function returns true",
242 type: "assertEvalEquals", 232 values: {
243 standard: "Evaluated function returns the expected value", 233 fn: fn,
244 values: { 234 params: params
245 fn: fn, 235 }
246 params: params, 236 });
247 subject: subject, 237 };
248 expected: expected
249 }
250 });
251 };
252 238
253 /** 239 /**
254 * Asserts that a given input field has the provided value. 240 * Asserts that the result of a code evaluation in remote DOM equals
255 * 241 * an expected value.
256 * @param String input_name The name attribute of the input element 242 *
257 * @param String expected_value The expected value of the input element 243 * @param Function fn The function to be evaluated in remote DOM
258 * @param String message Test description 244 * @param Boolean expected The expected value
259 * @return Object An assertion result object 245 * @param String|null message Test description
260 */ 246 * @param Object|null params Object containing the parameters to inject into the function (optional)
261 this.assertField = function assertField(input_name, expected_value, message) { 247 * @return Object An assertion result object
262 var actual_value = casper.evaluate(function(input_name) { 248 */
263 var input = document.querySelector('input[name="' + input_name + '"]'); 249 Tester.prototype.assertEvalEquals = Tester.prototype.assertEvalEqual = function assertEvalEquals(fn, expected, message, params) {
264 return input ? input.value : null; 250 "use strict";
265 }, { input_name: input_name }); 251 var subject = this.casper.evaluate(fn, params);
266 return this.assert(this.testEquals(actual_value, expected_value), message, { 252 return this.assert(this.testEquals(subject, expected), message, {
267 type: 'assertField', 253 type: "assertEvalEquals",
268 standard: f('"%s" input field has the value "%s"', input_name, expected_value), 254 standard: "Evaluated function returns the expected value",
269 values: { 255 values: {
270 input_name: input_name, 256 fn: fn,
271 actual_value: actual_value, 257 params: params,
272 expected_value: expected_value 258 subject: subject,
273 } 259 expected: expected
274 }); 260 }
275 }; 261 });
262 };
276 263
277 /** 264 /**
278 * Asserts that an element matching the provided selector expression exists in 265 * Asserts that a given input field has the provided value.
279 * remote DOM. 266 *
280 * 267 * @param String input_name The name attribute of the input element
281 * @param String selector Selector expression 268 * @param String expected_value The expected value of the input element
282 * @param String message Test description 269 * @param String message Test description
283 * @return Object An assertion result object 270 * @return Object An assertion result object
284 */ 271 */
285 this.assertExists = this.assertExist = this.assertSelectorExists = this.assertSelectorExist = function assertExists(selector, message) { 272 Tester.prototype.assertField = function assertField(input_name, expected_value, message) {
286 return this.assert(casper.exists(selector), message, { 273 "use strict";
287 type: "assertExists", 274 var actual_value = this.casper.evaluate(function(input_name) {
288 standard: f("Found an element matching: %s", selector), 275 var input = document.querySelector('input[name="' + input_name + '"]');
289 values: { 276 return input ? input.value : null;
290 selector: selector 277 }, { input_name: input_name });
291 } 278 return this.assert(this.testEquals(actual_value, expected_value), message, {
292 }); 279 type: 'assertField',
293 }; 280 standard: f('"%s" input field has the value "%s"', input_name, expected_value),
281 values: {
282 input_name: input_name,
283 actual_value: actual_value,
284 expected_value: expected_value
285 }
286 });
287 };
294 288
295 /** 289 /**
296 * Asserts that an element matching the provided selector expression does not 290 * Asserts that an element matching the provided selector expression exists in
297 * exists in remote DOM. 291 * remote DOM.
298 * 292 *
299 * @param String selector Selector expression 293 * @param String selector Selector expression
300 * @param String message Test description 294 * @param String message Test description
301 * @return Object An assertion result object 295 * @return Object An assertion result object
302 */ 296 */
303 this.assertDoesntExist = this.assertNotExists = function assertDoesntExist(selector, message) { 297 Tester.prototype.assertExists = Tester.prototype.assertExist = this.assertSelectorExists = Tester.prototype.assertSelectorExist = function assertExists(selector, message) {
304 return this.assert(!casper.exists(selector), message, { 298 "use strict";
305 type: "assertDoesntExist", 299 return this.assert(this.casper.exists(selector), message, {
306 standard: f("No element found matching selector: %s", selector), 300 type: "assertExists",
307 values: { 301 standard: f("Found an element matching: %s", selector),
308 selector: selector 302 values: {
309 } 303 selector: selector
310 }); 304 }
311 }; 305 });
306 };
312 307
313 /** 308 /**
314 * Asserts that current HTTP status is the one passed as argument. 309 * Asserts that an element matching the provided selector expression does not
315 * 310 * exists in remote DOM.
316 * @param Number status HTTP status code 311 *
317 * @param String message Test description 312 * @param String selector Selector expression
318 * @return Object An assertion result object 313 * @param String message Test description
319 */ 314 * @return Object An assertion result object
320 this.assertHttpStatus = function assertHttpStatus(status, message) { 315 */
321 var currentHTTPStatus = casper.currentHTTPStatus; 316 Tester.prototype.assertDoesntExist = Tester.prototype.assertNotExists = function assertDoesntExist(selector, message) {
322 return this.assert(this.testEquals(casper.currentHTTPStatus, status), message, { 317 "use strict";
323 type: "assertHttpStatus", 318 return this.assert(!this.casper.exists(selector), message, {
324 standard: f("HTTP status code is: %s", status), 319 type: "assertDoesntExist",
325 values: { 320 standard: f("No element found matching selector: %s", selector),
326 current: currentHTTPStatus, 321 values: {
327 expected: status 322 selector: selector
328 } 323 }
329 }); 324 });
330 }; 325 };
331 326
332 /** 327 /**
333 * Asserts that a provided string matches a provided RegExp pattern. 328 * Asserts that current HTTP status is the one passed as argument.
334 * 329 *
335 * @param String subject The string to test 330 * @param Number status HTTP status code
336 * @param RegExp pattern A RegExp object instance 331 * @param String message Test description
337 * @param String message Test description 332 * @return Object An assertion result object
338 * @return Object An assertion result object 333 */
339 */ 334 Tester.prototype.assertHttpStatus = function assertHttpStatus(status, message) {
340 this.assertMatch = this.assertMatches = function assertMatch(subject, pattern, message) { 335 "use strict";
341 return this.assert(pattern.test(subject), message, { 336 var currentHTTPStatus = this.casper.currentHTTPStatus;
342 type: "assertMatch", 337 return this.assert(this.testEquals(this.casper.currentHTTPStatus, status), message, {
343 standard: "Subject matches the provided pattern", 338 type: "assertHttpStatus",
344 values: { 339 standard: f("HTTP status code is: %s", status),
345 subject: subject, 340 values: {
346 pattern: pattern.toString() 341 current: currentHTTPStatus,
347 } 342 expected: status
348 }); 343 }
349 }; 344 });
345 };
350 346
351 /** 347 /**
352 * Asserts a condition resolves to false. 348 * Asserts that a provided string matches a provided RegExp pattern.
353 * 349 *
354 * @param Boolean condition The condition to test 350 * @param String subject The string to test
355 * @param String message Test description 351 * @param RegExp pattern A RegExp object instance
356 * @return Object An assertion result object 352 * @param String message Test description
357 */ 353 * @return Object An assertion result object
358 this.assertNot = function assertNot(condition, message) { 354 */
359 return this.assert(!condition, message, { 355 Tester.prototype.assertMatch = Tester.prototype.assertMatches = function assertMatch(subject, pattern, message) {
360 type: "assertNot", 356 "use strict";
361 standard: "Subject is falsy", 357 return this.assert(pattern.test(subject), message, {
362 values: { 358 type: "assertMatch",
363 condition: condition 359 standard: "Subject matches the provided pattern",
364 } 360 values: {
365 }); 361 subject: subject,
366 }; 362 pattern: pattern.toString()
363 }
364 });
365 };
367 366
368 /** 367 /**
369 * Asserts that a selector expression is not currently visible. 368 * Asserts a condition resolves to false.
370 * 369 *
371 * @param String expected selector expression 370 * @param Boolean condition The condition to test
372 * @param String message Test description 371 * @param String message Test description
373 * @return Object An assertion result object 372 * @return Object An assertion result object
374 */ 373 */
375 this.assertNotVisible = this.assertInvisible = function assertNotVisible(selector, message) { 374 Tester.prototype.assertNot = function assertNot(condition, message) {
376 return this.assert(!casper.visible(selector), message, { 375 "use strict";
377 type: "assertVisible", 376 return this.assert(!condition, message, {
378 standard: "Selector is not visible", 377 type: "assertNot",
379 values: { 378 standard: "Subject is falsy",
380 selector: selector 379 values: {
381 } 380 condition: condition
382 }); 381 }
383 }; 382 });
383 };
384 384
385 /** 385 /**
386 * Asserts that the provided function called with the given parameters 386 * Asserts that a selector expression is not currently visible.
387 * will raise an exception. 387 *
388 * 388 * @param String expected selector expression
389 * @param Function fn The function to test 389 * @param String message Test description
390 * @param Array args The arguments to pass to the function 390 * @return Object An assertion result object
391 * @param String message Test description 391 */
392 * @return Object An assertion result object 392 Tester.prototype.assertNotVisible = Tester.prototype.assertInvisible = function assertNotVisible(selector, message) {
393 */ 393 "use strict";
394 this.assertRaises = this.assertRaise = this.assertThrows = function assertRaises(fn, args, message) { 394 return this.assert(!this.casper.visible(selector), message, {
395 var context = { 395 type: "assertVisible",
396 type: "assertRaises", 396 standard: "Selector is not visible",
397 standard: "Function raises an error" 397 values: {
398 }; 398 selector: selector
399 try {
400 fn.apply(null, args);
401 this.assert(false, message, context);
402 } catch (error) {
403 this.assert(true, message, utils.mergeObjects(context, {
404 values: {
405 error: error
406 }
407 }));
408 } 399 }
409 }; 400 });
401 };
410 402
411 /** 403 /**
412 * Asserts that the current page has a resource that matches the provided test 404 * Asserts that the provided function called with the given parameters
413 * 405 * will raise an exception.
414 * @param Function/String test A test function that is called with every response 406 *
415 * @param String message Test description 407 * @param Function fn The function to test
416 * @return Object An assertion result object 408 * @param Array args The arguments to pass to the function
417 */ 409 * @param String message Test description
418 this.assertResourceExists = this.assertResourceExist = function assertResourceExists(test, message) { 410 * @return Object An assertion result object
419 return this.assert(casper.resourceExists(test), message, { 411 */
420 type: "assertResourceExists", 412 Tester.prototype.assertRaises = Tester.prototype.assertRaise = this.assertThrows = function assertRaises(fn, args, message) {
421 standard: "Expected resource has been found", 413 "use strict";
414 var context = {
415 type: "assertRaises",
416 standard: "Function raises an error"
417 };
418 try {
419 fn.apply(null, args);
420 this.assert(false, message, context);
421 } catch (error) {
422 this.assert(true, message, utils.mergeObjects(context, {
422 values: { 423 values: {
423 test: test 424 error: error
424 } 425 }
425 }); 426 }));
426 }; 427 }
428 };
427 429
428 /** 430 /**
429 * Asserts that given text exists in the document body. 431 * Asserts that the current page has a resource that matches the provided test
430 * 432 *
431 * @param String text Text to be found 433 * @param Function/String test A test function that is called with every response
432 * @param String message Test description 434 * @param String message Test description
433 * @return Object An assertion result object 435 * @return Object An assertion result object
434 */ 436 */
435 this.assertTextExists = this.assertTextExist = function assertTextExists(text, message) { 437 Tester.prototype.assertResourceExists = Tester.prototype.assertResourceExist = function assertResourceExists(test, message) {
436 var textFound = (casper.evaluate(function _evaluate() { 438 "use strict";
437 return document.body.textContent || document.body.innerText; 439 return this.assert(this.casper.resourceExists(test), message, {
438 }).indexOf(text) !== -1); 440 type: "assertResourceExists",
439 return this.assert(textFound, message, { 441 standard: "Expected resource has been found",
440 type: "assertTextExists", 442 values: {
441 standard: "Found expected text within the document body", 443 test: test
442 values: { 444 }
443 text: text 445 });
444 } 446 };
445 });
446 };
447 447
448 /** 448 /**
449 * Asserts that given text exists in the provided selector. 449 * Asserts that given text exists in the document body.
450 * 450 *
451 * @param String selector Selector expression 451 * @param String text Text to be found
452 * @param String text Text to be found 452 * @param String message Test description
453 * @param String message Test description 453 * @return Object An assertion result object
454 * @return Object An assertion result object 454 */
455 */ 455 Tester.prototype.assertTextExists = Tester.prototype.assertTextExist = function assertTextExists(text, message) {
456 this.assertSelectorHasText = function assertSelectorHasText(selector, text, message) { 456 "use strict";
457 var textFound = casper.fetchText(selector).indexOf(text) !== -1; 457 var textFound = (this.casper.evaluate(function _evaluate() {
458 return this.assert(textFound, message, { 458 return document.body.textContent || document.body.innerText;
459 type: "assertTextInSelector", 459 }).indexOf(text) !== -1);
460 standard: f('Found "%s" within the selector "%s"', text, selector), 460 return this.assert(textFound, message, {
461 values: { 461 type: "assertTextExists",
462 selector: selector, 462 standard: "Found expected text within the document body",
463 text: text 463 values: {
464 } 464 text: text
465 }); 465 }
466 }; 466 });
467 };
467 468
468 /** 469 /**
469 * Asserts that given text does not exist in the provided selector. 470 * Asserts that given text exists in the provided selector.
470 * 471 *
471 * @param String selector Selector expression 472 * @param String selector Selector expression
472 * @param String text Text not to be found 473 * @param String text Text to be found
473 * @param String message Test description 474 * @param String message Test description
474 * @return Object An assertion result object 475 * @return Object An assertion result object
475 */ 476 */
476 this.assertSelectorDoesntHaveText = function assertSelectorDoesntHaveText(selector, text, message) { 477 Tester.prototype.assertSelectorHasText = function assertSelectorHasText(selector, text, message) {
477 var textFound = casper.fetchText(selector).indexOf(text) === -1; 478 "use strict";
478 return this.assert(textFound, message, { 479 var textFound = this.casper.fetchText(selector).indexOf(text) !== -1;
479 type: "assertNoTextInSelector", 480 return this.assert(textFound, message, {
480 standard: f('Did not find "%s" within the selector "%s"', text, selector), 481 type: "assertTextInSelector",
481 values: { 482 standard: f('Found "%s" within the selector "%s"', text, selector),
482 selector: selector, 483 values: {
483 text: text 484 selector: selector,
484 } 485 text: text
485 }); 486 }
486 }; 487 });
488 };
487 489
488 /** 490 /**
489 * Asserts that title of the remote page equals to the expected one. 491 * Asserts that given text does not exist in the provided selector.
490 * 492 *
491 * @param String expected The expected title string 493 * @param String selector Selector expression
492 * @param String message Test description 494 * @param String text Text not to be found
493 * @return Object An assertion result object 495 * @param String message Test description
494 */ 496 * @return Object An assertion result object
495 this.assertTitle = function assertTitle(expected, message) { 497 */
496 var currentTitle = casper.getTitle(); 498 Tester.prototype.assertSelectorDoesntHaveText = function assertSelectorDoesntHaveText(selector, text, message) {
497 return this.assert(this.testEquals(currentTitle, expected), message, { 499 "use strict";
498 type: "assertTitle", 500 var textFound = this.casper.fetchText(selector).indexOf(text) === -1;
499 standard: f('Page title is: "%s"', expected), 501 return this.assert(textFound, message, {
500 values: { 502 type: "assertNoTextInSelector",
501 subject: currentTitle, 503 standard: f('Did not find "%s" within the selector "%s"', text, selector),
502 expected: expected 504 values: {
503 } 505 selector: selector,
504 }); 506 text: text
505 }; 507 }
508 });
509 };
506 510
507 /** 511 /**
508 * Asserts that title of the remote page matched the provided pattern. 512 * Asserts that title of the remote page equals to the expected one.
509 * 513 *
510 * @param RegExp pattern The pattern to test the title against 514 * @param String expected The expected title string
511 * @param String message Test description 515 * @param String message Test description
512 * @return Object An assertion result object 516 * @return Object An assertion result object
513 */ 517 */
514 this.assertTitleMatch = this.assertTitleMatches = function assertTitleMatch(pattern, message) { 518 Tester.prototype.assertTitle = function assertTitle(expected, message) {
515 var currentTitle = casper.getTitle(); 519 "use strict";
516 return this.assert(pattern.test(currentTitle), message, { 520 var currentTitle = this.casper.getTitle();
517 type: "assertTitle", 521 return this.assert(this.testEquals(currentTitle, expected), message, {
518 details: "Page title does not match the provided pattern", 522 type: "assertTitle",
519 values: { 523 standard: f('Page title is: "%s"', expected),
520 subject: currentTitle, 524 values: {
521 pattern: pattern.toString() 525 subject: currentTitle,
522 } 526 expected: expected
523 }); 527 }
524 }; 528 });
529 };
525 530
526 /** 531 /**
527 * Asserts that the provided subject is of the given type. 532 * Asserts that title of the remote page matched the provided pattern.
528 * 533 *
529 * @param mixed subject The value to test 534 * @param RegExp pattern The pattern to test the title against
530 * @param String type The javascript type name 535 * @param String message Test description
531 * @param String message Test description 536 * @return Object An assertion result object
532 * @return Object An assertion result object 537 */
533 */ 538 Tester.prototype.assertTitleMatch = Tester.prototype.assertTitleMatches = function assertTitleMatch(pattern, message) {
534 this.assertType = function assertType(subject, type, message) { 539 "use strict";
535 var actual = utils.betterTypeOf(subject); 540 var currentTitle = this.casper.getTitle();
536 return this.assert(this.testEquals(actual, type), message, { 541 return this.assert(pattern.test(currentTitle), message, {
537 type: "assertType", 542 type: "assertTitle",
538 standard: f('Subject type is: "%s"', type), 543 details: "Page title does not match the provided pattern",
539 values: { 544 values: {
540 subject: subject, 545 subject: currentTitle,
541 type: type, 546 pattern: pattern.toString()
542 actual: actual 547 }
543 } 548 });
544 }); 549 };
545 };
546 550
547 /** 551 /**
548 * Asserts that a the current page url matches the provided RegExp 552 * Asserts that the provided subject is of the given type.
549 * pattern. 553 *
550 * 554 * @param mixed subject The value to test
551 * @param RegExp pattern A RegExp object instance 555 * @param String type The javascript type name
552 * @param String message Test description 556 * @param String message Test description
553 * @return Object An assertion result object 557 * @return Object An assertion result object
554 */ 558 */
555 this.assertUrlMatch = this.assertUrlMatches = function assertUrlMatch(pattern, message) { 559 Tester.prototype.assertType = function assertType(subject, type, message) {
556 var currentUrl = casper.getCurrentUrl(); 560 "use strict";
557 return this.assert(pattern.test(currentUrl), message, { 561 var actual = utils.betterTypeOf(subject);
558 type: "assertUrlMatch", 562 return this.assert(this.testEquals(actual, type), message, {
559 standard: "Current url matches the provided pattern", 563 type: "assertType",
560 values: { 564 standard: f('Subject type is: "%s"', type),
561 currentUrl: currentUrl, 565 values: {
562 pattern: pattern.toString() 566 subject: subject,
563 } 567 type: type,
564 }); 568 actual: actual
565 }; 569 }
570 });
571 };
566 572
567 /** 573 /**
568 * Asserts that a selector expression is currently visible. 574 * Asserts that a the current page url matches the provided RegExp
569 * 575 * pattern.
570 * @param String expected selector expression 576 *
571 * @param String message Test description 577 * @param RegExp pattern A RegExp object instance
572 * @return Object An assertion result object 578 * @param String message Test description
573 */ 579 * @return Object An assertion result object
574 this.assertVisible = function assertVisible(selector, message) { 580 */
575 return this.assert(casper.visible(selector), message, { 581 Tester.prototype.assertUrlMatch = Tester.prototype.assertUrlMatches = function assertUrlMatch(pattern, message) {
576 type: "assertVisible", 582 "use strict";
577 standard: "Selector is visible", 583 var currentUrl = this.casper.getCurrentUrl();
578 values: { 584 return this.assert(pattern.test(currentUrl), message, {
579 selector: selector 585 type: "assertUrlMatch",
580 } 586 standard: "Current url matches the provided pattern",
581 }); 587 values: {
582 }; 588 currentUrl: currentUrl,
589 pattern: pattern.toString()
590 }
591 });
592 };
583 593
584 /** 594 /**
585 * Prints out a colored bar onto the console. 595 * Asserts that a selector expression is currently visible.
586 * 596 *
587 */ 597 * @param String expected selector expression
588 this.bar = function bar(text, style) { 598 * @param String message Test description
589 casper.echo(text, style, this.options.pad); 599 * @return Object An assertion result object
590 }; 600 */
601 Tester.prototype.assertVisible = function assertVisible(selector, message) {
602 "use strict";
603 return this.assert(this.casper.visible(selector), message, {
604 type: "assertVisible",
605 standard: "Selector is visible",
606 values: {
607 selector: selector
608 }
609 });
610 };
591 611
592 /** 612 /**
593 * Render a colorized output. Basically a proxy method for 613 * Prints out a colored bar onto the console.
594 * Casper.Colorizer#colorize() 614 *
595 */ 615 */
596 this.colorize = function colorize(message, style) { 616 Tester.prototype.bar = function bar(text, style) {
597 return casper.getColorizer().colorize(message, style); 617 "use strict";
598 }; 618 this.casper.echo(text, style, this.options.pad);
619 };
599 620
600 /** 621 /**
601 * Writes a comment-style formatted message to stdout. 622 * Render a colorized output. Basically a proxy method for
602 * 623 * Casper.Colorizer#colorize()
603 * @param String message 624 */
604 */ 625 Tester.prototype.colorize = function colorize(message, style) {
605 this.comment = function comment(message) { 626 "use strict";
606 casper.echo('# ' + message, 'COMMENT'); 627 return this.casper.getColorizer().colorize(message, style);
607 }; 628 };
608 629
609 /** 630 /**
610 * Declares the current test suite done. 631 * Writes a comment-style formatted message to stdout.
611 * 632 *
612 */ 633 * @param String message
613 this.done = function done() { 634 */
614 this.emit('test.done'); 635 Tester.prototype.comment = function comment(message) {
615 this.running = false; 636 "use strict";
616 }; 637 this.casper.echo('# ' + message, 'COMMENT');
638 };
617 639
618 /** 640 /**
619 * Writes an error-style formatted message to stdout. 641 * Declares the current test suite done.
620 * 642 *
621 * @param String message 643 */
622 */ 644 Tester.prototype.done = function done() {
623 this.error = function error(message) { 645 "use strict";
624 casper.echo(message, 'ERROR'); 646 this.emit('test.done');
625 }; 647 this.running = false;
648 };
626 649
627 /** 650 /**
628 * Executes a file, wraping and evaluating its code in an isolated 651 * Writes an error-style formatted message to stdout.
629 * environment where only the current `casper` instance is passed. 652 *
630 * 653 * @param String message
631 * @param String file Absolute path to some js/coffee file 654 */
632 */ 655 Tester.prototype.error = function error(message) {
633 this.exec = function exec(file) { 656 "use strict";
634 file = this.filter('exec.file', file) || file; 657 this.casper.echo(message, 'ERROR');
635 if (!fs.isFile(file) || !utils.isJsFile(file)) { 658 };
636 var e = new CasperError(f("Cannot exec %s: can only exec() files with .js or .coffee extensions", file));
637 e.fileName = file;
638 throw e;
639 }
640 this.currentTestFile = file;
641 phantom.injectJs(file);
642 };
643 659
644 /** 660 /**
645 * Adds a failed test entry to the stack. 661 * Executes a file, wraping and evaluating its code in an isolated
646 * 662 * environment where only the current `casper` instance is passed.
647 * @param String message 663 *
648 */ 664 * @param String file Absolute path to some js/coffee file
649 this.fail = function fail(message) { 665 */
650 return this.assert(false, message, { 666 Tester.prototype.exec = function exec(file) {
651 type: "fail", 667 "use strict";
652 standard: "explicit call to fail()" 668 file = this.filter('exec.file', file) || file;
653 }); 669 if (!fs.isFile(file) || !utils.isJsFile(file)) {
654 }; 670 var e = new CasperError(f("Cannot exec %s: can only exec() files with .js or .coffee extensions", file));
671 e.fileName = file;
672 throw e;
673 }
674 this.currentTestFile = file;
675 phantom.injectJs(file);
676 };
655 677
656 /** 678 /**
657 * Recursively finds all test files contained in a given directory. 679 * Adds a failed test entry to the stack.
658 * 680 *
659 * @param String dir Path to some directory to scan 681 * @param String message
660 */ 682 */
661 this.findTestFiles = function findTestFiles(dir) { 683 Tester.prototype.fail = function fail(message) {
662 var self = this; 684 "use strict";
663 if (!fs.isDirectory(dir)) { 685 return this.assert(false, message, {
664 return []; 686 type: "fail",
665 } 687 standard: "explicit call to fail()"
666 var entries = fs.list(dir).filter(function _filter(entry) { 688 });
667 return entry !== '.' && entry !== '..'; 689 };
668 }).map(function _map(entry) {
669 return fs.absolute(fs.pathJoin(dir, entry));
670 });
671 entries.forEach(function _forEach(entry) {
672 if (fs.isDirectory(entry)) {
673 entries = entries.concat(self.findTestFiles(entry));
674 }
675 });
676 return entries.filter(function _filter(entry) {
677 return utils.isJsFile(fs.absolute(fs.pathJoin(dir, entry)));
678 }).sort();
679 };
680 690
681 /** 691 /**
682 * Formats a message to highlight some parts of it. 692 * Recursively finds all test files contained in a given directory.
683 * 693 *
684 * @param String message 694 * @param String dir Path to some directory to scan
685 * @param String style 695 */
686 */ 696 Tester.prototype.findTestFiles = function findTestFiles(dir) {
687 this.formatMessage = function formatMessage(message, style) { 697 "use strict";
688 var parts = /^([a-z0-9_\.]+\(\))(.*)/i.exec(message); 698 var self = this;
689 if (!parts) { 699 if (!fs.isDirectory(dir)) {
690 return message; 700 return [];
701 }
702 var entries = fs.list(dir).filter(function _filter(entry) {
703 return entry !== '.' && entry !== '..';
704 }).map(function _map(entry) {
705 return fs.absolute(fs.pathJoin(dir, entry));
706 });
707 entries.forEach(function _forEach(entry) {
708 if (fs.isDirectory(entry)) {
709 entries = entries.concat(self.findTestFiles(entry));
691 } 710 }
692 return this.colorize(parts[1], 'PARAMETER') + this.colorize(parts[2], style); 711 });
693 }; 712 return entries.filter(function _filter(entry) {
713 return utils.isJsFile(fs.absolute(fs.pathJoin(dir, entry)));
714 }).sort();
715 };
694 716
695 /** 717 /**
696 * Retrieves current failure data and all failed cases. 718 * Formats a message to highlight some parts of it.
697 * 719 *
698 * @return Object casedata An object containg information about cases 720 * @param String message
699 * @return Number casedata.length The number of failed cases 721 * @param String style
700 * @return Array casedata.cases An array of all the failed case objects 722 */
701 */ 723 Tester.prototype.formatMessage = function formatMessage(message, style) {
702 this.getFailures = function getFailures() { 724 "use strict";
703 return { 725 var parts = /^([a-z0-9_\.]+\(\))(.*)/i.exec(message);
704 length: this.testResults.failed, 726 if (!parts) {
705 cases: this.testResults.failures 727 return message;
706 }; 728 }
707 }; 729 return this.colorize(parts[1], 'PARAMETER') + this.colorize(parts[2], style);
730 };
708 731
709 /** 732 /**
710 * Retrieves current passed data and all passed cases. 733 * Retrieves current failure data and all failed cases.
711 * 734 *
712 * @return Object casedata An object containg information about cases 735 * @return Object casedata An object containg information about cases
713 * @return Number casedata.length The number of passed cases 736 * @return Number casedata.length The number of failed cases
714 * @return Array casedata.cases An array of all the passed case objects 737 * @return Array casedata.cases An array of all the failed case objects
715 */ 738 */
716 this.getPasses = function getPasses() { 739 Tester.prototype.getFailures = function getFailures() {
717 return { 740 "use strict";
718 length: this.testResults.passed, 741 return {
719 cases: this.testResults.passes 742 length: this.testResults.failed,
720 }; 743 cases: this.testResults.failures
721 }; 744 };
745 };
722 746
723 /** 747 /**
724 * Writes an info-style formatted message to stdout. 748 * Retrieves current passed data and all passed cases.
725 * 749 *
726 * @param String message 750 * @return Object casedata An object containg information about cases
727 */ 751 * @return Number casedata.length The number of passed cases
728 this.info = function info(message) { 752 * @return Array casedata.cases An array of all the passed case objects
729 casper.echo(message, 'PARAMETER'); 753 */
754 Tester.prototype.getPasses = function getPasses() {
755 "use strict";
756 return {
757 length: this.testResults.passed,
758 cases: this.testResults.passes
730 }; 759 };
760 };
731 761
732 /** 762 /**
733 * Adds a successful test entry to the stack. 763 * Writes an info-style formatted message to stdout.
734 * 764 *
735 * @param String message 765 * @param String message
736 */ 766 */
737 this.pass = function pass(message) { 767 Tester.prototype.info = function info(message) {
738 return this.assert(true, message, { 768 "use strict";
739 type: "pass", 769 this.casper.echo(message, 'PARAMETER');
740 standard: "explicit call to pass()" 770 };
741 });
742 };
743 771
744 /** 772 /**
745 * Processes an assertion result by emitting the appropriate event and 773 * Adds a successful test entry to the stack.
746 * printing result onto the console. 774 *
747 * 775 * @param String message
748 * @param Object result An assertion result object 776 */
749 * @return Object The passed assertion result Object 777 Tester.prototype.pass = function pass(message) {
750 */ 778 "use strict";
751 this.processAssertionResult = function processAssertionResult(result) { 779 return this.assert(true, message, {
752 var eventName, style, status; 780 type: "pass",
753 if (result.success === true) { 781 standard: "explicit call to pass()"
754 eventName = 'success'; 782 });
755 style = 'INFO'; 783 };
756 status = this.options.passText;
757 this.testResults.passed++;
758 } else {
759 eventName = 'fail';
760 style = 'RED_BAR';
761 status = this.options.failText;
762 this.testResults.failed++;
763 }
764 var message = result.message || result.standard;
765 casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
766 this.emit(eventName, result);
767 return result;
768 };
769 784
770 /** 785 /**
771 * Renders a detailed report for each failed test. 786 * Processes an assertion result by emitting the appropriate event and
772 * 787 * printing result onto the console.
773 * @param Array failures 788 *
774 */ 789 * @param Object result An assertion result object
775 this.renderFailureDetails = function renderFailureDetails(failures) { 790 * @return Object The passed assertion result Object
776 if (failures.length === 0) { 791 */
777 return; 792 Tester.prototype.processAssertionResult = function processAssertionResult(result) {
778 } 793 "use strict";
779 casper.echo(f("\nDetails for the %d failed test%s:\n", failures.length, failures.length > 1 ? "s" : ""), "PARAMETER"); 794 var eventName, style, status;
780 failures.forEach(function _forEach(failure) { 795 if (result.success === true) {
781 var type, message, line; 796 eventName = 'success';
782 type = failure.type || "unknown"; 797 style = 'INFO';
783 line = ~~failure.line; 798 status = this.options.passText;
784 message = failure.message; 799 this.testResults.passed++;
785 casper.echo(f('In %s:%s', failure.file, line)); 800 } else {
786 casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT"); 801 eventName = 'fail';
787 }); 802 style = 'RED_BAR';
788 }; 803 status = this.options.failText;
804 this.testResults.failed++;
805 }
806 var message = result.message || result.standard;
807 this.casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
808 this.emit(eventName, result);
809 return result;
810 };
789 811
790 /** 812 /**
791 * Render tests results, an optionally exit phantomjs. 813 * Renders a detailed report for each failed test.
792 * 814 *
793 * @param Boolean exit 815 * @param Array failures
794 */ 816 */
795 this.renderResults = function renderResults(exit, status, save) { 817 Tester.prototype.renderFailureDetails = function renderFailureDetails(failures) {
796 save = utils.isString(save) ? save : this.options.save; 818 "use strict";
797 var total = this.testResults.passed + this.testResults.failed, statusText, style, result; 819 if (failures.length === 0) {
798 var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0)); 820 return;
799 if (total === 0) { 821 }
822 this.casper.echo(f("\nDetails for the %d failed test%s:\n", failures.length, failures.length > 1 ? "s" : ""), "PARAMETER");
823 failures.forEach(function _forEach(failure) {
824 var type, message, line;
825 type = failure.type || "unknown";
826 line = ~~failure.line;
827 message = failure.message;
828 this.casper.echo(f('In %s:%s', failure.file, line));
829 this.casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT");
830 });
831 };
832
833 /**
834 * Render tests results, an optionally exit phantomjs.
835 *
836 * @param Boolean exit
837 */
838 Tester.prototype.renderResults = function renderResults(exit, status, save) {
839 "use strict";
840 save = utils.isString(save) ? save : this.options.save;
841 var total = this.testResults.passed + this.testResults.failed, statusText, style, result;
842 var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0));
843 if (total === 0) {
844 statusText = this.options.failText;
845 style = 'RED_BAR';
846 result = f("%s Looks like you didn't run any test.", statusText);
847 } else {
848 if (this.testResults.failed > 0) {
800 statusText = this.options.failText; 849 statusText = this.options.failText;
801 style = 'RED_BAR'; 850 style = 'RED_BAR';
802 result = f("%s Looks like you didn't run any test.", statusText);
803 } else { 851 } else {
804 if (this.testResults.failed > 0) { 852 statusText = this.options.passText;
805 statusText = this.options.failText; 853 style = 'GREEN_BAR';
806 style = 'RED_BAR';
807 } else {
808 statusText = this.options.passText;
809 style = 'GREEN_BAR';
810 }
811 result = f('%s %s tests executed, %d passed, %d failed.',
812 statusText, total, this.testResults.passed, this.testResults.failed);
813 } 854 }
814 casper.echo(result, style, this.options.pad); 855 result = f('%s %s tests executed, %d passed, %d failed.',
815 if (this.testResults.failed > 0) { 856 statusText, total, this.testResults.passed, this.testResults.failed);
816 this.renderFailureDetails(this.testResults.failures); 857 }
817 } 858 this.casper.echo(result, style, this.options.pad);
818 if (save && utils.isFunction(require)) { 859 if (this.testResults.failed > 0) {
819 try { 860 this.renderFailureDetails(this.testResults.failures);
820 fs.write(save, this.exporter.getXML(), 'w'); 861 }
821 casper.echo(f('Result log stored in %s', save), 'INFO', 80); 862 if (save && utils.isFunction(require)) {
822 } catch (e) { 863 try {
823 casper.echo(f('Unable to write results to %s: %s', save, e), 'ERROR', 80); 864 fs.write(save, this.exporter.getXML(), 'w');
824 } 865 this.casper.echo(f('Result log stored in %s', save), 'INFO', 80);
825 } 866 } catch (e) {
826 if (exit === true) { 867 this.casper.echo(f('Unable to write results to %s: %s', save, e), 'ERROR', 80);
827 casper.exit(exitStatus);
828 }
829 };
830
831 /**
832 * Runs al suites contained in the paths passed as arguments.
833 *
834 */
835 this.runSuites = function runSuites() {
836 var testFiles = [], self = this;
837 if (arguments.length === 0) {
838 throw new CasperError("runSuites() needs at least one path argument");
839 } 868 }
840 this.loadIncludes.includes.forEach(function _forEachInclude(include) { 869 }
841 phantom.injectJs(include); 870 if (exit === true) {
842 }); 871 this.casper.exit(exitStatus);
843 872 }
844 this.loadIncludes.pre.forEach(function _forEachPreTest(preTestFile) { 873 };
845 testFiles = testFiles.concat(preTestFile);
846 });
847 874
848 Array.prototype.forEach.call(arguments, function _forEachArgument(path) { 875 /**
849 if (!fs.exists(path)) { 876 * Runs al suites contained in the paths passed as arguments.
850 self.bar(f("Path %s doesn't exist", path), "RED_BAR"); 877 *
851 } 878 */
852 if (fs.isDirectory(path)) { 879 Tester.prototype.runSuites = function runSuites() {
853 testFiles = testFiles.concat(self.findTestFiles(path)); 880 "use strict";
854 } else if (fs.isFile(path)) { 881 var testFiles = [], self = this;
855 testFiles.push(path); 882 if (arguments.length === 0) {
856 } 883 throw new CasperError("runSuites() needs at least one path argument");
857 }); 884 }
885 this.loadIncludes.includes.forEach(function _forEachInclude(include) {
886 phantom.injectJs(include);
887 });
858 888
859 this.loadIncludes.post.forEach(function _forEachPostTest(postTestFile) { 889 this.loadIncludes.pre.forEach(function _forEachPreTest(preTestFile) {
860 testFiles = testFiles.concat(postTestFile); 890 testFiles = testFiles.concat(preTestFile);
861 }); 891 });
862 892
863 if (testFiles.length === 0) { 893 Array.prototype.forEach.call(arguments, function _forEachArgument(path) {
864 this.bar(f("No test file found in %s, aborting.", Array.prototype.slice.call(arguments)), "RED_BAR"); 894 if (!fs.exists(path)) {
865 casper.exit(1); 895 self.bar(f("Path %s doesn't exist", path), "RED_BAR");
866 } 896 }
867 self.currentSuiteNum = 0; 897 if (fs.isDirectory(path)) {
868 var interval = setInterval(function _check(self) { 898 testFiles = testFiles.concat(self.findTestFiles(path));
869 if (self.running) { 899 } else if (fs.isFile(path)) {
870 return; 900 testFiles.push(path);
871 } 901 }
872 if (self.currentSuiteNum === testFiles.length) { 902 });
873 self.emit('tests.complete');
874 clearInterval(interval);
875 } else {
876 self.runTest(testFiles[self.currentSuiteNum]);
877 self.currentSuiteNum++;
878 }
879 }, 100, this);
880 };
881 903
882 /** 904 this.loadIncludes.post.forEach(function _forEachPostTest(postTestFile) {
883 * Runs a test file 905 testFiles = testFiles.concat(postTestFile);
884 * 906 });
885 */
886 this.runTest = function runTest(testFile) {
887 this.bar(f('Test file: %s', testFile), 'INFO_BAR');
888 this.running = true; // this.running is set back to false with done()
889 this.exec(testFile);
890 };
891 907
892 /** 908 if (testFiles.length === 0) {
893 * Tests equality between the two passed arguments. 909 this.bar(f("No test file found in %s, aborting.", Array.prototype.slice.call(arguments)), "RED_BAR");
894 * 910 this.casper.exit(1);
895 * @param Mixed v1 911 }
896 * @param Mixed v2 912 self.currentSuiteNum = 0;
897 * @param Boolean 913 var interval = setInterval(function _check(self) {
898 */ 914 if (self.running) {
899 this.testEquals = this.testEqual = function testEquals(v1, v2) { 915 return;
900 return utils.equals(v1, v2); 916 }
901 }; 917 if (self.currentSuiteNum === testFiles.length) {
918 self.emit('tests.complete');
919 clearInterval(interval);
920 } else {
921 self.runTest(testFiles[self.currentSuiteNum]);
922 self.currentSuiteNum++;
923 }
924 }, 100, this);
925 };
902 926
903 /** 927 /**
904 * Processes an error caught while running tests contained in a given test 928 * Runs a test file
905 * file. 929 *
906 * 930 */
907 * @param Error|String error The error 931 Tester.prototype.runTest = function runTest(testFile) {
908 * @param String file Test file where the error occurred 932 "use strict";
909 * @param Number line Line number (optional) 933 this.bar(f('Test file: %s', testFile), 'INFO_BAR');
910 */ 934 this.running = true; // this.running is set back to false with done()
911 this.uncaughtError = function uncaughtError(error, file, line) { 935 this.exec(testFile);
912 return this.processAssertionResult({
913 success: false,
914 type: "uncaughtError",
915 file: file,
916 line: ~~line || "unknown",
917 message: utils.isObject(error) ? error.message : error,
918 values: {
919 error: error
920 }
921 });
922 };
923 }; 936 };
924 937
925 // Tester class is an EventEmitter 938 /**
926 utils.inherits(Tester, events.EventEmitter); 939 * Tests equality between the two passed arguments.
940 *
941 * @param Mixed v1
942 * @param Mixed v2
943 * @param Boolean
944 */
945 Tester.prototype.testEquals = Tester.prototype.testEqual = function testEquals(v1, v2) {
946 "use strict";
947 return utils.equals(v1, v2);
948 };
927 949
928 exports.Tester = Tester; 950 /**
951 * Processes an error caught while running tests contained in a given test
952 * file.
953 *
954 * @param Error|String error The error
955 * @param String file Test file where the error occurred
956 * @param Number line Line number (optional)
957 */
958 Tester.prototype.uncaughtError = function uncaughtError(error, file, line) {
959 "use strict";
960 return this.processAssertionResult({
961 success: false,
962 type: "uncaughtError",
963 file: file,
964 line: ~~line || "unknown",
965 message: utils.isObject(error) ? error.message : error,
966 values: {
967 error: error
968 }
969 });
970 };
......
...@@ -30,459 +30,482 @@ ...@@ -30,459 +30,482 @@
30 30
31 /*global CasperError console exports phantom require*/ 31 /*global CasperError console exports phantom require*/
32 32
33 (function(exports) { 33 /**
34 * Provides a better typeof operator equivalent, able to retrieve the array
35 * type.
36 *
37 * @param mixed input
38 * @return String
39 * @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
40 */
41 function betterTypeOf(input) {
34 "use strict"; 42 "use strict";
35 43 try {
36 /** 44 return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
37 * Provides a better typeof operator equivalent, able to retrieve the array 45 } catch (e) {
38 * type. 46 return typeof input;
39 *
40 * @param mixed input
41 * @return String
42 * @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
43 */
44 function betterTypeOf(input) {
45 try {
46 return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
47 } catch (e) {
48 return typeof input;
49 }
50 } 47 }
51 exports.betterTypeOf = betterTypeOf; 48 }
49 exports.betterTypeOf = betterTypeOf;
52 50
53 /** 51 /**
54 * Cleans a passed URL if it lacks a slash at the end when a sole domain is used. 52 * Cleans a passed URL if it lacks a slash at the end when a sole domain is used.
55 * 53 *
56 * @param String url An HTTP URL 54 * @param String url An HTTP URL
57 * @return String 55 * @return String
58 */ 56 */
59 function cleanUrl(url) { 57 function cleanUrl(url) {
60 var parts = /(https?):\/\/(.*)/i.exec(url); 58 "use strict";
61 if (!parts) { 59 var parts = /(https?):\/\/(.*)/i.exec(url);
62 return url; 60 if (!parts) {
63 }
64 var protocol = parts[1];
65 var subparts = parts[2].split('/');
66 if (subparts.length === 1) {
67 return format("%s://%s/", protocol, subparts[0]);
68 }
69 return url; 61 return url;
70 } 62 }
71 exports.cleanUrl = cleanUrl; 63 var protocol = parts[1];
72 64 var subparts = parts[2].split('/');
73 /** 65 if (subparts.length === 1) {
74 * Dumps a JSON representation of passed value to the console. Used for 66 return format("%s://%s/", protocol, subparts[0]);
75 * debugging purpose only.
76 *
77 * @param Mixed value
78 */
79 function dump(value) {
80 console.log(serialize(value, 4));
81 } 67 }
82 exports.dump = dump; 68 return url;
69 }
70 exports.cleanUrl = cleanUrl;
71
72 /**
73 * Dumps a JSON representation of passed value to the console. Used for
74 * debugging purpose only.
75 *
76 * @param Mixed value
77 */
78 function dump(value) {
79 "use strict";
80 console.log(serialize(value, 4));
81 }
82 exports.dump = dump;
83 83
84 /** 84 /**
85 * Tests equality between the two passed arguments. 85 * Tests equality between the two passed arguments.
86 * 86 *
87 * @param Mixed v1 87 * @param Mixed v1
88 * @param Mixed v2 88 * @param Mixed v2
89 * @param Boolean 89 * @param Boolean
90 */ 90 */
91 function equals(v1, v2) { 91 function equals(v1, v2) {
92 if (betterTypeOf(v1) !== betterTypeOf(v2)) { 92 "use strict";
93 if (betterTypeOf(v1) !== betterTypeOf(v2)) {
94 return false;
95 }
96 if (isFunction(v1)) {
97 return v1.toString() === v2.toString();
98 }
99 if (v1 instanceof Object) {
100 if (Object.keys(v1).length !== Object.keys(v2).length) {
93 return false; 101 return false;
94 } 102 }
95 if (isFunction(v1)) { 103 for (var k in v1) {
96 return v1.toString() === v2.toString(); 104 if (!equals(v1[k], v2[k])) {
97 }
98 if (v1 instanceof Object) {
99 if (Object.keys(v1).length !== Object.keys(v2).length) {
100 return false; 105 return false;
101 } 106 }
102 for (var k in v1) {
103 if (!equals(v1[k], v2[k])) {
104 return false;
105 }
106 }
107 return true;
108 } 107 }
109 return v1 === v2; 108 return true;
110 } 109 }
111 exports.equals = equals; 110 return v1 === v2;
111 }
112 exports.equals = equals;
112 113
113 /** 114 /**
114 * Returns the file extension in lower case. 115 * Returns the file extension in lower case.
115 * 116 *
116 * @param String file File path 117 * @param String file File path
117 * @return string 118 * @return string
118 */ 119 */
119 function fileExt(file) { 120 function fileExt(file) {
120 try { 121 "use strict";
121 return file.split('.').pop().toLowerCase().trim(); 122 try {
122 } catch(e) { 123 return file.split('.').pop().toLowerCase().trim();
123 return ''; 124 } catch(e) {
124 } 125 return '';
125 } 126 }
126 exports.fileExt = fileExt; 127 }
128 exports.fileExt = fileExt;
127 129
128 /** 130 /**
129 * Takes a string and append blanks until the pad value is reached. 131 * Takes a string and append blanks until the pad value is reached.
130 * 132 *
131 * @param String text 133 * @param String text
132 * @param Number pad Pad value (optional; default: 80) 134 * @param Number pad Pad value (optional; default: 80)
133 * @return String 135 * @return String
134 */ 136 */
135 function fillBlanks(text, pad) { 137 function fillBlanks(text, pad) {
136 pad = pad || 80; 138 "use strict";
137 if (text.length < pad) { 139 pad = pad || 80;
138 text += new Array(pad - text.length + 1).join(' '); 140 if (text.length < pad) {
139 } 141 text += new Array(pad - text.length + 1).join(' ');
140 return text;
141 } 142 }
142 exports.fillBlanks = fillBlanks; 143 return text;
144 }
145 exports.fillBlanks = fillBlanks;
143 146
144 /** 147 /**
145 * Formats a string with passed parameters. Ported from nodejs `util.format()`. 148 * Formats a string with passed parameters. Ported from nodejs `util.format()`.
146 * 149 *
147 * @return String 150 * @return String
148 */ 151 */
149 function format(f) { 152 function format(f) {
150 var i = 1; 153 "use strict";
151 var args = arguments; 154 var i = 1;
152 var len = args.length; 155 var args = arguments;
153 var str = String(f).replace(/%[sdj%]/g, function _replace(x) { 156 var len = args.length;
154 if (i >= len) return x; 157 var str = String(f).replace(/%[sdj%]/g, function _replace(x) {
155 switch (x) { 158 if (i >= len) return x;
156 case '%s': 159 switch (x) {
157 return String(args[i++]); 160 case '%s':
158 case '%d': 161 return String(args[i++]);
159 return Number(args[i++]); 162 case '%d':
160 case '%j': 163 return Number(args[i++]);
161 return JSON.stringify(args[i++]); 164 case '%j':
162 case '%%': 165 return JSON.stringify(args[i++]);
163 return '%'; 166 case '%%':
164 default: 167 return '%';
165 return x; 168 default:
166 } 169 return x;
167 });
168 for (var x = args[i]; i < len; x = args[++i]) {
169 if (x === null || typeof x !== 'object') {
170 str += ' ' + x;
171 } else {
172 str += '[obj]';
173 }
174 } 170 }
175 return str; 171 });
176 } 172 for (var x = args[i]; i < len; x = args[++i]) {
177 exports.format = format; 173 if (x === null || typeof x !== 'object') {
178 174 str += ' ' + x;
179 /** 175 } else {
180 * Retrieves the value of an Object foreign property using a dot-separated 176 str += '[obj]';
181 * path string.
182 *
183 * Beware, this function doesn't handle object key names containing a dot.
184 *
185 * @param Object obj The source object
186 * @param String path Dot separated path, eg. "x.y.z"
187 */
188 function getPropertyPath(obj, path) {
189 if (!isObject(obj) || !isString(path)) {
190 return undefined;
191 } 177 }
192 var value = obj;
193 path.split('.').forEach(function(property) {
194 if (typeof value === "object" && property in value) {
195 value = value[property];
196 } else {
197 value = undefined;
198 }
199 });
200 return value;
201 } 178 }
202 exports.getPropertyPath = getPropertyPath; 179 return str;
180 }
181 exports.format = format;
203 182
204 /** 183 /**
205 * Inherit the prototype methods from one constructor into another. 184 * Retrieves the value of an Object foreign property using a dot-separated
206 * 185 * path string.
207 * @param {function} ctor Constructor function which needs to inherit the 186 *
208 * prototype. 187 * Beware, this function doesn't handle object key names containing a dot.
209 * @param {function} superCtor Constructor function to inherit prototype from. 188 *
210 */ 189 * @param Object obj The source object
211 function inherits(ctor, superCtor) { 190 * @param String path Dot separated path, eg. "x.y.z"
212 ctor.super_ = ctor.__super__ = superCtor; 191 */
213 ctor.prototype = Object.create(superCtor.prototype, { 192 function getPropertyPath(obj, path) {
214 constructor: { 193 "use strict";
215 value: ctor, 194 if (!isObject(obj) || !isString(path)) {
216 enumerable: false, 195 return undefined;
217 writable: true,
218 configurable: true
219 }
220 });
221 } 196 }
222 exports.inherits = inherits; 197 var value = obj;
198 path.split('.').forEach(function(property) {
199 if (typeof value === "object" && property in value) {
200 value = value[property];
201 } else {
202 value = undefined;
203 }
204 });
205 return value;
206 }
207 exports.getPropertyPath = getPropertyPath;
223 208
224 /** 209 /**
225 * Checks if value is a javascript Array 210 * Inherit the prototype methods from one constructor into another.
226 * 211 *
227 * @param mixed value 212 * @param {function} ctor Constructor function which needs to inherit the
228 * @return Boolean 213 * prototype.
229 */ 214 * @param {function} superCtor Constructor function to inherit prototype from.
230 function isArray(value) { 215 */
231 return Array.isArray(value) || isType(value, "array"); 216 function inherits(ctor, superCtor) {
232 } 217 "use strict";
233 exports.isArray = isArray; 218 ctor.super_ = ctor.__super__ = superCtor;
219 ctor.prototype = Object.create(superCtor.prototype, {
220 constructor: {
221 value: ctor,
222 enumerable: false,
223 writable: true,
224 configurable: true
225 }
226 });
227 }
228 exports.inherits = inherits;
234 229
235 /** 230 /**
236 * Checks if passed argument is an instance of Capser object. 231 * Checks if value is a javascript Array
237 * 232 *
238 * @param mixed value 233 * @param mixed value
239 * @return Boolean 234 * @return Boolean
240 */ 235 */
241 function isCasperObject(value) { 236 function isArray(value) {
242 return value instanceof require('casper').Casper; 237 "use strict";
243 } 238 return Array.isArray(value) || isType(value, "array");
244 exports.isCasperObject = isCasperObject; 239 }
240 exports.isArray = isArray;
245 241
246 /** 242 /**
247 * Checks if value is a phantomjs clipRect-compatible object 243 * Checks if passed argument is an instance of Capser object.
248 * 244 *
249 * @param mixed value 245 * @param mixed value
250 * @return Boolean 246 * @return Boolean
251 */ 247 */
252 function isClipRect(value) { 248 function isCasperObject(value) {
253 return isType(value, "cliprect") || ( 249 "use strict";
254 isObject(value) && 250 return value instanceof require('casper').Casper;
255 isNumber(value.top) && isNumber(value.left) && 251 }
256 isNumber(value.width) && isNumber(value.height) 252 exports.isCasperObject = isCasperObject;
257 );
258 }
259 exports.isClipRect = isClipRect;
260 253
261 /** 254 /**
262 * Checks if value is a javascript Function 255 * Checks if value is a phantomjs clipRect-compatible object
263 * 256 *
264 * @param mixed value 257 * @param mixed value
265 * @return Boolean 258 * @return Boolean
266 */ 259 */
267 function isFunction(value) { 260 function isClipRect(value) {
268 return isType(value, "function"); 261 "use strict";
269 } 262 return isType(value, "cliprect") || (
270 exports.isFunction = isFunction; 263 isObject(value) &&
264 isNumber(value.top) && isNumber(value.left) &&
265 isNumber(value.width) && isNumber(value.height)
266 );
267 }
268 exports.isClipRect = isClipRect;
271 269
272 /** 270 /**
273 * Checks if passed resource involves an HTTP url. 271 * Checks if value is a javascript Function
274 * 272 *
275 * @param Object resource The PhantomJS HTTP resource object 273 * @param mixed value
276 * @return Boolean 274 * @return Boolean
277 */ 275 */
278 function isHTTPResource(resource) { 276 function isFunction(value) {
279 return isObject(resource) && /^http/i.test(resource.url); 277 "use strict";
280 } 278 return isType(value, "function");
281 exports.isHTTPResource = isHTTPResource; 279 }
280 exports.isFunction = isFunction;
282 281
283 /** 282 /**
284 * Checks if a file is apparently javascript compatible (.js or .coffee). 283 * Checks if passed resource involves an HTTP url.
285 * 284 *
286 * @param String file Path to the file to test 285 * @param Object resource The PhantomJS HTTP resource object
287 * @return Boolean 286 * @return Boolean
288 */ 287 */
289 function isJsFile(file) { 288 function isHTTPResource(resource) {
290 var ext = fileExt(file); 289 "use strict";
291 return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1; 290 return isObject(resource) && /^http/i.test(resource.url);
292 } 291 }
293 exports.isJsFile = isJsFile; 292 exports.isHTTPResource = isHTTPResource;
294 293
295 /** 294 /**
296 * Checks if the provided value is null 295 * Checks if a file is apparently javascript compatible (.js or .coffee).
297 * 296 *
298 * @return Boolean 297 * @param String file Path to the file to test
299 */ 298 * @return Boolean
300 function isNull(value) { 299 */
301 return isType(value, "null"); 300 function isJsFile(file) {
302 } 301 "use strict";
303 exports.isNull = isNull; 302 var ext = fileExt(file);
303 return isString(ext, "string") && ['js', 'coffee'].indexOf(ext) !== -1;
304 }
305 exports.isJsFile = isJsFile;
304 306
305 /** 307 /**
306 * Checks if value is a javascript Number 308 * Checks if the provided value is null
307 * 309 *
308 * @param mixed value 310 * @return Boolean
309 * @return Boolean 311 */
310 */ 312 function isNull(value) {
311 function isNumber(value) { 313 "use strict";
312 return isType(value, "number"); 314 return isType(value, "null");
313 } 315 }
314 exports.isNumber = isNumber; 316 exports.isNull = isNull;
315 317
316 /** 318 /**
317 * Checks if value is a javascript Object 319 * Checks if value is a javascript Number
318 * 320 *
319 * @param mixed value 321 * @param mixed value
320 * @return Boolean 322 * @return Boolean
321 */ 323 */
322 function isObject(value) { 324 function isNumber(value) {
323 var objectTypes = ["array", "object", "qtruntimeobject"]; 325 "use strict";
324 return objectTypes.indexOf(betterTypeOf(value)) >= 0; 326 return isType(value, "number");
325 } 327 }
326 exports.isObject = isObject; 328 exports.isNumber = isNumber;
327 329
328 /** 330 /**
329 * Checks if value is a javascript String 331 * Checks if value is a javascript Object
330 * 332 *
331 * @param mixed value 333 * @param mixed value
332 * @return Boolean 334 * @return Boolean
333 */ 335 */
334 function isString(value) { 336 function isObject(value) {
335 return isType(value, "string"); 337 "use strict";
336 } 338 var objectTypes = ["array", "object", "qtruntimeobject"];
337 exports.isString = isString; 339 return objectTypes.indexOf(betterTypeOf(value)) >= 0;
340 }
341 exports.isObject = isObject;
338 342
339 /** 343 /**
340 * Shorthands for checking if a value is of the given type. Can check for 344 * Checks if value is a javascript String
341 * arrays. 345 *
342 * 346 * @param mixed value
343 * @param mixed what The value to check 347 * @return Boolean
344 * @param String typeName The type name ("string", "number", "function", etc.) 348 */
345 * @return Boolean 349 function isString(value) {
346 */ 350 "use strict";
347 function isType(what, typeName) { 351 return isType(value, "string");
348 if (typeof typeName !== "string" || !typeName) { 352 }
349 throw new CasperError("You must pass isType() a typeName string"); 353 exports.isString = isString;
350 }
351 return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
352 }
353 exports.isType = isType;
354 354
355 /** 355 /**
356 * Checks if the provided value is undefined 356 * Shorthands for checking if a value is of the given type. Can check for
357 * 357 * arrays.
358 * @return Boolean 358 *
359 */ 359 * @param mixed what The value to check
360 function isUndefined(value) { 360 * @param String typeName The type name ("string", "number", "function", etc.)
361 return isType(value, "undefined"); 361 * @return Boolean
362 */
363 function isType(what, typeName) {
364 "use strict";
365 if (typeof typeName !== "string" || !typeName) {
366 throw new CasperError("You must pass isType() a typeName string");
362 } 367 }
363 exports.isUndefined = isUndefined; 368 return betterTypeOf(what).toLowerCase() === typeName.toLowerCase();
369 }
370 exports.isType = isType;
364 371
365 /** 372 /**
366 * Checks if value is a valid selector Object. 373 * Checks if the provided value is undefined
367 * 374 *
368 * @param mixed value 375 * @return Boolean
369 * @return Boolean 376 */
370 */ 377 function isUndefined(value) {
371 function isValidSelector(value) { 378 "use strict";
372 if (isString(value)) { 379 return isType(value, "undefined");
373 try { 380 }
374 // phantomjs env has a working document object, let's use it 381 exports.isUndefined = isUndefined;
375 document.querySelector(value); 382
376 } catch(e) { 383 /**
377 if ('name' in e && e.name === 'SYNTAX_ERR') { 384 * Checks if value is a valid selector Object.
378 return false; 385 *
379 } 386 * @param mixed value
380 } 387 * @return Boolean
381 return true; 388 */
382 } else if (isObject(value)) { 389 function isValidSelector(value) {
383 if (!value.hasOwnProperty('type')) { 390 "use strict";
384 return false; 391 if (isString(value)) {
385 } 392 try {
386 if (!value.hasOwnProperty('path')) { 393 // phantomjs env has a working document object, let's use it
387 return false; 394 document.querySelector(value);
388 } 395 } catch(e) {
389 if (['css', 'xpath'].indexOf(value.type) === -1) { 396 if ('name' in e && e.name === 'SYNTAX_ERR') {
390 return false; 397 return false;
391 } 398 }
392 return true;
393 } 399 }
394 return false; 400 return true;
401 } else if (isObject(value)) {
402 if (!value.hasOwnProperty('type')) {
403 return false;
404 }
405 if (!value.hasOwnProperty('path')) {
406 return false;
407 }
408 if (['css', 'xpath'].indexOf(value.type) === -1) {
409 return false;
410 }
411 return true;
395 } 412 }
396 exports.isValidSelector = isValidSelector; 413 return false;
414 }
415 exports.isValidSelector = isValidSelector;
397 416
398 /** 417 /**
399 * Checks if the provided var is a WebPage instance 418 * Checks if the provided var is a WebPage instance
400 * 419 *
401 * @param mixed what 420 * @param mixed what
402 * @return Boolean 421 * @return Boolean
403 */ 422 */
404 function isWebPage(what) { 423 function isWebPage(what) {
405 return betterTypeOf(what) === "qtruntimeobject" && what.objectName === 'WebPage'; 424 "use strict";
406 } 425 return betterTypeOf(what) === "qtruntimeobject" && what.objectName === 'WebPage';
407 exports.isWebPage = isWebPage; 426 }
427 exports.isWebPage = isWebPage;
408 428
409 /** 429 /**
410 * Object recursive merging utility. 430 * Object recursive merging utility.
411 * 431 *
412 * @param Object origin the origin object 432 * @param Object origin the origin object
413 * @param Object add the object to merge data into origin 433 * @param Object add the object to merge data into origin
414 * @return Object 434 * @return Object
415 */ 435 */
416 function mergeObjects(origin, add) { 436 function mergeObjects(origin, add) {
417 for (var p in add) { 437 "use strict";
418 try { 438 for (var p in add) {
419 if (add[p].constructor === Object) { 439 try {
420 origin[p] = mergeObjects(origin[p], add[p]); 440 if (add[p].constructor === Object) {
421 } else { 441 origin[p] = mergeObjects(origin[p], add[p]);
422 origin[p] = add[p]; 442 } else {
423 } 443 origin[p] = add[p];
424 } catch(e) {
425 origin[p] = add[p];
426 } 444 }
445 } catch(e) {
446 origin[p] = add[p];
427 } 447 }
428 return origin;
429 } 448 }
430 exports.mergeObjects = mergeObjects; 449 return origin;
450 }
451 exports.mergeObjects = mergeObjects;
431 452
432 /** 453 /**
433 * Creates an (SG|X)ML node element. 454 * Creates an (SG|X)ML node element.
434 * 455 *
435 * @param String name The node name 456 * @param String name The node name
436 * @param Object attributes Optional attributes 457 * @param Object attributes Optional attributes
437 * @return HTMLElement 458 * @return HTMLElement
438 */ 459 */
439 function node(name, attributes) { 460 function node(name, attributes) {
440 var _node = document.createElement(name); 461 "use strict";
441 for (var attrName in attributes) { 462 var _node = document.createElement(name);
442 var value = attributes[attrName]; 463 for (var attrName in attributes) {
443 if (attributes.hasOwnProperty(attrName) && isString(attrName)) { 464 var value = attributes[attrName];
444 _node.setAttribute(attrName, value); 465 if (attributes.hasOwnProperty(attrName) && isString(attrName)) {
445 } 466 _node.setAttribute(attrName, value);
446 } 467 }
447 return _node;
448 } 468 }
449 exports.node = node; 469 return _node;
470 }
471 exports.node = node;
450 472
451 /** 473 /**
452 * Serializes a value using JSON. 474 * Serializes a value using JSON.
453 * 475 *
454 * @param Mixed value 476 * @param Mixed value
455 * @return String 477 * @return String
456 */ 478 */
457 function serialize(value, indent) { 479 function serialize(value, indent) {
458 if (isArray(value)) { 480 "use strict";
459 value = value.map(function _map(prop) { 481 if (isArray(value)) {
460 return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop; 482 value = value.map(function _map(prop) {
461 }); 483 return isFunction(prop) ? prop.toString().replace(/\s{2,}/, '') : prop;
462 } 484 });
463 return JSON.stringify(value, null, indent);
464 } 485 }
465 exports.serialize = serialize; 486 return JSON.stringify(value, null, indent);
487 }
488 exports.serialize = serialize;
466 489
467 /** 490 /**
468 * Returns unique values from an array. 491 * Returns unique values from an array.
469 * 492 *
470 * Note: ugly code is ugly, but efficient: http://jsperf.com/array-unique2/8 493 * Note: ugly code is ugly, but efficient: http://jsperf.com/array-unique2/8
471 * 494 *
472 * @param Array array 495 * @param Array array
473 * @return Array 496 * @return Array
474 */ 497 */
475 function unique(array) { 498 function unique(array) {
476 var o = {}, 499 "use strict";
477 r = []; 500 var o = {},
478 for (var i = 0, len = array.length; i !== len; i++) { 501 r = [];
479 var d = array[i]; 502 for (var i = 0, len = array.length; i !== len; i++) {
480 if (o[d] !== 1) { 503 var d = array[i];
481 o[d] = 1; 504 if (o[d] !== 1) {
482 r[r.length] = d; 505 o[d] = 1;
483 } 506 r[r.length] = d;
484 } 507 }
485 return r;
486 } 508 }
487 exports.unique = unique; 509 return r;
488 })(exports); 510 }
511 exports.unique = unique;
......
...@@ -33,6 +33,36 @@ ...@@ -33,6 +33,36 @@
33 var utils = require('utils'); 33 var utils = require('utils');
34 var fs = require('fs'); 34 var fs = require('fs');
35 35
36 /**
37 * Generates a value for 'classname' attribute of the JUnit XML report.
38 *
39 * Uses the (relative) file name of the current casper script without file
40 * extension as classname.
41 *
42 * @param String classname
43 * @return String
44 */
45 function generateClassName(classname) {
46 "use strict";
47 classname = classname.replace(phantom.casperPath, "").trim();
48 var script = classname || phantom.casperScript;
49 if (script.indexOf(fs.workingDirectory) === 0) {
50 script = script.substring(fs.workingDirectory.length + 1);
51 }
52 if (script.indexOf('/') === 0) {
53 script = script.substring(1, script.length);
54 }
55 if (~script.indexOf('.')) {
56 script = script.substring(0, script.lastIndexOf('.'));
57 }
58 return script || "unknown";
59 }
60
61 /**
62 * Creates a XUnit instance
63 *
64 * @return XUnit
65 */
36 exports.create = function create() { 66 exports.create = function create() {
37 "use strict"; 67 "use strict";
38 return new XUnitExporter(); 68 return new XUnitExporter();
...@@ -88,31 +118,6 @@ XUnitExporter.prototype.addFailure = function addFailure(classname, name, messag ...@@ -88,31 +118,6 @@ XUnitExporter.prototype.addFailure = function addFailure(classname, name, messag
88 }; 118 };
89 119
90 /** 120 /**
91 * Generates a value for 'classname' attribute of the JUnit XML report.
92 *
93 * Uses the (relative) file name of the current casper script without file
94 * extension as classname.
95 *
96 * @param String classname
97 * @return String
98 */
99 function generateClassName(classname) {
100 "use strict";
101 classname = classname.replace(phantom.casperPath, "").trim();
102 var script = classname || phantom.casperScript;
103 if (script.indexOf(fs.workingDirectory) === 0) {
104 script = script.substring(fs.workingDirectory.length + 1);
105 }
106 if (script.indexOf('/') === 0) {
107 script = script.substring(1, script.length);
108 }
109 if (~script.indexOf('.')) {
110 script = script.substring(0, script.lastIndexOf('.'));
111 }
112 return script || "unknown";
113 }
114
115 /**
116 * Retrieves generated XML object - actually an HTMLElement. 121 * Retrieves generated XML object - actually an HTMLElement.
117 * 122 *
118 * @return HTMLElement 123 * @return HTMLElement
......
1 /*global phantom*/
2
1 if (!phantom.casperLoaded) { 3 if (!phantom.casperLoaded) {
2 console.log('This script must be invoked using the casperjs executable'); 4 console.log('This script must be invoked using the casperjs executable');
3 phantom.exit(1); 5 phantom.exit(1);
...@@ -15,6 +17,7 @@ var casper = require('casper').create({ ...@@ -15,6 +17,7 @@ var casper = require('casper').create({
15 17
16 // local utils 18 // local utils
17 function checkSelfTest(tests) { 19 function checkSelfTest(tests) {
20 "use strict";
18 var isCasperTest = false; 21 var isCasperTest = false;
19 tests.forEach(function(test) { 22 tests.forEach(function(test) {
20 var testDir = fs.absolute(fs.dirname(test)); 23 var testDir = fs.absolute(fs.dirname(test));
...@@ -28,6 +31,7 @@ function checkSelfTest(tests) { ...@@ -28,6 +31,7 @@ function checkSelfTest(tests) {
28 } 31 }
29 32
30 function checkIncludeFile(include) { 33 function checkIncludeFile(include) {
34 "use strict";
31 var absInclude = fs.absolute(include.trim()); 35 var absInclude = fs.absolute(include.trim());
32 if (!fs.exists(absInclude)) { 36 if (!fs.exists(absInclude)) {
33 casper.warn("%s file not found, can't be included", absInclude); 37 casper.warn("%s file not found, can't be included", absInclude);
...@@ -60,6 +64,7 @@ if (casper.cli.get('no-colors') === true) { ...@@ -60,6 +64,7 @@ if (casper.cli.get('no-colors') === true) {
60 // test paths are passed as args 64 // test paths are passed as args
61 if (casper.cli.args.length) { 65 if (casper.cli.args.length) {
62 tests = casper.cli.args.filter(function(path) { 66 tests = casper.cli.args.filter(function(path) {
67 "use strict";
63 return fs.isFile(path) || fs.isDirectory(path); 68 return fs.isFile(path) || fs.isDirectory(path);
64 }); 69 });
65 } else { 70 } else {
...@@ -75,6 +80,7 @@ if (!phantom.casperSelfTest && checkSelfTest(tests)) { ...@@ -75,6 +80,7 @@ if (!phantom.casperSelfTest && checkSelfTest(tests)) {
75 80
76 // includes handling 81 // includes handling
77 this.loadIncludes.forEach(function(include){ 82 this.loadIncludes.forEach(function(include){
83 "use strict";
78 var container; 84 var container;
79 if (casper.cli.has(include)) { 85 if (casper.cli.has(include)) {
80 container = casper.cli.get(include).split(',').map(function(file) { 86 container = casper.cli.get(include).split(',').map(function(file) {
...@@ -89,6 +95,7 @@ this.loadIncludes.forEach(function(include){ ...@@ -89,6 +95,7 @@ this.loadIncludes.forEach(function(include){
89 95
90 // test suites completion listener 96 // test suites completion listener
91 casper.test.on('tests.complete', function() { 97 casper.test.on('tests.complete', function() {
98 "use strict";
92 this.renderResults(true, undefined, casper.cli.get('xunit') || undefined); 99 this.renderResults(true, undefined, casper.cli.get('xunit') || undefined);
93 }); 100 });
94 101
......
1 /** 1 /**
2 * CasperJS local HTTP test server 2 * CasperJS local HTTP test server
3 */ 3 */
4
5 /*global phantom casper require*/
6
4 var colorizer = require('colorizer').create('Colorizer'); 7 var colorizer = require('colorizer').create('Colorizer');
5 var fs = require('fs'); 8 var fs = require('fs');
6 var utils = require('utils'); 9 var utils = require('utils');
...@@ -9,10 +12,12 @@ var service; ...@@ -9,10 +12,12 @@ var service;
9 var testServerPort = 54321; 12 var testServerPort = 54321;
10 13
11 function info(message) { 14 function info(message) {
15 "use strict";
12 console.log(colorizer.colorize('INFO', 'INFO_BAR') + ' ' + message); 16 console.log(colorizer.colorize('INFO', 'INFO_BAR') + ' ' + message);
13 } 17 }
14 18
15 service = server.listen(testServerPort, function(request, response) { 19 service = server.listen(testServerPort, function(request, response) {
20 "use strict";
16 var pageFile = fs.pathJoin(phantom.casperPath, request.url); 21 var pageFile = fs.pathJoin(phantom.casperPath, request.url);
17 if (!fs.exists(pageFile) || !fs.isFile(pageFile)) { 22 if (!fs.exists(pageFile) || !fs.isFile(pageFile)) {
18 response.statusCode = 404; 23 response.statusCode = 404;
...@@ -26,16 +31,18 @@ service = server.listen(testServerPort, function(request, response) { ...@@ -26,16 +31,18 @@ service = server.listen(testServerPort, function(request, response) {
26 31
27 // overriding Casper.open to prefix all test urls 32 // overriding Casper.open to prefix all test urls
28 casper.setFilter('open.location', function(location) { 33 casper.setFilter('open.location', function(location) {
34 "use strict";
29 if (/^file/.test(location)) { 35 if (/^file/.test(location)) {
30 return location; 36 return location;
31 } 37 }
32 if (!/^http/.test(location)) { 38 if (!/^http/.test(location)) {
33 return f('http://localhost:%d/%s', testServerPort, location); 39 return utils.format('http://localhost:%d/%s', testServerPort, location);
34 } 40 }
35 return location; 41 return location;
36 }); 42 });
37 43
38 // test suites completion listener 44 // test suites completion listener
39 casper.test.on('tests.complete', function() { 45 casper.test.on('tests.complete', function() {
46 "use strict";
40 server.close(); 47 server.close();
41 }); 48 });
......