diff --git a/.gitignore b/.gitignore index 2b029badf0..eb5ce80503 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ test/*.map node_modules npm-debug.log +sandbox/* +!sandbox/*.example diff --git a/.jshintrc b/.jshintrc index 5acda60d6f..4835fca2bc 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,7 +1,4 @@ { - // Hopefully people are smart enough not to use eval - // But goog.base uses execScript so it throws an error. - // When compiled goog.base is stripped out completely. "evil" : true, "validthis": true, "browser" : true, @@ -14,7 +11,7 @@ "trailing" : true, "undef" : true, "laxbreak" : true, - "predef" : [ // Extra globals. + "predef" : [ "_V_", "videojs", "vjs", diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000000..e080af3d07 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,148 @@ +module.exports = function(grunt) { + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + concat: { + dist: { + src: [ + 'src/js/goog.base.js', + 'src/js/core.js', + 'src/js/lib.js', + 'src/js/events.js', + 'src/js/component.js', + 'src/js/player.js', + 'src/js/media.js', + 'src/js/media.html5.js', + 'src/js/media.flash.js', + 'src/js/controls.js', + 'src/js/tracks.js', + 'src/js/setup.js', + 'src/js/json.js', + 'src/js/exports.js' + ], + dest: 'dist/video.js' + }, + test: { + src: [ + 'test/unit/phantom-logging.js', + 'test/unit/component.js', + 'test/unit/core.js', + 'test/unit/events.js', + 'test/unit/lib.js', + 'test/unit/media.html5.js', + 'test/unit/player.js', + 'test/unit/setup.js', + ], + dest: 'test/unit.js' + } + }, + // Current forEach issue: https://github.com/gruntjs/grunt/issues/610 + // npm install https://github.com/gruntjs/grunt-contrib-jshint/archive/7fd70e86c5a8d489095fa81589d95dccb8eb3a46.tar.gz + jshint: { + dist: { + src: ["dist/video.js"], + options: { + jshintrc: ".jshintrc" + } + } + }, + qunit: { + all: ['test/unit.html'] + }, + watch: { + files: [ "src/**/*.js" ], + tasks: "dev" + } + + }); + + // Default task. + // grunt.registerTask('default', 'lint:beforeconcat concat lint:afterconcat'); + // // Default task(s). + // grunt.registerTask('default', ['uglify']); + + grunt.loadNpmTasks("grunt-contrib-concat"); + grunt.loadNpmTasks("grunt-contrib-jshint"); + grunt.loadNpmTasks("grunt-contrib-qunit"); + grunt.loadNpmTasks("grunt-contrib-watch"); + + grunt.registerTask( "dev", [ "compile" ] ); // "build:*:*", "jshint" + // compiled += grunt.file.read( filepath ); + + var exec = require('child_process').exec, + fs = require('fs'), + gzip = require('zlib').gzip; + + grunt.registerMultiTask('build', 'Building Source', function(){ + grunt.log.writeln(this.target) + if (this.target === 'latest') { + var files = this.data.files; + var dist = ''; + + // for (prop in this.file) { + // grunt.log.writeln(prop + ":" + this.file[prop]) + // } + + files.forEach(function(file){ + dist += grunt.file.read('src/js/' + file) + }); + + grunt.file.write('dist/video.js', dist); + } else if (this.target === 'test') { + grunt.task.run('build:latest'); + } + + }); + + grunt.registerTask('compile', 'Minify JS files using Closure Compiler.', function() { + var done = this.async(); + + var command = 'java -jar build/compiler/compiler.jar'; + command += ' --compilation_level ADVANCED_OPTIMIZATIONS'; + + var files = [ + 'goog.base.js', + 'core.js', + 'lib.js', + 'events.js', + 'component.js', + 'player.js', + 'media.js', + 'media.html5.js', + 'media.flash.js', + 'controls.js', + 'tracks.js', + 'setup.js', + 'json.js', + 'exports.js' + ]; + + files.forEach(function(file){ + command += ' --js=src/js/'+file; + }); + + command += ' --externs src/js/media.flash.externs.js'; + // command += ' --formatting=pretty_print'; + command += ' --js_output_file=test/video.compiled.js'; + command += ' --create_source_map test/video.compiled.js.map --source_map_format=V3'; + // command += ' --externs test/qunit-externs.js'; + command += ' --output_wrapper "(function() {%output%})();//@ sourceMappingURL=video.compiled.js.map"'; + + exec(command, { maxBuffer: 500*1024 }, function(err, stdout, stderr){ + + if (err) { + grunt.warn(err); + done(false); + } + + if (stdout) { + grunt.log.writeln(stdout); + } + + grunt.log.writeln("done!") + done(); + }); + }); + +}; diff --git a/design/Icon Design.png b/design/Icon Design.png deleted file mode 100644 index ae1b79fe70..0000000000 Binary files a/design/Icon Design.png and /dev/null differ diff --git a/design/loading.gif b/design/loading.gif deleted file mode 100644 index 70b533298e..0000000000 Binary files a/design/loading.gif and /dev/null differ diff --git a/design/skin.old.css b/design/skin.old.css deleted file mode 100644 index 657ae32179..0000000000 --- a/design/skin.old.css +++ /dev/null @@ -1,190 +0,0 @@ -/* DEFAULT SKIN (override in another file) -================================================================================ -Using all CSS to draw the controls. Images could be used if desired. -Instead of editing this file, I recommend creating your own skin CSS file to be included after this file, -so you can upgrade to newer versions easier. */ - -.vjs-original-skin .vjs-controls { - position: absolute; margin: 0; opacity: 0.85; color: #fff; - display: block; /* Start hidden */ - left: 0; right: 0; /* 100% width of video-js-box */ - width: 100%; - bottom: 0px; /* Distance from the bottom of the box/video. Keep 0. Use height to add more bottom margin. */ - height: 35px; /* Including any margin you want above or below control items */ - padding: 0; /* Controls are absolutely position, so no padding necessary */ - -webkit-transition: opacity 0.5s linear; - -moz-transition: opacity 0.5s linear; - -o-transition: opacity 0.5s linear; - -ms-transition: opacity 0.5s linear; - transition: opacity 0.5s linear; -} - -.vjs-original-skin .vjs-control { - position: absolute; /* Use top, bottom, left, and right to specifically position the control. */ - text-align: center; margin: 0; padding: 0; - height: 25px; /* Default height of individual controls */ - top: 5px; /* Top margin to put space between video and controls when controls are below */ - - /* CSS Background Gradients - Using to give the aqua-ish look. */ - /* Default */ background-color: #0B151A; - /* Webkit */ background: #1F3744 -webkit-gradient(linear, left top, left bottom, from(#0B151A), to(#1F3744)) left 12px; - /* Firefox */ background: #1F3744 -moz-linear-gradient(top, #0B151A, #1F3744) left 12px; - - /* CSS Curved Corners */ - -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; - - /* CSS Shadows */ - -webkit-box-shadow: 1px 1px 2px #000; -moz-box-shadow: 1px 1px 2px #000; box-shadow: 1px 1px 2px #000; -} - -/* Placement of Control Items - - Left side of pogress bar, use left & width - - Rigth side of progress bar, use right & width - - Expand with the video (like progress bar) use left & right - (using div.x to make more specific than vjs-control style) -*/ -.vjs-original-skin div.vjs-play-control { left: 5px; width: 25px; } -.vjs-original-skin div.vjs-progress-control { left: 35px; right: 165px; } /* Using left & right so it expands with the width of the video */ -.vjs-original-skin div.vjs-time-control { width: 75px; right: 90px; } /* Time control and progress bar are combined to look like one */ -.vjs-original-skin div.vjs-volume-control { width: 50px; right: 35px; } -.vjs-original-skin div.vjs-fullscreen-control { width: 25px; right: 5px; } - -/* Removing curved corners on progress control and time control to join them. */ -.vjs-original-skin div.vjs-progress-control { - -webkit-border-top-right-radius: 0; -moz-border-radius-topright: 0; border-top-right-radius: 0; - -webkit-border-bottom-right-radius: 0; -moz-border-radius-bottomright: 0; border-bottom-right-radius: 0; -} -.vjs-original-skin div.vjs-time-control { - -webkit-border-top-left-radius: 0; -moz-border-radius-topleft: 0; border-top-left-radius: 0; - -webkit-border-bottom-left-radius: 0; -moz-border-radius-bottomleft: 0; border-bottom-left-radius: 0; -} - -/* Play/Pause --------------------------------------------------------------------------------- */ -.vjs-original-skin .vjs-play-control { cursor: pointer !important; } -/* Play Icon */ -.vjs-original-skin .vjs-play-control span { display: block; font-size: 0; line-height: 0; } -.vjs-original-skin.vjs-paused .vjs-play-control span { - width: 0; height: 0; margin: 8px 0 0 8px; - /* Drawing the play triangle with borders - http://www.infimum.dk/HTML/slantinfo.html */ - border-left: 10px solid #fff; /* Width & Color of play icon */ - /* Height of play icon is total top & bottom border widths. Color is transparent. */ - border-top: 5px solid rgba(0,0,0,0); border-bottom: 5px solid rgba(0,0,0,0); -} -.vjs-original-skin.vjs-playing .vjs-play-control span { - width: 3px; height: 10px; margin: 8px auto 0; - /* Drawing the pause bars with borders */ - border-top: 0px; border-left: 3px solid #fff; border-bottom: 0px; border-right: 3px solid #fff; -} - -/* Progress --------------------------------------------------------------------------------- */ -.vjs-original-skin .vjs-progress-holder { /* Box containing play and load progresses */ - position: relative; padding: 0; overflow:hidden; cursor: pointer !important; - height: 9px; border: 1px solid #777; - margin: 7px 1px 0 5px; /* Placement within the progress control item */ - -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; -} -.vjs-original-skin .vjs-progress-holder div { /* Progress Bars */ - position: absolute; display: block; width: 0; height: 9px; margin: 0; padding: 0; - left: 0; top: 0; /*Needed for IE6*/ - -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; -} -.vjs-original-skin .vjs-play-progress { - /* CSS Gradient */ - /* Default */ background: #fff; - /* Webkit */ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#777)); - /* Firefox */ background: -moz-linear-gradient(top, #fff, #777); -} -.vjs-original-skin .vjs-load-progress { - opacity: 0.8; - /* CSS Gradient */ - /* Default */ background-color: #555; - /* Webkit */ background: -webkit-gradient(linear, left top, left bottom, from(#555), to(#aaa)); - /* Firefox */ background: -moz-linear-gradient(top, #555, #aaa); -} - -/* Time Display --------------------------------------------------------------------------------- */ -.vjs-original-skin .vjs-time-control { font-size: 10px; line-height: 1; font-weight: normal; font-family: Helvetica, Arial, sans-serif; } -.vjs-original-skin .vjs-time-control span { line-height: 25px; /* Centering vertically */ } - -/* Volume --------------------------------------------------------------------------------- */ -.vjs-original-skin .vjs-volume-control { cursor: pointer !important; } -.vjs-original-skin .vjs-volume-control div { display: block; margin: 0 5px 0 5px; padding: 4px 0 0 0; } -/* Drawing the volume icon using 6 span elements */ -.vjs-original-skin .vjs-volume-control div span { /* Individual volume bars */ - float: left; padding: 0; font-size: 0; line-height: 0; - margin: 0 2px 0 0; /* Space between */ - width: 5px; height: 0px; /* Total height is height + bottom border */ - border-bottom: 18px solid #555; /* Default (off) color and height of visible portion */ -} -.vjs-original-skin .vjs-volume-control div span.vjs-volume-level-on { border-color: #fff; /* Volume on bar color */ } -/* Creating differnt bar heights through height (transparent) and bottom border (visible). */ -.vjs-original-skin .vjs-volume-control div span.vjs-vc-1 { border-bottom-width: 2px; height: 16px; } -.vjs-original-skin .vjs-volume-control div span.vjs-vc-2 { border-bottom-width: 4px; height: 14px; } -.vjs-original-skin .vjs-volume-control div span.vjs-vc-3 { border-bottom-width: 7px; height: 11px; } -.vjs-original-skin .vjs-volume-control div span.vjs-vc-4 { border-bottom-width: 10px; height: 8px; } -.vjs-original-skin .vjs-volume-control div span.vjs-vc-5 { border-bottom-width: 14px; height: 4px; } -.vjs-original-skin .vjs-volume-control div span.vjs-vc-6 { margin-right: 0; } - -/* Fullscreen --------------------------------------------------------------------------------- */ -.vjs-original-skin .vjs-fullscreen-control { cursor: pointer !important; } -.vjs-original-skin .vjs-fullscreen-control div { - padding: 0; text-align: left; vertical-align: top; cursor: pointer !important; - margin: 5px 0 0 5px; /* Placement within the fullscreen control item */ - width: 20px; height: 20px; -} -/* Drawing the fullscreen icon using 4 span elements */ -.vjs-original-skin .vjs-fullscreen-control div span { float: left; margin: 0; padding: 0; font-size: 0; line-height: 0; width: 0; text-align: left; vertical-align: top; } -.vjs-original-skin .vjs-fullscreen-control div span.vjs-fc-1 { /* Top-left triangle */ - margin-right: 3px; /* Space between top-left and top-right */ - margin-bottom: 3px; /* Space between top-left and bottom-left */ - border-top: 6px solid #fff; /* Height and color */ - border-right: 6px solid rgba(0,0,0,0); /* Width */ -} -.vjs-original-skin .vjs-fullscreen-control div span.vjs-fc-2 { border-top: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); } -.vjs-original-skin .vjs-fullscreen-control div span.vjs-fc-3 { clear: both; margin: 0 3px 0 0; border-bottom: 6px solid #fff; border-right: 6px solid rgba(0,0,0,0); } -.vjs-original-skin .vjs-fullscreen-control div span.vjs-fc-4 { border-bottom: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); } -/* Icon when video is in fullscreen mode */ -.vjs-original-skin.vjs-fullscreen .vjs-fullscreen-control div span.vjs-fc-1 { border: none; border-bottom: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); } -.vjs-original-skin.vjs-fullscreen .vjs-fullscreen-control div span.vjs-fc-2 { border: none; border-bottom: 6px solid #fff; border-right: 6px solid rgba(0,0,0,0); } -.vjs-original-skin.vjs-fullscreen .vjs-fullscreen-control div span.vjs-fc-3 { border: none; border-top: 6px solid #fff; border-left: 6px solid rgba(0,0,0,0); } -.vjs-original-skin.vjs-fullscreen .vjs-fullscreen-control div span.vjs-fc-4 { border: none; border-top: 6px solid #fff; border-right: 6px solid rgba(0,0,0,0); } - - - - - -/* Big Play Button (at start) ----------------------------------------------------------*/ -.vjs-original-skin .vjs-big-play-button { - display: block; /* Start hidden */ z-index: 2; - position: absolute; top: 50%; left: 50%; width: 80px; height: 80px; margin: -43px 0 0 -43px; text-align: center; vertical-align: center; cursor: pointer !important; - border: 3px solid #fff; opacity: 0.9; - -webkit-border-radius: 20px; -moz-border-radius: 20px; border-radius: 20px; - - /* CSS Background Gradients */ - /* Default */ background-color: #0B151A; - /* Webkit */ background: #1F3744 -webkit-gradient(linear, left top, left bottom, from(#0B151A), to(#1F3744)) left 40px; - /* Firefox */ background: #1F3744 -moz-linear-gradient(top, #0B151A, #1F3744) left 40px; - - /* CSS Shadows */ - -webkit-box-shadow: 4px 4px 8px #000; -moz-box-shadow: 4px 4px 8px #000; box-shadow: 4px 4px 8px #000; -} - -.vjs-original-skin div.vjs-big-play-button:hover { - -webkit-box-shadow: 0px 0px 80px #fff; -moz-box-shadow: 0px 0px 80px #fff; box-shadow: 0px 0px 80px #fff; -} - -.vjs-original-skin div.vjs-big-play-button span { - display: block; font-size: 0; line-height: 0; - width: 0; height: 0; margin: 20px 0 0 23px; - /* Drawing the play triangle with borders - http://www.infimum.dk/HTML/slantinfo.html */ - border-left: 40px solid #fff; /* Width & Color of play icon */ - /* Height of play icon is total top & bottom border widths. Color is transparent. */ - border-top: 20px solid rgba(0,0,0,0); border-bottom: 20px solid rgba(0,0,0,0); -} \ No newline at end of file diff --git a/dev.html.example b/dev.html.example deleted file mode 100644 index 1d78f163b8..0000000000 --- a/dev.html.example +++ /dev/null @@ -1,52 +0,0 @@ - - - - - HTML5 Video Player - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/package.json b/package.json index 4e4f38f033..a36a671f98 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,33 @@ -{ "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" - } -} \ No newline at end of file +{ + "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": "grunt qunit" + }, + "repository": { + "type": "git", + "url": "https://github.com/zencoder/video-js.git" + }, + "devDependencies": { + "grunt-cli": "~0.1.0", + "grunt": "0.4.0rc4", + "grunt-contrib-jshint": "~0.1.0", + "grunt-contrib-nodeunit": "~0.1.0", + "jshint": "0.6.1", + "connect": "2.1.3", + "grunt-contrib-uglify": "~0.1.0", + "grunt-closure-compiler": "0.0.13", + "grunt-contrib-watch": "~0.1.4", + "grunt-contrib-concat": "~0.1.1", + "grunt-contrib-qunit": "~0.1.0" + } +} diff --git a/sandbox/index.html.example b/sandbox/index.html.example new file mode 100644 index 0000000000..b7be76f2ca --- /dev/null +++ b/sandbox/index.html.example @@ -0,0 +1,53 @@ + + + + + Video.js Sandbox + + + + + + + + + + + + + + + + + + + + + + + +

