├── .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 | "" +
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 += "Expected: | " + expected + " |
";
805 |
806 | if ( actual != expected ) {
807 | output += "Result: | " + actual + " |
";
808 | output += "Diff: | " + QUnit.diff( expected, actual ) + " |
";
809 | }
810 |
811 | source = sourceFromStacktrace();
812 |
813 | if ( source ) {
814 | details.source = source;
815 | output += "Source: | " + escapeInnerText( source ) + " |
";
816 | }
817 |
818 | output += "
";
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 += "Result: | " + escapeInnerText( actual ) + " |
";
850 | }
851 |
852 | if ( source ) {
853 | details.source = source;
854 | output += "Source: | " + escapeInnerText( source ) + " |
";
855 | }
856 |
857 | output += "
";
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 | }
--------------------------------------------------------------------------------