From a3a9f2c2fbf26d97fd0b22d44d9a2adde9c63a8c Mon Sep 17 00:00:00 2001 From: "Brian J. Miller" <brian.miller@scorm.com> Date: Tue, 17 Feb 2015 15:40:09 -0600 Subject: [PATCH 1/5] Display config settings on test index --- test/index.html | 89 +++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/test/index.html b/test/index.html index 1878ab6..bccff38 100644 --- a/test/index.html +++ b/test/index.html @@ -20,42 +20,59 @@ </head> <body> -<h2>Full Suite</h2> -<a href="complete.html">Complete Test</a> - -<h2>Single Test Files</h2> -<ul> - <li><a href="single/TinCan.html">TinCan</a></li> - <li><a href="single/TinCan-async.html">TinCan Async</a></li> - <li><a href="single/TinCan-sync.html">TinCan Sync</a></li> - <li><a href="single/LRS.html">LRS</a></li> - <li><a href="single/LRS-browser.html">LRS (Browser Only)</a></li> - <li><a href="single/About.html">About</a></li> - <li><a href="single/State.html">State</a></li> - <li><a href="single/ActivityProfile.html">ActivityProfile</a></li> - <li><a href="single/AgentProfile.html">AgentProfile</a></li> - <li><a href="single/StatementsResult.html">StatementsResult</a></li> - <li><a href="single/Agent.html">Agent</a></li> - <li><a href="single/Group.html">Group</a></li> - <li><a href="single/AgentAccount.html">AgentAccount</a></li> - <li><a href="single/Activity.html">Activity</a></li> - <li><a href="single/ActivityDefinition.html">ActivityDefinition</a></li> - <li><a href="single/Context.html">Context</a></li> - <li><a href="single/ContextActivities.html">ContextActivities</a></li> - <li><a href="single/InteractionComponent.html">InteractionComponent</a></li> - <li><a href="single/Result.html">Result</a></li> - <li><a href="single/Score.html">Score</a></li> - <li><a href="single/Statement.html">Statement</a></li> - <li><a href="single/StatementRef.html">StatementRef</a></li> - <li><a href="single/SubStatement.html">SubStatement</a></li> - <li><a href="single/Verb.html">Verb</a></li> - <li><a href="single/Utils.html">Utils</a></li> -</ul> - -<h2>Special Conditions</h2> -<ul> - <li><a href="single/offline.html">Offline / CORS Server Denied</a></li> -</ul> +<div style="display: inline-block; *display: inline; vertical-align: top; width: 30%;"> + <h2>Full Suite</h2> + <a href="complete.html">Complete Test</a> + + <h2>Single Test Files</h2> + <ul> + <li><a href="single/TinCan.html">TinCan</a></li> + <li><a href="single/TinCan-async.html">TinCan Async</a></li> + <li><a href="single/TinCan-sync.html">TinCan Sync</a></li> + <li><a href="single/LRS.html">LRS</a></li> + <li><a href="single/LRS-browser.html">LRS (Browser Only)</a></li> + <li><a href="single/About.html">About</a></li> + <li><a href="single/State.html">State</a></li> + <li><a href="single/ActivityProfile.html">ActivityProfile</a></li> + <li><a href="single/AgentProfile.html">AgentProfile</a></li> + <li><a href="single/StatementsResult.html">StatementsResult</a></li> + <li><a href="single/Agent.html">Agent</a></li> + <li><a href="single/Group.html">Group</a></li> + <li><a href="single/AgentAccount.html">AgentAccount</a></li> + <li><a href="single/Activity.html">Activity</a></li> + <li><a href="single/ActivityDefinition.html">ActivityDefinition</a></li> + <li><a href="single/Context.html">Context</a></li> + <li><a href="single/ContextActivities.html">ContextActivities</a></li> + <li><a href="single/InteractionComponent.html">InteractionComponent</a></li> + <li><a href="single/Result.html">Result</a></li> + <li><a href="single/Score.html">Score</a></li> + <li><a href="single/Statement.html">Statement</a></li> + <li><a href="single/StatementRef.html">StatementRef</a></li> + <li><a href="single/SubStatement.html">SubStatement</a></li> + <li><a href="single/Verb.html">Verb</a></li> + <li><a href="single/Utils.html">Utils</a></li> + </ul> + + <h2>Special Conditions</h2> + <ul> + <li><a href="single/offline.html">Offline / CORS Server Denied</a></li> + </ul> +</div> +<div style="display: inline-block; *display: inline; vertical-align: top;"> + <h2>Configuration</h2> + <pre id="config"></pre> +</div> + +<script> + if (typeof module === "undefined") { + module = {}; + } +</script> +<script src="config.js"></script> +<script> + var configEl = document.getElementById("config"); + configEl.innerHTML = JSON.stringify(module.exports, null, 4); +</script> </body> </html> From 1d2da6e7a6ada69ef2e4b024c5e00804c56de360 Mon Sep 17 00:00:00 2001 From: "Brian J. Miller" <brian.miller@scorm.com> Date: Thu, 19 Feb 2015 13:36:07 -0600 Subject: [PATCH 2/5] Make it easy to run a set of unit tests from the CLI --- test/node-runner.js | 71 +++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/test/node-runner.js b/test/node-runner.js index 1489adc..7af05ef 100644 --- a/test/node-runner.js +++ b/test/node-runner.js @@ -1,4 +1,48 @@ -var testRunner = require("qunit"); +var testRunner = require("qunit"), + args = process.argv.slice(2), + tests, + isJSUnitFile = /^js\/unit\//, + isAbsoluteFile = /^\//; + +if (args.length) { + tests = args; +} +else { + tests = [ + "State.js", + "ActivityProfile.js", + "AgentProfile.js", + "StatementsResult.js", + "Agent.js", + "Group.js", + "Activity.js", + "ActivityDefinition.js", + "ContextActivities.js", + "Context.js", + "InteractionComponent.js", + "Result.js", + "Score.js", + "Statement.js", + "StatementRef.js", + "SubStatement.js", + "Verb.js", + "Utils.js", + "TinCan.js", + "TinCan-async.js", + "LRS.js", + "About.js" + ]; +} + +for (i = 0; i < tests.length; i += 1) { + if (isJSUnitFile.test(tests[i])) { + tests[i] = __dirname + "/" + tests[i]; + continue; + } + if (! isAbsoluteFile.test(tests[i])) { + tests[i] = __dirname + "/js/unit/" + tests[i]; + } +} testRunner.setup( { @@ -31,30 +75,7 @@ testRunner.run( { path: __dirname + "/config.js", namespace: "TinCanTestCfg" } ], code: { path: __dirname + "/../build/tincan-node.js", namespace: "TinCan" }, - tests: [ - __dirname + "/js/unit/State.js" - , __dirname + "/js/unit/ActivityProfile.js" - , __dirname + "/js/unit/AgentProfile.js" - , __dirname + "/js/unit/StatementsResult.js" - , __dirname + "/js/unit/Agent.js" - , __dirname + "/js/unit/Group.js" - , __dirname + "/js/unit/Activity.js" - , __dirname + "/js/unit/ActivityDefinition.js" - , __dirname + "/js/unit/ContextActivities.js" - , __dirname + "/js/unit/Context.js" - , __dirname + "/js/unit/InteractionComponent.js" - , __dirname + "/js/unit/Result.js" - , __dirname + "/js/unit/Score.js" - , __dirname + "/js/unit/Statement.js" - , __dirname + "/js/unit/StatementRef.js" - , __dirname + "/js/unit/SubStatement.js" - , __dirname + "/js/unit/Verb.js" - , __dirname + "/js/unit/Utils.js" - , __dirname + "/js/unit/TinCan.js" - , __dirname + "/js/unit/TinCan-async.js" - , __dirname + "/js/unit/LRS.js" - , __dirname + "/js/unit/About.js" - ] + tests: tests }, function (err, report) { if (err) { From eb643dcc3706a282ff64eb8002d689f569ac244f Mon Sep 17 00:00:00 2001 From: "Brian J. Miller" <brian.miller@scorm.com> Date: Thu, 19 Feb 2015 13:43:31 -0600 Subject: [PATCH 3/5] Make grunt fail when there is a pkg/bower version mismatch --- Gruntfile.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index afcd67b..71ab1c6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,7 +28,9 @@ module.exports = function(grunt) { "src/About.js" ], browserFileList = coreFileList.slice(), - nodeFileList = coreFileList.slice(); + nodeFileList = coreFileList.slice(), + pkg, + bower; browserFileList.push( "src/Environment/Browser.js" @@ -37,9 +39,16 @@ module.exports = function(grunt) { "src/Environment/Node.js" ); + pkg = grunt.file.readJSON("package.json"); + bower = grunt.file.readJSON("bower.json"); + + if (pkg.version !== bower.version) { + grunt.fail.fatal("package.json and bower.json versions do not match"); + } + // Project configuration. grunt.initConfig({ - pkg: grunt.file.readJSON("package.json"), + pkg: pkg, watch: { files: ["src/**/*.js"], From ee1c12e6ac55a709f913494736ecd1a499abc0d1 Mon Sep 17 00:00:00 2001 From: "Brian J. Miller" <brian.miller@scorm.com> Date: Thu, 19 Feb 2015 13:37:14 -0600 Subject: [PATCH 4/5] Improve TinCan.Utils.parseURL for edge cases --- src/Utils.js | 58 ++++++++++++++++++++++++++++++------------- test/js/unit/Utils.js | 41 +++++++++++++++++++++++++++--- 2 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/Utils.js b/src/Utils.js index f14358e..2f77603 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -240,26 +240,50 @@ TinCan client library @private */ parseURL: function (url) { - var parts = String(url).split("?"), - pairs, - pair, - i, - params = {} - ; - if (parts.length === 2) { - pairs = parts[1].split("&"); - for (i = 0; i < pairs.length; i += 1) { - pair = pairs[i].split("="); - if (pair.length === 2 && pair[0]) { - params[pair[0]] = decodeURIComponent(pair[1]); - } + // + // see http://stackoverflow.com/a/21553982 + // and http://stackoverflow.com/a/2880929 + // + var reURLInformation, + match, + result, + paramMatch, + pl = /\+/g, // Regex for replacing addition symbol with a space + search = /([^&=]+)=?([^&]*)/g, + decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); }; + + reURLInformation = new RegExp( + [ + "^(https?:)//", // protocol + "(([^:/?#]*)(?::([0-9]+))?)", // host (hostname and port) + "(/[^?#]*)", // pathname + "(\\?[^#]*|)", // search + "(#.*|)$" // hash + ].join("") + ); + match = url.match(reURLInformation); + result = { + protocol: match[1], + host: match[2], + hostname: match[3], + port: match[4], + pathname: match[5], + search: match[6], + hash: match[7], + params: {} + }; + + // 'path' is for backwards compatibility + result.path = result.protocol + "//" + result.host + result.pathname; + + if (result.search !== "") { + // extra parens to let jshint know this is an expression + while ((paramMatch = search.exec(result.search.substring(1)))) { + result.params[decode(paramMatch[1])] = decode(paramMatch[2]); } } - return { - path: parts[0], - params: params - }; + return result; }, /** diff --git a/test/js/unit/Utils.js b/test/js/unit/Utils.js index 3d9aff7..1b70a63 100644 --- a/test/js/unit/Utils.js +++ b/test/js/unit/Utils.js @@ -91,24 +91,59 @@ deepEqual( result, { + protocol: "http:", + host: "tincanapi.com:8080", + hostname: "tincanapi.com", + port: "8080", + pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test", + search: "", + hash: "", params: {}, path: "http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test" }, - "return value: no params" + "return value: no params" ); result = TinCan.Utils.parseURL("http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test?paramA=1¶mB=2"); deepEqual( result, { + protocol: "http:", + host: "tincanapi.com:8080", + hostname: "tincanapi.com", + port: "8080", + pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test", + search: "?paramA=1¶mB=2", + hash: "", params: { paramA: "1", paramB: "2" }, path: "http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test" }, - "return value: with params" - ); + "return value: with params" + ); + + result = TinCan.Utils.parseURL("https://tincanapi.com/TinCanJS/Test/TinCan.Utils_parseURL/test?paramA=1¶mB=2&weirdParam=odd?secondQuestionMark#withHash"); + deepEqual( + result, + { + protocol: "https:", + host: "tincanapi.com", + hostname: "tincanapi.com", + port: undefined, + pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test", + search: "?paramA=1¶mB=2&weirdParam=odd?secondQuestionMark", + hash: "#withHash", + params: { + paramA: "1", + paramB: "2", + weirdParam: "odd?secondQuestionMark" + }, + path: "https://tincanapi.com/TinCanJS/Test/TinCan.Utils_parseURL/test" + }, + "return value: with odd params, https no port, and hash" + ); } ); test( From e779bb14b0ddf34cc5ba8faab238157874de493d Mon Sep 17 00:00:00 2001 From: "Brian J. Miller" <brian.miller@scorm.com> Date: Tue, 3 Mar 2015 08:54:10 -0600 Subject: [PATCH 5/5] Add unit test for encoded # in query params --- test/js/unit/Utils.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/js/unit/Utils.js b/test/js/unit/Utils.js index 1b70a63..becebd5 100644 --- a/test/js/unit/Utils.js +++ b/test/js/unit/Utils.js @@ -144,6 +144,27 @@ }, "return value: with odd params, https no port, and hash" ); + + result = TinCan.Utils.parseURL("http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test?paramA=1¶mB=2¶mC=%23isahashsymbol#theRealHash"); + deepEqual( + result, + { + protocol: "http:", + host: "tincanapi.com:8080", + hostname: "tincanapi.com", + port: "8080", + pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test", + search: "?paramA=1¶mB=2¶mC=%23isahashsymbol", + hash: "#theRealHash", + params: { + paramA: "1", + paramB: "2", + paramC: "#isahashsymbol" + }, + path: "http://tincanapi.com:8080/TinCanJS/Test/TinCan.Utils_parseURL/test" + }, + "return value: with params" + ); } ); test(