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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +36 -0
  4. data/.rubocop.yml +1159 -0
  5. data/.travis.yml +29 -0
  6. data/Appraisals +20 -0
  7. data/CHANGELOG.md +93 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE +19 -0
  10. data/README.md +121 -0
  11. data/Rakefile +33 -0
  12. data/UPGRADING.md +24 -0
  13. data/component-name-lookup.md +145 -0
  14. data/config.ru +25 -0
  15. data/gemfiles/opal_0.8_react_13.gemfile +13 -0
  16. data/gemfiles/opal_0.8_react_14.gemfile +13 -0
  17. data/gemfiles/opal_0.8_react_15.gemfile +13 -0
  18. data/gemfiles/opal_0.9_react_13.gemfile +13 -0
  19. data/gemfiles/opal_0.9_react_14.gemfile +13 -0
  20. data/gemfiles/opal_0.9_react_15.gemfile +13 -0
  21. data/hyper-react.gemspec +43 -0
  22. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
  23. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  24. data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  25. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  26. data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  27. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  28. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  29. data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  30. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +109 -0
  31. data/lib/hyper-react.rb +52 -0
  32. data/lib/rails-helpers/top_level_rails_component.rb +54 -0
  33. data/lib/react-sources/react-server.js +2 -0
  34. data/lib/react/api.rb +162 -0
  35. data/lib/react/callbacks.rb +42 -0
  36. data/lib/react/children.rb +30 -0
  37. data/lib/react/component.rb +139 -0
  38. data/lib/react/component/api.rb +50 -0
  39. data/lib/react/component/base.rb +9 -0
  40. data/lib/react/component/class_methods.rb +214 -0
  41. data/lib/react/component/dsl_instance_methods.rb +27 -0
  42. data/lib/react/component/params.rb +6 -0
  43. data/lib/react/component/props_wrapper.rb +83 -0
  44. data/lib/react/component/should_component_update.rb +98 -0
  45. data/lib/react/component/tags.rb +144 -0
  46. data/lib/react/element.rb +168 -0
  47. data/lib/react/event.rb +76 -0
  48. data/lib/react/ext/hash.rb +9 -0
  49. data/lib/react/ext/string.rb +8 -0
  50. data/lib/react/hash.rb +13 -0
  51. data/lib/react/native_library.rb +92 -0
  52. data/lib/react/object.rb +15 -0
  53. data/lib/react/observable.rb +29 -0
  54. data/lib/react/react-source.rb +9 -0
  55. data/lib/react/rendering_context.rb +142 -0
  56. data/lib/react/state.rb +190 -0
  57. data/lib/react/test.rb +16 -0
  58. data/lib/react/test/dsl.rb +17 -0
  59. data/lib/react/test/matchers/render_html_matcher.rb +49 -0
  60. data/lib/react/test/rspec.rb +15 -0
  61. data/lib/react/test/session.rb +46 -0
  62. data/lib/react/top_level.rb +132 -0
  63. data/lib/react/validator.rb +136 -0
  64. data/lib/reactive-ruby/component_loader.rb +49 -0
  65. data/lib/reactive-ruby/isomorphic_helpers.rb +197 -0
  66. data/lib/reactive-ruby/rails.rb +7 -0
  67. data/lib/reactive-ruby/rails/component_mount.rb +46 -0
  68. data/lib/reactive-ruby/rails/controller_helper.rb +15 -0
  69. data/lib/reactive-ruby/rails/railtie.rb +14 -0
  70. data/lib/reactive-ruby/serializers.rb +15 -0
  71. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
  72. data/lib/reactive-ruby/version.rb +3 -0
  73. data/lib/reactrb/auto-import.rb +32 -0
  74. data/lib/reactrb/deep-compare.rb +24 -0
  75. data/lib/reactrb/new-event-name-convention.rb +11 -0
  76. data/lib/sources/react-latest.js +21169 -0
  77. data/lib/sources/react-v13.js +21645 -0
  78. data/lib/sources/react-v14.js +20821 -0
  79. data/lib/sources/react-v15.js +21170 -0
  80. data/logo1.png +0 -0
  81. data/logo2.png +0 -0
  82. data/logo3.png +0 -0
  83. data/path_release_steps.md +9 -0
  84. data/spec/controller_helper_spec.rb +34 -0
  85. data/spec/index.html.erb +10 -0
  86. data/spec/react/callbacks_spec.rb +106 -0
  87. data/spec/react/children_spec.rb +76 -0
  88. data/spec/react/component/base_spec.rb +32 -0
  89. data/spec/react/component_spec.rb +872 -0
  90. data/spec/react/dsl_spec.rb +296 -0
  91. data/spec/react/element_spec.rb +136 -0
  92. data/spec/react/event_spec.rb +24 -0
  93. data/spec/react/native_library_spec.rb +344 -0
  94. data/spec/react/observable_spec.rb +7 -0
  95. data/spec/react/opal_jquery_extensions_spec.rb +66 -0
  96. data/spec/react/param_declaration_spec.rb +258 -0
  97. data/spec/react/react_spec.rb +209 -0
  98. data/spec/react/state_spec.rb +55 -0
  99. data/spec/react/test/dsl_spec.rb +43 -0
  100. data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
  101. data/spec/react/test/rspec_spec.rb +62 -0
  102. data/spec/react/test/session_spec.rb +100 -0
  103. data/spec/react/test/utils_spec.rb +45 -0
  104. data/spec/react/top_level_component_spec.rb +96 -0
  105. data/spec/react/tutorial/tutorial_spec.rb +36 -0
  106. data/spec/react/validator_spec.rb +124 -0
  107. data/spec/reactive-ruby/component_loader_spec.rb +71 -0
  108. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
  109. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +10 -0
  110. data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
  111. data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
  112. data/spec/spec_helper.rb +115 -0
  113. data/spec/support/react/spec_helpers.rb +64 -0
  114. data/spec/vendor/es5-shim.min.js +6 -0
  115. data/spec/vendor/jquery-2.2.4.min.js +4 -0
  116. 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,7 @@
1
+ require 'spec_helper'
2
+
3
+ if opal?
4
+ describe 'React::Observable' do
5
+ # tbd
6
+ end
7
+ 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