turbo-rails 2.0.7 → 2.0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +59 -0
- data/app/assets/javascripts/turbo.js +1991 -1914
- data/app/assets/javascripts/turbo.min.js +6 -6
- data/app/assets/javascripts/turbo.min.js.map +1 -1
- data/app/javascript/turbo/cable_stream_source_element.js +10 -0
- data/app/models/concerns/turbo/broadcastable.rb +32 -22
- data/app/models/turbo/streams/tag_builder.rb +30 -12
- data/lib/turbo/engine.rb +51 -6
- data/lib/turbo/system_test_helper.rb +128 -0
- data/lib/turbo/version.rb +1 -1
- metadata +4 -17
@@ -3,6 +3,8 @@ import { subscribeTo } from "./cable"
|
|
3
3
|
import snakeize from "./snakeize"
|
4
4
|
|
5
5
|
class TurboCableStreamSourceElement extends HTMLElement {
|
6
|
+
static observedAttributes = ["channel", "signed-stream-name"]
|
7
|
+
|
6
8
|
async connectedCallback() {
|
7
9
|
connectStreamSource(this)
|
8
10
|
this.subscription = await subscribeTo(this.channel, {
|
@@ -15,6 +17,14 @@ class TurboCableStreamSourceElement extends HTMLElement {
|
|
15
17
|
disconnectedCallback() {
|
16
18
|
disconnectStreamSource(this)
|
17
19
|
if (this.subscription) this.subscription.unsubscribe()
|
20
|
+
this.subscriptionDisconnected()
|
21
|
+
}
|
22
|
+
|
23
|
+
attributeChangedCallback() {
|
24
|
+
if (this.subscription) {
|
25
|
+
this.disconnectedCallback()
|
26
|
+
this.connectedCallback()
|
27
|
+
}
|
18
28
|
}
|
19
29
|
|
20
30
|
dispatchMessageEvent(data) {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records).
|
1
|
+
# Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records if ActiveJob is loaded).
|
2
2
|
# This makes it convenient to execute both synchronous and asynchronous updates, and render directly from callbacks in models
|
3
3
|
# or from controllers or jobs that act on those models. Here's an example:
|
4
4
|
#
|
@@ -241,13 +241,13 @@ module Turbo::Broadcastable
|
|
241
241
|
#
|
242
242
|
# # Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
|
243
243
|
# clearance.broadcast_remove_to examiner.identity, :clearances
|
244
|
-
def broadcast_remove_to(*streamables, target: self)
|
245
|
-
Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target) unless suppressed_turbo_broadcasts?
|
244
|
+
def broadcast_remove_to(*streamables, target: self, **rendering)
|
245
|
+
Turbo::StreamsChannel.broadcast_remove_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
246
246
|
end
|
247
247
|
|
248
248
|
# Same as <tt>#broadcast_remove_to</tt>, but the designated stream is automatically set to the current model.
|
249
|
-
def broadcast_remove
|
250
|
-
broadcast_remove_to self
|
249
|
+
def broadcast_remove(**rendering)
|
250
|
+
broadcast_remove_to self, **rendering
|
251
251
|
end
|
252
252
|
|
253
253
|
# Replace this broadcastable model in the dom for subscribers of the stream name identified by the passed
|
@@ -265,7 +265,7 @@ module Turbo::Broadcastable
|
|
265
265
|
# # to the stream named "identity:2:clearances"
|
266
266
|
# clearance.broadcast_replace_to examiner.identity, :clearance, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
267
267
|
def broadcast_replace_to(*streamables, **rendering)
|
268
|
-
Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self
|
268
|
+
Turbo::StreamsChannel.broadcast_replace_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
269
269
|
end
|
270
270
|
|
271
271
|
# Same as <tt>#broadcast_replace_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -288,7 +288,7 @@ module Turbo::Broadcastable
|
|
288
288
|
# # to the stream named "identity:2:clearances"
|
289
289
|
# # clearance.broadcast_update_to examiner.identity, :clearances, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
|
290
290
|
def broadcast_update_to(*streamables, **rendering)
|
291
|
-
Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self
|
291
|
+
Turbo::StreamsChannel.broadcast_update_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
292
292
|
end
|
293
293
|
|
294
294
|
# Same as <tt>#broadcast_update_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -308,8 +308,10 @@ module Turbo::Broadcastable
|
|
308
308
|
# # to the stream named "identity:2:clearances"
|
309
309
|
# clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5",
|
310
310
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
311
|
-
def broadcast_before_to(*streamables, target
|
312
|
-
|
311
|
+
def broadcast_before_to(*streamables, target: nil, targets: nil, **rendering)
|
312
|
+
raise ArgumentError, "at least one of target or targets is required" unless target || targets
|
313
|
+
|
314
|
+
Turbo::StreamsChannel.broadcast_before_to(*streamables, **extract_options_and_add_target(rendering.merge(target: target, targets: targets)))
|
313
315
|
end
|
314
316
|
|
315
317
|
# Insert a rendering of this broadcastable model after the target identified by it's dom id passed as <tt>target</tt>
|
@@ -324,8 +326,10 @@ module Turbo::Broadcastable
|
|
324
326
|
# # to the stream named "identity:2:clearances"
|
325
327
|
# clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5",
|
326
328
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
327
|
-
def broadcast_after_to(*streamables, target
|
328
|
-
|
329
|
+
def broadcast_after_to(*streamables, target: nil, targets: nil, **rendering)
|
330
|
+
raise ArgumentError, "at least one of target or targets is required" unless target || targets
|
331
|
+
|
332
|
+
Turbo::StreamsChannel.broadcast_after_to(*streamables, **extract_options_and_add_target(rendering.merge(target: target, targets: targets)))
|
329
333
|
end
|
330
334
|
|
331
335
|
# Append a rendering of this broadcastable model to the target identified by it's dom id passed as <tt>target</tt>
|
@@ -341,7 +345,7 @@ module Turbo::Broadcastable
|
|
341
345
|
# clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
|
342
346
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
343
347
|
def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
|
344
|
-
Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target
|
348
|
+
Turbo::StreamsChannel.broadcast_append_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
345
349
|
end
|
346
350
|
|
347
351
|
# Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -362,7 +366,7 @@ module Turbo::Broadcastable
|
|
362
366
|
# clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
|
363
367
|
# partial: "clearances/other_partial", locals: { a: 1 }
|
364
368
|
def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
|
365
|
-
Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target
|
369
|
+
Turbo::StreamsChannel.broadcast_prepend_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
366
370
|
end
|
367
371
|
|
368
372
|
# Same as <tt>#broadcast_prepend_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -389,7 +393,7 @@ module Turbo::Broadcastable
|
|
389
393
|
# # to the stream named "identity:2:clearances"
|
390
394
|
# clearance.broadcast_action_to examiner.identity, :clearances, action: :prepend, target: "clearances"
|
391
395
|
def broadcast_action_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
|
392
|
-
Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action,
|
396
|
+
Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, attributes: attributes, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
393
397
|
end
|
394
398
|
|
395
399
|
# Same as <tt>#broadcast_action_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -399,7 +403,7 @@ module Turbo::Broadcastable
|
|
399
403
|
|
400
404
|
# Same as <tt>broadcast_replace_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
401
405
|
def broadcast_replace_later_to(*streamables, **rendering)
|
402
|
-
Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self
|
406
|
+
Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
403
407
|
end
|
404
408
|
|
405
409
|
# Same as <tt>#broadcast_replace_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -409,7 +413,7 @@ module Turbo::Broadcastable
|
|
409
413
|
|
410
414
|
# Same as <tt>broadcast_update_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
411
415
|
def broadcast_update_later_to(*streamables, **rendering)
|
412
|
-
Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self
|
416
|
+
Turbo::StreamsChannel.broadcast_update_later_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
413
417
|
end
|
414
418
|
|
415
419
|
# Same as <tt>#broadcast_update_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -419,7 +423,7 @@ module Turbo::Broadcastable
|
|
419
423
|
|
420
424
|
# Same as <tt>broadcast_append_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
421
425
|
def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
|
422
|
-
Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target
|
426
|
+
Turbo::StreamsChannel.broadcast_append_later_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
423
427
|
end
|
424
428
|
|
425
429
|
# Same as <tt>#broadcast_append_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -429,7 +433,7 @@ module Turbo::Broadcastable
|
|
429
433
|
|
430
434
|
# Same as <tt>broadcast_prepend_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
431
435
|
def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
|
432
|
-
Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target
|
436
|
+
Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
433
437
|
end
|
434
438
|
|
435
439
|
# Same as <tt>#broadcast_prepend_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -449,7 +453,7 @@ module Turbo::Broadcastable
|
|
449
453
|
|
450
454
|
# Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
451
455
|
def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
|
452
|
-
Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action,
|
456
|
+
Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, attributes: attributes, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
|
453
457
|
end
|
454
458
|
|
455
459
|
# Same as <tt>#broadcast_action_later_to</tt>, but the designated stream is automatically set to the current model.
|
@@ -485,7 +489,7 @@ module Turbo::Broadcastable
|
|
485
489
|
# desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
|
486
490
|
# be using +broadcast_render_later_to+, unless you specifically know why synchronous rendering is needed.
|
487
491
|
def broadcast_render_to(*streamables, **rendering)
|
488
|
-
Turbo::StreamsChannel.broadcast_render_to(*streamables, **
|
492
|
+
Turbo::StreamsChannel.broadcast_render_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
|
489
493
|
end
|
490
494
|
|
491
495
|
# Same as <tt>broadcast_render_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
|
@@ -496,7 +500,7 @@ module Turbo::Broadcastable
|
|
496
500
|
# Same as <tt>broadcast_render_later</tt> but run with the added option of naming the stream using the passed
|
497
501
|
# <tt>streamables</tt>.
|
498
502
|
def broadcast_render_later_to(*streamables, **rendering)
|
499
|
-
Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **
|
503
|
+
Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **extract_options_and_add_target(rendering)) unless suppressed_turbo_broadcasts?
|
500
504
|
end
|
501
505
|
|
502
506
|
private
|
@@ -504,11 +508,17 @@ module Turbo::Broadcastable
|
|
504
508
|
self.class.broadcast_target_default
|
505
509
|
end
|
506
510
|
|
511
|
+
def extract_options_and_add_target(rendering = {}, target: broadcast_target_default)
|
512
|
+
broadcast_rendering_with_defaults(rendering).tap do |options|
|
513
|
+
options[:target] = target if !options.key?(:target) && !options.key?(:targets)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
507
517
|
def broadcast_rendering_with_defaults(options)
|
508
518
|
options.tap do |o|
|
509
519
|
# Add the current instance into the locals with the element name (which is the un-namespaced name)
|
510
520
|
# as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
|
511
|
-
o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self
|
521
|
+
o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self).compact
|
512
522
|
|
513
523
|
if o[:html] || o[:partial]
|
514
524
|
return o
|
@@ -77,8 +77,9 @@ class Turbo::Streams::TagBuilder
|
|
77
77
|
# <%= turbo_stream.replace "clearance_5" do %>
|
78
78
|
# <div id='clearance_5'>Replace the dom target identified by clearance_5</div>
|
79
79
|
# <% end %>
|
80
|
-
|
81
|
-
|
80
|
+
# <%= turbo_stream.replace clearance, "<div>Morph the dom target</div>", method: :morph %>
|
81
|
+
def replace(target, content = nil, method: nil, **rendering, &block)
|
82
|
+
action :replace, target, content, method: method, **rendering, &block
|
82
83
|
end
|
83
84
|
|
84
85
|
# Replace the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in, a rendering result determined
|
@@ -90,8 +91,9 @@ class Turbo::Streams::TagBuilder
|
|
90
91
|
# <%= turbo_stream.replace_all ".clearance_item" do %>
|
91
92
|
# <div class='.clearance_item'>Replace the dom target identified by the class clearance_item</div>
|
92
93
|
# <% end %>
|
93
|
-
|
94
|
-
|
94
|
+
# <%= turbo_stream.replace_all clearance, "<div>Morph the dom target</div>", method: :morph %>
|
95
|
+
def replace_all(targets, content = nil, method: nil, **rendering, &block)
|
96
|
+
action_all :replace, targets, content, method: method, **rendering, &block
|
95
97
|
end
|
96
98
|
|
97
99
|
# Insert the <tt>content</tt> passed in, a rendering result determined by the <tt>rendering</tt> keyword arguments,
|
@@ -155,8 +157,9 @@ class Turbo::Streams::TagBuilder
|
|
155
157
|
# <%= turbo_stream.update "clearance_5" do %>
|
156
158
|
# Update the content of the dom target identified by clearance_5
|
157
159
|
# <% end %>
|
158
|
-
|
159
|
-
|
160
|
+
# <%= turbo_stream.update clearance, "<div>Morph the dom target</div>", method: :morph %>
|
161
|
+
def update(target, content = nil, method: nil, **rendering, &block)
|
162
|
+
action :update, target, content, method: method, **rendering, &block
|
160
163
|
end
|
161
164
|
|
162
165
|
# Update the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
|
@@ -168,8 +171,9 @@ class Turbo::Streams::TagBuilder
|
|
168
171
|
# <%= turbo_stream.update_all "clearance_item" do %>
|
169
172
|
# Update the content of the dom target identified by the class clearance_item
|
170
173
|
# <% end %>
|
171
|
-
|
172
|
-
|
174
|
+
# <%= turbo_stream.update_all clearance, "<div>Morph the dom target</div>", method: :morph %>
|
175
|
+
def update_all(targets, content = nil, method: nil, **rendering, &block)
|
176
|
+
action_all :update, targets, content, method: method, **rendering, &block
|
173
177
|
end
|
174
178
|
|
175
179
|
# Append to the target in the dom identified with <tt>target</tt> either the <tt>content</tt> passed in or a
|
@@ -228,23 +232,37 @@ class Turbo::Streams::TagBuilder
|
|
228
232
|
action_all :prepend, targets, content, **rendering, &block
|
229
233
|
end
|
230
234
|
|
235
|
+
# Creates a `turbo-stream` tag with an `[action="refresh"`] attribute and a
|
236
|
+
# `[request-id]` attribute that defaults to `Turbo.current_request_id`:
|
237
|
+
#
|
238
|
+
# turbo_stream.refresh
|
239
|
+
# # => <turbo-stream action="refresh" request-id="ef083d55-7516-41b1-ad28-16f553399c6a"></turbo-stream>
|
240
|
+
#
|
241
|
+
# turbo_stream.refresh request_id: "abc123"
|
242
|
+
# # => <turbo-stream action="refresh" request-id="abc123"></turbo-stream>
|
243
|
+
def refresh(...)
|
244
|
+
turbo_stream_refresh_tag(...)
|
245
|
+
end
|
246
|
+
|
231
247
|
# Send an action of the type <tt>name</tt> to <tt>target</tt>. Options described in the concrete methods.
|
232
|
-
def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
248
|
+
def action(name, target, content = nil, method: nil, allow_inferred_rendering: true, **rendering, &block)
|
233
249
|
template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
|
234
250
|
|
235
|
-
turbo_stream_action_tag name, target: target, template: template
|
251
|
+
turbo_stream_action_tag name, target: target, template: template, method: method
|
236
252
|
end
|
237
253
|
|
238
254
|
# Send an action of the type <tt>name</tt> to <tt>targets</tt>. Options described in the concrete methods.
|
239
|
-
def action_all(name, targets, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
255
|
+
def action_all(name, targets, content = nil, method: nil, allow_inferred_rendering: true, **rendering, &block)
|
240
256
|
template = render_template(targets, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
|
241
257
|
|
242
|
-
turbo_stream_action_tag name, targets: targets, template: template
|
258
|
+
turbo_stream_action_tag name, targets: targets, template: template, method: method
|
243
259
|
end
|
244
260
|
|
245
261
|
private
|
246
262
|
def render_template(target, content = nil, allow_inferred_rendering: true, **rendering, &block)
|
247
263
|
case
|
264
|
+
when target.respond_to?(:render_in) && content.nil?
|
265
|
+
target.render_in(@view_context, &block)
|
248
266
|
when content.respond_to?(:render_in)
|
249
267
|
content.render_in(@view_context, &block)
|
250
268
|
when content
|
data/lib/turbo/engine.rb
CHANGED
@@ -5,6 +5,7 @@ module Turbo
|
|
5
5
|
isolate_namespace Turbo
|
6
6
|
config.eager_load_namespaces << Turbo
|
7
7
|
config.turbo = ActiveSupport::OrderedOptions.new
|
8
|
+
config.turbo.test_connect_after_actions = %i[visit]
|
8
9
|
config.autoload_once_paths = %W(
|
9
10
|
#{root}/app/channels
|
10
11
|
#{root}/app/controllers
|
@@ -15,6 +16,27 @@ module Turbo
|
|
15
16
|
#{root}/app/jobs
|
16
17
|
)
|
17
18
|
|
19
|
+
# If the parent application does not use Active Job, app/jobs cannot
|
20
|
+
# be eager loaded, because it references the ActiveJob constant.
|
21
|
+
#
|
22
|
+
# When turbo-rails depends on Rails 7 or above, the entire block can be
|
23
|
+
# reduced to
|
24
|
+
#
|
25
|
+
# unless defined?(ActiveJob)
|
26
|
+
# Rails.autoloaders.once.do_not_eager_load("#{root}/app/jobs")
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
initializer "turbo.no_active_job", before: :set_eager_load_paths do
|
30
|
+
unless defined?(ActiveJob)
|
31
|
+
if Rails.autoloaders.zeitwerk_enabled?
|
32
|
+
Rails.autoloaders.once.do_not_eager_load("#{root}/app/jobs")
|
33
|
+
else
|
34
|
+
# This else branch only runs in Rails 6.x + classic mode.
|
35
|
+
config.eager_load_paths.delete("#{root}/app/jobs")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
18
40
|
# If the parent application does not use Action Cable, app/channels cannot
|
19
41
|
# be eager loaded, because it references the ActionCable constant.
|
20
42
|
#
|
@@ -70,8 +92,10 @@ module Turbo
|
|
70
92
|
end
|
71
93
|
|
72
94
|
initializer "turbo.broadcastable" do
|
73
|
-
ActiveSupport.on_load(:
|
74
|
-
|
95
|
+
ActiveSupport.on_load(:active_job) do
|
96
|
+
ActiveSupport.on_load(:active_record) do
|
97
|
+
include Turbo::Broadcastable
|
98
|
+
end
|
75
99
|
end
|
76
100
|
end
|
77
101
|
|
@@ -99,10 +123,12 @@ module Turbo
|
|
99
123
|
include Turbo::TestAssertions
|
100
124
|
end
|
101
125
|
|
102
|
-
ActiveSupport.on_load(:
|
103
|
-
ActiveSupport.on_load(:
|
104
|
-
|
105
|
-
|
126
|
+
ActiveSupport.on_load(:active_job) do
|
127
|
+
ActiveSupport.on_load(:action_cable) do
|
128
|
+
ActiveSupport.on_load(:active_support_test_case) do
|
129
|
+
require "turbo/broadcastable/test_helper"
|
130
|
+
include Turbo::Broadcastable::TestHelper
|
131
|
+
end
|
106
132
|
end
|
107
133
|
end
|
108
134
|
|
@@ -126,5 +152,24 @@ module Turbo
|
|
126
152
|
end
|
127
153
|
end
|
128
154
|
end
|
155
|
+
|
156
|
+
initializer "turbo.system_test_helper" do
|
157
|
+
ActiveSupport.on_load(:action_dispatch_system_test_case) do
|
158
|
+
require "turbo/system_test_helper"
|
159
|
+
include Turbo::SystemTestHelper
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
config.after_initialize do |app|
|
164
|
+
ActiveSupport.on_load(:action_dispatch_system_test_case) do
|
165
|
+
app.config.turbo.test_connect_after_actions.map do |method|
|
166
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
167
|
+
def #{method}(...) # def visit(...)
|
168
|
+
super.tap { connect_turbo_cable_stream_sources } # super.tap { connect_turbo_cable_stream_sources }
|
169
|
+
end # end
|
170
|
+
RUBY
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
129
174
|
end
|
130
175
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Turbo::SystemTestHelper
|
2
|
+
# Delay until every `<turbo-cable-stream-source>` element present in the page
|
3
|
+
# is ready to receive broadcasts
|
4
|
+
#
|
5
|
+
# test "renders broadcasted Messages" do
|
6
|
+
# message = Message.new content: "Hello, from Action Cable"
|
7
|
+
#
|
8
|
+
# visit "/"
|
9
|
+
# click_link "All Messages"
|
10
|
+
# message.save! # execute server-side code to broadcast a Message
|
11
|
+
#
|
12
|
+
# assert_text message.content
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# By default, calls to `#visit` will wait for all `<turbo-cable-stream-source>`
|
16
|
+
# elements to connect. You can control this by modifying the
|
17
|
+
# `config.turbo.test_connect_after_actions`. For example, to wait after calls to
|
18
|
+
# `#click_link`, add the following to `config/environments/test.rb`:
|
19
|
+
#
|
20
|
+
# # config/environments/test.rb
|
21
|
+
# config.turbo.test_connect_after_actions << :click_link
|
22
|
+
#
|
23
|
+
# To disable automatic connecting, set the configuration to `[]`:
|
24
|
+
#
|
25
|
+
# # config/environments/test.rb
|
26
|
+
# config.turbo.test_connect_after_actions = []
|
27
|
+
#
|
28
|
+
def connect_turbo_cable_stream_sources(**options, &block)
|
29
|
+
all(:turbo_cable_stream_source, **options, connected: false, wait: 0).each do |element|
|
30
|
+
element.assert_matches_selector(:turbo_cable_stream_source, **options, connected: true, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Asserts that a `<turbo-cable-stream-source>` element is present in the
|
35
|
+
# document
|
36
|
+
#
|
37
|
+
# ==== Arguments
|
38
|
+
#
|
39
|
+
# * <tt>locator</tt> optional locator to determine the element's
|
40
|
+
# `[signed-stream-name]` attribute. Can be of any type that is a valid
|
41
|
+
# argument to <tt>Turbo::Streams::StreamName#signed_stream_name</tt>.
|
42
|
+
#
|
43
|
+
# ==== Options
|
44
|
+
#
|
45
|
+
# * <tt>:connected</tt> matches the `[connected]` attribute
|
46
|
+
# * <tt>:channel</tt> matches the `[channel]` attribute. Can be a Class,
|
47
|
+
# String, Symbol, or Regexp
|
48
|
+
# * <tt>:signed_stream_name</tt> matches the element's `[signed-stream-name]`
|
49
|
+
# attribute. Can be of any type that is a valid
|
50
|
+
# argument to <tt>Turbo::Streams::StreamName#signed_stream_name</tt>.
|
51
|
+
#
|
52
|
+
# In addition to the filters listed above, accepts any valid Capybara global
|
53
|
+
# filter option.
|
54
|
+
def assert_turbo_cable_stream_source(...)
|
55
|
+
assert_selector(:turbo_cable_stream_source, ...)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Asserts that a `<turbo-cable-stream-source>` element is absent from the
|
59
|
+
# document
|
60
|
+
#
|
61
|
+
# ==== Arguments
|
62
|
+
#
|
63
|
+
# * <tt>locator</tt> optional locator to determine the element's
|
64
|
+
# `[signed-stream-name]` attribute. Can be of any type that is a valid
|
65
|
+
# argument to <tt>Turbo::Streams::StreamName#signed_stream_name</tt>.
|
66
|
+
#
|
67
|
+
# ==== Options
|
68
|
+
#
|
69
|
+
# * <tt>:connected</tt> matches the `[connected]` attribute
|
70
|
+
# * <tt>:channel</tt> matches the `[channel]` attribute. Can be a Class,
|
71
|
+
# String, Symbol, or Regexp
|
72
|
+
# * <tt>:signed_stream_name</tt> matches the element's `[signed-stream-name]`
|
73
|
+
# attribute. Can be of any type that is a valid
|
74
|
+
# argument to <tt>Turbo::Streams::StreamName#signed_stream_name</tt>.
|
75
|
+
#
|
76
|
+
# In addition to the filters listed above, accepts any valid Capybara global
|
77
|
+
# filter option.
|
78
|
+
def assert_no_turbo_cable_stream_source(...)
|
79
|
+
assert_no_selector(:turbo_cable_stream_source, ...)
|
80
|
+
end
|
81
|
+
|
82
|
+
Capybara.add_selector :turbo_cable_stream_source do
|
83
|
+
xpath do |locator|
|
84
|
+
xpath = XPath.descendant.where(XPath.local_name == "turbo-cable-stream-source")
|
85
|
+
xpath.where(SignedStreamNameConditions.new(locator).reduce(:|))
|
86
|
+
end
|
87
|
+
|
88
|
+
expression_filter :connected do |xpath, value|
|
89
|
+
builder(xpath).add_attribute_conditions(connected: value)
|
90
|
+
end
|
91
|
+
|
92
|
+
expression_filter :channel do |xpath, value|
|
93
|
+
builder(xpath).add_attribute_conditions(channel: value.try(:name) || value)
|
94
|
+
end
|
95
|
+
|
96
|
+
expression_filter :signed_stream_name do |xpath, value|
|
97
|
+
case value
|
98
|
+
when TrueClass, FalseClass, NilClass, Regexp
|
99
|
+
builder(xpath).add_attribute_conditions("signed-stream-name": value)
|
100
|
+
else
|
101
|
+
xpath.where(SignedStreamNameConditions.new(value).reduce(:|))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class SignedStreamNameConditions # :nodoc:
|
107
|
+
include Turbo::Streams::StreamName, Enumerable
|
108
|
+
|
109
|
+
def initialize(value)
|
110
|
+
@value = value
|
111
|
+
end
|
112
|
+
|
113
|
+
def attribute
|
114
|
+
XPath.attr(:"signed-stream-name")
|
115
|
+
end
|
116
|
+
|
117
|
+
def each
|
118
|
+
if @value.is_a?(String)
|
119
|
+
yield attribute == @value
|
120
|
+
yield attribute == signed_stream_name(@value)
|
121
|
+
elsif @value.is_a?(Array) || @value.respond_to?(:to_key)
|
122
|
+
yield attribute == signed_stream_name(@value)
|
123
|
+
elsif @value.present?
|
124
|
+
yield attribute == @value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/turbo/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbo-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Stephenson
|
@@ -10,22 +10,8 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-09-
|
13
|
+
date: 2024-09-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
|
-
- !ruby/object:Gem::Dependency
|
16
|
-
name: activejob
|
17
|
-
requirement: !ruby/object:Gem::Requirement
|
18
|
-
requirements:
|
19
|
-
- - ">="
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: 6.0.0
|
22
|
-
type: :runtime
|
23
|
-
prerelease: false
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
requirements:
|
26
|
-
- - ">="
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
version: 6.0.0
|
29
15
|
- !ruby/object:Gem::Dependency
|
30
16
|
name: actionpack
|
31
17
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,6 +86,7 @@ files:
|
|
100
86
|
- lib/turbo-rails.rb
|
101
87
|
- lib/turbo/broadcastable/test_helper.rb
|
102
88
|
- lib/turbo/engine.rb
|
89
|
+
- lib/turbo/system_test_helper.rb
|
103
90
|
- lib/turbo/test_assertions.rb
|
104
91
|
- lib/turbo/test_assertions/integration_test_assertions.rb
|
105
92
|
- lib/turbo/version.rb
|
@@ -123,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
110
|
- !ruby/object:Gem::Version
|
124
111
|
version: '0'
|
125
112
|
requirements: []
|
126
|
-
rubygems_version: 3.5.
|
113
|
+
rubygems_version: 3.5.11
|
127
114
|
signing_key:
|
128
115
|
specification_version: 4
|
129
116
|
summary: The speed of a single-page web application without having to write any JavaScript.
|