hyper-react 0.99.6 → 1.0.0.lap21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +27 -0
  3. data/.gitignore +30 -37
  4. data/.rubocop.yml +1159 -0
  5. data/.travis.yml +32 -0
  6. data/Appraisals +31 -0
  7. data/CHANGELOG.md +143 -0
  8. data/DOCS.md +1515 -0
  9. data/Gemfile +2 -5
  10. data/LICENSE +19 -0
  11. data/README.md +5 -33
  12. data/Rakefile +25 -6
  13. data/UPGRADING.md +24 -0
  14. data/component-name-lookup.md +145 -0
  15. data/dciy.toml +3 -0
  16. data/dciy_prepare.sh +8 -0
  17. data/dciy_run.sh +10 -0
  18. data/hyper-react.gemspec +24 -18
  19. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +3 -0
  20. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js +5 -0
  21. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
  22. data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
  23. data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
  24. data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
  25. data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
  26. data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
  27. data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
  28. data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +117 -0
  29. data/lib/hyper-react.rb +66 -4
  30. data/lib/rails-helpers/top_level_rails_component.rb +75 -0
  31. data/lib/react/api.rb +203 -0
  32. data/lib/react/callbacks.rb +41 -0
  33. data/lib/react/children.rb +30 -0
  34. data/lib/react/component.rb +177 -0
  35. data/lib/react/component/api.rb +69 -0
  36. data/lib/react/component/base.rb +13 -0
  37. data/lib/react/component/class_methods.rb +181 -0
  38. data/lib/react/component/dsl_instance_methods.rb +23 -0
  39. data/lib/react/component/params.rb +6 -0
  40. data/lib/react/component/props_wrapper.rb +78 -0
  41. data/lib/react/component/should_component_update.rb +99 -0
  42. data/lib/react/component/tags.rb +108 -0
  43. data/lib/react/config.rb +5 -0
  44. data/lib/react/config/client.rb.erb +19 -0
  45. data/lib/react/config/server.rb +23 -0
  46. data/lib/react/element.rb +150 -0
  47. data/lib/react/event.rb +76 -0
  48. data/lib/react/ext/hash.rb +9 -0
  49. data/lib/react/ext/opal-jquery/element.rb +26 -0
  50. data/lib/react/ext/string.rb +8 -0
  51. data/lib/react/hash.rb +13 -0
  52. data/lib/react/native_library.rb +87 -0
  53. data/lib/react/object.rb +15 -0
  54. data/lib/react/react-source-browser.rb +3 -0
  55. data/lib/react/react-source-server.rb +3 -0
  56. data/lib/react/react-source.rb +16 -0
  57. data/lib/react/ref_callback.rb +31 -0
  58. data/lib/react/rendering_context.rb +146 -0
  59. data/lib/react/server.rb +19 -0
  60. data/lib/react/state_wrapper.rb +23 -0
  61. data/lib/react/test.rb +16 -0
  62. data/lib/react/test/dsl.rb +17 -0
  63. data/lib/react/test/matchers/render_html_matcher.rb +56 -0
  64. data/lib/react/test/rspec.rb +15 -0
  65. data/lib/react/test/session.rb +37 -0
  66. data/lib/react/test/utils.rb +71 -0
  67. data/lib/react/top_level.rb +110 -0
  68. data/lib/react/top_level_render.rb +28 -0
  69. data/lib/react/validator.rb +136 -0
  70. data/lib/reactive-ruby/component_loader.rb +43 -0
  71. data/lib/reactive-ruby/isomorphic_helpers.rb +235 -0
  72. data/lib/reactive-ruby/rails.rb +8 -0
  73. data/lib/reactive-ruby/rails/component_mount.rb +48 -0
  74. data/lib/reactive-ruby/rails/controller_helper.rb +14 -0
  75. data/lib/reactive-ruby/rails/railtie.rb +20 -0
  76. data/lib/reactive-ruby/serializers.rb +15 -0
  77. data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +41 -0
  78. data/lib/reactive-ruby/server_rendering/hyper_asset_container.rb +46 -0
  79. data/lib/reactive-ruby/version.rb +3 -0
  80. data/lib/reactrb/auto-import.rb +27 -0
  81. data/logo1.png +0 -0
  82. data/logo2.png +0 -0
  83. data/logo3.png +0 -0
  84. data/path_release_steps.md +9 -0
  85. data/spec/controller_helper_spec.rb +35 -0
  86. data/spec/index.html.erb +11 -0
  87. data/spec/react/callbacks_spec.rb +142 -0
  88. data/spec/react/children_spec.rb +132 -0
  89. data/spec/react/component/base_spec.rb +36 -0
  90. data/spec/react/component_spec.rb +1073 -0
  91. data/spec/react/dsl_spec.rb +323 -0
  92. data/spec/react/element_spec.rb +132 -0
  93. data/spec/react/event_spec.rb +39 -0
  94. data/spec/react/native_library_spec.rb +387 -0
  95. data/spec/react/observable_spec.rb +31 -0
  96. data/spec/react/opal_jquery_extensions_spec.rb +68 -0
  97. data/spec/react/param_declaration_spec.rb +253 -0
  98. data/spec/react/react_spec.rb +278 -0
  99. data/spec/react/refs_callback_spec.rb +65 -0
  100. data/spec/react/server_spec.rb +25 -0
  101. data/spec/react/state_spec.rb +52 -0
  102. data/spec/react/test/dsl_spec.rb +43 -0
  103. data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
  104. data/spec/react/test/rspec_spec.rb +62 -0
  105. data/spec/react/test/session_spec.rb +88 -0
  106. data/spec/react/test/utils_spec.rb +28 -0
  107. data/spec/react/top_level_component_spec.rb +103 -0
  108. data/spec/react/tutorial/tutorial_spec.rb +42 -0
  109. data/spec/react/validator_spec.rb +134 -0
  110. data/spec/reactive-ruby/component_loader_spec.rb +74 -0
  111. data/spec/reactive-ruby/isomorphic_helpers_spec.rb +157 -0
  112. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +17 -0
  113. data/spec/reactive-ruby/rails/component_mount_spec.rb +64 -0
  114. data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +39 -0
  115. data/spec/spec_helper.rb +55 -0
  116. data/spec/test_app/README.md +24 -0
  117. data/spec/test_app/Rakefile +6 -0
  118. data/spec/test_app/app/assets/config/manifest.js +3 -0
  119. data/spec/test_app/app/assets/images/.keep +0 -0
  120. data/spec/test_app/app/assets/javascripts/application.rb +7 -0
  121. data/spec/test_app/app/assets/javascripts/cable.js +13 -0
  122. data/spec/test_app/app/assets/javascripts/channels/.keep +0 -0
  123. data/spec/test_app/app/assets/javascripts/server_rendering.js +5 -0
  124. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  125. data/spec/test_app/app/channels/application_cable/channel.rb +4 -0
  126. data/spec/test_app/app/channels/application_cable/connection.rb +4 -0
  127. data/spec/test_app/app/controllers/application_controller.rb +3 -0
  128. data/spec/test_app/app/controllers/concerns/.keep +0 -0
  129. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  130. data/spec/test_app/app/jobs/application_job.rb +2 -0
  131. data/spec/test_app/app/mailers/application_mailer.rb +4 -0
  132. data/spec/test_app/app/models/application_record.rb +3 -0
  133. data/spec/test_app/app/models/concerns/.keep +0 -0
  134. data/spec/test_app/app/views/components.rb +11 -0
  135. data/spec/test_app/app/views/components/hello_world.rb +11 -0
  136. data/spec/test_app/app/views/components/todo.rb +14 -0
  137. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  138. data/spec/test_app/app/views/layouts/explicit_layout.html.erb +0 -0
  139. data/spec/test_app/app/views/layouts/mailer.html.erb +13 -0
  140. data/spec/test_app/app/views/layouts/mailer.text.erb +1 -0
  141. data/spec/test_app/app/views/layouts/test_layout.html.erb +0 -0
  142. data/spec/test_app/bin/bundle +3 -0
  143. data/spec/test_app/bin/rails +4 -0
  144. data/spec/test_app/bin/rake +4 -0
  145. data/spec/test_app/bin/setup +38 -0
  146. data/spec/test_app/bin/update +29 -0
  147. data/spec/test_app/bin/yarn +11 -0
  148. data/spec/test_app/config.ru +5 -0
  149. data/spec/test_app/config/application.rb +45 -0
  150. data/spec/test_app/config/boot.rb +6 -0
  151. data/spec/test_app/config/cable.yml +10 -0
  152. data/spec/test_app/config/database.yml +25 -0
  153. data/spec/test_app/config/environment.rb +5 -0
  154. data/spec/test_app/config/environments/development.rb +54 -0
  155. data/spec/test_app/config/environments/production.rb +91 -0
  156. data/spec/test_app/config/environments/test.rb +42 -0
  157. data/spec/test_app/config/initializers/application_controller_renderer.rb +8 -0
  158. data/spec/test_app/config/initializers/assets.rb +14 -0
  159. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  160. data/spec/test_app/config/initializers/cookies_serializer.rb +5 -0
  161. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  162. data/spec/test_app/config/initializers/inflections.rb +16 -0
  163. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  164. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  165. data/spec/test_app/config/locales/en.yml +33 -0
  166. data/spec/test_app/config/puma.rb +56 -0
  167. data/spec/test_app/config/routes.rb +3 -0
  168. data/spec/test_app/config/secrets.yml +32 -0
  169. data/spec/test_app/config/spring.rb +6 -0
  170. data/spec/test_app/db/development.sqlite3 +0 -0
  171. data/spec/test_app/db/schema.rb +15 -0
  172. data/spec/test_app/db/seeds.rb +7 -0
  173. data/spec/test_app/db/test.sqlite3 +0 -0
  174. data/spec/test_app/lib/assets/.keep +0 -0
  175. data/spec/test_app/log/.keep +0 -0
  176. data/spec/test_app/package.json +5 -0
  177. data/spec/test_app/public/404.html +67 -0
  178. data/spec/test_app/public/422.html +67 -0
  179. data/spec/test_app/public/500.html +66 -0
  180. data/spec/test_app/public/apple-touch-icon-precomposed.png +0 -0
  181. data/spec/test_app/public/apple-touch-icon.png +0 -0
  182. data/spec/test_app/public/favicon.ico +0 -0
  183. data/spec/vendor/es5-shim.min.js +7 -0
  184. data/spec/vendor/jquery-2.2.4.min.js +4 -0
  185. metadata +401 -61
  186. data/CODE_OF_CONDUCT.md +0 -49
  187. data/lib/react/version.rb +0 -3
