diff --git a/reporter/tap.js b/reporter/tap.js
new file mode 100644
index 000000000..9d1772518
--- /dev/null
+++ b/reporter/tap.js
@@ -0,0 +1,77 @@
+(function() {
+
+function reporter( rep ) {
+ if ( rep === "tap" ) {
+ tapReporter();
+ }
+};
+
+reporter.print = function( val ) {
+ console.log( val );
+};
+
+QUnit.reporter = reporter;
+
+function tapReporter() {
+ var testCount = 0;
+ var failed = 0;
+
+ QUnit.begin( function() {
+ QUnit.reporter.print( "TAP version 13" );
+ } );
+
+ QUnit.done( function( details ) {
+ var output = [
+ "",
+ "1.." + testCount,
+ "# tests " + testCount,
+ "# pass " + ( testCount - failed )
+ ];
+
+ QUnit.reporter.print( output.join( "\n" ) );
+ } );
+
+ QUnit.moduleStart( function( details ) {
+ QUnit.reporter.print( "# module: " + details.name );
+ } );
+
+ QUnit.testDone( function( details ) {
+ var assertion;
+ var failedMessage;
+ var output;
+ var i = 0;
+
+ testCount++;
+
+ output = "ok " + testCount + " - ";
+
+ if ( details.skipped ) {
+ output += "# SKIP " + details.name;
+ } else if ( details.failed == 0 ) {
+ output += details.name;
+ } else {
+ failed++;
+ output = "not " + output + details.name;
+
+ for ( i = 0; i < details.assertions.length; i++ ) {
+ assertion = details.assertions[ i ];
+ if ( assertion.result ) {
+ continue;
+ }
+ failedMessage = [
+ "",
+ " ---",
+ " message: '" + assertion.message + "'",
+ " severity: fail",
+ " ..."
+ ];
+
+ output += failedMessage.join( "\n" );
+ }
+ }
+
+ QUnit.reporter.print( output );
+ } );
+};
+
+})();
diff --git a/test/tap.html b/test/tap.html
new file mode 100644
index 000000000..1a271a67e
--- /dev/null
+++ b/test/tap.html
@@ -0,0 +1,14 @@
+
+
+
+
+ QUnit Test Suite - Tap Reporter
+
+
+
+
+
+
+
+
+
diff --git a/test/tap.js b/test/tap.js
new file mode 100644
index 000000000..4a159bdcb
--- /dev/null
+++ b/test/tap.js
@@ -0,0 +1,97 @@
+/* globals console:true */
+
+var storedResults = [];
+var originalPrint = QUnit.reporter.print;
+QUnit.reporter.print = function( output ) {
+ storedResults.push( output );
+};
+
+QUnit.config.reorder = false;
+
+QUnit.reporter( "tap" );
+
+QUnit.module( "print function" );
+
+QUnit[ console && console.log ? "test" : "skip" ]( "prints to console.log", function( assert ) {
+ var results = [];
+
+ // Duck punch console.log
+ this.originalLog = console.log;
+ console.log = function( arg ) {
+ results.push( arg );
+ };
+
+ originalPrint( "foo", "bar" );
+ originalPrint( "baz" );
+
+ assert.strictEqual( results.length, 2 );
+
+ assert.strictEqual( results[ 0 ], "foo", "calls console.log fn" );
+ assert.strictEqual( results[ 1 ], "baz" );
+
+ console.log = this.originalLog;
+} );
+
+QUnit.module( "tap reporter - samples" );
+
+QUnit.test( "this test will pass", function( assert ) {
+ assert.ok( true );
+ assert.equal( 1, 1 );
+} );
+
+QUnit.test( "async tests that pass", function( assert ) {
+ assert.ok( true );
+ var done = assert.async();
+
+ setTimeout( function() {
+ assert.ok( true );
+ done();
+ }, 13 );
+} );
+
+QUnit.module( "tap reporter - samples #2" );
+
+QUnit.test( "this test will pass", function( assert ) {
+ assert.ok( true );
+ assert.equal( 1, 1 );
+} );
+
+QUnit.test( "async tests that pass", function( assert ) {
+ assert.ok( true );
+ var done = assert.async();
+
+ setTimeout( function() {
+ assert.ok( true );
+ done();
+ }, 13 );
+} );
+
+var once = true;
+var expected = [
+ "TAP version 13",
+ "# module: print function",
+ "ok 1 - prints to console.log",
+ "# module: tap reporter - samples",
+ "ok 2 - this test will pass",
+ "ok 3 - async tests that pass",
+ "# module: tap reporter - samples #2",
+ "ok 4 - this test will pass",
+ "ok 5 - async tests that pass",
+ "",
+ "1..5",
+ "# tests 5",
+ "# pass 5"
+].join( "\n" );
+
+QUnit.done( function() {
+ if ( once ) {
+ once = false;
+ QUnit.module( "tap reporter" );
+ QUnit.test( "final tests", function( assert ) {
+
+ // Remove the current module
+ storedResults.pop();
+ assert.equal( storedResults.join( "\n" ), expected );
+ } );
+ }
+} );