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&paramB=2");
             deepEqual(
                 result,
                 {
+                    protocol: "http:",
+                    host: "tincanapi.com:8080",
+                    hostname: "tincanapi.com",
+                    port: "8080",
+                    pathname: "/TinCanJS/Test/TinCan.Utils_parseURL/test",
+                    search: "?paramA=1&paramB=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&paramB=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&paramB=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&paramB=2&paramC=%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&paramB=2&paramC=%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(