@@ -0,0 +1,3 @@
1
+ module React
2
+ VERSION = '1.0.0.lap21'
3
+ end
@@ -0,0 +1,27 @@
1
+ # rubocop:disable Style/FileName
2
+ # require 'reactrb/auto-import' to automatically
3
+ # import JS libraries and components when they are detected
4
+ if RUBY_ENGINE == 'opal'
5
+ # modifies const and method_missing so that they will attempt
6
+ # to auto import native libraries and components using React::NativeLibrary
7
+ class Object
8
+ class << self
9
+ alias _reactrb_original_const_missing const_missing
10
+ alias _reactrb_original_method_missing method_missing
11
+
12
+ def const_missing(const_name)
13
+ # Opal uses const_missing to initially define things,
14
+ # so we always call the original, and respond to the exception
15
+ _reactrb_original_const_missing(const_name)
16
+ rescue StandardError => e
17
+ React::NativeLibrary.import_const_from_native(Object, const_name, true) || raise(e)
18
+ end
19
+
20
+ def method_missing(method, *args, &block)
21
+ component_class = React::NativeLibrary.import_const_from_native(self, method, false)
22
+ _reactrb_original_method_missing(method, *args, &block) unless component_class
23
+ React::RenderingContext.render(component_class, *args, &block)
24
+ end
25
+ end
26
+ end
27
+ end
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,35 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rails-controller-testing'
4
+ Rails::Controller::Testing.install
5
+
6
+ class TestController < ActionController::Base; end
7
+
8
+ RSpec.describe TestController, type: :controller do
9
+ render_views
10
+
11
+ describe '#render_component' do
12
+ controller do
13
+
14
+ layout "test_layout"
15
+
16
+ def index
17
+ render_component
18
+ end
19
+
20
+ def new
21
+ render_component "Index", {}, layout: :explicit_layout
22
+ end
23
+ end
24
+
25
+ it 'renders with the default layout' do
26
+ get :index
27
+ expect(response).to render_template(layout: :test_layout)
28
+ end
29
+
30
+ it "renders with a specified layout" do
31
+ get :new
32
+ expect(response).to render_template(layout: :explicit_layout)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ </head>
6
+ <body>
7
+ <%= javascript_include_tag 'vendor/es5-shim.min' %>
8
+ <%= javascript_include_tag 'vendor/jquery-2.2.4.min' %>
9
+ <%= javascript_include_tag @server.main %>
10
+ </body>
11
+ </html>
@@ -0,0 +1,142 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'React::Callbacks', js: true do
4
+ it 'defines callback' do
5
+ on_client do
6
+ class Foo
7
+ include React::Callbacks
8
+ define_callback :before_dinner
9
+ before_dinner :wash_hands
10
+
11
+ def wash_hands;end
12
+ end
13
+ end
14
+
15
+ expect_evaluate_ruby do
16
+ instance = Foo.new
17
+ [ instance.respond_to?(:wash_hands), instance.run_callback(:before_dinner) ]
18
+ end.to eq([true, ["wash_hands"]])
19
+ end
20
+
21
+ it 'defines multiple callbacks' do
22
+ on_client do
23
+ class Foo
24
+ include React::Callbacks
25
+ define_callback :before_dinner
26
+ before_dinner :wash_hands, :turn_off_laptop
27
+
28
+ def wash_hands;end
29
+ def turn_off_laptop;end
30
+ end
31
+ end
32
+ expect_evaluate_ruby do
33
+ instance = Foo.new
34
+ [ instance.respond_to?(:wash_hands),
35
+ instance.respond_to?(:turn_off_laptop),
36
+ instance.run_callback(:before_dinner) ]
37
+ end.to eq([true, true, ["wash_hands", "turn_off_laptop" ]])
38
+ end
39
+
40
+ context 'using Hyperloop::Context.reset!' do
41
+ #after(:all) do
42
+ # Hyperloop::Context.instance_variable_set(:@context, nil)
43
+ #end
44
+ it 'clears callbacks on Hyperloop::Context.reset!' do
45
+ on_client do
46
+ Hyperloop::Context.reset!
47
+
48
+ class Foo
49
+ include React::Callbacks
50
+ define_callback :before_dinner
51
+
52
+ before_dinner :wash_hands, :turn_off_laptop
53
+
54
+ def wash_hands;end
55
+
56
+ def turn_off_laptop;end
57
+ end
58
+ end
59
+ expect_evaluate_ruby do
60
+ instance = Foo.new
61
+
62
+ Hyperloop::Context.reset!
63
+
64
+ Foo.class_eval do
65
+ before_dinner :wash_hands
66
+ end
67
+
68
+ instance.run_callback(:before_dinner)
69
+ end.to eq(["wash_hands"])
70
+ end
71
+ end
72
+
73
+ it 'defines block callback' do
74
+ on_client do
75
+ class Foo
76
+ include React::Callbacks
77
+ attr_accessor :a
78
+ attr_accessor :b
79
+
80
+ define_callback :before_dinner
81
+
82
+ before_dinner do
83
+ self.a = 10
84
+ end
85
+ before_dinner do
86
+ self.b = 20
87
+ end
88
+ end
89
+ end
90
+ expect_evaluate_ruby do
91
+ foo = Foo.new
92
+ foo.run_callback(:before_dinner)
93
+ [ foo.a, foo.b ]
94
+ end.to eq([10, 20])
95
+ end
96
+
97
+ it 'defines multiple callback group' do
98
+ on_client do
99
+ class Foo
100
+ include React::Callbacks
101
+ define_callback :before_dinner
102
+ define_callback :after_dinner
103
+ attr_accessor :a
104
+
105
+ before_dinner do
106
+ self.a = 10
107
+ end
108
+ end
109
+ end
110
+ expect_evaluate_ruby do
111
+ foo = Foo.new
112
+ foo.run_callback(:before_dinner)
113
+ foo.run_callback(:after_dinner)
114
+ foo.a
115
+ end.to eq(10)
116
+ end
117
+
118
+ it 'receives args as callback' do
119
+ on_client do
120
+ class Foo
121
+ include React::Callbacks
122
+ define_callback :before_dinner
123
+ define_callback :after_dinner
124
+
125
+ attr_accessor :lorem
126
+
127
+ before_dinner do |a, b|
128
+ self.lorem = "#{a}-#{b}"
129
+ end
130
+
131
+ after_dinner :eat_ice_cream
132
+ def eat_ice_cream(a,b,c); end
133
+ end
134
+ end
135
+ expect_evaluate_ruby do
136
+ foo = Foo.new
137
+ foo.run_callback(:before_dinner, 1, 2)
138
+ res1 = foo.run_callback(:after_dinner, 4, 5, 6)
139
+ [res1, foo.lorem]
140
+ end.to eq([["eat_ice_cream"], '1-2'])
141
+ end
142
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'React::Children', js: true do
4
+ describe 'with multiple child elements' do
5
+ before :each do
6
+ on_client do
7
+ class InitTest
8
+ def self.get_children
9
+ component = Class.new do
10
+ include React::Component
11
+ def render
12
+ div { 'lorem' }
13
+ end
14
+ end
15
+ childs = [ React.create_element('a'), React.create_element('li') ]
16
+ element = React.create_element(component) { childs }
17
+ el_children = element.to_n.JS[:props].JS[:children]
18
+ children = React::Children.new(el_children)
19
+ dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
20
+ React.render(element, dom_el)
21
+ children
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ it 'is enumerable' do
28
+ expect_evaluate_ruby do
29
+ InitTest.get_children.map { |elem| elem.element_type }
30
+ end.to eq(['a', 'li'])
31
+ end
32
+
33
+ it 'returns an Enumerator when not providing a block' do
34
+ expect_evaluate_ruby do
35
+ nodes = InitTest.get_children.each
36
+ [nodes.class.name, nodes.size]
37
+ end.to eq(["Enumerator", 2])
38
+ end
39
+
40
+ describe '#each' do
41
+ it 'returns an array of elements' do
42
+ expect_evaluate_ruby do
43
+ nodes = InitTest.get_children.each { |elem| elem.element_type }
44
+ [nodes.class.name, nodes.map(&:class)]
45
+ end.to eq(["Array", ["React::Element", "React::Element"]])
46
+ end
47
+ end
48
+
49
+ describe '#length' do
50
+ it 'returns the number of child elements' do
51
+ expect_evaluate_ruby do
52
+ InitTest.get_children.length
53
+ end.to eq(2)
54
+ end
55
+ end
56
+ end
57
+
58
+ describe 'with single child element' do
59
+ before :each do
60
+ on_client do
61
+ class InitTest
62
+ def self.get_children
63
+ component = Class.new do
64
+ include React::Component
65
+ def render
66
+ div { 'lorem' }
67
+ end
68
+ end
69
+ childs = [ React.create_element('a') ]
70
+ element = React.create_element(component) { childs }
71
+ el_children = element.to_n.JS[:props].JS[:children]
72
+ children = React::Children.new(el_children)
73
+ dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
74
+ React.render(element, dom_el)
75
+ children
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ it 'is enumerable containing single element' do
82
+ expect_evaluate_ruby do
83
+ InitTest.get_children.map { |elem| elem.element_type }
84
+ end.to eq(["a"])
85
+ end
86
+
87
+ describe '#length' do
88
+ it 'returns the number of child elements' do
89
+ expect_evaluate_ruby do
90
+ InitTest.get_children.length
91
+ end.to eq(1)
92
+ end
93
+ end
94
+ end
95
+
96
+ describe 'with no child element' do
97
+ before :each do
98
+ on_client do
99
+ class InitTest
100
+ def self.get_children
101
+ component = Class.new do
102
+ include React::Component
103
+ def render
104
+ div { 'lorem' }
105
+ end
106
+ end
107
+ element = React.create_element(component)
108
+ el_children = element.to_n.JS[:props].JS[:children]
109
+ children = React::Children.new(el_children)
110
+ dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
111
+ React.render(element, dom_el)
112
+ children
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ it 'is enumerable containing no elements' do
119
+ expect_evaluate_ruby do
120
+ InitTest.get_children.map { |elem| elem.element_type }
121
+ end.to eq([])
122
+ end
123
+
124
+ describe '#length' do
125
+ it 'returns the number of child elements' do
126
+ expect_evaluate_ruby do
127
+ InitTest.get_children.length
128
+ end.to eq(0)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'React::Component::Base', js: true do
4
+
5
+ before :each do
6
+ on_client do
7
+ class Foo < React::Component::Base
8
+ before_mount do
9
+ @instance_data = ["working"]
10
+ end
11
+ def render
12
+ @instance_data.first
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ it 'can create a simple component class' do
19
+ mount 'Foo'
20
+ expect(page.body[-50..-19]).to match(/<span>working<\/span>/)
21
+ end
22
+
23
+ it 'can create a simple component class that can be inherited to create another component class' do
24
+ mount 'Bar' do
25
+ class Bar < Foo
26
+ before_mount do
27
+ @instance_data << "well"
28
+ end
29
+ def render
30
+ @instance_data.join(" ")
31
+ end
32
+ end
33
+ end
34
+ expect(page.body[-50..-19]).to match(/<span>working well<\/span>/)
35
+ end
36
+ end
@@ -0,0 +1,1073 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'React::Component', js: true do
4
+
5
+ it 'defines component spec methods' do
6
+ on_client do
7
+ class Foo
8
+ include React::Component
9
+ def initialize(native = nil)
10
+ end
11
+
12
+ def render
13
+ React.create_element('div')
14
+ end
15
+ end
16
+ end
17
+ # class methods
18
+ expect_evaluate_ruby("Foo.respond_to?(:initial_state)").to be_truthy
19
+ expect_evaluate_ruby("Foo.respond_to?(:default_props)").to be_truthy
20
+ expect_evaluate_ruby("Foo.respond_to?(:prop_types)").to be_truthy
21
+ # instance_methods
22
+ expect_evaluate_ruby("Foo.new.respond_to?(:component_will_mount)").to be_truthy
23
+ expect_evaluate_ruby("Foo.new.respond_to?(:component_did_mount)").to be_truthy
24
+ expect_evaluate_ruby("Foo.new.respond_to?(:component_will_receive_props)").to be_truthy
25
+ expect_evaluate_ruby("Foo.new.respond_to?(:should_component_update?)").to be_truthy
26
+ expect_evaluate_ruby("Foo.new.respond_to?(:component_will_update)").to be_truthy
27
+ expect_evaluate_ruby("Foo.new.respond_to?(:component_did_update)").to be_truthy
28
+ expect_evaluate_ruby("Foo.new.respond_to?(:component_will_unmount)").to be_truthy
29
+ end
30
+
31
+ describe 'Life Cycle' do
32
+ before(:each) do
33
+ on_client do
34
+ class Foo
35
+ include React::Component
36
+ def self.call_history
37
+ @call_history ||= []
38
+ end
39
+ def render
40
+ React.create_element('div') { 'lorem' }
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'invokes `before_mount` registered methods when `componentWillMount()`' do
47
+ mount 'Foo' do
48
+ Foo.class_eval do
49
+ before_mount :bar, :bar2
50
+ def bar; self.class.call_history << "bar"; end
51
+ def bar2; self.class.call_history << "bar2"; end
52
+ end
53
+ end
54
+ expect_evaluate_ruby("Foo.call_history").to eq(["bar", "bar2"])
55
+ end
56
+
57
+ it 'invokes `after_mount` registered methods when `componentDidMount()`' do
58
+ mount 'Foo' do
59
+ Foo.class_eval do
60
+ after_mount :bar3, :bar4
61
+ def bar3; self.class.call_history << "bar3"; end
62
+ def bar4; self.class.call_history << "bar4"; end
63
+ end
64
+ end
65
+ expect_evaluate_ruby("Foo.call_history").to eq(["bar3", "bar4"])
66
+ end
67
+
68
+ it 'allows multiple class declared life cycle hooker' do
69
+ evaluate_ruby do
70
+ Foo.class_eval do
71
+ before_mount :bar
72
+ def bar; self.class.call_history << "bar"; end
73
+ end
74
+
75
+ class FooBar
76
+ include React::Component
77
+ after_mount :bar2
78
+ def self.call_history
79
+ @call_history ||= []
80
+ end
81
+ def bar2; self.class.call_history << "bar2"; end
82
+ def render
83
+ React.create_element('div') { 'lorem' }
84
+ end
85
+ end
86
+ instance = React::Test::Utils.render_component_into_document(Foo)
87
+ instance = React::Test::Utils.render_component_into_document(FooBar)
88
+ end
89
+ expect_evaluate_ruby("Foo.call_history").to eq(["bar"])
90
+ expect_evaluate_ruby("FooBar.call_history").to eq(["bar2"])
91
+ end
92
+
93
+ it 'allows block for life cycle callback' do
94
+ expect_evaluate_ruby do
95
+ Foo.class_eval do
96
+ before_mount do
97
+ set_state({ foo: "bar" })
98
+ end
99
+ end
100
+ instance = React::Test::Utils.render_component_into_document(Foo)
101
+ instance.state[:foo]
102
+ end.to eq('bar')
103
+ end
104
+
105
+ it 'invokes :after_error when componentDidCatch' do
106
+ client_option raise_on_js_errors: :off
107
+ mount 'Foo' do
108
+ class ErrorFoo
109
+ include Hyperloop::Component::Mixin
110
+ param :just
111
+ def render
112
+ raise 'ErrorFoo Error'
113
+ end
114
+ end
115
+ Foo.class_eval do
116
+ def self.get_error
117
+ @@error
118
+ end
119
+
120
+ def self.get_info
121
+ @@info
122
+ end
123
+
124
+ def render
125
+ DIV { ErrorFoo(just: :a_param) }
126
+ end
127
+
128
+ after_error do |error, info|
129
+ @@error = error.message
130
+ @@info = info[:componentStack]
131
+ end
132
+ end
133
+ end
134
+ expect_evaluate_ruby('Foo.get_error').to eq('ErrorFoo Error')
135
+ expect_evaluate_ruby('Foo.get_info').to eq("\n in ErrorFoo\n in div\n in Foo\n in React::TopLevelRailsComponent")
136
+ end
137
+ end
138
+
139
+ describe 'New style setter & getter' do
140
+ before(:each) do
141
+ on_client do
142
+ class Foo
143
+ include React::Component
144
+ def render
145
+ div { state.foo }
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ it 'implicitly will create a state variable when first written' do
152
+ mount 'Foo' do
153
+ Foo.class_eval do
154
+ before_mount do
155
+ state.foo! 'bar'
156
+ end
157
+ end
158
+ end
159
+ # this was a 'have_xpath' check, but these are totally unreliable in capybara with webdrivers
160
+ # leading to false positives and negatives
161
+ # this simple check for string inclusion makes this checks reliable
162
+ expect(page.body[-35..-19]).to include("<div>bar</div>")
163
+ end
164
+
165
+ it 'allows kernal method names like "format" to be used as state variable names' do
166
+ mount 'Foo' do
167
+ Foo.class_eval do
168
+ before_mount do
169
+ state.format! 'yes'
170
+ state.foo! state.format
171
+ end
172
+ end
173
+ end
174
+ expect(page.body[-35..-19]).to include("<div>yes</div>")
175
+ end
176
+
177
+ it 'returns an observer with the bang method and no arguments' do
178
+ mount 'Foo' do
179
+ Foo.class_eval do
180
+ before_mount do
181
+ state.foo!(state.baz!.class.name)
182
+ end
183
+ end
184
+ end
185
+ expect(page.body[-50..-19]).to include("<div>React::Observable</div>")
186
+ end
187
+
188
+ it 'returns the current value of a state when written' do
189
+ mount 'Foo' do
190
+ Foo.class_eval do
191
+ before_mount do
192
+ state.baz! 'bar'
193
+ state.foo!(state.baz!('pow'))
194
+ end
195
+ end
196
+ end
197
+ expect(page.body[-35..-19]).to include("<div>bar</div>")
198
+ end
199
+
200
+ it 'can access an explicitly defined state`' do
201
+ mount 'Foo' do
202
+ Foo.class_eval do
203
+ define_state foo: :bar
204
+ end
205
+ end
206
+ expect(page.body[-35..-19]).to include("<div>bar</div>")
207
+ end
208
+ end
209
+
210
+ describe 'State setter & getter' do
211
+ before(:each) do
212
+ on_client do
213
+ class Foo
214
+ include React::Component
215
+ def render
216
+ React.create_element('div') { 'lorem' }
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ it 'defines setter using `define_state`' do
223
+ expect_evaluate_ruby do
224
+ Foo.class_eval do
225
+ define_state :foo
226
+ before_mount :set_up
227
+ def set_up
228
+ mutate.foo 'bar'
229
+ end
230
+ end
231
+ instance = React::Test::Utils.render_component_into_document(Foo)
232
+ instance.state.foo
233
+ end.to eq('bar')
234
+ end
235
+
236
+ it 'defines init state by passing a block to `define_state`' do
237
+ expect_evaluate_ruby do
238
+ element_to_render = React.create_element(Foo)
239
+ Foo.class_eval do
240
+ define_state(:foo) { 10 }
241
+ end
242
+ dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
243
+ instance = React.render(element_to_render, dom_el)
244
+ instance.state.foo
245
+ end.to eq(10)
246
+ end
247
+
248
+ it 'defines getter using `define_state`' do
249
+ expect_evaluate_ruby do
250
+ Foo.class_eval do
251
+ define_state(:foo) { 10 }
252
+ before_mount :bump
253
+ def bump
254
+ mutate.foo(state.foo + 20)
255
+ end
256
+ end
257
+ instance = React::Test::Utils.render_component_into_document(Foo)
258
+ instance.state.foo
259
+ end.to eq(30)
260
+ end
261
+
262
+ it 'defines multiple state accessors by passing array to `define_state`' do
263
+ expect_evaluate_ruby do
264
+ Foo.class_eval do
265
+ define_state :foo, :foo2
266
+ before_mount :set_up
267
+ def set_up
268
+ mutate.foo 10
269
+ mutate.foo2 20
270
+ end
271
+ end
272
+ instance = React::Test::Utils.render_component_into_document(Foo)
273
+ [ instance.state.foo, instance.state.foo2 ]
274
+ end.to eq([10, 20])
275
+ end
276
+
277
+ it 'invokes `define_state` multiple times to define states' do
278
+ expect_evaluate_ruby do
279
+ Foo.class_eval do
280
+ define_state(:foo) { 30 }
281
+ define_state(:foo2) { 40 }
282
+ end
283
+ instance = React::Test::Utils.render_component_into_document(Foo)
284
+ [ instance.state.foo, instance.state.foo2 ]
285
+ end.to eq([30, 40])
286
+ end
287
+
288
+ it 'can initialize multiple state variables with a block' do
289
+ expect_evaluate_ruby do
290
+ Foo.class_eval do
291
+ define_state(:foo, :foo2) { 30 }
292
+ end
293
+ instance = React::Test::Utils.render_component_into_document(Foo)
294
+ [ instance.state.foo, instance.state.foo2 ]
295
+ end.to eq([30, 30])
296
+ end
297
+
298
+ it 'can mix multiple state variables with initializers and a block' do
299
+ expect_evaluate_ruby do
300
+ Foo.class_eval do
301
+ define_state(:x, :y, foo: 1, bar: 2) {3}
302
+ end
303
+ instance = React::Test::Utils.render_component_into_document(Foo)
304
+ [ instance.state.x, instance.state.y, instance.state.foo, instance.state.bar ]
305
+ end.to eq([3, 3, 1, 2])
306
+ end
307
+
308
+ it 'gets state in render method' do
309
+ mount 'Foo' do
310
+ Foo.class_eval do
311
+ define_state(:foo) { 10 }
312
+ def render
313
+ React.create_element('div') { state.foo }
314
+ end
315
+ end
316
+ end
317
+ expect(page.body[-35..-19]).to include("<div>10</div>")
318
+ end
319
+
320
+ it 'supports original `setState` as `set_state` method' do
321
+ expect_evaluate_ruby do
322
+ Foo.class_eval do
323
+ before_mount do
324
+ self.set_state(foo: 'bar')
325
+ end
326
+ end
327
+ instance = React::Test::Utils.render_component_into_document(Foo)
328
+ instance.state[:foo]
329
+ end.to eq('bar')
330
+ end
331
+
332
+ it '`set_state!` method works and doesnt replace other state' do
333
+ # this test changed because the function replaceState is gone in react
334
+ expect_evaluate_ruby do
335
+ Foo.class_eval do
336
+ before_mount do
337
+ set_state(foo: 'bar')
338
+ set_state!(bar: 'lorem')
339
+ end
340
+ end
341
+ instance = React::Test::Utils.render_component_into_document(Foo)
342
+ [ instance.state[:foo], instance.state[:bar] ]
343
+ end.to eq(['bar', 'lorem'])
344
+ end
345
+
346
+ it 'supports original `state` method' do
347
+ mount 'Foo' do
348
+ Foo.class_eval do
349
+ before_mount do
350
+ self.set_state(foo: 'bar')
351
+ end
352
+
353
+ def render
354
+ div { self.state[:foo] }
355
+ end
356
+ end
357
+ end
358
+ expect(page.body[-35..-19]).to include("<div>bar</div>")
359
+ end
360
+
361
+ it 'transforms state getter to Ruby object' do
362
+ mount 'Foo' do
363
+ Foo.class_eval do
364
+ define_state :foo
365
+
366
+ before_mount do
367
+ mutate.foo [{a: "Hello"}]
368
+ end
369
+
370
+ def render
371
+ div { state.foo[0][:a] }
372
+ end
373
+ end
374
+ end
375
+ expect(page.body[-40..-19]).to include("<div>Hello</div>")
376
+ end
377
+
378
+ it 'sets initial state with default value in constructor in @native object state property' do
379
+ mount 'StateFoo' do
380
+ class StateFoo
381
+ include Hyperloop::Component::Mixin
382
+ state bar: 25
383
+
384
+ def initialize(native)
385
+ super(native)
386
+ @@initial_state = @native.JS[:state].JS[:bar]
387
+ end
388
+
389
+ def self.initial_state
390
+ @@initial_state ||= 0
391
+ end
392
+
393
+ def render
394
+ React.create_element('div') { 'lorem' }
395
+ end
396
+ end
397
+ end
398
+ expect_evaluate_ruby('StateFoo.initial_state').to eq(25)
399
+ end
400
+
401
+ it 'doesnt cause extra render when setting initial state' do
402
+ mount 'StateFoo' do
403
+ class StateFoo
404
+ include Hyperloop::Component::Mixin
405
+ state bar: 25
406
+
407
+ def self.render_count
408
+ @@render_count ||= 0
409
+ end
410
+ def self.incr_render_count
411
+ @@render_count ||= 0
412
+ @@render_count += 1
413
+ end
414
+
415
+ def render
416
+ StateFoo.incr_render_count
417
+ React.create_element('div') { 'lorem' }
418
+ end
419
+ end
420
+ end
421
+ expect_evaluate_ruby('StateFoo.render_count').to eq(1)
422
+ end
423
+
424
+ it 'doesnt cause extra render when setting state in :before_mount' do
425
+ mount 'StateFoo' do
426
+ class StateFoo
427
+ include Hyperloop::Component::Mixin
428
+
429
+ def self.render_count
430
+ @@render_count ||= 0
431
+ end
432
+ def self.incr_render_count
433
+ @@render_count ||= 0
434
+ @@render_count += 1
435
+ end
436
+
437
+ before_mount do
438
+ mutate.bar 50
439
+ end
440
+
441
+ def render
442
+ StateFoo.incr_render_count
443
+ React.create_element('div') { 'lorem' }
444
+ end
445
+ end
446
+ end
447
+ expect_evaluate_ruby('StateFoo.render_count').to eq(1)
448
+ end
449
+
450
+
451
+ it 'doesnt cause extra render when setting state in :before_receive_props' do
452
+ mount 'Foo' do
453
+ class StateFoo
454
+ include Hyperloop::Component::Mixin
455
+
456
+ param :drinks
457
+
458
+ def self.render_count
459
+ @@render_count ||= 0
460
+ end
461
+ def self.incr_render_count
462
+ @@render_count ||= 0
463
+ @@render_count += 1
464
+ end
465
+
466
+ before_receive_props do |new_params|
467
+ mutate.bar 50
468
+ end
469
+
470
+ def render
471
+ StateFoo.incr_render_count
472
+ React.create_element('div') { 'lorem' }
473
+ end
474
+ end
475
+
476
+ Foo.class_eval do
477
+ define_state :foo
478
+
479
+ before_mount do
480
+ state.foo 25
481
+ end
482
+
483
+ def render
484
+ div { StateFoo(drinks: state.foo) }
485
+ end
486
+
487
+ after_mount do
488
+ mutate.foo 50
489
+ end
490
+ end
491
+ end
492
+ expect_evaluate_ruby('StateFoo.render_count').to eq(2)
493
+ end
494
+ end
495
+
496
+ describe 'Props' do
497
+ describe 'this.props could be accessed through `params` method' do
498
+ before do
499
+ on_client do
500
+ class Foo
501
+ include React::Component
502
+ end
503
+ end
504
+ end
505
+
506
+ it 'reads from parent passed properties through `params`' do
507
+ mount 'Foo', prop: 'foobar' do
508
+ Foo.class_eval do
509
+ param :prop
510
+ def render
511
+ React.create_element('div') { params[:prop] }
512
+ end
513
+ end
514
+ end
515
+ expect(page.body[-40..-19]).to include("<div>foobar</div>")
516
+ end
517
+
518
+ it 'accesses nested params as orignal Ruby object' do
519
+ mount 'Foo', prop: [{foo: 10}] do
520
+ Foo.class_eval do
521
+ param :prop
522
+ def render
523
+ React.create_element('div') { params[:prop][0][:foo] }
524
+ end
525
+ end
526
+ end
527
+ expect(page.body[-35..-19]).to include("<div>10</div>")
528
+ end
529
+ end
530
+
531
+ describe 'Props Updating', v13_only: true do
532
+ before do
533
+ on_client do
534
+ class Foo
535
+ include React::Component
536
+ end
537
+ end
538
+ end
539
+
540
+ it '`setProps` as method `set_props` is no longer supported' do
541
+ expect_evaluate_ruby do
542
+ Foo.class_eval do
543
+ param :foo
544
+ def render
545
+ React.create_element('div') { params[:foo] }
546
+ end
547
+ end
548
+ instance = React::Test::Utils.render_component_into_document(Foo, foo: 10)
549
+ begin
550
+ instance.set_props(foo: 20)
551
+ rescue
552
+ 'got risen'
553
+ end
554
+ end.to eq('got risen')
555
+ end
556
+
557
+ it 'original `replaceProps` as method `set_props!` is no longer supported' do
558
+ expect_evaluate_ruby do
559
+ Foo.class_eval do
560
+ param :foo
561
+ def render
562
+ React.create_element('div') { params[:foo] ? 'exist' : 'null' }
563
+ end
564
+ end
565
+ instance = React::Test::Utils.render_component_into_document(Foo, foo: 10)
566
+ begin
567
+ instance.set_props!(bar: 20)
568
+ rescue
569
+ 'got risen'
570
+ end
571
+ end.to eq('got risen')
572
+ end
573
+ end
574
+
575
+ describe 'Prop validation' do
576
+ before do
577
+ on_client do
578
+ class Foo
579
+ include Hyperloop::Component::Mixin
580
+ end
581
+ end
582
+ end
583
+
584
+ it 'specifies validation rules using `params` class method' do
585
+ expect_evaluate_ruby do
586
+ Foo.class_eval do
587
+ params do
588
+ requires :foo, type: String
589
+ optional :bar
590
+ end
591
+ end
592
+ Foo.prop_types
593
+ end.to have_key('_componentValidator')
594
+ end
595
+
596
+ it 'logs error in warning if validation failed' do
597
+ evaluate_ruby do
598
+ class Lorem; end
599
+ Foo.class_eval do
600
+ params do
601
+ requires :foo
602
+ requires :lorem, type: Lorem
603
+ optional :bar, type: String
604
+ end
605
+
606
+ def render; div; end
607
+ end
608
+ React::Test::Utils.render_component_into_document(Foo, bar: 10, lorem: Lorem.new)
609
+ end
610
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
611
+ .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/)
612
+ end
613
+
614
+ it 'should not log anything if validation pass' do
615
+ evaluate_ruby do
616
+ class Lorem; end
617
+ Foo.class_eval do
618
+ params do
619
+ requires :foo
620
+ requires :lorem, type: Lorem
621
+ optional :bar, type: String
622
+ end
623
+
624
+ def render; div; end
625
+ end
626
+ React::Test::Utils.render_component_into_document(Foo, foo: 10, bar: '10', lorem: Lorem.new)
627
+ end
628
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")).to_not match(/prop/)
629
+ end
630
+ end
631
+
632
+ describe 'Default props' do
633
+ it 'sets default props using validation helper' do
634
+ on_client do
635
+ class Foo
636
+ include React::Component
637
+ params do
638
+ optional :foo, default: 'foo'
639
+ optional :bar, default: 'bar'
640
+ end
641
+
642
+ def render
643
+ div { params[:foo] + '-' + params[:bar]}
644
+ end
645
+ end
646
+ end
647
+ mount 'Foo'
648
+ expect(page.body[-40..-19]).to include("<div>foo-bar</div>")
649
+ mount 'Foo', foo: 'lorem'
650
+ expect(page.body[-40..-19]).to include("<div>lorem-bar</div>")
651
+ end
652
+ end
653
+ end
654
+
655
+ describe 'Anonymous Component' do
656
+ it "will not generate spurious warning messages" do
657
+ evaluate_ruby do
658
+ foo = Class.new(React::Component::Base)
659
+ foo.class_eval do
660
+ def render; "hello" end
661
+ end
662
+
663
+ React::Test::Utils.render_component_into_document(foo)
664
+ end
665
+ expect(page.driver.browser.manage.logs.get(:browser)
666
+ .reject { |entry| entry.to_s.include?("Deprecated feature") }
667
+ .map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n").size)
668
+ .to eq(0)
669
+ end
670
+ end
671
+
672
+ describe 'Render Error Handling' do
673
+ it "will generate a message if render returns something other than an Element or a String" do
674
+ mount 'Foo' do
675
+ class Foo < React::Component::Base
676
+ def render; Hash.new; end
677
+ end
678
+ end
679
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
680
+ .to match(/Instead the Hash \{\} was returned/)
681
+ end
682
+ it "will generate a message if render returns a Component class" do
683
+ mount 'Foo' do
684
+ class Foo < React::Component::Base
685
+ def render; Foo; end
686
+ end
687
+ end
688
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
689
+ .to match(/Did you mean Foo()/)
690
+ end
691
+ it "will generate a message if more than 1 element is generated" do
692
+ mount 'Foo' do
693
+ class Foo < React::Component::Base
694
+ def render; "hello".span; "goodby".span; end
695
+ end
696
+ end
697
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
698
+ .to match(/Instead 2 elements were generated/)
699
+ end
700
+ it "will generate a message if the element generated is not the element returned" do
701
+ mount 'Foo' do
702
+ class Foo < React::Component::Base
703
+ def render; "hello".span; "goodby".span.delete; end
704
+ end
705
+ end
706
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
707
+ .to match(/A different element was returned than was generated within the DSL/)
708
+ end
709
+ end
710
+
711
+ describe 'Event handling' do
712
+ before do
713
+ on_client do
714
+ class Foo
715
+ include React::Component
716
+ end
717
+ end
718
+ end
719
+
720
+ it 'works in render method' do
721
+ expect_evaluate_ruby do
722
+ Foo.class_eval do
723
+ define_state(:clicked) { false }
724
+
725
+ def render
726
+ React.create_element('div').on(:click) do
727
+ mutate.clicked true
728
+ end
729
+ end
730
+ end
731
+ instance = React::Test::Utils.render_component_into_document(Foo)
732
+ React::Test::Utils.simulate_click(instance)
733
+ instance.state.clicked
734
+ end.to eq(true)
735
+ end
736
+
737
+ it 'invokes handler on `this.props` using emit' do
738
+ on_client do
739
+ Foo.class_eval do
740
+ param :on_foo_fubmit, type: Proc
741
+ after_mount :setup
742
+
743
+ def setup
744
+ self.emit(:foo_submit, 'bar')
745
+ end
746
+
747
+ def render
748
+ React.create_element('div')
749
+ end
750
+ end
751
+ end
752
+ evaluate_ruby do
753
+ element = React.create_element(Foo).on(:foo_submit) { 'bar' }
754
+ React::Test::Utils.render_into_document(element)
755
+ end
756
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
757
+ .to_not match(/Exception raised/)
758
+ end
759
+
760
+ it 'invokes handler with multiple params using emit' do
761
+ on_client do
762
+ Foo.class_eval do
763
+ param :on_foo_invoked, type: Proc
764
+ after_mount :setup
765
+
766
+ def setup
767
+ self.emit(:foo_invoked, [1,2,3], 'bar')
768
+ end
769
+
770
+ def render
771
+ React.create_element('div')
772
+ end
773
+ end
774
+ end
775
+
776
+ evaluate_ruby do
777
+ element = React.create_element(Foo).on(:foo_invoked) { return [1,2,3], 'bar' }
778
+ React::Test::Utils.render_into_document(element)
779
+ end
780
+ expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
781
+ .to_not match(/Exception raised/)
782
+ end
783
+ end
784
+
785
+ describe '#render' do
786
+ it 'supports element building helpers' do
787
+ on_client do
788
+ class Foo
789
+ include React::Component
790
+ param :foo
791
+ def render
792
+ div do
793
+ span { params[:foo] }
794
+ end
795
+ end
796
+ end
797
+
798
+ class Bar
799
+ include React::Component
800
+ def render
801
+ div do
802
+ React::RenderingContext.render(Foo, foo: 'astring')
803
+ end
804
+ end
805
+ end
806
+ end
807
+ evaluate_ruby do
808
+ React::Test::Utils.render_component_into_document(Bar)
809
+ end
810
+ expect(page.body[-80..-19]).to include("<div><div><span>astring</span></div></div>")
811
+ end
812
+
813
+ it 'builds single node in top-level render without providing a block' do
814
+ mount 'Foo' do
815
+ class Foo
816
+ include React::Component
817
+
818
+ def render
819
+ div
820
+ end
821
+ end
822
+ end
823
+ expect(page.body).to include('<div data-react-class="React.TopLevelRailsComponent" data-react-props="{&quot;render_params&quot;:{},&quot;component_name&quot;:&quot;Foo&quot;,&quot;controller&quot;:&quot;ReactTest&quot;}"><div></div></div>')
824
+ end
825
+
826
+ it 'redefines `p` to make method missing work' do
827
+ mount 'Foo' do
828
+ class Foo
829
+ include React::Component
830
+
831
+ def render
832
+ div {
833
+ p(class_name: 'foo')
834
+ p
835
+ div { 'lorem ipsum' }
836
+ p(id: '10')
837
+ }
838
+ end
839
+ end
840
+ end
841
+ expect(page.body).to include('<div><p class="foo"></p><p></p><div>lorem ipsum</div><p id="10"></p></div>')
842
+ end
843
+
844
+ it 'only overrides `p` in render context' do
845
+ mount 'Foo' do
846
+
847
+ class Foo
848
+ include React::Component
849
+
850
+ def self.result
851
+ @@result ||= 'ooopsy'
852
+ end
853
+
854
+ def self.result_two
855
+ @@result_two ||= 'ooopsy'
856
+ end
857
+
858
+ before_mount do
859
+ @@result = p 'first'
860
+ end
861
+
862
+ after_mount do
863
+ @@result_two = p 'second'
864
+ end
865
+
866
+ def render
867
+ p do
868
+ 'third'
869
+ end
870
+ end
871
+ end
872
+ end
873
+ expect_evaluate_ruby('Kernel.p "first"').to eq('first')
874
+ expect_evaluate_ruby('p "second"').to eq('second')
875
+ expect_evaluate_ruby('Foo.result').to eq('first')
876
+ expect_evaluate_ruby('Foo.result_two').to eq('second')
877
+ expect(page.body[-40..-10]).to include("<p>third</p>")
878
+ expect(page.body[-40..-10]).not_to include("<p>first</p>")
879
+ end
880
+ end
881
+
882
+ describe 'new react 15/16 custom isMounted implementation' do
883
+ it 'returns true if after mounted' do
884
+ expect_evaluate_ruby do
885
+ class Foo
886
+ include React::Component
887
+
888
+ def render
889
+ React.create_element('div')
890
+ end
891
+ end
892
+
893
+ component = React::Test::Utils.render_component_into_document(Foo)
894
+ component.mounted?
895
+ end.to eq(true)
896
+ end
897
+ end
898
+
899
+ describe '.params_changed?' do
900
+
901
+ before(:each) do
902
+ on_client do
903
+ class Foo < React::Component::Base
904
+ def needs_update?(next_params, next_state)
905
+ next_params.changed?
906
+ end
907
+ end
908
+ end
909
+ end
910
+
911
+ it "returns false if new and old params are the same" do
912
+ expect_evaluate_ruby do
913
+ @foo = Foo.new(nil)
914
+ @foo.instance_eval { @native.JS[:props] = JS.call(:eval, 'function bla(){return {value1: 1, value2: 2};}bla();') }
915
+ @foo.should_component_update?({ value2: 2, value1: 1 }, {})
916
+ end.to be_falsy
917
+ end
918
+
919
+ it "returns true if new and old params are have different values" do
920
+ expect_evaluate_ruby do
921
+ @foo = Foo.new(nil)
922
+ @foo.instance_eval { @native.JS[:props] = JS.call(:eval, 'function bla(){return {value1: 1, value2: 2};}bla();') }
923
+ @foo.should_component_update?({value2: 2, value1: 2}, {})
924
+ end.to be_truthy
925
+ end
926
+
927
+ it "returns true if new and old params are have different keys" do
928
+ expect_evaluate_ruby do
929
+ @foo = Foo.new(nil)
930
+ @foo.instance_eval { @native.JS[:props] = JS.call(:eval, 'function bla(){return {value1: 1, value2: 2};}bla();') }
931
+ @foo.should_component_update?({value2: 2, value1: 1, value3: 3}, {})
932
+ end.to be_truthy
933
+ end
934
+ end
935
+
936
+ describe '#should_component_update?' do
937
+
938
+ before(:each) do
939
+ on_client do
940
+ class Foo < React::Component::Base
941
+ def needs_update?(next_params, next_state)
942
+ next_state.changed?
943
+ end
944
+ end
945
+
946
+ EMPTIES = [`{}`, `undefined`, `null`, `false`]
947
+ end
948
+ end
949
+
950
+ it "returns false if both new and old states are empty" do
951
+ expect_evaluate_ruby do
952
+ @foo = Foo.new(nil)
953
+ return_values = []
954
+ EMPTIES.each do |empty1|
955
+ EMPTIES.each do |empty2|
956
+ @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return #{empty1};}bla();") }
957
+ return_values << @foo.should_component_update?({}, Hash.new(empty2))
958
+ end
959
+ end
960
+ return_values
961
+ end.to all( be_falsy )
962
+ end
963
+
964
+ it "returns true if old state is empty, but new state is not" do
965
+ expect_evaluate_ruby do
966
+ @foo = Foo.new(nil)
967
+ return_values = []
968
+ EMPTIES.each do |empty|
969
+ @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return #{empty};}bla();") }
970
+ return_values << @foo.should_component_update?({}, {foo: 12})
971
+ end
972
+ return_values
973
+ end.to all( be_truthy )
974
+ end
975
+
976
+ it "returns true if new state is empty, but old state is not" do
977
+ expect_evaluate_ruby do
978
+ @foo = Foo.new(nil)
979
+ return_values = []
980
+ EMPTIES.each do |empty|
981
+ @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {foo: 12};}bla();") }
982
+ return_values << @foo.should_component_update?({}, Hash.new(empty))
983
+ end
984
+ return_values
985
+ end.to all( be_truthy )
986
+ end
987
+
988
+ it "returns true if new state and old state have different time stamps" do
989
+ expect_evaluate_ruby do
990
+ @foo = Foo.new(nil)
991
+ return_values = []
992
+ EMPTIES.each do |empty|
993
+ @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'***_state_updated_at-***': 12};}bla();") }
994
+ return_values << @foo.should_component_update?({}, {'***_state_updated_at-***' => 13})
995
+ end
996
+ return_values
997
+ end.to all ( be_truthy )
998
+ end
999
+
1000
+ it "returns false if new state and old state have the same time stamps" do
1001
+ expect_evaluate_ruby do
1002
+ @foo = Foo.new(nil)
1003
+ return_values = []
1004
+ EMPTIES.each do |empty|
1005
+ @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'***_state_updated_at-***': 12};}bla();") }
1006
+ return_values << @foo.should_component_update?({}, {'***_state_updated_at-***' => 12})
1007
+ end
1008
+ return_values
1009
+ end.to all( be_falsy )
1010
+ end
1011
+
1012
+ it "returns true if new state without timestamp is different from old state" do
1013
+ expect_evaluate_ruby do
1014
+ @foo = Foo.new(nil)
1015
+ return_values = []
1016
+ EMPTIES.each do |empty|
1017
+ @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'my_state': 12};}bla();") }
1018
+ return_values << @foo.should_component_update?({}, {'my-state' => 13})
1019
+ end
1020
+ return_values
1021
+ end.to all ( be_truthy )
1022
+ end
1023
+
1024
+ it "returns false if new state without timestamp is the same as old state" do
1025
+ expect_evaluate_ruby do
1026
+ @foo = Foo.new(nil)
1027
+ return_values = []
1028
+ EMPTIES.each do |empty|
1029
+ @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'my_state': 12};}bla();") }
1030
+ return_values << @foo.should_component_update?({}, {'my_state' => 12})
1031
+ end
1032
+ return_values
1033
+ end.to all( be_falsy )
1034
+ end
1035
+ end
1036
+
1037
+ describe '#children' do
1038
+ before(:each) do
1039
+ on_client do
1040
+ class Foo
1041
+ include React::Component
1042
+ def render
1043
+ React.create_element('div') { 'lorem' }
1044
+ end
1045
+ end
1046
+ end
1047
+ end
1048
+
1049
+ it 'returns React::Children collection with child elements' do
1050
+ evaluate_ruby do
1051
+ ele = React.create_element(Foo) {
1052
+ [React.create_element('a'), React.create_element('li')]
1053
+ }
1054
+ instance = React::Test::Utils.render_into_document(ele)
1055
+
1056
+ CHILDREN = instance.children
1057
+ end
1058
+ expect_evaluate_ruby("CHILDREN.class.name").to eq('React::Children')
1059
+ expect_evaluate_ruby("CHILDREN.count").to eq(2)
1060
+ expect_evaluate_ruby("CHILDREN.map(&:element_type)").to eq(['a', 'li'])
1061
+ end
1062
+
1063
+ it 'returns an empty Enumerator if there are no children' do
1064
+ evaluate_ruby do
1065
+ ele = React.create_element(Foo)
1066
+ instance = React::Test::Utils.render_into_document(ele)
1067
+ NODES = instance.children.each
1068
+ end
1069
+ expect_evaluate_ruby("NODES.size").to eq(0)
1070
+ expect_evaluate_ruby("NODES.count").to eq(0)
1071
+ end
1072
+ end
1073
+ end