hkroger-websocket-rails 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +328 -0
  3. data/Gemfile +27 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +237 -0
  6. data/Rakefile +72 -0
  7. data/bin/thin-socketrails +45 -0
  8. data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
  9. data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
  10. data/lib/assets/javascripts/websocket_rails/event.js.coffee +42 -0
  11. data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
  12. data/lib/assets/javascripts/websocket_rails/main.js +6 -0
  13. data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
  14. data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
  15. data/lib/config.ru +3 -0
  16. data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
  17. data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
  18. data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +63 -0
  19. data/lib/hkroger-websocket-rails.rb +1 -0
  20. data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
  21. data/lib/rails/config/routes.rb +7 -0
  22. data/lib/rails/tasks/websocket_rails.tasks +42 -0
  23. data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
  24. data/lib/spec_helpers/matchers/trigger_matchers.rb +113 -0
  25. data/lib/spec_helpers/spec_helper_event.rb +34 -0
  26. data/lib/websocket-rails.rb +108 -0
  27. data/lib/websocket_rails/base_controller.rb +197 -0
  28. data/lib/websocket_rails/channel.rb +97 -0
  29. data/lib/websocket_rails/channel_manager.rb +55 -0
  30. data/lib/websocket_rails/configuration.rb +169 -0
  31. data/lib/websocket_rails/connection_adapters.rb +195 -0
  32. data/lib/websocket_rails/connection_adapters/http.rb +120 -0
  33. data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -0
  34. data/lib/websocket_rails/connection_manager.rb +119 -0
  35. data/lib/websocket_rails/controller_factory.rb +80 -0
  36. data/lib/websocket_rails/data_store.rb +145 -0
  37. data/lib/websocket_rails/dispatcher.rb +129 -0
  38. data/lib/websocket_rails/engine.rb +26 -0
  39. data/lib/websocket_rails/event.rb +189 -0
  40. data/lib/websocket_rails/event_map.rb +184 -0
  41. data/lib/websocket_rails/event_queue.rb +33 -0
  42. data/lib/websocket_rails/internal_events.rb +37 -0
  43. data/lib/websocket_rails/logging.rb +133 -0
  44. data/lib/websocket_rails/spec_helpers.rb +3 -0
  45. data/lib/websocket_rails/synchronization.rb +182 -0
  46. data/lib/websocket_rails/user_manager.rb +276 -0
  47. data/lib/websocket_rails/version.rb +3 -0
  48. data/spec/dummy/Rakefile +7 -0
  49. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  50. data/spec/dummy/app/controllers/chat_controller.rb +53 -0
  51. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  52. data/spec/dummy/app/models/user.rb +2 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +45 -0
  56. data/spec/dummy/config/boot.rb +10 -0
  57. data/spec/dummy/config/database.yml +22 -0
  58. data/spec/dummy/config/environment.rb +5 -0
  59. data/spec/dummy/config/environments/development.rb +26 -0
  60. data/spec/dummy/config/environments/production.rb +49 -0
  61. data/spec/dummy/config/environments/test.rb +34 -0
  62. data/spec/dummy/config/events.rb +7 -0
  63. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy/config/initializers/inflections.rb +10 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  66. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  67. data/spec/dummy/config/initializers/session_store.rb +8 -0
  68. data/spec/dummy/config/locales/en.yml +5 -0
  69. data/spec/dummy/config/routes.rb +58 -0
  70. data/spec/dummy/db/development.sqlite3 +0 -0
  71. data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
  72. data/spec/dummy/db/schema.rb +23 -0
  73. data/spec/dummy/db/test.sqlite3 +0 -0
  74. data/spec/dummy/log/development.log +17 -0
  75. data/spec/dummy/log/production.log +0 -0
  76. data/spec/dummy/log/server.log +0 -0
  77. data/spec/dummy/public/404.html +26 -0
  78. data/spec/dummy/public/422.html +26 -0
  79. data/spec/dummy/public/500.html +26 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/public/javascripts/application.js +2 -0
  82. data/spec/dummy/public/javascripts/controls.js +965 -0
  83. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  84. data/spec/dummy/public/javascripts/effects.js +1123 -0
  85. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  86. data/spec/dummy/public/javascripts/rails.js +202 -0
  87. data/spec/dummy/script/rails +6 -0
  88. data/spec/integration/connection_manager_spec.rb +135 -0
  89. data/spec/javascripts/support/jasmine.yml +52 -0
  90. data/spec/javascripts/support/jasmine_helper.rb +38 -0
  91. data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
  92. data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
  93. data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
  94. data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
  95. data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
  96. data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +274 -0
  97. data/spec/spec_helper.rb +41 -0
  98. data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
  99. data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +247 -0
  100. data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
  101. data/spec/support/helper_methods.rb +42 -0
  102. data/spec/support/mock_web_socket.rb +41 -0
  103. data/spec/unit/base_controller_spec.rb +74 -0
  104. data/spec/unit/channel_manager_spec.rb +58 -0
  105. data/spec/unit/channel_spec.rb +169 -0
  106. data/spec/unit/connection_adapters/http_spec.rb +88 -0
  107. data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
  108. data/spec/unit/connection_adapters_spec.rb +259 -0
  109. data/spec/unit/connection_manager_spec.rb +148 -0
  110. data/spec/unit/controller_factory_spec.rb +76 -0
  111. data/spec/unit/data_store_spec.rb +106 -0
  112. data/spec/unit/dispatcher_spec.rb +203 -0
  113. data/spec/unit/event_map_spec.rb +120 -0
  114. data/spec/unit/event_queue_spec.rb +36 -0
  115. data/spec/unit/event_spec.rb +181 -0
  116. data/spec/unit/logging_spec.rb +162 -0
  117. data/spec/unit/synchronization_spec.rb +150 -0
  118. data/spec/unit/target_validator_spec.rb +88 -0
  119. data/spec/unit/user_manager_spec.rb +165 -0
  120. metadata +320 -0
