reactrb 0.8.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +24 -3
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +1154 -3
  5. data/.travis.yml +20 -0
  6. data/Appraisals +20 -0
  7. data/CHANGELOG.md +28 -3
  8. data/Gemfile +4 -5
  9. data/README.md +6 -9
  10. data/Rakefile +6 -1
  11. data/config.ru +7 -6
  12. data/gemfiles/opal_0.8_react_13.gemfile +13 -0
  13. data/gemfiles/opal_0.8_react_14.gemfile +13 -0
  14. data/gemfiles/opal_0.8_react_15.gemfile +13 -0
  15. data/gemfiles/opal_0.9_react_13.gemfile +13 -0
  16. data/gemfiles/opal_0.9_react_14.gemfile +13 -0
  17. data/gemfiles/opal_0.9_react_15.gemfile +13 -0
  18. data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +1 -1
  19. data/lib/rails-helpers/top_level_rails_component.rb +1 -1
  20. data/lib/react-sources/react-server.js +2 -0
  21. data/lib/react/api.rb +13 -12
  22. data/lib/react/children.rb +30 -0
  23. data/lib/react/component.rb +27 -46
  24. data/lib/react/component/class_methods.rb +28 -32
  25. data/lib/react/component/dsl_instance_methods.rb +4 -34
  26. data/lib/react/component/params.rb +6 -0
  27. data/lib/react/component/props_wrapper.rb +22 -27
  28. data/lib/react/component/should_component_update.rb +98 -0
  29. data/lib/react/component/tags.rb +45 -4
  30. data/lib/react/element.rb +26 -13
  31. data/lib/react/object.rb +15 -0
  32. data/lib/react/react-source.rb +9 -0
  33. data/lib/react/rendering_context.rb +97 -93
  34. data/lib/react/state.rb +27 -21
  35. data/lib/react/test.rb +16 -0
  36. data/lib/react/test/dsl.rb +17 -0
  37. data/lib/react/test/matchers/render_html_matcher.rb +49 -0
  38. data/lib/react/test/rspec.rb +15 -0
  39. data/lib/react/test/session.rb +46 -0
  40. data/lib/react/top_level.rb +50 -14
  41. data/lib/react/validator.rb +5 -5
  42. data/lib/reactive-ruby/isomorphic_helpers.rb +0 -7
  43. data/lib/reactive-ruby/version.rb +1 -1
  44. data/lib/reactrb.rb +14 -14
  45. data/lib/reactrb/deep-compare.rb +24 -0
  46. data/lib/sources/react-latest.js +2 -0
  47. data/lib/sources/react-v13.js +4 -1
  48. data/lib/sources/react-v14.js +3 -84
  49. data/lib/sources/react-v15.js +3 -0
  50. data/logo1.png +0 -0
  51. data/logo2.png +0 -0
  52. data/logo3.png +0 -0
  53. data/path_release_steps.md +1 -1
  54. data/reactrb.gemspec +2 -3
  55. data/spec/react/children_spec.rb +76 -0
  56. data/spec/react/component/base_spec.rb +3 -7
  57. data/spec/react/component_spec.rb +181 -60
  58. data/spec/react/dsl_spec.rb +26 -19
  59. data/spec/react/element_spec.rb +16 -1
  60. data/spec/react/native_library_spec.rb +20 -0
  61. data/spec/react/opal_jquery_extensions_spec.rb +27 -0
  62. data/spec/react/param_declaration_spec.rb +47 -78
  63. data/spec/react/react_spec.rb +7 -9
  64. data/spec/react/state_spec.rb +29 -0
  65. data/spec/react/test/dsl_spec.rb +43 -0
  66. data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
  67. data/spec/react/test/rspec_spec.rb +62 -0
  68. data/spec/react/test/session_spec.rb +100 -0
  69. data/spec/react/test/utils_spec.rb +45 -0
  70. data/spec/react/top_level_component_spec.rb +33 -5
  71. data/spec/react/tutorial/tutorial_spec.rb +5 -5
  72. data/spec/react/validator_spec.rb +10 -13
  73. data/spec/reactive-ruby/component_loader_spec.rb +3 -0
  74. data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +5 -4
  75. data/spec/spec_helper.rb +6 -3
  76. data/spec/support/react/spec_helpers.rb +9 -2
  77. metadata +47 -124
  78. data/example/examples/Gemfile +0 -7
  79. data/example/examples/app/basics.js.rb +0 -42
  80. data/example/examples/app/items.rb +0 -11
  81. data/example/examples/app/jquery.js +0 -5
  82. data/example/examples/app/nodes.rb +0 -61
  83. data/example/examples/app/react-router.js +0 -6
  84. data/example/examples/app/react_api_demo.rb +0 -29
  85. data/example/examples/app/rerendering.rb +0 -72
  86. data/example/examples/app/reuse.rb +0 -59
  87. data/example/examples/app/show.rb +0 -52
  88. data/example/examples/config.ru +0 -38
  89. data/example/rails-tutorial/.gitignore +0 -17
  90. data/example/rails-tutorial/Gemfile +0 -51
  91. data/example/rails-tutorial/README.rdoc +0 -28
  92. data/example/rails-tutorial/Rakefile +0 -6
  93. data/example/rails-tutorial/app/assets/images/.keep +0 -0
  94. data/example/rails-tutorial/app/assets/javascripts/application.rb +0 -15
  95. data/example/rails-tutorial/app/assets/stylesheets/application.css +0 -15
  96. data/example/rails-tutorial/app/controllers/application_controller.rb +0 -6
  97. data/example/rails-tutorial/app/controllers/concerns/.keep +0 -0
  98. data/example/rails-tutorial/app/controllers/home_controller.rb +0 -6
  99. data/example/rails-tutorial/app/helpers/application_helper.rb +0 -2
  100. data/example/rails-tutorial/app/mailers/.keep +0 -0
  101. data/example/rails-tutorial/app/models/.keep +0 -0
  102. data/example/rails-tutorial/app/models/concerns/.keep +0 -0
  103. data/example/rails-tutorial/app/views/components.rb +0 -3
  104. data/example/rails-tutorial/app/views/components/home/show.rb +0 -47
  105. data/example/rails-tutorial/app/views/layouts/application.html.erb +0 -14
  106. data/example/rails-tutorial/bin/bundle +0 -3
  107. data/example/rails-tutorial/bin/rails +0 -8
  108. data/example/rails-tutorial/bin/rake +0 -8
  109. data/example/rails-tutorial/bin/setup +0 -29
  110. data/example/rails-tutorial/bin/spring +0 -15
  111. data/example/rails-tutorial/config.ru +0 -4
  112. data/example/rails-tutorial/config/application.rb +0 -26
  113. data/example/rails-tutorial/config/boot.rb +0 -3
  114. data/example/rails-tutorial/config/database.yml +0 -25
  115. data/example/rails-tutorial/config/environment.rb +0 -5
  116. data/example/rails-tutorial/config/environments/development.rb +0 -41
  117. data/example/rails-tutorial/config/environments/production.rb +0 -79
  118. data/example/rails-tutorial/config/environments/test.rb +0 -42
  119. data/example/rails-tutorial/config/initializers/assets.rb +0 -11
  120. data/example/rails-tutorial/config/initializers/backtrace_silencers.rb +0 -7
  121. data/example/rails-tutorial/config/initializers/cookies_serializer.rb +0 -3
  122. data/example/rails-tutorial/config/initializers/filter_parameter_logging.rb +0 -4
  123. data/example/rails-tutorial/config/initializers/inflections.rb +0 -16
  124. data/example/rails-tutorial/config/initializers/mime_types.rb +0 -4
  125. data/example/rails-tutorial/config/initializers/session_store.rb +0 -3
  126. data/example/rails-tutorial/config/initializers/wrap_parameters.rb +0 -14
  127. data/example/rails-tutorial/config/locales/en.yml +0 -23
  128. data/example/rails-tutorial/config/routes.rb +0 -59
  129. data/example/rails-tutorial/config/secrets.yml +0 -22
  130. data/example/rails-tutorial/db/seeds.rb +0 -7
  131. data/example/rails-tutorial/lib/assets/.keep +0 -0
  132. data/example/rails-tutorial/lib/tasks/.keep +0 -0
  133. data/example/rails-tutorial/log/.keep +0 -0
  134. data/example/rails-tutorial/public/404.html +0 -67
  135. data/example/rails-tutorial/public/422.html +0 -67
  136. data/example/rails-tutorial/public/500.html +0 -66
  137. data/example/rails-tutorial/public/favicon.ico +0 -0
  138. data/example/rails-tutorial/public/robots.txt +0 -5
  139. data/example/rails-tutorial/test/controllers/.keep +0 -0
  140. data/example/rails-tutorial/test/fixtures/.keep +0 -0
  141. data/example/rails-tutorial/test/helpers/.keep +0 -0
  142. data/example/rails-tutorial/test/integration/.keep +0 -0
  143. data/example/rails-tutorial/test/mailers/.keep +0 -0
  144. data/example/rails-tutorial/test/models/.keep +0 -0
  145. data/example/rails-tutorial/test/test_helper.rb +0 -10
  146. data/example/rails-tutorial/vendor/assets/javascripts/.keep +0 -0
  147. data/example/rails-tutorial/vendor/assets/stylesheets/.keep +0 -0
  148. data/example/sinatra-tutorial/.DS_Store +0 -0
  149. data/example/sinatra-tutorial/Gemfile +0 -5
  150. data/example/sinatra-tutorial/README.md +0 -8
  151. data/example/sinatra-tutorial/_comments.json +0 -42
  152. data/example/sinatra-tutorial/app/example.rb +0 -290
  153. data/example/sinatra-tutorial/app/jquery.js +0 -5
  154. data/example/sinatra-tutorial/config.ru +0 -58
  155. data/example/sinatra-tutorial/public/base.css +0 -62
  156. data/example/todos/Gemfile +0 -11
  157. data/example/todos/README.md +0 -37
  158. data/example/todos/Rakefile +0 -8
  159. data/example/todos/app/application.rb +0 -22
  160. data/example/todos/app/components/app.react.rb +0 -61
  161. data/example/todos/app/components/footer.react.rb +0 -31
  162. data/example/todos/app/components/todo_item.react.rb +0 -46
  163. data/example/todos/app/components/todo_list.react.rb +0 -25
  164. data/example/todos/app/models/todo.rb +0 -19
  165. data/example/todos/config.ru +0 -14
  166. data/example/todos/index.html.haml +0 -16
  167. data/example/todos/spec/todo_spec.rb +0 -28
  168. data/example/todos/vendor/base.css +0 -410
  169. data/example/todos/vendor/bg.png +0 -0
  170. data/example/todos/vendor/jquery.js +0 -4
