hkroger-websocket-rails 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+