Skip to content

Commit 4ab5b60

Browse files
committed
Add tests for targets and target collections
1 parent 8cfba5d commit 4ab5b60

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { html, fixture, assert } from '@open-wc/testing'
2+
3+
import { extractTargets } from '../attributes'
4+
import Schema, { defaultSchema } from '../schema'
5+
6+
Schema.set(defaultSchema)
7+
8+
describe('extractTargets', () => {
9+
it('should extract no targets by default', async () => {
10+
const dom = await fixture(
11+
html`<div data-reflex-target="post">Post</div>`
12+
)
13+
const targets = extractTargets(undefined, null)
14+
assert.deepStrictEqual(targets, {})
15+
})
16+
17+
it('should extract multiple targets from page', async () => {
18+
const dom = await fixture(
19+
html`
20+
<div data-reflex-target="post">Post</div>
21+
<div data-reflex-target="comment" class="comment-1">Comment 1</div>
22+
<div data-reflex-target="comment" class="comment-2">Comment 2</div>
23+
`
24+
)
25+
const targets = extractTargets("page", null)
26+
27+
assert.equal(targets["post"][0]["name"], "post")
28+
assert.equal(targets["post"][0]["selector"], "/html/body/div[1]/div[1]")
29+
assert.equal(targets["post"][0]["attrs"]["data-reflex-target"], "post")
30+
31+
assert.equal(targets["comment"][0]["name"], "comment")
32+
assert.equal(targets["comment"][0]["selector"], "/html/body/div[1]/div[2]")
33+
assert.equal(targets["comment"][0]["attrs"]["data-reflex-target"], "comment")
34+
assert.equal(targets["comment"][0]["attrs"]["class"], "comment-1")
35+
36+
assert.equal(targets["comment"][1]["name"], "comment")
37+
assert.equal(targets["comment"][1]["selector"], "/html/body/div[1]/div[3]")
38+
assert.equal(targets["comment"][1]["attrs"]["data-reflex-target"], "comment")
39+
assert.equal(targets["comment"][1]["attrs"]["class"], "comment-2")
40+
})
41+
42+
it('should limit targets to parent controller if specified', async () => {
43+
const controller = await fixture( // Note: fixture() returns the first element in the DOM
44+
html`
45+
<div data-controller="test">
46+
<div data-reflex-target="included">In</div>
47+
</div>
48+
<div data-reflex-target="not_included">Out</div>
49+
`
50+
)
51+
52+
const targets = extractTargets("controller", controller)
53+
54+
assert.equal(targets["included"][0]["name"], "included")
55+
assert.equal(targets["included"][0]["selector"], "/html/body/div[1]/div[1]/div[1]")
56+
assert.equal(targets["not_included"], undefined)
57+
})
58+
})