@@ -0,0 +1,15 @@
1
+ # Lazy load HTML tag constants in the form DIV or A
2
+ # This is needed to allow for a HAML expression like this DIV.my_class
3
+ class Object
4
+ class << self
5
+ alias _reactrb_tag_original_const_missing const_missing
6
+
7
+ def const_missing(const_name)
8
+ # Opal uses const_missing to initially define things,
9
+ # so we always call the original, and respond to the exception
10
+ _reactrb_tag_original_const_missing(const_name)
11
+ rescue StandardError => e
12
+ React::Component::Tags.html_tag_class_for(const_name) || raise(e)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ if RUBY_ENGINE == 'opal'
2
+ require 'react.js'
3
+ require "react-server.js"
4
+ else
5
+ require "react/rails/asset_variant"
6
+ react_directory = React::Rails::AssetVariant.new(addons: true).react_directory
7
+ Opal.append_path react_directory.untaint
8
+ Opal.append_path File.expand_path('../../react-sources/', __FILE__).untaint
9
+ end
@@ -2,113 +2,117 @@ module React
2
2
  class RenderingContext
3
3
  class << self
4
4
  attr_accessor :waiting_on_resources
5
- end
6
5
 
7
- def self.build_only(name, *args, &block)
8
- React::Component.deprecation_warning(
9
- '..._as_node is deprecated. Render component and then use the .node method instead'
10
- )
11
- React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
12
- end
6
+ def build_only(name, *args, &block)
7
+ React::Component.deprecation_warning(
8
+ '..._as_node is deprecated. Render component and then use the .node method instead'
9
+ )
10
+ React::RenderingContext.build { React::RenderingContext.render(name, *args, &block) }.to_n
11
+ end
13
12
 