@@ -0,0 +1,112 @@
1
+ describe 'WebSocketRails.Channel:', ->
2
+ beforeEach ->
3
+ @dispatcher = new class WebSocketRailsStub
4
+ new_message: -> true
5
+ dispatch: -> true
6
+ trigger_event: (event) -> true
7
+ state: 'connected'
8
+ _conn:
9
+ connection_id: 12345
10
+ @channel = new WebSocketRails.Channel('public', @dispatcher)
11
+ sinon.spy @dispatcher, 'trigger_event'
12
+
13
+ afterEach ->
14
+ @dispatcher.trigger_event.restore()
15
+
16
+ describe '.bind', ->
17
+ it 'should add a function to the callbacks collection', ->
18
+ test_func = ->
19
+ @channel.bind 'event_name', test_func
20
+ expect(@channel._callbacks['event_name'].length).toBe 1
21
+ expect(@channel._callbacks['event_name']).toContain test_func
22
+
23
+ describe '.unbind', ->
24
+ it 'should remove the callbacks of an event', ->
25
+ callback = ->
26
+ @channel.bind 'event', callback
27
+ @channel.unbind 'event'
28
+ expect(@channel._callbacks['event']).toBeUndefined()
29
+
30
+ describe '.trigger', ->
31
+ describe 'before the channel token is set', ->
32
+ it 'queues the events', ->
33
+ @channel.trigger 'someEvent', 'someData'
34
+ queue = @channel._queue
35
+ expect(queue[0].name).toEqual 'someEvent'
36
+ expect(queue[0].data).toEqual 'someData'
37
+
38
+ describe 'when channel token is set', ->
39
+ it 'adds token to event metadata and dispatches event', ->
40
+ @channel._token = 'valid token'
41
+ @channel.trigger 'someEvent', 'someData'
42
+ expect(@dispatcher.trigger_event.calledWith(['someEvent',{token: 'valid token', data: 'someData'}]))
43
+
44
+ describe '.destroy', ->
45
+ it 'should destroy all callbacks', ->
46
+ event_callback = -> true
47
+ @channel.bind('new_message', @event_callback)
48
+
49
+ @channel.destroy()
50
+
51
+ expect(@channel._callbacks).toEqual {}
52
+
53
+ describe 'when this channel\'s connection is still active', ->
54
+ it 'should send unsubscribe event', ->
55
+ @channel.destroy()
56
+ expect(@dispatcher.trigger_event.args[0][0].name).toEqual 'websocket_rails.unsubscribe'
57
+
58
+ describe 'when this channel\'s connection is no more active', ->
59
+ beforeEach ->
60
+ @dispatcher._conn.connection_id++
61
+
62
+ it 'should not send unsubscribe event', ->
63
+ @channel.destroy()
64
+ expect(@dispatcher.trigger_event.notCalled).toEqual true
65
+
66
+ describe 'public channels', ->
67
+ beforeEach ->
68
+ @channel = new WebSocketRails.Channel('forchan', @dispatcher, false)
69
+ @event = @dispatcher.trigger_event.lastCall.args[0]
70
+
71
+ it 'should trigger an event containing the channel name', ->
72
+ expect(@event.data.channel).toEqual 'forchan'
73
+
74
+ it 'should trigger an event containing the correct connection_id', ->
75
+ expect(@event.connection_id).toEqual 12345
76
+
77
+ it 'should initialize an empty callbacks property', ->
78
+ expect(@channel._callbacks).toBeDefined()
79
+ expect(@channel._callbacks).toEqual {}
80
+
81
+ it 'should be public', ->
82
+ expect(@channel.is_private).toBeFalsy
83
+
84
+ describe 'channel tokens', ->
85
+ it 'should set token when event_name is websocket_rails.channel_token', ->
86
+ @channel.dispatch('websocket_rails.channel_token', {token: 'abc123'})
87
+ expect(@channel._token).toEqual 'abc123'
88
+
89
+ it "should refresh channel's connection_id after channel_token has been received", ->
90
+ # this is needed in case we would init the channel connection
91
+ # just before the connection has been established
92
+ @channel.connection_id = null
93
+ @channel.dispatch('websocket_rails.channel_token', {token: 'abc123'})
94
+ expect(@channel.connection_id).toEqual @dispatcher._conn.connection_id
95
+
96
+ it 'should flush the event queue after setting token', ->
97
+ @channel.trigger 'someEvent', 'someData'
98
+ @channel.dispatch('websocket_rails.channel_token', {token: 'abc123'})
99
+ expect(@channel._queue.length).toEqual(0)
100
+
101
+ describe 'private channels', ->
102
+ beforeEach ->
103
+ @channel = new WebSocketRails.Channel('forchan', @dispatcher, true)
104
+ @event = @dispatcher.trigger_event.lastCall.args[0]
105
+
106
+ it 'should trigger a subscribe_private event when created', ->
107
+ expect(@event.name).toEqual 'websocket_rails.subscribe_private'
108
+
109
+ it 'should be private', ->
110
+ expect(@channel.is_private).toBeTruthy
111
+
112
+
@@ -0,0 +1,69 @@
1
+ describe 'WebSocketRails.Event', ->
2
+
3
+ describe 'standard events', ->
4
+ beforeEach ->
5
+ @data = ['event', {data: { message: 'test'} }, 12345]
6
+ @event = new WebSocketRails.Event(@data)
7
+
8
+ it 'should generate an ID', ->
9
+ expect(@event.id).not.toBeNull
10
+
11
+ it 'should have a connection ID', ->
12
+ expect(@event.connection_id).toEqual 12345
13
+
14
+ it 'should assign the correct properties when passed a data array', ->
15
+ expect(@event.name).toEqual 'event'
16
+ expect(@event.data.message).toEqual 'test'
17
+
18
+ describe '.serialize()', ->
19
+ it 'should serialize the event as JSON', ->
20
+ @event.id = 1
21
+ serialized = "[\"event\",{\"id\":1,\"data\":{\"message\":\"test\"}}]"
22
+ expect(@event.serialize()).toEqual serialized
23
+
24
+ describe '.is_channel()', ->
25
+ it 'should be false', ->
26
+ expect(@event.is_channel()).toEqual false
27
+
28
+ describe 'channel events', ->
29
+ beforeEach ->
30
+ @data = ['event',{channel:'channel',data:{message: 'test'}}]
31
+ @event = new WebSocketRails.Event(@data)
32
+
33
+ it 'should assign the channel property', ->
34
+ expect(@event.channel).toEqual 'channel'
35
+ expect(@event.name).toEqual 'event'
36
+ expect(@event.data.message).toEqual 'test'
37
+
38
+ describe '.is_channel()', ->
39
+ it 'should be true', ->
40
+ expect(@event.is_channel()).toEqual true
41
+
42
+ describe '.serialize()', ->
43
+ it 'should serialize the event as JSON', ->
44
+ @event.id = 1
45
+ serialized = "[\"event\",{\"id\":1,\"channel\":\"channel\",\"data\":{\"message\":\"test\"}}]"
46
+ expect(@event.serialize()).toEqual serialized
47
+
48
+ describe '.run_callbacks()', ->
49
+ beforeEach ->
50
+ success_func = (data) ->
51
+ data
52
+ failure_func = (data) ->
53
+ data
54
+ @data = ['event', {data: { message: 'test'} }, 12345]
55
+ @event = new WebSocketRails.Event(@data, success_func, failure_func)
56
+
57
+ describe 'when successful', ->
58
+ it 'should run the success callback when passed true', ->
59
+ expect(@event.run_callbacks(true, 'success')).toEqual 'success'
60
+
61
+ it 'should not run the failure callback', ->
62
+ expect(@event.run_callbacks(true, 'success')).toBeUndefined
63
+
64
+ describe 'when failure', ->
65
+ it 'should run the failure callback when passed true', ->
66
+ expect(@event.run_callbacks(false, 'failure')).toEqual 'failure'
67
+
68
+ it 'should not run the failure callback', ->
69
+ expect(@event.run_callbacks(false, 'failure')).toBeUndefined
@@ -0,0 +1,6 @@
1
+ window.helpers =
2
+ startConnection: (dispatcher, connection_id = 1) ->
3
+ message =
4
+ data:
5
+ connection_id: connection_id
6
+ dispatcher.new_message [['client_connected', message]]
@@ -0,0 +1,158 @@
1
+ describe 'WebsocketRails.WebSocketConnection:', ->
2
+ SAMPLE_EVENT_DATA = ['event','message']
3
+ SAMPLE_EVENT =
4
+ data: JSON.stringify(SAMPLE_EVENT_DATA)
5
+
6
+ beforeEach ->
7
+ @dispatcher =
8
+ new_message: -> true
9
+ dispatch: -> true
10
+ state: 'connected'
11
+ # Have to stub the WebSocket object due to Firefox error during jasmine:ci
12
+ window.WebSocket = class WebSocketStub
13
+ constructor: (@url, @dispatcher) ->
14
+ send: -> true
15
+ close: -> @onclose(null)
16
+ @connection = new WebSocketRails.WebSocketConnection('localhost:3000/websocket', @dispatcher)
17
+ @dispatcher._conn = @connection
18
+
19
+ describe 'constructor', ->
20
+
21
+ it 'should redirect onmessage events\' data from the WebSocket object to this.on_message', ->
22
+ mock_connection = sinon.mock @connection
23
+ mock_connection.expects('on_message').once().withArgs SAMPLE_EVENT_DATA
24
+ @connection._conn.onmessage(SAMPLE_EVENT)
25
+ mock_connection.verify()
26
+
27
+ it 'should redirect onclose events from the WebSocket object to this.on_close', ->
28
+ mock_connection = sinon.mock @connection
29
+ mock_connection.expects('on_close').once().withArgs SAMPLE_EVENT
30
+ @connection._conn.onclose(SAMPLE_EVENT)
31
+ mock_connection.verify()
32
+
33
+ describe 'with ssl', ->
34
+ it 'should not add the ws:// prefix to the URL', ->
35
+ connection = new WebSocketRails.WebSocketConnection('wss://localhost.com')
36
+ expect(connection.url).toEqual 'wss://localhost.com'
37
+
38
+ describe 'without ssl', ->
39
+ it 'should add the ws:// prefix to the URL', ->
40
+ expect(@connection.url).toEqual 'ws://localhost:3000/websocket'
41
+
42
+ describe '.close', ->
43
+ it 'should close the connection', ->
44
+ @connection.close()
45
+ expect(@dispatcher.state).toEqual 'disconnected'
46
+
47
+ describe '.trigger', ->
48
+
49
+ describe 'before the connection has been fully established', ->
50
+ it 'should queue up the events', ->
51
+ @connection.dispatcher.state = 'connecting'
52
+ event = new WebSocketRails.Event ['event','message']
53
+ mock_queue = sinon.mock @connection.message_queue
54
+ mock_queue.expects('push').once().withArgs event
55
+
56
+ describe 'after the connection has been fully established', ->
57
+ it 'should encode the data and send it through the WebSocket object', ->
58
+ @connection.dispatcher.state = 'connected'
59
+ event = new WebSocketRails.Event ['event','message']
60
+ @connection._conn =
61
+ send: -> true
62
+ mock_connection = sinon.mock @connection._conn
63
+ mock_connection.expects('send').once().withArgs event.serialize()
64
+ @connection.trigger event
65
+ mock_connection.verify()
66
+
67
+ describe '.on_message', ->
68
+
69
+ it 'should decode the message and pass it to the dispatcher', ->
70
+ mock_dispatcher = sinon.mock @connection.dispatcher
71
+ mock_dispatcher.expects('new_message').once().withArgs SAMPLE_EVENT_DATA
72
+ @connection.on_message SAMPLE_EVENT_DATA
73
+ mock_dispatcher.verify()
74
+
75
+
76
+
77
+ describe '.on_close', ->
78
+ it 'should dispatch the connection_closed event and pass the original event', ->
79
+ event = new WebSocketRails.Event ['event','message']
80
+ close_event = new WebSocketRails.Event(['connection_closed', event ])
81
+ sinon.spy @dispatcher, 'dispatch'
82
+ @connection.on_close close_event
83
+
84
+ dispatcher = @dispatcher.dispatch
85
+ lastCall = dispatcher.lastCall.args[0]
86
+ expect(dispatcher.calledOnce).toBe(true)
87
+ expect(lastCall.data).toEqual event.data
88
+
89
+ dispatcher.restore()
90
+
91
+ it 'sets the connection state on the dispatcher to disconnected', ->
92
+ close_event = new WebSocketRails.Event(['connection_closed', {} ])
93
+ @connection.on_close close_event
94
+
95
+ expect(@dispatcher.state).toEqual('disconnected')
96
+
97
+ describe '.on_error', ->
98
+ it 'should dispatch the connection_error event and pass the original event', ->
99
+
100
+ event = new WebSocketRails.Event ['event','message']
101
+ error_event = new WebSocketRails.Event(['connection_error', event ])
102
+ sinon.spy @dispatcher, 'dispatch'
103
+ @connection.on_error event
104
+
105
+ dispatcher = @dispatcher.dispatch
106
+ lastCall = dispatcher.lastCall.args[0]
107
+ expect(dispatcher.calledOnce).toBe(true)
108
+ expect(lastCall.data).toEqual event.data
109
+
110
+ dispatcher.restore()
111
+
112
+ it 'sets the connection state on the dispatcher to disconnected', ->
113
+ close_event = new WebSocketRails.Event(['connection_closed', {} ])
114
+ @connection.on_error close_event
115
+
116
+ expect(@dispatcher.state).toEqual('disconnected')
117
+
118
+ describe "it's no longer active connection", ->
119
+ beforeEach ->
120
+ @new_connection = new WebSocketRails.WebSocketConnection('localhost:3000/websocket', @dispatcher)
121
+ @dispatcher._conn = @new_connection
122
+
123
+ it ".on_error should not react to the event response", ->
124
+ mock_dispatcher = sinon.mock @connection.dispatcher
125
+ mock_dispatcher.expects('dispatch').never()
126
+ @connection.on_error SAMPLE_EVENT_DATA
127
+ mock_dispatcher.verify()
128
+
129
+ it ".on_close should not react to the event response", ->
130
+ mock_dispatcher = sinon.mock @connection.dispatcher
131
+ mock_dispatcher.expects('dispatch').never()
132
+ @connection.on_close SAMPLE_EVENT_DATA
133
+ mock_dispatcher.verify()
134
+
135
+ it ".on_message should not react to the event response", ->
136
+ mock_dispatcher = sinon.mock @connection.dispatcher
137
+ mock_dispatcher.expects('new_message').never()
138
+ @connection.on_message SAMPLE_EVENT_DATA
139
+ mock_dispatcher.verify()
140
+
141
+ describe '.flush_queue', ->
142
+ beforeEach ->
143
+ @event = new WebSocketRails.Event ['event','message']
144
+ @connection.message_queue.push @event
145
+ @connection._conn =
146
+ send: -> true
147
+
148
+ it 'should send out all of the messages in the queue', ->
149
+ mock_connection = sinon.mock @connection._conn
150
+ mock_connection.expects('send').once().withArgs @event.serialize()
151
+ @connection.flush_queue()
152
+ mock_connection.verify()
153
+
154
+ it 'should empty the queue after sending', ->
155
+ expect( @connection.message_queue.length ).toEqual 1
156
+ @connection.flush_queue()
157
+ expect( @connection.message_queue.length ).toEqual 0
158
+
@@ -0,0 +1,274 @@
1
+ describe 'WebSocketRails:', ->
2
+ beforeEach ->
3
+ @url = 'localhost:3000/websocket'
4
+ WebSocketRails.WebSocketConnection = class WebSocketConnectionStub extends WebSocketRails.AbstractConnection
5
+ connection_type: 'websocket'
6
+ WebSocketRails.HttpConnection = class HttpConnectionStub extends WebSocketRails.AbstractConnection
7
+ connection_type: 'http'
8
+ @dispatcher = new WebSocketRails @url
9
+
10
+ describe 'constructor', ->
11
+ it 'should start connection automatically', ->
12
+ expect(@dispatcher.state).toEqual 'connecting'
13
+
14
+ describe '.connect', ->
15
+
16
+ it 'should set the new_message method on connection to this.new_message', ->
17
+ expect(@dispatcher._conn.new_message).toEqual @dispatcher.new_message
18
+
19
+ it 'should set the initial state to connecting', ->
20
+ expect(@dispatcher.state).toEqual 'connecting'
21
+
22
+ describe 'when use_websockets is true', ->
23
+ it 'should use the WebSocket Connection', ->
24
+ dispatcher = new WebSocketRails @url, true
25
+ expect(dispatcher._conn.connection_type).toEqual 'websocket'
26
+
27
+ describe 'when use_websockets is false', ->
28
+ it 'should use the Http Connection', ->
29
+ dispatcher = new WebSocketRails @url, false
30
+ expect(dispatcher._conn.connection_type).toEqual 'http'
31
+
32
+ describe 'when the browser does not support WebSockets', ->
33
+ it 'should use the Http Connection', ->
34
+ window.WebSocket = 'undefined'
35
+ dispatcher = new WebSocketRails @url, true
36
+ expect(dispatcher._conn.connection_type).toEqual 'http'
37
+
38
+ describe '.disconnect', ->
39
+ beforeEach ->
40
+ @dispatcher.disconnect()
41
+
42
+ it 'should close the connection', ->
43
+ expect(@dispatcher.state).toEqual 'disconnected'
44
+
45
+ it 'existing connection should be destroyed', ->
46
+ expect(@dispatcher._conn).toBeUndefined()
47
+
48
+ describe '.reconnect', ->
49
+ OLD_CONNECTION_ID = 1
50
+ NEW_CONNECTION_ID = 2
51
+
52
+ it 'should connect, when disconnected', ->
53
+ mock_dispatcher = sinon.mock @dispatcher
54
+ mock_dispatcher.expects('connect').once()
55
+ @dispatcher.disconnect()
56
+ @dispatcher.reconnect()
57
+ mock_dispatcher.verify()
58
+
59
+ it 'should recreate the connection', ->
60
+ helpers.startConnection(@dispatcher, OLD_CONNECTION_ID)
61
+ @dispatcher.reconnect()
62
+ helpers.startConnection(@dispatcher, NEW_CONNECTION_ID)
63
+
64
+ expect(@dispatcher._conn.connection_id).toEqual NEW_CONNECTION_ID
65
+
66
+ it 'should resend all uncompleted events', ->
67
+ event = @dispatcher.trigger('create_post')
68
+
69
+ helpers.startConnection(@dispatcher, OLD_CONNECTION_ID)
70
+ @dispatcher.reconnect()
71
+ helpers.startConnection(@dispatcher, NEW_CONNECTION_ID)
72
+
73
+ expect(@dispatcher.queue[event.id].connection_id).toEqual NEW_CONNECTION_ID
74
+
75
+ it 'should not resend completed events', ->
76
+ event = @dispatcher.trigger('create_post')
77
+ event.run_callbacks(true, {})
78
+
79
+ helpers.startConnection(@dispatcher, OLD_CONNECTION_ID)
80
+ @dispatcher.reconnect()
81
+ helpers.startConnection(@dispatcher, NEW_CONNECTION_ID)
82
+
83
+ expect(@dispatcher.queue[event.id].connection_id).toEqual OLD_CONNECTION_ID
84
+
85
+ it 'should reconnect to all channels', ->
86
+ mock_dispatcher = sinon.mock @dispatcher
87
+ mock_dispatcher.expects('reconnect_channels').once()
88
+ @dispatcher.reconnect()
89
+ mock_dispatcher.verify()
90
+
91
+ describe '.reconnect_channels', ->
92
+ beforeEach ->
93
+ @channel_callback = -> true
94
+ helpers.startConnection(@dispatcher, 1)
95
+ @dispatcher.subscribe('public 4chan')
96
+ @dispatcher.subscribe_private('private 4chan')
97
+ @dispatcher.channels['public 4chan'].bind('new_post', @channel_callback)
98
+
99
+ it 'should recreate existing channels, keeping their private/public type', ->
100
+ @dispatcher.reconnect_channels()
101
+ expect(@dispatcher.channels['public 4chan'].is_private).toEqual false
102
+ expect(@dispatcher.channels['private 4chan'].is_private).toEqual true
103
+
104
+ it 'should move all existing callbacks from old channel objects to new ones', ->
105
+ old_public_channel = @dispatcher.channels['public 4chan']
106
+
107
+ @dispatcher.reconnect_channels()
108
+
109
+ expect(old_public_channel._callbacks).toEqual {}
110
+ expect(@dispatcher.channels['public 4chan']._callbacks).toEqual {new_post: [@channel_callback]}
111
+
112
+ describe '.new_message', ->
113
+
114
+ describe 'when this.state is "connecting"', ->
115
+ beforeEach ->
116
+ @connection_id = 123
117
+
118
+ it 'should call this.connection_established on the "client_connected" event', ->
119
+ mock_dispatcher = sinon.mock @dispatcher
120
+ mock_dispatcher.expects('connection_established').once().withArgs(connection_id: @connection_id)
121
+ helpers.startConnection(@dispatcher, @connection_id)
122
+ mock_dispatcher.verify()
123
+
124
+ it 'should set the state to connected', ->
125
+ helpers.startConnection(@dispatcher, @connection_id)
126
+ expect(@dispatcher.state).toEqual 'connected'
127
+
128
+ it 'should flush any messages queued before the connection was established', ->
129
+ mock_con = sinon.mock @dispatcher._conn
130
+ mock_con.expects('flush_queue').once()
131
+ helpers.startConnection(@dispatcher, @connection_id)
132
+ mock_con.verify()
133
+
134
+ it 'should set the correct connection_id', ->
135
+ helpers.startConnection(@dispatcher, @connection_id)
136
+ expect(@dispatcher._conn.connection_id).toEqual 123
137
+
138
+ it 'should call the user defined on_open callback', ->
139
+ spy = sinon.spy()
140
+ @dispatcher.on_open = spy
141
+ helpers.startConnection(@dispatcher, @connection_id)
142
+ expect(spy.calledOnce).toEqual true
143
+
144
+ describe 'after the connection has been established', ->
145
+ beforeEach ->
146
+ @dispatcher.state = 'connected'
147
+ @attributes =
148
+ data: 'message'
149
+ channel: 'channel'
150
+
151
+ it 'should dispatch channel messages', ->
152
+ data = [['event',@attributes]]
153
+ mock_dispatcher = sinon.mock @dispatcher
154
+ mock_dispatcher.expects('dispatch_channel').once()
155
+ @dispatcher.new_message data
156
+ mock_dispatcher.verify()
157
+
158
+ it 'should dispatch standard events', ->
159
+ data = [['event','message']]
160
+ mock_dispatcher = sinon.mock @dispatcher
161
+ mock_dispatcher.expects('dispatch').once()
162
+ @dispatcher.new_message data
163
+ mock_dispatcher.verify()
164
+
165
+ describe 'result events', ->
166
+ beforeEach ->
167
+ @attributes['success'] = true
168
+ @attributes['id'] = 1
169
+ @event = { run_callbacks: (data) -> }
170
+ @event_mock = sinon.mock @event
171
+ @dispatcher.queue[1] = @event
172
+ @event_data = [['event',@attributes]]
173
+
174
+ it 'should run callbacks for result events', ->
175
+ @event_mock.expects('run_callbacks').once()
176
+ @dispatcher.new_message @event_data
177
+ @event_mock.verify()
178
+
179
+ it 'should remove the event from the queue', ->
180
+ @dispatcher.new_message @event_data
181
+ expect(@dispatcher.queue[1]).toBeUndefined()
182
+
183
+
184
+ describe '.bind', ->
185
+
186
+ it 'should store the callback on the correct event', ->
187
+ callback = ->
188
+ @dispatcher.bind 'event', callback
189
+ expect(@dispatcher.callbacks['event']).toContain callback
190
+
191
+ describe '.unbind', ->
192
+
193
+ it 'should delete the callback on the correct event', ->
194
+ callback = ->
195
+ @dispatcher.bind 'event', callback
196
+ @dispatcher.unbind 'event'
197
+ expect(@dispatcher.callbacks['event']).toBeUndefined()
198
+
199
+ describe '.dispatch', ->
200
+
201
+ it 'should execute the callback for the correct event', ->
202
+ callback = sinon.spy()
203
+ event = new WebSocketRails.Event(['event',{data: 'message'}])
204
+ @dispatcher.bind 'event', callback
205
+ @dispatcher.dispatch event
206
+ expect(callback.calledWith('message')).toEqual true
207
+
208
+ describe 'triggering events with', ->
209
+ beforeEach ->
210
+ @dispatcher._conn =
211
+ connection_id: 123
212
+ trigger: ->
213
+
214
+ describe '.trigger', ->
215
+ it 'should add the event to the queue', ->
216
+ event = @dispatcher.trigger 'event', 'message'
217
+ expect(@dispatcher.queue[event.id]).toEqual event
218
+
219
+ it 'should delegate to the connection object', ->
220
+ conn_trigger = sinon.spy @dispatcher._conn, 'trigger'
221
+ @dispatcher.trigger 'event', 'message'
222
+ expect(conn_trigger.called).toEqual true
223
+
224
+ it "should not delegate to the connection object, if it's not available", ->
225
+ @dispatcher._conn = null
226
+ @dispatcher.trigger 'event', 'message'
227
+
228
+ describe '.connection_stale', ->
229
+ describe 'when state is connected', ->
230
+ it 'should return false', ->
231
+ @dispatcher.state = 'connected'
232
+ expect(@dispatcher.connection_stale()).toEqual false
233
+
234
+ describe 'when state is disconnected', ->
235
+ it 'should return true', ->
236
+ @dispatcher.state = 'disconnected'
237
+ expect(@dispatcher.connection_stale()).toEqual true
238
+
239
+ describe 'working with channels', ->
240
+ beforeEach ->
241
+ WebSocketRails.Channel = (@name,@dispatcher,@is_private) ->
242
+
243
+ describe '.subscribe', ->
244
+ describe 'for new channels', ->
245
+ it 'should create and store a new Channel object', ->
246
+ channel = @dispatcher.subscribe 'test_channel'
247
+ expect(channel.name).toEqual 'test_channel'
248
+
249
+ describe 'for existing channels', ->
250
+ it 'should return the same Channel object', ->
251
+ channel = @dispatcher.subscribe 'test_channel'
252
+ expect(@dispatcher.subscribe('test_channel')).toEqual channel
253
+
254
+ describe '.subscribe_private', ->
255
+ it 'should create private channels', ->
256
+ private_channel = @dispatcher.subscribe_private 'private_something'
257
+ expect(private_channel.is_private).toBe true
258
+
259
+ describe '.unsubscribe', ->
260
+ describe 'for existing channels', ->
261
+ it 'should remove the Channel object', ->
262
+ @dispatcher.unsubscribe 'test_channel'
263
+ expect(@dispatcher.channels['test_channel']).toBeUndefined
264
+
265
+ describe '.dispatch_channel', ->
266
+
267
+ it 'should delegate to the Channel object', ->
268
+ channel = @dispatcher.subscribe 'test'
269
+ channel.dispatch = ->
270
+ spy = sinon.spy channel, 'dispatch'
271
+ event = new WebSocketRails.Event(['event',{channel: 'test', data: 'awesome'}])
272
+ @dispatcher.dispatch_channel event
273
+ expect(spy.calledWith('event', 'awesome')).toEqual true
274
+