├── .gitignore ├── aurora-websocket.min.js ├── readme.md ├── server-examples └── node │ ├── audio │ └── No Broadcast - Conundrum.flac │ ├── aurora-ws-server.coffee │ ├── aurora-ws-server.js │ ├── aurora-ws-server.js.map │ └── package.json ├── src ├── aurora-websocket.coffee ├── aurora-websocket.js ├── aurora-websocket.js.map ├── aurora-websocket.min.js └── aurora-websocket.min.js.map └── tests ├── README.md ├── aurora-websocket.min.js ├── aurora.js ├── config.coffee ├── crc32.coffee ├── data └── m4a │ └── base.m4a ├── helpers.coffee ├── qunit ├── qunit.css └── qunit.js ├── sources └── websocket.coffee ├── test.coffee ├── test.html └── ws-server ├── aurora-ws-server.coffee ├── aurora-ws-server.js ├── aurora-ws-server.map └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | server-examples/node/node_modules/ -------------------------------------------------------------------------------- /aurora-websocket.min.js: -------------------------------------------------------------------------------- 1 | (function(){var e={}.hasOwnProperty,t=function(t,r){function o(){this.constructor=t}for(var n in r)e.call(r,n)&&(t[n]=r[n]);return o.prototype=r.prototype,t.prototype=new o,t.__super__=r.prototype,t};AV.WebSocketSource=function(e){function r(e,t){return this.serverUrl=e,this.fileName=t,"undefined"==typeof WebSocket||null===WebSocket?this.emit("error","This browser does not have WebSocket support."):(this.socket=new WebSocket(this.serverUrl),null==this.socket.binaryType?(this.socket.close(),this.emit("error","This browser does not have binary WebSocket support.")):(this.bytesLoaded=0,this._setupSocket(),void 0))}return t(r,e),r.prototype.start=function(){return this._send(JSON.stringify({resume:!0}))},r.prototype.pause=function(){return this._send(JSON.stringify({pause:!0}))},r.prototype.reset=function(){return this._send(JSON.stringify({reset:!0}))},r.prototype._send=function(e){return this.open?this.socket.send(e):this._bufferMessage=e},r.prototype._setupSocket=function(){var e=this;return this.socket.binaryType="arraybuffer",this.socket.onopen=function(){return e.open=!0,e.fileName&&e.socket.send(JSON.stringify({fileName:e.fileName})),e._bufferMessage?(e.socket.send(e._bufferMessage),e._bufferMessage=null):void 0},this.socket.onmessage=function(t){var r,o;return o=t.data,"string"!=typeof o?(r=new AV.Buffer(new Uint8Array(o)),e.bytesLoaded+=r.length,e.length&&e.emit("progress",100*(e.bytesLoaded/e.length)),e.emit("data",r)):(o=JSON.parse(o),null!=o.fileSize?e.length=o.fileSize:null!=o.error?e.emit("error",o.error):o.end?e.socket.close():void 0)},this.socket.onclose=function(t){return e.open=!1,t.wasClean?e.emit("end"):e.emit("error","WebSocket closed uncleanly with code "+t.code+".")},this.socket.onerror=function(t){return e.emit("error",t)}},r}(AV.EventEmitter),AV.Asset.fromWebSocket=function(e,t){var r;return r=new AV.WebSocketSource(e,t),new AV.Asset(r)},AV.Player.fromWebSocket=function(e,t){var r;return r=AV.Asset.fromWebSocket(e,t),new AV.Player(r)}}).call(this); -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | aurora-websocket 2 | ================ 3 | 4 | WebSocket streaming plugin for [aurora.js](https://github.com/audiocogs/aurora.js) 5 | 6 | 7 | This will expose the following method on the aurora.js AV.Player class: 8 | 9 | ```javascript 10 | AV.Player.fromWebSocket(serverUrl, fileName) 11 | ``` 12 | 13 | Use your WebSocket server URI and track file name, eg: 14 | 15 | ```javascript 16 | var player = AV.Player.fromWebSocket('ws://localhost:8080', '01 Conundrum.flac'); 17 | player.play(); 18 | ``` 19 | 20 | Look to the server-examples folder for the server implementation required. 21 | So far I have only provided an implementation in Node, feel free to pull request with others. 22 | 23 | 24 | MIT Licensed. -------------------------------------------------------------------------------- /server-examples/node/audio/No Broadcast - Conundrum.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabslab/aurora-websocket/c6538b7184ffa2aced29cbe6d4187c09be518398/server-examples/node/audio/No Broadcast - Conundrum.flac -------------------------------------------------------------------------------- /server-examples/node/aurora-ws-server.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | Throttle = require('stream-throttle').Throttle 4 | WebSocketServer = require('ws').Server 5 | port = 8080 6 | 7 | wss = new WebSocketServer { port } 8 | audioFolder = './audio' 9 | 10 | wss.on 'connection', (ws) -> 11 | audioStream = null 12 | playing = false 13 | 14 | ws.on 'close', -> 15 | audioStream?.removeAllListeners() 16 | audioStream = null 17 | 18 | ws.on 'message', (msg) -> 19 | msg = JSON.parse msg 20 | 21 | if msg.fileName? 22 | audioPath = path.join audioFolder, msg.fileName 23 | fs.stat audioPath, (err, stats) -> 24 | if err 25 | ws.send JSON.stringify { error: 'Could not retrieve file.' } 26 | else 27 | ws.send JSON.stringify { fileSize: stats.size } 28 | createFileStream audioPath 29 | 30 | else if msg.resume 31 | audioStream?.resume() 32 | playing = true 33 | 34 | else if msg.pause 35 | audioStream?.pause() 36 | playing = false 37 | 38 | else if msg.reset 39 | audioStream?.removeAllListeners() 40 | playing = false 41 | createFileStream() 42 | 43 | return 44 | 45 | createFileStream = (audioPath) -> 46 | # throttle to a rate that should be enough for FLAC playback 47 | # if we don't throttle the WebSocket client can't start playback 48 | # until the whole file has streamed since the events happen too fast 49 | audioStream = fs.createReadStream(audioPath).pipe new Throttle({ rate: 700 * 1024 }) 50 | 51 | unless playing 52 | audioStream.pause() 53 | 54 | audioStream.on 'data', (data) -> 55 | ws.send data, { binary: true } 56 | 57 | audioStream.on 'end', -> 58 | ws.send JSON.stringify { end: true } 59 | 60 | console.log "Serving WebSocket for Aurora.js on port #{port}" 61 | -------------------------------------------------------------------------------- /server-examples/node/aurora-ws-server.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | (function() { 3 | var Throttle, WebSocketServer, audioFolder, fs, path, port, wss; 4 | 5 | fs = require('fs'); 6 | 7 | path = require('path'); 8 | 9 | Throttle = require('stream-throttle').Throttle; 10 | 11 | WebSocketServer = require('ws').Server; 12 | 13 | port = 8080; 14 | 15 | wss = new WebSocketServer({ 16 | port: port 17 | }); 18 | 19 | audioFolder = './audio'; 20 | 21 | wss.on('connection', function(ws) { 22 | var audioStream, createFileStream, playing; 23 | audioStream = null; 24 | playing = false; 25 | ws.on('close', function() { 26 | if (audioStream != null) { 27 | audioStream.removeAllListeners(); 28 | } 29 | return audioStream = null; 30 | }); 31 | ws.on('message', function(msg) { 32 | var audioPath; 33 | msg = JSON.parse(msg); 34 | if (msg.fileName != null) { 35 | audioPath = path.join(audioFolder, msg.fileName); 36 | fs.stat(audioPath, function(err, stats) { 37 | if (err) { 38 | return ws.send(JSON.stringify({ 39 | error: 'Could not retrieve file.' 40 | })); 41 | } else { 42 | ws.send(JSON.stringify({ 43 | fileSize: stats.size 44 | })); 45 | return createFileStream(audioPath); 46 | } 47 | }); 48 | } else if (msg.resume) { 49 | if (audioStream != null) { 50 | audioStream.resume(); 51 | } 52 | playing = true; 53 | } else if (msg.pause) { 54 | if (audioStream != null) { 55 | audioStream.pause(); 56 | } 57 | playing = false; 58 | } else if (msg.reset) { 59 | if (audioStream != null) { 60 | audioStream.removeAllListeners(); 61 | } 62 | playing = false; 63 | createFileStream(); 64 | } 65 | }); 66 | return createFileStream = function(audioPath) { 67 | audioStream = fs.createReadStream(audioPath).pipe(new Throttle({ 68 | rate: 700 * 1024 69 | })); 70 | if (!playing) { 71 | audioStream.pause(); 72 | } 73 | audioStream.on('data', function(data) { 74 | return ws.send(data, { 75 | binary: true 76 | }); 77 | }); 78 | return audioStream.on('end', function() { 79 | return ws.send(JSON.stringify({ 80 | end: true 81 | })); 82 | }); 83 | }; 84 | }); 85 | 86 | console.log("Serving WebSocket for Aurora.js on port " + port); 87 | 88 | }).call(this); 89 | 90 | //# sourceMappingURL=aurora-ws-server.js.map 91 | -------------------------------------------------------------------------------- /server-examples/node/aurora-ws-server.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "aurora-ws-server.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "aurora-ws-server.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "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" 10 | } -------------------------------------------------------------------------------- /server-examples/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurora-ws-server", 3 | "version": "1.0.0", 4 | "description": "Server implementation for streaming audio over WebSockets to the aurora.js framework.", 5 | "main": "aurora-ws-server.js", 6 | "dependencies": { 7 | "ws": "~0.4.28", 8 | "stream-throttle": "~0.1.3" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "author": "Fabien Brooke", 15 | "license": "MIT" 16 | } 17 | -------------------------------------------------------------------------------- /src/aurora-websocket.coffee: -------------------------------------------------------------------------------- 1 | class AV.WebSocketSource extends AV.EventEmitter 2 | constructor: (@serverUrl, @fileName) -> 3 | if not WebSocket? 4 | return @emit 'error', 'This browser does not have WebSocket support.' 5 | 6 | @socket = new WebSocket(@serverUrl) 7 | 8 | if not @socket.binaryType? 9 | @socket.close() 10 | return @emit 'error', 'This browser does not have binary WebSocket support.' 11 | 12 | @bytesLoaded = 0 13 | 14 | @_setupSocket() 15 | 16 | start: -> 17 | @_send { resume: true } 18 | 19 | pause: -> 20 | @_send { pause: true } 21 | 22 | reset: -> 23 | @_send { reset: true } 24 | 25 | _send: (msg) -> 26 | if not @open 27 | # only the latest message is relevant 28 | # so an array is not used to buffer 29 | @_bufferMessage = msg 30 | else 31 | @socket.send JSON.stringify msg 32 | 33 | _setupSocket: -> 34 | @socket.binaryType = 'arraybuffer' 35 | 36 | @socket.onopen = => 37 | @open = true 38 | if @fileName 39 | @_send { @fileName } 40 | # send any buffered message 41 | if @_bufferMessage 42 | @_send @_bufferMessage 43 | @_bufferMessage = null 44 | 45 | @socket.onmessage = (e) => 46 | data = e.data 47 | if typeof data is 'string' 48 | data = JSON.parse data 49 | if data.fileSize? 50 | @length = data.fileSize 51 | else if data.error? 52 | @emit 'error', data.error 53 | else if data.end 54 | @socket.close() 55 | else 56 | buf = new AV.Buffer(new Uint8Array(data)) 57 | @bytesLoaded += buf.length 58 | if @length 59 | @emit 'progress', @bytesLoaded / @length * 100 60 | @emit 'data', buf 61 | 62 | @socket.onclose = (e) => 63 | @open = false 64 | if e.wasClean 65 | @emit 'end' 66 | else 67 | @emit 'error', 'WebSocket closed uncleanly with code ' + e.code + '.' 68 | 69 | @socket.onerror = (err) => 70 | @emit 'error', err 71 | 72 | 73 | AV.Asset.fromWebSocket = (serverUrl, fileName) -> 74 | source = new AV.WebSocketSource(serverUrl, fileName) 75 | return new AV.Asset(source) 76 | 77 | AV.Player.fromWebSocket = (serverUrl, fileName) -> 78 | asset = AV.Asset.fromWebSocket(serverUrl, fileName) 79 | return new AV.Player(asset) -------------------------------------------------------------------------------- /src/aurora-websocket.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | (function() { 3 | var __hasProp = {}.hasOwnProperty, 4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 5 | 6 | AV.WebSocketSource = (function(_super) { 7 | __extends(WebSocketSource, _super); 8 | 9 | function WebSocketSource(serverUrl, fileName) { 10 | this.serverUrl = serverUrl; 11 | this.fileName = fileName; 12 | if (typeof WebSocket === "undefined" || WebSocket === null) { 13 | return this.emit('error', 'This browser does not have WebSocket support.'); 14 | } 15 | this.socket = new WebSocket(this.serverUrl); 16 | if (this.socket.binaryType == null) { 17 | this.socket.close(); 18 | return this.emit('error', 'This browser does not have binary WebSocket support.'); 19 | } 20 | this.bytesLoaded = 0; 21 | this._setupSocket(); 22 | } 23 | 24 | WebSocketSource.prototype.start = function() { 25 | return this._send({ 26 | resume: true 27 | }); 28 | }; 29 | 30 | WebSocketSource.prototype.pause = function() { 31 | return this._send({ 32 | pause: true 33 | }); 34 | }; 35 | 36 | WebSocketSource.prototype.reset = function() { 37 | return this._send({ 38 | reset: true 39 | }); 40 | }; 41 | 42 | WebSocketSource.prototype._send = function(msg) { 43 | if (!this.open) { 44 | return this._bufferMessage = msg; 45 | } else { 46 | return this.socket.send(JSON.stringify(msg)); 47 | } 48 | }; 49 | 50 | WebSocketSource.prototype._setupSocket = function() { 51 | this.socket.binaryType = 'arraybuffer'; 52 | this.socket.onopen = (function(_this) { 53 | return function() { 54 | _this.open = true; 55 | if (_this.fileName) { 56 | _this._send({ 57 | fileName: _this.fileName 58 | }); 59 | } 60 | if (_this._bufferMessage) { 61 | _this._send(_this._bufferMessage); 62 | return _this._bufferMessage = null; 63 | } 64 | }; 65 | })(this); 66 | this.socket.onmessage = (function(_this) { 67 | return function(e) { 68 | var buf, data; 69 | data = e.data; 70 | if (typeof data === 'string') { 71 | data = JSON.parse(data); 72 | if (data.fileSize != null) { 73 | return _this.length = data.fileSize; 74 | } else if (data.error != null) { 75 | return _this.emit('error', data.error); 76 | } else if (data.end) { 77 | return _this.socket.close(); 78 | } 79 | } else { 80 | buf = new AV.Buffer(new Uint8Array(data)); 81 | _this.bytesLoaded += buf.length; 82 | if (_this.length) { 83 | _this.emit('progress', _this.bytesLoaded / _this.length * 100); 84 | } 85 | return _this.emit('data', buf); 86 | } 87 | }; 88 | })(this); 89 | this.socket.onclose = (function(_this) { 90 | return function(e) { 91 | _this.open = false; 92 | if (e.wasClean) { 93 | return _this.emit('end'); 94 | } else { 95 | return _this.emit('error', 'WebSocket closed uncleanly with code ' + e.code + '.'); 96 | } 97 | }; 98 | })(this); 99 | return this.socket.onerror = (function(_this) { 100 | return function(err) { 101 | return _this.emit('error', err); 102 | }; 103 | })(this); 104 | }; 105 | 106 | return WebSocketSource; 107 | 108 | })(AV.EventEmitter); 109 | 110 | AV.Asset.fromWebSocket = function(serverUrl, fileName) { 111 | var source; 112 | source = new AV.WebSocketSource(serverUrl, fileName); 113 | return new AV.Asset(source); 114 | }; 115 | 116 | AV.Player.fromWebSocket = function(serverUrl, fileName) { 117 | var asset; 118 | asset = AV.Asset.fromWebSocket(serverUrl, fileName); 119 | return new AV.Player(asset); 120 | }; 121 | 122 | }).call(this); 123 | 124 | //# sourceMappingURL=aurora-websocket.js.map 125 | -------------------------------------------------------------------------------- /src/aurora-websocket.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "aurora-websocket.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "aurora-websocket.coffee" 7 | ], 8 | "names": [], 9 | "mappings": "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" 10 | } -------------------------------------------------------------------------------- /src/aurora-websocket.min.js: -------------------------------------------------------------------------------- 1 | (function(){var __hasProp={}.hasOwnProperty,__extends=function(child,parent){for(var key in parent){if(__hasProp.call(parent,key))child[key]=parent[key]}function ctor(){this.constructor=child}ctor.prototype=parent.prototype;child.prototype=new ctor;child.__super__=parent.prototype;return child};AV.WebSocketSource=function(_super){__extends(WebSocketSource,_super);function WebSocketSource(serverUrl,fileName){this.serverUrl=serverUrl;this.fileName=fileName;if(typeof WebSocket==="undefined"||WebSocket===null){return this.emit("error","This browser does not have WebSocket support.")}this.socket=new WebSocket(this.serverUrl);if(this.socket.binaryType==null){this.socket.close();return this.emit("error","This browser does not have binary WebSocket support.")}this.bytesLoaded=0;this._setupSocket()}WebSocketSource.prototype.start=function(){return this._send({resume:true})};WebSocketSource.prototype.pause=function(){return this._send({pause:true})};WebSocketSource.prototype.reset=function(){return this._send({reset:true})};WebSocketSource.prototype._send=function(msg){if(!this.open){return this._bufferMessage=msg}else{return this.socket.send(JSON.stringify(msg))}};WebSocketSource.prototype._setupSocket=function(){this.socket.binaryType="arraybuffer";this.socket.onopen=function(_this){return function(){_this.open=true;if(_this.fileName){_this._send({fileName:_this.fileName})}if(_this._bufferMessage){_this._send(_this._bufferMessage);return _this._bufferMessage=null}}}(this);this.socket.onmessage=function(_this){return function(e){var buf,data;data=e.data;if(typeof data==="string"){data=JSON.parse(data);if(data.fileSize!=null){return _this.length=data.fileSize}else if(data.error!=null){return _this.emit("error",data.error)}else if(data.end){return _this.socket.close()}}else{buf=new AV.Buffer(new Uint8Array(data));_this.bytesLoaded+=buf.length;if(_this.length){_this.emit("progress",_this.bytesLoaded/_this.length*100)}return _this.emit("data",buf)}}}(this);this.socket.onclose=function(_this){return function(e){_this.open=false;if(e.wasClean){return _this.emit("end")}else{return _this.emit("error","WebSocket closed uncleanly with code "+e.code+".")}}}(this);return this.socket.onerror=function(_this){return function(err){return _this.emit("error",err)}}(this)};return WebSocketSource}(AV.EventEmitter);AV.Asset.fromWebSocket=function(serverUrl,fileName){var source;source=new AV.WebSocketSource(serverUrl,fileName);return new AV.Asset(source)};AV.Player.fromWebSocket=function(serverUrl,fileName){var asset;asset=AV.Asset.fromWebSocket(serverUrl,fileName);return new AV.Player(asset)}}).call(this); 2 | //# sourceMappingURL=aurora-websocket.min.js.map -------------------------------------------------------------------------------- /src/aurora-websocket.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"aurora-websocket.min.js","sources":["aurora-websocket.coffee"],"names":[],"mappings":"CAAA,WAAA,GAAA,cAAA,0QAAM,IAAG,gBAAA,SAAA,QACP,UAAA,gBAAA,OAAa,SAAA,iBAAE,UAAY,UAAb,KAAC,UAAA,SAAW,MAAC,SAAA,QACzB,UAAO,aAAA,aAAA,YAAA,KAAP,CACE,MAAO,MAAC,KAAK,QAAS,iDAExB,KAAC,OAAa,GAAA,WAAU,KAAC,UAEzB,IAAO,KAAA,OAAA,YAAA,KAAP,CACE,KAAC,OAAO,OACR,OAAO,MAAC,KAAK,QAAS,wDAExB,KAAC,YAAc,CAEf,MAAC,eAZH,gBAAA,UAcA,MAAO,iBACL,MAAC,OAAQ,OAAQ,OAfnB,iBAAA,UAiBA,MAAO,iBACL,MAAC,OAAQ,MAAO,OAlBlB,iBAAA,UAoBA,MAAO,iBACL,MAAC,OAAQ,MAAO,OArBlB,iBAAA,UAuBA,MAAO,SAAC,KACN,IAAG,KAAK,KAAR,OAGE,MAAC,eAAiB,QAHpB,OAKE,MAAC,OAAO,KAAK,KAAK,UAAU,OA7BhC,iBAAA,UA+BA,aAAc,WACZ,KAAC,OAAO,WAAa,aAErB,MAAC,OAAO,OAAS,SAAA,aAAA,YACf,MAAC,KAAO,IACR,IAAG,MAAC,SAAJ,CACE,MAAC,OAAS,SAAD,MAAC,WAEZ,GAAG,MAAC,eAAJ,CACE,MAAC,MAAM,MAAC,sBACR,OAAC,eAAiB,QAPL,KASjB,MAAC,OAAO,UAAY,SAAA,aAAA,UAAC,GACnB,GAAA,KAAA,IAAA,MAAO,EAAE,IACT,UAAG,QAAe,SAAlB,CACE,KAAO,KAAK,MAAM,KAClB,IAAG,KAAA,UAAA,KAAH,OACE,OAAC,OAAS,KAAK,aACZ,IAAG,KAAA,OAAA,KAAH,OACH,OAAC,KAAK,QAAS,KAAK,WACjB,IAAG,KAAK,IAAR,OACH,OAAC,OAAO,aAPZ,CASE,IAAU,GAAA,IAAG,OAAW,GAAA,YAAW,MACnC,OAAC,aAAe,IAAI,MACpB,IAAG,MAAC,OAAJ,CACE,MAAC,KAAK,WAAY,MAAC,YAAc,MAAC,OAAS,WAC7C,OAAC,KAAK,OAAQ,QAfE,KAiBpB,MAAC,OAAO,QAAU,SAAA,aAAA,UAAC,GACjB,MAAC,KAAO,KACR,IAAG,EAAE,SAAL,OACE,OAAC,KAAK,WADR,OAGE,OAAC,KAAK,QAAS,wCAA0C,EAAE,KAAO,QALpD,YAOlB,MAAC,OAAO,QAAU,SAAA,aAAA,UAAC,WACjB,OAAC,KAAK,QAAS,OADC,+BApEW,GAAG,aAwEpC,IAAG,MAAM,cAAgB,SAAC,UAAW,UACnC,GAAA,OAAA,QAAa,GAAA,IAAG,gBAAgB,UAAW,SAC3C,OAAW,IAAA,IAAG,MAAM,QAEtB,IAAG,OAAO,cAAgB,SAAC,UAAW,UACpC,GAAA,MAAA,OAAQ,GAAG,MAAM,cAAc,UAAW,SAC1C,OAAW,IAAA,IAAG,OAAO"} -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | Tests 2 | ===== 3 | 4 | The tests for Aurora are written using the [QUnit](http://qunitjs.com/) testing framework. They 5 | run in both Node.js and the browser. 6 | 7 | ##Setup 8 | 9 | Running the tests requires running an HTTP server to host both QUnit itself (for the browser), 10 | as well as the test data files as used by both the browser and Node to test HTTP loading. 11 | 12 | To start a simple static HTTP server in the tests directory, run the following command: 13 | 14 | python -m SimpleHTTPServer 15 | 16 | If you already have the test directory on an HTTP server, all you need to do is set the base URL of 17 | the "tests" folder to the `HTTP_BASE` variable in `config.coffee`. 18 | 19 | ## To run in the browser: 20 | 21 | 1. Follow the setup steps above. 22 | 23 | 2. Start HTTP server to host the tests: 24 | 25 | importer test.coffee -p 3031 26 | 27 | You may need to install `importer` using `npm install importer -g` first. 28 | 29 | 3. Run the WebSocket server in the ws-server folder: 30 | 31 | cd ws-server 32 | npm install 33 | node aurora-ws-server.js 34 | 35 | 4. Open `test.html` in your browser, using the HTTP server that you set up above. -------------------------------------------------------------------------------- /tests/aurora-websocket.min.js: -------------------------------------------------------------------------------- 1 | (function(){var e={}.hasOwnProperty,t=function(t,r){function o(){this.constructor=t}for(var n in r)e.call(r,n)&&(t[n]=r[n]);return o.prototype=r.prototype,t.prototype=new o,t.__super__=r.prototype,t};AV.WebSocketSource=function(e){function r(e,t){return this.serverUrl=e,this.fileName=t,"undefined"==typeof WebSocket||null===WebSocket?this.emit("error","This browser does not have WebSocket support."):(this.socket=new WebSocket(this.serverUrl),null==this.socket.binaryType?(this.socket.close(),this.emit("error","This browser does not have binary WebSocket support.")):(this.bytesLoaded=0,this._setupSocket(),void 0))}return t(r,e),r.prototype.start=function(){return this._send(JSON.stringify({resume:!0}))},r.prototype.pause=function(){return this._send(JSON.stringify({pause:!0}))},r.prototype.reset=function(){return this._send(JSON.stringify({reset:!0}))},r.prototype._send=function(e){return this.open?this.socket.send(e):this._bufferMessage=e},r.prototype._setupSocket=function(){var e=this;return this.socket.binaryType="arraybuffer",this.socket.onopen=function(){return e.open=!0,e.fileName&&e.socket.send(JSON.stringify({fileName:e.fileName})),e._bufferMessage?(e.socket.send(e._bufferMessage),e._bufferMessage=null):void 0},this.socket.onmessage=function(t){var r,o;return o=t.data,"string"!=typeof o?(r=new AV.Buffer(new Uint8Array(o)),e.bytesLoaded+=r.length,e.length&&e.emit("progress",100*(e.bytesLoaded/e.length)),e.emit("data",r)):(o=JSON.parse(o),null!=o.fileSize?e.length=o.fileSize:null!=o.error?e.emit("error",o.error):o.end?e.socket.close():void 0)},this.socket.onclose=function(t){return e.open=!1,t.wasClean?e.emit("end"):e.emit("error","WebSocket closed uncleanly with code "+t.code+".")},this.socket.onerror=function(t){return e.emit("error",t)}},r}(AV.EventEmitter),AV.Asset.fromWebSocket=function(e,t){var r;return r=new AV.WebSocketSource(e,t),new AV.Asset(r)},AV.Player.fromWebSocket=function(e,t){var r;return r=AV.Asset.fromWebSocket(e,t),new AV.Player(r)}}).call(this); -------------------------------------------------------------------------------- /tests/config.coffee: -------------------------------------------------------------------------------- 1 | # set this to the base tests directory on an HTTP server 2 | HTTP_BASE = 'http://localhost:8000/' 3 | WS_SERVER = 'ws://localhost:8080/' 4 | -------------------------------------------------------------------------------- /tests/crc32.coffee: -------------------------------------------------------------------------------- 1 | class CRC32 2 | CRC32_TABLE = [ 3 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 4 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 5 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 6 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 7 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 8 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 9 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 10 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 11 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 12 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 13 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 14 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 15 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 16 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 17 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 18 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 19 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 20 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 21 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 22 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 23 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 24 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 25 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 26 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 27 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 28 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 29 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 30 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 31 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 32 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 33 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 34 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 35 | ] 36 | 37 | constructor: -> 38 | @crc = ~0 39 | 40 | update: (buffer) -> 41 | for byte in buffer.data 42 | @crc = CRC32_TABLE[(@crc ^ byte) & 0xff] ^ (@crc >>> 8) 43 | 44 | return 45 | 46 | toHex: -> 47 | return (~@crc >>> 0).toString(16) -------------------------------------------------------------------------------- /tests/data/m4a/base.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabslab/aurora-websocket/c6538b7184ffa2aced29cbe6d4187c09be518398/tests/data/m4a/base.m4a -------------------------------------------------------------------------------- /tests/helpers.coffee: -------------------------------------------------------------------------------- 1 | # setup testing environment 2 | assert = QUnit 3 | test = QUnit.test 4 | module = (name, fn) -> 5 | QUnit.module name 6 | fn() -------------------------------------------------------------------------------- /tests/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.10.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests ol { 115 | margin-top: 0.5em; 116 | padding: 0.5em; 117 | 118 | background-color: #fff; 119 | 120 | border-radius: 5px; 121 | -moz-border-radius: 5px; 122 | -webkit-border-radius: 5px; 123 | } 124 | 125 | #qunit-tests table { 126 | border-collapse: collapse; 127 | margin-top: .2em; 128 | } 129 | 130 | #qunit-tests th { 131 | text-align: right; 132 | vertical-align: top; 133 | padding: 0 .5em 0 0; 134 | } 135 | 136 | #qunit-tests td { 137 | vertical-align: top; 138 | } 139 | 140 | #qunit-tests pre { 141 | margin: 0; 142 | white-space: pre-wrap; 143 | word-wrap: break-word; 144 | } 145 | 146 | #qunit-tests del { 147 | background-color: #e0f2be; 148 | color: #374e0c; 149 | text-decoration: none; 150 | } 151 | 152 | #qunit-tests ins { 153 | background-color: #ffcaca; 154 | color: #500; 155 | text-decoration: none; 156 | } 157 | 158 | /*** Test Counts */ 159 | 160 | #qunit-tests b.counts { color: black; } 161 | #qunit-tests b.passed { color: #5E740B; } 162 | #qunit-tests b.failed { color: #710909; } 163 | 164 | #qunit-tests li li { 165 | padding: 5px; 166 | background-color: #fff; 167 | border-bottom: none; 168 | list-style-position: inside; 169 | } 170 | 171 | /*** Passing Styles */ 172 | 173 | #qunit-tests li li.pass { 174 | color: #3c510c; 175 | background-color: #fff; 176 | border-left: 10px solid #C6E746; 177 | } 178 | 179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 180 | #qunit-tests .pass .test-name { color: #366097; } 181 | 182 | #qunit-tests .pass .test-actual, 183 | #qunit-tests .pass .test-expected { color: #999999; } 184 | 185 | #qunit-banner.qunit-pass { background-color: #C6E746; } 186 | 187 | /*** Failing Styles */ 188 | 189 | #qunit-tests li li.fail { 190 | color: #710909; 191 | background-color: #fff; 192 | border-left: 10px solid #EE5757; 193 | white-space: pre; 194 | } 195 | 196 | #qunit-tests > li:last-child { 197 | border-radius: 0 0 5px 5px; 198 | -moz-border-radius: 0 0 5px 5px; 199 | -webkit-border-bottom-right-radius: 5px; 200 | -webkit-border-bottom-left-radius: 5px; 201 | } 202 | 203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 204 | #qunit-tests .fail .test-name, 205 | #qunit-tests .fail .module-name { color: #000000; } 206 | 207 | #qunit-tests .fail .test-actual { color: #EE5757; } 208 | #qunit-tests .fail .test-expected { color: green; } 209 | 210 | #qunit-banner.qunit-fail { background-color: #EE5757; } 211 | 212 | 213 | /** Result */ 214 | 215 | #qunit-testresult { 216 | padding: 0.5em 0.5em 0.5em 2.5em; 217 | 218 | color: #2b81af; 219 | background-color: #D2E0E6; 220 | 221 | border-bottom: 1px solid white; 222 | } 223 | #qunit-testresult .module-name { 224 | font-weight: bold; 225 | } 226 | 227 | /** Fixture */ 228 | 229 | #qunit-fixture { 230 | position: absolute; 231 | top: -10000px; 232 | left: -10000px; 233 | width: 1000px; 234 | height: 1000px; 235 | } -------------------------------------------------------------------------------- /tests/qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.10.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | (function( window ) { 12 | 13 | var QUnit, 14 | config, 15 | onErrorFnPrev, 16 | testId = 0, 17 | fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 18 | toString = Object.prototype.toString, 19 | hasOwn = Object.prototype.hasOwnProperty, 20 | // Keep a local reference to Date (GH-283) 21 | Date = window.Date, 22 | defined = { 23 | setTimeout: typeof window.setTimeout !== "undefined", 24 | sessionStorage: (function() { 25 | var x = "qunit-test-string"; 26 | try { 27 | sessionStorage.setItem( x, x ); 28 | sessionStorage.removeItem( x ); 29 | return true; 30 | } catch( e ) { 31 | return false; 32 | } 33 | }()) 34 | }; 35 | 36 | function Test( settings ) { 37 | extend( this, settings ); 38 | this.assertions = []; 39 | this.testNumber = ++Test.count; 40 | } 41 | 42 | Test.count = 0; 43 | 44 | Test.prototype = { 45 | init: function() { 46 | var a, b, li, 47 | tests = id( "qunit-tests" ); 48 | 49 | if ( tests ) { 50 | b = document.createElement( "strong" ); 51 | b.innerHTML = this.name; 52 | 53 | // `a` initialized at top of scope 54 | a = document.createElement( "a" ); 55 | a.innerHTML = "Rerun"; 56 | a.href = QUnit.url({ testNumber: this.testNumber }); 57 | 58 | li = document.createElement( "li" ); 59 | li.appendChild( b ); 60 | li.appendChild( a ); 61 | li.className = "running"; 62 | li.id = this.id = "qunit-test-output" + testId++; 63 | 64 | tests.appendChild( li ); 65 | } 66 | }, 67 | setup: function() { 68 | if ( this.module !== config.previousModule ) { 69 | if ( config.previousModule ) { 70 | runLoggingCallbacks( "moduleDone", QUnit, { 71 | name: config.previousModule, 72 | failed: config.moduleStats.bad, 73 | passed: config.moduleStats.all - config.moduleStats.bad, 74 | total: config.moduleStats.all 75 | }); 76 | } 77 | config.previousModule = this.module; 78 | config.moduleStats = { all: 0, bad: 0 }; 79 | runLoggingCallbacks( "moduleStart", QUnit, { 80 | name: this.module 81 | }); 82 | } else if ( config.autorun ) { 83 | runLoggingCallbacks( "moduleStart", QUnit, { 84 | name: this.module 85 | }); 86 | } 87 | 88 | config.current = this; 89 | 90 | this.testEnvironment = extend({ 91 | setup: function() {}, 92 | teardown: function() {} 93 | }, this.moduleTestEnvironment ); 94 | 95 | runLoggingCallbacks( "testStart", QUnit, { 96 | name: this.testName, 97 | module: this.module 98 | }); 99 | 100 | // allow utility functions to access the current test environment 101 | // TODO why?? 102 | QUnit.current_testEnvironment = this.testEnvironment; 103 | 104 | if ( !config.pollution ) { 105 | saveGlobal(); 106 | } 107 | if ( config.notrycatch ) { 108 | this.testEnvironment.setup.call( this.testEnvironment ); 109 | return; 110 | } 111 | try { 112 | this.testEnvironment.setup.call( this.testEnvironment ); 113 | } catch( e ) { 114 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 115 | } 116 | }, 117 | run: function() { 118 | config.current = this; 119 | 120 | var running = id( "qunit-testresult" ); 121 | 122 | if ( running ) { 123 | running.innerHTML = "Running:
" + this.name; 124 | } 125 | 126 | if ( this.async ) { 127 | QUnit.stop(); 128 | } 129 | 130 | if ( config.notrycatch ) { 131 | this.callback.call( this.testEnvironment, QUnit.assert ); 132 | return; 133 | } 134 | 135 | try { 136 | this.callback.call( this.testEnvironment, QUnit.assert ); 137 | } catch( e ) { 138 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); 139 | // else next test will carry the responsibility 140 | saveGlobal(); 141 | 142 | // Restart the tests if they're blocking 143 | if ( config.blocking ) { 144 | QUnit.start(); 145 | } 146 | } 147 | }, 148 | teardown: function() { 149 | config.current = this; 150 | if ( config.notrycatch ) { 151 | this.testEnvironment.teardown.call( this.testEnvironment ); 152 | return; 153 | } else { 154 | try { 155 | this.testEnvironment.teardown.call( this.testEnvironment ); 156 | } catch( e ) { 157 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 158 | } 159 | } 160 | checkPollution(); 161 | }, 162 | finish: function() { 163 | config.current = this; 164 | if ( config.requireExpects && this.expected == null ) { 165 | QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 166 | } else if ( this.expected != null && this.expected != this.assertions.length ) { 167 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 168 | } else if ( this.expected == null && !this.assertions.length ) { 169 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 170 | } 171 | 172 | var assertion, a, b, i, li, ol, 173 | test = this, 174 | good = 0, 175 | bad = 0, 176 | tests = id( "qunit-tests" ); 177 | 178 | config.stats.all += this.assertions.length; 179 | config.moduleStats.all += this.assertions.length; 180 | 181 | if ( tests ) { 182 | ol = document.createElement( "ol" ); 183 | 184 | for ( i = 0; i < this.assertions.length; i++ ) { 185 | assertion = this.assertions[i]; 186 | 187 | li = document.createElement( "li" ); 188 | li.className = assertion.result ? "pass" : "fail"; 189 | li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 190 | ol.appendChild( li ); 191 | 192 | if ( assertion.result ) { 193 | good++; 194 | } else { 195 | bad++; 196 | config.stats.bad++; 197 | config.moduleStats.bad++; 198 | } 199 | } 200 | 201 | // store result when possible 202 | if ( QUnit.config.reorder && defined.sessionStorage ) { 203 | if ( bad ) { 204 | sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 205 | } else { 206 | sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 207 | } 208 | } 209 | 210 | if ( bad === 0 ) { 211 | ol.style.display = "none"; 212 | } 213 | 214 | // `b` initialized at top of scope 215 | b = document.createElement( "strong" ); 216 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 217 | 218 | addEvent(b, "click", function() { 219 | var next = b.nextSibling.nextSibling, 220 | display = next.style.display; 221 | next.style.display = display === "none" ? "block" : "none"; 222 | }); 223 | 224 | addEvent(b, "dblclick", function( e ) { 225 | var target = e && e.target ? e.target : window.event.srcElement; 226 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 227 | target = target.parentNode; 228 | } 229 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 230 | window.location = QUnit.url({ testNumber: test.testNumber }); 231 | } 232 | }); 233 | 234 | // `li` initialized at top of scope 235 | li = id( this.id ); 236 | li.className = bad ? "fail" : "pass"; 237 | li.removeChild( li.firstChild ); 238 | a = li.firstChild; 239 | li.appendChild( b ); 240 | li.appendChild ( a ); 241 | li.appendChild( ol ); 242 | 243 | } else { 244 | for ( i = 0; i < this.assertions.length; i++ ) { 245 | if ( !this.assertions[i].result ) { 246 | bad++; 247 | config.stats.bad++; 248 | config.moduleStats.bad++; 249 | } 250 | } 251 | } 252 | 253 | runLoggingCallbacks( "testDone", QUnit, { 254 | name: this.testName, 255 | module: this.module, 256 | failed: bad, 257 | passed: this.assertions.length - bad, 258 | total: this.assertions.length 259 | }); 260 | 261 | QUnit.reset(); 262 | 263 | config.current = undefined; 264 | }, 265 | 266 | queue: function() { 267 | var bad, 268 | test = this; 269 | 270 | synchronize(function() { 271 | test.init(); 272 | }); 273 | function run() { 274 | // each of these can by async 275 | synchronize(function() { 276 | test.setup(); 277 | }); 278 | synchronize(function() { 279 | test.run(); 280 | }); 281 | synchronize(function() { 282 | test.teardown(); 283 | }); 284 | synchronize(function() { 285 | test.finish(); 286 | }); 287 | } 288 | 289 | // `bad` initialized at top of scope 290 | // defer when previous test run passed, if storage is available 291 | bad = QUnit.config.reorder && defined.sessionStorage && 292 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 293 | 294 | if ( bad ) { 295 | run(); 296 | } else { 297 | synchronize( run, true ); 298 | } 299 | } 300 | }; 301 | 302 | // Root QUnit object. 303 | // `QUnit` initialized at top of scope 304 | QUnit = { 305 | 306 | // call on start of module test to prepend name to all tests 307 | module: function( name, testEnvironment ) { 308 | config.currentModule = name; 309 | config.currentModuleTestEnvironment = testEnvironment; 310 | config.modules[name] = true; 311 | }, 312 | 313 | asyncTest: function( testName, expected, callback ) { 314 | if ( arguments.length === 2 ) { 315 | callback = expected; 316 | expected = null; 317 | } 318 | 319 | QUnit.test( testName, expected, callback, true ); 320 | }, 321 | 322 | test: function( testName, expected, callback, async ) { 323 | var test, 324 | name = "" + escapeInnerText( testName ) + ""; 325 | 326 | if ( arguments.length === 2 ) { 327 | callback = expected; 328 | expected = null; 329 | } 330 | 331 | if ( config.currentModule ) { 332 | name = "" + config.currentModule + ": " + name; 333 | } 334 | 335 | test = new Test({ 336 | name: name, 337 | testName: testName, 338 | expected: expected, 339 | async: async, 340 | callback: callback, 341 | module: config.currentModule, 342 | moduleTestEnvironment: config.currentModuleTestEnvironment, 343 | stack: sourceFromStacktrace( 2 ) 344 | }); 345 | 346 | if ( !validTest( test ) ) { 347 | return; 348 | } 349 | 350 | test.queue(); 351 | }, 352 | 353 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 354 | expect: function( asserts ) { 355 | if (arguments.length === 1) { 356 | config.current.expected = asserts; 357 | } else { 358 | return config.current.expected; 359 | } 360 | }, 361 | 362 | start: function( count ) { 363 | config.semaphore -= count || 1; 364 | // don't start until equal number of stop-calls 365 | if ( config.semaphore > 0 ) { 366 | return; 367 | } 368 | // ignore if start is called more often then stop 369 | if ( config.semaphore < 0 ) { 370 | config.semaphore = 0; 371 | } 372 | // A slight delay, to avoid any current callbacks 373 | if ( defined.setTimeout ) { 374 | window.setTimeout(function() { 375 | if ( config.semaphore > 0 ) { 376 | return; 377 | } 378 | if ( config.timeout ) { 379 | clearTimeout( config.timeout ); 380 | } 381 | 382 | config.blocking = false; 383 | process( true ); 384 | }, 13); 385 | } else { 386 | config.blocking = false; 387 | process( true ); 388 | } 389 | }, 390 | 391 | stop: function( count ) { 392 | config.semaphore += count || 1; 393 | config.blocking = true; 394 | 395 | if ( config.testTimeout && defined.setTimeout ) { 396 | clearTimeout( config.timeout ); 397 | config.timeout = window.setTimeout(function() { 398 | QUnit.ok( false, "Test timed out" ); 399 | config.semaphore = 1; 400 | QUnit.start(); 401 | }, config.testTimeout ); 402 | } 403 | } 404 | }; 405 | 406 | // Asssert helpers 407 | // All of these must call either QUnit.push() or manually do: 408 | // - runLoggingCallbacks( "log", .. ); 409 | // - config.current.assertions.push({ .. }); 410 | QUnit.assert = { 411 | /** 412 | * Asserts rough true-ish result. 413 | * @name ok 414 | * @function 415 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 416 | */ 417 | ok: function( result, msg ) { 418 | if ( !config.current ) { 419 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 420 | } 421 | result = !!result; 422 | 423 | var source, 424 | details = { 425 | module: config.current.module, 426 | name: config.current.testName, 427 | result: result, 428 | message: msg 429 | }; 430 | 431 | msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); 432 | msg = "" + msg + ""; 433 | 434 | if ( !result ) { 435 | source = sourceFromStacktrace( 2 ); 436 | if ( source ) { 437 | details.source = source; 438 | msg += "
Source:
" + escapeInnerText( source ) + "
"; 439 | } 440 | } 441 | runLoggingCallbacks( "log", QUnit, details ); 442 | config.current.assertions.push({ 443 | result: result, 444 | message: msg 445 | }); 446 | }, 447 | 448 | /** 449 | * Assert that the first two arguments are equal, with an optional message. 450 | * Prints out both actual and expected values. 451 | * @name equal 452 | * @function 453 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 454 | */ 455 | equal: function( actual, expected, message ) { 456 | QUnit.push( expected == actual, actual, expected, message ); 457 | }, 458 | 459 | /** 460 | * @name notEqual 461 | * @function 462 | */ 463 | notEqual: function( actual, expected, message ) { 464 | QUnit.push( expected != actual, actual, expected, message ); 465 | }, 466 | 467 | /** 468 | * @name deepEqual 469 | * @function 470 | */ 471 | deepEqual: function( actual, expected, message ) { 472 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 473 | }, 474 | 475 | /** 476 | * @name notDeepEqual 477 | * @function 478 | */ 479 | notDeepEqual: function( actual, expected, message ) { 480 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 481 | }, 482 | 483 | /** 484 | * @name strictEqual 485 | * @function 486 | */ 487 | strictEqual: function( actual, expected, message ) { 488 | QUnit.push( expected === actual, actual, expected, message ); 489 | }, 490 | 491 | /** 492 | * @name notStrictEqual 493 | * @function 494 | */ 495 | notStrictEqual: function( actual, expected, message ) { 496 | QUnit.push( expected !== actual, actual, expected, message ); 497 | }, 498 | 499 | throws: function( block, expected, message ) { 500 | var actual, 501 | ok = false; 502 | 503 | // 'expected' is optional 504 | if ( typeof expected === "string" ) { 505 | message = expected; 506 | expected = null; 507 | } 508 | 509 | config.current.ignoreGlobalErrors = true; 510 | try { 511 | block.call( config.current.testEnvironment ); 512 | } catch (e) { 513 | actual = e; 514 | } 515 | config.current.ignoreGlobalErrors = false; 516 | 517 | if ( actual ) { 518 | // we don't want to validate thrown error 519 | if ( !expected ) { 520 | ok = true; 521 | // expected is a regexp 522 | } else if ( QUnit.objectType( expected ) === "regexp" ) { 523 | ok = expected.test( actual ); 524 | // expected is a constructor 525 | } else if ( actual instanceof expected ) { 526 | ok = true; 527 | // expected is a validation function which returns true is validation passed 528 | } else if ( expected.call( {}, actual ) === true ) { 529 | ok = true; 530 | } 531 | 532 | QUnit.push( ok, actual, null, message ); 533 | } else { 534 | QUnit.pushFailure( message, null, 'No exception was thrown.' ); 535 | } 536 | } 537 | }; 538 | 539 | /** 540 | * @deprecate since 1.8.0 541 | * Kept assertion helpers in root for backwards compatibility 542 | */ 543 | extend( QUnit, QUnit.assert ); 544 | 545 | /** 546 | * @deprecated since 1.9.0 547 | * Kept global "raises()" for backwards compatibility 548 | */ 549 | QUnit.raises = QUnit.assert.throws; 550 | 551 | /** 552 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 553 | * Kept to avoid TypeErrors for undefined methods. 554 | */ 555 | QUnit.equals = function() { 556 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 557 | }; 558 | QUnit.same = function() { 559 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 560 | }; 561 | 562 | // We want access to the constructor's prototype 563 | (function() { 564 | function F() {} 565 | F.prototype = QUnit; 566 | QUnit = new F(); 567 | // Make F QUnit's constructor so that we can add to the prototype later 568 | QUnit.constructor = F; 569 | }()); 570 | 571 | /** 572 | * Config object: Maintain internal state 573 | * Later exposed as QUnit.config 574 | * `config` initialized at top of scope 575 | */ 576 | config = { 577 | // The queue of tests to run 578 | queue: [], 579 | 580 | // block until document ready 581 | blocking: true, 582 | 583 | // when enabled, show only failing tests 584 | // gets persisted through sessionStorage and can be changed in UI via checkbox 585 | hidepassed: false, 586 | 587 | // by default, run previously failed tests first 588 | // very useful in combination with "Hide passed tests" checked 589 | reorder: true, 590 | 591 | // by default, modify document.title when suite is done 592 | altertitle: true, 593 | 594 | // when enabled, all tests must call expect() 595 | requireExpects: false, 596 | 597 | // add checkboxes that are persisted in the query-string 598 | // when enabled, the id is set to `true` as a `QUnit.config` property 599 | urlConfig: [ 600 | { 601 | id: "noglobals", 602 | label: "Check for Globals", 603 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 604 | }, 605 | { 606 | id: "notrycatch", 607 | label: "No try-catch", 608 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 609 | } 610 | ], 611 | 612 | // Set of all modules. 613 | modules: {}, 614 | 615 | // logging callback queues 616 | begin: [], 617 | done: [], 618 | log: [], 619 | testStart: [], 620 | testDone: [], 621 | moduleStart: [], 622 | moduleDone: [] 623 | }; 624 | 625 | // Initialize more QUnit.config and QUnit.urlParams 626 | (function() { 627 | var i, 628 | location = window.location || { search: "", protocol: "file:" }, 629 | params = location.search.slice( 1 ).split( "&" ), 630 | length = params.length, 631 | urlParams = {}, 632 | current; 633 | 634 | if ( params[ 0 ] ) { 635 | for ( i = 0; i < length; i++ ) { 636 | current = params[ i ].split( "=" ); 637 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 638 | // allow just a key to turn on a flag, e.g., test.html?noglobals 639 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 640 | urlParams[ current[ 0 ] ] = current[ 1 ]; 641 | } 642 | } 643 | 644 | QUnit.urlParams = urlParams; 645 | 646 | // String search anywhere in moduleName+testName 647 | config.filter = urlParams.filter; 648 | 649 | // Exact match of the module name 650 | config.module = urlParams.module; 651 | 652 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 653 | 654 | // Figure out if we're running the tests from a server or not 655 | QUnit.isLocal = location.protocol === "file:"; 656 | }()); 657 | 658 | // Export global variables, unless an 'exports' object exists, 659 | // in that case we assume we're in CommonJS (dealt with on the bottom of the script) 660 | if ( typeof exports === "undefined" ) { 661 | extend( window, QUnit ); 662 | 663 | // Expose QUnit object 664 | window.QUnit = QUnit; 665 | } 666 | 667 | // Extend QUnit object, 668 | // these after set here because they should not be exposed as global functions 669 | extend( QUnit, { 670 | config: config, 671 | 672 | // Initialize the configuration options 673 | init: function() { 674 | extend( config, { 675 | stats: { all: 0, bad: 0 }, 676 | moduleStats: { all: 0, bad: 0 }, 677 | started: +new Date(), 678 | updateRate: 1000, 679 | blocking: false, 680 | autostart: true, 681 | autorun: false, 682 | filter: "", 683 | queue: [], 684 | semaphore: 0 685 | }); 686 | 687 | var tests, banner, result, 688 | qunit = id( "qunit" ); 689 | 690 | if ( qunit ) { 691 | qunit.innerHTML = 692 | "

" + escapeInnerText( document.title ) + "

" + 693 | "

" + 694 | "
" + 695 | "

" + 696 | "
    "; 697 | } 698 | 699 | tests = id( "qunit-tests" ); 700 | banner = id( "qunit-banner" ); 701 | result = id( "qunit-testresult" ); 702 | 703 | if ( tests ) { 704 | tests.innerHTML = ""; 705 | } 706 | 707 | if ( banner ) { 708 | banner.className = ""; 709 | } 710 | 711 | if ( result ) { 712 | result.parentNode.removeChild( result ); 713 | } 714 | 715 | if ( tests ) { 716 | result = document.createElement( "p" ); 717 | result.id = "qunit-testresult"; 718 | result.className = "result"; 719 | tests.parentNode.insertBefore( result, tests ); 720 | result.innerHTML = "Running...
     "; 721 | } 722 | }, 723 | 724 | // Resets the test setup. Useful for tests that modify the DOM. 725 | reset: function() { 726 | var fixture = id( "qunit-fixture" ); 727 | if ( fixture ) { 728 | fixture.innerHTML = config.fixture; 729 | } 730 | }, 731 | 732 | // Trigger an event on an element. 733 | // @example triggerEvent( document.body, "click" ); 734 | triggerEvent: function( elem, type, event ) { 735 | if ( document.createEvent ) { 736 | event = document.createEvent( "MouseEvents" ); 737 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 738 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 739 | 740 | elem.dispatchEvent( event ); 741 | } else if ( elem.fireEvent ) { 742 | elem.fireEvent( "on" + type ); 743 | } 744 | }, 745 | 746 | // Safe object type checking 747 | is: function( type, obj ) { 748 | return QUnit.objectType( obj ) == type; 749 | }, 750 | 751 | objectType: function( obj ) { 752 | if ( typeof obj === "undefined" ) { 753 | return "undefined"; 754 | // consider: typeof null === object 755 | } 756 | if ( obj === null ) { 757 | return "null"; 758 | } 759 | 760 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; 761 | 762 | switch ( type ) { 763 | case "Number": 764 | if ( isNaN(obj) ) { 765 | return "nan"; 766 | } 767 | return "number"; 768 | case "String": 769 | case "Boolean": 770 | case "Array": 771 | case "Date": 772 | case "RegExp": 773 | case "Function": 774 | return type.toLowerCase(); 775 | } 776 | if ( typeof obj === "object" ) { 777 | return "object"; 778 | } 779 | return undefined; 780 | }, 781 | 782 | push: function( result, actual, expected, message ) { 783 | if ( !config.current ) { 784 | throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 785 | } 786 | 787 | var output, source, 788 | details = { 789 | module: config.current.module, 790 | name: config.current.testName, 791 | result: result, 792 | message: message, 793 | actual: actual, 794 | expected: expected 795 | }; 796 | 797 | message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); 798 | message = "" + message + ""; 799 | output = message; 800 | 801 | if ( !result ) { 802 | expected = escapeInnerText( QUnit.jsDump.parse(expected) ); 803 | actual = escapeInnerText( QUnit.jsDump.parse(actual) ); 804 | output += ""; 805 | 806 | if ( actual != expected ) { 807 | output += ""; 808 | output += ""; 809 | } 810 | 811 | source = sourceFromStacktrace(); 812 | 813 | if ( source ) { 814 | details.source = source; 815 | output += ""; 816 | } 817 | 818 | output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; 819 | } 820 | 821 | runLoggingCallbacks( "log", QUnit, details ); 822 | 823 | config.current.assertions.push({ 824 | result: !!result, 825 | message: output 826 | }); 827 | }, 828 | 829 | pushFailure: function( message, source, actual ) { 830 | if ( !config.current ) { 831 | throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); 832 | } 833 | 834 | var output, 835 | details = { 836 | module: config.current.module, 837 | name: config.current.testName, 838 | result: false, 839 | message: message 840 | }; 841 | 842 | message = escapeInnerText( message ) || "error"; 843 | message = "" + message + ""; 844 | output = message; 845 | 846 | output += ""; 847 | 848 | if ( actual ) { 849 | output += ""; 850 | } 851 | 852 | if ( source ) { 853 | details.source = source; 854 | output += ""; 855 | } 856 | 857 | output += "
    Result:
    " + escapeInnerText( actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; 858 | 859 | runLoggingCallbacks( "log", QUnit, details ); 860 | 861 | config.current.assertions.push({ 862 | result: false, 863 | message: output 864 | }); 865 | }, 866 | 867 | url: function( params ) { 868 | params = extend( extend( {}, QUnit.urlParams ), params ); 869 | var key, 870 | querystring = "?"; 871 | 872 | for ( key in params ) { 873 | if ( !hasOwn.call( params, key ) ) { 874 | continue; 875 | } 876 | querystring += encodeURIComponent( key ) + "=" + 877 | encodeURIComponent( params[ key ] ) + "&"; 878 | } 879 | return window.location.pathname + querystring.slice( 0, -1 ); 880 | }, 881 | 882 | extend: extend, 883 | id: id, 884 | addEvent: addEvent 885 | // load, equiv, jsDump, diff: Attached later 886 | }); 887 | 888 | /** 889 | * @deprecated: Created for backwards compatibility with test runner that set the hook function 890 | * into QUnit.{hook}, instead of invoking it and passing the hook function. 891 | * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. 892 | * Doing this allows us to tell if the following methods have been overwritten on the actual 893 | * QUnit object. 894 | */ 895 | extend( QUnit.constructor.prototype, { 896 | 897 | // Logging callbacks; all receive a single argument with the listed properties 898 | // run test/logs.html for any related changes 899 | begin: registerLoggingCallback( "begin" ), 900 | 901 | // done: { failed, passed, total, runtime } 902 | done: registerLoggingCallback( "done" ), 903 | 904 | // log: { result, actual, expected, message } 905 | log: registerLoggingCallback( "log" ), 906 | 907 | // testStart: { name } 908 | testStart: registerLoggingCallback( "testStart" ), 909 | 910 | // testDone: { name, failed, passed, total } 911 | testDone: registerLoggingCallback( "testDone" ), 912 | 913 | // moduleStart: { name } 914 | moduleStart: registerLoggingCallback( "moduleStart" ), 915 | 916 | // moduleDone: { name, failed, passed, total } 917 | moduleDone: registerLoggingCallback( "moduleDone" ) 918 | }); 919 | 920 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 921 | config.autorun = true; 922 | } 923 | 924 | QUnit.load = function() { 925 | runLoggingCallbacks( "begin", QUnit, {} ); 926 | 927 | // Initialize the config, saving the execution queue 928 | var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, 929 | numModules = 0, 930 | moduleFilterHtml = "", 931 | urlConfigHtml = "", 932 | oldconfig = extend( {}, config ); 933 | 934 | QUnit.init(); 935 | extend(config, oldconfig); 936 | 937 | config.blocking = false; 938 | 939 | len = config.urlConfig.length; 940 | 941 | for ( i = 0; i < len; i++ ) { 942 | val = config.urlConfig[i]; 943 | if ( typeof val === "string" ) { 944 | val = { 945 | id: val, 946 | label: val, 947 | tooltip: "[no tooltip available]" 948 | }; 949 | } 950 | config[ val.id ] = QUnit.urlParams[ val.id ]; 951 | urlConfigHtml += ""; 952 | } 953 | 954 | moduleFilterHtml += ""; 962 | 963 | // `userAgent` initialized at top of scope 964 | userAgent = id( "qunit-userAgent" ); 965 | if ( userAgent ) { 966 | userAgent.innerHTML = navigator.userAgent; 967 | } 968 | 969 | // `banner` initialized at top of scope 970 | banner = id( "qunit-header" ); 971 | if ( banner ) { 972 | banner.innerHTML = "" + banner.innerHTML + " "; 973 | } 974 | 975 | // `toolbar` initialized at top of scope 976 | toolbar = id( "qunit-testrunner-toolbar" ); 977 | if ( toolbar ) { 978 | // `filter` initialized at top of scope 979 | filter = document.createElement( "input" ); 980 | filter.type = "checkbox"; 981 | filter.id = "qunit-filter-pass"; 982 | 983 | addEvent( filter, "click", function() { 984 | var tmp, 985 | ol = document.getElementById( "qunit-tests" ); 986 | 987 | if ( filter.checked ) { 988 | ol.className = ol.className + " hidepass"; 989 | } else { 990 | tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 991 | ol.className = tmp.replace( / hidepass /, " " ); 992 | } 993 | if ( defined.sessionStorage ) { 994 | if (filter.checked) { 995 | sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); 996 | } else { 997 | sessionStorage.removeItem( "qunit-filter-passed-tests" ); 998 | } 999 | } 1000 | }); 1001 | 1002 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { 1003 | filter.checked = true; 1004 | // `ol` initialized at top of scope 1005 | ol = document.getElementById( "qunit-tests" ); 1006 | ol.className = ol.className + " hidepass"; 1007 | } 1008 | toolbar.appendChild( filter ); 1009 | 1010 | // `label` initialized at top of scope 1011 | label = document.createElement( "label" ); 1012 | label.setAttribute( "for", "qunit-filter-pass" ); 1013 | label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); 1014 | label.innerHTML = "Hide passed tests"; 1015 | toolbar.appendChild( label ); 1016 | 1017 | urlConfigCheckboxes = document.createElement( 'span' ); 1018 | urlConfigCheckboxes.innerHTML = urlConfigHtml; 1019 | addEvent( urlConfigCheckboxes, "change", function( event ) { 1020 | var params = {}; 1021 | params[ event.target.name ] = event.target.checked ? true : undefined; 1022 | window.location = QUnit.url( params ); 1023 | }); 1024 | toolbar.appendChild( urlConfigCheckboxes ); 1025 | 1026 | if (numModules > 1) { 1027 | moduleFilter = document.createElement( 'span' ); 1028 | moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); 1029 | moduleFilter.innerHTML = moduleFilterHtml; 1030 | addEvent( moduleFilter, "change", function() { 1031 | var selectBox = moduleFilter.getElementsByTagName("select")[0], 1032 | selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); 1033 | 1034 | window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); 1035 | }); 1036 | toolbar.appendChild(moduleFilter); 1037 | } 1038 | } 1039 | 1040 | // `main` initialized at top of scope 1041 | main = id( "qunit-fixture" ); 1042 | if ( main ) { 1043 | config.fixture = main.innerHTML; 1044 | } 1045 | 1046 | if ( config.autostart ) { 1047 | QUnit.start(); 1048 | } 1049 | }; 1050 | 1051 | addEvent( window, "load", QUnit.load ); 1052 | 1053 | // `onErrorFnPrev` initialized at top of scope 1054 | // Preserve other handlers 1055 | onErrorFnPrev = window.onerror; 1056 | 1057 | // Cover uncaught exceptions 1058 | // Returning true will surpress the default browser handler, 1059 | // returning false will let it run. 1060 | window.onerror = function ( error, filePath, linerNr ) { 1061 | var ret = false; 1062 | if ( onErrorFnPrev ) { 1063 | ret = onErrorFnPrev( error, filePath, linerNr ); 1064 | } 1065 | 1066 | // Treat return value as window.onerror itself does, 1067 | // Only do our handling if not surpressed. 1068 | if ( ret !== true ) { 1069 | if ( QUnit.config.current ) { 1070 | if ( QUnit.config.current.ignoreGlobalErrors ) { 1071 | return true; 1072 | } 1073 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1074 | } else { 1075 | QUnit.test( "global failure", extend( function() { 1076 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1077 | }, { validTest: validTest } ) ); 1078 | } 1079 | return false; 1080 | } 1081 | 1082 | return ret; 1083 | }; 1084 | 1085 | function done() { 1086 | config.autorun = true; 1087 | 1088 | // Log the last module results 1089 | if ( config.currentModule ) { 1090 | runLoggingCallbacks( "moduleDone", QUnit, { 1091 | name: config.currentModule, 1092 | failed: config.moduleStats.bad, 1093 | passed: config.moduleStats.all - config.moduleStats.bad, 1094 | total: config.moduleStats.all 1095 | }); 1096 | } 1097 | 1098 | var i, key, 1099 | banner = id( "qunit-banner" ), 1100 | tests = id( "qunit-tests" ), 1101 | runtime = +new Date() - config.started, 1102 | passed = config.stats.all - config.stats.bad, 1103 | html = [ 1104 | "Tests completed in ", 1105 | runtime, 1106 | " milliseconds.
    ", 1107 | "", 1108 | passed, 1109 | " tests of ", 1110 | config.stats.all, 1111 | " passed, ", 1112 | config.stats.bad, 1113 | " failed." 1114 | ].join( "" ); 1115 | 1116 | if ( banner ) { 1117 | banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); 1118 | } 1119 | 1120 | if ( tests ) { 1121 | id( "qunit-testresult" ).innerHTML = html; 1122 | } 1123 | 1124 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 1125 | // show ✖ for good, ✔ for bad suite result in title 1126 | // use escape sequences in case file gets loaded with non-utf-8-charset 1127 | document.title = [ 1128 | ( config.stats.bad ? "\u2716" : "\u2714" ), 1129 | document.title.replace( /^[\u2714\u2716] /i, "" ) 1130 | ].join( " " ); 1131 | } 1132 | 1133 | // clear own sessionStorage items if all tests passed 1134 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 1135 | // `key` & `i` initialized at top of scope 1136 | for ( i = 0; i < sessionStorage.length; i++ ) { 1137 | key = sessionStorage.key( i++ ); 1138 | if ( key.indexOf( "qunit-test-" ) === 0 ) { 1139 | sessionStorage.removeItem( key ); 1140 | } 1141 | } 1142 | } 1143 | 1144 | // scroll back to top to show results 1145 | if ( window.scrollTo ) { 1146 | window.scrollTo(0, 0); 1147 | } 1148 | 1149 | runLoggingCallbacks( "done", QUnit, { 1150 | failed: config.stats.bad, 1151 | passed: passed, 1152 | total: config.stats.all, 1153 | runtime: runtime 1154 | }); 1155 | } 1156 | 1157 | /** @return Boolean: true if this test should be ran */ 1158 | function validTest( test ) { 1159 | var include, 1160 | filter = config.filter && config.filter.toLowerCase(), 1161 | module = config.module && config.module.toLowerCase(), 1162 | fullName = (test.module + ": " + test.testName).toLowerCase(); 1163 | 1164 | // Internally-generated tests are always valid 1165 | if ( test.callback && test.callback.validTest === validTest ) { 1166 | delete test.callback.validTest; 1167 | return true; 1168 | } 1169 | 1170 | if ( config.testNumber ) { 1171 | return test.testNumber === config.testNumber; 1172 | } 1173 | 1174 | if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { 1175 | return false; 1176 | } 1177 | 1178 | if ( !filter ) { 1179 | return true; 1180 | } 1181 | 1182 | include = filter.charAt( 0 ) !== "!"; 1183 | if ( !include ) { 1184 | filter = filter.slice( 1 ); 1185 | } 1186 | 1187 | // If the filter matches, we need to honour include 1188 | if ( fullName.indexOf( filter ) !== -1 ) { 1189 | return include; 1190 | } 1191 | 1192 | // Otherwise, do the opposite 1193 | return !include; 1194 | } 1195 | 1196 | // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) 1197 | // Later Safari and IE10 are supposed to support error.stack as well 1198 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 1199 | function extractStacktrace( e, offset ) { 1200 | offset = offset === undefined ? 3 : offset; 1201 | 1202 | var stack, include, i, regex; 1203 | 1204 | if ( e.stacktrace ) { 1205 | // Opera 1206 | return e.stacktrace.split( "\n" )[ offset + 3 ]; 1207 | } else if ( e.stack ) { 1208 | // Firefox, Chrome 1209 | stack = e.stack.split( "\n" ); 1210 | if (/^error$/i.test( stack[0] ) ) { 1211 | stack.shift(); 1212 | } 1213 | if ( fileName ) { 1214 | include = []; 1215 | for ( i = offset; i < stack.length; i++ ) { 1216 | if ( stack[ i ].indexOf( fileName ) != -1 ) { 1217 | break; 1218 | } 1219 | include.push( stack[ i ] ); 1220 | } 1221 | if ( include.length ) { 1222 | return include.join( "\n" ); 1223 | } 1224 | } 1225 | return stack[ offset ]; 1226 | } else if ( e.sourceURL ) { 1227 | // Safari, PhantomJS 1228 | // hopefully one day Safari provides actual stacktraces 1229 | // exclude useless self-reference for generated Error objects 1230 | if ( /qunit.js$/.test( e.sourceURL ) ) { 1231 | return; 1232 | } 1233 | // for actual exceptions, this is useful 1234 | return e.sourceURL + ":" + e.line; 1235 | } 1236 | } 1237 | function sourceFromStacktrace( offset ) { 1238 | try { 1239 | throw new Error(); 1240 | } catch ( e ) { 1241 | return extractStacktrace( e, offset ); 1242 | } 1243 | } 1244 | 1245 | function escapeInnerText( s ) { 1246 | if ( !s ) { 1247 | return ""; 1248 | } 1249 | s = s + ""; 1250 | return s.replace( /[\&<>]/g, function( s ) { 1251 | switch( s ) { 1252 | case "&": return "&"; 1253 | case "<": return "<"; 1254 | case ">": return ">"; 1255 | default: return s; 1256 | } 1257 | }); 1258 | } 1259 | 1260 | function synchronize( callback, last ) { 1261 | config.queue.push( callback ); 1262 | 1263 | if ( config.autorun && !config.blocking ) { 1264 | process( last ); 1265 | } 1266 | } 1267 | 1268 | function process( last ) { 1269 | function next() { 1270 | process( last ); 1271 | } 1272 | var start = new Date().getTime(); 1273 | config.depth = config.depth ? config.depth + 1 : 1; 1274 | 1275 | while ( config.queue.length && !config.blocking ) { 1276 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 1277 | config.queue.shift()(); 1278 | } else { 1279 | window.setTimeout( next, 13 ); 1280 | break; 1281 | } 1282 | } 1283 | config.depth--; 1284 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 1285 | done(); 1286 | } 1287 | } 1288 | 1289 | function saveGlobal() { 1290 | config.pollution = []; 1291 | 1292 | if ( config.noglobals ) { 1293 | for ( var key in window ) { 1294 | // in Opera sometimes DOM element ids show up here, ignore them 1295 | if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { 1296 | continue; 1297 | } 1298 | config.pollution.push( key ); 1299 | } 1300 | } 1301 | } 1302 | 1303 | function checkPollution( name ) { 1304 | var newGlobals, 1305 | deletedGlobals, 1306 | old = config.pollution; 1307 | 1308 | saveGlobal(); 1309 | 1310 | newGlobals = diff( config.pollution, old ); 1311 | if ( newGlobals.length > 0 ) { 1312 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1313 | } 1314 | 1315 | deletedGlobals = diff( old, config.pollution ); 1316 | if ( deletedGlobals.length > 0 ) { 1317 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 1318 | } 1319 | } 1320 | 1321 | // returns a new Array with the elements that are in a but not in b 1322 | function diff( a, b ) { 1323 | var i, j, 1324 | result = a.slice(); 1325 | 1326 | for ( i = 0; i < result.length; i++ ) { 1327 | for ( j = 0; j < b.length; j++ ) { 1328 | if ( result[i] === b[j] ) { 1329 | result.splice( i, 1 ); 1330 | i--; 1331 | break; 1332 | } 1333 | } 1334 | } 1335 | return result; 1336 | } 1337 | 1338 | function extend( a, b ) { 1339 | for ( var prop in b ) { 1340 | if ( b[ prop ] === undefined ) { 1341 | delete a[ prop ]; 1342 | 1343 | // Avoid "Member not found" error in IE8 caused by setting window.constructor 1344 | } else if ( prop !== "constructor" || a !== window ) { 1345 | a[ prop ] = b[ prop ]; 1346 | } 1347 | } 1348 | 1349 | return a; 1350 | } 1351 | 1352 | function addEvent( elem, type, fn ) { 1353 | if ( elem.addEventListener ) { 1354 | elem.addEventListener( type, fn, false ); 1355 | } else if ( elem.attachEvent ) { 1356 | elem.attachEvent( "on" + type, fn ); 1357 | } else { 1358 | fn(); 1359 | } 1360 | } 1361 | 1362 | function id( name ) { 1363 | return !!( typeof document !== "undefined" && document && document.getElementById ) && 1364 | document.getElementById( name ); 1365 | } 1366 | 1367 | function registerLoggingCallback( key ) { 1368 | return function( callback ) { 1369 | config[key].push( callback ); 1370 | }; 1371 | } 1372 | 1373 | // Supports deprecated method of completely overwriting logging callbacks 1374 | function runLoggingCallbacks( key, scope, args ) { 1375 | //debugger; 1376 | var i, callbacks; 1377 | if ( QUnit.hasOwnProperty( key ) ) { 1378 | QUnit[ key ].call(scope, args ); 1379 | } else { 1380 | callbacks = config[ key ]; 1381 | for ( i = 0; i < callbacks.length; i++ ) { 1382 | callbacks[ i ].call( scope, args ); 1383 | } 1384 | } 1385 | } 1386 | 1387 | // Test for equality any JavaScript type. 1388 | // Author: Philippe Rathé 1389 | QUnit.equiv = (function() { 1390 | 1391 | // Call the o related callback with the given arguments. 1392 | function bindCallbacks( o, callbacks, args ) { 1393 | var prop = QUnit.objectType( o ); 1394 | if ( prop ) { 1395 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 1396 | return callbacks[ prop ].apply( callbacks, args ); 1397 | } else { 1398 | return callbacks[ prop ]; // or undefined 1399 | } 1400 | } 1401 | } 1402 | 1403 | // the real equiv function 1404 | var innerEquiv, 1405 | // stack to decide between skip/abort functions 1406 | callers = [], 1407 | // stack to avoiding loops from circular referencing 1408 | parents = [], 1409 | 1410 | getProto = Object.getPrototypeOf || function ( obj ) { 1411 | return obj.__proto__; 1412 | }, 1413 | callbacks = (function () { 1414 | 1415 | // for string, boolean, number and null 1416 | function useStrictEquality( b, a ) { 1417 | if ( b instanceof a.constructor || a instanceof b.constructor ) { 1418 | // to catch short annotaion VS 'new' annotation of a 1419 | // declaration 1420 | // e.g. var i = 1; 1421 | // var j = new Number(1); 1422 | return a == b; 1423 | } else { 1424 | return a === b; 1425 | } 1426 | } 1427 | 1428 | return { 1429 | "string": useStrictEquality, 1430 | "boolean": useStrictEquality, 1431 | "number": useStrictEquality, 1432 | "null": useStrictEquality, 1433 | "undefined": useStrictEquality, 1434 | 1435 | "nan": function( b ) { 1436 | return isNaN( b ); 1437 | }, 1438 | 1439 | "date": function( b, a ) { 1440 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1441 | }, 1442 | 1443 | "regexp": function( b, a ) { 1444 | return QUnit.objectType( b ) === "regexp" && 1445 | // the regex itself 1446 | a.source === b.source && 1447 | // and its modifers 1448 | a.global === b.global && 1449 | // (gmi) ... 1450 | a.ignoreCase === b.ignoreCase && 1451 | a.multiline === b.multiline && 1452 | a.sticky === b.sticky; 1453 | }, 1454 | 1455 | // - skip when the property is a method of an instance (OOP) 1456 | // - abort otherwise, 1457 | // initial === would have catch identical references anyway 1458 | "function": function() { 1459 | var caller = callers[callers.length - 1]; 1460 | return caller !== Object && typeof caller !== "undefined"; 1461 | }, 1462 | 1463 | "array": function( b, a ) { 1464 | var i, j, len, loop; 1465 | 1466 | // b could be an object literal here 1467 | if ( QUnit.objectType( b ) !== "array" ) { 1468 | return false; 1469 | } 1470 | 1471 | len = a.length; 1472 | if ( len !== b.length ) { 1473 | // safe and faster 1474 | return false; 1475 | } 1476 | 1477 | // track reference to avoid circular references 1478 | parents.push( a ); 1479 | for ( i = 0; i < len; i++ ) { 1480 | loop = false; 1481 | for ( j = 0; j < parents.length; j++ ) { 1482 | if ( parents[j] === a[i] ) { 1483 | loop = true;// dont rewalk array 1484 | } 1485 | } 1486 | if ( !loop && !innerEquiv(a[i], b[i]) ) { 1487 | parents.pop(); 1488 | return false; 1489 | } 1490 | } 1491 | parents.pop(); 1492 | return true; 1493 | }, 1494 | 1495 | "object": function( b, a ) { 1496 | var i, j, loop, 1497 | // Default to true 1498 | eq = true, 1499 | aProperties = [], 1500 | bProperties = []; 1501 | 1502 | // comparing constructors is more strict than using 1503 | // instanceof 1504 | if ( a.constructor !== b.constructor ) { 1505 | // Allow objects with no prototype to be equivalent to 1506 | // objects with Object as their constructor. 1507 | if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1508 | ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { 1509 | return false; 1510 | } 1511 | } 1512 | 1513 | // stack constructor before traversing properties 1514 | callers.push( a.constructor ); 1515 | // track reference to avoid circular references 1516 | parents.push( a ); 1517 | 1518 | for ( i in a ) { // be strict: don't ensures hasOwnProperty 1519 | // and go deep 1520 | loop = false; 1521 | for ( j = 0; j < parents.length; j++ ) { 1522 | if ( parents[j] === a[i] ) { 1523 | // don't go down the same path twice 1524 | loop = true; 1525 | } 1526 | } 1527 | aProperties.push(i); // collect a's properties 1528 | 1529 | if (!loop && !innerEquiv( a[i], b[i] ) ) { 1530 | eq = false; 1531 | break; 1532 | } 1533 | } 1534 | 1535 | callers.pop(); // unstack, we are done 1536 | parents.pop(); 1537 | 1538 | for ( i in b ) { 1539 | bProperties.push( i ); // collect b's properties 1540 | } 1541 | 1542 | // Ensures identical properties name 1543 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 1544 | } 1545 | }; 1546 | }()); 1547 | 1548 | innerEquiv = function() { // can take multiple arguments 1549 | var args = [].slice.apply( arguments ); 1550 | if ( args.length < 2 ) { 1551 | return true; // end transition 1552 | } 1553 | 1554 | return (function( a, b ) { 1555 | if ( a === b ) { 1556 | return true; // catch the most you can 1557 | } else if ( a === null || b === null || typeof a === "undefined" || 1558 | typeof b === "undefined" || 1559 | QUnit.objectType(a) !== QUnit.objectType(b) ) { 1560 | return false; // don't lose time with error prone cases 1561 | } else { 1562 | return bindCallbacks(a, callbacks, [ b, a ]); 1563 | } 1564 | 1565 | // apply transition with (1..n) arguments 1566 | }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); 1567 | }; 1568 | 1569 | return innerEquiv; 1570 | }()); 1571 | 1572 | /** 1573 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1574 | * http://flesler.blogspot.com Licensed under BSD 1575 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1576 | * 1577 | * @projectDescription Advanced and extensible data dumping for Javascript. 1578 | * @version 1.0.0 1579 | * @author Ariel Flesler 1580 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1581 | */ 1582 | QUnit.jsDump = (function() { 1583 | function quote( str ) { 1584 | return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; 1585 | } 1586 | function literal( o ) { 1587 | return o + ""; 1588 | } 1589 | function join( pre, arr, post ) { 1590 | var s = jsDump.separator(), 1591 | base = jsDump.indent(), 1592 | inner = jsDump.indent(1); 1593 | if ( arr.join ) { 1594 | arr = arr.join( "," + s + inner ); 1595 | } 1596 | if ( !arr ) { 1597 | return pre + post; 1598 | } 1599 | return [ pre, inner + arr, base + post ].join(s); 1600 | } 1601 | function array( arr, stack ) { 1602 | var i = arr.length, ret = new Array(i); 1603 | this.up(); 1604 | while ( i-- ) { 1605 | ret[i] = this.parse( arr[i] , undefined , stack); 1606 | } 1607 | this.down(); 1608 | return join( "[", ret, "]" ); 1609 | } 1610 | 1611 | var reName = /^function (\w+)/, 1612 | jsDump = { 1613 | parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1614 | stack = stack || [ ]; 1615 | var inStack, res, 1616 | parser = this.parsers[ type || this.typeOf(obj) ]; 1617 | 1618 | type = typeof parser; 1619 | inStack = inArray( obj, stack ); 1620 | 1621 | if ( inStack != -1 ) { 1622 | return "recursion(" + (inStack - stack.length) + ")"; 1623 | } 1624 | //else 1625 | if ( type == "function" ) { 1626 | stack.push( obj ); 1627 | res = parser.call( this, obj, stack ); 1628 | stack.pop(); 1629 | return res; 1630 | } 1631 | // else 1632 | return ( type == "string" ) ? parser : this.parsers.error; 1633 | }, 1634 | typeOf: function( obj ) { 1635 | var type; 1636 | if ( obj === null ) { 1637 | type = "null"; 1638 | } else if ( typeof obj === "undefined" ) { 1639 | type = "undefined"; 1640 | } else if ( QUnit.is( "regexp", obj) ) { 1641 | type = "regexp"; 1642 | } else if ( QUnit.is( "date", obj) ) { 1643 | type = "date"; 1644 | } else if ( QUnit.is( "function", obj) ) { 1645 | type = "function"; 1646 | } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1647 | type = "window"; 1648 | } else if ( obj.nodeType === 9 ) { 1649 | type = "document"; 1650 | } else if ( obj.nodeType ) { 1651 | type = "node"; 1652 | } else if ( 1653 | // native arrays 1654 | toString.call( obj ) === "[object Array]" || 1655 | // NodeList objects 1656 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1657 | ) { 1658 | type = "array"; 1659 | } else { 1660 | type = typeof obj; 1661 | } 1662 | return type; 1663 | }, 1664 | separator: function() { 1665 | return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; 1666 | }, 1667 | indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1668 | if ( !this.multiline ) { 1669 | return ""; 1670 | } 1671 | var chr = this.indentChar; 1672 | if ( this.HTML ) { 1673 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1674 | } 1675 | return new Array( this._depth_ + (extra||0) ).join(chr); 1676 | }, 1677 | up: function( a ) { 1678 | this._depth_ += a || 1; 1679 | }, 1680 | down: function( a ) { 1681 | this._depth_ -= a || 1; 1682 | }, 1683 | setParser: function( name, parser ) { 1684 | this.parsers[name] = parser; 1685 | }, 1686 | // The next 3 are exposed so you can use them 1687 | quote: quote, 1688 | literal: literal, 1689 | join: join, 1690 | // 1691 | _depth_: 1, 1692 | // This is the list of parsers, to modify them, use jsDump.setParser 1693 | parsers: { 1694 | window: "[Window]", 1695 | document: "[Document]", 1696 | error: "[ERROR]", //when no parser is found, shouldn"t happen 1697 | unknown: "[Unknown]", 1698 | "null": "null", 1699 | "undefined": "undefined", 1700 | "function": function( fn ) { 1701 | var ret = "function", 1702 | name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE 1703 | 1704 | if ( name ) { 1705 | ret += " " + name; 1706 | } 1707 | ret += "( "; 1708 | 1709 | ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); 1710 | return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); 1711 | }, 1712 | array: array, 1713 | nodelist: array, 1714 | "arguments": array, 1715 | object: function( map, stack ) { 1716 | var ret = [ ], keys, key, val, i; 1717 | QUnit.jsDump.up(); 1718 | if ( Object.keys ) { 1719 | keys = Object.keys( map ); 1720 | } else { 1721 | keys = []; 1722 | for ( key in map ) { 1723 | keys.push( key ); 1724 | } 1725 | } 1726 | keys.sort(); 1727 | for ( i = 0; i < keys.length; i++ ) { 1728 | key = keys[ i ]; 1729 | val = map[ key ]; 1730 | ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 1731 | } 1732 | QUnit.jsDump.down(); 1733 | return join( "{", ret, "}" ); 1734 | }, 1735 | node: function( node ) { 1736 | var a, val, 1737 | open = QUnit.jsDump.HTML ? "<" : "<", 1738 | close = QUnit.jsDump.HTML ? ">" : ">", 1739 | tag = node.nodeName.toLowerCase(), 1740 | ret = open + tag; 1741 | 1742 | for ( a in QUnit.jsDump.DOMAttrs ) { 1743 | val = node[ QUnit.jsDump.DOMAttrs[a] ]; 1744 | if ( val ) { 1745 | ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); 1746 | } 1747 | } 1748 | return ret + close + open + "/" + tag + close; 1749 | }, 1750 | functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function 1751 | var args, 1752 | l = fn.length; 1753 | 1754 | if ( !l ) { 1755 | return ""; 1756 | } 1757 | 1758 | args = new Array(l); 1759 | while ( l-- ) { 1760 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1761 | } 1762 | return " " + args.join( ", " ) + " "; 1763 | }, 1764 | key: quote, //object calls it internally, the key part of an item in a map 1765 | functionCode: "[code]", //function calls it internally, it's the content of the function 1766 | attribute: quote, //node calls it internally, it's an html attribute value 1767 | string: quote, 1768 | date: quote, 1769 | regexp: literal, //regex 1770 | number: literal, 1771 | "boolean": literal 1772 | }, 1773 | DOMAttrs: { 1774 | //attributes to dump from nodes, name=>realName 1775 | id: "id", 1776 | name: "name", 1777 | "class": "className" 1778 | }, 1779 | HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) 1780 | indentChar: " ",//indentation unit 1781 | multiline: true //if true, items in a collection, are separated by a \n, else just a space. 1782 | }; 1783 | 1784 | return jsDump; 1785 | }()); 1786 | 1787 | // from Sizzle.js 1788 | function getText( elems ) { 1789 | var i, elem, 1790 | ret = ""; 1791 | 1792 | for ( i = 0; elems[i]; i++ ) { 1793 | elem = elems[i]; 1794 | 1795 | // Get the text from text nodes and CDATA nodes 1796 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1797 | ret += elem.nodeValue; 1798 | 1799 | // Traverse everything else, except comment nodes 1800 | } else if ( elem.nodeType !== 8 ) { 1801 | ret += getText( elem.childNodes ); 1802 | } 1803 | } 1804 | 1805 | return ret; 1806 | } 1807 | 1808 | // from jquery.js 1809 | function inArray( elem, array ) { 1810 | if ( array.indexOf ) { 1811 | return array.indexOf( elem ); 1812 | } 1813 | 1814 | for ( var i = 0, length = array.length; i < length; i++ ) { 1815 | if ( array[ i ] === elem ) { 1816 | return i; 1817 | } 1818 | } 1819 | 1820 | return -1; 1821 | } 1822 | 1823 | /* 1824 | * Javascript Diff Algorithm 1825 | * By John Resig (http://ejohn.org/) 1826 | * Modified by Chu Alan "sprite" 1827 | * 1828 | * Released under the MIT license. 1829 | * 1830 | * More Info: 1831 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1832 | * 1833 | * Usage: QUnit.diff(expected, actual) 1834 | * 1835 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" 1836 | */ 1837 | QUnit.diff = (function() { 1838 | function diff( o, n ) { 1839 | var i, 1840 | ns = {}, 1841 | os = {}; 1842 | 1843 | for ( i = 0; i < n.length; i++ ) { 1844 | if ( ns[ n[i] ] == null ) { 1845 | ns[ n[i] ] = { 1846 | rows: [], 1847 | o: null 1848 | }; 1849 | } 1850 | ns[ n[i] ].rows.push( i ); 1851 | } 1852 | 1853 | for ( i = 0; i < o.length; i++ ) { 1854 | if ( os[ o[i] ] == null ) { 1855 | os[ o[i] ] = { 1856 | rows: [], 1857 | n: null 1858 | }; 1859 | } 1860 | os[ o[i] ].rows.push( i ); 1861 | } 1862 | 1863 | for ( i in ns ) { 1864 | if ( !hasOwn.call( ns, i ) ) { 1865 | continue; 1866 | } 1867 | if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { 1868 | n[ ns[i].rows[0] ] = { 1869 | text: n[ ns[i].rows[0] ], 1870 | row: os[i].rows[0] 1871 | }; 1872 | o[ os[i].rows[0] ] = { 1873 | text: o[ os[i].rows[0] ], 1874 | row: ns[i].rows[0] 1875 | }; 1876 | } 1877 | } 1878 | 1879 | for ( i = 0; i < n.length - 1; i++ ) { 1880 | if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 1881 | n[ i + 1 ] == o[ n[i].row + 1 ] ) { 1882 | 1883 | n[ i + 1 ] = { 1884 | text: n[ i + 1 ], 1885 | row: n[i].row + 1 1886 | }; 1887 | o[ n[i].row + 1 ] = { 1888 | text: o[ n[i].row + 1 ], 1889 | row: i + 1 1890 | }; 1891 | } 1892 | } 1893 | 1894 | for ( i = n.length - 1; i > 0; i-- ) { 1895 | if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 1896 | n[ i - 1 ] == o[ n[i].row - 1 ]) { 1897 | 1898 | n[ i - 1 ] = { 1899 | text: n[ i - 1 ], 1900 | row: n[i].row - 1 1901 | }; 1902 | o[ n[i].row - 1 ] = { 1903 | text: o[ n[i].row - 1 ], 1904 | row: i - 1 1905 | }; 1906 | } 1907 | } 1908 | 1909 | return { 1910 | o: o, 1911 | n: n 1912 | }; 1913 | } 1914 | 1915 | return function( o, n ) { 1916 | o = o.replace( /\s+$/, "" ); 1917 | n = n.replace( /\s+$/, "" ); 1918 | 1919 | var i, pre, 1920 | str = "", 1921 | out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), 1922 | oSpace = o.match(/\s+/g), 1923 | nSpace = n.match(/\s+/g); 1924 | 1925 | if ( oSpace == null ) { 1926 | oSpace = [ " " ]; 1927 | } 1928 | else { 1929 | oSpace.push( " " ); 1930 | } 1931 | 1932 | if ( nSpace == null ) { 1933 | nSpace = [ " " ]; 1934 | } 1935 | else { 1936 | nSpace.push( " " ); 1937 | } 1938 | 1939 | if ( out.n.length === 0 ) { 1940 | for ( i = 0; i < out.o.length; i++ ) { 1941 | str += "" + out.o[i] + oSpace[i] + ""; 1942 | } 1943 | } 1944 | else { 1945 | if ( out.n[0].text == null ) { 1946 | for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { 1947 | str += "" + out.o[n] + oSpace[n] + ""; 1948 | } 1949 | } 1950 | 1951 | for ( i = 0; i < out.n.length; i++ ) { 1952 | if (out.n[i].text == null) { 1953 | str += "" + out.n[i] + nSpace[i] + ""; 1954 | } 1955 | else { 1956 | // `pre` initialized at top of scope 1957 | pre = ""; 1958 | 1959 | for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { 1960 | pre += "" + out.o[n] + oSpace[n] + ""; 1961 | } 1962 | str += " " + out.n[i].text + nSpace[i] + pre; 1963 | } 1964 | } 1965 | } 1966 | 1967 | return str; 1968 | }; 1969 | }()); 1970 | 1971 | // for CommonJS enviroments, export everything 1972 | if ( typeof exports !== "undefined" ) { 1973 | extend(exports, QUnit); 1974 | } 1975 | 1976 | // get at whatever the global object is, like window in browsers 1977 | }( (function() {return this;}.call()) )); -------------------------------------------------------------------------------- /tests/sources/websocket.coffee: -------------------------------------------------------------------------------- 1 | #import "../crc32.coffee" 2 | 3 | module 'sources/websocket', -> 4 | asyncTest = assert.asyncTest 5 | 6 | # check that the data returned by the source is correct, using a CRC32 checksum 7 | asyncTest 'data', -> 8 | crc = new CRC32 9 | source = new AV.WebSocketSource WS_SERVER, "base.m4a" 10 | 11 | source.on 'data', (chunk) -> 12 | crc.update chunk 13 | 14 | source.on 'end', -> 15 | assert.equal crc.toHex(), '84d9f967' 16 | assert.start() 17 | 18 | source.start() 19 | 20 | asyncTest 'progress', -> 21 | source = new AV.WebSocketSource WS_SERVER, "base.m4a" 22 | 23 | lastProgress = 0 24 | source.on 'progress', (progress) -> 25 | assert.ok progress > lastProgress, 'progress > lastProgress' 26 | assert.ok progress <= 100, 'progress <= 100' 27 | lastProgress = progress 28 | 29 | source.on 'end', -> 30 | assert.equal lastProgress, 100 31 | assert.start() 32 | 33 | source.start() -------------------------------------------------------------------------------- /tests/test.coffee: -------------------------------------------------------------------------------- 1 | #import "config.coffee" 2 | #import "helpers.coffee" 3 | 4 | #import "sources/websocket.coffee" -------------------------------------------------------------------------------- /tests/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | QUnit 5 | 6 | 7 | 8 |
    9 |
    10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/ws-server/aurora-ws-server.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | WebSocketServer = require('ws').Server 4 | port = 8080 5 | 6 | wss = new WebSocketServer { port } 7 | audioFolder = '../data/m4a' 8 | 9 | wss.on 'connection', (ws) -> 10 | audioStream = null 11 | audioPath = '' 12 | playing = false 13 | 14 | ws.on 'close', -> 15 | audioStream?.removeAllListeners() 16 | 17 | ws.on 'message', (msg) -> 18 | msg = JSON.parse msg 19 | 20 | if msg.fileName? 21 | audioPath = path.join audioFolder, msg.fileName 22 | fs.stat audioPath, (err, stats) -> 23 | if err 24 | ws.send JSON.stringify { error: 'Could not retrieve file.' } 25 | else 26 | ws.send JSON.stringify { fileSize: stats.size } 27 | createFileStream() 28 | 29 | else if msg.resume 30 | audioStream?.resume() 31 | playing = true 32 | 33 | else if msg.pause 34 | audioStream?.pause() 35 | playing = false 36 | 37 | else if msg.reset 38 | audioStream?.removeAllListeners() 39 | playing = false 40 | createFileStream() 41 | 42 | return 43 | 44 | createFileStream = -> 45 | audioStream = fs.createReadStream audioPath 46 | 47 | unless playing 48 | audioStream.pause() 49 | 50 | audioStream.on 'data', (data) -> 51 | ws.send data, { binary: true } 52 | 53 | audioStream.on 'end', -> 54 | ws.send JSON.stringify { end: true } 55 | 56 | console.log "Serving WebSocket for Aurora.js on port #{port}" -------------------------------------------------------------------------------- /tests/ws-server/aurora-ws-server.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var WebSocketServer, audioFolder, fs, path, port, wss; 4 | 5 | fs = require('fs'); 6 | 7 | path = require('path'); 8 | 9 | WebSocketServer = require('ws').Server; 10 | 11 | port = 8080; 12 | 13 | wss = new WebSocketServer({ 14 | port: port 15 | }); 16 | 17 | audioFolder = '../data/m4a'; 18 | 19 | wss.on('connection', function(ws) { 20 | var audioPath, audioStream, createFileStream, playing; 21 | audioStream = null; 22 | audioPath = ''; 23 | playing = false; 24 | ws.on('close', function() { 25 | return audioStream != null ? audioStream.removeAllListeners() : void 0; 26 | }); 27 | ws.on('message', function(msg) { 28 | msg = JSON.parse(msg); 29 | if (msg.fileName != null) { 30 | audioPath = path.join(audioFolder, msg.fileName); 31 | fs.stat(audioPath, function(err, stats) { 32 | if (err) { 33 | return ws.send(JSON.stringify({ 34 | error: 'Could not retrieve file.' 35 | })); 36 | } else { 37 | ws.send(JSON.stringify({ 38 | fileSize: stats.size 39 | })); 40 | return createFileStream(); 41 | } 42 | }); 43 | } else if (msg.resume) { 44 | if (audioStream != null) { 45 | audioStream.resume(); 46 | } 47 | playing = true; 48 | } else if (msg.pause) { 49 | if (audioStream != null) { 50 | audioStream.pause(); 51 | } 52 | playing = false; 53 | } else if (msg.reset) { 54 | if (audioStream != null) { 55 | audioStream.removeAllListeners(); 56 | } 57 | playing = false; 58 | createFileStream(); 59 | } 60 | }); 61 | return createFileStream = function() { 62 | audioStream = fs.createReadStream(audioPath); 63 | if (!playing) { 64 | audioStream.pause(); 65 | } 66 | audioStream.on('data', function(data) { 67 | return ws.send(data, { 68 | binary: true 69 | }); 70 | }); 71 | return audioStream.on('end', function() { 72 | return ws.send(JSON.stringify({ 73 | end: true 74 | })); 75 | }); 76 | }; 77 | }); 78 | 79 | console.log("Serving WebSocket for Aurora.js on port " + port); 80 | 81 | }).call(this); 82 | 83 | /* 84 | //@ sourceMappingURL=aurora-ws-server.map 85 | */ 86 | -------------------------------------------------------------------------------- /tests/ws-server/aurora-ws-server.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "aurora-ws-server.js", 4 | "sourceRoot": "", 5 | "sources": [ 6 | "aurora-ws-server.coffee" 7 | ], 8 | "names": [], 9 | "mappings": ";AAAA;CAAA,KAAA,2CAAA;;CAAA,CAAA,CAAK,CAAA,GAAA;;CAAL,CACA,CAAO,CAAP,EAAO,CAAA;;CADP,CAEA,CAAkB,CAAA,EAFlB,CAEkB,QAAlB;;CAFA,CAGA,CAAO,CAAP;;CAHA,CAKA,CAAA,CAAU,WAAA;CAAgB,CAAE,EAAA;CAL5B,GAKU;;CALV,CAMA,CAAc,QAAd,EANA;;CAAA,CAQA,CAAG,MAAmB,GAAtB;CACE,OAAA,yCAAA;CAAA,EAAc,CAAd,OAAA;CAAA,CAAA,CACY,CAAZ,KAAA;CADA,EAEU,CAAV,CAFA,EAEA;CAFA,CAIE,CAAa,CAAf,GAAA,EAAe;CACA,EAAb,QAAW,OAAX;CADF,IAAe;CAJf,CAOE,CAAe,CAAjB,KAAA;CACE,EAAA,CAAU,CAAJ,CAAN;CAEA,GAAG,EAAH,cAAA;CACE,CAAmC,CAAvB,CAAI,IAAhB,CAAA,EAAY;CAAZ,CACE,CAAiB,CAAnB,CAAmB,GAAnB,CAAA;CACE,EAAA,CAAG,MAAH;CACK,CAAD,EAAF,KAAQ,UAAR;CAAuB,CAAS,GAAP,SAAA,YAAF;CAAvB,aAAQ;MADV,MAAA;CAGE,CAAE,EAAF,KAAQ,GAAR;CAAuB,CAAY,EAAZ,CAAiB,GAAf,MAAA;CAAzB,aAAQ;CACR,eAAA,GAAA;YALe;CAAnB,QAAmB;CAOT,EAAD,CAAH,EATR,EAAA;;CAUe,KAAb,IAAA,CAAW;UAAX;CAAA,EACU,CADV,GACA,CAAA;CAEU,EAAD,CAAH,CAbR,CAAA,EAAA;;CAce,IAAb,KAAA,CAAW;UAAX;CAAA,EACU,EADV,EACA,CAAA;CAEU,EAAD,CAAH,CAjBR,CAAA,EAAA;;CAkBe,SAAb,CAAW,OAAX;UAAA;CAAA,EACU,EADV,EACA,CAAA;CADA,OAEA,QAAA;QAvBa;CAAjB,IAAiB;GA2BE,MAAA,EAAnB,KAAA;CACE,CAAgB,CAAF,GAAd,GAAc,EAAd,KAAc;AAEP,CAAP,GAAA,EAAA,CAAA;CACE,IAAA,GAAA,GAAW;QAHb;CAAA,CAKA,CAAuB,CAAA,EAAvB,GAAwB,EAAb;CACN,CAAD,EAAF,WAAA;CAAc,CAAU,EAAV,EAAE,IAAA;CADK,SACrB;CADF,MAAuB;CAGX,CAAZ,CAAsB,EAAtB,IAAsB,EAAX,EAAX;CACK,CAAD,EAAF,KAAQ,MAAR;CAAuB,CAAO,CAAL,CAAF,MAAE;CAAzB,SAAQ;CADV,MAAsB;CA5CL,IAmCA;CAnCrB,EAAqB;;CARrB,CAuDA,CAAA,CAAA,GAAO,mCAAM;CAvDb" 10 | } -------------------------------------------------------------------------------- /tests/ws-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurora-ws-server", 3 | "version": "1.0.0", 4 | "description": "Server implementation for streaming audio over WebSockets to the aurora.js framework.", 5 | "main": "aurora-ws-server.js", 6 | "dependencies": { 7 | "ws": "~0.4.28" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "Fabien Brooke", 14 | "license": "MIT" 15 | } --------------------------------------------------------------------------------