14
- def self.render(name, *args, &block)
15
- remove_nodes_from_args(args)
16
- @buffer ||= [] unless @buffer
17
- if block
18
- element = build do
19
- saved_waiting_on_resources = waiting_on_resources
20
- self.waiting_on_resources = nil
21
- run_child_block(name.nil?, &block)
22
- if name
23
- buffer = @buffer.dup
24
- React.create_element(name, *args) { buffer }.tap do |element|
25
- element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
13
+ def render(name, *args, &block)
14
+ remove_nodes_from_args(args)
15
+ @buffer ||= [] unless @buffer
16
+ if block
17
+ element = build do
18
+ saved_waiting_on_resources = waiting_on_resources
19
+ self.waiting_on_resources = nil
20
+ run_child_block(name.nil?, &block)
21
+ if name
22
+ buffer = @buffer.dup
23
+ React.create_element(name, *args) { buffer }.tap do |element|
24
+ element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
25
+ end
26
+ elsif @buffer.last.is_a? React::Element
27
+ @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
28
+ else
29
+ @buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
26
30
  end
27
- elsif @buffer.last.is_a? React::Element
28
- @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
29
- else
30
- @buffer.last.to_s.span.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
31
31
  end
32
+ elsif name.is_a? React::Element
33
+ element = name
34
+ else
35
+ element = React.create_element(name, *args)
36
+ element.waiting_on_resources = waiting_on_resources
32
37
  end