test/reflex_targets_test.rb

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "test_helper"
4+
require "mocha/minitest"
5+
6+
class StimulusReflex::ReflexTargetsTest < ActionCable::Channel::TestCase
7+
tests StimulusReflex::Channel
8+
9+
attr_reader :reflex
10+
delegate :post_targets, :button_target, :absent_target, :unicorn_targets, :cable_ready, to: :reflex
11+
12+
setup do
13+
stub_connection(session_id: SecureRandom.uuid)
14+
def connection.env
15+
@env ||= {}
16+
end
17+
18+
@element = StimulusReflex::Element.new({}, selector: "/html/body/button[1]")
19+
@reflex = build_with_targets
20+
end
21+
22+
def build_with_targets(targets_data: nil, target_scope: "page")
23+
targets_data ||= {
24+
"post" => [
25+
{ "name" => "post", "selector" => "/html/body/div[1]", "attrs" => { "class" => "" } },
26+
{ "name" => "post", "selector" => "/html/body/div[2]", "attrs" => { "class" => "special" } },
27+
{ "name" => "post", "selector" => "/html/body/div[3]", "attrs" => { "class" => "special" } }
28+
],
29+
"button" => [
30+
{ "name" => "button", "selector" => "/html/body/button[1]", "dataset" => {} }
31+
]
32+
}
33+
34+
reflex_data = StimulusReflex::ReflexData.new(
35+
element: @element,
36+
url: "https://test.stimulusreflex.com",
37+
targets: targets_data,
38+
id: "123",
39+
version: StimulusReflex::VERSION,
40+
reflex_controller: "stimulus-reflex",
41+
target_scope: target_scope
42+
)
43+
44+
StimulusReflex::Reflex.new(subscribe, reflex_data: reflex_data)
45+
end
46+
47+
def build_payload(operations = [])
48+
{
49+
"cableReady" => true,
50+
"operations" => Array.wrap(operations),
51+
"version" => CableReady::VERSION
52+
}
53+
end
54+
55+
test "shares a cable_ready instance with targets and target collections" do
56+
assert_equal reflex.cable_ready, button_target.cable_ready
57+
assert_equal reflex.cable_ready, post_targets.cable_ready
58+
assert_equal reflex.cable_ready, post_targets.first.cable_ready
59+
end
60+
61+
test "builds chainable operations on a (singular) target" do
62+
expected = build_payload(
63+
[
64+
{"selector" => "/html/body/button[1]", "xpath" => true, "name" => "updated", "reflexId" => "123", "operation" => "addCssClass"},
65+
{"selector" => "/html/body/button[1]", "xpath" => true, "text" => "Button", "reflexId" => "123", "operation" => "textContent"}
66+
]
67+
)
68+
69+
assert_broadcast_on(reflex.stream_name, expected) do
70+
button_target.add_css_class(name: "updated").text_content(text: "Button")
71+
72+
reflex.cable_ready.broadcast
73+
end
74+
end
75+
76+
test "builds chainable operations on (plural) multi-target collection using select_all" do
77+
expected = build_payload(
78+
[
79+
{"selector" => "[data-reflex-target='post']", "selectAll" => true, "name" => "updated", "reflexId" => "123", "operation" => "addCssClass"},
80+
{"selector" => "[data-reflex-target='post']", "selectAll" => true, "text" => "Post", "reflexId" => "123", "operation" => "textContent"}
81+
]
82+
)
83+
84+
assert_broadcast_on(reflex.stream_name, expected) do
85+
post_targets.add_css_class(name: "updated").text_content(text: "Post")
86+
87+
reflex.cable_ready.broadcast
88+
end
89+
end
90+
91+
test "target collections respond to array-like interface" do
92+
assert_equal post_targets.any?, true
93+
assert_equal post_targets.many?, true
94+
assert_equal post_targets.count, 3
95+
assert_equal post_targets.first.selector, "/html/body/div[1]"
96+
assert_equal post_targets.last.selector, "/html/body/div[3]"
97+
98+
special_targets = post_targets.select{ |target| target.attrs[:class].include?("special") }
99+
100+
assert_equal special_targets.count, 2
101+
assert_equal special_targets.first.selector, "/html/body/div[2]"
102+
assert_equal special_targets.last.selector, "/html/body/div[3]"
103+
end
104+
105+
test "doesn't raise an exception / halt execution if operation(s) are called on a missing target" do
106+
expected = build_payload(
107+
[
108+
{"selector" => "/html/body/button[1]", "xpath" => true, "name" => "success", "reflexId" => "123", "operation" => "addCssClass"},
109+
{"selector" => "/html/body/button[1]", "xpath" => true, "text" => "I'm still updated!", "reflexId" => "123", "operation" => "textContent"}
110+
]
111+
)
112+
113+
assert_broadcast_on(reflex.stream_name, expected) do
114+
absent_target.add_css_class(name: "nope").text_content(text: "I'm not even here!")
115+
button_target.add_css_class(name: "success").text_content(text: "I'm still updated!")
116+
117+
reflex.cable_ready.broadcast
118+
end
119+
end
120+
121+
test "missing/undefined targets that *might* exist but are currently not in the DOM still respond to inspection" do
122+
assert_equal absent_target.any?, false
123+
assert_equal absent_target.present?, false
124+
assert_equal unicorn_targets.count, 0
125+
assert_equal unicorn_targets.first.present?, false
126+
assert_equal unicorn_targets.any?, false
127+
end
128+
129+
test "targets in a multi-target collection can also be operated on individually" do
130+
expected = build_payload(
131+
[
132+
{"selector" => "/html/body/div[2]", "xpath" => true, "name" => "upgrade", "reflexId" => "123", "operation" => "addCssClass"},
133+
{"selector" => "/html/body/div[3]", "xpath" => true, "name" => "upgrade", "reflexId" => "123", "operation" => "addCssClass"},
134+
{"selector" => "/html/body/div[1]", "xpath" => true, "name" => "downgrade", "reflexId" => "123", "operation" => "addCssClass"}
135+
]
136+
)
137+
138+
assert_broadcast_on(reflex.stream_name, expected) do
139+
post_targets
140+
.select{ |target| target.attrs[:class].include?("special") }
141+
.each{ |target| target.add_css_class(name: "upgrade") }
142+
143+
post_targets
144+
.find{ |target| target.attrs[:class].blank? }
145+
.add_css_class(name: "downgrade")
146+
147+
reflex.cable_ready.broadcast
148+
end
149+
end
150+
151+
test "plays nicely with other operations interspersed" do
152+
expected = build_payload(
153+
[
154+
{"selector" => "[data-reflex-target='post']", "selectAll" => true, "name" => "hey", "reflexId" => "123", "operation" => "addCssClass"},
155+
{"selector" => "#other", "name" => "thing", "reflexId" => "123", "operation" => "addCssClass"},
156+
{"selector" => "[data-reflex-target='post']", "selectAll" => true, "text" => "I'm a Post", "reflexId" => "123", "operation" => "textContent"}
157+
]
158+
)
159+
160+
assert_broadcast_on(reflex.stream_name, expected) do
161+
post_targets.add_css_class(name: "hey")
162+
cable_ready.add_css_class(selector: "#other", name: "thing")
163+
post_targets.text_content(text: "I'm a Post")
164+
165+
reflex.cable_ready.broadcast
166+
end
167+
end
168+
end

0 commit comments

Comments
 (0)