From e07f9172fa2623859a6d2a7aac146b7764201ba3 Mon Sep 17 00:00:00 2001
From: Steve Heffernan <steve@zencoder.com>
Date: Mon, 10 Dec 2012 16:40:12 -0800
Subject: [PATCH 1/6] Updated tests to include unit and integration tests.

---
 .jshintrc                                     |  12 ++
 test/integration.html                         |  42 +++++
 test/{ => integration}/test.js                | 154 +++++++++---------
 test/test.html                                |  52 ------
 test/unit.html                                |  45 +++++
 test/unit/component.js                        |  67 ++++++++
 test/unit/controls.js                         |   1 +
 test/unit/core.js                             |   1 +
 test/unit/events.js                           |   1 +
 test/unit/lib.js                              |  69 ++++++++
 test/unit/player.js                           |  22 +++
 test/unit/setup.js                            |   1 +
 test/unit/tech.js                             |   1 +
 test/unit/tracks.js                           |   1 +
 test/{ => vendor}/qunit/.gitignore            |   0
 test/{ => vendor}/qunit/README.md             |   0
 .../qunit/addons/canvas/README.md             |   0
 .../qunit/addons/canvas/canvas-test.js        |   0
 .../qunit/addons/canvas/canvas.html           |   0
 .../qunit/addons/canvas/qunit-canvas.js       |   0
 .../qunit/addons/close-enough/README.md       |   0
 .../addons/close-enough/close-enough-test.js  |   0
 .../addons/close-enough/close-enough.html     |   0
 .../addons/close-enough/qunit-close-enough.js |   0
 .../qunit/addons/composite/README.md          |   0
 .../addons/composite/composite-demo-test.html |   0
 .../addons/composite/composite-test.html      |   0
 .../qunit/addons/composite/composite-test.js  |   0
 .../addons/composite/dummy-qunit-test.html    |   0
 .../addons/composite/dummy-same-test.html     |   0
 .../qunit/addons/composite/index.html         |   0
 .../addons/composite/qunit-composite.css      |   0
 .../qunit/addons/composite/qunit-composite.js |   0
 test/{ => vendor}/qunit/addons/step/README.md |   0
 .../qunit/addons/step/qunit-step.js           |   0
 .../qunit/addons/step/step-test.js            |   0
 test/{ => vendor}/qunit/addons/step/step.html |   0
 test/{ => vendor}/qunit/package.json          |   0
 test/{ => vendor}/qunit/qunit/qunit.css       |   0
 test/{ => vendor}/qunit/qunit/qunit.js        |   0
 test/{ => vendor}/qunit/test/headless.html    |   0
 test/{ => vendor}/qunit/test/index.html       |   0
 test/{ => vendor}/qunit/test/logs.html        |   0
 test/{ => vendor}/qunit/test/logs.js          |   0
 test/{ => vendor}/qunit/test/same.js          |   0
 test/{ => vendor}/qunit/test/test.js          |   0
 46 files changed, 340 insertions(+), 129 deletions(-)
 create mode 100644 .jshintrc
 create mode 100644 test/integration.html
 rename test/{ => integration}/test.js (65%)
 delete mode 100644 test/test.html
 create mode 100644 test/unit.html
 create mode 100644 test/unit/component.js
 create mode 100644 test/unit/controls.js
 create mode 100644 test/unit/core.js
 create mode 100644 test/unit/events.js
 create mode 100644 test/unit/lib.js
 create mode 100644 test/unit/player.js
 create mode 100644 test/unit/setup.js
 create mode 100644 test/unit/tech.js
 create mode 100644 test/unit/tracks.js
 rename test/{ => vendor}/qunit/.gitignore (100%)
 rename test/{ => vendor}/qunit/README.md (100%)
 rename test/{ => vendor}/qunit/addons/canvas/README.md (100%)
 rename test/{ => vendor}/qunit/addons/canvas/canvas-test.js (100%)
 rename test/{ => vendor}/qunit/addons/canvas/canvas.html (100%)
 rename test/{ => vendor}/qunit/addons/canvas/qunit-canvas.js (100%)
 rename test/{ => vendor}/qunit/addons/close-enough/README.md (100%)
 rename test/{ => vendor}/qunit/addons/close-enough/close-enough-test.js (100%)
 rename test/{ => vendor}/qunit/addons/close-enough/close-enough.html (100%)
 rename test/{ => vendor}/qunit/addons/close-enough/qunit-close-enough.js (100%)
 rename test/{ => vendor}/qunit/addons/composite/README.md (100%)
 rename test/{ => vendor}/qunit/addons/composite/composite-demo-test.html (100%)
 rename test/{ => vendor}/qunit/addons/composite/composite-test.html (100%)
 rename test/{ => vendor}/qunit/addons/composite/composite-test.js (100%)
 rename test/{ => vendor}/qunit/addons/composite/dummy-qunit-test.html (100%)
 rename test/{ => vendor}/qunit/addons/composite/dummy-same-test.html (100%)
 rename test/{ => vendor}/qunit/addons/composite/index.html (100%)
 rename test/{ => vendor}/qunit/addons/composite/qunit-composite.css (100%)
 rename test/{ => vendor}/qunit/addons/composite/qunit-composite.js (100%)
 rename test/{ => vendor}/qunit/addons/step/README.md (100%)
 rename test/{ => vendor}/qunit/addons/step/qunit-step.js (100%)
 rename test/{ => vendor}/qunit/addons/step/step-test.js (100%)
 rename test/{ => vendor}/qunit/addons/step/step.html (100%)
 rename test/{ => vendor}/qunit/package.json (100%)
 rename test/{ => vendor}/qunit/qunit/qunit.css (100%)
 rename test/{ => vendor}/qunit/qunit/qunit.js (100%)
 rename test/{ => vendor}/qunit/test/headless.html (100%)
 rename test/{ => vendor}/qunit/test/index.html (100%)
 rename test/{ => vendor}/qunit/test/logs.html (100%)
 rename test/{ => vendor}/qunit/test/logs.js (100%)
 rename test/{ => vendor}/qunit/test/same.js (100%)
 rename test/{ => vendor}/qunit/test/test.js (100%)

diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 000000000..e0722690b
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,12 @@
+{
+    "validthis": true,
+    "laxcomma" : true,
+    "laxbreak" : true,
+    "browser"  : true,
+    "eqnull"   : true,
+    "debug"    : true,
+    "devel"    : true,
+    "boss"     : true,
+    "expr"     : true,
+    "asi"      : true
+}
\ No newline at end of file
diff --git a/test/integration.html b/test/integration.html
new file mode 100644
index 000000000..4baa6ea99
--- /dev/null
+++ b/test/integration.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Video.js Test Suite</title>
+
+  <!-- QUnit -->
+  <link rel="stylesheet" href="vendor/qunit/qunit/qunit.css" />
+  <script src="vendor/qunit/qunit/qunit.js"></script>
+
+  <!-- Video.js CSS -->
+  <link rel="stylesheet" href="../design/video-js.css" type="text/css">
+
+  <!-- Video.js JavaScript -->
+  <script src='../src/core.js'></script>
+  <script src='../src/lib.js'></script>
+  <script src='../src/component.js'></script>
+  <script src='../src/controls.js'></script>
+  <script src='../src/events.js'></script>
+  <script src='../src/json.js'></script>
+  <script src='../src/player.js'></script>
+  <script src='../src/tech.js'></script>
+  <script src='../src/tracks.js'></script>
+
+  <script src='../tech/html5/html5.js'></script>
+  <script src='../tech/flash/flash.js'></script>
+
+  <script src='../src/setup.js'></script>
+
+  <!-- Integration Tests -->
+  <script src="integration/test.js"></script>
+
+</head>
+<body>
+  <div>
+    <h1 id="qunit-header">Bootstrap Plugin Test Suite</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests"></ol>
+    <div id="qunit-fixture"></div>
+  </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/test.js b/test/integration/test.js
similarity index 65%
rename from test/test.js
rename to test/integration/test.js
index c4ce4b2b1..ff0b9644c 100644
--- a/test/test.js
+++ b/test/integration/test.js
@@ -43,7 +43,7 @@ function createVideoTag(id){
 
 function playerSetup(){
 
-  _V_.el("player_box").appendChild(createVideoTag())
+  _V_.el("qunit-fixture").appendChild(createVideoTag())
 
   var vid = document.getElementById("vid1");
   this.player = _V_(vid);
@@ -137,107 +137,107 @@ test("Paused", 2, function() {
   this.player.play();
 });
 
-test("currentTime()", 1, function() {
-  stop();
+// test("currentTime()", 1, function() {
+//   stop();
 
-  // Try for 3 time updates, sometimes it updates at 0 seconds.
-  // var tries = 0;
+//   // Try for 3 time updates, sometimes it updates at 0 seconds.
+//   // var tries = 0;
 
-  // Can't rely on just time update because it's faked for Flash.
-  this.player.one("loadeddata", _V_.proxy(this, function(){
+//   // Can't rely on just time update because it's faked for Flash.
+//   this.player.one("loadeddata", _V_.proxy(this, function(){
 
-    this.player.addEvent("timeupdate", _V_.proxy(this, function(){
+//     this.player.addEvent("timeupdate", _V_.proxy(this, function(){
 
-      if (this.player.currentTime() > 0) {
-        ok(true, "Time is greater than 0.");
-        start();
-      } else {
-        // tries++;
-      }
+//       if (this.player.currentTime() > 0) {
+//         ok(true, "Time is greater than 0.");
+//         start();
+//       } else {
+//         // tries++;
+//       }
 
-      // if (tries >= 3) {
-      //   start();
-      // }
-    }));
+//       // if (tries >= 3) {
+//       //   start();
+//       // }
+//     }));
 
-  }));
+//   }));
   
-  this.player.play();
-});
+//   this.player.play();
+// });
 
 
-test("currentTime(seconds)", 2, function() {
-  stop();
+// test("currentTime(seconds)", 2, function() {
+//   stop();
 
-  // var afterPlayback = _V_.proxy(this, function(){
-  //   this.player.currentTime(this.player.duration() / 2);
-  // 
-  //   this.player.addEvent("timeupdate", _V_.proxy(this, function(){
-  //     ok(this.player.currentTime() > 0, "Time is greater than 0.");
-  //     
-  //     this.player.pause();
-  //     
-  //     this.player.addEvent("timeupdate", _V_.proxy(this, function(){
-  //       ok(this.player.currentTime() == 0, "Time is 0.");
-  //       start();
-  //     }));
-  // 
-  //     this.player.currentTime(0);
-  //   }));
-  // });
+//   // var afterPlayback = _V_.proxy(this, function(){
+//   //   this.player.currentTime(this.player.duration() / 2);
+//   // 
+//   //   this.player.addEvent("timeupdate", _V_.proxy(this, function(){
+//   //     ok(this.player.currentTime() > 0, "Time is greater than 0.");
+//   //     
+//   //     this.player.pause();
+//   //     
+//   //     this.player.addEvent("timeupdate", _V_.proxy(this, function(){
+//   //       ok(this.player.currentTime() == 0, "Time is 0.");
+//   //       start();
+//   //     }));
+//   // 
+//   //     this.player.currentTime(0);
+//   //   }));
+//   // });
 
-  // Wait for Source to be ready.
-  this.player.one("loadeddata", _V_.proxy(this, function(){
+//   // Wait for Source to be ready.
+//   this.player.one("loadeddata", _V_.proxy(this, function(){
 
-    _V_.log("loadeddata", this.player);
-    this.player.currentTime(this.player.duration() - 1);
+//     _V_.log("loadeddata", this.player);
+//     this.player.currentTime(this.player.duration() - 1);
 
-  }));
+//   }));
   
-  this.player.one("seeked", _V_.proxy(this, function(){
+//   this.player.one("seeked", _V_.proxy(this, function(){
 
-    _V_.log("seeked", this.player.currentTime())
-    ok(this.player.currentTime() > 1, "Time is greater than 1.");
+//     _V_.log("seeked", this.player.currentTime())
+//     ok(this.player.currentTime() > 1, "Time is greater than 1.");
 
-    this.player.one("seeked", _V_.proxy(this, function(){
+//     this.player.one("seeked", _V_.proxy(this, function(){
       
-      _V_.log("seeked2", this.player.currentTime())
+//       _V_.log("seeked2", this.player.currentTime())
 
-      ok(this.player.currentTime() <= 1, "Time is less than 1.");
-      start();
+//       ok(this.player.currentTime() <= 1, "Time is less than 1.");
+//       start();
 
-    }));
+//     }));
 
-    this.player.currentTime(0);
+//     this.player.currentTime(0);
 
-  }));
+//   }));
 
 
-  this.player.play();
+//   this.player.play();
 
-  // this.player.one("timeupdate", _V_.proxy(this, function(){
-  // 
-  //   this.player.currentTime(this.player.duration() / 2);
-  // 
-  //   this.player.one("timeupdate", _V_.proxy(this, function(){
-  //     ok(this.player.currentTime() > 0, "Time is greater than 0.");
-  // 
-  //     this.player.pause();
-  //     this.player.currentTime(0);
-  // 
-  //     this.player.one("timeupdate", _V_.proxy(this, function(){
-  // 
-  //       ok(this.player.currentTime() == 0, "Time is 0.");
-  //       start();
-  // 
-  //     }));
-  // 
-  //   }));
-  // 
-  // 
-  // }));
+//   // this.player.one("timeupdate", _V_.proxy(this, function(){
+//   // 
+//   //   this.player.currentTime(this.player.duration() / 2);
+//   // 
+//   //   this.player.one("timeupdate", _V_.proxy(this, function(){
+//   //     ok(this.player.currentTime() > 0, "Time is greater than 0.");
+//   // 
+//   //     this.player.pause();
+//   //     this.player.currentTime(0);
+//   // 
+//   //     this.player.one("timeupdate", _V_.proxy(this, function(){
+//   // 
+//   //       ok(this.player.currentTime() == 0, "Time is 0.");
+//   //       start();
+//   // 
+//   //     }));
+//   // 
+//   //   }));
+//   // 
+//   // 
+//   // }));
 
-});
+// });
 
 /* Events
 ================================================================================ */
diff --git a/test/test.html b/test/test.html
deleted file mode 100644
index 08b84500e..000000000
--- a/test/test.html
+++ /dev/null
@@ -1,52 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="UTF-8" />
-  <title>QUnit Test Suite</title>
-
-  <link rel="stylesheet" href="../design/video-js.css" type="text/css">
-
-  <!--[if IE]>
-    <script src="https://getfirebug.com/firebug-lite.js" type="text/javascript" charset="utf-8"></script>
-  <![endif]-->
-
-  <!-- Video.js Source Files -->
-  <script src='../src/core.js'></script>
-  <script src='../src/lib.js'></script>
-  <script src='../src/component.js'></script>
-  <script src='../src/controls.js'></script>
-  <script src='../src/events.js'></script>
-  <script src='../src/json.js'></script>
-  <script src='../src/player.js'></script>
-  <script src='../src/tech.js'></script>
-  <script src='../src/tracks.js'></script>
-
-  <script src='../tech/html5/html5.js'></script>
-  <script src='../tech/flash/flash.js'></script>
-
-
-  <script src='../src/setup.js'></script>
-  <!-- END Video.js Source Files -->
-
-  <script type="text/javascript" charset="utf-8">
-    // Easy access to test Flash over HTML5. Add ?flash to URL
-    if (window.location.href.indexOf("?flash") !== -1) {
-      _V_.options.techOrder = ["flash"]
-    }
-  </script>
-
-  <link rel="stylesheet" href="qunit/qunit/qunit.css" type="text/css" media="screen">
-  <script type="text/javascript" src="qunit/qunit/qunit.js"></script>
-  <script type="text/javascript" src="test.js"></script>
-</head>
-<body>
-  <h1 id="qunit-header">QUnit Test Suite</h1>
-  <h2 id="qunit-banner"></h2>
-  <div id="qunit-testrunner-toolbar"></div>
-  <h2 id="qunit-userAgent"></h2>
-  <ol id="qunit-tests"></ol>
-  <div id="qunit-fixture">test markup</div>
-  
-  <div id="player_box"></div>
-</body>
-</html>
diff --git a/test/unit.html b/test/unit.html
new file mode 100644
index 000000000..b6eaf3912
--- /dev/null
+++ b/test/unit.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Video.js Test Suite</title>
+
+  <!-- QUnit -->
+  <link rel="stylesheet" href="vendor/qunit/qunit/qunit.css" />
+  <script src="vendor/qunit/qunit/qunit.js"></script>
+
+  <!-- Video.js CSS -->
+  <link rel="stylesheet" href="../design/video-js.css" type="text/css">
+
+  <!-- Video.js JavaScript -->
+  <script src='../src/core.js'></script>
+  <script src='../src/lib.js'></script>
+  <script src='../src/component.js'></script>
+  <script src='../src/controls.js'></script>
+  <script src='../src/events.js'></script>
+  <script src='../src/json.js'></script>
+  <script src='../src/player.js'></script>
+  <script src='../src/tech.js'></script>
+  <script src='../src/tracks.js'></script>
+
+  <script src='../tech/html5/html5.js'></script>
+  <script src='../tech/flash/flash.js'></script>
+
+  <script src='../src/setup.js'></script>
+
+  <!-- Unit Tests -->
+  <script src="unit/component.js"></script>
+  <script src="unit/controls.js"></script>
+  <script src="unit/player.js"></script>
+  <script src="unit/lib.js"></script>
+
+</head>
+<body>
+  <div>
+    <h1 id="qunit-header">Bootstrap Plugin Test Suite</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests"></ol>
+    <div id="qunit-fixture"></div>
+  </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/test/unit/component.js b/test/unit/component.js
new file mode 100644
index 000000000..25f635b18
--- /dev/null
+++ b/test/unit/component.js
@@ -0,0 +1,67 @@
+module("Component");
+
+test('should create an element', function(){
+  var comp = new _V_.Component({}, {});
+
+  ok(comp.el.nodeName);
+});
+
+test('should init child coponents from options', function(){
+  var comp = new _V_.Component({}, {
+    components: {
+      'component': true
+    }
+  });
+
+  ok(comp.el.childNodes.length === 1);
+});
+
+test('should show and hide an element', function(){
+  var comp = new _V_.Component({}, {});
+
+  comp.hide();
+  ok(comp.el.style.display === 'none');
+  comp.show();
+  ok(comp.el.style.display === 'block');
+});
+
+test('should add and remove a CSS class', function(){
+  var comp = new _V_.Component({}, {});
+
+  comp.addClass('test-class');
+  ok(comp.el.className.indexOf('test-class') !== -1);
+  comp.removeClass('test-class');
+  ok(comp.el.className.indexOf('test-class') === -1);
+});
+
+test('should add and remove event listeners to element', function(){
+  var comp = new _V_.Component({}, {});
+
+  // No need to make this async because we're triggering events inline.
+  // We're going to trigger the event after removing the listener,
+  // So if we get extra asserts that's a problem.
+  expect(1);
+
+  var testListener = function(){
+    ok(true, 'fired event once');
+  };
+
+  comp.on('test-event', testListener);
+  comp.trigger('test-event');
+  comp.off('test-event', testListener);
+  comp.trigger('test-event');
+});
+
+test('should trigger a listener once using one()', function(){
+  var comp = new _V_.Component({}, {});
+
+  expect(1);
+
+  var testListener = function(){
+    ok(true, 'fired event once');
+  };
+
+  comp.one('test-event', testListener);
+  comp.trigger('test-event');
+  comp.trigger('test-event');
+});
\ No newline at end of file
diff --git a/test/unit/controls.js b/test/unit/controls.js
new file mode 100644
index 000000000..ca2694ed5
--- /dev/null
+++ b/test/unit/controls.js
@@ -0,0 +1 @@
+module("Controls");
\ No newline at end of file
diff --git a/test/unit/core.js b/test/unit/core.js
new file mode 100644
index 000000000..cbe914c0b
--- /dev/null
+++ b/test/unit/core.js
@@ -0,0 +1 @@
+module("Core");
\ No newline at end of file
diff --git a/test/unit/events.js b/test/unit/events.js
new file mode 100644
index 000000000..2bf79e400
--- /dev/null
+++ b/test/unit/events.js
@@ -0,0 +1 @@
+module("Events");
\ No newline at end of file
diff --git a/test/unit/lib.js b/test/unit/lib.js
new file mode 100644
index 000000000..e557dd40e
--- /dev/null
+++ b/test/unit/lib.js
@@ -0,0 +1,69 @@
+module("Lib");
+
+test('should merge two objects', function(){
+  var obj1 = { a:1, b:2 };
+  var obj2 = { b:3, c:4 };
+
+  _V_.merge(obj1, obj2);
+
+  deepEqual(obj1, {a:1,b:3,c:4} );
+});
+
+test('should create an element with attributes', function(){
+  var el = _V_.createElement('div', { className: 'test-class', 'data-test': 'asdf' })
+  ok(el.className === 'test-class');
+  ok(el.getAttribute('data-test') === 'asdf' );
+});
+
+test('should insert an element first', function(){
+  var el1 = document.createElement('div');
+  var el2 = document.createElement('div');
+  var el3 = document.createElement('div');
+
+  _V_.insertFirst(el2, el1);
+  ok(el1.childNodes[0] === el2);
+  _V_.insertFirst(el3, el1);
+  ok(el1.childNodes[0] === el3);
+});
+
+test('should add and remove a CSS class', function(){
+  var el = document.createElement('div');
+
+  _V_.addClass(el, 'test-class')
+  ok(el.className.indexOf('test-class') !== -1);
+  _V_.removeClass(el, 'test-class')
+  ok(el.className.indexOf('test-class') === -1);
+});
+
+test('should format the time', function(){
+  ok(_V_.formatTime(120) === "2:00");
+  ok(_V_.formatTime(18121) === "5:02:01");
+});
+
+test('should uppercase a word', function(){
+  ok(_V_.uc('asdf') === "Asdf");
+});
+
+test('should trim a string', function(){
+  ok(_V_.trim(' asdf ') === "asdf");
+});
+
+test('should round a number', function(){
+  ok(_V_.round(1.01) === 1);
+  ok(_V_.round(1.01, 1) === 1.0);
+  ok(_V_.round(1.01, 2) === 1.01);
+  ok(_V_.round(1.05, 1) === 1.1);
+});
+
+test('should test that an object is empty', function(){
+  ok(_V_.isEmpty({}) === true);
+  ok(_V_.isEmpty({ asdf: 'asdf' }) === false);
+});
+
+test('should create a fake timerange', function(){
+  var tr = _V_.createTimeRange(0, 100);
+
+  ok(tr.start() === 0);
+  ok(tr.end() === 100);
+  ok(tr.length === 1);
+});
\ No newline at end of file
diff --git a/test/unit/player.js b/test/unit/player.js
new file mode 100644
index 000000000..b6695aea0
--- /dev/null
+++ b/test/unit/player.js
@@ -0,0 +1,22 @@
+module("Player", {
+  setup: function(){
+    var videoTag = document.createElement('video');
+    videoTag.id = 'example_1';
+    videoTag.className = 'video-js vjs-default-skin';
+
+    var fixture = document.getElementById('qunit-fixture');
+    fixture.appendChild(videoTag);
+
+    this.player = new _V_.Player(videoTag, {});
+  },
+  teardown: function(){
+
+  }
+});
+
+test('should create and embed a new player element', function(){
+  ok(this.player.el.nodeName === 'DIV');
+  ok(this.player.el.parentNode.id === 'qunit-fixture');
+  ok(this.player.el.className.indexOf('video-js vjs-default-skin') !== -1);
+  ok(this.player.el.id === 'example_1');
+});
\ No newline at end of file
diff --git a/test/unit/setup.js b/test/unit/setup.js
new file mode 100644
index 000000000..6c0bc4ed2
--- /dev/null
+++ b/test/unit/setup.js
@@ -0,0 +1 @@
+module("Setup");
\ No newline at end of file
diff --git a/test/unit/tech.js b/test/unit/tech.js
new file mode 100644
index 000000000..0979c9012
--- /dev/null
+++ b/test/unit/tech.js
@@ -0,0 +1 @@
+module("Tech");
\ No newline at end of file
diff --git a/test/unit/tracks.js b/test/unit/tracks.js
new file mode 100644
index 000000000..37a285533
--- /dev/null
+++ b/test/unit/tracks.js
@@ -0,0 +1 @@
+module("Tracks");
\ No newline at end of file
diff --git a/test/qunit/.gitignore b/test/vendor/qunit/.gitignore
similarity index 100%
rename from test/qunit/.gitignore
rename to test/vendor/qunit/.gitignore
diff --git a/test/qunit/README.md b/test/vendor/qunit/README.md
similarity index 100%
rename from test/qunit/README.md
rename to test/vendor/qunit/README.md
diff --git a/test/qunit/addons/canvas/README.md b/test/vendor/qunit/addons/canvas/README.md
similarity index 100%
rename from test/qunit/addons/canvas/README.md
rename to test/vendor/qunit/addons/canvas/README.md
diff --git a/test/qunit/addons/canvas/canvas-test.js b/test/vendor/qunit/addons/canvas/canvas-test.js
similarity index 100%
rename from test/qunit/addons/canvas/canvas-test.js
rename to test/vendor/qunit/addons/canvas/canvas-test.js
diff --git a/test/qunit/addons/canvas/canvas.html b/test/vendor/qunit/addons/canvas/canvas.html
similarity index 100%
rename from test/qunit/addons/canvas/canvas.html
rename to test/vendor/qunit/addons/canvas/canvas.html
diff --git a/test/qunit/addons/canvas/qunit-canvas.js b/test/vendor/qunit/addons/canvas/qunit-canvas.js
similarity index 100%
rename from test/qunit/addons/canvas/qunit-canvas.js
rename to test/vendor/qunit/addons/canvas/qunit-canvas.js
diff --git a/test/qunit/addons/close-enough/README.md b/test/vendor/qunit/addons/close-enough/README.md
similarity index 100%
rename from test/qunit/addons/close-enough/README.md
rename to test/vendor/qunit/addons/close-enough/README.md
diff --git a/test/qunit/addons/close-enough/close-enough-test.js b/test/vendor/qunit/addons/close-enough/close-enough-test.js
similarity index 100%
rename from test/qunit/addons/close-enough/close-enough-test.js
rename to test/vendor/qunit/addons/close-enough/close-enough-test.js
diff --git a/test/qunit/addons/close-enough/close-enough.html b/test/vendor/qunit/addons/close-enough/close-enough.html
similarity index 100%
rename from test/qunit/addons/close-enough/close-enough.html
rename to test/vendor/qunit/addons/close-enough/close-enough.html
diff --git a/test/qunit/addons/close-enough/qunit-close-enough.js b/test/vendor/qunit/addons/close-enough/qunit-close-enough.js
similarity index 100%
rename from test/qunit/addons/close-enough/qunit-close-enough.js
rename to test/vendor/qunit/addons/close-enough/qunit-close-enough.js
diff --git a/test/qunit/addons/composite/README.md b/test/vendor/qunit/addons/composite/README.md
similarity index 100%
rename from test/qunit/addons/composite/README.md
rename to test/vendor/qunit/addons/composite/README.md
diff --git a/test/qunit/addons/composite/composite-demo-test.html b/test/vendor/qunit/addons/composite/composite-demo-test.html
similarity index 100%
rename from test/qunit/addons/composite/composite-demo-test.html
rename to test/vendor/qunit/addons/composite/composite-demo-test.html
diff --git a/test/qunit/addons/composite/composite-test.html b/test/vendor/qunit/addons/composite/composite-test.html
similarity index 100%
rename from test/qunit/addons/composite/composite-test.html
rename to test/vendor/qunit/addons/composite/composite-test.html
diff --git a/test/qunit/addons/composite/composite-test.js b/test/vendor/qunit/addons/composite/composite-test.js
similarity index 100%
rename from test/qunit/addons/composite/composite-test.js
rename to test/vendor/qunit/addons/composite/composite-test.js
diff --git a/test/qunit/addons/composite/dummy-qunit-test.html b/test/vendor/qunit/addons/composite/dummy-qunit-test.html
similarity index 100%
rename from test/qunit/addons/composite/dummy-qunit-test.html
rename to test/vendor/qunit/addons/composite/dummy-qunit-test.html
diff --git a/test/qunit/addons/composite/dummy-same-test.html b/test/vendor/qunit/addons/composite/dummy-same-test.html
similarity index 100%
rename from test/qunit/addons/composite/dummy-same-test.html
rename to test/vendor/qunit/addons/composite/dummy-same-test.html
diff --git a/test/qunit/addons/composite/index.html b/test/vendor/qunit/addons/composite/index.html
similarity index 100%
rename from test/qunit/addons/composite/index.html
rename to test/vendor/qunit/addons/composite/index.html
diff --git a/test/qunit/addons/composite/qunit-composite.css b/test/vendor/qunit/addons/composite/qunit-composite.css
similarity index 100%
rename from test/qunit/addons/composite/qunit-composite.css
rename to test/vendor/qunit/addons/composite/qunit-composite.css
diff --git a/test/qunit/addons/composite/qunit-composite.js b/test/vendor/qunit/addons/composite/qunit-composite.js
similarity index 100%
rename from test/qunit/addons/composite/qunit-composite.js
rename to test/vendor/qunit/addons/composite/qunit-composite.js
diff --git a/test/qunit/addons/step/README.md b/test/vendor/qunit/addons/step/README.md
similarity index 100%
rename from test/qunit/addons/step/README.md
rename to test/vendor/qunit/addons/step/README.md
diff --git a/test/qunit/addons/step/qunit-step.js b/test/vendor/qunit/addons/step/qunit-step.js
similarity index 100%
rename from test/qunit/addons/step/qunit-step.js
rename to test/vendor/qunit/addons/step/qunit-step.js
diff --git a/test/qunit/addons/step/step-test.js b/test/vendor/qunit/addons/step/step-test.js
similarity index 100%
rename from test/qunit/addons/step/step-test.js
rename to test/vendor/qunit/addons/step/step-test.js
diff --git a/test/qunit/addons/step/step.html b/test/vendor/qunit/addons/step/step.html
similarity index 100%
rename from test/qunit/addons/step/step.html
rename to test/vendor/qunit/addons/step/step.html
diff --git a/test/qunit/package.json b/test/vendor/qunit/package.json
similarity index 100%
rename from test/qunit/package.json
rename to test/vendor/qunit/package.json
diff --git a/test/qunit/qunit/qunit.css b/test/vendor/qunit/qunit/qunit.css
similarity index 100%
rename from test/qunit/qunit/qunit.css
rename to test/vendor/qunit/qunit/qunit.css
diff --git a/test/qunit/qunit/qunit.js b/test/vendor/qunit/qunit/qunit.js
similarity index 100%
rename from test/qunit/qunit/qunit.js
rename to test/vendor/qunit/qunit/qunit.js
diff --git a/test/qunit/test/headless.html b/test/vendor/qunit/test/headless.html
similarity index 100%
rename from test/qunit/test/headless.html
rename to test/vendor/qunit/test/headless.html
diff --git a/test/qunit/test/index.html b/test/vendor/qunit/test/index.html
similarity index 100%
rename from test/qunit/test/index.html
rename to test/vendor/qunit/test/index.html
diff --git a/test/qunit/test/logs.html b/test/vendor/qunit/test/logs.html
similarity index 100%
rename from test/qunit/test/logs.html
rename to test/vendor/qunit/test/logs.html
diff --git a/test/qunit/test/logs.js b/test/vendor/qunit/test/logs.js
similarity index 100%
rename from test/qunit/test/logs.js
rename to test/vendor/qunit/test/logs.js
diff --git a/test/qunit/test/same.js b/test/vendor/qunit/test/same.js
similarity index 100%
rename from test/qunit/test/same.js
rename to test/vendor/qunit/test/same.js
diff --git a/test/qunit/test/test.js b/test/vendor/qunit/test/test.js
similarity index 100%
rename from test/qunit/test/test.js
rename to test/vendor/qunit/test/test.js

From 9293076b75a119a1bb4af20e28d86bf7e177549b Mon Sep 17 00:00:00 2001
From: Steve Heffernan <steve@zencoder.com>
Date: Mon, 10 Dec 2012 16:41:18 -0800
Subject: [PATCH 2/6] Removed original build.sh. Was causing confusion.

---
 build.sh | 51 ---------------------------------------------------
 1 file changed, 51 deletions(-)
 delete mode 100755 build.sh

diff --git a/build.sh b/build.sh
deleted file mode 100755
index 6b28abb09..000000000
--- a/build.sh
+++ /dev/null
@@ -1,51 +0,0 @@
-#! /bin/csh
-# Combines Source Files. In terminal, sh build.sh
-# It will put a new video.js file under dist/
-
-# Create dist directory if it doesn't already exist
-mkdir -p dist
-
-# FILES=../src/*
-# for f in $FILES
-# do
-#   echo "Processing $f file..."
-#   # take action on each file. $f store current file name
-#   cat $f
-# done
-
-cat src/_begin.js > dist/video.js
-
-cat src/core.js >> dist/video.js
-cat src/lib.js >> dist/video.js
-cat src/json.js >> dist/video.js
-cat src/events.js >> dist/video.js
-
-cat src/component.js >> dist/video.js
-cat src/player.js >> dist/video.js
-cat src/tech.js >> dist/video.js
-cat src/controls.js >> dist/video.js
-
-cat src/tracks.js >> dist/video.js
-
-# h5swf temporarily requires swfobject
-# cat flash/swfobject.js >> dist/video.js
-
-cat tech/html5/html5.js >> dist/video.js
-cat tech/flash/flash.js >> dist/video.js
-
-cat src/setup.js >> dist/video.js
-
-cat src/_end.js >> dist/video.js
-
-
-# Copy Files
-cp design/video-js.css dist/video-js.css
-cp design/video-js.png dist/video-js.png
-cp flash/video-js.swf dist/video-js.swf
-
-cp build/release-files/README.md dist/README.md
-cp build/release-files/demo.html dist/demo.html
-cp LGPLv3-LICENSE.txt dist/LGPLv3-LICENSE.txt
-
-java -jar build/lib/yuicompressor-2.4.7.jar dist/video.js -o dist/video.min.js
-java -jar build/lib/yuicompressor-2.4.7.jar dist/video-js.css -o dist/video-js.min.css

From d8ec3cbc87a021ee68f6aec2a7729af1e6fc7a9b Mon Sep 17 00:00:00 2001
From: Steve Heffernan <steve@zencoder.com>
Date: Mon, 10 Dec 2012 16:44:41 -0800
Subject: [PATCH 3/6] Removed root source-list.js. Not sure what it was there
 for.

---
 source-list.js | 1 -
 1 file changed, 1 deletion(-)
 delete mode 100644 source-list.js

diff --git a/source-list.js b/source-list.js
deleted file mode 100644
index cb3607714..000000000
--- a/source-list.js
+++ /dev/null
@@ -1 +0,0 @@
-var vjsSourceList = [];
\ No newline at end of file

From 0a8d967a3efe8c03faa799f8e4846ced5899a330 Mon Sep 17 00:00:00 2001
From: Steve Heffernan <steve@zencoder.com>
Date: Mon, 10 Dec 2012 16:46:48 -0800
Subject: [PATCH 4/6] Removed demo-subtitles.srt. Not using it.

---
 demo-subtitles.srt | 13 -------------
 1 file changed, 13 deletions(-)
 delete mode 100644 demo-subtitles.srt

diff --git a/demo-subtitles.srt b/demo-subtitles.srt
deleted file mode 100644
index 1a5217306..000000000
--- a/demo-subtitles.srt
+++ /dev/null
@@ -1,13 +0,0 @@
-1
-00:00:00,000 --> 00:00:07,200
-[Background Music Playing]
-
-2
-00:00:16,000 --> 00:00:17,200
-Welcome to this week's episode
-
-3
-00:00:17,400 --> 00:00:19,000
-of explore California
-
-

From be0febaad98006200bbfec691c8a46e547bb24f1 Mon Sep 17 00:00:00 2001
From: Steve Heffernan <steve@zencoder.com>
Date: Mon, 10 Dec 2012 18:51:48 -0800
Subject: [PATCH 5/6] Added Travis CI config, and also supporting package.json.
 Removed flwplayer folder. Added phantomjs for Travis CI test running,
 including temp makefile.

---
 .gitignore                    |   4 +-
 .jshintrc                     |  14 ++--
 .travis.yml                   |   3 +
 Makefile                      |  10 +++
 decisions.txt                 |   8 --
 package.json                  |  18 ++++
 tech/flowplayer/flowplayer.js | 154 ----------------------------------
 test/phantom.js               |  63 ++++++++++++++
 test/server.js                |  14 ++++
 test/unit.html                |   3 +
 test/unit/phantom-logging.js  |  21 +++++
 11 files changed, 144 insertions(+), 168 deletions(-)
 create mode 100644 .travis.yml
 create mode 100644 Makefile
 delete mode 100644 decisions.txt
 create mode 100644 package.json
 delete mode 100644 tech/flowplayer/flowplayer.js
 create mode 100644 test/phantom.js
 create mode 100644 test/server.js
 create mode 100644 test/unit/phantom-logging.js

diff --git a/.gitignore b/.gitignore
index d31b48e9e..38451c2e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
 dist/*
 dev.html
 projects
-.zenflow-log
\ No newline at end of file
+.zenflow-log
+
+node_modules
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
index e0722690b..bfb9ebe82 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,12 +1,16 @@
 {
     "validthis": true,
-    "laxcomma" : true,
-    "laxbreak" : true,
     "browser"  : true,
-    "eqnull"   : true,
     "debug"    : true,
-    "devel"    : true,
     "boss"     : true,
     "expr"     : true,
-    "asi"      : true
+    "eqnull"   : true,
+    "quotmark" : "double",
+    "sub"      : true,
+    "trailing" : true,
+    "undef"    : true,
+    "predef"   : [  // Extra globals.
+        "_V_",
+        "VideoJS"
+    ]
 }
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..c2ba3f90b
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+  - 0.8
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..3f700e414
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+# Using makefile temporarily to run tests on Travis CI
+
+test:
+	# jshint src/*.js --config .jshintrc
+	node test/server.js &
+	phantomjs test/phantom.js "http://localhost:3000/test/unit.html"
+	kill -9 `cat test/pid.txt`
+	rm test/pid.txt
+
+.PHONY: test
\ No newline at end of file
diff --git a/decisions.txt b/decisions.txt
deleted file mode 100644
index 3f030af59..000000000
--- a/decisions.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-Tracking/Polling CurrentTime Manually
--------------------------------------
-- Necessary for most flash players and browsers that don't trigger timeupdate events. 
-- In VJS 1-2, we always tracked time because timeupdate was rare.
-- Now most browsers support it well. There is a delay bug in the current Chrome.
-    http://code.google.com/p/chromium/issues/detail?id=92251
-  Andrew from Chrome told me it would be fix in an upcoming release.
-Going to rely on browsers timeupdates when available now.
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..858e72e4b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,18 @@
+{
+    "name": "Video.js"
+  , "description": "An HTML5 and Flash video player with a common API and skin for both."
+  , "version": "3.2.3"
+  , "keywords": ["html5", "flash", "video", "player"]
+  , "homepage": "http://videojs.com"
+  , "author": "Steve Heffernan"
+  , "scripts": { "test": "make test" }
+  , "repository": {
+      "type": "git"
+    , "url": "https://github.com/zencoder/video-js.git"
+  }
+  , "devDependencies": {
+    , "jshint": "0.6.1"
+    , "connect": "2.1.3"
+    , "phantomjs": "1.7.0"
+  }
+}
\ No newline at end of file
diff --git a/tech/flowplayer/flowplayer.js b/tech/flowplayer/flowplayer.js
deleted file mode 100644
index b8cd962eb..000000000
--- a/tech/flowplayer/flowplayer.js
+++ /dev/null
@@ -1,154 +0,0 @@
-// **** NEEDS UPDATING ******
-
-// Flowplayer API Connector
-VideoJS.tech.flowplayer = {
-  name: "Flowplayer",
-  
-  supported: function(){
-    if (flowplayer) { 
-      return true;
-    } else {
-      return false;
-    }
-  },
-  canPlaySource: function(sourceObj){
-    if (sourceObj.type in _V_.tech.flowplayer.supports.format) { return "maybe"; }
-  },
-  supports: {
-    format: {
-      "video/flv": "FLV",
-      "video/x-flv": "FLV",
-      "video/mp4": "MP4",
-      "video/m4v": "MP4"
-    },
-    
-    // Optional events that we can manually mimic with timers
-    event: {
-      progress: false,
-      timeupdate: false
-    }
-  },
-  init: function(sourceObj){
-    var player = this;
-    flowplayer(
-      this.box.id, // Where it will put the swf object inside of
-      {
-        src: 'http://releases.flowplayer.org/swf/flowplayer-3.2.5.swf', 
-        wmode: 'opaque'
-      },
-      {
-        clip: { 
-          url: sourceObj.src, 
-          autoPlay: false, 
-          scaling: "fit",
-          onBegin: _V_.proxy(this, function(){
-            this.triggerEvent("loadstart");
-          })
-        },
-        autoPlay: true,
-        onLoad: function() {
-          player.tels.flowplayer = document.getElementById(player.box.id+"_api");
-          player.tels.flowplayer.className = "vjs-tech";
-          player.tels.flowplayer.api = this; // Need to re-establish API on object
-          player.triggerEvent("techready");
-        },
-        // Hide Flowplayer's big play button
-        play: {opacity: 0},
-        // Hide Flowplayer's controls
-        plugins: { controls: { autoHide: "always" } },
-        plugins:  { controls: null }
-      }
-    );
-  },
-  api: {
-    setupTriggers: function(){
-      // Map flowplayer events to video.js events
-      var map = [
-        { f:"onStart", v:"play" },
-        { f:"onResume", v:"play" },
-        { f:"onPause", v:"pause" },
-        { f:"onVolume", v:"volumechange" },
-        { f:"onError", v:"error" }
-      ];
-      _V_.each(map, _V_.proxy(this, function(item){
-        this.tels.flowplayer.api[item.f](_V_.proxy(this, function(e){ this.triggerEvent(item.v, e); }));
-      }));
-    },
-
-    play: function(){ this.tels.flowplayer.api.play(); },
-    pause: function(){ this.tels.flowplayer.api.pause(); },
-    paused: function(){ 
-      return !this.tels.flowplayer.api.isPlaying(); // More accurate than isPaused
-    },
-
-    currentTime: function(){ return this.tels.flowplayer.api.getTime(); },
-    setCurrentTime: function(seconds){ this.tels.flowplayer.api.seek(seconds); },
-
-    duration: function(){
-      var clip = this.tels.flowplayer.api.getClip();
-      return (clip) ? clip.duration : 0;
-    },
-
-    buffered: function(){
-      var status = this.tels.flowplayer.api.getStatus();
-      return _V_.createTimeRange(status.bufferStart, status.bufferEnd);
-    },
-
-    volume: function(){ return _V_.round(this.tels.flowplayer.api.getVolume() / 100, 2); },
-    setVolume: function(percentAsDecimal){ this.tels.flowplayer.api.setVolume(parseInt(percentAsDecimal * 100)); },
-    muted: function(){ return this.volume() == 0; },
-    setMuted: function(bool){
-      if (bool) {
-        this.values.mutedVol = this.volume();
-        this.volume(0);
-      } else {
-        this.volume(this.values.mutedVol);
-      }
-    },
-
-    supportsFullScreen: function(){
-      return false; // Flash does not allow fullscreen through javascript
-      // Maybe at click listener, and say "click screen".
-    },
-    enterFullScreen: function(){ this.tels.flowplayer.api.toggleFullscreen(); },
-
-    readError: function(eventArguments){
-      var errorCode = arguments[0],
-          errorMessage = arguments[1];
-      return errorMessage;
-    },
-
-    src: function(src){
-      this.setClip(src);
-    },
-    load: function(){
-      // Flowplayer always auto loads
-    }
-  }
-};
-
-
-/* 
- * flowplayer.js 3.2.4. The Flowplayer API
- * 
- * Copyright 2009 Flowplayer Oy
- * 
- * This file is part of Flowplayer.
- * 
- * Flowplayer is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * Flowplayer is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with Flowplayer.  If not, see <http://www.gnu.org/licenses/>.
- * 
- * Date: 2010-08-25 12:48:46 +0000 (Wed, 25 Aug 2010)
- * Revision: 551 
- */
-(function(){function g(o){console.log("$f.fireEvent",[].slice.call(o))}function k(q){if(!q||typeof q!="object"){return q}var o=new q.constructor();for(var p in q){if(q.hasOwnProperty(p)){o[p]=k(q[p])}}return o}function m(t,q){if(!t){return}var o,p=0,r=t.length;if(r===undefined){for(o in t){if(q.call(t[o],o,t[o])===false){break}}}else{for(var s=t[0];p<r&&q.call(s,p,s)!==false;s=t[++p]){}}return t}function c(o){return document.getElementById(o)}function i(q,p,o){if(typeof p!="object"){return q}if(q&&p){m(p,function(r,s){if(!o||typeof s!="function"){q[r]=s}})}return q}function n(s){var q=s.indexOf(".");if(q!=-1){var p=s.slice(0,q)||"*";var o=s.slice(q+1,s.length);var r=[];m(document.getElementsByTagName(p),function(){if(this.className&&this.className.indexOf(o)!=-1){r.push(this)}});return r}}function f(o){o=o||window.event;if(o.preventDefault){o.stopPropagation();o.preventDefault()}else{o.returnValue=false;o.cancelBubble=true}return false}function j(q,o,p){q[o]=q[o]||[];q[o].push(p)}function e(){return"_"+(""+Math.random()).slice(2,10)}var h=function(t,r,s){var q=this,p={},u={};q.index=r;if(typeof t=="string"){t={url:t}}i(this,t,true);m(("Begin*,Start,Pause*,Resume*,Seek*,Stop*,Finish*,LastSecond,Update,BufferFull,BufferEmpty,BufferStop").split(","),function(){var v="on"+this;if(v.indexOf("*")!=-1){v=v.slice(0,v.length-1);var w="onBefore"+v.slice(2);q[w]=function(x){j(u,w,x);return q}}q[v]=function(x){j(u,v,x);return q};if(r==-1){if(q[w]){s[w]=q[w]}if(q[v]){s[v]=q[v]}}});i(this,{onCuepoint:function(x,w){if(arguments.length==1){p.embedded=[null,x];return q}if(typeof x=="number"){x=[x]}var v=e();p[v]=[x,w];if(s.isLoaded()){s._api().fp_addCuepoints(x,r,v)}return q},update:function(w){i(q,w);if(s.isLoaded()){s._api().fp_updateClip(w,r)}var v=s.getConfig();var x=(r==-1)?v.clip:v.playlist[r];i(x,w,true)},_fireEvent:function(v,y,w,A){if(v=="onLoad"){m(p,function(B,C){if(C[0]){s._api().fp_addCuepoints(C[0],r,B)}});return false}A=A||q;if(v=="onCuepoint"){var z=p[y];if(z){return z[1].call(s,A,w)}}if(y&&"onBeforeBegin,onMetaData,onStart,onUpdate,onResume".indexOf(v)!=-1){i(A,y);if(y.metaData){if(!A.duration){A.duration=y.metaData.duration}else{A.fullDuration=y.metaData.duration}}}var x=true;m(u[v],function(){x=this.call(s,A,y,w)});return x}});if(t.onCuepoint){var o=t.onCuepoint;q.onCuepoint.apply(q,typeof o=="function"?[o]:o);delete t.onCuepoint}m(t,function(v,w){if(typeof w=="function"){j(u,v,w);delete t[v]}});if(r==-1){s.onCuepoint=this.onCuepoint}};var l=function(p,r,q,t){var o=this,s={},u=false;if(t){i(s,t)}m(r,function(v,w){if(typeof w=="function"){s[v]=w;delete r[v]}});i(this,{animate:function(y,z,x){if(!y){return o}if(typeof z=="function"){x=z;z=500}if(typeof y=="string"){var w=y;y={};y[w]=z;z=500}if(x){var v=e();s[v]=x}if(z===undefined){z=500}r=q._api().fp_animate(p,y,z,v);return o},css:function(w,x){if(x!==undefined){var v={};v[w]=x;w=v}r=q._api().fp_css(p,w);i(o,r);return o},show:function(){this.display="block";q._api().fp_showPlugin(p);return o},hide:function(){this.display="none";q._api().fp_hidePlugin(p);return o},toggle:function(){this.display=q._api().fp_togglePlugin(p);return o},fadeTo:function(y,x,w){if(typeof x=="function"){w=x;x=500}if(w){var v=e();s[v]=w}this.display=q._api().fp_fadeTo(p,y,x,v);this.opacity=y;return o},fadeIn:function(w,v){return o.fadeTo(1,w,v)},fadeOut:function(w,v){return o.fadeTo(0,w,v)},getName:function(){return p},getPlayer:function(){return q},_fireEvent:function(w,v,x){if(w=="onUpdate"){var z=q._api().fp_getPlugin(p);if(!z){return}i(o,z);delete o.methods;if(!u){m(z.methods,function(){var B=""+this;o[B]=function(){var C=[].slice.call(arguments);var D=q._api().fp_invoke(p,B,C);return D==="undefined"||D===undefined?o:D}});u=true}}var A=s[w];if(A){var y=A.apply(o,v);if(w.slice(0,1)=="_"){delete s[w]}return y}return o}})};function b(q,G,t){var w=this,v=null,D=false,u,s,F=[],y={},x={},E,r,p,C,o,A;i(w,{id:function(){return E},isLoaded:function(){return(v!==null&&v.fp_play!==undefined&&!D)},getParent:function(){return q},hide:function(H){if(H){q.style.height="0px"}if(w.isLoaded()){v.style.height="0px"}return w},show:function(){q.style.height=A+"px";if(w.isLoaded()){v.style.height=o+"px"}return w},isHidden:function(){return w.isLoaded()&&parseInt(v.style.height,10)===0},load:function(J){if(!w.isLoaded()&&w._fireEvent("onBeforeLoad")!==false){var H=function(){u=q.innerHTML;if(u&&!flashembed.isSupported(G.version)){q.innerHTML=""}if(J){J.cached=true;j(x,"onLoad",J)}flashembed(q,G,{config:t})};var I=0;m(a,function(){this.unload(function(K){if(++I==a.length){H()}})})}return w},unload:function(J){if(this.isFullscreen()&&/WebKit/i.test(navigator.userAgent)){if(J){J(false)}return w}if(u.replace(/\s/g,"")!==""){if(w._fireEvent("onBeforeUnload")===false){if(J){J(false)}return w}D=true;try{if(v){v.fp_close();w._fireEvent("onUnload")}}catch(H){}var I=function(){v=null;q.innerHTML=u;D=false;if(J){J(true)}};setTimeout(I,50)}else{if(J){J(false)}}return w},getClip:function(H){if(H===undefined){H=C}return F[H]},getCommonClip:function(){return s},getPlaylist:function(){return F},getPlugin:function(H){var J=y[H];if(!J&&w.isLoaded()){var I=w._api().fp_getPlugin(H);if(I){J=new l(H,I,w);y[H]=J}}return J},getScreen:function(){return w.getPlugin("screen")},getControls:function(){return w.getPlugin("controls")._fireEvent("onUpdate")},getLogo:function(){try{return w.getPlugin("logo")._fireEvent("onUpdate")}catch(H){}},getPlay:function(){return w.getPlugin("play")._fireEvent("onUpdate")},getConfig:function(H){return H?k(t):t},getFlashParams:function(){return G},loadPlugin:function(K,J,M,L){if(typeof M=="function"){L=M;M={}}var I=L?e():"_";w._api().fp_loadPlugin(K,J,M,I);var H={};H[I]=L;var N=new l(K,null,w,H);y[K]=N;return N},getState:function(){return w.isLoaded()?v.fp_getState():-1},play:function(I,H){var J=function(){if(I!==undefined){w._api().fp_play(I,H)}else{w._api().fp_play()}};if(w.isLoaded()){J()}else{if(D){setTimeout(function(){w.play(I,H)},50)}else{w.load(function(){J()})}}return w},getVersion:function(){var I="flowplayer.js 3.2.4";if(w.isLoaded()){var H=v.fp_getVersion();H.push(I);return H}return I},_api:function(){if(!w.isLoaded()){throw"Flowplayer "+w.id()+" not loaded when calling an API method"}return v},setClip:function(H){w.setPlaylist([H]);return w},getIndex:function(){return p},_swfHeight:function(){return v.clientHeight}});m(("Click*,Load*,Unload*,Keypress*,Volume*,Mute*,Unmute*,PlaylistReplace,ClipAdd,Fullscreen*,FullscreenExit,Error,MouseOver,MouseOut").split(","),function(){var H="on"+this;if(H.indexOf("*")!=-1){H=H.slice(0,H.length-1);var I="onBefore"+H.slice(2);w[I]=function(J){j(x,I,J);return w}}w[H]=function(J){j(x,H,J);return w}});m(("pause,resume,mute,unmute,stop,toggle,seek,getStatus,getVolume,setVolume,getTime,isPaused,isPlaying,startBuffering,stopBuffering,isFullscreen,toggleFullscreen,reset,close,setPlaylist,addClip,playFeed,setKeyboardShortcutsEnabled,isKeyboardShortcutsEnabled").split(","),function(){var H=this;w[H]=function(J,I){if(!w.isLoaded()){return w}var K=null;if(J!==undefined&&I!==undefined){K=v["fp_"+H](J,I)}else{K=(J===undefined)?v["fp_"+H]():v["fp_"+H](J)}return K==="undefined"||K===undefined?w:K}});w._fireEvent=function(Q){if(typeof Q=="string"){Q=[Q]}var R=Q[0],O=Q[1],M=Q[2],L=Q[3],K=0;if(t.debug){g(Q)}if(!w.isLoaded()&&R=="onLoad"&&O=="player"){v=v||c(r);o=w._swfHeight();m(F,function(){this._fireEvent("onLoad")});m(y,function(S,T){T._fireEvent("onUpdate")});s._fireEvent("onLoad")}if(R=="onLoad"&&O!="player"){return}if(R=="onError"){if(typeof O=="string"||(typeof O=="number"&&typeof M=="number")){O=M;M=L}}if(R=="onContextMenu"){m(t.contextMenu[O],function(S,T){T.call(w)});return}if(R=="onPluginEvent"||R=="onBeforePluginEvent"){var H=O.name||O;var I=y[H];if(I){I._fireEvent("onUpdate",O);return I._fireEvent(M,Q.slice(3))}return}if(R=="onPlaylistReplace"){F=[];var N=0;m(O,function(){F.push(new h(this,N++,w))})}if(R=="onClipAdd"){if(O.isInStream){return}O=new h(O,M,w);F.splice(M,0,O);for(K=M+1;K<F.length;K++){F[K].index++}}var P=true;if(typeof O=="number"&&O<F.length){C=O;var J=F[O];if(J){P=J._fireEvent(R,M,L)}if(!J||P!==false){P=s._fireEvent(R,M,L,J)}}m(x[R],function(){P=this.call(w,O,M);if(this.cached){x[R].splice(K,1)}if(P===false){return false}K++});return P};function B(){if($f(q)){$f(q).getParent().innerHTML="";p=$f(q).getIndex();a[p]=w}else{a.push(w);p=a.length-1}A=parseInt(q.style.height,10)||q.clientHeight;E=q.id||"fp"+e();r=G.id||E+"_api";G.id=r;t.playerId=E;if(typeof t=="string"){t={clip:{url:t}}}if(typeof t.clip=="string"){t.clip={url:t.clip}}t.clip=t.clip||{};if(q.getAttribute("href",2)&&!t.clip.url){t.clip.url=q.getAttribute("href",2)}s=new h(t.clip,-1,w);t.playlist=t.playlist||[t.clip];var I=0;m(t.playlist,function(){var K=this;if(typeof K=="object"&&K.length){K={url:""+K}}m(t.clip,function(L,M){if(M!==undefined&&K[L]===undefined&&typeof M!="function"){K[L]=M}});t.playlist[I]=K;K=new h(K,I,w);F.push(K);I++});m(t,function(K,L){if(typeof L=="function"){if(s[K]){s[K](L)}else{j(x,K,L)}delete t[K]}});m(t.plugins,function(K,L){if(L){y[K]=new l(K,L,w)}});if(!t.plugins||t.plugins.controls===undefined){y.controls=new l("controls",null,w)}y.canvas=new l("canvas",null,w);u=q.innerHTML;function J(L){var K=w.hasiPadSupport&&w.hasiPadSupport();if(/iPad|iPhone|iPod/i.test(navigator.userAgent)&&!/.flv$/i.test(F[0].url)&&!K){return true}if(!w.isLoaded()&&w._fireEvent("onBeforeClick")!==false){w.load()}return f(L)}function H(){if(u.replace(/\s/g,"")!==""){if(q.addEventListener){q.addEventListener("click",J,false)}else{if(q.attachEvent){q.attachEvent("onclick",J)}}}else{if(q.addEventListener){q.addEventListener("click",f,false)}w.load()}}setTimeout(H,0)}if(typeof q=="string"){var z=c(q);if(!z){throw"Flowplayer cannot access element: "+q}q=z;B()}else{B()}}var a=[];function d(o){this.length=o.length;this.each=function(p){m(o,p)};this.size=function(){return o.length}}window.flowplayer=window.$f=function(){var p=null;var o=arguments[0];if(!arguments.length){m(a,function(){if(this.isLoaded()){p=this;return false}});return p||a[0]}if(arguments.length==1){if(typeof o=="number"){return a[o]}else{if(o=="*"){return new d(a)}m(a,function(){if(this.id()==o.id||this.id()==o||this.getParent()==o){p=this;return false}});return p}}if(arguments.length>1){var t=arguments[1],q=(arguments.length==3)?arguments[2]:{};if(typeof t=="string"){t={src:t}}t=i({bgcolor:"#000000",version:[9,0],expressInstall:"http://static.flowplayer.org/swf/expressinstall.swf",cachebusting:true},t);if(typeof o=="string"){if(o.indexOf(".")!=-1){var s=[];m(n(o),function(){s.push(new b(this,k(t),k(q)))});return new d(s)}else{var r=c(o);return new b(r!==null?r:o,t,q)}}else{if(o){return new b(o,t,q)}}}return null};i(window.$f,{fireEvent:function(){var o=[].slice.call(arguments);var q=$f(o[0]);return q?q._fireEvent(o.slice(1)):null},addPlugin:function(o,p){b.prototype[o]=p;return $f},each:m,extend:i});if(typeof jQuery=="function"){jQuery.fn.flowplayer=function(q,p){if(!arguments.length||typeof arguments[0]=="number"){var o=[];this.each(function(){var r=$f(this);if(r){o.push(r)}});return arguments.length?o[arguments[0]]:new d(o)}return this.each(function(){$f(this,k(q),p?k(p):{})})}}})();(function(){var h=document.all,j="http://www.adobe.com/go/getflashplayer",c=typeof jQuery=="function",e=/(\d+)[^\d]+(\d+)[^\d]*(\d*)/,b={width:"100%",height:"100%",id:"_"+(""+Math.random()).slice(9),allowfullscreen:true,allowscriptaccess:"always",quality:"high",version:[3,0],onFail:null,expressInstall:null,w3c:false,cachebusting:false};if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){}})}function i(m,l){if(l){for(var f in l){if(l.hasOwnProperty(f)){m[f]=l[f]}}}return m}function a(f,n){var m=[];for(var l in f){if(f.hasOwnProperty(l)){m[l]=n(f[l])}}return m}window.flashembed=function(f,m,l){if(typeof f=="string"){f=document.getElementById(f.replace("#",""))}if(!f){return}if(typeof m=="string"){m={src:m}}return new d(f,i(i({},b),m),l)};var g=i(window.flashembed,{conf:b,getVersion:function(){var m,f;try{f=navigator.plugins["Shockwave Flash"].description.slice(16)}catch(o){try{m=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");f=m&&m.GetVariable("$version")}catch(n){try{m=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");f=m&&m.GetVariable("$version")}catch(l){}}}f=e.exec(f);return f?[f[1],f[3]]:[0,0]},asString:function(l){if(l===null||l===undefined){return null}var f=typeof l;if(f=="object"&&l.push){f="array"}switch(f){case"string":l=l.replace(new RegExp('(["\\\\])',"g"),"\\$1");l=l.replace(/^\s?(\d+\.?\d+)%/,"$1pct");return'"'+l+'"';case"array":return"["+a(l,function(o){return g.asString(o)}).join(",")+"]";case"function":return'"function()"';case"object":var m=[];for(var n in l){if(l.hasOwnProperty(n)){m.push('"'+n+'":'+g.asString(l[n]))}}return"{"+m.join(",")+"}"}return String(l).replace(/\s/g," ").replace(/\'/g,'"')},getHTML:function(o,l){o=i({},o);var n='<object width="'+o.width+'" height="'+o.height+'" id="'+o.id+'" name="'+o.id+'"';if(o.cachebusting){o.src+=((o.src.indexOf("?")!=-1?"&":"?")+Math.random())}if(o.w3c||!h){n+=' data="'+o.src+'" type="application/x-shockwave-flash"'}else{n+=' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'}n+=">";if(o.w3c||h){n+='<param name="movie" value="'+o.src+'" />'}o.width=o.height=o.id=o.w3c=o.src=null;o.onFail=o.version=o.expressInstall=null;for(var m in o){if(o[m]){n+='<param name="'+m+'" value="'+o[m]+'" />'}}var p="";if(l){for(var f in l){if(l[f]){var q=l[f];p+=f+"="+(/function|object/.test(typeof q)?g.asString(q):q)+"&"}}p=p.slice(0,-1);n+='<param name="flashvars" value=\''+p+"' />"}n+="</object>";return n},isSupported:function(f){return k[0]>f[0]||k[0]==f[0]&&k[1]>=f[1]}});var k=g.getVersion();function d(f,n,m){if(g.isSupported(n.version)){f.innerHTML=g.getHTML(n,m)}else{if(n.expressInstall&&g.isSupported([6,65])){f.innerHTML=g.getHTML(i(n,{src:n.expressInstall}),{MMredirectURL:location.href,MMplayerType:"PlugIn",MMdoctitle:document.title})}else{if(!f.innerHTML.replace(/\s/g,"")){f.innerHTML="<h2>Flash version "+n.version+" or greater is required</h2><h3>"+(k[0]>0?"Your version is "+k:"You have no flash plugin installed")+"</h3>"+(f.tagName=="A"?"<p>Click here to download latest version</p>":"<p>Download latest version from <a href='"+j+"'>here</a></p>");if(f.tagName=="A"){f.onclick=function(){location.href=j}}}if(n.onFail){var l=n.onFail.call(this);if(typeof l=="string"){f.innerHTML=l}}}}if(h){window[n.id]=document.getElementById(n.id)}i(this,{getRoot:function(){return f},getOptions:function(){return n},getConf:function(){return m},getApi:function(){return f.firstChild}})}if(c){jQuery.tools=jQuery.tools||{version:"3.2.4"};jQuery.tools.flashembed={conf:b};jQuery.fn.flashembed=function(l,f){return this.each(function(){$(this).data("flashembed",flashembed(this,l,f))})}}})();
diff --git a/test/phantom.js b/test/phantom.js
new file mode 100644
index 000000000..a8d840916
--- /dev/null
+++ b/test/phantom.js
@@ -0,0 +1,63 @@
+// Simple phantom.js integration script
+// Adapted from Modernizr & Bootstrap
+
+function waitFor(testFx, onReady, timeOutMillis) {
+  var maxtimeOutMillis = timeOutMillis ? timeOutMillis :  5001 //< Default Max Timout is 5s
+    , start = new Date().getTime()
+    , condition = false
+    , interval = setInterval(function () {
+        if ((new Date().getTime() - start < maxtimeOutMillis) && !condition) {
+          // If not time-out yet and condition not yet fulfilled
+          condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()) //< defensive code
+        } else {
+          if (!condition) {
+            // If condition still not fulfilled (timeout but condition is 'false')
+            console.log("'waitFor()' timeout")
+            phantom.exit(1)
+          } else {
+            // Condition fulfilled (timeout and/or condition is 'true')
+            typeof(onReady) === "string" ? eval(onReady) : onReady() //< Do what it's supposed to do once the condition is fulfilled
+            clearInterval(interval) //< Stop this interval
+          }
+        }
+    }, 100) //< repeat check every 100ms
+}
+
+
+if (phantom.args.length === 0 || phantom.args.length > 2) {
+  console.log('Usage: phantom.js URL')
+  phantom.exit()
+}
+
+var page = new WebPage()
+
+// Route "console.log()" calls from within the Page context to the main Phantom context (i.e. current "this")
+page.onConsoleMessage = function(msg) {
+  console.log(msg)
+};
+
+page.open(phantom.args[0], function(status){
+  if (status !== "success") {
+    console.log("Unable to access network")
+    phantom.exit()
+  } else {
+    waitFor(function(){
+      return page.evaluate(function(){
+        var el = document.getElementById('qunit-testresult')
+        if (el && el.innerText.match('completed')) {
+          return true
+        }
+        return false
+      })
+    }, function(){
+      var failedNum = page.evaluate(function(){
+        var el = document.getElementById('qunit-testresult')
+        try {
+          return el.getElementsByClassName('failed')[0].innerHTML
+        } catch (e) { }
+        return 10000
+      });
+      phantom.exit((parseInt(failedNum, 10) > 0) ? 1 : 0)
+    })
+  }
+})
\ No newline at end of file
diff --git a/test/server.js b/test/server.js
new file mode 100644
index 000000000..eaa60f55d
--- /dev/null
+++ b/test/server.js
@@ -0,0 +1,14 @@
+/*
+ * Simple connect server for phantom.js
+ * Adapted from Modernizr & Bootstrap
+ */
+
+var connect = require('connect')
+  , http = require('http')
+  , fs   = require('fs')
+  , app = connect()
+      .use(connect.static(__dirname + '/../'));
+
+http.createServer(app).listen(3000);
+
+fs.writeFileSync(__dirname + '/pid.txt', process.pid, 'utf-8')
\ No newline at end of file
diff --git a/test/unit.html b/test/unit.html
index b6eaf3912..231ad6f5e 100644
--- a/test/unit.html
+++ b/test/unit.html
@@ -7,6 +7,9 @@
   <link rel="stylesheet" href="vendor/qunit/qunit/qunit.css" />
   <script src="vendor/qunit/qunit/qunit.js"></script>
 
+  <!-- phantomjs logging script-->
+  <script src="unit/phantom-logging.js"></script>
+
   <!-- Video.js CSS -->
   <link rel="stylesheet" href="../design/video-js.css" type="text/css">
 
diff --git a/test/unit/phantom-logging.js b/test/unit/phantom-logging.js
new file mode 100644
index 000000000..0f5fd9e4f
--- /dev/null
+++ b/test/unit/phantom-logging.js
@@ -0,0 +1,21 @@
+// Logging setup for phantom integration
+// adapted from Modernizr & Bootstrap
+
+QUnit.begin = function () {
+  console.log("Starting test suite")
+  console.log("================================================\n")
+}
+
+QUnit.moduleDone = function (opts) {
+  if (opts.failed === 0) {
+    console.log("\u2714 All tests passed in '" + opts.name + "' module")
+  } else {
+    console.log("\u2716 " + opts.failed + " tests failed in '" + opts.name + "' module")
+  }
+}
+
+QUnit.done = function (opts) {
+  console.log("\n================================================")
+  console.log("Tests completed in " + opts.runtime + " milliseconds")
+  console.log(opts.passed + " tests of " + opts.total + " passed, " + opts.failed + " failed.")
+}
\ No newline at end of file

From 4d7da6cb8af4e3288c18adfe3e7c4bb189f197ab Mon Sep 17 00:00:00 2001
From: Steve Heffernan <steve@zencoder.com>
Date: Mon, 10 Dec 2012 18:54:48 -0800
Subject: [PATCH 6/6] Adding line to CHANGELOG: Added automated test suite and
 support for Travis CI.

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 653996a0b..4f86a3dac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+* Added automated test suite and support for Travis CI.
 * Updated docs to use Github markdown
 * Allow disabling of default components
 * Duration is now setable (need ed for HLS m3u8 files)