33
- elsif name.is_a? React::Element
34
- element = name
35
- else
36
- element = React.create_element(name, *args)
37
- element.waiting_on_resources = waiting_on_resources
38
+ @buffer << element
39
+ self.waiting_on_resources = nil
40
+ element
38
41
  end
39
- @buffer << element
40
- self.waiting_on_resources = nil
41
- element
42
- end
43
42
 
44
- def self.build
45
- current = @buffer
46
- @buffer = []
47
- return_val = yield @buffer
48
- @buffer = current
49
- return_val
50
- end
43
+ def build
44
+ current = @buffer
45
+ @buffer = []
46
+ return_val = yield @buffer
47
+ @buffer = current
48
+ return_val
49
+ end
51
50
 
52
- def self.as_node(element)
53
- @buffer.delete(element)
54
- element
55
- end
51
+ def as_node(element)
52
+ @buffer.delete(element)
53
+ element
54
+ end
56
55
 
57
- class << self; alias delete as_node; end
56
+ alias delete as_node
58
57
 
59
- def self.replace(e1, e2)
60
- @buffer[@buffer.index(e1)] = e2
61
- end
58
+ def rendered?(element)
59
+ @buffer.include? element
60
+ end
62
61
 
63
- def self.remove_nodes_from_args(args)
64
- args[0].each do |key, value|
65
- value.as_node if value.is_a?(Element) rescue nil
66
- end if args[0] && args[0].is_a?(Hash)
67
- end
62
+ def replace(e1, e2)
63
+ @buffer[@buffer.index(e1)] = e2
64
+ end
68
65
 
