summaryrefslogtreecommitdiffstats
path: root/content/browser/resources
diff options
context:
space:
mode:
authortyoverby@chromium.org <tyoverby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-26 17:45:59 +0000
committertyoverby@chromium.org <tyoverby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-26 17:45:59 +0000
commit446cbb7551632e737d32998107b652e37a2f5ca7 (patch)
tree77793fca3dbd4dd6ec003e56d2adb49750ec40a0 /content/browser/resources
parent7bbf03c589baa5f2bc26b3748c84ccd979e056de (diff)
downloadchromium_src-446cbb7551632e737d32998107b652e37a2f5ca7.zip
chromium_src-446cbb7551632e737d32998107b652e37a2f5ca7.tar.gz
chromium_src-446cbb7551632e737d32998107b652e37a2f5ca7.tar.bz2
First part of the new media-internals javascript frontend
BUG= 260005 Review URL: https://chromiumcodereview.appspot.com/20063004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@213949 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/resources')
-rw-r--r--content/browser/resources/media/new/media_internals.html4
-rw-r--r--content/browser/resources/media/new/media_internals.js7
-rw-r--r--content/browser/resources/media/new/player_info.js80
-rw-r--r--content/browser/resources/media/new/player_info_test.html146
-rw-r--r--content/browser/resources/media/new/player_manager.js111
-rw-r--r--content/browser/resources/media/new/player_manager_test.html155
-rw-r--r--content/browser/resources/media/new/webui_resource_test.js210
7 files changed, 707 insertions, 6 deletions
diff --git a/content/browser/resources/media/new/media_internals.html b/content/browser/resources/media/new/media_internals.html
index 288a7b9..0e95353 100644
--- a/content/browser/resources/media/new/media_internals.html
+++ b/content/browser/resources/media/new/media_internals.html
@@ -1,11 +1,9 @@
-<!DOCTYPE HTML>
-
<!--
Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
-
+<!DOCTYPE html>
<html i18n-values="dir:textdirection">
<head>
<meta charset="utf-8">
diff --git a/content/browser/resources/media/new/media_internals.js b/content/browser/resources/media/new/media_internals.js
index 11f6749..103ef74 100644
--- a/content/browser/resources/media/new/media_internals.js
+++ b/content/browser/resources/media/new/media_internals.js
@@ -2,14 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-var media = {}
+var media = {};
-var doNothing = function () {};
+var doNothing = function() {};
+// Silence the backend calls.
media.initialize = doNothing;
media.addAudioStream = doNothing;
media.cacheEntriesByKey = doNothing;
-media.onReceiveEverything = doNothing;
+media.onReceiveEverything = doNothing;
media.onItemDeleted = doNothing;
media.onRendererTerminated = doNothing;
media.onNetUpdate = doNothing;
diff --git a/content/browser/resources/media/new/player_info.js b/content/browser/resources/media/new/player_info.js
new file mode 100644
index 0000000..af1f194
--- /dev/null
+++ b/content/browser/resources/media/new/player_info.js
@@ -0,0 +1,80 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview A class for keeping track of the details of a player.
+ */
+
+var PlayerInfo = (function() {
+ 'use strict';
+
+ /**
+ * A class that keeps track of properties on a media player.
+ * @param id A unique id that can be used to identify this player.
+ */
+ function PlayerInfo(id) {
+ this.id = id;
+ // The current value of the properties for this player.
+ this.properties = {};
+ // All of the past (and present) values of the properties.
+ this.pastValues = {};
+
+ // Every single event in the order in which they were received.
+ this.allEvents = [];
+ this.lastRendered = 0;
+
+ this.firstTimestamp_ = -1;
+ }
+
+ PlayerInfo.prototype = {
+ /**
+ * Adds or set a property on this player.
+ * This is the default logging method as it keeps track of old values.
+ * @param timestamp The time in milliseconds since the Epoch.
+ * @param key A String key that describes the property.
+ * @param value The value of the property.
+ */
+ addProperty: function(timestamp, key, value) {
+ // The first timestamp that we get will be recorded.
+ // Then, all future timestamps are deltas of that.
+ if (this.firstTimestamp_ === -1) {
+ this.firstTimestamp_ = timestamp;
+ }
+
+ if (typeof key !== 'string') {
+ throw new Error(typeof key + ' is not a valid key type');
+ }
+
+ this.properties[key] = value;
+
+ if (!this.pastValues[key]) {
+ this.pastValues[key] = [];
+ }
+
+ var recordValue = {
+ time: timestamp - this.firstTimestamp_,
+ key: key,
+ value: value
+ };
+
+ this.pastValues[key].push(recordValue);
+ this.allEvents.push(recordValue);
+ },
+
+ /**
+ * Adds or set a property on this player.
+ * Does not keep track of old values. This is better for
+ * values that get spammed repeatedly.
+ * @param timestamp The time in milliseconds since the Epoch.
+ * @param key A String key that describes the property.
+ * @param value The value of the property.
+ */
+ addPropertyNoRecord: function(timestamp, key, value) {
+ this.addProperty(timestamp, key, value);
+ this.allEvents.pop();
+ }
+ };
+
+ return PlayerInfo;
+}());
diff --git a/content/browser/resources/media/new/player_info_test.html b/content/browser/resources/media/new/player_info_test.html
new file mode 100644
index 0000000..46cc05ee
--- /dev/null
+++ b/content/browser/resources/media/new/player_info_test.html
@@ -0,0 +1,146 @@
+<!--
+Copyright 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="webui_resource_test.js"></script>
+ <script src="player_manager.js"></script>
+ <script src="player_info.js"></script>
+ </head>
+ <body>
+ <script>
+ window.setUp = function() {
+ window.pi = new PlayerInfo('example_id');
+ };
+
+ window.tearDown = function() {
+ window.pi = null;
+ };
+
+ // Test that an ID is set correctly.
+ window.testConstructorStringID = function() {
+ assertEquals('example_id', window.pi.id);
+ };
+
+ // Test that numerical IDs are valid.
+ window.testConstructorNumberId = function() {
+ var pi = new PlayerInfo(5);
+ assertEquals(5, pi.id);
+ };
+
+ // Make sure that a new PlayerInfo has no events.
+ window.testEmptyEvents = function() {
+ assertEquals(0, window.pi.allEvents.length);
+ };
+
+ // Check that the most recent property gets updated.
+ window.testAddProperty = function() {
+ var key = 'key',
+ value = 'value',
+ value2 = 'value2';
+
+ window.pi.addProperty(0, key, value);
+ assertEquals(value, window.pi.properties[key]);
+
+ window.pi.addProperty(0, key, value2);
+ assertEquals(value2, window.pi.properties[key]);
+
+ };
+
+ // Make sure that the first timestamp that gets sent
+ // is recorded as the base timestamp.
+ window.testFirstTimestamp = function() {
+ var pi = new PlayerInfo('example_ID');
+ var timestamp = 5000;
+ pi.addProperty(timestamp, 'key', 'value');
+
+ assertEquals(timestamp, pi.firstTimestamp_);
+ };
+
+ // Adding a property with a non-string key should
+ // throw an exception.
+ window.testWrongKeyType = function() {
+ var pi = new PlayerInfo('example_ID');
+ assertThrows(function() {
+ pi.addProperty(0, 5, 'some value');
+ });
+ };
+
+ // Subsequent events should have their log offset based
+ // on the first timestamp added.
+ window.testAddPropertyTimestampOffset = function() {
+ var firstTimestamp = 500,
+ secondTimestamp = 550,
+ deltaT = secondTimestamp - firstTimestamp,
+ key = 'key',
+ value = 'value';
+
+ var pi = new PlayerInfo('example_ID');
+ pi.addProperty(firstTimestamp, key, value);
+ pi.addProperty(secondTimestamp, key, value);
+
+ assertEquals(firstTimestamp, pi.firstTimestamp_);
+ assertEquals(0, pi.allEvents[0].time);
+ assertEquals(deltaT, pi.allEvents[1].time);
+
+ assertTrue(undefined !== pi.pastValues[key]);
+
+ console.log(pi.pastValues);
+
+ assertEquals(0, pi.pastValues[key][0].time);
+ assertEquals(deltaT, pi.pastValues[key][1].time);
+ };
+
+ // Check to make sure that properties are correctly
+ // added to the relevant pastValues array.
+ window.testAddPropertyPastValues = function() {
+ var pi = new PlayerInfo('example_ID'),
+ timestamp = 50,
+ key = 'key',
+ value = 'value';
+
+ pi.addProperty(timestamp, key, value);
+
+ assertEquals(value, pi.pastValues[key][0].value);
+ assertEquals(key, pi.pastValues[key][0].key);
+ assertEquals(0, pi.pastValues[key][0].time);
+ };
+
+ // The list of all events should be recorded in correctly.
+ window.testAllEvents = function() {
+ var pi = new PlayerInfo('example_ID'),
+ timestamp = 50,
+ key = 'key',
+ value = 'value',
+ key2 = 'key2',
+ value2 = 'value2';
+
+ pi.addProperty(timestamp, key, value);
+ assertEquals(value, pi.allEvents[0].value);
+ assertEquals(key, pi.allEvents[0].key);
+
+ pi.addProperty(timestamp, key2, value2);
+ assertEquals(value2, pi.allEvents[1].value);
+ assertEquals(key2, pi.allEvents[1].key);
+ };
+
+ // Using noRecord should make it not show up in allEvents,
+ // but it should still show up in pastValues[key].
+ window.testNoRecord = function() {
+ var pi = new PlayerInfo('example_ID'),
+ timestamp = 50,
+ key = 'key',
+ value = 'value';
+ pi.addPropertyNoRecord(timestamp, key, value);
+
+ assertEquals(value, pi.properties[key]);
+ assertEquals(0, pi.allEvents.length);
+ assertEquals(1, pi.pastValues[key].length);
+ };
+ runTests();
+ </script>
+ </body>
+</html>
diff --git a/content/browser/resources/media/new/player_manager.js b/content/browser/resources/media/new/player_manager.js
new file mode 100644
index 0000000..3de9335
--- /dev/null
+++ b/content/browser/resources/media/new/player_manager.js
@@ -0,0 +1,111 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Keeps track of all the existing
+ * PlayerProperty objects and is the entry-point for messages from the backend.
+ */
+var PlayerManager = (function() {
+ 'use strict';
+
+ function PlayerManager(renderManager) {
+ this.players_ = {};
+ this.renderman_ = renderManager;
+ renderManager.playerManager = this;
+
+ this.shouldRemovePlayer_ = function() {
+ // This is only temporary until we get the UI hooked up.
+ return true;
+ };
+ }
+
+ PlayerManager.prototype = {
+
+ /**
+ * Adds a player to the list of players to manage.
+ */
+ addPlayer: function(id) {
+ if (this.players_[id]) {
+ return;
+ }
+ // Make the PlayerProperty and add it to the mapping
+ this.players_[id] = new PlayerInfo(id);
+
+ this.renderman_.redrawList();
+ },
+
+ /**
+ * Attempts to remove a player from the UI.
+ * @param id The ID of the player to remove.
+ */
+ removePlayer: function(id) {
+ // Look at the check box to see if we should actually
+ // remove it from the UI
+ if (this.shouldRemovePlayer_()) {
+ delete this.players_[id];
+ this.renderman_.redrawList();
+ } else if (this.players_[id]) {
+ // Set a property on it to be removed at a later time
+ this.players_[id].toRemove = true;
+ }
+ },
+
+ /**
+ * Selects a player and displays it on the UI.
+ * This method is called from the UI.
+ * @param id The ID of the player to display.
+ */
+ selectPlayer: function(id) {
+ if (!this.players_[id]) {
+ throw new Error('[selectPlayer] Id ' + id + ' does not exist.');
+ }
+
+ this.renderman_.select(id);
+ },
+
+ updatePlayerInfoNoRecord: function(id, timestamp, key, value) {
+ if (!this.players_[id]) {
+ console.error('[updatePlayerInfo] Id ' + id +
+ ' does not exist');
+ return;
+ }
+
+ this.players_[id].addPropertyNoRecord(timestamp, key, value);
+
+ // If we can potentially rename the player, do so.
+ if (key === 'name' || key === 'url') {
+ this.renderman_.redrawList();
+ }
+
+ this.renderman_.update();
+ },
+
+ /**
+ *
+ * @param id The unique ID that identifies the player to be updated.
+ * @param timestamp The timestamp of when the change occured. This
+ * timestamp is *not* normalized.
+ * @param key The name of the property to be added/changed.
+ * @param value The value of the property.
+ */
+ updatePlayerInfo: function(id, timestamp, key, value) {
+ if (!this.players_[id]) {
+ console.error('[updatePlayerInfo] Id ' + id +
+ ' does not exist');
+ return;
+ }
+
+ this.players_[id].addProperty(timestamp, key, value);
+
+ // If we can potentially rename the player, do so.
+ if (key === 'name' || key === 'url') {
+ this.renderman_.redrawList();
+ }
+
+ this.renderman_.update();
+ }
+ };
+
+ return PlayerManager;
+}());
diff --git a/content/browser/resources/media/new/player_manager_test.html b/content/browser/resources/media/new/player_manager_test.html
new file mode 100644
index 0000000..eff78b5
--- /dev/null
+++ b/content/browser/resources/media/new/player_manager_test.html
@@ -0,0 +1,155 @@
+<!--
+Copyright 2013 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="webui_resource_test.js"></script>
+ <script src="player_manager.js"></script>
+ <script src="player_info.js"></script>
+ </head>
+ <body>
+ <script>
+ var doNothing = function() {
+ };
+
+ var emptyRenderMan = {
+ redrawList: doNothing,
+ update: doNothing,
+ select: doNothing
+ };
+
+ window.setUp = function() {
+ window.pm = new PlayerManager(emptyRenderMan);
+ };
+
+ window.tearDown = function() {
+ window.pm = null;
+ };
+
+ // Test a normal case of .addPlayer
+ window.testAddPlayer = function() {
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ };
+
+ // Make sure that adding a player forces a redraw
+ // on the renderManager.
+ window.testAddPlayerForceRedraw = function() {
+ var redrew = false;
+ var mockRenderManager = {
+ redrawList: function() {
+ redrew = true;
+ }
+ };
+ var pm = new PlayerManager(mockRenderManager);
+
+ pm.addPlayer('someid');
+ assertTrue(redrew);
+ };
+
+ // On occasion, the backend will add an existing ID multiple times.
+ // make sure this doesn't break anything.
+ window.testAddPlayerAlreadyExisting = function() {
+ window.pm.addPlayer('someid');
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ };
+
+ // If the removal is set, make sure that a player
+ // gets removed from the PlayerManager.
+ window.testRemovePlayerShouldRemove = function() {
+ // Because we don't have the checkbox.
+ window.pm.shouldRemovePlayer_ = function() {
+ return true;
+ };
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ window.pm.removePlayer('someid');
+ assertTrue(undefined === window.pm.players_['someid']);
+ };
+
+ // On the removal of a player, the renderer should be forced
+ // to redraw the list.
+ window.testRemovePlayerRedraw = function() {
+ var redrew = false;
+
+ var fakeObj = {
+ redrawList: function() {
+ redrew = true;
+ }
+ };
+
+ var pm = new PlayerManager(fakeObj);
+ // Because we don't have the checkbox;
+ pm.shouldRemovePlayer_ = function() {
+ return true;
+ };
+
+
+ pm.addPlayer('someid');
+ assertTrue(undefined !== pm.players_['someid']);
+ pm.removePlayer('someid');
+ assertTrue(undefined === pm.players_['someid']);
+
+ assertTrue(redrew);
+ };
+
+ // If you shouldn't remove the player, the player shouldn't be
+ // removed.
+ window.testRemovePlayerNoRemove = function() {
+ window.pm = new PlayerManager(emptyRenderMan);
+ // Because we don't have the checkbox;
+ window.pm.shouldRemovePlayer_ = function() {
+ return false;
+ };
+ window.pm.addPlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ window.pm.removePlayer('someid');
+ assertTrue(undefined !== window.pm.players_['someid']);
+ };
+
+
+ // Removing a nonexistant player shouldn't break anything
+ // The backend also occasionally does this.
+ window.testRemovePlayerNonExistant = function() {
+ // Because we don't have the checkbox;
+ window.pm.shouldRemovePlayer_ = function() {
+ return false;
+ };
+ window.pm.removePlayer('someid');
+ assertTrue(undefined === window.pm.players_['someid']);
+ };
+
+ // Trying to select a non-existant player should throw
+ // an exception
+ window.testSelectNonExistant = function() {
+ assertThrows(function() {
+ window.pm.selectPlayer('someId');
+ });
+ };
+
+ // Selecting an existing player should trigger a redraw
+ window.testSelectExistingPlayer = function() {
+ var selected = false;
+ var redrew = false;
+ var pm = new PlayerManager({
+ select: function() {
+ selected = true;
+ },
+ redrawList: function() {
+ redrew = true;
+ }
+ });
+ pm.addPlayer('someId');
+ pm.selectPlayer('someId');
+
+ assertTrue(selected);
+ assertTrue(redrew);
+ };
+ runTests();
+ </script>
+ </body>
+</html>
diff --git a/content/browser/resources/media/new/webui_resource_test.js b/content/browser/resources/media/new/webui_resource_test.js
new file mode 100644
index 0000000..6b05a30
--- /dev/null
+++ b/content/browser/resources/media/new/webui_resource_test.js
@@ -0,0 +1,210 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Tests that an observation matches the expected value.
+ * @param {Object} expected The expected value.
+ * @param {Object} observed The actual value.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertEquals(expected, observed, opt_message) {
+ if (observed !== expected) {
+ var message = 'Assertion Failed\n Observed: ' + observed +
+ '\n Expected: ' + expected;
+ if (opt_message)
+ message = message + '\n ' + opt_message;
+ throw new Error(message);
+ }
+}
+
+/**
+ * Verifies that a test result is true.
+ * @param {boolean} observed The observed value.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertTrue(observed, opt_message) {
+ assertEquals(true, observed, opt_message);
+}
+
+/**
+ * Verifies that a test result is false.
+ * @param {boolean} observed The observed value.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertFalse(observed, opt_message) {
+ assertEquals(false, observed, opt_message);
+}
+
+/**
+ * Verifies that the observed and reference values differ.
+ * @param {Object} reference The target value for comparison.
+ * @param {Object} observed The test result.
+ * @param {string=} opt_message Optional message to include with a test
+ * failure.
+ */
+function assertNotEqual(reference, observed, opt_message) {
+ if (observed === reference) {
+ var message = 'Assertion Failed\n Observed: ' + observed +
+ '\n Reference: ' + reference;
+ if (opt_message)
+ message = message + '\n ' + opt_message;
+ throw new Error(message);
+ }
+}
+
+/**
+ * Verifies that a test evaluation results in an exception.
+ * @param {!Function} f The test function.
+ */
+function assertThrows(f) {
+ var triggeredError = false;
+ try {
+ f();
+ } catch (err) {
+ triggeredError = true;
+ }
+ if (!triggeredError)
+ throw new Error('Assertion Failed: throw expected.');
+}
+
+/**
+ * Verifies that the contents of the expected and observed arrays match.
+ * @param {!Array} expected The expected result.
+ * @param {!Array} observed The actual result.
+ */
+function assertArrayEquals(expected, observed) {
+ var v1 = Array.prototype.slice.call(expected);
+ var v2 = Array.prototype.slice.call(observed);
+ var equal = v1.length == v2.length;
+ if (equal) {
+ for (var i = 0; i < v1.length; i++) {
+ if (v1[i] !== v2[i]) {
+ equal = false;
+ break;
+ }
+ }
+ }
+ if (!equal) {
+ var message =
+ ['Assertion Failed', 'Observed: ' + v2, 'Expected: ' + v1].join('\n ');
+ throw new Error(message);
+ }
+}
+
+/**
+ * Verifies that the expected and observed result have the same content.
+ * @param {*} expected The expected result.
+ * @param {*} observed The actual result.
+ */
+function assertDeepEquals(expected, observed, opt_message) {
+ if (typeof expected == 'object' && expected != null) {
+ assertNotEqual(null, observed);
+ for (var key in expected) {
+ assertTrue(key in observed, opt_message);
+ assertDeepEquals(expected[key], observed[key], opt_message);
+ }
+ for (var key in observed) {
+ assertTrue(key in expected, opt_message);
+ }
+ } else {
+ assertEquals(expected, observed, opt_message);
+ }
+}
+
+/**
+ * Defines runTests.
+ */
+(function(exports) {
+ /**
+ * List of test cases.
+ * @type {Array.<string>} List of function names for tests to run.
+ */
+ var testCases = [];
+
+ /**
+ * Indicates if all tests have run successfully.
+ * @type {boolean}
+ */
+ var cleanTestRun = true;
+
+ /**
+ * Armed during setup of a test to call the matching tear down code.
+ * @type {Function}
+ */
+ var pendingTearDown = null;
+
+ /**
+ * Runs all functions starting with test and reports success or
+ * failure of the test suite.
+ */
+ function runTests() {
+ for (var name in window) {
+ if (typeof window[name] == 'function' && /^test/.test(name))
+ testCases.push(name);
+ }
+ if (!testCases.length) {
+ console.error('Failed to find test cases.');
+ cleanTestRun = false;
+ }
+ continueTesting();
+ }
+
+ function reportPass(name) {
+ 'use strict';
+ var text = document.createTextNode(name + ': PASSED');
+ var span = document.createElement('span');
+ span.appendChild(text);
+ document.body.appendChild(span);
+ document.body.appendChild(document.createElement('br'));
+ }
+
+ function reportFail(name) {
+ 'use strict';
+ var text = document.createTextNode(name + ': =========FAILED=======');
+ var span = document.createElement('span');
+ span.appendChild(text);
+ document.body.appendChild(span);
+ document.body.appendChild(document.createElement('br'));
+ }
+
+ /**
+ * Runs the next test in the queue. Reports the test results if the queue is
+ * empty.
+ */
+ function continueTesting() {
+ if (pendingTearDown) {
+ pendingTearDown();
+ pendingTearDown = null;
+ }
+ if (testCases.length > 0) {
+ var fn = testCases.pop();
+ var isAsyncTest = window[fn].length;
+ try {
+ if (window.setUp)
+ window.setUp();
+ pendingTearDown = window.tearDown;
+ window[fn](continueTesting);
+ reportPass(fn);
+ } catch (err) {
+ reportFail(fn);
+ console.error('Failure in test ' + fn + '\n' + err);
+ console.log(err.stack);
+ cleanTestRun = false;
+ }
+ // Asynchronous tests must manually call continueTesting when complete.
+ if (!isAsyncTest)
+ continueTesting();
+ }
+ if (testCases.length) {
+ domAutomationController.setAutomationId(1);
+ domAutomationController.send('PENDING');
+ }
+ };
+
+ exports.runTests = runTests;
+})(this);
+