Skip to content

Commit 0aad1ef

Browse files
committed
Merge branch 'feat/react17-cell' into 'edge'
Add the React 17 cell to edge's matrix (hyperstack-org#51) See merge request ru/hyperstack!65
2 parents d250306 + 200d604 commit 0aad1ef

6 files changed

Lines changed: 77 additions & 18 deletions

File tree

‎.gitlab-ci.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ stages:
135135
# `rake hyperstack:matrix:check` fails if this list and the table disagree. (#51)
136136
parallel:
137137
matrix:
138-
- HYPERSTACK_CELL: [rails61-react16]
138+
- HYPERSTACK_CELL: [rails61-react16, rails61-react17]
139139

140140
before_script:
141141
# - sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf
@@ -196,7 +196,7 @@ stages:
196196
# `rake hyperstack:matrix:check` fails if this list and the table disagree. (#51)
197197
parallel:
198198
matrix:
199-
- HYPERSTACK_CELL: [rails61-react16]
199+
- HYPERSTACK_CELL: [rails61-react16, rails61-react17]
200200

201201
# language: ruby
202202
# cache:

‎ruby/hyper-component/spec/client_features/component_spec.rb‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,16 @@ def self.get_info
163163
end
164164
end
165165
expect_evaluate_ruby('Foo.get_error').to eq('ErrorFoo Error')
166-
expect_evaluate_ruby('Foo.get_info').to eq("\n in ErrorFoo (created by Foo)\n in div (created by Foo)\n in Foo (created by Hyperstack::Internal::Component::TopLevelRailsComponent)\n in Hyperstack::Internal::Component::TopLevelRailsComponent")
166+
# React 17 replaced the synthetic component stack ("in Foo (created by Bar)")
167+
# with native error frames, which name files rather than components. What the
168+
# callback guarantees on every version is that it receives a non-empty
169+
# componentStack; only React <= 16 can be asserted component by component. (#51)
170+
expect_evaluate_ruby('Foo.get_info.is_a?(String) && !Foo.get_info.empty?').to be_truthy
171+
if react_version_major <= 16
172+
expect_evaluate_ruby('Foo.get_info').to eq("\n in ErrorFoo (created by Foo)\n in div (created by Foo)\n in Foo (created by Hyperstack::Internal::Component::TopLevelRailsComponent)\n in Hyperstack::Internal::Component::TopLevelRailsComponent")
173+
else
174+
expect_evaluate_ruby('Foo.get_info').to match(/ErrorFoo|at eval|factory\.js/)
175+
end
167176
end
168177
end
169178

@@ -546,7 +555,7 @@ class Lorem; end
546555
end
547556
Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo, bar: 10, lorem: Lorem.new)
548557
end
549-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
558+
expect(console_messages)
550559
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/)
551560
end
552561

@@ -564,7 +573,7 @@ class Lorem; end
564573
end
565574
Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo, foo: 10, bar: '10', lorem: Lorem.new)
566575
end
567-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")).to_not match(/prop/)
576+
expect(console_messages).to_not match(/prop/)
568577
end
569578
end
570579

@@ -616,7 +625,7 @@ class Foo < Hyperloop::Component
616625
render { Foo }
617626
end
618627
end
619-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
628+
expect(console_messages)
620629
.to match(/did you mean to say Foo()/)
621630
end
622631
end

‎ruby/hyper-component/spec/client_features/param_declaration_spec.rb‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class Foo < Hyperloop::Component
125125
end
126126
end
127127
expect(page.body[-60..-19]).to include('<div>12-string</div>')
128-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
128+
expect(console_messages)
129129
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo1` could not be converted to String/)
130130
end
131131

@@ -155,7 +155,7 @@ class Foo2 < Hyperloop::Component
155155

156156
Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo2, bar: 10, lorem: Lorem.new)
157157
end
158-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
158+
expect(console_messages)
159159
.to match(/Warning: Failed prop( type|Type): In component `Foo2`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/)
160160
end
161161

@@ -191,7 +191,7 @@ class Foo < Hyperloop::Component
191191
param :bar, type: []
192192
end
193193
end
194-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
194+
expect(console_messages)
195195
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to Array/)
196196
end
197197

@@ -202,7 +202,7 @@ class Foo < Hyperloop::Component
202202
param :bar, type: [String]
203203
end
204204
end
205-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
205+
expect(console_messages)
206206
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo`\[0\] could not be converted to String/)
207207
end
208208

@@ -227,7 +227,7 @@ def self._react_param_conversion(json, validate_only)
227227
end
228228
end
229229
expect(page.body[-60..-19]).to include('<span>1, 2</span>')
230-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
230+
expect(console_messages)
231231
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle/)
232232
end
233233