69
- # run_child_block gathers the element(s) generated by a child block.
70
- # for example when rendering this div: div { "hello".span; "goodby".span }
71
- # two child Elements will be generated.
72
- #
73
- # the final value of the block should either be
74
- # 1 an object that responds to :acts_as_string?
75
- # 2 a string,
76
- # 3 an element that is NOT yet pushed on the rendering buffer
77
- # 4 or the last element pushed on the buffer
78
- #
79
- # in case 1 we change the object to a string, and then it becomes case 2
80
- # in case 2 we automatically push the string onto the buffer
81
- # in case 3 we also push the Element onto the buffer IF the buffer is empty
82
- # case 4 requires no special processing
83
- #
84
- # Once we have taken care of these special cases we do a check IF we are in an
85
- # outer rendering scope. In this case react only allows us to generate 1 Element
86
- # so we insure that is the case, and also check to make sure that element in the buffer
87
- # is the element returned
88
-
89
- def self.run_child_block(is_outer_scope)
90
- result = yield
91
- result = result.to_s if result.try :acts_as_string?
92
- @buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
93
- raise_render_error(result) if is_outer_scope && @buffer != [result]
94
- end
66
+ def remove_nodes_from_args(args)
67
+ args[0].each do |key, value|
68
+ value.as_node if value.is_a?(Element) rescue nil
69
+ end if args[0] && args[0].is_a?(Hash)
70
+ end
95
71
 
96
- # heurestically raise a meaningful error based on the situation
97
-
98
- def self.raise_render_error(result)
99
- improper_render 'A different element was returned than was generated within the DSL.',
100
- 'Possibly improper use of Element#delete.' if @buffer.count == 1
101
- improper_render "Instead #{@buffer.count} elements were generated.",
102
- 'Do you want to wrap your elements in a div?' if @buffer.count > 1
103
- improper_render "Instead the component #{result} was returned.",
104
- "Did you mean #{result}()?" if result.try :reactrb_component?
105
- improper_render "Instead the #{result.class} #{result} was returned.",
106
- 'You may need to convert this to a string.'
107
- end
72
+ # run_child_block gathers the element(s) generated by a child block.
73
+ # for example when rendering this div: div { "hello".span; "goodby".span }
74
+ # two child Elements will be generated.
75
+ #
76
+ # the final value of the block should either be
77
+ # 1 an object that responds to :acts_as_string?
78
+ # 2 a string,
79
+ # 3 an element that is NOT yet pushed on the rendering buffer
80
+ # 4 or the last element pushed on the buffer
81
+ #
82
+ # in case 1 we change the object to a string, and then it becomes case 2
83
+ # in case 2 we automatically push the string onto the buffer
84
+ # in case 3 we also push the Element onto the buffer IF the buffer is empty
85
+ # case 4 requires no special processing
86
+ #
87
+ # Once we have taken care of these special cases we do a check IF we are in an
88
+ # outer rendering scope. In this case react only allows us to generate 1 Element
89
+ # so we insure that is the case, and also check to make sure that element in the buffer
90
+ # is the element returned
91
+
92
+ def run_child_block(is_outer_scope)
93
+ result = yield
94
+ result = result.to_s if result.try :acts_as_string?
95
+ @buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
96
+ raise_render_error(result) if is_outer_scope && @buffer != [result]
97
+ end
98
+
99
+ # heurestically raise a meaningful error based on the situation
108
100
 
109
- def self.improper_render(message, solution)
110
- raise "a component's render method must generate and return exactly 1 element or a string.\n"\
111
- " #{message} #{solution}"
101
+ def raise_render_error(result)
102
+ improper_render 'A different element was returned than was generated within the DSL.',
103
+ 'Possibly improper use of Element#delete.' if @buffer.count == 1
104
+ improper_render "Instead #{@buffer.count} elements were generated.",
105
+ 'Do you want to wrap your elements in a div?' if @buffer.count > 1
106
+ improper_render "Instead the component #{result} was returned.",
107
+ "Did you mean #{result}()?" if result.try :reactrb_component?
108
+ improper_render "Instead the #{result.class} #{result} was returned.",
109
+ 'You may need to convert this to a string.'
110
+ end
111
+
112
+ def improper_render(message, solution)
113
+ raise "a component's render method must generate and return exactly 1 element or a string.\n"\
114
+ " #{message} #{solution}"
115
+ end
112
116
  end
113
117
  end
114
118
 
@@ -33,6 +33,9 @@ module React
33
33
  end
34
34
 
35
35
  class State
36
+
37
+ @rendering_level = 0
38
+
36
39
  class << self
37
40
  attr_reader :current_observer
38
41
 
@@ -41,12 +44,23 @@ module React
41
44
  end
42
45
 
43
46
  def get_state(object, name, current_observer = @current_observer)
44
- # get current value of name for object, remember that the current object depends on this state, current observer can be overriden with last param
47
+ # get current value of name for object, remember that the current object depends on this state,
48
+ # current observer can be overriden with last param
45
49
  new_observers[current_observer][object] << name if current_observer && !new_observers[current_observer][object].include?(name)
46
50
  states[object][name]
47
51
  end
48
52
 
49
- def set_state2(object, name, value) # set object's name state to value, tell all observers it has changed. Observers must implement update_react_js_state
53
+ def set_state(object, name, value, wait_till_thread_completes = nil)
54
+ states[object][name] = value
55
+ if wait_till_thread_completes
56
+ notify_observers_after_thread_completes(object, name, value)
57
+ elsif @rendering_level == 0
58
+ notify_observers(object, name, value)
59
+ end
60
+ value
61
+ end
62
+
63
+ def notify_observers(object, name, value)
50
64
  object_needs_notification = object.respond_to? :update_react_js_state
51
65
  observers_by_name[object][name].dup.each do |observer|
52
66
  observer.update_react_js_state(object, name, value)
@@ -55,23 +69,14 @@ module React
55
69
  object.update_react_js_state(nil, name, value) if object_needs_notification
56
70
  end
57
71
 
58
- def set_state(object, name, value, delay=nil)
59
- states[object][name] = value
60
- if delay
61
- @delayed_updates ||= []
62
- @delayed_updates << [object, name, value]
63
- @delayed_updater ||= after(0.001) do
64
- delayed_updates = @delayed_updates
65
- @delayed_updates = []
66
- @delayed_updater = nil
67
- delayed_updates.each do |object, name, value|
68
- set_state2(object, name, value)
69
- end
70
- end
71
- else
72
- set_state2(object, name, value)
72
+ def notify_observers_after_thread_completes(object, name, value)
73
+ (@delayed_updates ||= []) << [object, name, value]
74
+ @delayed_updater ||= after(0) do
75
+ delayed_updates = @delayed_updates
76
+ @delayed_updates = []
77
+ @delayed_updater = nil
78
+ delayed_updates.each { |args| notify_observers(*args) }
73
79
  end
74
- value
75
80
  end
76
81
 
77
82
  def will_be_observing?(object, name, current_observer)
@@ -108,7 +113,7 @@ module React
108
113
  current_observers.delete(@current_observer)
109
114
  end
110
115
 
111
- def set_state_context_to(observer) # wrap all execution that may set or get states in a block so we know which observer is executing
116
+ def set_state_context_to(observer, rendering = nil) # wrap all execution that may set or get states in a block so we know which observer is executing
112
117
  if `typeof window.reactive_ruby_timing !== 'undefined'`
113
118
  @nesting_level = (@nesting_level || 0) + 1
114
119
  start_time = Time.now.to_f
@@ -116,10 +121,12 @@ module React
116
121
  end
117
122
  saved_current_observer = @current_observer
118
123
  @current_observer = observer
124
+ @rendering_level += 1 if rendering
119
125
  return_value = yield
120
126
  return_value
121
127
  ensure
122
128
  @current_observer = saved_current_observer
129
+ @rendering_level -= 1 if rendering
123
130
  @nesting_level = [0, @nesting_level - 1].max if `typeof window.reactive_ruby_timing !== 'undefined'`
124
131
  return_value
125
132
  end
@@ -130,11 +137,10 @@ module React
130
137
 
