diff --git a/lib/parser.js b/lib/parser.js
index c4846e8a..735f0bf1 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -242,7 +242,7 @@
for (var i = 1, length = ''; i < data.length; i++) {
if (data[i] == '\ufffd') {
- ret.push(exports.decodePacket(data.substr(i + 1).substr(0, length)));
+ ret.push(parser.decodePacket(data.substr(i + 1).substr(0, length)));
i += Number(length) + 1;
length = '';
} else {
diff --git a/test/qunit/index.html b/test/qunit/index.html
index cb4421a3..7d6ed36b 100644
--- a/test/qunit/index.html
+++ b/test/qunit/index.html
@@ -9,6 +9,7 @@
+
diff --git a/test/qunit/parser.qunit.js b/test/qunit/parser.qunit.js
new file mode 100644
index 00000000..10767d0e
--- /dev/null
+++ b/test/qunit/parser.qunit.js
@@ -0,0 +1,537 @@
+/*!
+ * socket.io-node
+ * Copyright(c) 2011 LearnBoost
+ * MIT Licensed
+ */
+(function(nativeJSON){
+ module('parser.js');
+
+ // use the correct JSON decoder
+ var JSON = nativeJSON ? nativeJSON : io.JSON;
+
+ test('decoding error packet', function(){
+ deepEqual(
+ io.parser.decodePacket('7:::')
+ , {
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: ''
+ }
+ , 'decoding error packet'
+ );
+ });
+
+ test('decoding error packet with reason', function(){
+ deepEqual(
+ io.parser.decodePacket('7:::0')
+ , {
+ type: 'error'
+ , reason: 'transport not supported'
+ , advice: ''
+ , endpoint: ''
+ }
+ , 'decoding error packet with reason'
+ );
+ });
+
+ test('decoding error packet with reason and advice', function(){
+ deepEqual(
+ io.parser.decodePacket('7:::2+0')
+ , {
+ type: 'error'
+ , reason: 'unauthorized'
+ , advice: 'reconnect'
+ , endpoint: ''
+ }
+ , 'decoding error packet with reason and advice'
+ );
+ });
+
+ test('decoding error packet with endpoint', function(){
+ deepEqual(
+ io.parser.decodePacket('7::/woot')
+ , {
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: '/woot'
+ }
+ , 'decoding error packet with endpoint'
+ );
+ });
+
+ test('decoding ack packet', function(){
+ deepEqual(
+ io.parser.decodePacket('6:::140')
+ , {
+ type: 'ack'
+ , ackId: '140'
+ , endpoint: ''
+ , args: []
+ }
+ , 'decoding ack packet'
+ );
+ });
+
+ test('decoding ack packet with args', function(){
+ deepEqual(
+ io.parser.decodePacket('6:::12+' + JSON.stringify(['woot', 'wa']))
+ , {
+ type: 'ack'
+ , ackId: '12'
+ , endpoint: ''
+ , args: ['woot', 'wa']
+ }
+ , 'decoding ack packet with args'
+ );
+ });
+
+
+ test('decoding ack packet with bad json', function(){
+ deepEqual(
+ io.parser.decodePacket('6:::1+{"++]')
+ , {
+ type: 'ack'
+ , ackId: '1'
+ , endpoint: ''
+ , args: []
+ }
+ , 'decoding ack packet with bad json'
+ );
+ });
+
+ test('decoding json packet', function(){
+ deepEqual(
+ io.parser.decodePacket('4:::"2"')
+ , {
+ type: 'json'
+ , endpoint: ''
+ , data: '2'
+ }
+ , 'decoding json packet'
+ );
+ });
+
+ test('decoding json packet with message id and ack data', function(){
+ deepEqual(
+ io.parser.decodePacket('4:::"2"')
+ , {
+ type: 'json'
+ , endpoint: ''
+ , data: '2'
+ }
+ , 'decoding json packet with message id and ack data'
+ );
+ });
+
+ test('decoding an event packet', function(){
+ deepEqual(
+ io.parser.decodePacket('5:::woot')
+ , {
+ type: 'event'
+ , name: 'woot'
+ , endpoint: ''
+ , args: []
+ }
+ , 'decoding an event packet'
+ );
+ });
+
+ test('decoding an event packet with message id and ack', function(){
+ deepEqual(
+ io.parser.decodePacket('5:1+::tobi')
+ , {
+ type: 'event'
+ , id: "1"
+ , ack: 'data'
+ , endpoint: ''
+ , name: 'tobi'
+ , args: []
+ }
+ , 'decoding an event packet with message id and ack'
+ );
+ });
+
+ test('decoding an event packet with data', function(){
+ deepEqual(
+ io.parser.decodePacket('5:::edwald\ufffd[{"a": "b"},2,"3"]')
+ , {
+ type: 'event'
+ , name: 'edwald'
+ , endpoint: ''
+ , args: [{a: 'b'}, 2, '3']
+ }
+ , 'decoding an event packet with data'
+ );
+ });
+
+ test('decoding a message packet', function(){
+ deepEqual(
+ io.parser.decodePacket('3:::woot')
+ , {
+ type: 'message'
+ , endpoint: ''
+ , data: 'woot'
+ }
+ , 'decoding a message packet'
+ );
+ });
+
+ test('decoding a message packet with unicode characters', function(){
+ deepEqual(
+ io.parser.decodePacket('3:::разъем')
+ , {
+ type: 'message'
+ , endpoint: ''
+ , data: 'разъем'
+ }
+ , 'decoding a message packet with unicode characters'
+ );
+ });
+
+ test('decoding a message packet with whitespace character', function(){
+ var whitespace = String.fromCharCode(56361)
+ , packet = io.parser.decodePacket('3:::' + whitespace);
+
+ equal(packet.data[0].charCodeAt(0), 56361, 'correct white space unicode number');
+ deepEqual(
+ packet
+ , {
+ type: 'message'
+ , endpoint: ''
+ , data: whitespace
+ }
+ , 'decoding a message packet with whitespace character'
+ );
+ });
+
+ test('decoding a message packet with id and endpoint', function(){
+ deepEqual(
+ io.parser.decodePacket('3:5:/tobi')
+ , {
+ type: 'message'
+ , id: "5"
+ , ack: true
+ , endpoint: '/tobi'
+ , data: ''
+ }
+ , 'decoding a message packet with id and endpoint'
+ );
+ });
+
+ test('decoding a heartbeat packet', function(){
+ deepEqual(
+ io.parser.decodePacket('2:::')
+ , {
+ type: 'heartbeat'
+ , endpoint: ''
+ }
+ , 'decoding a heartbeat packet'
+ );
+ });
+
+ test('decoding a connection packet', function(){
+ deepEqual(
+ io.parser.decodePacket('1::/tobi')
+ , {
+ type: 'connect'
+ , endpoint: '/tobi'
+ , qs: ''
+ }
+ , 'decoding a connection packet'
+ );
+ });
+
+ test('decoding a connection packet with query string', function(){
+ deepEqual(
+ io.parser.decodePacket('1::/test:?test=1')
+ , {
+ type: 'connect'
+ , endpoint: '/test'
+ , qs: '?test=1'
+ }
+ , 'decoding a connection packet with query string'
+ );
+ });
+
+ test('decoding a disconnection packet', function(){
+ deepEqual(
+ io.parser.decodePacket('0::/woot')
+ , {
+ type: 'disconnect'
+ , endpoint: '/woot'
+ }
+ , 'decoding a disconnection packet'
+ );
+ });
+
+ test('encoding error packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: ''
+ })
+ , '7::'
+ , 'encoding error packet'
+ );
+ });
+
+ test('encoding error packet with reason', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'error'
+ , reason: 'transport not supported'
+ , advice: ''
+ , endpoint: ''
+ })
+ , '7:::0'
+ , 'encoding error packet with reason'
+ );
+ });
+
+ test('encoding error packet with reason and advice', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'error'
+ , reason: 'unauthorized'
+ , advice: 'reconnect'
+ , endpoint: ''
+ })
+ , '7:::2+0'
+ , 'encoding error packet with reason and advice'
+ );
+ });
+
+ test('encoding error packet with endpoint', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'error'
+ , reason: ''
+ , advice: ''
+ , endpoint: '/woot'
+ })
+ , '7::/woot'
+ , 'encoding error packet with endpoint'
+ );
+ });
+
+ test('encoding ack packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'ack'
+ , ackId: '140'
+ , endpoint: ''
+ , args: []
+ })
+ , '6:::140'
+ , 'encoding ack packet'
+ );
+ });
+
+ test('encoding ack packet with args', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'ack'
+ , ackId: '12'
+ , endpoint: ''
+ , args: ['woot', 'wa']
+ })
+ , '6:::12+' + JSON.stringify(['woot', 'wa'])
+ , 'encoding ack packet with args'
+ );
+ });
+
+ test('encoding json packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'json'
+ , endpoint: ''
+ , data: '2'
+ })
+ , '4:::"2"'
+ , 'encoding json packet'
+ );
+ });
+
+ test('encoding json packet with message id and ack data', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'json'
+ , id: 1
+ , ack: 'data'
+ , endpoint: ''
+ , data: { a: 'b' }
+ })
+ , '4:1+::{"a":"b"}'
+ , 'encoding json packet with message id and ack data'
+ );
+ });
+
+ test('encoding an event packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'event'
+ , name: 'woot'
+ , endpoint: ''
+ , args: []
+ })
+ , '5:::woot'
+ , 'encoding an event packet'
+ );
+ });
+
+ test('encoding an event packet with message id and ack', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'event'
+ , id: 1
+ , ack: 'data'
+ , endpoint: ''
+ , name: 'tobi'
+ , args: []
+ })
+ , '5:1+::tobi'
+ , 'encoding an event packet with message id and ack'
+ );
+ });
+
+ test('encoding an event packet with data', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'event'
+ , name: 'edwald'
+ , endpoint: ''
+ , args: [{a: 'b'}, 2, '3']
+ })
+ , '5:::edwald\ufffd[{"a":"b"},2,"3"]'
+ , 'encoding an event packet with data'
+ );
+ });
+
+ test('encoding a message packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'message'
+ , endpoint: ''
+ , data: 'woot'
+ })
+ , '3:::woot'
+ , 'encoding a message packet'
+ );
+ });
+
+ test('encoding a message packet with unicode characters', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'message'
+ , endpoint: ''
+ , data: 'разъем'
+ })
+ , '3:::разъем'
+ , 'encoding a message packet with unicode characters'
+ );
+ });
+
+ test('encoding a message packet with whitespace character', function(){
+ var whitespace = String.fromCharCode(56361)
+ , packet = io.parser.encodePacket({
+ type: 'message'
+ , endpoint: ''
+ , data: whitespace
+ });
+ equal(packet[4].charCodeAt(0), 56361, 'correct white space unicode number');
+ deepEqual(
+ packet
+ , '3:::' + whitespace
+ , 'encoding a message packet with whitespace character'
+ );
+ });
+
+ test('encoding a message packet with id and endpoint', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'message'
+ , id: 5
+ , ack: true
+ , endpoint: '/tobi'
+ , data: ''
+ })
+ , '3:5:/tobi'
+ , 'encoding a message packet with id and endpoint'
+ );
+ });
+
+ test('encoding a heartbeat packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'heartbeat'
+ , endpoint: ''
+ })
+ , '2::'
+ , 'encoding a heartbeat packet'
+ );
+ });
+
+ test('encoding a connection packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'connect'
+ , endpoint: '/tobi'
+ , qs: ''
+ })
+ , '1::/tobi'
+ , 'encoding a connection packet'
+ );
+ });
+
+ test('encoding a connection packet with query string', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'connect'
+ , endpoint: '/test'
+ , qs: '?test=1'
+ })
+ , '1::/test:?test=1'
+ , 'encoding a connection packet with query string'
+ );
+ });
+
+ test('encoding a disconnection packet', function(){
+ deepEqual(
+ io.parser.encodePacket({
+ type: 'disconnect'
+ , endpoint: '/woot'
+ })
+ , '0::/woot'
+ , 'encoding a disconnection packet'
+ );
+ });
+
+ test('test decoding a payload', function(){
+ deepEqual(
+ io.parser.decodePayload('\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d'
+ + '\ufffd3\ufffd0::')
+ , [
+ { type: 'message', data: '5', endpoint: '' }
+ , { type: 'message', data: '53d', endpoint: '' }
+ , { type: 'disconnect', endpoint: '' }
+ ]
+ , 'test decoding a payload'
+ );
+ });
+
+ test('test encoding a payload', function(){
+ deepEqual(
+ io.parser.encodePayload([
+ io.parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
+ , io.parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
+ ])
+ , '\ufffd5\ufffd3:::5\ufffd7\ufffd3:::53d'
+ , 'test decoding a payload'
+ );
+ });
+
+}(window.JSON))
\ No newline at end of file