‎ruby/hyper-component/spec/deprecated_features/param_declaration_legacy_spec.rb‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def setup
4949
element = Hyperstack::Component::ReactAPI.create_element(Foo).on(:foo_submit) { 'bar' }
5050
Hyperstack::Component::ReactTestUtils.render_into_document(element)
5151
end
52-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
52+
expect(console_messages)
5353
.to_not match(/Exception raised/)
5454
end
5555

@@ -73,7 +73,7 @@ def setup
7373
element = Hyperstack::Component::ReactAPI.create_element(Foo).on(:foo_invoked) { [[1, 2, 3], 'bar'] }
7474
Hyperstack::Component::ReactTestUtils.render_into_document(element)
7575
end
76-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
76+
expect(console_messages)
7777
.to_not match(/Exception raised/)
7878
end
7979
end
@@ -199,7 +199,7 @@ class Foo < Hyperloop::Component
199199
end
200200
end
201201
expect(page.body[-60..-19]).to include('<div>12-string</div>')
202-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
202+
expect(console_messages)
203203
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo1` could not be converted to String/)
204204
end
205205

@@ -216,7 +216,7 @@ class Foo2 < Hyperloop::Component
216216

217217
Hyperstack::Component::ReactTestUtils.render_component_into_document(Foo2, bar: 10, lorem: Lorem.new)
218218
end
219-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
219+
expect(console_messages)
220220
.to match(/Warning: Failed prop( type|Type): In component `Foo2`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/)
221221
end
222222

@@ -254,7 +254,7 @@ class Foo < Hyperloop::Component
254254
param :bar, type: []
255255
end
256256
end
257-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
257+
expect(console_messages)
258258
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to Array/)
259259
end
260260

@@ -265,7 +265,7 @@ class Foo < Hyperloop::Component
265265
param :bar, type: [String]
266266
end
267267
end
268-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
268+
expect(console_messages)
269269
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo`\[0\] could not be converted to String/)
270270
end
271271

@@ -290,7 +290,7 @@ def self._react_param_conversion(json, validate_only)
290290
end
291291
end
292292
expect(page.body[-60..-19]).to include('<span>1, 2</span>')
293-
expect(page.driver.browser.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
293+
expect(console_messages)
294294
.to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle/)
295295
end
296296

‎ruby/hyper-component/spec/spec_helper.rb‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,38 @@ class JavaScriptError < StandardError; end
9090
# end
9191
# end
9292
# end
93+
94+
# React <= 16 interpolated its warnings before handing them to console.error, so
95+
# Chrome captured one readable sentence. React 17 passes the *format string* and
96+
# its arguments separately instead:
97+
#
98+
# "Warning: Failed %s type: %s%s" "prop" "In component `Foo` ..." " at eval ..."
99+
#
100+
# Same information, different shape. Re-interpolating here lets the assertions be
101+
# written once, against the readable form, on either React version -- rather than
102+
# teaching every expectation about both. (#51)
103+
def console_messages
104+
page.driver.browser.logs.get(:browser)
105+
.map { |m| expand_console_format(m.message.gsub(/\\n/, "\n")) }
106+
.to_a.join("\n")
107+
end
108+
109+
def expand_console_format(message)
110+
return message unless message.include?('%s')
111+
112+
segments = message.scan(/"((?:[^"\\]|\\.)*)"/m).flatten
113+
return message if segments.size < 2
114+
115+
format_str, *args = segments
116+
expanded = format_str.dup
117+
args.each { |arg| expanded = expanded.sub('%s', arg) }
118+
"#{message.split('"').first}#{expanded}"
119+
end
120+
121+
# The React the browser actually loaded. Which React a cell gets is decided by
122+
# react-rails (2.x bundles 16.x, 2.7.x bundles 17.0.2, 3.3 bundles 18.2) or, on
123+
# the esbuild pipeline, by npm -- so asking the page is the only reliable answer.
124+
# Used where React changed observable behaviour rather than API. (#51)
125+
def react_version_major
126+
@react_version_major ||= evaluate_ruby('`React.version`').to_s.split('.').first.to_i
127+
end

‎supported_versions.yml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,18 @@ cells:
3232
pipeline: sprockets
3333
react: "16.14" # derived: bundled by react-rails 2.x
3434
env: {} # the gemspec defaults already describe this cell
35+
36+
# React 17 takes the SAME hyper-component code path as 16 -- neither has
37+
# ReactDOM.createRoot, so both use ReactDOM.render/findDOMNode. So this cell is
38+
# not about new component code: it covers the react-rails 2.7 integration,
39+
# which edge's default pin (`< 2.7.0`) deliberately excludes. Costs no code
40+
# change, only a version selection. (#51)
41+
- id: rails61-react17
42+
ruby: "3.4"
43+
rails: "6.1"
44+
opal: "1.8"
45+
react_rails: "2.7"
46+
pipeline: sprockets
47+
react: "17.0.2" # derived: bundled by react-rails 2.7.x
48+
env:
49+
REACT_RAILS_VERSION: "~> 2.7.1"

0 commit comments

Comments
 (0)