131
138
  [:new_observers, :current_observers, :observers_by_name].each do |method_name|
132
139
  define_method(method_name) do
133
- instance_variable_get("@#{method_name}") or
140
+ instance_variable_get("@#{method_name}") ||
134
141
  instance_variable_set("@#{method_name}", Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = [] } })
135
142
  end
136
143
  end
137
-
138
144
  end
139
145
  end
140
146
  end
@@ -0,0 +1,16 @@
1
+ require 'react/test/session'
2
+ require 'react/test/dsl'
3
+
4
+ module React
5
+ module Test
6
+ class << self
7
+ def current_session
8
+ @current_session ||= Session.new
9
+ end
10
+
11
+ def reset_session!
12
+ @current_session = nil
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'react/test'
2
+
3
+ module React
4
+ module Test
5
+ module DSL
6
+ def component
7
+ React::Test.current_session
8
+ end
9
+
10
+ Session::DSL_METHODS.each do |method|
11
+ define_method method do |*args, &block|
12
+ component.public_send(method, *args, &block)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module React
2
+ module Test
3
+ module Matchers
4
+ class RenderHTMLMatcher
5
+ def initialize(expected)
6
+ @expected = expected
7
+ @params = {}
8
+ end
9
+
10
+ def with_params(params)
11
+ @params = params
12
+ self
13
+ end
14
+
15
+ def matches?(component)
16
+ @component = component
17
+ @actual = render_to_html
18
+ @expected == @actual
19
+ end
20
+
21
+ def failure_message
22
+ failure_string
23
+ end
24
+
25
+ def negative_failure_message
26
+ failure_string(:negative)
27
+ end
28
+
29
+ private
30
+
31
+ def render_to_html
32
+ element = React.create_element(@component, @params)
33
+ React.render_to_static_markup(element)
34
+ end
35
+
36
+ def failure_string(negative = false)
37
+ str = "expected '#{@component.name}' with params '#{@params}' to "
38
+ str = str + "not " if negative
39
+ str = str + "render '#{@expected}', but '#{@actual}' was rendered."
40
+ str
41
+ end
42
+ end
43
+
44
+ def render(*args)
45
+ RenderHTMLMatcher.new(*args)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,15 @@
1
+ require 'react/test/dsl'
2
+ require 'react/test/matchers/render_html_matcher'
3
+
4
+ RSpec.configure do |config|
5
+ config.include React::Test::DSL, type: :component
6
+ config.include React::Test::Matchers, type: :component
7
+
8
+ config.after do
9
+ React::Test.reset_session!
10
+ end
11
+
12
+ config.before do
13
+ # nothing yet
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ module React
2
+ module Test
3
+ class Session
4
+ DSL_METHODS = %i[mount instance native element update_params
5
+ force_update! html].freeze
6
+
7
+ def mount(component_klass, params = {})
8
+ @element = React.create_element(component_klass, params)
9
+ instance
10
+ end
11
+
12
+ def instance
13
+ unless @instance
14
+ @native = Native(`React.addons.TestUtils.renderIntoDocument(#{element.to_n})`)
15
+ @instance = `#{@native.to_n}._getOpalInstance()`
16
+ end
17
+ @instance
18
+ end
19
+
20
+ def native
21
+ @native
22
+ end
23
+
24
+ def element
25
+ @element
26
+ end
27
+
28
+ def update_params(params)
29
+ cloned_element = React::Element.new(`React.cloneElement(#{self.element.to_n}, #{params.to_n})`)
30
+ prev_container = `#{self.instance.dom_node}.parentNode`
31
+ React.render(cloned_element, prev_container)
32
+ nil
33
+ end
34
+
35
+ def force_update!
36
+ native.force_update!
37
+ end
38
+
39
+ def html
40
+ # How can we get the current ReactElement w/o violating private APIs?
41
+ elem = Native(native[:_reactInternalInstance][:_currentElement])
42
+ React.render_to_static_markup(elem)
43
+ end
44
+ end
45
+ end
46
+ end