Created
December 12, 2009 18:04
-
-
Save tswicegood/254984 to your computer and use it in GitHub Desktop.
As described and demoed at http://24ways.org/2009/self-testing-pages-with-javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
sleuth.js | |
@author Ross Bruniges | |
@version 0.9 | |
A light-weight JavaScript based HTML/CSS/JS confirmation tool (NOT a validator) | |
Write tests in JSON (example found at - ????) | |
Call in page via sleuth.test_page.init(json_objs) | |
*/ | |
var sleuth = window.sleuth || {}; | |
sleuth.test_page={ | |
// variable set for the main container - used for appending to later | |
$logger:"", | |
// number of tests run (in total if using multiple JSON) | |
num_passed:0, | |
// total number of failures | |
num_failed:0, | |
// CSS class applied to passing and failing tests | |
test_fail_class:"fail", | |
test_pass_class:"win", | |
// message for when tests pass | |
test_pass_message:"Tests run and passed :)", | |
// location of CSS for console - CSS is loaded in via JS | |
console_css_location:"js/tests/test_suite.css", | |
// version of jQuery to use if it's not already in the project | |
/** | |
Setting up the test suite: | |
- Loads jQuery if required | |
- appends the console element to the body | |
- loads in CSS for console | |
- applies click event to show/hide console | |
@param json_url the json objects used in this particular page | |
(can be either a single string or an array of strings) | |
*/ | |
init:function(json_url) { | |
$('body').append('<div id="test_console"><div class="controls"><p class="title">sleuth.js</p><a href="#">Hide results</a></div><div class="bd"></div></div>'); | |
sleuth.test_page.$logger = $('#test_console .bd'); | |
// load in CSS for the logger | |
var style = document.createElement('link'); | |
style.type = "text/css"; | |
style.rel = "stylesheet"; | |
style.href = sleuth.test_page.console_css_location; | |
document.getElementsByTagName('head')[0].appendChild(style); | |
// assign functionality for the show/hide link | |
$('#test_console a').click(function() { | |
sleuth.test_page.toggle_results($(this)); | |
return false; | |
}); | |
// getting the JSON | |
sleuth.test_page.get_JSON(json_url); | |
}, | |
/** | |
Helper function to show/hide the test results on click | |
@param elm the HTML element that the click event is to be attached to | |
*/ | |
toggle_results:function(elm) { | |
if (sleuth.test_page.$logger.css('display') === "none") { | |
sleuth.test_page.$logger.show("slow"); | |
elm.text("Hide results"); | |
elm.removeClass('show'); | |
elm.parent().fadeTo("slow",1); | |
} else { | |
sleuth.test_page.$logger.hide("slow"); | |
elm.text("Show results"); | |
elm.addClass('show'); | |
elm.parent().fadeTo("slow", 0.3); | |
} | |
}, | |
/** | |
Loads the JSON objects and on success calls parse_JSON to start the tests | |
@param json Either a string containing the path to a single JSON object | |
or an array containing multiple paths (as strings) | |
*/ | |
get_JSON:function(json) { | |
$.getJSON(json, function(json) { | |
sleuth.test_page.parse_JSON(json); | |
}); | |
}, | |
/** | |
Parses through the tests contained in the JSON and calls init_tests for each, | |
appends a title to the console, if detects the last iteration | |
calls create_summary | |
@param data loaded JSON data sent from get_JSON | |
*/ | |
parse_JSON:function(data) { | |
sleuth.test_page.$logger.append("<p class='title'>Results for " + data.title + "</p>"); | |
$.each(data.tests, function(key, value) { | |
sleuth.test_page.init_tests(key, value); | |
}); | |
sleuth.test_page.create_summary(); | |
}, | |
/** | |
Function containing the initial check run on each test: | |
- type check to see if we need run this test at all (used when rechecking) | |
- grabs selector defined in test.selector and checks for existance | |
- appends div to console for display of message | |
@param i current index of iteration through test (used for appending) | |
@param obj the current test we're running though (JSON segment) | |
@return true is test pass (and no further tests required) | |
@return false is test fails at the first hurdle | |
*/ | |
init_tests:function(i,obj) { | |
// grabbing the selector we're looking for | |
var $master_elm = $(obj.selector); | |
// assigning this to a var as we'll use it in all places where we traverse through child elements | |
sleuth.test_page.$logger.append("<div id='test_" + i + "' class='message'><p><em>" + obj.name + "</em></p></div>"); | |
// assing this to a varible here - we'll need it for both cases | |
var $container = $('#test_' + i); | |
// OK - so if we don't find the initial selector we have big fail, so lets check for this first | |
if (!$master_elm.length) { | |
var err_sum = obj.message.replace(/VAR/gi, obj.selector); | |
sleuth.test_page.haz_failed(err_sum, $container); | |
return; | |
} | |
/* | |
now the first hurdle has been cleared we perform the additional checks if required, | |
if not then woohoo we've passed! | |
*/ | |
if (obj.check_for) { | |
$.each(obj.check_for,function(key, value){ | |
sleuth.test_page.assign_checks($master_elm, $container, key, value); | |
}); | |
} else { | |
sleuth.test_page.tests_passed($container); | |
return; | |
} | |
}, | |
/** | |
Method which define the test to be run on each JSON segment | |
If sent an invalid test it fails with a generic error message | |
@param target_selector HTML object to apply test to | |
@param error_elm the div to append message to | |
@param check_name name of the test to apply (used in switch statment) | |
@param obj JSON segment containing values to be used in test | |
*/ | |
assign_checks:function(target_selector, error_elm, check_name, obj) { | |
// ok - so we know the checks we have and will use switch to pass over them and assign checks as appropriate | |
switch(check_name) { | |
case "contains": | |
sleuth.test_page.confirm_html(target_selector, error_elm, obj); | |
break; | |
default: | |
sleuth.test_page.haz_failed("This will fail as no code has been written to perform the check against " + check_name, error_elm); | |
break; | |
} | |
}, | |
/** | |
Method called when tests fail: | |
- adds sleuth.test_page.test_fail_class to message container for styling | |
- increments sleuth.test_page.num_failed | |
- appends message to message container | |
@param msg the message we're providing to the user | |
@param elm the div element for this test to apply msg to | |
*/ | |
haz_failed:function(msg,elm) { | |
elm.addClass(sleuth.test_page.test_fail_class); | |
sleuth.test_page.num_failed++; | |
elm.append("<p>" + msg + "</p>"); | |
}, | |
/** | |
Method called when tests pass: | |
- increments sleuth.test_page.num_passed | |
- appends message to message container | |
@param msg the message we're providing to the user | |
@param elm the div element for this test to apply msg to | |
*/ | |
tests_passed:function(elm) { | |
elm.addClass(sleuth.test_page.test_pass_class); | |
sleuth.test_page.num_passed++; | |
elm.append("<p>" + sleuth.test_page.test_pass_message + "</p>"); | |
}, | |
/** | |
Method to create a summary of all tests to the user on tests completion | |
Creates and assigns click events to a link allowing user to recheck page | |
(which will also call AJAX only tests) | |
*/ | |
create_summary:function() { | |
var num_tests = sleuth.test_page.num_passed + sleuth.test_page.num_failed; | |
// creating html string to append when finished | |
var summary_html = "<div class='test_summary'><p><strong>Summary</strong></p><ul>"; | |
summary_html += "<li>Tests run: " + num_tests + "</li>"; | |
summary_html += "<li>Tests passed: " + sleuth.test_page.num_passed + "</li>"; | |
summary_html += "<li>Tests failed: " + sleuth.test_page.num_failed + "</li>"; | |
summary_html += "</ul>"; | |
summary_html += "</div>"; | |
sleuth.test_page.$logger.prepend(summary_html); | |
}, | |
/** | |
Method to parse page and look for specific HTML selectors: | |
- creates empty arrays for all missing elements | |
- checks for position length of jQuery object gained from using HTML selector | |
- pushes all un-matched files into array of missing files | |
- if unique object detected in JSON checks for length of jQuery object of greater than 1 | |
@param target_selector jQuery object containing the element we're testing against | |
@param error_elm the div to apply message to on completion | |
@param obj JSON segment containing test data and nested | |
tests for asserting only one selector on the page (optional) | |
*/ | |
confirm_html:function(target_selector, error_elm, obj) { | |
var missing_elms = []; | |
$.each(obj.elements, function(i, val) { | |
if (!target_selector.find(val).length) { | |
missing_elms.push(val); | |
} | |
}); | |
if (missing_elms.length) { | |
var file_list = missing_elms.join('</li><li>'); | |
var err_sum = obj.message.replace(/VAR/gi, file_list); | |
sleuth.test_page.haz_failed(err_sum, error_elm); | |
return; | |
} | |
// we've made it through all the checks unscathed - so let's pass the test | |
sleuth.test_page.tests_passed(error_elm); | |
return; | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#test_console { | |
border: 1px solid #999; | |
background: #fff; | |
position: absolute; | |
font-size: 10px; | |
top: 10px; | |
right: 10px; | |
text-align: left; | |
-moz-border-radius: 5px; | |
-moz-box-shadow: 5px 5px 5px #000; | |
padding: 10px; | |
/* we need a width to constrain the box */ | |
_width: 320px; | |
} | |
#test_console .controls { | |
overflow: hidden; | |
zoom: 1; | |
} | |
#test_console a { | |
float: right; | |
font-weight: bold; | |
padding-right: 20px; | |
background: transparent url(icons/hide.png) no-repeat top right; | |
line-height: 1.6; | |
margin-left: 10px; | |
} | |
#test_console a.show { | |
background-image: url(icons/show.png); | |
} | |
#test_console .bd { | |
width: 300px; | |
height: 300px; | |
margin-top: 10px; | |
overflow-y:scroll; | |
} | |
#test_console .bd a { | |
float: none; | |
margin-top: 1em; | |
display: block; | |
background: none; | |
padding-right: 0; | |
margin-left: 0; | |
} | |
#test_console .title { | |
font-weight: bold; | |
font-size: 1.6em; | |
float: left; | |
} | |
#test_console .bd .title { | |
font-size: 1.2em; | |
padding-top: 0.5em; | |
float: none; | |
clear:both; | |
} | |
#test_console .bd .message { | |
border-bottom: 2px solid #fff; | |
padding: 0.2em 5px 0.2em 50px; | |
background: transparent url(icons/win.jpg) no-repeat 5px 5px; | |
min-height: 50px; | |
/* IE has epic min-height FAIL */ | |
_height: 50px; | |
margin-right: 10px; | |
background-color: #E5EECC; | |
color: #617F10; | |
} | |
#test_console .bd div:last-child { | |
border-bottom: none; | |
} | |
#test_console .bd div.fail { | |
background-image: url(icons/fail.jpg); | |
background-color: #FCEEF0; | |
color: #B90D33; | |
} | |
#test_console p, | |
#test_console ul, | |
#test_console fieldset { | |
font-size: 1.2em; | |
} | |
#test_console p { | |
margin-bottom: 0.5em; | |
} | |
#test_console li { | |
list-style-type: disc; | |
margin-left: 20px; | |
} | |
#test_console p em { | |
font-style: normal; | |
font-weight: bold; | |
} | |
#test_console div.test_summary { | |
background: none; | |
padding-left: 0; | |
color: #666; | |
width: 45%; | |
float: left; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment