cable_ready 4.4.3 → 5.0.0.pre1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +238 -155
- data/Gemfile.lock +144 -100
- data/LATEST +1 -0
- data/README.md +13 -13
- data/Rakefile +8 -2
- data/app/channels/cable_ready/stream.rb +12 -0
- data/app/helpers/cable_ready_helper.rb +11 -0
- data/app/jobs/cable_ready_broadcast_job.rb +14 -0
- data/bin/standardize +1 -1
- data/lib/cable_ready.rb +41 -0
- data/lib/cable_ready/broadcaster.rb +3 -4
- data/lib/cable_ready/cable_car.rb +17 -0
- data/lib/cable_ready/channel.rb +14 -36
- data/lib/cable_ready/channels.rb +22 -65
- data/lib/cable_ready/compoundable.rb +11 -0
- data/lib/cable_ready/config.rb +78 -0
- data/lib/cable_ready/identifiable.rb +19 -0
- data/lib/cable_ready/operation_builder.rb +69 -0
- data/lib/cable_ready/sanity_checker.rb +151 -0
- data/lib/cable_ready/stream_identifier.rb +13 -0
- data/lib/cable_ready/version.rb +1 -1
- data/lib/generators/cable_ready/channel_generator.rb +71 -0
- data/lib/generators/cable_ready/initializer_generator.rb +14 -0
- data/lib/generators/cable_ready/stream_from_generator.rb +43 -0
- data/lib/generators/cable_ready/templates/config/initializers/cable_ready.rb +18 -0
- data/test/lib/cable_ready/cable_car_test.rb +28 -0
- data/test/lib/cable_ready/identifiable_test.rb +75 -0
- data/test/lib/cable_ready/operation_builder_test.rb +128 -0
- data/test/lib/generators/cable_ready/channel_generator_test.rb +157 -0
- data/test/support/generator_test_helpers.rb +28 -0
- data/test/test_helper.rb +15 -0
- metadata +66 -15
- data/cable_ready.gemspec +0 -25
- data/package.json +0 -36
- data/tags +0 -57
- data/yarn.lock +0 -2552
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CableReady::SanityChecker
|
|
4
|
+
LATEST_VERSION_FORMAT = /^(\d+\.\d+\.\d+)$/
|
|
5
|
+
NODE_VERSION_FORMAT = /(\d+\.\d+\.\d+.*):/
|
|
6
|
+
JSON_VERSION_FORMAT = /(\d+\.\d+\.\d+.*)"/
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def check!
|
|
10
|
+
return if CableReady.config.on_failed_sanity_checks == :ignore
|
|
11
|
+
return if called_by_generate_config?
|
|
12
|
+
|
|
13
|
+
instance = new
|
|
14
|
+
instance.check_javascript_package_version
|
|
15
|
+
instance.check_new_version_available
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def called_by_generate_config?
|
|
21
|
+
ARGV.include? "cable_ready:initializer"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def check_javascript_package_version
|
|
26
|
+
if javascript_package_version.nil?
|
|
27
|
+
warn_and_exit <<~WARN
|
|
28
|
+
Can't locate the cable_ready npm package.
|
|
29
|
+
Either add it to your package.json as a dependency or use "yarn link cable_ready" if you are doing development.
|
|
30
|
+
WARN
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
unless javascript_version_matches?
|
|
34
|
+
warn_and_exit <<~WARN
|
|
35
|
+
The cable_ready npm package version (#{javascript_package_version}) does not match the Rubygem version (#{gem_version}).
|
|
36
|
+
To update the cable_ready npm package:
|
|
37
|
+
yarn upgrade cable_ready@#{gem_version}
|
|
38
|
+
WARN
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check_new_version_available
|
|
43
|
+
return unless Rails.env.development?
|
|
44
|
+
return if CableReady.config.on_new_version_available == :ignore
|
|
45
|
+
return unless using_stable_release
|
|
46
|
+
begin
|
|
47
|
+
latest_version = URI.open("https://raw.githubusercontent.com/stimulusreflex/cable_ready/master/LATEST", open_timeout: 1, read_timeout: 1).read.strip
|
|
48
|
+
if latest_version != CableReady::VERSION
|
|
49
|
+
puts <<~WARN
|
|
50
|
+
|
|
51
|
+
There is a new version of CableReady available!
|
|
52
|
+
Current: #{CableReady::VERSION} Latest: #{latest_version}
|
|
53
|
+
|
|
54
|
+
If you upgrade, it is very important that you update BOTH Gemfile and package.json
|
|
55
|
+
Then, run `bundle install && yarn install` to update to #{latest_version}.
|
|
56
|
+
|
|
57
|
+
WARN
|
|
58
|
+
exit if CableReady.config.on_new_version_available == :exit
|
|
59
|
+
end
|
|
60
|
+
rescue
|
|
61
|
+
puts "CableReady #{CableReady::VERSION} update check skipped: connection timeout"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def javascript_version_matches?
|
|
68
|
+
javascript_package_version == gem_version
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def using_stable_release
|
|
72
|
+
stable = CableReady::VERSION.match?(LATEST_VERSION_FORMAT)
|
|
73
|
+
puts "CableReady #{CableReady::VERSION} update check skipped: pre-release build" unless stable
|
|
74
|
+
stable
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def gem_version
|
|
78
|
+
@_gem_version ||= CableReady::VERSION.gsub(".pre", "-pre")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def javascript_package_version
|
|
82
|
+
@_js_version ||= find_javascript_package_version
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def find_javascript_package_version
|
|
86
|
+
if (match = search_file(package_json_path, regex: /version/))
|
|
87
|
+
match[JSON_VERSION_FORMAT, 1]
|
|
88
|
+
elsif (match = search_file(yarn_lock_path, regex: /^cable_ready/))
|
|
89
|
+
match[NODE_VERSION_FORMAT, 1]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def search_file(path, regex:)
|
|
94
|
+
return unless File.exist?(path)
|
|
95
|
+
File.foreach(path).grep(regex).first
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def package_json_path
|
|
99
|
+
Rails.root.join("node_modules", "cable_ready", "package.json")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def yarn_lock_path
|
|
103
|
+
Rails.root.join("yarn.lock")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def initializer_path
|
|
107
|
+
@_initializer_path ||= Rails.root.join("config", "initializers", "cable_ready.rb")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def warn_and_exit(text)
|
|
111
|
+
puts "WARNING:"
|
|
112
|
+
puts text
|
|
113
|
+
exit_with_info if CableReady.config.on_failed_sanity_checks == :exit
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def exit_with_info
|
|
117
|
+
puts
|
|
118
|
+
|
|
119
|
+
if File.exist?(initializer_path)
|
|
120
|
+
puts <<~INFO
|
|
121
|
+
If you know what you are doing and you want to start the application anyway,
|
|
122
|
+
you can add the following directive to the CableReady initializer,
|
|
123
|
+
which is located at #{initializer_path}
|
|
124
|
+
|
|
125
|
+
CableReady.configure do |config|
|
|
126
|
+
config.on_failed_sanity_checks = :warn
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
INFO
|
|
130
|
+
else
|
|
131
|
+
puts <<~INFO
|
|
132
|
+
If you know what you are doing and you want to start the application anyway,
|
|
133
|
+
you can create a CableReady initializer with the command:
|
|
134
|
+
|
|
135
|
+
bundle exec rails generate cable_ready:config
|
|
136
|
+
|
|
137
|
+
Then open your initializer at
|
|
138
|
+
|
|
139
|
+
#{initializer_path}
|
|
140
|
+
|
|
141
|
+
and then add the following directive:
|
|
142
|
+
|
|
143
|
+
CableReady.configure do |config|
|
|
144
|
+
config.on_failed_sanity_checks = :warn
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
INFO
|
|
148
|
+
end
|
|
149
|
+
exit false unless Rails.env.test?
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CableReady
|
|
4
|
+
module StreamIdentifier
|
|
5
|
+
def verified_stream_identifier(signed_stream_identifier)
|
|
6
|
+
CableReady.signed_stream_verifier.verified signed_stream_identifier
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def signed_stream_identifier(compoundable)
|
|
10
|
+
CableReady.signed_stream_verifier.generate compoundable
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/cable_ready/version.rb
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CableReady::ChannelGenerator < Rails::Generators::NamedBase
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
class_option :stream_from, type: :string
|
|
7
|
+
class_option :stream_for, type: :string
|
|
8
|
+
class_option :stimulus, type: :boolean
|
|
9
|
+
|
|
10
|
+
def check_options
|
|
11
|
+
raise "Can't specify --stream-from and --stream-for at the same time" if options.key?(:stream_from) && options.key?(:stream_for)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_channel
|
|
15
|
+
generate "channel", file_name
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def enhance_channels
|
|
19
|
+
if using_broadcast_to?
|
|
20
|
+
gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_for #{resource}.find(params[:id])\n"
|
|
21
|
+
template "app/javascript/controllers/%file_name%_controller.js" if using_stimulus?
|
|
22
|
+
else
|
|
23
|
+
prepend_to_file "app/javascript/channels/#{file_name}_channel.js", "import CableReady from 'cable_ready'\n"
|
|
24
|
+
inject_into_file "app/javascript/channels/#{file_name}_channel.js", after: "// Called when there's incoming data on the websocket for this channel\n" do
|
|
25
|
+
<<-JS
|
|
26
|
+
if (data.cableReady) CableReady.perform(data.operations)
|
|
27
|
+
JS
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_from \"#{identifier}\"\n"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def option_given?
|
|
37
|
+
options.key?(:stream_from) || options.key?(:stream_for)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def using_broadcast_to?
|
|
41
|
+
@using_broadcast_to ||= option_given? ? options.key?(:stream_for) : yes?("Are you streaming to a resource using broadcast_to? (y/N)")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def using_stimulus?
|
|
45
|
+
@using_stimulus ||= options.fetch(:stimulus) {
|
|
46
|
+
yes?("Are you going to use a Stimulus controller to subscribe to this channel? (y/N)")
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def resource
|
|
51
|
+
return @resource if @resource
|
|
52
|
+
|
|
53
|
+
stream_for = options.fetch(:stream_for) {
|
|
54
|
+
ask("Which resource are you streaming for?", default: class_name)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
stream_for = file_name if stream_for == "stream_for"
|
|
58
|
+
@resource = stream_for.camelize
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def identifier
|
|
62
|
+
return @identifier if @identifier
|
|
63
|
+
|
|
64
|
+
stream_from = options.fetch(:stream_from) {
|
|
65
|
+
ask("What is the stream identifier that goes into stream_from?", default: file_name)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
stream_from = file_name if stream_from == "stream_from"
|
|
69
|
+
@identifier = stream_from.underscore
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module CableReady
|
|
6
|
+
class InitializerGenerator < Rails::Generators::Base
|
|
7
|
+
desc "Creates a CableReady initializer template in config/initializers"
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
def copy_initializer_file
|
|
11
|
+
copy_file "config/initializers/cable_ready.rb"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module CableReady
|
|
7
|
+
class StreamFromGenerator < Rails::Generators::Base
|
|
8
|
+
desc "Initializes CableReady with a reference to the shared ActionCable consumer"
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
|
|
11
|
+
def copy_controller_file
|
|
12
|
+
main_folder = defined?(Webpacker) ? Webpacker.config.source_path.to_s.gsub("#{Rails.root}/", "") : "app/javascript"
|
|
13
|
+
|
|
14
|
+
filepath = [
|
|
15
|
+
"#{main_folder}/controllers/index.js",
|
|
16
|
+
"#{main_folder}/controllers/index.ts",
|
|
17
|
+
"#{main_folder}/packs/application.js",
|
|
18
|
+
"#{main_folder}/packs/application.ts"
|
|
19
|
+
]
|
|
20
|
+
.select { |path| File.exist?(path) }
|
|
21
|
+
.map { |path| Rails.root.join(path) }
|
|
22
|
+
.first
|
|
23
|
+
|
|
24
|
+
lines = File.open(filepath, "r") { |f| f.readlines }
|
|
25
|
+
|
|
26
|
+
unless lines.find { |line| line.start_with?("import CableReady") }
|
|
27
|
+
matches = lines.select { |line| line =~ /\A(require|import)/ }
|
|
28
|
+
lines.insert lines.index(matches.last).to_i + 1, "import CableReady from 'cable_ready'\n"
|
|
29
|
+
File.open(filepath, "w") { |f| f.write lines.join }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
unless lines.find { |line| line.start_with?("import consumer") }
|
|
33
|
+
matches = lines.select { |line| line =~ /\A(require|import)/ }
|
|
34
|
+
lines.insert lines.index(matches.last).to_i + 1, "import consumer from '../channels/consumer'\n"
|
|
35
|
+
File.open(filepath, "w") { |f| f.write lines.join }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
unless lines.find { |line| line.include?("CableReady.initialize({ consumer })") }
|
|
39
|
+
append_to_file filepath, "CableReady.initialize({ consumer })"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
CableReady.configure do |config|
|
|
4
|
+
# Enable/disable exiting / warning when the sanity checks fail options:
|
|
5
|
+
# `:exit` or `:warn` or `:ignore`
|
|
6
|
+
|
|
7
|
+
# config.on_failed_sanity_checks = :exit
|
|
8
|
+
|
|
9
|
+
# Enable/disable exiting / warning when there's a new CableReady release
|
|
10
|
+
# `:exit` or `:warn` or `:ignore`
|
|
11
|
+
|
|
12
|
+
# config.on_new_version_available = :ignore
|
|
13
|
+
|
|
14
|
+
# Define your own custom operations
|
|
15
|
+
# https://cableready.stimulusreflex.com/customization#custom-operations
|
|
16
|
+
|
|
17
|
+
# config.add_operation_name :jazz_hands
|
|
18
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require_relative "../../../lib/cable_ready"
|
|
5
|
+
|
|
6
|
+
class CableReady::CableCarTest < ActiveSupport::TestCase
|
|
7
|
+
setup do
|
|
8
|
+
@cable_car = CableReady::CableCar.instance
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test "dispatch should return json-ifiable payload" do
|
|
12
|
+
CableReady::CableCar.instance.reset!
|
|
13
|
+
dispatch = CableReady::CableCar.instance.inner_html(selector: "#users", html: "<span>winning</span>").dispatch
|
|
14
|
+
assert_equal({"innerHtml" => [{"selector" => "#users", "html" => "<span>winning</span>"}]}, dispatch)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test "dispatch should clear operations" do
|
|
18
|
+
CableReady::CableCar.instance.reset!
|
|
19
|
+
CableReady::CableCar.instance.inner_html(selector: "#users", html: "<span>winning</span>").dispatch
|
|
20
|
+
assert_equal({}, CableReady::CableCar.instance.instance_variable_get(:@enqueued_operations))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
test "dispatch should maintain operations if clear is false" do
|
|
24
|
+
CableReady::CableCar.instance.reset!
|
|
25
|
+
CableReady::CableCar.instance.inner_html(selector: "#users", html: "<span>winning</span>").dispatch(clear: false)
|
|
26
|
+
assert_equal({"inner_html" => [{"selector" => "#users", "html" => "<span>winning</span>"}]}, CableReady::CableCar.instance.instance_variable_get(:@enqueued_operations))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require_relative "../../../lib/cable_ready"
|
|
5
|
+
|
|
6
|
+
class User
|
|
7
|
+
include ActiveModel::Model
|
|
8
|
+
|
|
9
|
+
attr_accessor :id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class CableReady::IdentifiableTest < ActiveSupport::TestCase
|
|
13
|
+
include CableReady::Identifiable
|
|
14
|
+
|
|
15
|
+
test "should handle nil" do
|
|
16
|
+
assert_equal "#", dom_id(nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "should work with strings" do
|
|
20
|
+
assert_equal "#users", dom_id("users")
|
|
21
|
+
assert_equal "#users", dom_id("users ")
|
|
22
|
+
assert_equal "#users", dom_id(" users ")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test "should work with symbols" do
|
|
26
|
+
assert_equal "#users", dom_id(:users)
|
|
27
|
+
assert_equal "#active_users", dom_id(:active_users)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test "should just return one hash" do
|
|
31
|
+
assert_equal "#users", dom_id("users")
|
|
32
|
+
assert_equal "#users", dom_id("#users")
|
|
33
|
+
assert_equal "#users", dom_id("##users")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
test "should strip prefixes" do
|
|
37
|
+
assert_equal "#active_users", dom_id(" users ", " active ")
|
|
38
|
+
assert_equal "#all_active_users", dom_id(" users ", " all_active ")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
test "should not include provided prefix if prefix is nil" do
|
|
42
|
+
assert_equal "#users", dom_id("users", nil)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
test "should work with ActiveRecord::Relation" do
|
|
46
|
+
relation = mock("ActiveRecord::Relation")
|
|
47
|
+
|
|
48
|
+
relation.stubs(:is_a?).with(ActiveRecord::Relation).returns(true).at_least_once
|
|
49
|
+
relation.stubs(:is_a?).with(ActiveRecord::Base).never
|
|
50
|
+
relation.stubs(:model_name).returns(OpenStruct.new(plural: "users"))
|
|
51
|
+
|
|
52
|
+
assert_equal "#users", dom_id(relation)
|
|
53
|
+
assert_equal "#users", dom_id(relation, nil)
|
|
54
|
+
assert_equal "#active_users", dom_id(relation, "active")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
test "should work with ActiveRecord::Base" do
|
|
58
|
+
User.any_instance.stubs(:is_a?).with(ActiveRecord::Relation).returns(false)
|
|
59
|
+
User.any_instance.stubs(:is_a?).with(ActiveRecord::Base).returns(true)
|
|
60
|
+
|
|
61
|
+
assert_equal "#new_user", dom_id(User.new(id: nil))
|
|
62
|
+
|
|
63
|
+
user = User.new(id: 42)
|
|
64
|
+
|
|
65
|
+
assert_equal "#user_42", dom_id(user)
|
|
66
|
+
assert_equal "#user_42", dom_id(user, nil)
|
|
67
|
+
assert_equal "#all_active_user_42", dom_id(user, "all_active")
|
|
68
|
+
|
|
69
|
+
user = User.new(id: 99)
|
|
70
|
+
|
|
71
|
+
assert_equal "#user_99", dom_id(user)
|
|
72
|
+
assert_equal "#user_99", dom_id(user, nil)
|
|
73
|
+
assert_equal "#all_active_user_99", dom_id(user, "all_active")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
require_relative "../../../lib/cable_ready"
|
|
5
|
+
|
|
6
|
+
class CableReady::OperationBuilderTest < ActiveSupport::TestCase
|
|
7
|
+
setup do
|
|
8
|
+
@operation_builder = CableReady::OperationBuilder.new("test")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test "should create enqueued operations" do
|
|
12
|
+
assert_not_nil @operation_builder.instance_variable_get(:@enqueued_operations)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
test "should add observer to cable ready" do
|
|
16
|
+
assert_not_nil CableReady.config.instance_variable_get(:@observer_peers)[@operation_builder]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test "should remove observer when destroyed" do
|
|
20
|
+
@operation_builder = nil
|
|
21
|
+
assert_nil CableReady.config.instance_variable_get(:@observer_peers)[@operation_builder]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test "should add operation method" do
|
|
25
|
+
@operation_builder.add_operation_method("foobar")
|
|
26
|
+
assert @operation_builder.respond_to?(:foobar)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
test "added operation method should add keys" do
|
|
30
|
+
@operation_builder.add_operation_method("foobar")
|
|
31
|
+
@operation_builder.foobar({name: "passed_option"})
|
|
32
|
+
|
|
33
|
+
operations = @operation_builder.instance_variable_get(:@enqueued_operations)
|
|
34
|
+
|
|
35
|
+
assert_equal 1, operations["foobar"].size
|
|
36
|
+
assert_equal({"name" => "passed_option"}, operations["foobar"].first)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
test "should json-ify operations" do
|
|
40
|
+
@operation_builder.add_operation_method("foobar")
|
|
41
|
+
@operation_builder.foobar({name: "passed_option"})
|
|
42
|
+
assert_equal("{\"foobar\":[{\"name\":\"passed_option\"}]}", @operation_builder.to_json)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
test "should apply! many operations" do
|
|
46
|
+
@operation_builder.apply!(foobar: [{name: "passed_option"}])
|
|
47
|
+
|
|
48
|
+
operations = @operation_builder.instance_variable_get(:@enqueued_operations)
|
|
49
|
+
assert_equal 1, operations["foobar"].size
|
|
50
|
+
assert_equal({"name" => "passed_option"}, operations["foobar"].first)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
test "should apply! many operations from a string" do
|
|
54
|
+
@operation_builder.apply!(JSON.generate({foobar: [{name: "passed_option"}]}))
|
|
55
|
+
|
|
56
|
+
operations = @operation_builder.instance_variable_get(:@enqueued_operations)
|
|
57
|
+
assert_equal 1, operations["foobar"].size
|
|
58
|
+
assert_equal({"name" => "passed_option"}, operations["foobar"].first)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test "operations payload should omit empty operations" do
|
|
62
|
+
@operation_builder.add_operation_method("foobar")
|
|
63
|
+
payload = @operation_builder.operations_payload
|
|
64
|
+
assert_equal({}, payload)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
test "operations payload should camelize keys" do
|
|
68
|
+
@operation_builder.add_operation_method("foo_bar")
|
|
69
|
+
@operation_builder.foo_bar({beep_boop: "passed_option"})
|
|
70
|
+
assert_equal({"fooBar" => [{"beepBoop" => "passed_option"}]}, @operation_builder.operations_payload)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
test "should take first argument as selector" do
|
|
74
|
+
@operation_builder.add_operation_method("inner_html")
|
|
75
|
+
|
|
76
|
+
@operation_builder.inner_html("#smelly", html: "<span>I rock</span>")
|
|
77
|
+
|
|
78
|
+
operations = {
|
|
79
|
+
"innerHtml" => [{"html" => "<span>I rock</span>", "selector" => "#smelly"}]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
assert_equal(operations, @operation_builder.operations_payload)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
test "should use previously passed selector in next operation" do
|
|
86
|
+
@operation_builder.add_operation_method("inner_html")
|
|
87
|
+
@operation_builder.add_operation_method("set_focus")
|
|
88
|
+
|
|
89
|
+
@operation_builder.set_focus("#smelly").inner_html(html: "<span>I rock</span>")
|
|
90
|
+
|
|
91
|
+
operations = {
|
|
92
|
+
"setFocus" => [{"selector" => "#smelly"}],
|
|
93
|
+
"innerHtml" => [{"html" => "<span>I rock</span>", "selector" => "#smelly"}]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
assert_equal(operations, @operation_builder.operations_payload)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
test "should clear previous_selector after calling reset!" do
|
|
100
|
+
@operation_builder.add_operation_method("inner_html")
|
|
101
|
+
@operation_builder.inner_html(selector: "#smelly", html: "<span>I rock</span>")
|
|
102
|
+
|
|
103
|
+
@operation_builder.reset!
|
|
104
|
+
|
|
105
|
+
@operation_builder.inner_html(html: "<span>winning</span>")
|
|
106
|
+
|
|
107
|
+
assert_equal({"innerHtml" => [{"html" => "<span>winning</span>"}]}, @operation_builder.operations_payload)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
test "should use previous_selector if present and should use `selector` if explicitly provided" do
|
|
111
|
+
@operation_builder.add_operation_method("inner_html")
|
|
112
|
+
@operation_builder.add_operation_method("set_focus")
|
|
113
|
+
|
|
114
|
+
@operation_builder.set_focus("#smelly").inner_html(html: "<span>I rock</span>").inner_html(html: "<span>I rock too</span>", selector: "#smelly2")
|
|
115
|
+
|
|
116
|
+
operations = {
|
|
117
|
+
"setFocus" => [
|
|
118
|
+
{"selector" => "#smelly"}
|
|
119
|
+
],
|
|
120
|
+
"innerHtml" => [
|
|
121
|
+
{"html" => "<span>I rock</span>", "selector" => "#smelly"},
|
|
122
|
+
{"html" => "<span>I rock too</span>", "selector" => "#smelly2"}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
assert_equal(operations, @operation_builder.operations_payload)
|
|
127
|
+
end
|
|
128
|
+
end
|