rails_chat 0.0.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.
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.md +155 -0
- data/Rakefile +12 -0
- data/app/assets/images/emoticons.png +0 -0
- data/app/assets/javascripts/emoticons.js +127 -0
- data/app/assets/javascripts/emoticons_defination.js +365 -0
- data/app/assets/javascripts/rails_chat.js +72 -0
- data/app/assets/stylesheets/emoticons.css +115 -0
- data/lib/generators/rails_chat/install_generator.rb +27 -0
- data/lib/generators/rails_chat/templates/rails_chat.ru +10 -0
- data/lib/generators/rails_chat/templates/rails_chat.yml +10 -0
- data/lib/rails_chat/engine.rb +16 -0
- data/lib/rails_chat/faye_extension.rb +39 -0
- data/lib/rails_chat/view_helpers.rb +22 -0
- data/lib/rails_chat.rb +78 -0
- data/spec/fixtures/rails_chat.yml +9 -0
- data/spec/javascripts/rails_chat_spec.js +108 -0
- data/spec/javascripts/support/jasmine.yml +73 -0
- data/spec/javascripts/support/jasmine_config.rb +23 -0
- data/spec/javascripts/support/jasmine_runner.rb +32 -0
- data/spec/rails_chat/faye_extension_spec.rb +67 -0
- data/spec/rails_chat_spec.rb +141 -0
- data/spec/spec_helper.rb +8 -0
- metadata +154 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
function buildRailsChat(doc) {
|
2
|
+
var self = {
|
3
|
+
connecting: false,
|
4
|
+
fayeClient: null,
|
5
|
+
fayeCallbacks: [],
|
6
|
+
subscriptions: {},
|
7
|
+
subscriptionCallbacks: {},
|
8
|
+
|
9
|
+
faye: function(callback) {
|
10
|
+
if (self.fayeClient) {
|
11
|
+
callback(self.fayeClient);
|
12
|
+
} else {
|
13
|
+
self.fayeCallbacks.push(callback);
|
14
|
+
if (self.subscriptions.server && !self.connecting) {
|
15
|
+
self.connecting = true;
|
16
|
+
var script = doc.createElement("script");
|
17
|
+
script.type = "text/javascript";
|
18
|
+
script.src = self.subscriptions.server + ".js";
|
19
|
+
script.onload = self.connectToFaye;
|
20
|
+
doc.documentElement.appendChild(script);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
},
|
24
|
+
|
25
|
+
connectToFaye: function() {
|
26
|
+
self.fayeClient = new Faye.Client(self.subscriptions.server);
|
27
|
+
self.fayeClient.addExtension(self.fayeExtension);
|
28
|
+
for (var i=0; i < self.fayeCallbacks.length; i++) {
|
29
|
+
self.fayeCallbacks[i](self.fayeClient);
|
30
|
+
};
|
31
|
+
},
|
32
|
+
|
33
|
+
fayeExtension: {
|
34
|
+
outgoing: function(message, callback) {
|
35
|
+
if (message.channel == "/meta/subscribe") {
|
36
|
+
// Attach the signature and timestamp to subscription messages
|
37
|
+
var subscription = self.subscriptions[message.subscription];
|
38
|
+
if (!message.ext) message.ext = {};
|
39
|
+
message.ext.rails_chat_signature = subscription.signature;
|
40
|
+
message.ext.rails_chat_timestamp = subscription.timestamp;
|
41
|
+
}
|
42
|
+
callback(message);
|
43
|
+
}
|
44
|
+
},
|
45
|
+
|
46
|
+
sign: function(options) {
|
47
|
+
if (!self.subscriptions.server) {
|
48
|
+
self.subscriptions.server = options.server;
|
49
|
+
}
|
50
|
+
self.subscriptions[options.channel] = options;
|
51
|
+
self.faye(function(faye) {
|
52
|
+
faye.subscribe(options.channel, self.handleResponse);
|
53
|
+
});
|
54
|
+
},
|
55
|
+
|
56
|
+
handleResponse: function(message) {
|
57
|
+
if (message.eval) {
|
58
|
+
eval(message.eval);
|
59
|
+
}
|
60
|
+
if (callback = self.subscriptionCallbacks[message.channel]) {
|
61
|
+
callback(message.data, message.channel);
|
62
|
+
}
|
63
|
+
},
|
64
|
+
|
65
|
+
subscribe: function(channel, callback) {
|
66
|
+
self.subscriptionCallbacks[channel] = callback;
|
67
|
+
}
|
68
|
+
};
|
69
|
+
return self;
|
70
|
+
}
|
71
|
+
|
72
|
+
var RailsChat = buildRailsChat(document);
|
@@ -0,0 +1,115 @@
|
|
1
|
+
.emoticon {
|
2
|
+
display: inline-block;
|
3
|
+
width: 24px;
|
4
|
+
height: 24px;
|
5
|
+
text-indent: -10000px;
|
6
|
+
overflow: hidden;
|
7
|
+
background: url('emoticons.png') no-repeat;
|
8
|
+
text-align: left;
|
9
|
+
position: relative;
|
10
|
+
top:30%;
|
11
|
+
}
|
12
|
+
.emoticon > * {
|
13
|
+
display: none;
|
14
|
+
}
|
15
|
+
.emoticon-smile { background-position: 0 0; }
|
16
|
+
.emoticon-sad-smile { background-position: 0 -29px; }
|
17
|
+
.emoticon-big-smile { background-position: 0 -58px; }
|
18
|
+
.emoticon-cool { background-position: 0 -87px; }
|
19
|
+
.emoticon-surprised { background-position: 0 -116px; }
|
20
|
+
.emoticon-wink { background-position: 0 -145px; }
|
21
|
+
.emoticon-crying { background-position: 0 -174px; }
|
22
|
+
.emoticon-sweating { background-position: 0 -203px; }
|
23
|
+
.emoticon-speechless { background-position: 0 -232px; }
|
24
|
+
.emoticon-kiss { background-position: 0 -261px; }
|
25
|
+
.emoticon-tongue-out { background-position: 0 -290px; }
|
26
|
+
.emoticon-blush { background-position: 0 -319px; }
|
27
|
+
.emoticon-wondering { background-position: 0 -348px; }
|
28
|
+
.emoticon-sleepy { background-position: 0 -377px; }
|
29
|
+
.emoticon-dull { background-position: 0 -406px; }
|
30
|
+
.emoticon-in-love { background-position: 0 -435px; }
|
31
|
+
.emoticon-evil-grin { background-position: 0 -464px; }
|
32
|
+
.emoticon-talking { background-position: 0 -493px; }
|
33
|
+
.emoticon-yawn { background-position: 0 -522px; }
|
34
|
+
.emoticon-puke { background-position: 0 -551px; }
|
35
|
+
.emoticon-doh { background-position: 0 -580px; }
|
36
|
+
.emoticon-angry { background-position: 0 -609px; }
|
37
|
+
.emoticon-speechless-1 { background-position: 0 -638px; }
|
38
|
+
.emoticon-it-wasnt-me { background-position: 0 -667px; }
|
39
|
+
.emoticon-kiss-1 { background-position: 0 -696px; }
|
40
|
+
.emoticon-party { background-position: 0 -725px; }
|
41
|
+
.emoticon-tongue-out-1 { background-position: 0 -754px; }
|
42
|
+
.emoticon-wondering-1 { background-position: 0 -783px; }
|
43
|
+
.emoticon-worried { background-position: 0 -812px; }
|
44
|
+
.emoticon-mmm { background-position: 0 -841px; }
|
45
|
+
.emoticon-sleepy-1 { background-position: 0 -870px; }
|
46
|
+
.emoticon-dull-1 { background-position: 0 -899px; }
|
47
|
+
.emoticon-nerd { background-position: 0 -928px; }
|
48
|
+
.emoticon-evil-grin-1 { background-position: 0 -957px; }
|
49
|
+
.emoticon-lips-sealed { background-position: 0 -986px; }
|
50
|
+
.emoticon-hi { background-position: 0 -1015px; }
|
51
|
+
.emoticon-call { background-position: 0 -1044px; }
|
52
|
+
.emoticon-devil { background-position: 0 -1073px; }
|
53
|
+
.emoticon-angel { background-position: 0 -1102px; }
|
54
|
+
.emoticon-angry-1 { background-position: 0 -1131px; }
|
55
|
+
.emoticon-envy { background-position: 0 -1160px; }
|
56
|
+
.emoticon-worried-1 { background-position: 0 -1189px; }
|
57
|
+
.emoticon-nerd-1 { background-position: 0 -1218px; }
|
58
|
+
.emoticon-wait { background-position: 0 -1247px; }
|
59
|
+
.emoticon-bear { background-position: 0 -1276px; }
|
60
|
+
.emoticon-lips-sealed-1 { background-position: 0 -1305px; }
|
61
|
+
.emoticon-make-up { background-position: 0 -1334px; }
|
62
|
+
.emoticon-giggle { background-position: 0 -1363px; }
|
63
|
+
.emoticon-clapping { background-position: 0 -1392px; }
|
64
|
+
.emoticon-thinking { background-position: 0 -1421px; }
|
65
|
+
.emoticon-bow { background-position: 0 -1450px; }
|
66
|
+
.emoticon-rofl { background-position: 0 -1479px; }
|
67
|
+
.emoticon-whew { background-position: 0 -1508px; }
|
68
|
+
.emoticon-happy { background-position: 0 -1537px; }
|
69
|
+
.emoticon-smirk { background-position: 0 -1566px; }
|
70
|
+
.emoticon-nod { background-position: 0 -1595px; }
|
71
|
+
.emoticon-shake { background-position: 0 -1624px; }
|
72
|
+
.emoticon-punch { background-position: 0 -1653px; }
|
73
|
+
.emoticon-emo { background-position: 0 -1682px; }
|
74
|
+
.emoticon-yes { background-position: 0 -1711px; }
|
75
|
+
.emoticon-no { background-position: 0 -1740px; }
|
76
|
+
.emoticon-handshake { background-position: 0 -1769px; }
|
77
|
+
.emoticon-skype { background-position: 0 -1798px; }
|
78
|
+
.emoticon-heart { background-position: 0 -1827px; }
|
79
|
+
.emoticon-broken-heart { background-position: 0 -1856px; }
|
80
|
+
.emoticon-mail { background-position: 0 -1885px; }
|
81
|
+
.emoticon-flower { background-position: 0 -1914px; }
|
82
|
+
.emoticon-rain { background-position: 0 -1943px; }
|
83
|
+
.emoticon-sun { background-position: 0 -1972px; }
|
84
|
+
.emoticon-time { background-position: -29px 0; }
|
85
|
+
.emoticon-music { background-position: -29px -29px; }
|
86
|
+
.emoticon-movie { background-position: -29px -58px; }
|
87
|
+
.emoticon-phone { background-position: -29px -87px; }
|
88
|
+
.emoticon-coffee { background-position: -29px -116px; }
|
89
|
+
.emoticon-pizza { background-position: -29px -145px; }
|
90
|
+
.emoticon-cash { background-position: -29px -174px; }
|
91
|
+
.emoticon-muscle { background-position: -29px -203px; }
|
92
|
+
.emoticon-cake { background-position: -29px -232px; }
|
93
|
+
.emoticon-beer { background-position: -29px -261px; }
|
94
|
+
.emoticon-drink { background-position: -29px -290px; }
|
95
|
+
.emoticon-dance { background-position: -29px -319px; }
|
96
|
+
.emoticon-ninja { background-position: -29px -348px; }
|
97
|
+
.emoticon-star { background-position: -29px -377px; }
|
98
|
+
.emoticon-mooning { background-position: -29px -406px; }
|
99
|
+
.emoticon-middlefinger { background-position: -29px -435px; }
|
100
|
+
.emoticon-bandit { background-position: -29px -464px; }
|
101
|
+
.emoticon-drunk { background-position: -29px -493px; }
|
102
|
+
.emoticon-smoke { background-position: -29px -522px; }
|
103
|
+
.emoticon-toivo { background-position: -29px -551px; }
|
104
|
+
.emoticon-rock { background-position: -29px -580px; }
|
105
|
+
.emoticon-headbang { background-position: -29px -609px; }
|
106
|
+
.emoticon-bug { background-position: -29px -638px; }
|
107
|
+
.emoticon-fubar { background-position: -29px -666px; }
|
108
|
+
.emoticon-poolparty { background-position: -29px -695px; }
|
109
|
+
.emoticon-swear { background-position: -29px -724px; }
|
110
|
+
.emoticon-tmi { background-position: -29px -753px; }
|
111
|
+
.emoticon-heidy { background-position: -29px -782px; }
|
112
|
+
.emoticon-myspace { background-position: -29px -811px; }
|
113
|
+
.emoticon-malthe { background-position: -29px -840px; }
|
114
|
+
.emoticon-tauri { background-position: -29px -869px; }
|
115
|
+
.emoticon-priidu { background-position: -29px -898px; }
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module RailsChat
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
def self.source_root
|
5
|
+
File.dirname(__FILE__) + "/templates"
|
6
|
+
end
|
7
|
+
|
8
|
+
def copy_files
|
9
|
+
template "rails_chat.yml", "config/rails_chat.yml"
|
10
|
+
if ::Rails.version < "3.1"
|
11
|
+
copy_file "../../../../app/assets/javascripts/rails_chat.js", "public/javascripts/rails_chat.js"
|
12
|
+
copy_file "../../../../app/assets/javascripts/emoticons_defination.js", "public/javascripts/emoticons_defination.js"
|
13
|
+
copy_file "../../../../app/assets/javascripts/emoticons.js", "public/javascripts/emoticons.js"
|
14
|
+
copy_file "../../../../app/assets/stylesheets/emoticons.css", "public/stylesheets/emoticons.css"
|
15
|
+
copy_file "../../../../app/assets/images/emoticons.png", "public/images/emoticons.png"
|
16
|
+
else
|
17
|
+
copy_file "../../../../app/assets/javascripts/rails_chat.js", "app/assets/javascripts/rails_chat.js"
|
18
|
+
copy_file "../../../../app/assets/javascripts/emoticons_defination.js", "app/assets/javascripts/emoticons_defination.js"
|
19
|
+
copy_file "../../../../app/assets/javascripts/emoticons.js", "app/assets/javascripts/emoticons.js"
|
20
|
+
copy_file "../../../../app/assets/stylesheets/emoticons.css", "app/assets/stylesheets/emoticons.css"
|
21
|
+
copy_file "../../../../app/assets/images/emoticons.png", "app/assets/images/emoticons.png"
|
22
|
+
end
|
23
|
+
copy_file "rails_chat.ru", "rails_chat.ru"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Run with: rackup rails_chat.ru -s thin -E production
|
2
|
+
require "bundler/setup"
|
3
|
+
require "yaml"
|
4
|
+
require "faye"
|
5
|
+
require "rails_chat"
|
6
|
+
|
7
|
+
Faye::WebSocket.load_adapter('thin')
|
8
|
+
|
9
|
+
RailsChat.load_config(File.expand_path("../config/rails_chat.yml", __FILE__), ENV["RAILS_ENV"] || "development")
|
10
|
+
run RailsChat.faye_app
|
@@ -0,0 +1,10 @@
|
|
1
|
+
development:
|
2
|
+
server: "http://localhost:9292/faye"
|
3
|
+
secret_token: "secret"
|
4
|
+
test:
|
5
|
+
server: "http://localhost:9292/faye"
|
6
|
+
secret_token: "secret"
|
7
|
+
production:
|
8
|
+
server: "http://example.com/faye"
|
9
|
+
secret_token: "<%= defined?(SecureRandom) ? SecureRandom.hex(32) : ActiveSupport::SecureRandom.hex(32) %>"
|
10
|
+
signature_expiration: 3600 # one hour
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rails_chat/view_helpers"
|
2
|
+
|
3
|
+
module RailsChat
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
# Loads the rails_chat.yml file if it exists.
|
6
|
+
initializer "rails_chat.config" do
|
7
|
+
path = Rails.root.join("config/rails_chat.yml")
|
8
|
+
RailsChat.load_config(path, Rails.env) if path.exist?
|
9
|
+
end
|
10
|
+
|
11
|
+
# Adds the ViewHelpers into ActionView::Base
|
12
|
+
initializer "rails_chat.view_helpers" do
|
13
|
+
ActionView::Base.send :include, ViewHelpers
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RailsChat
|
2
|
+
# This class is an extension for the Faye::RackAdapter.
|
3
|
+
# It is used inside of RailsChat.faye_app.
|
4
|
+
class FayeExtension
|
5
|
+
# Callback to handle incoming Faye messages. This authenticates both
|
6
|
+
# subscribe and publish calls.
|
7
|
+
def incoming(message, callback)
|
8
|
+
if message["channel"] == "/meta/subscribe"
|
9
|
+
authenticate_subscribe(message)
|
10
|
+
elsif message["channel"] !~ %r{^/meta/}
|
11
|
+
authenticate_publish(message)
|
12
|
+
end
|
13
|
+
callback.call(message)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Ensure the subscription signature is correct and that it has not expired.
|
19
|
+
def authenticate_subscribe(message)
|
20
|
+
subscription = RailsChat.subscription(:channel => message["subscription"], :timestamp => message["ext"]["rails_chat_timestamp"])
|
21
|
+
if message["ext"]["rails_chat_signature"] != subscription[:signature]
|
22
|
+
message["error"] = "Incorrect signature."
|
23
|
+
elsif RailsChat.signature_expired? message["ext"]["rails_chat_timestamp"].to_i
|
24
|
+
message["error"] = "Signature has expired."
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensures the secret token is correct before publishing.
|
29
|
+
def authenticate_publish(message)
|
30
|
+
if RailsChat.config[:secret_token].nil?
|
31
|
+
raise Error, "No secret_token config set, ensure rails_chat.yml is loaded properly."
|
32
|
+
elsif message["ext"]["rails_chat_token"] != RailsChat.config[:secret_token]
|
33
|
+
message["error"] = "Incorrect token."
|
34
|
+
else
|
35
|
+
message["ext"]["rails_chat_token"] = nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RailsChat
|
2
|
+
module ViewHelpers
|
3
|
+
# Publish the given data or block to the client by sending
|
4
|
+
# a Net::HTTP POST request to the Faye server. If a block
|
5
|
+
# or string is passed in, it is evaluated as JavaScript
|
6
|
+
# on the client. Otherwise it will be converted to JSON
|
7
|
+
# for use in a JavaScript callback.
|
8
|
+
def publish_to(channel, data = nil, &block)
|
9
|
+
RailsChat.publish_to(channel, data || capture(&block))
|
10
|
+
end
|
11
|
+
|
12
|
+
# Subscribe the client to the given channel. This generates
|
13
|
+
# some JavaScript calling RailsChat.sign with the subscription
|
14
|
+
# options.
|
15
|
+
def subscribe_to(channel)
|
16
|
+
subscription = RailsChat.subscription(:channel => channel)
|
17
|
+
content_tag "script", :type => "text/javascript" do
|
18
|
+
raw("RailsChat.sign(#{subscription.to_json});")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/rails_chat.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
require "net/http"
|
3
|
+
require "net/https"
|
4
|
+
|
5
|
+
require "rails_chat/faye_extension"
|
6
|
+
require "rails_chat/engine" if defined? Rails
|
7
|
+
|
8
|
+
module RailsChat
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_reader :config
|
13
|
+
|
14
|
+
# Resets the configuration to the default (empty hash)
|
15
|
+
def reset_config
|
16
|
+
@config = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Loads the configuration from a given YAML file and environment (such as production)
|
20
|
+
def load_config(filename, environment)
|
21
|
+
yaml = YAML.load_file(filename)[environment.to_s]
|
22
|
+
raise ArgumentError, "The #{environment} environment does not exist in #{filename}" if yaml.nil?
|
23
|
+
yaml.each { |k, v| config[k.to_sym] = v }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Publish the given data to a specific channel. This ends up sending
|
27
|
+
# a Net::HTTP POST request to the Faye server.
|
28
|
+
def publish_to(channel, data)
|
29
|
+
publish_message(message(channel, data))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sends the given message hash to the Faye server using Net::HTTP.
|
33
|
+
def publish_message(message)
|
34
|
+
raise Error, "No server specified, ensure rails_chat.yml was loaded properly." unless config[:server]
|
35
|
+
url = URI.parse(config[:server])
|
36
|
+
|
37
|
+
form = Net::HTTP::Post.new(url.path.empty? ? '/' : url.path)
|
38
|
+
form.set_form_data(:message => message.to_json)
|
39
|
+
|
40
|
+
http = Net::HTTP.new(url.host, url.port)
|
41
|
+
http.use_ssl = url.scheme == "https"
|
42
|
+
http.start {|h| h.request(form)}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a message hash for sending to Faye
|
46
|
+
def message(channel, data)
|
47
|
+
message = {:channel => channel, :data => {:channel => channel}, :ext => {:rails_chat_token => config[:secret_token]}}
|
48
|
+
if data.kind_of? String
|
49
|
+
message[:data][:eval] = data
|
50
|
+
else
|
51
|
+
message[:data][:data] = data
|
52
|
+
end
|
53
|
+
message
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns a subscription hash to pass to the RailsChat.sign call in JavaScript.
|
57
|
+
# Any options passed are merged to the hash.
|
58
|
+
def subscription(options = {})
|
59
|
+
sub = {:server => config[:server], :timestamp => (Time.now.to_f * 1000).round}.merge(options)
|
60
|
+
sub[:signature] = Digest::SHA1.hexdigest([config[:secret_token], sub[:channel], sub[:timestamp]].join)
|
61
|
+
sub
|
62
|
+
end
|
63
|
+
|
64
|
+
# Determine if the signature has expired given a timestamp.
|
65
|
+
def signature_expired?(timestamp)
|
66
|
+
timestamp < ((Time.now.to_f - config[:signature_expiration])*1000).round if config[:signature_expiration]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the Faye Rack application.
|
70
|
+
# Any options given are passed to the Faye::RackAdapter.
|
71
|
+
def faye_app(options = {})
|
72
|
+
options = {:mount => "/faye", :timeout => 45, :extensions => [FayeExtension.new]}.merge(options)
|
73
|
+
Faye::RackAdapter.new(options)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
reset_config
|
78
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
development:
|
2
|
+
server: http://dev.local:9292/faye
|
3
|
+
secret_token: DEVELOPMENT_SECRET_TOKEN
|
4
|
+
signature_expiration: 600
|
5
|
+
production:
|
6
|
+
server: http://example.com/faye
|
7
|
+
secret_token: PRODUCTION_SECRET_TOKEN
|
8
|
+
signature_expiration: 600
|
9
|
+
#write down your secret token and url here
|
@@ -0,0 +1,108 @@
|
|
1
|
+
describe("RailsChat", function() {
|
2
|
+
var pub, doc;
|
3
|
+
beforeEach(function() {
|
4
|
+
Faye = {}; // To simulate global Faye object
|
5
|
+
doc = {};
|
6
|
+
pub = buildRailsChat(doc);
|
7
|
+
});
|
8
|
+
|
9
|
+
it("adds a subscription callback", function() {
|
10
|
+
pub.subscribe("hello", "callback");
|
11
|
+
expect(pub.subscriptionCallbacks["hello"]).toEqual("callback");
|
12
|
+
});
|
13
|
+
|
14
|
+
it("has a fayeExtension which adds matching subscription signature and timestamp to outgoing message", function() {
|
15
|
+
var called = false;
|
16
|
+
var message = {channel: "/meta/subscribe", subscription: "hello"}
|
17
|
+
pub.subscriptions["hello"] = {signature: "abcd", timestamp: "1234"}
|
18
|
+
pub.fayeExtension.outgoing(message, function(message) {
|
19
|
+
expect(message.ext.rails_chat_signature).toEqual("abcd");
|
20
|
+
expect(message.ext.rails_chat_timestamp).toEqual("1234");
|
21
|
+
called = true;
|
22
|
+
});
|
23
|
+
expect(called).toBeTruthy();
|
24
|
+
});
|
25
|
+
|
26
|
+
it("evaluates javascript in message response", function() {
|
27
|
+
pub.handleResponse({eval: 'self.subscriptions.foo = "bar"'});
|
28
|
+
expect(pub.subscriptions.foo).toEqual("bar");
|
29
|
+
});
|
30
|
+
|
31
|
+
it("triggers callback matching message channel in response", function() {
|
32
|
+
var called = false;
|
33
|
+
pub.subscribe("test", function(data, channel) {
|
34
|
+
expect(data).toEqual("abcd");
|
35
|
+
expect(channel).toEqual("test");
|
36
|
+
called = true;
|
37
|
+
});
|
38
|
+
pub.handleResponse({channel: "test", data: "abcd"});
|
39
|
+
expect(called).toBeTruthy();
|
40
|
+
});
|
41
|
+
|
42
|
+
it("adds a faye subscription with response handler when signing", function() {
|
43
|
+
var faye = {subscribe: jasmine.createSpy()};
|
44
|
+
spyOn(pub, 'faye').andCallFake(function(callback) {
|
45
|
+
callback(faye);
|
46
|
+
});
|
47
|
+
var options = {server: "server", channel: "somechannel"};
|
48
|
+
pub.sign(options);
|
49
|
+
expect(faye.subscribe).toHaveBeenCalledWith("somechannel", pub.handleResponse);
|
50
|
+
expect(pub.subscriptions.server).toEqual("server");
|
51
|
+
expect(pub.subscriptions.somechannel).toEqual(options);
|
52
|
+
});
|
53
|
+
|
54
|
+
it("adds a faye subscription with response handler when signing", function() {
|
55
|
+
var faye = {subscribe: jasmine.createSpy()};
|
56
|
+
spyOn(pub, 'faye').andCallFake(function(callback) {
|
57
|
+
callback(faye);
|
58
|
+
});
|
59
|
+
var options = {server: "server", channel: "somechannel"};
|
60
|
+
pub.sign(options);
|
61
|
+
expect(faye.subscribe).toHaveBeenCalledWith("somechannel", pub.handleResponse);
|
62
|
+
expect(pub.subscriptions.server).toEqual("server");
|
63
|
+
expect(pub.subscriptions.somechannel).toEqual(options);
|
64
|
+
});
|
65
|
+
|
66
|
+
it("triggers faye callback function immediately when fayeClient is available", function() {
|
67
|
+
var called = false;
|
68
|
+
pub.fayeClient = "faye";
|
69
|
+
pub.faye(function(faye) {
|
70
|
+
expect(faye).toEqual("faye");
|
71
|
+
called = true;
|
72
|
+
});
|
73
|
+
expect(called).toBeTruthy();
|
74
|
+
});
|
75
|
+
|
76
|
+
it("adds fayeCallback when client and server aren't available", function() {
|
77
|
+
pub.faye("callback");
|
78
|
+
expect(pub.fayeCallbacks[0]).toEqual("callback");
|
79
|
+
});
|
80
|
+
|
81
|
+
it("adds a script tag loading faye js when the server is present", function() {
|
82
|
+
script = {};
|
83
|
+
doc.createElement = function() { return script; };
|
84
|
+
doc.documentElement = {appendChild: jasmine.createSpy()};
|
85
|
+
pub.subscriptions.server = "path/to/faye";
|
86
|
+
pub.faye("callback");
|
87
|
+
expect(pub.fayeCallbacks[0]).toEqual("callback");
|
88
|
+
expect(script.type).toEqual("text/javascript");
|
89
|
+
expect(script.src).toEqual("path/to/faye.js");
|
90
|
+
expect(script.onload).toEqual(pub.connectToFaye);
|
91
|
+
expect(doc.documentElement.appendChild).toHaveBeenCalledWith(script);
|
92
|
+
});
|
93
|
+
|
94
|
+
it("connects to faye server, adds extension, and executes callbacks", function() {
|
95
|
+
callback = jasmine.createSpy();
|
96
|
+
client = {addExtension: jasmine.createSpy()};
|
97
|
+
Faye.Client = function(server) {
|
98
|
+
expect(server).toEqual("server")
|
99
|
+
return client;
|
100
|
+
};
|
101
|
+
pub.subscriptions.server = "server";
|
102
|
+
pub.fayeCallbacks.push(callback);
|
103
|
+
pub.connectToFaye();
|
104
|
+
expect(pub.fayeClient).toEqual(client);
|
105
|
+
expect(client.addExtension).toHaveBeenCalledWith(pub.fayeExtension);
|
106
|
+
expect(callback).toHaveBeenCalledWith(client);
|
107
|
+
});
|
108
|
+
});
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# src_files
|
2
|
+
#
|
3
|
+
# Return an array of filepaths relative to src_dir to include before jasmine specs.
|
4
|
+
# Default: []
|
5
|
+
#
|
6
|
+
# EXAMPLE:
|
7
|
+
#
|
8
|
+
# src_files:
|
9
|
+
# - lib/source1.js
|
10
|
+
# - lib/source2.js
|
11
|
+
# - dist/**/*.js
|
12
|
+
#
|
13
|
+
src_files:
|
14
|
+
- app/assets/javascripts/rails_chat.js
|
15
|
+
|
16
|
+
# stylesheets
|
17
|
+
#
|
18
|
+
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
|
19
|
+
# Default: []
|
20
|
+
#
|
21
|
+
# EXAMPLE:
|
22
|
+
#
|
23
|
+
# stylesheets:
|
24
|
+
# - css/style.css
|
25
|
+
# - stylesheets/*.css
|
26
|
+
#
|
27
|
+
stylesheets:
|
28
|
+
|
29
|
+
# helpers
|
30
|
+
#
|
31
|
+
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
|
32
|
+
# Default: ["helpers/**/*.js"]
|
33
|
+
#
|
34
|
+
# EXAMPLE:
|
35
|
+
#
|
36
|
+
# helpers:
|
37
|
+
# - helpers/**/*.js
|
38
|
+
#
|
39
|
+
helpers:
|
40
|
+
|
41
|
+
# spec_files
|
42
|
+
#
|
43
|
+
# Return an array of filepaths relative to spec_dir to include.
|
44
|
+
# Default: ["**/*[sS]pec.js"]
|
45
|
+
#
|
46
|
+
# EXAMPLE:
|
47
|
+
#
|
48
|
+
# spec_files:
|
49
|
+
# - **/*[sS]pec.js
|
50
|
+
#
|
51
|
+
spec_files:
|
52
|
+
|
53
|
+
# src_dir
|
54
|
+
#
|
55
|
+
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
|
56
|
+
# Default: project root
|
57
|
+
#
|
58
|
+
# EXAMPLE:
|
59
|
+
#
|
60
|
+
# src_dir: public
|
61
|
+
#
|
62
|
+
src_dir:
|
63
|
+
|
64
|
+
# spec_dir
|
65
|
+
#
|
66
|
+
# Spec directory path. Your spec_files must be returned relative to this path.
|
67
|
+
# Default: spec/javascripts
|
68
|
+
#
|
69
|
+
# EXAMPLE:
|
70
|
+
#
|
71
|
+
# spec_dir: spec/javascripts
|
72
|
+
#
|
73
|
+
spec_dir:
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Jasmine
|
2
|
+
class Config
|
3
|
+
|
4
|
+
# Add your overrides or custom config code here
|
5
|
+
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
# Note - this is necessary for rspec2, which has removed the backtrace
|
11
|
+
module Jasmine
|
12
|
+
class SpecBuilder
|
13
|
+
def declare_spec(parent, spec)
|
14
|
+
me = self
|
15
|
+
example_name = spec["name"]
|
16
|
+
@spec_ids << spec["id"]
|
17
|
+
backtrace = @example_locations[parent.description + " " + example_name]
|
18
|
+
parent.it example_name, {} do
|
19
|
+
me.report_spec(spec["id"])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
$:.unshift(ENV['JASMINE_GEM_PATH']) if ENV['JASMINE_GEM_PATH'] # for gem testing purposes
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'jasmine'
|
5
|
+
jasmine_config_overrides = File.expand_path(File.join(File.dirname(__FILE__), 'jasmine_config.rb'))
|
6
|
+
require jasmine_config_overrides if File.exist?(jasmine_config_overrides)
|
7
|
+
if Jasmine::Dependencies.rspec2?
|
8
|
+
require 'rspec'
|
9
|
+
else
|
10
|
+
require 'spec'
|
11
|
+
end
|
12
|
+
|
13
|
+
jasmine_config = Jasmine::Config.new
|
14
|
+
spec_builder = Jasmine::SpecBuilder.new(jasmine_config)
|
15
|
+
|
16
|
+
should_stop = false
|
17
|
+
|
18
|
+
if Jasmine::Dependencies.rspec2?
|
19
|
+
RSpec.configuration.after(:suite) do
|
20
|
+
spec_builder.stop if should_stop
|
21
|
+
end
|
22
|
+
else
|
23
|
+
Spec::Runner.configure do |config|
|
24
|
+
config.after(:suite) do
|
25
|
+
spec_builder.stop if should_stop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
spec_builder.start
|
31
|
+
should_stop = true
|
32
|
+
spec_builder.declare_suites
|