hyper-react 0.99.6 → 1.0.0.lap21

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 (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