hyper-react 0.10.0
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 +7 -0
- data/.codeclimate.yml +27 -0
- data/.gitignore +36 -0
- data/.rubocop.yml +1159 -0
- data/.travis.yml +29 -0
- data/Appraisals +20 -0
- data/CHANGELOG.md +93 -0
- data/Gemfile +6 -0
- data/LICENSE +19 -0
- data/README.md +121 -0
- data/Rakefile +33 -0
- data/UPGRADING.md +24 -0
- data/component-name-lookup.md +145 -0
- data/config.ru +25 -0
- data/gemfiles/opal_0.8_react_13.gemfile +13 -0
- data/gemfiles/opal_0.8_react_14.gemfile +13 -0
- data/gemfiles/opal_0.8_react_15.gemfile +13 -0
- data/gemfiles/opal_0.9_react_13.gemfile +13 -0
- data/gemfiles/opal_0.9_react_14.gemfile +13 -0
- data/gemfiles/opal_0.9_react_15.gemfile +13 -0
- data/hyper-react.gemspec +43 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +109 -0
- data/lib/hyper-react.rb +52 -0
- data/lib/rails-helpers/top_level_rails_component.rb +54 -0
- data/lib/react-sources/react-server.js +2 -0
- data/lib/react/api.rb +162 -0
- data/lib/react/callbacks.rb +42 -0
- data/lib/react/children.rb +30 -0
- data/lib/react/component.rb +139 -0
- data/lib/react/component/api.rb +50 -0
- data/lib/react/component/base.rb +9 -0
- data/lib/react/component/class_methods.rb +214 -0
- data/lib/react/component/dsl_instance_methods.rb +27 -0
- data/lib/react/component/params.rb +6 -0
- data/lib/react/component/props_wrapper.rb +83 -0
- data/lib/react/component/should_component_update.rb +98 -0
- data/lib/react/component/tags.rb +144 -0
- data/lib/react/element.rb +168 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/hash.rb +13 -0
- data/lib/react/native_library.rb +92 -0
- data/lib/react/object.rb +15 -0
- data/lib/react/observable.rb +29 -0
- data/lib/react/react-source.rb +9 -0
- data/lib/react/rendering_context.rb +142 -0
- data/lib/react/state.rb +190 -0
- data/lib/react/test.rb +16 -0
- data/lib/react/test/dsl.rb +17 -0
- data/lib/react/test/matchers/render_html_matcher.rb +49 -0
- data/lib/react/test/rspec.rb +15 -0
- data/lib/react/test/session.rb +46 -0
- data/lib/react/top_level.rb +132 -0
- data/lib/react/validator.rb +136 -0
- data/lib/reactive-ruby/component_loader.rb +49 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +197 -0
- data/lib/reactive-ruby/rails.rb +7 -0
- data/lib/reactive-ruby/rails/component_mount.rb +46 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +15 -0
- data/lib/reactive-ruby/rails/railtie.rb +14 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/lib/reactrb/auto-import.rb +32 -0
- data/lib/reactrb/deep-compare.rb +24 -0
- data/lib/reactrb/new-event-name-convention.rb +11 -0
- data/lib/sources/react-latest.js +21169 -0
- data/lib/sources/react-v13.js +21645 -0
- data/lib/sources/react-v14.js +20821 -0
- data/lib/sources/react-v15.js +21170 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- data/spec/controller_helper_spec.rb +34 -0
- data/spec/index.html.erb +10 -0
- data/spec/react/callbacks_spec.rb +106 -0
- data/spec/react/children_spec.rb +76 -0
- data/spec/react/component/base_spec.rb +32 -0
- data/spec/react/component_spec.rb +872 -0
- data/spec/react/dsl_spec.rb +296 -0
- data/spec/react/element_spec.rb +136 -0
- data/spec/react/event_spec.rb +24 -0
- data/spec/react/native_library_spec.rb +344 -0
- data/spec/react/observable_spec.rb +7 -0
- data/spec/react/opal_jquery_extensions_spec.rb +66 -0
- data/spec/react/param_declaration_spec.rb +258 -0
- data/spec/react/react_spec.rb +209 -0
- data/spec/react/state_spec.rb +55 -0
- data/spec/react/test/dsl_spec.rb +43 -0
- data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
- data/spec/react/test/rspec_spec.rb +62 -0
- data/spec/react/test/session_spec.rb +100 -0
- data/spec/react/test/utils_spec.rb +45 -0
- data/spec/react/top_level_component_spec.rb +96 -0
- data/spec/react/tutorial/tutorial_spec.rb +36 -0
- data/spec/react/validator_spec.rb +124 -0
- data/spec/reactive-ruby/component_loader_spec.rb +71 -0
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
- data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +10 -0
- data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
- data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
- data/spec/spec_helper.rb +115 -0
- data/spec/support/react/spec_helpers.rb +64 -0
- data/spec/vendor/es5-shim.min.js +6 -0
- data/spec/vendor/jquery-2.2.4.min.js +4 -0
- metadata +387 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'reactrb/auto-import'
|
|
3
|
+
|
|
4
|
+
if opal?
|
|
5
|
+
|
|
6
|
+
module NativeLibraryTestModule
|
|
7
|
+
class Component < React::Component::Base
|
|
8
|
+
param :time_stamp
|
|
9
|
+
backtrace :none
|
|
10
|
+
render { NativeComponent(name: "There - #{params.time_stamp}") }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class NestedComponent < React::Component::Base
|
|
14
|
+
param :time_stamp
|
|
15
|
+
backtrace :none
|
|
16
|
+
render { NativeLibrary::NativeNestedLibrary::NativeComponent(name: "There - #{params.time_stamp}") }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe "React::NativeLibrary" do
|
|
21
|
+
|
|
22
|
+
after(:each) do
|
|
23
|
+
%x{
|
|
24
|
+
delete window.NativeLibrary;
|
|
25
|
+
delete window.NativeComponent;
|
|
26
|
+
delete window.nativeLibrary;
|
|
27
|
+
delete window.nativeComponent;
|
|
28
|
+
delete window.NativeObject;
|
|
29
|
+
}
|
|
30
|
+
Object.send :remove_const, :NativeLibrary
|
|
31
|
+
Object.send :remove_const, :NativeComponent
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "functional stateless component (supported in reactjs v14+ only)" do
|
|
35
|
+
it "is not detected as native React.js component by `native_react_component?`", v13_only: true do
|
|
36
|
+
expect(React::API.native_react_component?(`function C(){ return null }`)).to be_falsy
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "is detected as native React.js component by `native_react_component?`", v13_exclude: true do
|
|
40
|
+
expect(React::API.native_react_component?(`function C(){ return null }`)).to be_truthy
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "imports a React.js functional stateless component", v13_exclude: true do
|
|
44
|
+
%x{
|
|
45
|
+
window.NativeLibrary = {
|
|
46
|
+
FunctionalComponent: function HelloMessage(props){
|
|
47
|
+
return React.createElement("div", null, "Hello ", props.name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
52
|
+
Foo.class_eval do
|
|
53
|
+
imports "NativeLibrary.FunctionalComponent"
|
|
54
|
+
end
|
|
55
|
+
expect(React.render_to_static_markup(
|
|
56
|
+
React.create_element(Foo, name: "There"))).to eq('<div>Hello There</div>')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "can use native_react_component? to detect a native React.js component" do
|
|
61
|
+
%x{
|
|
62
|
+
window.NativeComponent = React.createClass({
|
|
63
|
+
displayName: "HelloMessage",
|
|
64
|
+
render: function render() {
|
|
65
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
expect(React::API.native_react_component?(`window.NativeComponent`)).to be_truthy
|
|
70
|
+
expect(React::API.native_react_component?(`{render: function render() {}}`)).to be_falsy
|
|
71
|
+
expect(React::API.native_react_component?(`window.DoesntExist`)).to be_falsy
|
|
72
|
+
expect(React::API.native_react_component?(``)).to be_falsy
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "will import a React.js library into the Ruby name space" do
|
|
76
|
+
%x{
|
|
77
|
+
window.NativeLibrary = {
|
|
78
|
+
NativeComponent: React.createClass({
|
|
79
|
+
displayName: "HelloMessage",
|
|
80
|
+
render: function render() {
|
|
81
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
stub_const 'Foo', Class.new(React::NativeLibrary)
|
|
87
|
+
Foo.class_eval do
|
|
88
|
+
imports "NativeLibrary"
|
|
89
|
+
end
|
|
90
|
+
expect(React.render_to_static_markup(
|
|
91
|
+
React.create_element(Foo::NativeComponent, name: "There"))).to eq('<div>Hello There</div>')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "will import a nested React.js library into the Ruby name space" do
|
|
95
|
+
%x{
|
|
96
|
+
window.NativeLibrary = {
|
|
97
|
+
NestedLibrary: {
|
|
98
|
+
NativeComponent: React.createClass({
|
|
99
|
+
displayName: "HelloMessage",
|
|
100
|
+
render: function render() {
|
|
101
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
102
|
+
}
|
|
103
|
+
})}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
stub_const 'Foo', Class.new(React::NativeLibrary)
|
|
107
|
+
Foo.class_eval do
|
|
108
|
+
imports "NativeLibrary"
|
|
109
|
+
end
|
|
110
|
+
expect(React.render_to_static_markup(
|
|
111
|
+
React.create_element(Foo::NestedLibrary::NativeComponent, name: "There"))).to eq('<div>Hello There</div>')
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "will rename an imported a React.js component" do
|
|
115
|
+
%x{
|
|
116
|
+
window.NativeLibrary = {
|
|
117
|
+
NativeComponent: React.createClass({
|
|
118
|
+
displayName: "HelloMessage",
|
|
119
|
+
render: function render() {
|
|
120
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
stub_const 'Foo', Class.new(React::NativeLibrary)
|
|
126
|
+
Foo.class_eval do
|
|
127
|
+
imports "NativeLibrary"
|
|
128
|
+
rename "NativeComponent" => "Bar"
|
|
129
|
+
end
|
|
130
|
+
expect(React.render_to_static_markup(
|
|
131
|
+
React.create_element(Foo::Bar, name: "There"))).to eq('<div>Hello There</div>')
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "will give a reasonable error when failing to import a renamed component" do
|
|
135
|
+
%x{
|
|
136
|
+
window.NativeLibrary = {
|
|
137
|
+
NativeComponent: React.createClass({
|
|
138
|
+
displayName: "HelloMessage",
|
|
139
|
+
render: function render() {
|
|
140
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
stub_const 'Foo', Class.new(React::NativeLibrary)
|
|
146
|
+
expect do
|
|
147
|
+
Foo.class_eval do
|
|
148
|
+
imports "NativeLibrary"
|
|
149
|
+
rename "MispelledComponent" => "Bar"
|
|
150
|
+
end
|
|
151
|
+
end.to raise_error(/could not import MispelledComponent/)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "will import a single React.js component into the ruby name space" do
|
|
155
|
+
%x{
|
|
156
|
+
window.NativeComponent = React.createClass({
|
|
157
|
+
displayName: "HelloMessage",
|
|
158
|
+
render: function render() {
|
|
159
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
164
|
+
Foo.class_eval do
|
|
165
|
+
imports "NativeComponent"
|
|
166
|
+
end
|
|
167
|
+
expect(React.render_to_static_markup(
|
|
168
|
+
React.create_element(Foo, name: "There"))).to eq('<div>Hello There</div>')
|
|
169
|
+
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "will import a name scoped React.js component into the ruby name space" do
|
|
173
|
+
%x{
|
|
174
|
+
window.NativeLibrary = {
|
|
175
|
+
NativeComponent: React.createClass({
|
|
176
|
+
displayName: "HelloMessage",
|
|
177
|
+
render: function render() {
|
|
178
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
184
|
+
Foo.class_eval do
|
|
185
|
+
imports "NativeLibrary.NativeComponent"
|
|
186
|
+
end
|
|
187
|
+
expect(React.render_to_static_markup(
|
|
188
|
+
React.create_element(Foo, name: "There"))).to eq('<div>Hello There</div>')
|
|
189
|
+
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it "will give a meaningful error if the React.js component is invalid" do
|
|
193
|
+
%x{
|
|
194
|
+
window.NativeObject = {}
|
|
195
|
+
}
|
|
196
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
197
|
+
expect do
|
|
198
|
+
Foo.class_eval do
|
|
199
|
+
imports "NativeObject"
|
|
200
|
+
end
|
|
201
|
+
end.to raise_error("Foo cannot import 'NativeObject': does not appear to be a native react component.")
|
|
202
|
+
expect do
|
|
203
|
+
Foo.class_eval do
|
|
204
|
+
imports "window.Baz"
|
|
205
|
+
end
|
|
206
|
+
end.to raise_error(/^Foo cannot import \'window\.Baz\'\: (?!does not appear to be a native react component)..*$/)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context "automatic importing" do
|
|
210
|
+
|
|
211
|
+
it "will automatically import a React.js component when referenced in another component" do
|
|
212
|
+
%x{
|
|
213
|
+
window.NativeComponent = React.createClass({
|
|
214
|
+
displayName: "HelloMessage",
|
|
215
|
+
render: function render() {
|
|
216
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
217
|
+
}
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
expect(React.render_to_static_markup(
|
|
221
|
+
React.create_element(NativeLibraryTestModule::Component, time_stamp: Time.now))).to match(/<div>Hello There.*<\/div>/)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it "will automatically import a React.js component when referenced in another component" do
|
|
225
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
226
|
+
Foo.class_eval do
|
|
227
|
+
render { NativeComponent(name: "There") }
|
|
228
|
+
end
|
|
229
|
+
%x{
|
|
230
|
+
window.NativeComponent = React.createClass({
|
|
231
|
+
displayName: "HelloMessage",
|
|
232
|
+
render: function render() {
|
|
233
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
expect(React.render_to_static_markup(
|
|
238
|
+
React.create_element(Foo))).to eq('<div>Hello There</div>')
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it 'will automatically import a React.js component when referenced in another component with the _as_node suffix' do
|
|
242
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
243
|
+
Foo.class_eval do
|
|
244
|
+
render(:div) do
|
|
245
|
+
e = React::Element.new(NativeComponent_as_node(name: 'There'))
|
|
246
|
+
2.times { e.render }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
%x{
|
|
250
|
+
window.NativeComponent = React.createClass({
|
|
251
|
+
displayName: "HelloMessage",
|
|
252
|
+
render: function render() {
|
|
253
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
expect(React.render_to_static_markup(
|
|
258
|
+
React.create_element(Foo))).to eq('<div><div>Hello There</div><div>Hello There</div></div>')
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it "will automatically import a React.js component in a library when referenced in another component with the _as_node suffix" do
|
|
262
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
263
|
+
Foo.class_eval do
|
|
264
|
+
render(:div) do
|
|
265
|
+
e = React::Element.new(NativeLibrary::NativeComponent_as_node(name: 'There'))
|
|
266
|
+
2.times { e.render }
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
%x{
|
|
270
|
+
window.NativeLibrary = {
|
|
271
|
+
NativeComponent: React.createClass({
|
|
272
|
+
displayName: "HelloMessage",
|
|
273
|
+
render: function render() {
|
|
274
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
expect(React.render_to_static_markup(
|
|
280
|
+
React.create_element(Foo))).to eq('<div><div>Hello There</div><div>Hello There</div></div>')
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it "will automatically import a React.js component when referenced as a constant" do
|
|
284
|
+
%x{
|
|
285
|
+
window.NativeComponent = React.createClass({
|
|
286
|
+
displayName: "HelloMessage",
|
|
287
|
+
render: function render() {
|
|
288
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
expect(React.render_to_static_markup(
|
|
293
|
+
React.create_element(NativeComponent, name: "There"))).to eq('<div>Hello There</div>')
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it "will automatically import a native library containing a React.js component" do
|
|
297
|
+
%x{
|
|
298
|
+
window.NativeLibrary = {
|
|
299
|
+
NativeNestedLibrary: {
|
|
300
|
+
NativeComponent: React.createClass({
|
|
301
|
+
displayName: "HelloMessage",
|
|
302
|
+
render: function render() {
|
|
303
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
expect(React.render_to_static_markup(
|
|
311
|
+
React.create_element(NativeLibraryTestModule::NestedComponent, time_stamp: Time.now))).to match(/<div>Hello There.*<\/div>/)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it "the library and components can begin with lower case letters" do
|
|
315
|
+
%x{
|
|
316
|
+
window.nativeLibrary = {
|
|
317
|
+
nativeComponent: React.createClass({
|
|
318
|
+
displayName: "HelloMessage",
|
|
319
|
+
render: function render() {
|
|
320
|
+
return React.createElement("div", null, "Hello ", this.props.name);
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
expect(React.render_to_static_markup(
|
|
326
|
+
React.create_element(NativeLibrary::NativeComponent, name: "There"))).to eq('<div>Hello There</div>')
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
it "will produce a sensible error if the component is not in the library" do
|
|
330
|
+
%x{
|
|
331
|
+
window.NativeLibrary = {
|
|
332
|
+
NativeNestedLibrary: {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
expect do
|
|
337
|
+
React.render_to_static_markup(React.create_element(NativeLibraryTestModule::NestedComponent, time_stamp: Time.now))
|
|
338
|
+
end.to raise_error("could not import a react component named: NativeLibrary.NativeNestedLibrary.NativeComponent")
|
|
339
|
+
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if opal?
|
|
4
|
+
describe 'Element' do
|
|
5
|
+
after(:each) do
|
|
6
|
+
React::API.clear_component_class_cache
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'will reuse the wrapper componet class for the same Element' do
|
|
10
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
11
|
+
Foo.class_eval do
|
|
12
|
+
param :name
|
|
13
|
+
def render
|
|
14
|
+
"hello #{params.name}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def component_will_unmount
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
expect_any_instance_of(Foo).to_not receive(:component_will_unmount)
|
|
23
|
+
|
|
24
|
+
test_div = Element.new(:div)
|
|
25
|
+
test_div.render { Foo(name: 'fred') }
|
|
26
|
+
test_div.render { Foo(name: 'freddy') }
|
|
27
|
+
expect(Element[test_div].find('span').html).to eq('hello freddy')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'renders a top level component using render with a block' do
|
|
31
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
32
|
+
Foo.class_eval do
|
|
33
|
+
param :name
|
|
34
|
+
def render
|
|
35
|
+
"hello #{params.name}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
test_div = Element.new(:div)
|
|
39
|
+
test_div.render { Foo(name: 'fred') }
|
|
40
|
+
expect(Element[test_div].find('span').html).to eq('hello fred')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'renders a top level component using render with a container and params ' do
|
|
44
|
+
test_div = Element.new(:div)
|
|
45
|
+
test_div.render(:span, id: :render_test_span) { 'hello' }
|
|
46
|
+
expect(Element[test_div].find('#render_test_span').html).to eq('hello')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'will find the DOM node given a react element' do
|
|
50
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
51
|
+
Foo.class_eval do
|
|
52
|
+
def render
|
|
53
|
+
div { 'hello' }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
expect(Element[renderToDocument(Foo)].html).to eq('hello')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "accepts plain js object as selector" do
|
|
61
|
+
expect {
|
|
62
|
+
Element[`window`]
|
|
63
|
+
}.not_to raise_error
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if opal?
|
|
4
|
+
describe 'the param macro', type: :component do
|
|
5
|
+
it 'defines collect_other_params_as method on params proxy' do
|
|
6
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
7
|
+
Foo.class_eval do
|
|
8
|
+
collect_other_params_as :foo
|
|
9
|
+
|
|
10
|
+
def render
|
|
11
|
+
div { params.foo[:bar] }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
expect(Foo).to render('<div>biz</div>').with_params(bar: 'biz')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "can create and access a required param" do
|
|
19
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
20
|
+
Foo.class_eval do
|
|
21
|
+
param :foo
|
|
22
|
+
|
|
23
|
+
def render
|
|
24
|
+
div { params.foo }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
expect(Foo).to render('<div>bar</div>').with_params(foo: :bar)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "can create and access an optional params" do
|
|
32
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
33
|
+
Foo.class_eval do
|
|
34
|
+
|
|
35
|
+
param foo1: :no_bar1
|
|
36
|
+
param foo2: :no_bar2
|
|
37
|
+
param :foo3, default: :no_bar3
|
|
38
|
+
param :foo4, default: :no_bar4
|
|
39
|
+
|
|
40
|
+
def render
|
|
41
|
+
div { "#{params.foo1}-#{params.foo2}-#{params.foo3}-#{params.foo4}" }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
expect(Foo).to render('<div>bar1-no_bar2-bar3-no_bar4</div>').with_params(foo1: :bar1, foo3: :bar3)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'can specify validation rules with the type option' do
|
|
49
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
50
|
+
Foo.class_eval do
|
|
51
|
+
param :foo, type: String
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
expect(Foo.prop_types).to have_key(:_componentValidator)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "can type check params" do
|
|
58
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
59
|
+
Foo.class_eval do
|
|
60
|
+
|
|
61
|
+
param :foo1, type: String
|
|
62
|
+
param :foo2, type: String
|
|
63
|
+
|
|
64
|
+
def render
|
|
65
|
+
div { "#{params.foo1}-#{params.foo2}" }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
expect(Foo).to render('<div>12-string</div>').with_params(foo1: 12, foo2: "string")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'logs error in warning if validation failed' do
|
|
73
|
+
stub_const 'Lorem', Class.new
|
|
74
|
+
stub_const 'Foo2', Class.new(React::Component::Base)
|
|
75
|
+
Foo2.class_eval do
|
|
76
|
+
param :foo
|
|
77
|
+
param :lorem, type: Lorem
|
|
78
|
+
param :bar, default: nil, type: String
|
|
79
|
+
def render; div; end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
%x{
|
|
83
|
+
var log = [];
|
|
84
|
+
var org_warn_console = window.console.warn;
|
|
85
|
+
var org_error_console = window.console.error;
|
|
86
|
+
window.console.warn = window.console.error = function(str){log.push(str)}
|
|
87
|
+
}
|
|
88
|
+
renderToDocument(Foo2, bar: 10, lorem: Lorem.new)
|
|
89
|
+
`window.console.warn = org_warn_console; window.console.error = org_error_console;`
|
|
90
|
+
|
|
91
|
+
expect(`log[0]`).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/)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'should not log anything if validation passes' do
|
|
95
|
+
stub_const 'Lorem', Class.new
|
|
96
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
97
|
+
Foo.class_eval do
|
|
98
|
+
param :foo
|
|
99
|
+
param :lorem, type: Lorem
|
|
100
|
+
param :bar, default: nil, type: String
|
|
101
|
+
|
|
102
|
+
def render; div; end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
%x{
|
|
106
|
+
var log = [];
|
|
107
|
+
var org_warn_console = window.console.warn;
|
|
108
|
+
var org_error_console = window.console.error;
|
|
109
|
+
window.console.warn = window.console.error = function(str){log.push(str)}
|
|
110
|
+
}
|
|
111
|
+
renderToDocument(Foo, foo: 10, bar: '10', lorem: Lorem.new)
|
|
112
|
+
`window.console.warn = org_warn_console; window.console.error = org_error_console;`
|
|
113
|
+
expect(`log`).to eq([])
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe 'advanced type handling' do
|
|
117
|
+
before(:each) do
|
|
118
|
+
%x{
|
|
119
|
+
window.dummy_log = [];
|
|
120
|
+
window.org_warn_console = window.console.warn;
|
|
121
|
+
window.org_error_console = window.console.warn
|
|
122
|
+
window.console.warn = window.console.error = function(str){window.dummy_log.push(str)}
|
|
123
|
+
}
|
|
124
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
125
|
+
Foo.class_eval { def render; ""; end }
|
|
126
|
+
end
|
|
127
|
+
after(:each) do
|
|
128
|
+
`window.console.warn = window.org_warn_console; window.console.error = window.org_error_console`
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "can use the [] notation for arrays" do
|
|
132
|
+
Foo.class_eval do
|
|
133
|
+
param :foo, type: []
|
|
134
|
+
param :bar, type: []
|
|
135
|
+
end
|
|
136
|
+
renderToDocument(Foo, foo: 10, bar: [10])
|
|
137
|
+
expect(`window.dummy_log[0]`).to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to Array/)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "can use the [xxx] notation for arrays of a specific type" do
|
|
141
|
+
Foo.class_eval do
|
|
142
|
+
param :foo, type: [String]
|
|
143
|
+
param :bar, type: [String]
|
|
144
|
+
end
|
|
145
|
+
renderToDocument(Foo, foo: [10], bar: ["10"])
|
|
146
|
+
expect(`window.dummy_log[0]`).to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo`\[0\] could not be converted to String/)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "can convert a json hash to a type" do
|
|
150
|
+
stub_const "BazWoggle", Class.new
|
|
151
|
+
BazWoggle.class_eval do
|
|
152
|
+
def initialize(kind)
|
|
153
|
+
@kind = kind
|
|
154
|
+
end
|
|
155
|
+
attr_accessor :kind
|
|
156
|
+
def self._react_param_conversion(json, validate_only)
|
|
157
|
+
new(json[:bazwoggle]) if json[:bazwoggle]
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
Foo.class_eval do
|
|
161
|
+
param :foo, type: BazWoggle
|
|
162
|
+
param :bar, type: BazWoggle
|
|
163
|
+
param :baz, type: [BazWoggle]
|
|
164
|
+
def render
|
|
165
|
+
"#{params.bar.kind}, #{params.baz[0].kind}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
params = { foo: "", bar: { bazwoggle: 1 }, baz: [{ bazwoggle: 2 }] }
|
|
170
|
+
expect(Foo).to render('<span>1, 2</span>').with_params(params)
|
|
171
|
+
expect(`window.dummy_log[0]`).to match(/Warning: Failed prop( type|Type): In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle/)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe "converts params only once" do
|
|
175
|
+
it "not on every access" do
|
|
176
|
+
stub_const "BazWoggle", Class.new
|
|
177
|
+
BazWoggle.class_eval do
|
|
178
|
+
def initialize(kind)
|
|
179
|
+
@kind = kind
|
|
180
|
+
end
|
|
181
|
+
attr_accessor :kind
|
|
182
|
+
def self._react_param_conversion(json, validate_only)
|
|
183
|
+
new(json[:bazwoggle]) if json[:bazwoggle]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
Foo.class_eval do
|
|
187
|
+
param :foo, type: BazWoggle
|
|
188
|
+
def render
|
|
189
|
+
params.foo.kind = params.foo.kind+1
|
|
190
|
+
"#{params.foo.kind}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
expect(React.render_to_static_markup(React.create_element(Foo, foo: {bazwoggle: 1}))).to eq('<span>2</span>')
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "even if contains an embedded native object" do
|
|
197
|
+
pending 'Fix after merging'
|
|
198
|
+
stub_const "Bar", Class.new(React::Component::Base)
|
|
199
|
+
stub_const "BazWoggle", Class.new
|
|
200
|
+
BazWoggle.class_eval do
|
|
201
|
+
def initialize(kind)
|
|
202
|
+
@kind = kind
|
|
203
|
+
end
|
|
204
|
+
attr_accessor :kind
|
|
205
|
+
def self._react_param_conversion(json, validate_only)
|
|
206
|
+
new(JSON.from_object(json[0])[:bazwoggle]) if JSON.from_object(json[0])[:bazwoggle]
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
Bar.class_eval do
|
|
210
|
+
param :foo, type: BazWoggle
|
|
211
|
+
def render
|
|
212
|
+
params.foo.kind.to_s
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
Foo.class_eval do
|
|
216
|
+
export_state :change_me
|
|
217
|
+
before_mount do
|
|
218
|
+
Foo.change_me! "initial"
|
|
219
|
+
end
|
|
220
|
+
def render
|
|
221
|
+
Bar(foo: Native([`{bazwoggle: #{Foo.change_me}}`]))
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
div = `document.createElement("div")`
|
|
225
|
+
React.render(React.create_element(Foo, {}), div)
|
|
226
|
+
Foo.change_me! "updated"
|
|
227
|
+
expect(`div.children[0].innerHTML`).to eq("updated")
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it "will alias a Proc type param" do
|
|
232
|
+
Foo.class_eval do
|
|
233
|
+
param :foo, type: Proc
|
|
234
|
+
def render
|
|
235
|
+
params.foo
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
expect(Foo).to render('<span>works!</span>').with_params(foo: lambda { 'works!' })
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it "will create a 'bang' (i.e. update) method if the type is React::Observable" do
|
|
242
|
+
Foo.class_eval do
|
|
243
|
+
param :foo, type: React::Observable
|
|
244
|
+
before_mount do
|
|
245
|
+
params.foo! "ha!"
|
|
246
|
+
end
|
|
247
|
+
def render
|
|
248
|
+
params.foo
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
current_state = ""
|
|
252
|
+
observer = React::Observable.new(current_state) { |new_state| current_state = new_state }
|
|
253
|
+
expect(Foo).to render('<span>ha!</span>').with_params(foo: observer)
|
|
254
|
+
expect(current_state).to eq("ha!")
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|