You can use /sandbox/ for writing and testing your own code. Nothing in /sandbox/ will get checked into the repo, except files that end in .example, so please don't edit or add those files. To get started make a copy of index.html.example and rename it to index.html.

+ + + + + + + diff --git a/design/video-js.css b/src/css/video-js.css similarity index 100% rename from design/video-js.css rename to src/css/video-js.css diff --git a/src/css/video-js.fw.png b/src/css/video-js.fw.png new file mode 100644 index 0000000000..2c7f208c21 Binary files /dev/null and b/src/css/video-js.fw.png differ diff --git a/design/video-js.png b/src/css/video-js.png similarity index 100% rename from design/video-js.png rename to src/css/video-js.png diff --git a/src/component.js b/src/js/component.js similarity index 100% rename from src/component.js rename to src/js/component.js diff --git a/src/controls.js b/src/js/controls.js similarity index 100% rename from src/controls.js rename to src/js/controls.js diff --git a/src/core.js b/src/js/core.js similarity index 100% rename from src/core.js rename to src/js/core.js diff --git a/src/events.js b/src/js/events.js similarity index 100% rename from src/events.js rename to src/js/events.js diff --git a/src/exports.js b/src/js/exports.js similarity index 100% rename from src/exports.js rename to src/js/exports.js diff --git a/src/goog.base.js b/src/js/goog.base.js similarity index 100% rename from src/goog.base.js rename to src/js/goog.base.js diff --git a/src/json.js b/src/js/json.js similarity index 100% rename from src/json.js rename to src/js/json.js diff --git a/src/lib.js b/src/js/lib.js similarity index 100% rename from src/lib.js rename to src/js/lib.js diff --git a/src/media.flash.externs.js b/src/js/media.flash.externs.js similarity index 100% rename from src/media.flash.externs.js rename to src/js/media.flash.externs.js diff --git a/src/media.flash.js b/src/js/media.flash.js similarity index 100% rename from src/media.flash.js rename to src/js/media.flash.js diff --git a/src/media.html5.js b/src/js/media.html5.js similarity index 100% rename from src/media.html5.js rename to src/js/media.html5.js diff --git a/src/media.js b/src/js/media.js similarity index 100% rename from src/media.js rename to src/js/media.js diff --git a/src/player.js b/src/js/player.js similarity index 100% rename from src/player.js rename to src/js/player.js diff --git a/src/setup.js b/src/js/setup.js similarity index 100% rename from src/setup.js rename to src/js/setup.js diff --git a/src/tracks.js b/src/js/tracks.js similarity index 100% rename from src/tracks.js rename to src/js/tracks.js diff --git a/tech/flash/video-js.swf b/src/swf/video-js.swf similarity index 100% rename from tech/flash/video-js.swf rename to src/swf/video-js.swf diff --git a/tech/flash/swfobject.js b/tech/flash/swfobject.js deleted file mode 100644 index 8eafe9dd83..0000000000 --- a/tech/flash/swfobject.js +++ /dev/null @@ -1,4 +0,0 @@ -/* SWFObject v2.2 - is released under the MIT License -*/ -var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab 0 ? callback.apply(null, params) : callback.call(); - } - - - /** - * Stores submitted callbacks for each iframe being tracked and each - * event for that iframe. - * - * @param eventName (String): Name of the event. Eg. api_onPlay - * @param callback (Function): Function that should get executed when the - * event is fired. - * @param target_id (String) [Optional]: If handling more than one iframe then - * it stores the different callbacks for different iframes based on the iframe's - * id. - */ - function storeCallback(eventName, callback, target_id) { - if (target_id) { - if (!eventCallbacks[target_id]) { - eventCallbacks[target_id] = {}; - } - eventCallbacks[target_id][eventName] = callback; - } - else { - eventCallbacks[eventName] = callback; - } - } - - /** - * Retrieves stored callbacks. - */ - function getCallback(eventName, target_id) { - if (target_id) { - return eventCallbacks[target_id][eventName]; - } - else { - return eventCallbacks[eventName]; - } - } - - function removeCallback(eventName, target_id) { - if (target_id && eventCallbacks[target_id]) { - if (!eventCallbacks[target_id][eventName]) { - return false; - } - eventCallbacks[target_id][eventName] = null; - } - else { - if (!eventCallbacks[eventName]) { - return false; - } - eventCallbacks[eventName] = null; - } - - return true; - } - - /** - * Returns a domain's root domain. - * Eg. returns http://vimeo.com when http://vimeo.com/channels is sbumitted - * - * @param url (String): Url to test against. - * @return url (String): Root domain of submitted url - */ - function getDomainFromUrl(url) { - if (url.substr(0, 2) === '//') { - url = window.location.protocol + url; - } - - var url_pieces = url.split('/'), - domain_str = ''; - - for(var i = 0, length = url_pieces.length; i < length; i++) { - if(i<3) {domain_str += url_pieces[i];} - else {break;} - if(i<2) {domain_str += '/';} - } - - return domain_str; - } - - function isFunction(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - } - - function isArray(obj) { - return toString.call(obj) === '[object Array]'; - } - - // Give the init function the Froogaloop prototype for later instantiation - Froogaloop.fn.init.prototype = Froogaloop.fn; - - // Listens for the message event. - // W3C - if (window.addEventListener) { - window.addEventListener('message', onMessageReceived, false); - } - // IE - else { - window.attachEvent('onmessage', onMessageReceived); - } - - // Expose froogaloop to the global object - return (window.Froogaloop = window.$f = Froogaloop); - -})(); diff --git a/tech/youtube/youtube.html b/tech/youtube/youtube.html deleted file mode 100644 index 33e5f73d57..0000000000 --- a/tech/youtube/youtube.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - YouTube Test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tech/youtube/youtube.js b/tech/youtube/youtube.js deleted file mode 100644 index 8a4dfe070c..0000000000 --- a/tech/youtube/youtube.js +++ /dev/null @@ -1,369 +0,0 @@ - -/* VideoJS-YouTube - YouTube iFrame Wrapper -================================================================================ */ -_V_.youtube = _V_.PlaybackTech.extend({ - - init: function(player, options){ - this.player = player; - - var source = options.source; - - // Extract the YouTube videoID from the source - var videoId = this.getVideoId(source.src); - - // Which element to embed in - var parentEl = options.parentEl; - - // Generate ID for iFrame - var objId = player.el.id+"_youtube_api"; - - // Create an iFrame for the YouTube player - var iFrm = this.el = _V_.createElement("iframe", { - id: objId + "_iframe", - name: objId + "_iframe", - className: "vjs-tech", - scrolling: "no", - marginWidth: 0, - marginHeight: 0, - frameBorder: 0, - webkitAllowFullScreen: "", - mozallowfullscreen: "", - allowFullScreen: "" - }); - - // Store a global list of currently loading players - _V_.youtube.loadingEls = _V_.youtube.loadingEls || []; - _V_.youtube.loadingEls.push(parentEl); - - var playerOptions = player.options; - var optionsParams = options.params || {}; - - // Setup player parameters - var params = { - disablekb: 1, - enablejsapi: 1, - iv_load_policy: 3, - modestbranding: 1, - playerapiid: objId, - wmode: "opaque", // Opaque is needed to overlay controls, but can affect playback performance (Flash only) - rel: 0, - showinfo: 0, - showsearch: 0, - controls: this.toBoolInt(optionsParams.ytcontrols || playerOptions.ytcontrols), - autoplay: this.toBoolInt(optionsParams.autoplay || playerOptions.autoplay), - loop: this.toBoolInt(optionsParams.loop || playerOptions.loop), - hd: this.toBoolInt(optionsParams.hd || playerOptions.hd) - }; - - var p = (document.location.protocol == 'https:') ? 'https:' : 'http:'; - - if (document.domain != 'localhost' && document.location.protocol != 'file:') - params.origin = p + "//" + document.domain; - - this.player.apiArgs = { - videoId: videoId, - playerVars: params, - events: { - "onReady": _V_.youtube.onReady, - "onStateChange": _V_.youtube.onStateChange, - "onPlaybackQualityChange": _V_.youtube.onPlaybackQualityChange, - "onError": _V_.youtube.onError - } - }; - - _V_.addEvent(parentEl, "techready", _V_.proxy(this, function(){ - // YouTube JS API is ready, load the player - iFrm.src = p + "//www.youtube.com/embed/" + videoId + "?" + - this.makeQueryString(params); - // Initialize the YouTube Player object. Only pass events as arguments, - // since all of our other parameters went into the iFrame URL - this.youtube = new YT.Player(iFrm, { events: this.player.apiArgs.events }); - })); - - // Add iFrame to player div - _V_.insertFirst(iFrm, parentEl); - - _V_.youtube.updateVideoQuality(this.player, null); - - this.loadApi(); - }, - - destroy: function(){ - this.el.parentNode.removeChild(this.el); - this.youtube.destroy(); - delete this.youtube; - }, - - play: function(){ this.youtube.playVideo(); }, - pause: function(){ this.youtube.pauseVideo(); }, - paused: function(){ - var state = this.youtube.getPlayerState(); - return state !== YT.PlayerState.PLAYING && - state !== YT.PlayerState.BUFFERING; - }, - - src: function(src){ - delete this.player.error; - - // FIXME: Does this work or do we have to set the iFrame src again? - var videoId = this.getVideoId(src); - this.youtube.loadVideoById(videoId); - }, - load: function(){ }, - poster: function(){ - var videoId = this.getVideoId(this.youtube.getVideoUrl()); - return "http://img.youtube.com/vi/" + videoId + "/0.jpg"; - }, - - currentTime: function(){ return this.youtube.getCurrentTime() || 0; }, - setCurrentTime: function(seconds){ - this.youtube.seekTo(seconds, true); - this.player.triggerEvent("timeupdate"); - }, - - duration: function(){ return this.youtube.getDuration() || 0; }, - buffered: function(){ - var loadedBytes = this.youtube.getVideoBytesLoaded(); - var totalBytes = this.youtube.getVideoBytesTotal(); - if (!loadedBytes || !totalBytes) return 0; - - var duration = this.youtube.getDuration(); - var secondsBuffered = (loadedBytes / totalBytes) * duration; - var secondsOffset = (this.youtube.getVideoStartBytes() / totalBytes) * duration; - return _V_.createTimeRange(secondsOffset, secondsOffset + secondsBuffered); - }, - - volume: function(){ - if (isNaN(this.youtube.volumeVal)) - this.youtube.volumeVal = this.youtube.getVolume() / 100.0; - return this.youtube.volumeVal; - }, - setVolume: function(percentAsDecimal){ - if (percentAsDecimal != this.youtube.volumeVal) { - this.youtube.volumeVal = percentAsDecimal; - this.youtube.setVolume(percentAsDecimal * 100.0); - this.player.triggerEvent("volumechange"); - } - }, - muted: function(){ return this.youtube.isMuted(); }, - setMuted: function(muted){ - if (muted) - this.youtube.mute(); - else - this.youtube.unMute(); - - // Volume changes do not show up in the API immediately, so we need - // to wait for a moment - var self = this; - setTimeout(function() { self.player.triggerEvent("volumechange"); }, 50); - }, - - width: function(){ return this.el.offsetWidth; }, - height: function(){ return this.el.offsetHeight; }, - - currentSrc: function(){ return this.youtube.getVideoUrl(); }, - - preload: function(){ return false; }, - setPreload: function(val){ }, - autoplay: function(){ return !!this.player.apiArgs.playerVars.autoplay; }, - setAutoplay: function(val){ }, - loop: function(){ return !!this.player.apiArgs.playerVars.loop; }, - setLoop: function(val){ - this.player.apiArgs.playerVars.loop = (val ? 1 : 0); - // We handle looping manually - //this.youtube.setLoop(val); - }, - - supportsFullScreen: function(){ return false; }, - enterFullScreen: function(){ return false; }, - - error: function(){ return this.player.error; }, - seeking: function(){ return false; }, - ended: function(){ return this.youtube.getPlayerState() === YT.PlayerState.ENDED; }, - videoWidth: function(){ return this.player.videoWidth; }, - videoHeight: function(){ return this.player.videoHeight; }, - controls: function(){ return this.player.options.controls; }, - defaultMuted: function(){ return false; }, - - // Helpers ------------------------------------------------------------------ - - makeQueryString: function(args) { - var array = []; - for (var key in args) { - if (args.hasOwnProperty(key)) - array.push(encodeURIComponent(key) + "=" + encodeURIComponent(args[key])); - } - return array.join("&"); - }, - - getVideoId: function(url) { - return url.match(/v=([^&]+)/)[1]; - }, - - toBoolInt: function(val) { - return val ? 1 : 0; - }, - - loadApi: function() { - // Check if the YouTube JS API has already been loaded - var js, id = "youtube-jssdk", ref = document.getElementsByTagName("script")[0]; - if (_V_.el(id)) { - window.onYouTubePlayerAPIReady(); - return; - } - - // Asynchronously load the YouTube JS API - var p = (document.location.protocol == "https:") ? "https:" : "http:"; - js = _V_.createElement("script", { id: id, async: true, src: p + "//www.youtube.com/player_api" }); - ref.parentNode.insertBefore(js, ref); - } -}); - -// Event callbacks ------------------------------------------------------------ - -_V_.youtube.onReady = function(e){ - var player = e.target.getIframe().parentNode.player; - - player.tech.triggerReady(); - player.triggerReady(); - player.triggerEvent("durationchange"); - - _V_.youtube.hideOverlay(player); -}; - -_V_.youtube.onStateChange = function(e){ - var player = e.target.getIframe().parentNode.player; - - // Suppress any duplicate events from YouTube - if (player.lastState === e.data) - return; - - switch (e.data) { - case -1: // Unstarted - player.triggerEvent("durationchange"); - break; - case YT.PlayerState.CUED: - break; - case YT.PlayerState.ENDED: - player.triggerEvent("ended"); - _V_.youtube.hideOverlay(player); - - // YouTube looping doesn't seem to play well with VideoJS, so we need to - // implement it manually here - if (player.apiArgs.playerVars.loop) { - player.tech.youtube.seekTo(0, true); - player.tech.youtube.playVideo(); - } else { - player.tech.youtube.stopVideo(); - } - break; - case YT.PlayerState.PLAYING: - player.triggerEvent("timeupdate"); - player.triggerEvent("playing"); - player.triggerEvent("play"); - break; - case YT.PlayerState.PAUSED: - player.triggerEvent("pause"); - break; - case YT.PlayerState.BUFFERING: - player.triggerEvent("timeupdate"); - player.triggerEvent("waiting"); - // Hide the waiting spinner since YouTube has its own - player.loadingSpinner.hide(); - break; - } - - player.lastState = e.data; -}; - -_V_.youtube.onPlaybackQualityChange = function(e){ - var player = e.target.getIframe().parentNode.player; - _V_.youtube.updateVideoQuality(player, e.data); - player.triggerEvent("ratechange"); -}; - -_V_.youtube.onError = function(e){ - var player = e.target.getIframe().parentNode.player; - player.error = e.data; - player.triggerEvent("error"); -}; - -// Helpers -------------------------------------------------------------------- - -_V_.youtube.hideOverlay = function(player) { - // Hide the big play button and poster since YouTube provides these. Using - // our own prevents the video from playing on the first click in mobile - // devices - player.bigPlayButton.hide(); - player.posterImage.hide(); -}; - -_V_.youtube.updateVideoQuality = function(player, quality) { - switch (quality) { - case 'medium': - player.videoWidth = 480; - player.videoHeight = 360; - break; - case 'large': - player.videoWidth = 640; - player.videoHeight = 480; - break; - case 'hd720': - player.videoWidth = 960; - player.videoHeight = 720; - break; - case 'hd1080': - player.videoWidth = 1440; - player.videoHeight = 1080; - break; - case 'highres': - player.videoWidth = 1920; - player.videoHeight = 1080; - break; - case 'small': - player.videoWidth = 320; - player.videoHeight = 240; - break; - default: - player.videoWidth = 0; - player.videoHeight = 0; - break; - } -}; - -// Support testing ------------------------------------------------------------ - -_V_.youtube.isSupported = function(){ - return true; -}; - -_V_.youtube.canPlaySource = function(srcObj){ - return srcObj.type == "video/youtube"; -}; - -_V_.youtube.prototype.support = { - formats: { - "video/youtube": "YT" - }, - - // Optional events that we can manually mimic with timers - progressEvents: false, - timeupdateEvents: false, - - //fullscreen: true, - // In iOS, if you move a video element in the DOM, it breaks video playback. - movingElementInDOM: !_V_.isIOS(), - - fullscreenResize: true, - parentResize: true -}; - -// YouTube JS API load callback ----------------------------------------------- - -window.onYouTubePlayerAPIReady = function() { - // Fire a techready event for each loading player - var loadingEl; - while ((loadingEl = _V_.youtube.loadingEls.shift())) { - loadingEl.player.triggerEvent("techready"); - } -}; diff --git a/test/unit/phantom-logging.js b/test/phantom-logging.js similarity index 71% rename from test/unit/phantom-logging.js rename to test/phantom-logging.js index 0f5fd9e4f5..9e1c517609 100644 --- a/test/unit/phantom-logging.js +++ b/test/phantom-logging.js @@ -8,14 +8,15 @@ QUnit.begin = function () { QUnit.moduleDone = function (opts) { if (opts.failed === 0) { - console.log("\u2714 All tests passed in '" + opts.name + "' module") + console.log("\n\u2714 All tests passed in '" + opts.name + "' module") } else { - console.log("\u2716 " + opts.failed + " tests failed in '" + opts.name + "' module") + console.log("\n\u2716 " + opts.failed + " tests failed in '" + opts.name + "' module") } } -QUnit.done = function (opts) { +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 + return false; +}); diff --git a/test/unit.html b/test/unit.html index 02ca6adea6..c181694ea0 100644 --- a/test/unit.html +++ b/test/unit.html @@ -8,24 +8,24 @@ - + - + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/test/unit.js b/test/unit.js new file mode 100644 index 0000000000..b69d90476a --- /dev/null +++ b/test/unit.js @@ -0,0 +1,646 @@ +module("Component"); + +test('should create an element', function(){ + var comp = new vjs.Component({}, {}); + + ok(comp.el().nodeName); +}); + +test('should add a child component', function(){ + var comp = new vjs.Component({}); + + var child = comp.addChild("component"); + + ok(comp.children().length === 1); + ok(comp.children()[0] === child); + ok(comp.el().childNodes[0] === child.el()); + ok(comp.getChild('component') === child); + ok(comp.getChildById(child.id()) === child); +}); + +test('should init child coponents from options', function(){ + var comp = new vjs.Component({}, { + children: { + 'component': true + } + }); + + ok(comp.children().length === 1); + ok(comp.el().childNodes.length === 1); +}); + +test('should dispose of component and children', function(){ + var comp = new vjs.Component({}); + + // Add a child + var child = comp.addChild("Component"); + ok(comp.children().length === 1); + + // Add a listener + comp.on('click', function(){ return true; }); + var data = vjs.getData(comp.el()); + var id = comp.el()[vjs.expando]; + + comp.dispose(); + + ok(!comp.children(), 'component children were deleted'); + ok(!comp.el(), 'component element was deleted'); + ok(!child.children(), 'child children were deleted'); + ok(!child.el(), 'child element was deleted'); + ok(!vjs.cache[id], 'listener cache nulled') + ok(vjs.isEmpty(data), 'original listener cache object was emptied') +}); + +test('should add and remove event listeners to element', function(){ + var comp = new vjs.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(2); + + var testListener = function(){ + ok(true, 'fired event once'); + ok(this === comp, 'listener has the component as context'); + }; + + 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 vjs.Component({}, {}); + + expect(1); + + var testListener = function(){ + ok(true, 'fired event once'); + }; + + comp.one('test-event', testListener); + comp.trigger('test-event'); + comp.trigger('test-event'); +}); + +test('should trigger a listener when ready', function(){ + expect(2); + + var optionsReadyListener = function(){ + ok(true, 'options listener fired') + }; + var methodReadyListener = function(){ + ok(true, 'ready method listener fired') + }; + + var comp = new vjs.Component({}, {}, optionsReadyListener); + + comp.triggerReady(); + + comp.ready(methodReadyListener); + + // First two listeners should only be fired once and then removed + comp.triggerReady(); +}); + +test('should add and remove a CSS class', function(){ + var comp = new vjs.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 show and hide an element', function(){ + var comp = new vjs.Component({}, {}); + + comp.hide(); + ok(comp.el().style.display === 'none'); + comp.show(); + ok(comp.el().style.display === 'block'); +}); + +test('should change the width and height of a component', function(){ + var container = document.createElement('div'); + var comp = new vjs.Component({}, {}); + var el = comp.el(); + var fixture = document.getElementById('qunit-fixture'); + + fixture.appendChild(container); + container.appendChild(el); + // Container of el needs dimensions or the component won't have dimensions + container.style.width = '1000px' + container.style.height = '1000px' + + comp.width('50%'); + comp.height('123px'); + + ok(comp.width() === 500, 'percent values working'); + ok(vjs.getComputedStyleValue(el, 'width') === comp.width() + 'px', 'matches computed style'); + ok(comp.height() === 123, 'px values working'); + + comp.width(321); + ok(comp.width() === 321, 'integer values working'); +}); + +module("Core"); + +test('should create a video tag and have access children in old IE', function(){ + var fixture = document.getElementById('qunit-fixture'); + + fixture.innerHTML += ""; + + vid = document.getElementById('test_vid_id'); + + ok(vid.childNodes.length === 1); + ok(vid.childNodes[0].getAttribute('type') === 'video/mp4'); +}); + +test('should return a video player instance', function(){ + var fixture = document.getElementById('qunit-fixture'); + fixture.innerHTML += ""; + + var player = videojs('test_vid_id'); + ok(player, 'created player from tag'); + ok(player.id() === 'test_vid_id'); + ok(videojs.players['test_vid_id'] === player, 'added player to global reference') + + var playerAgain = videojs('test_vid_id'); + ok(player === playerAgain, 'did not create a second player from same tag'); + + var tag2 = document.getElementById('test_vid_id2'); + var player2 = videojs(tag2); + ok(player2.id() === 'test_vid_id2', 'created player from element'); +}); + +module("Events"); + +test('should add and remove an event listener to an element', function(){ + expect(1); + + var el = document.createElement('div'); + var listener = function(){ + ok(true, 'Click Triggered'); + }; + + vjs.on(el, 'click', listener); + vjs.trigger(el, 'click'); // 1 click + vjs.off(el, 'click', listener) + vjs.trigger(el, 'click'); // No click should happen. +}); + +test('should remove all listeners of a type', function(){ + var el = document.createElement('div'); + var clicks = 0; + var listener = function(){ + clicks++; + }; + var listener2 = function(){ + clicks++; + }; + + vjs.on(el, 'click', listener); + vjs.on(el, 'click', listener2); + vjs.trigger(el, 'click'); // 2 clicks + + ok(clicks === 2, 'both click listeners fired') + + vjs.off(el, 'click') + vjs.trigger(el, 'click'); // No click should happen. + + ok(clicks === 2, 'no click listeners fired') +}); + +test('should remove all listeners from an element', function(){ + expect(2); + + var el = document.createElement('div'); + var listener = function(){ + ok(true, 'Fake1 Triggered'); + }; + var listener2 = function(){ + ok(true, 'Fake2 Triggered'); + }; + + vjs.on(el, 'fake1', listener); + vjs.on(el, 'fake2', listener2); + + vjs.trigger(el, 'fake1'); + vjs.trigger(el, 'fake2'); + + vjs.off(el); + + // No listener should happen. + vjs.trigger(el, 'fake1'); + vjs.trigger(el, 'fake2'); +}); + +test('should listen only once', function(){ + expect(1); + + var el = document.createElement('div'); + var listener = function(){ + ok(true, 'Click Triggered'); + }; + + vjs.one(el, 'click', listener); + vjs.trigger(el, 'click'); // 1 click + vjs.trigger(el, 'click'); // No click should happen. +}); + +module("Lib"); + +test('should create an element', function(){ + var div = vjs.createEl(); + var span = vjs.createEl('span', { "data-test": "asdf", innerHTML:'fdsa' }) + ok(div.nodeName === 'DIV'); + ok(span.nodeName === 'SPAN'); + ok(span['data-test'] === 'asdf'); + ok(span.innerHTML === "fdsa"); +}); + +test('should make a string start with an uppercase letter', function(){ + var foo = vjs.capitalize('bar') + ok(foo === 'Bar'); +}); + +test('should loop through each property on an object', function(){ + var asdf = { + a: 1, + b: 2, + 'c': 3 + } + + // Add 3 to each value + vjs.eachProp(asdf, function(key, value){ + asdf[key] = value + 3; + }); + + deepEqual(asdf,{a:4,b:5,'c':6}) +}); + +test('should add context to a function', function(){ + var newContext = { test: 'obj'}; + var asdf = function(){ + ok(this === newContext); + } + var fdsa = vjs.bind(newContext, asdf); + + fdsa(); +}); + +test('should add and remove a class name on an element', function(){ + var el = document.createElement('div'); + vjs.addClass(el, 'test-class') + ok(el.className === 'test-class', 'class added'); + vjs.addClass(el, 'test-class') + ok(el.className === 'test-class', 'same class not duplicated'); + vjs.addClass(el, 'test-class2') + ok(el.className === 'test-class test-class2', 'added second class'); + vjs.removeClass(el, 'test-class') + ok(el.className === 'test-class2', 'removed first class'); +}); + +test('should get and remove data from an element', function(){ + var el = document.createElement('div'); + var data = vjs.getData(el); + var id = el[vjs.expando]; + + ok(typeof data === 'object', 'data object created'); + + // Add data + var testData = { asdf: 'fdsa' }; + data.test = testData; + ok(vjs.getData(el).test === testData, 'data added'); + + // Remove all data + vjs.removeData(el); + + ok(!vjs.cache[id], 'cached item nulled') + ok(el[vjs.expando] === null || el[vjs.expando] === undefined, 'element data id removed') +}); + +test('should read tag attributes from elements, including HTML5 in all browsers', function(){ + var container = document.createElement('div'); + + var tags = ''; + tags += '