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
Binary file
Binary file
Binary file
@@ -0,0 +1,9 @@
1
+
2
+ For example assuming you are releasing fix to 0.8.18
3
+
4
+ 1. Checkout 0-8-stable
5
+ 2. Update tests, fix the bug and commit the changes.
6
+ 3. Build & Release to RubyGems (Remember the version in version.rb should already be 0.8.19)
7
+ 4. Create a tag 'v0.8.19' pointing to that commit.
8
+ 5. Bump the version in 0-8-stable to 0.8.20 so it will be ready for the next patch level release.
9
+ 6. Commit the version bump, and do a `git push --tags` so the new tag goes up
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ if ruby?
4
+ class TestController < ActionController::Base; end
5
+
6
+ RSpec.describe TestController, type: :controller do
7
+ render_views
8
+
9
+ describe '#render_component' do
10
+ controller do
11
+
12
+ layout "test_layout"
13
+
14
+ def index
15
+ render_component
16
+ end
17
+
18
+ def new
19
+ render_component "Index", {}, layout: :explicit_layout
20
+ end
21
+ end
22
+
23
+ it 'renders with the default layout' do
24
+ get :index, no_prerender: true
25
+ expect(response).to render_template(layout: :test_layout)
26
+ end
27
+
28
+ it "renders with a specified layout" do
29
+ get :new, no_prerender: true
30
+ expect(response).to render_template(layout: :explicit_layout)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ </head>
5
+ <body>
6
+ <%= javascript_include_tag 'vendor/es5-shim.min' %>
7
+ <%= javascript_include_tag 'vendor/jquery-2.2.4.min' %>
8
+ <%= javascript_include_tag @server.main %>
9
+ </body>
10
+ </html>
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ if opal?
4
+ describe React::Callbacks do
5
+ it 'defines callback' do
6
+ stub_const 'Foo', Class.new
7
+ Foo.class_eval do
8
+ include React::Callbacks
9
+ define_callback :before_dinner
10
+ before_dinner :wash_hand
11
+
12
+ def wash_hand
13
+ end
14
+ end
15
+
16
+ expect_any_instance_of(Foo).to receive(:wash_hand)
17
+ Foo.new.run_callback(:before_dinner)
18
+ end
19
+
20
+ it 'defines multiple callbacks' do
21
+ stub_const 'Foo', Class.new
22
+ Foo.class_eval do
23
+ include React::Callbacks
24
+ define_callback :before_dinner
25
+
26
+ before_dinner :wash_hand, :turn_of_laptop
27
+
28
+ def wash_hand;end
29
+
30
+ def turn_of_laptop;end
31
+ end
32
+
33
+ expect_any_instance_of(Foo).to receive(:wash_hand)
34
+ expect_any_instance_of(Foo).to receive(:turn_of_laptop)
35
+ Foo.new.run_callback(:before_dinner)
36
+ end
37
+
38
+ it 'defines block callback' do
39
+ stub_const 'Foo', Class.new
40
+ Foo.class_eval do
41
+ include React::Callbacks
42
+ attr_accessor :a
43
+ attr_accessor :b
44
+
45
+ define_callback :before_dinner
46
+
47
+ before_dinner do
48
+ self.a = 10
49
+ end
50
+ before_dinner do
51
+ self.b = 20
52
+ end
53
+ end
54
+
55
+ foo = Foo.new
56
+ foo.run_callback(:before_dinner)
57
+ expect(foo.a).to eq(10)
58
+ expect(foo.b).to eq(20)
59
+ end
60
+
61
+ it 'defines multiple callback group' do
62
+ stub_const 'Foo', Class.new
63
+ Foo.class_eval do
64
+ include React::Callbacks
65
+ define_callback :before_dinner
66
+ define_callback :after_dinner
67
+ attr_accessor :a
68
+
69
+ before_dinner do
70
+ self.a = 10
71
+ end
72
+ end
73
+
74
+ foo = Foo.new
75
+ foo.run_callback(:before_dinner)
76
+ foo.run_callback(:after_dinner)
77
+
78
+ expect(foo.a).to eq(10)
79
+ end
80
+
81
+ it 'receives args as callback' do
82
+ stub_const 'Foo', Class.new
83
+ Foo.class_eval do
84
+ include React::Callbacks
85
+ define_callback :before_dinner
86
+ define_callback :after_dinner
87
+
88
+ attr_accessor :lorem
89
+
90
+ before_dinner do |a, b|
91
+ self.lorem = "#{a}-#{b}"
92
+ end
93
+
94
+ after_dinner :eat_ice_cream
95
+ def eat_ice_cream(a,b,c); end
96
+ end
97
+
98
+ expect_any_instance_of(Foo).to receive(:eat_ice_cream).with(4,5,6)
99
+
100
+ foo = Foo.new
101
+ foo.run_callback(:before_dinner, 1, 2)
102
+ foo.run_callback(:after_dinner, 4, 5, 6)
103
+ expect(foo.lorem).to eq('1-2')
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ if opal?
4
+ describe React::Children do
5
+ let(:component) {
6
+ Class.new do
7
+ include React::Component
8
+ def render
9
+ div { 'lorem' }
10
+ end
11
+ end
12
+ }
13
+ let(:childs) { [React.create_element('a'), React.create_element('li')] }
14
+ let(:element) { React.create_element(component) { childs } }
15
+ let(:children) { described_class.new(`#{element.to_n}.props.children`) }
16
+
17
+ before(:each) do
18
+ renderElementToDocument(element)
19
+ end
20
+
21
+ it 'is enumerable' do
22
+ nodes = children.map { |elem| elem.element_type }
23
+ expect(nodes).to eq(['a', 'li'])
24
+ end
25
+
26
+ it 'returns an Enumerator when not providing a block' do
27
+ nodes = children.each
28
+ expect(nodes).to be_a(Enumerator)
29
+ expect(nodes.size).to eq(2)
30
+ end
31
+
32
+ describe '#each' do
33
+ it 'returns an array of elements' do
34
+ nodes = children.each { |elem| elem.element_type }
35
+ expect(nodes).to be_a(Array)
36
+ expect(nodes.map(&:class)).to eq(childs.map(&:class))
37
+ end
38
+ end
39
+
40
+ describe '#length' do
41
+ it 'returns the number of child elements' do
42
+ expect(children.length).to eq(2)
43
+ end
44
+ end
45
+
46
+ describe 'with single child element' do
47
+ let(:childs) { [React.create_element('a')] }
48
+
49
+ it 'is enumerable containing single element' do
50
+ nodes = children.map { |elem| elem.element_type }
51
+ expect(nodes).to eq(['a'])
52
+ end
53
+
54
+ describe '#length' do
55
+ it 'returns the number of child elements' do
56
+ expect(children.length).to eq(1)
57
+ end
58
+ end
59
+ end
60
+
61
+ describe 'with no child element' do
62
+ let(:element) { React.create_element(component) }
63
+
64
+ it 'is enumerable containing no elements' do
65
+ nodes = children.map { |elem| elem.element_type }
66
+ expect(nodes).to eq([])
67
+ end
68
+
69
+ describe '#length' do
70
+ it 'returns the number of child elements' do
71
+ expect(children.length).to eq(0)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ if opal?
4
+ RSpec.describe React::Component::Base, type: :component do
5
+ after(:each) do
6
+ React::API.clear_component_class_cache
7
+ end
8
+
9
+ it 'can be inherited to create a component class' do
10
+ stub_const 'Foo', Class.new(React::Component::Base)
11
+ Foo.class_eval do
12
+ before_mount do
13
+ @instance_data = ["working"]
14
+ end
15
+ def render
16
+ @instance_data.first
17
+ end
18
+ end
19
+ stub_const 'Bar', Class.new(Foo)
20
+ Bar.class_eval do
21
+ before_mount do
22
+ @instance_data << "well"
23
+ end
24
+ def render
25
+ @instance_data.join(" ")
26
+ end
27
+ end
28
+ expect(Foo).to render("<span>working</span>")
29
+ expect(Bar).to render("<span>working well</span>")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,872 @@
1
+ require 'spec_helper'
2
+
3
+ if opal?
4
+ describe React::Component, type: :component do
5
+ after(:each) do
6
+ React::API.clear_component_class_cache
7
+ end
8
+
9
+ it 'defines component spec methods' do
10
+ stub_const 'Foo', Class.new
11
+ Foo.class_eval do
12
+ include React::Component
13
+ def render
14
+ React.create_element('div')
15
+ end
16
+ end
17
+
18
+ # Class Methods
19
+ expect(Foo).to respond_to('initial_state')
20
+ expect(Foo).to respond_to('default_props')
21
+ expect(Foo).to respond_to('prop_types')
22
+
23
+ # Instance method
24
+ expect(Foo.new).to respond_to('component_will_mount')
25
+ expect(Foo.new).to respond_to('component_did_mount')
26
+ expect(Foo.new).to respond_to('component_will_receive_props')
27
+ expect(Foo.new).to respond_to('should_component_update?')
28
+ expect(Foo.new).to respond_to('component_will_update')
29
+ expect(Foo.new).to respond_to('component_did_update')
30
+ expect(Foo.new).to respond_to('component_will_unmount')
31
+ end
32
+
33
+ describe 'Life Cycle' do
34
+ before(:each) do
35
+ stub_const 'Foo', Class.new
36
+ Foo.class_eval do
37
+ include React::Component
38
+ def render
39
+ React.create_element('div') { 'lorem' }
40
+ end
41
+ end
42
+ end
43
+
44
+ it 'invokes `before_mount` registered methods when `componentWillMount()`' do
45
+ Foo.class_eval do
46
+ before_mount :bar, :bar2
47
+ def bar; end
48
+ def bar2; end
49
+ end
50
+
51
+ expect_any_instance_of(Foo).to receive(:bar)
52
+ expect_any_instance_of(Foo).to receive(:bar2)
53
+
54
+ renderToDocument(Foo)
55
+ end
56
+
57
+ it 'invokes `after_mount` registered methods when `componentDidMount()`' do
58
+ Foo.class_eval do
59
+ after_mount :bar3, :bar4
60
+ def bar3; end
61
+ def bar4; end
62
+ end
63
+
64
+ expect_any_instance_of(Foo).to receive(:bar3)
65
+ expect_any_instance_of(Foo).to receive(:bar4)
66
+
67
+ renderToDocument(Foo)
68
+ end
69
+
70
+ it 'allows multiple class declared life cycle hooker' do
71
+ stub_const 'FooBar', Class.new
72
+ Foo.class_eval do
73
+ before_mount :bar
74
+ def bar; end
75
+ end
76
+
77
+ FooBar.class_eval do
78
+ include React::Component
79
+ after_mount :bar2
80
+ def bar2; end
81
+ def render
82
+ React.create_element('div') { 'lorem' }
83
+ end
84
+ end
85
+
86
+ expect_any_instance_of(Foo).to receive(:bar)
87
+
88
+ renderToDocument(Foo)
89
+ end
90
+
91
+ it 'allows block for life cycle callback' do
92
+ Foo.class_eval do
93
+ export_state(:foo)
94
+
95
+ before_mount do
96
+ foo! 'bar'
97
+ end
98
+ end
99
+
100
+ element = renderToDocument(Foo)
101
+ expect(Foo.foo).to be('bar')
102
+ end
103
+ end
104
+
105
+ describe 'New style setter & getter' do
106
+ before(:each) do
107
+ stub_const 'Foo', Class.new
108
+ Foo.class_eval do
109
+ include React::Component
110
+ def render
111
+ div { state.foo }
112
+ end
113
+ end
114
+ end
115
+
116
+ it 'implicitly will create a state variable when first written' do
117
+ Foo.class_eval do
118
+ before_mount do
119
+ state.foo! 'bar'
120
+ end
121
+ end
122
+
123
+ expect(Foo).to render('<div>bar</div>')
124
+ end
125
+
126
+ it 'allows kernal method names like "format" to be used as state variable names' do
127
+ Foo.class_eval do
128
+ before_mount do
129
+ state.format! 'yes'
130
+ state.foo! state.format
131
+ end
132
+ end
133
+
134
+ expect(Foo).to render('<div>yes</div>')
135
+ end
136
+
137
+ it 'returns an observer with the bang method and no arguments' do
138
+ Foo.class_eval do
139
+ before_mount do
140
+ state.foo!(state.baz!.class.name)
141
+ end
142
+ end
143
+
144
+ expect(Foo).to render('<div>React::Observable</div>')
145
+ end
146
+
147
+ it 'returns the current value of a state when written' do
148
+ Foo.class_eval do
149
+ before_mount do
150
+ state.baz! 'bar'
151
+ state.foo!(state.baz!('pow'))
152
+ end
153
+ end
154
+
155
+ expect(Foo).to render('<div>bar</div>')
156
+ end
157
+
158
+ it 'can access an explicitly defined state`' do
159
+ Foo.class_eval do
160
+ define_state foo: :bar
161
+ end
162
+
163
+ expect(Foo).to render('<div>bar</div>')
164
+ end
165
+
166
+ end
167
+
168
+ describe 'State setter & getter' do
169
+ before(:each) do
170
+ stub_const 'Foo', Class.new
171
+ Foo.class_eval do
172
+ include React::Component
173
+ def render
174
+ React.create_element('div') { 'lorem' }
175
+ end
176
+ end
177
+ end
178
+
179
+ it 'defines setter using `define_state`' do
180
+ Foo.class_eval do
181
+ define_state :foo
182
+ before_mount :set_up
183
+ def set_up
184
+ self.foo = 'bar'
185
+ end
186
+ end
187
+
188
+ element = renderToDocument(Foo)
189
+ expect(element.state.foo).to be('bar')
190
+ end
191
+
192
+ it 'defines init state by passing a block to `define_state`' do
193
+ Foo.class_eval do
194
+ define_state(:foo) { 10 }
195
+ end
196
+
197
+ element = renderToDocument(Foo)
198
+ expect(element.state.foo).to be(10)
199
+ end
200
+
201
+ it 'defines getter using `define_state`' do
202
+ Foo.class_eval do
203
+ define_state(:foo) { 10 }
204
+ before_mount :bump
205
+ def bump
206
+ self.foo = self.foo + 20
207
+ end
208
+ end
209
+
210
+ element = renderToDocument(Foo)
211
+ expect(element.state.foo).to be(30)
212
+ end
213
+
214
+ it 'defines multiple state accessors by passing array to `define_state`' do
215
+ Foo.class_eval do
216
+ define_state :foo, :foo2
217
+ before_mount :set_up
218
+ def set_up
219
+ self.foo = 10
220
+ self.foo2 = 20
221
+ end
222
+ end
223
+
224
+ element = renderToDocument(Foo)
225
+ expect(element.state.foo).to be(10)
226
+ expect(element.state.foo2).to be(20)
227
+ end
228
+
229
+ it 'invokes `define_state` multiple times to define states' do
230
+ Foo.class_eval do
231
+ define_state(:foo) { 30 }
232
+ define_state(:foo2) { 40 }
233
+ end
234
+
235
+ element = renderToDocument(Foo)
236
+ expect(element.state.foo).to be(30)
237
+ expect(element.state.foo2).to be(40)
238
+ end
239
+
240
+ it 'can initialize multiple state variables with a block' do
241
+ Foo.class_eval do
242
+ define_state(:foo, :foo2) { 30 }
243
+ end
244
+ element = renderToDocument(Foo)
245
+ expect(element.state.foo).to be(30)
246
+ expect(element.state.foo2).to be(30)
247
+ end
248
+
249
+ it 'can mix multiple state variables with initializers and a block' do
250
+ Foo.class_eval do
251
+ define_state(:x, :y, foo: 1, bar: 2) {3}
252
+ end
253
+ element = renderToDocument(Foo)
254
+ expect(element.state.x).to be(3)
255
+ expect(element.state.y).to be(3)
256
+ expect(element.state.foo).to be(1)
257
+ expect(element.state.bar).to be(2)
258
+ end
259
+
260
+ it 'gets state in render method' do
261
+ Foo.class_eval do
262
+ define_state(:foo) { 10 }
263
+ def render
264
+ React.create_element('div') { self.foo }
265
+ end
266
+ end
267
+
268
+ element = renderToDocument(Foo)
269
+ expect(Element[element].text).to eq('10')
270
+ end
271
+
272
+ it 'supports original `setState` as `set_state` method' do
273
+ Foo.class_eval do
274
+ before_mount do
275
+ self.set_state(foo: 'bar')
276
+ end
277
+ end
278
+
279
+ element = renderToDocument(Foo)
280
+ expect(element.state.foo).to be('bar')
281
+ end
282
+
283
+ it 'supports original `replaceState` as `set_state!` method' do
284
+ Foo.class_eval do
285
+ before_mount do
286
+ self.set_state(foo: 'bar')
287
+ self.set_state!(bar: 'lorem')
288
+ end
289
+ end
290
+
291
+ element = renderToDocument(Foo)
292
+ expect(element.state.foo).to be_nil
293
+ expect(element.state.bar).to eq('lorem')
294
+ end
295
+
296
+ it 'supports original `state` method' do
297
+ Foo.class_eval do
298
+ before_mount do
299
+ self.set_state(foo: 'bar')
300
+ end
301
+
302
+ def render
303
+ div { self.state[:foo] }
304
+ end
305
+ end
306
+
307
+ expect(Foo).to render('<div>bar</div>')
308
+ end
309
+
310
+ it 'transforms state getter to Ruby object' do
311
+ Foo.class_eval do
312
+ define_state :foo
313
+
314
+ before_mount do
315
+ self.foo = [{a: "Hello"}]
316
+ end
317
+
318
+ def render
319
+ div { self.foo[0][:a] }
320
+ end
321
+ end
322
+
323
+ expect(Foo).to render('<div>Hello</div>')
324
+ end
325
+ end
326
+
327
+ describe 'Props' do
328
+ describe 'this.props could be accessed through `params` method' do
329
+ before do
330
+ stub_const 'Foo', Class.new
331
+ Foo.class_eval do
332
+ include React::Component
333
+ end
334
+ end
335
+
336
+ it 'reads from parent passed properties through `params`' do
337
+ Foo.class_eval do
338
+ def render
339
+ React.create_element('div') { params[:prop] }
340
+ end
341
+ end
342
+
343
+ element = renderToDocument(Foo, prop: 'foobar')
344
+ expect(Element[element].text).to eq('foobar')
345
+ end
346
+
347
+ it 'accesses nested params as orignal Ruby object' do
348
+ Foo.class_eval do
349
+ def render
350
+ React.create_element('div') { params[:prop][0][:foo] }
351
+ end
352
+ end
353
+
354
+ element = renderToDocument(Foo, prop: [{foo: 10}])
355
+ expect(Element[element].text).to eq('10')
356
+ end
357
+ end
358
+
359
+ describe 'Props Updating', v13_only: true do
360
+ before do
361
+ stub_const 'Foo', Class.new
362
+ Foo.class_eval do
363
+ include React::Component
364
+ end
365
+ end
366
+
367
+ it 'supports original `setProps` as method `set_props`' do
368
+ Foo.class_eval do
369
+ def render
370
+ React.create_element('div') { params[:foo] }
371
+ end
372
+ end
373
+
374
+ element = renderToDocument(Foo, {foo: 10})
375
+ element.set_props(foo: 20)
376
+ expect(`#{element.dom_node}.innerHTML`).to eq('20')
377
+ end
378
+
379
+ it 'supports original `replaceProps` as method `set_props!`' do
380
+ Foo.class_eval do
381
+ def render
382
+ React.create_element('div') { params[:foo] ? 'exist' : 'null' }
383
+ end
384
+ end
385
+
386
+ element = renderToDocument(Foo, {foo: 10})
387
+ element.set_props!(bar: 20)
388
+ expect(element.getDOMNode.innerHTML).to eq('null')
389
+ end
390
+ end
391
+
392
+ describe 'Prop validation' do
393
+ before do
394
+ stub_const 'Foo', Class.new
395
+ Foo.class_eval do
396
+ include React::Component
397
+ end
398
+ end
399
+
400
+ it 'specifies validation rules using `params` class method' do
401
+ Foo.class_eval do
402
+ params do
403
+ requires :foo, type: String
404
+ optional :bar
405
+ end
406
+ end
407
+
408
+ expect(Foo.prop_types).to have_key(:_componentValidator)
409
+ end
410
+
411
+ it 'logs error in warning if validation failed' do
412
+ stub_const 'Lorem', Class.new
413
+ Foo.class_eval do
414
+ params do
415
+ requires :foo
416
+ requires :lorem, type: Lorem
417
+ optional :bar, type: String
418
+ end
419
+
420
+ def render; div; end
421
+ end
422
+
423
+ %x{
424
+ var log = [];
425
+ var org_warn_console = window.console.warn;
426
+ var org_error_console = window.console.error;
427
+ window.console.warn = window.console.error = function(str){log.push(str)}
428
+ }
429
+ renderToDocument(Foo, bar: 10, lorem: Lorem.new)
430
+ `window.console.warn = org_warn_console; window.console.error = org_error_console;`
431
+ expect(`log[0]`).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/)
432
+ end
433
+
434
+ it 'should not log anything if validation pass' do
435
+ stub_const 'Lorem', Class.new
436
+ Foo.class_eval do
437
+ params do
438
+ requires :foo
439
+ requires :lorem, type: Lorem
440
+ optional :bar, type: String
441
+ end
442
+
443
+ def render; div; end
444
+ end
445
+
446
+ %x{
447
+ var log = [];
448
+ var org_warn_console = window.console.warn;
449
+ var org_error_console = window.console.error
450
+ window.console.warn = window.console.error = function(str){log.push(str)}
451
+ }
452
+ renderToDocument(Foo, foo: 10, bar: '10', lorem: Lorem.new)
453
+ `window.console.warn = org_warn_console; window.console.error = org_error_console;`
454
+ expect(`log`).to eq([])
455
+ end
456
+ end
457
+
458
+ describe 'Default props' do
459
+ it 'sets default props using validation helper' do
460
+ stub_const 'Foo', Class.new
461
+ Foo.class_eval do
462
+ include React::Component
463
+ params do
464
+ optional :foo, default: 'foo'
465
+ optional :bar, default: 'bar'
466
+ end
467
+
468
+ def render
469
+ div { params[:foo] + '-' + params[:bar]}
470
+ end
471
+ end
472
+
473
+ expect(Foo).to render('<div>lorem-bar</div>').with_params(foo: 'lorem')
474
+ expect(Foo).to render('<div>foo-bar</div>')
475
+ end
476
+ end
477
+ end
478
+
479
+ describe 'Anonymous Component' do
480
+ it "will not generate spurious warning messages" do
481
+ foo = Class.new(React::Component::Base)
482
+ foo.class_eval do
483
+ def render; "hello" end
484
+ end
485
+
486
+ %x{
487
+ var log = [];
488
+ var org_warn_console = window.console.warn;
489
+ var org_error_console = window.console.error
490
+ window.console.warn = window.console.error = function(str){log.push(str)}
491
+ }
492
+ renderToDocument(foo)
493
+ `window.console.warn = org_warn_console; window.console.error = org_error_console;`
494
+ expect(`log`).to eq([])
495
+ end
496
+ end
497
+
498
+ describe 'Render Error Handling' do
499
+ before(:each) do
500
+ %x{
501
+ window.test_log = [];
502
+ window.org_warn_console = window.console.warn;
503
+ window.org_error_console = window.console.error
504
+ window.console.warn = window.console.error = function(str){window.test_log.push(str)}
505
+ }
506
+ end
507
+ it "will generate a message if render returns something other than an Element or a String" do
508
+ foo = Class.new(React::Component::Base)
509
+ foo.class_eval do
510
+ def render; Hash.new; end
511
+ end
512
+
513
+ renderToDocument(foo)
514
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
515
+ expect(`test_log`.first).to match /Instead the Hash \{\} was returned/
516
+ end
517
+ it "will generate a message if render returns a Component class" do
518
+ stub_const 'Foo', Class.new(React::Component::Base)
519
+ foo = Class.new(React::Component::Base)
520
+ foo.class_eval do
521
+ def render; Foo; end
522
+ end
523
+
524
+ renderToDocument(foo)
525
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
526
+ expect(`test_log`.first).to match /Did you mean Foo()/
527
+ end
528
+ it "will generate a message if more than 1 element is generated" do
529
+ foo = Class.new(React::Component::Base)
530
+ foo.class_eval do
531
+ def render; "hello".span; "goodby".span; end
532
+ end
533
+
534
+ renderToDocument(foo)
535
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
536
+ expect(`test_log`.first).to match /Instead 2 elements were generated/
537
+ end
538
+ it "will generate a message if the element generated is not the element returned" do
539
+ foo = Class.new(React::Component::Base)
540
+ foo.class_eval do
541
+ def render; "hello".span; "goodby".span.delete; end
542
+ end
543
+
544
+ renderToDocument(foo)
545
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
546
+ expect(`test_log`.first).to match /A different element was returned than was generated within the DSL/
547
+ end
548
+ end
549
+
550
+ describe 'Event handling' do
551
+ before do
552
+ stub_const 'Foo', Class.new
553
+ Foo.class_eval do
554
+ include React::Component
555
+ end
556
+ end
557
+
558
+ it 'works in render method' do
559
+ Foo.class_eval do
560
+ define_state(:clicked) { false }
561
+
562
+ def render
563
+ React.create_element('div').on(:click) do
564
+ self.clicked = true
565
+ end
566
+ end
567
+ end
568
+
569
+ element = React.create_element(Foo)
570
+ instance = renderElementToDocument(element)
571
+ simulateEvent(:click, instance)
572
+ expect(instance.state.clicked).to eq(true)
573
+ end
574
+
575
+ it 'invokes handler on `this.props` using emit' do
576
+ Foo.class_eval do
577
+ after_mount :setup
578
+
579
+ def setup
580
+ self.emit(:foo_submit, 'bar')
581
+ end
582
+
583
+ def render
584
+ React.create_element('div')
585
+ end
586
+ end
587
+
588
+ expect { |b|
589
+ element = React.create_element(Foo).on(:foo_submit, &b)
590
+ renderElementToDocument(element)
591
+ }.to yield_with_args('bar')
592
+ end
593
+
594
+ it 'invokes handler with multiple params using emit' do
595
+ Foo.class_eval do
596
+ after_mount :setup
597
+
598
+ def setup
599
+ self.emit(:foo_invoked, [1,2,3], 'bar')
600
+ end
601
+
602
+ def render
603
+ React.create_element('div')
604
+ end
605
+ end
606
+
607
+ expect { |b|
608
+ element = React.create_element(Foo).on(:foo_invoked, &b)
609
+ renderElementToDocument(element)
610
+ }.to yield_with_args([1,2,3], 'bar')
611
+ end
612
+ end
613
+
614
+ describe '#refs' do
615
+ before do
616
+ stub_const 'Foo', Class.new
617
+ Foo.class_eval do
618
+ include React::Component
619
+ end
620
+ end
621
+
622
+ it 'correctly assigns refs' do
623
+ Foo.class_eval do
624
+ def render
625
+ React.create_element('input', type: :text, ref: :field)
626
+ end
627
+ end
628
+
629
+ element = renderToDocument(Foo)
630
+ expect(element.refs.field).not_to be_nil
631
+ end
632
+
633
+ it 'accesses refs through `refs` method' do
634
+ Foo.class_eval do
635
+ def render
636
+ React.create_element('input', type: :text, ref: :field).on(:click) do
637
+ refs[:field].value = 'some_stuff'
638
+ end
639
+ end
640
+ end
641
+
642
+ element = renderToDocument(Foo)
643
+ simulateEvent(:click, element)
644
+
645
+ expect(element.refs.field.value).to eq('some_stuff')
646
+ end
647
+
648
+ it "allows access the actual DOM node" do
649
+ Foo.class_eval do
650
+ after_mount do
651
+ dom = refs[:my_div].dom_node
652
+ `dom.innerHTML = 'Modified'`
653
+ end
654
+
655
+ def render
656
+ React.create_element('div', ref: :my_div) { "Original Content" }
657
+ end
658
+ end
659
+
660
+ instance = renderToDocument(Foo)
661
+ expect(`#{instance.dom_node}.innerHTML`).to eq('Modified')
662
+ end
663
+ end
664
+
665
+ describe '#render' do
666
+ it 'supports element building helpers' do
667
+ stub_const 'Foo', Class.new
668
+ Foo.class_eval do
669
+ include React::Component
670
+
671
+ def render
672
+ div do
673
+ span { params[:foo] }
674
+ end
675
+ end
676
+ end
677
+
678
+ stub_const 'Bar', Class.new
679
+ Bar.class_eval do
680
+ include React::Component
681
+ def render
682
+ div do
683
+ present Foo, foo: 'astring'
684
+ end
685
+ end
686
+ end
687
+
688
+ expect(Bar).to render('<div><div><span>astring</span></div></div>')
689
+ end
690
+
691
+ it 'builds single node in top-level render without providing a block' do
692
+ stub_const 'Foo', Class.new
693
+ Foo.class_eval do
694
+ include React::Component
695
+
696
+ def render
697
+ div
698
+ end
699
+ end
700
+
701
+ expect(Foo).to render('<div></div>')
702
+ end
703
+
704
+ it 'redefines `p` to make method missing work' do
705
+ stub_const 'Foo', Class.new
706
+ Foo.class_eval do
707
+ include React::Component
708
+
709
+ def render
710
+ p(class_name: 'foo') do
711
+ p
712
+ div { 'lorem ipsum' }
713
+ p(id: '10')
714
+ end
715
+ end
716
+ end
717
+
718
+ markup = '<p class="foo"><p></p><div>lorem ipsum</div><p id="10"></p></p>'
719
+ expect(Foo).to render(markup)
720
+ end
721
+
722
+ it 'only overrides `p` in render context' do
723
+ stub_const 'Foo', Class.new
724
+ Foo.class_eval do
725
+ include React::Component
726
+
727
+ before_mount do
728
+ p 'first'
729
+ end
730
+
731
+ after_mount do
732
+ p 'second'
733
+ end
734
+
735
+ def render
736
+ div
737
+ end
738
+ end
739
+
740
+ expect(Kernel).to receive(:p).with('first')
741
+ expect(Kernel).to receive(:p).with('second')
742
+ renderToDocument(Foo)
743
+ end
744
+ end
745
+
746
+ describe 'isMounted()' do
747
+ it 'returns true if after mounted' do
748
+ stub_const 'Foo', Class.new
749
+ Foo.class_eval do
750
+ include React::Component
751
+
752
+ def render
753
+ React.create_element('div')
754
+ end
755
+ end
756
+
757
+ component = renderToDocument(Foo)
758
+ expect(component.mounted?).to eq(true)
759
+ end
760
+ end
761
+
762
+ describe '.params_changed?' do
763
+
764
+ before(:each) do
765
+ stub_const 'Foo', Class.new(React::Component::Base)
766
+ Foo.define_method :needs_update? do |next_params, next_state|
767
+ next_params.changed?
768
+ end
769
+ @foo = Foo.new
770
+ end
771
+
772
+ it "returns false if new and old params are the same" do
773
+ @foo.instance_variable_set("@native", `{props: {value1: 1, value2: 2}}`)
774
+ expect(@foo.should_component_update?(`{value2: 2, value1: 1}`, `null`)).to be_falsy
775
+ end
776
+
777
+ it "returns true if new and old params are have different values" do
778
+ @foo.instance_variable_set("@native", `{props: {value1: 1, value2: 2}}`)
779
+ expect(@foo.should_component_update?(`{value2: 2, value1: 2}`, `null`)).to be_truthy
780
+ end
781
+
782
+ it "returns true if new and old params are have different keys" do
783
+ @foo.instance_variable_set("@native", `{props: {value1: 1, value2: 2}}`)
784
+ expect(@foo.should_component_update?(`{value2: 2, value1: 1, value3: 3}`, `null`)).to be_truthy
785
+ end
786
+ end
787
+
788
+ describe '#state_changed?' do
789
+
790
+ empties = [`{}`, `undefined`, `null`, `false`]
791
+
792
+ before(:each) do
793
+ stub_const 'Foo', Class.new(React::Component::Base)
794
+ Foo.define_method :needs_update? do |next_params, next_state|
795
+ next_state.changed?
796
+ end
797
+ @foo = Foo.new
798
+ end
799
+
800
+ it "returns false if both new and old states are empty" do
801
+ empties.each do |empty1|
802
+ empties.each do |empty2|
803
+ @foo.instance_variable_set("@native", `{state: #{empty1}}`)
804
+ expect(@foo.should_component_update?(`{}`, empty2)).to be_falsy
805
+ end
806
+ end
807
+ end
808
+
809
+ it "returns true if old state is empty, but new state is not" do
810
+ empties.each do |empty|
811
+ @foo.instance_variable_set("@native", `{state: #{empty}}`)
812
+ expect(@foo.should_component_update?(`{}`, `{foo: 12}`)).to be_truthy
813
+ end
814
+ end
815
+
816
+ it "returns true if new state is empty, but old state is not" do
817
+ empties.each do |empty|
818
+ @foo.instance_variable_set("@native", `{state: {foo: 12}}`)
819
+ expect(@foo.should_component_update?(`{}`, empty)).to be_truthy
820
+ end
821
+ end
822
+
823
+ it "returns true if new state and old state have different time stamps" do
824
+ @foo.instance_variable_set("@native", `{state: {'***_state_updated_at-***': 12}}`)
825
+ expect(@foo.should_component_update?(`{}`, `{'***_state_updated_at-***': 13}`)).to be_truthy
826
+ end
827
+
828
+ it "returns false if new state and old state have the same time stamps" do
829
+ @foo.instance_variable_set("@native", `{state: {'***_state_updated_at-***': 12}}`)
830
+ expect(@foo.should_component_update?(`{}`, `{'***_state_updated_at-***': 12}`)).to be_falsy
831
+ end
832
+
833
+ end
834
+
835
+ describe '#children' do
836
+ before(:each) do
837
+ stub_const 'Foo', Class.new
838
+ Foo.class_eval do
839
+ include React::Component
840
+ export_state :the_children
841
+ before_mount do
842
+ the_children! children
843
+ end
844
+ def render
845
+ React.create_element('div') { 'lorem' }
846
+ end
847
+ end
848
+ end
849
+
850
+ it 'returns React::Children collection with child elements' do
851
+ ele = React.create_element(Foo) {
852
+ [React.create_element('a'), React.create_element('li')]
853
+ }
854
+ renderElementToDocument(ele)
855
+
856
+ children = Foo.the_children
857
+
858
+ expect(children).to be_a(React::Children)
859
+ expect(children.count).to eq(2)
860
+ expect(children.map(&:element_type)).to eq(['a', 'li'])
861
+ end
862
+
863
+ it 'returns an empty Enumerator if there are no children' do
864
+ ele = React.create_element(Foo)
865
+ renderElementToDocument(ele)
866
+ nodes = Foo.the_children.each
867
+ expect(nodes.size).to eq(0)
868
+ expect(nodes.count).to eq(0)
869
+ end
870
+ end
871
+ end
872
+ end