reactive-ruby 0.7.31 → 0.7.32
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/rails-helpers/top_level_rails_component.rb +16 -16
- data/lib/react/callbacks.rb +10 -3
- data/lib/react/component.rb +33 -7
- data/lib/react/component/base.rb +11 -0
- data/lib/react/component/class_methods.rb +54 -20
- data/lib/react/state.rb +35 -4
- data/lib/react/top_level.rb +10 -1
- data/lib/react/validator.rb +12 -2
- data/lib/reactive-ruby.rb +1 -0
- data/lib/reactive-ruby/component_loader.rb +3 -0
- data/lib/reactive-ruby/version.rb +1 -1
- data/spec/react/component_base_spec.rb +33 -0
- data/spec/react/component_spec.rb +58 -6
- data/spec/react/observable_spec.rb +7 -0
- data/spec/react/param_declaration_spec.rb +182 -13
- data/spec/react/validator_spec.rb +13 -10
- data/spec/reactive-ruby/component_loader_spec.rb +8 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 212dc6ace47eb491a9464258c6df6e26e4c35579
|
4
|
+
data.tar.gz: faa7ee755e54749e9acce87bc8c08ff1c890bc88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b721773a255f59e5a852788fd75a4fd2ab7340b9c8f8e5d0846ca009adf0d37150fb83010ad80810e203352467744f81cc10008c3daeff0b97898f1cacf8971
|
7
|
+
data.tar.gz: 4b53af87056f9a2dbe19eddcd1217b7ab95daa705edb90011501702e956a97200dd28fff5e4396b30735c0344e408ee3b0cf8b7693294f6c01159307afbe5b15
|
data/Gemfile.lock
CHANGED
@@ -8,33 +8,33 @@ module React
|
|
8
8
|
|
9
9
|
export_component
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
param :component_name
|
12
|
+
param :controller
|
13
|
+
param :render_params
|
14
14
|
|
15
15
|
backtrace :on
|
16
16
|
|
17
17
|
def render
|
18
18
|
paths_searched = []
|
19
|
-
if component_name.start_with? "::"
|
20
|
-
paths_searched << component_name.gsub(/^\:\:/,"")
|
21
|
-
component = component_name.gsub(/^\:\:/,"").split("::").inject(Module) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
22
|
-
return present component, render_params if component && component.method_defined?(:render)
|
19
|
+
if params.component_name.start_with? "::"
|
20
|
+
paths_searched << params.component_name.gsub(/^\:\:/,"")
|
21
|
+
component = params.component_name.gsub(/^\:\:/,"").split("::").inject(Module) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
22
|
+
return present component, params.render_params if component && component.method_defined?(:render)
|
23
23
|
else
|
24
24
|
self.class.search_path.each do |path|
|
25
|
-
# try each path + controller + component_name
|
26
|
-
paths_searched << "#{path.name + '::' unless path == Module}#{controller}::#{component_name}"
|
27
|
-
component = "#{controller}::#{component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
28
|
-
return present component, render_params if component && component.method_defined?(:render)
|
25
|
+
# try each path + params.controller + params.component_name
|
26
|
+
paths_searched << "#{path.name + '::' unless path == Module}#{params.controller}::#{params.component_name}"
|
27
|
+
component = "#{params.controller}::#{params.component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
28
|
+
return present component, params.render_params if component && component.method_defined?(:render)
|
29
29
|
end
|
30
30
|
self.class.search_path.each do |path|
|
31
|
-
# then try each path + component_name
|
32
|
-
paths_searched << "#{path.name + '::' unless path == Module}#{component_name}"
|
33
|
-
component = "#{component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
34
|
-
return present component, render_params if component && component.method_defined?(:render)
|
31
|
+
# then try each path + params.component_name
|
32
|
+
paths_searched << "#{path.name + '::' unless path == Module}#{params.component_name}"
|
33
|
+
component = "#{params.component_name}".split("::").inject(path) { |scope, next_const| scope.const_get(next_const, false) } rescue nil
|
34
|
+
return present component, params.render_params if component && component.method_defined?(:render)
|
35
35
|
end
|
36
36
|
end
|
37
|
-
raise "Could not find component class '#{component_name}' for controller '#{controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
|
37
|
+
raise "Could not find component class '#{params.component_name}' for params.controller '#{params.controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
data/lib/react/callbacks.rb
CHANGED
@@ -7,9 +7,7 @@ module React
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def run_callback(name, *args)
|
10
|
-
|
11
|
-
callbacks = self.class.send(attribute_name)
|
12
|
-
callbacks.each do |callback|
|
10
|
+
self.class.callbacks_for(name).each do |callback|
|
13
11
|
if callback.is_a?(Proc)
|
14
12
|
instance_exec(*args, &callback)
|
15
13
|
else
|
@@ -30,6 +28,15 @@ module React
|
|
30
28
|
self.send("#{attribute_name}=", callbacks)
|
31
29
|
end
|
32
30
|
end
|
31
|
+
|
32
|
+
def callbacks_for(callback_name)
|
33
|
+
attribute_name = "_#{callback_name}_callbacks"
|
34
|
+
if superclass.respond_to? :callbacks_for
|
35
|
+
superclass.callbacks_for(callback_name)
|
36
|
+
else
|
37
|
+
[]
|
38
|
+
end + self.send(attribute_name)
|
39
|
+
end
|
33
40
|
end
|
34
41
|
end
|
35
42
|
end
|
data/lib/react/component.rb
CHANGED
@@ -14,6 +14,22 @@ module React
|
|
14
14
|
def self.included(base)
|
15
15
|
base.include(API)
|
16
16
|
base.include(React::Callbacks)
|
17
|
+
base.instance_eval do
|
18
|
+
|
19
|
+
class PropsWrapper
|
20
|
+
|
21
|
+
def initialize(props_hash)
|
22
|
+
@props_hash = props_hash
|
23
|
+
@processed_params = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](prop)
|
27
|
+
return unless @props_hash
|
28
|
+
@props_hash[prop]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
17
33
|
base.class_eval do
|
18
34
|
class_attribute :initial_state
|
19
35
|
define_callback :before_mount
|
@@ -23,6 +39,11 @@ module React
|
|
23
39
|
define_callback :after_update
|
24
40
|
define_callback :before_unmount
|
25
41
|
|
42
|
+
def deprecated_params_method(name, *args, &block)
|
43
|
+
self.class.deprecation_warning "Direct access to param `#{name}`. Use `params.#{name}` instead."
|
44
|
+
params.send(name, *args, &block)
|
45
|
+
end
|
46
|
+
|
26
47
|
def render
|
27
48
|
raise "no render defined"
|
28
49
|
end unless method_defined?(:render)
|
@@ -97,6 +118,11 @@ module React
|
|
97
118
|
end
|
98
119
|
|
99
120
|
def params
|
121
|
+
@props_wrapper
|
122
|
+
#Hash.new(`#{@native}.props`)
|
123
|
+
end
|
124
|
+
|
125
|
+
def props
|
100
126
|
Hash.new(`#{@native}.props`)
|
101
127
|
end
|
102
128
|
|
@@ -105,8 +131,8 @@ module React
|
|
105
131
|
end
|
106
132
|
|
107
133
|
def state
|
108
|
-
raise "No native ReactComponent associated" unless @native
|
109
|
-
|
134
|
+
#raise "No native ReactComponent associated" unless @native
|
135
|
+
@state_wrapper ||= StateWrapper.new(@native, self)
|
110
136
|
end
|
111
137
|
|
112
138
|
def update_react_js_state(object, name, value)
|
@@ -123,7 +149,7 @@ module React
|
|
123
149
|
|
124
150
|
def component_will_mount
|
125
151
|
IsomorphicHelpers.load_context(true) if IsomorphicHelpers.on_opal_client?
|
126
|
-
@
|
152
|
+
@props_wrapper = self.class.const_get("PropsWrapper").new(Hash.new(`#{@native}.props`))
|
127
153
|
set_state! initial_state if initial_state
|
128
154
|
React::State.initialize_states(self, initial_state)
|
129
155
|
React::State.set_state_context_to(self) { self.run_callback(:before_mount) }
|
@@ -144,14 +170,13 @@ module React
|
|
144
170
|
# need to rethink how this works in opal-react, or if its actually that useful within the react.rb environment
|
145
171
|
# for now we are just using it to clear processed_params
|
146
172
|
React::State.set_state_context_to(self) { self.run_callback(:before_receive_props, Hash.new(next_props)) }
|
147
|
-
@processed_params = {}
|
148
173
|
rescue Exception => e
|
149
174
|
self.class.process_exception(e, self)
|
150
175
|
end
|
151
176
|
|
152
177
|
def props_changed?(next_props)
|
153
|
-
return true unless
|
154
|
-
|
178
|
+
return true unless props.keys.sort == next_props.keys.sort
|
179
|
+
props.detect { |k, v| `#{next_props[k]} != #{params[k]}`}
|
155
180
|
end
|
156
181
|
|
157
182
|
def should_component_update?(next_props, next_state)
|
@@ -177,6 +202,7 @@ module React
|
|
177
202
|
|
178
203
|
def component_will_update(next_props, next_state)
|
179
204
|
React::State.set_state_context_to(self) { self.run_callback(:before_update, Hash.new(next_props), Hash.new(next_state)) }
|
205
|
+
@props_wrapper = self.class.const_get("PropsWrapper").new(Hash.new(next_props))
|
180
206
|
rescue Exception => e
|
181
207
|
self.class.process_exception(e, self)
|
182
208
|
end
|
@@ -220,7 +246,7 @@ module React
|
|
220
246
|
end
|
221
247
|
|
222
248
|
def method_missing(n, *args, &block)
|
223
|
-
return
|
249
|
+
return props[n] if props.key? n # TODO deprecate and remove - done so that params shadow tags, no longer needed
|
224
250
|
name = n
|
225
251
|
if name =~ /_as_node$/
|
226
252
|
node_only = true
|
@@ -18,8 +18,17 @@ module React
|
|
18
18
|
raise e if reraise
|
19
19
|
end
|
20
20
|
|
21
|
+
def deprecation_warning(message)
|
22
|
+
@deprecation_messages ||= []
|
23
|
+
message = "Warning: Deprecated feature used in #{self.name}. #{message}"
|
24
|
+
unless @deprecation_messages.include? message
|
25
|
+
@deprecation_messages << message
|
26
|
+
IsomorphicHelpers.log message, :warning
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
21
30
|
def validator
|
22
|
-
@validator ||= React::Validator.new
|
31
|
+
@validator ||= React::Validator.new(self)
|
23
32
|
end
|
24
33
|
|
25
34
|
def prop_types
|
@@ -47,50 +56,73 @@ module React
|
|
47
56
|
end
|
48
57
|
|
49
58
|
def define_param_method(name, param_type)
|
59
|
+
wrapper = const_get("PropsWrapper")
|
50
60
|
if param_type == React::Observable
|
51
|
-
(@two_way_params ||= []) << name
|
52
|
-
define_method("#{name}") do
|
53
|
-
|
61
|
+
#(@two_way_params ||= []) << name
|
62
|
+
wrapper.define_method("#{name}") do
|
63
|
+
@props_hash[name].instance_variable_get("@value") if @props_hash[name]
|
54
64
|
end
|
55
|
-
define_method("#{name}!") do |*args|
|
56
|
-
return unless
|
65
|
+
wrapper.define_method("#{name}!") do |*args|
|
66
|
+
return unless @props_hash[name]
|
57
67
|
if args.count > 0
|
58
|
-
current_value =
|
59
|
-
|
68
|
+
current_value = @props_hash[name].instance_variable_get("@value")
|
69
|
+
@props_hash[name].call args[0]
|
60
70
|
current_value
|
61
71
|
else
|
62
|
-
current_value =
|
63
|
-
|
64
|
-
|
72
|
+
current_value = @props_hash[name].instance_variable_get("@value")
|
73
|
+
@props_hash[name].call current_value unless @dont_update_state rescue nil # rescue in case we in middle of render
|
74
|
+
@props_hash[name]
|
65
75
|
end
|
66
76
|
end
|
77
|
+
define_method("#{name}") { deprecated_params_method("#{name}") }
|
78
|
+
define_method("#{name}!") { |*args| deprecated_params_method("#{name}!", *args) }
|
67
79
|
elsif param_type == Proc
|
68
|
-
define_method("#{name}") do |*args, &block|
|
69
|
-
|
80
|
+
wrapper.define_method("#{name}") do |*args, &block|
|
81
|
+
@props_hash[name].call(*args, &block) if @props_hash[name]
|
70
82
|
end
|
83
|
+
define_method("#{name}") { deprecated_params_method("#{name}", *args, &block) }
|
71
84
|
else
|
72
|
-
define_method("#{name}") do
|
85
|
+
wrapper.define_method("#{name}") do
|
73
86
|
@processed_params[name] ||= if param_type.respond_to? :_react_param_conversion
|
74
|
-
param_type._react_param_conversion
|
87
|
+
param_type._react_param_conversion @props_hash[name]
|
75
88
|
elsif param_type.is_a?(Array) && param_type[0].respond_to?(:_react_param_conversion)
|
76
|
-
|
89
|
+
@props_hash[name].collect { |param| param_type[0]._react_param_conversion param }
|
77
90
|
else
|
78
|
-
|
91
|
+
@props_hash[name]
|
79
92
|
end
|
80
93
|
end
|
94
|
+
define_method("#{name}") { deprecated_params_method("#{name}") }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def param(*args)
|
99
|
+
if args[0].is_a? Hash
|
100
|
+
options = args[0]
|
101
|
+
name = options.first[0]
|
102
|
+
default = options.first[1]
|
103
|
+
options.delete(name)
|
104
|
+
options.merge!({default: default})
|
105
|
+
else
|
106
|
+
name = args[0]
|
107
|
+
options = args[1] || {}
|
108
|
+
end
|
109
|
+
if options[:default]
|
110
|
+
validator.optional(name, options)
|
111
|
+
else
|
112
|
+
validator.requires(name, options)
|
81
113
|
end
|
82
114
|
end
|
83
115
|
|
84
116
|
def required_param(name, options = {})
|
117
|
+
deprecation_warning "`required_param` is deprecated, use `param` instead."
|
85
118
|
validator.requires(name, options)
|
86
|
-
define_param_method(name, options[:type])
|
87
119
|
end
|
88
120
|
|
89
121
|
alias_method :require_param, :required_param
|
90
122
|
|
91
123
|
def optional_param(name, options = {})
|
124
|
+
deprecation_warning "`optional_param` is deprecated, use `param param_name: default_value` instead."
|
92
125
|
validator.optional(name, options)
|
93
|
-
define_param_method(name, options[:type]) unless name == :params
|
94
126
|
end
|
95
127
|
|
96
128
|
def collect_other_params_as(name)
|
@@ -123,14 +155,16 @@ module React
|
|
123
155
|
|
124
156
|
def define_state_methods(this, name, from = nil, &block)
|
125
157
|
this.define_method("#{name}") do
|
158
|
+
self.class.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? || from == this
|
126
159
|
React::State.get_state(from || self, name)
|
127
160
|
end
|
128
161
|
this.define_method("#{name}=") do |new_state|
|
162
|
+
self.class.deprecation_warning "Direct assignment to state `#{name}`. Use `#{(from && from != this) ? from : 'state'}.#{name}!` instead."
|
129
163
|
yield name, React::State.get_state(from || self, name), new_state if block && block.arity > 0
|
130
164
|
React::State.set_state(from || self, name, new_state)
|
131
165
|
end
|
132
166
|
this.define_method("#{name}!") do |*args|
|
133
|
-
|
167
|
+
self.class.deprecation_warning "Direct access to state `#{name}`. Use `state.#{name}` instead." if from.nil? or from == this
|
134
168
|
if args.count > 0
|
135
169
|
yield name, React::State.get_state(from || self, name), args[0] if block && block.arity > 0
|
136
170
|
current_value = React::State.get_state(from || self, name)
|
data/lib/react/state.rb
CHANGED
@@ -1,4 +1,39 @@
|
|
1
1
|
module React
|
2
|
+
class StateWrapper
|
3
|
+
|
4
|
+
def initialize(native, from)
|
5
|
+
@state_hash = Hash.new(`#{native}.state`)
|
6
|
+
@from = from
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](state)
|
10
|
+
@state_hash[state]
|
11
|
+
end
|
12
|
+
|
13
|
+
def []=(state, new_value)
|
14
|
+
@state_hash[state] = new_value
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args)
|
18
|
+
if match = method.match(/^(.+)\!$/)
|
19
|
+
if args.count > 0
|
20
|
+
current_value = React::State.get_state(@from, match[1])
|
21
|
+
React::State.set_state(@from, $1, args[0])
|
22
|
+
current_value
|
23
|
+
else
|
24
|
+
current_state = React::State.get_state(@from, match[1])
|
25
|
+
React::State.set_state(@from, $1, current_state)
|
26
|
+
React::Observable.new(current_state) do |update|
|
27
|
+
React::State.set_state(@from, $1, update)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
React::State.get_state(@from, method)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
2
37
|
class State
|
3
38
|
class << self
|
4
39
|
attr_reader :current_observer
|
@@ -80,14 +115,10 @@ module React
|
|
80
115
|
@nesting_level = (@nesting_level || 0) + 1
|
81
116
|
start_time = Time.now.to_f
|
82
117
|
observer_name = (observer.class.respond_to?(:name) ? observer.class.name : observer.to_s) rescue "object:#{observer.object_id}"
|
83
|
-
puts "#{' ' * @nesting_level} Timing #{observer_name} Execution"
|
84
118
|
end
|
85
119
|
saved_current_observer = @current_observer
|
86
120
|
@current_observer = observer
|
87
121
|
return_value = yield
|
88
|
-
if `typeof window.reactive_ruby_timing !== 'undefined'`
|
89
|
-
puts "#{' ' * @nesting_level} Timing #{observer_name} Completed in #{'%.04f' % (Time.now.to_f-start_time)} seconds"
|
90
|
-
end
|
91
122
|
return_value
|
92
123
|
ensure
|
93
124
|
@current_observer = saved_current_observer
|
data/lib/react/top_level.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require "native"
|
2
2
|
require 'active_support'
|
3
|
-
require 'react/component'
|
3
|
+
require 'react/component/base'
|
4
4
|
|
5
5
|
module React
|
6
6
|
HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
|
@@ -49,4 +49,13 @@ module React
|
|
49
49
|
def self.unmount_component_at_node(node)
|
50
50
|
`React.unmountComponentAtNode(node.$$class ? node[0] : node)`
|
51
51
|
end
|
52
|
+
|
52
53
|
end
|
54
|
+
|
55
|
+
Element.instance_eval do
|
56
|
+
class ::Element::DummyContext < React::Component::Base
|
57
|
+
end
|
58
|
+
def render(&block)
|
59
|
+
React.render(React::RenderingContext.render(nil) {::Element::DummyContext.new.instance_eval &block}, self)
|
60
|
+
end
|
61
|
+
end if Object.const_defined?("Element")
|
data/lib/react/validator.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
module React
|
2
|
+
|
2
3
|
class Validator
|
3
4
|
attr_accessor :errors
|
4
5
|
private :errors
|
5
6
|
|
7
|
+
def initialize(component_class)
|
8
|
+
@component_class = component_class
|
9
|
+
end
|
10
|
+
|
6
11
|
def self.build(&block)
|
7
12
|
self.new.build(&block)
|
8
13
|
end
|
@@ -15,11 +20,13 @@ module React
|
|
15
20
|
def requires(name, options = {})
|
16
21
|
options[:required] = true
|
17
22
|
define_rule(name, options)
|
23
|
+
@component_class.define_param_method(name, options[:type])
|
18
24
|
end
|
19
25
|
|
20
26
|
def optional(name, options = {})
|
21
27
|
options[:required] = false
|
22
28
|
define_rule(name, options)
|
29
|
+
@component_class.define_param_method(name, options[:type]) unless name == :params
|
23
30
|
end
|
24
31
|
|
25
32
|
def allow_undefined_props=(allow)
|
@@ -73,11 +80,14 @@ module React
|
|
73
80
|
|
74
81
|
def validate_types(prop_name, value)
|
75
82
|
return unless klass = rules[prop_name][:type]
|
76
|
-
if klass.is_a?(Array)
|
83
|
+
if !klass.is_a?(Array)
|
84
|
+
allow_nil = !!rules[prop_name][:allow_nil]
|
85
|
+
type_check("`#{prop_name}`", value, klass, allow_nil)
|
86
|
+
elsif klass.length > 0
|
77
87
|
validate_value_array(prop_name, value)
|
78
88
|
else
|
79
89
|
allow_nil = !!rules[prop_name][:allow_nil]
|
80
|
-
type_check("`#{prop_name}`", value,
|
90
|
+
type_check("`#{prop_name}`", value, Array, allow_nil)
|
81
91
|
end
|
82
92
|
end
|
83
93
|
|
data/lib/reactive-ruby.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
if opal?
|
4
|
+
describe React::Component::Base do
|
5
|
+
after(:each) do
|
6
|
+
React::API.clear_component_class_cache
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'can be inherited to create a component class' do
|
10
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
11
|
+
Foo.class_eval do
|
12
|
+
before_mount do
|
13
|
+
@instance_data = ["working"]
|
14
|
+
end
|
15
|
+
def render
|
16
|
+
@instance_data.first
|
17
|
+
end
|
18
|
+
end
|
19
|
+
stub_const 'Bar', Class.new(Foo)
|
20
|
+
Bar.class_eval do
|
21
|
+
before_mount do
|
22
|
+
@instance_data << "well"
|
23
|
+
end
|
24
|
+
def render
|
25
|
+
@instance_data.join(" ")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
expect(React.render_to_static_markup(React.create_element(Foo))).to eq("<span>working</span>")
|
29
|
+
expect(React.render_to_static_markup(React.create_element(Bar))).to eq("<span>working well</span>")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -102,6 +102,58 @@ describe React::Component do
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
describe 'New style setter & getter' do
|
106
|
+
before(:each) do
|
107
|
+
stub_const 'Foo', Class.new
|
108
|
+
Foo.class_eval do
|
109
|
+
include React::Component
|
110
|
+
def render
|
111
|
+
div { state.foo }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'implicitly will create a state variable when first written' do
|
117
|
+
Foo.class_eval do
|
118
|
+
before_mount do
|
119
|
+
state.foo! 'bar'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('<div>bar</div>')
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'returns an observer with the bang method and no arguments' do
|
127
|
+
Foo.class_eval do
|
128
|
+
before_mount do
|
129
|
+
state.foo!(state.baz!.class.name)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('<div>React::Observable</div>')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns the current value of a state when written' do
|
137
|
+
Foo.class_eval do
|
138
|
+
before_mount do
|
139
|
+
state.baz! 'bar'
|
140
|
+
state.foo!(state.baz!('pow'))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('<div>bar</div>')
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'can access an explicitly defined state`' do
|
148
|
+
Foo.class_eval do
|
149
|
+
define_state foo: :bar
|
150
|
+
end
|
151
|
+
|
152
|
+
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('<div>bar</div>')
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
105
157
|
describe 'State setter & getter' do
|
106
158
|
before(:each) do
|
107
159
|
stub_const 'Foo', Class.new
|
@@ -359,11 +411,11 @@ describe React::Component do
|
|
359
411
|
|
360
412
|
%x{
|
361
413
|
var log = [];
|
362
|
-
var
|
363
|
-
window.console =
|
414
|
+
var org_warn_console = window.console.warn;
|
415
|
+
window.console.warn = function(str){log.push(str)}
|
364
416
|
}
|
365
417
|
renderToDocument(Foo, bar: 10, lorem: Lorem.new)
|
366
|
-
`window.console =
|
418
|
+
`window.console.warn = org_warn_console;`
|
367
419
|
expect(`log`).to eq(["Warning: Failed propType: In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String"])
|
368
420
|
end
|
369
421
|
|
@@ -381,11 +433,11 @@ describe React::Component do
|
|
381
433
|
|
382
434
|
%x{
|
383
435
|
var log = [];
|
384
|
-
var
|
385
|
-
window.console =
|
436
|
+
var org_warn_console = window.console.warn;
|
437
|
+
window.console.warn = function(str){log.push(str)}
|
386
438
|
}
|
387
439
|
renderToDocument(Foo, foo: 10, bar: '10', lorem: Lorem.new)
|
388
|
-
`window.console =
|
440
|
+
`window.console.warn = org_warn_console;`
|
389
441
|
expect(`log`).to eq([])
|
390
442
|
end
|
391
443
|
end
|
@@ -1,18 +1,187 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
if opal?
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
it "can
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
|
5
|
+
describe 'the param macro' do
|
6
|
+
|
7
|
+
it "can create and access a required param" do
|
8
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
9
|
+
Foo.class_eval do
|
10
|
+
param :foo
|
11
|
+
|
12
|
+
def render
|
13
|
+
div { params.foo }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
expect(React.render_to_static_markup(React.create_element(Foo, {foo: :bar}))).to eq('<div>bar</div>')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can create and access an optional params" do
|
21
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
22
|
+
Foo.class_eval do
|
23
|
+
|
24
|
+
param foo1: :no_bar1
|
25
|
+
param foo2: :no_bar2
|
26
|
+
param :foo3, default: :no_bar3
|
27
|
+
param :foo4, default: :no_bar4
|
28
|
+
|
29
|
+
def render
|
30
|
+
div { "#{params.foo1}-#{params.foo2}-#{params.foo3}-#{params.foo4}" }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
expect(React.render_to_static_markup(React.create_element(Foo, {foo1: :bar1, foo3: :bar3}))).to eq('<div>bar1-no_bar2-bar3-no_bar4</div>')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'can specify validation rules with the type option' do
|
38
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
39
|
+
Foo.class_eval do
|
40
|
+
param :foo, type: String
|
41
|
+
end
|
42
|
+
|
43
|
+
expect(Foo.prop_types).to have_key(:_componentValidator)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "can type check params" do
|
47
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
48
|
+
Foo.class_eval do
|
49
|
+
|
50
|
+
param :foo1, type: String
|
51
|
+
param :foo2, type: String
|
52
|
+
|
53
|
+
def render
|
54
|
+
div { "#{params.foo1}-#{params.foo2}" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
expect(React.render_to_static_markup(React.create_element(Foo, {foo1: 12, foo2: "string"}))).to eq('<div>12-string</div>')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'logs error in warning if validation failed' do
|
62
|
+
stub_const 'Lorem', Class.new
|
63
|
+
stub_const 'Foo2', Class.new(React::Component::Base)
|
64
|
+
Foo2.class_eval do
|
65
|
+
param :foo
|
66
|
+
param :lorem, type: Lorem
|
67
|
+
param :bar, default: nil, type: String
|
68
|
+
def render; div; end
|
69
|
+
end
|
70
|
+
|
71
|
+
%x{
|
72
|
+
var log = [];
|
73
|
+
var org_warn_console = window.console.warn;
|
74
|
+
window.console.warn = function(str){log.push(str)}
|
75
|
+
}
|
76
|
+
renderToDocument(Foo2, bar: 10, lorem: Lorem.new)
|
77
|
+
`window.console.warn = org_warn_console;`
|
78
|
+
expect(`log`).to eq(["Warning: Failed propType: In component `Foo2`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String"])
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should not log anything if validation passes' do
|
82
|
+
stub_const 'Lorem', Class.new
|
83
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
84
|
+
Foo.class_eval do
|
85
|
+
param :foo
|
86
|
+
param :lorem, type: Lorem
|
87
|
+
param :bar, default: nil, type: String
|
88
|
+
|
89
|
+
def render; div; end
|
90
|
+
end
|
91
|
+
|
92
|
+
%x{
|
93
|
+
var log = [];
|
94
|
+
var org_warn_console = window.console.warn;
|
95
|
+
window.console.warn = function(str){log.push(str)}
|
96
|
+
}
|
97
|
+
renderToDocument(Foo, foo: 10, bar: '10', lorem: Lorem.new)
|
98
|
+
`window.console.warn = org_warn_console;`
|
99
|
+
expect(`log`).to eq([])
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'advanced type handling' do
|
103
|
+
before(:each) do
|
104
|
+
%x{
|
105
|
+
window.dummy_log = [];
|
106
|
+
window.org_warn_console = window.console.warn;
|
107
|
+
window.console.warn = function(str){window.dummy_log.push(str)}
|
108
|
+
}
|
109
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
110
|
+
Foo.class_eval { def render; ""; end }
|
111
|
+
end
|
112
|
+
after(:each) do
|
113
|
+
`window.console.warn = window.org_warn_console;`
|
114
|
+
end
|
115
|
+
|
116
|
+
it "can use the [] notation for arrays" do
|
117
|
+
Foo.class_eval do
|
118
|
+
param :foo, type: []
|
119
|
+
param :bar, type: []
|
120
|
+
end
|
121
|
+
renderToDocument(Foo, foo: 10, bar: [10])
|
122
|
+
expect(`window.dummy_log`).to eq(["Warning: Failed propType: In component `Foo`\nProvided prop `foo` could not be converted to Array"])
|
123
|
+
end
|
124
|
+
|
125
|
+
it "can use the [xxx] notation for arrays of a specific type" do
|
126
|
+
Foo.class_eval do
|
127
|
+
param :foo, type: [String]
|
128
|
+
param :bar, type: [String]
|
129
|
+
end
|
130
|
+
renderToDocument(Foo, foo: [10], bar: ["10"])
|
131
|
+
expect(`window.dummy_log`).to eq(["Warning: Failed propType: In component `Foo`\nProvided prop `foo`[0] could not be converted to String"])
|
132
|
+
end
|
133
|
+
|
134
|
+
it "can convert a json hash to a type" do
|
135
|
+
stub_const "BazWoggle", Class.new
|
136
|
+
BazWoggle.class_eval do
|
137
|
+
def initialize(kind)
|
138
|
+
@kind = kind
|
139
|
+
end
|
140
|
+
attr_accessor :kind
|
141
|
+
def self._react_param_conversion(json, validate_only)
|
142
|
+
new(json[:bazwoggle]) if json[:bazwoggle]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
Foo.class_eval do
|
146
|
+
param :foo, type: BazWoggle
|
147
|
+
param :bar, type: BazWoggle
|
148
|
+
param :baz, type: [BazWoggle]
|
149
|
+
def render
|
150
|
+
"#{params.bar.kind}, #{params.baz[0].kind}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
expect(React.render_to_static_markup(React.create_element(
|
154
|
+
Foo, foo: "", bar: {bazwoggle: 1}, baz: [{bazwoggle: 2}]))).to eq('<span>1, 2</span>')
|
155
|
+
expect(`window.dummy_log`).to eq(["Warning: Failed propType: In component `Foo`\nProvided prop `foo` could not be converted to BazWoggle"])
|
156
|
+
end
|
157
|
+
|
158
|
+
it "will alias a Proc type param" do
|
159
|
+
Foo.class_eval do
|
160
|
+
param :foo, type: Proc
|
161
|
+
def render
|
162
|
+
params.foo
|
163
|
+
end
|
164
|
+
end
|
165
|
+
expect(React.render_to_static_markup(React.create_element(Foo, foo: lambda { 'works!' }))).to eq('<span>works!</span>')
|
166
|
+
end
|
167
|
+
|
168
|
+
it "will create a 'bang' (i.e. update) method if the type is React::Observable" do
|
169
|
+
Foo.class_eval do
|
170
|
+
param :foo, type: React::Observable
|
171
|
+
before_mount do
|
172
|
+
params.foo! "ha!"
|
173
|
+
end
|
174
|
+
def render
|
175
|
+
params.foo
|
176
|
+
end
|
177
|
+
end
|
178
|
+
current_state = ""
|
179
|
+
observer = React::Observable.new(current_state) { |new_state| current_state = new_state }
|
180
|
+
expect(React.render_to_static_markup(React.create_element(Foo, foo: observer))).to eq('<span>ha!</span>')
|
181
|
+
expect(current_state).to eq("ha!")
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
17
186
|
end
|
18
187
|
end
|
@@ -2,10 +2,13 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
if opal?
|
4
4
|
describe React::Validator do
|
5
|
+
before do
|
6
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
7
|
+
end
|
5
8
|
describe '#validate' do
|
6
9
|
describe "Presence validation" do
|
7
10
|
it "should check if required props provided" do
|
8
|
-
validator = React::Validator.build do
|
11
|
+
validator = React::Validator.new(Foo).build do
|
9
12
|
requires :foo
|
10
13
|
requires :bar
|
11
14
|
end
|
@@ -15,7 +18,7 @@ describe React::Validator do
|
|
15
18
|
end
|
16
19
|
|
17
20
|
it "should check if passed non specified prop" do
|
18
|
-
validator = React::Validator.build do
|
21
|
+
validator = React::Validator.new(Foo).build do
|
19
22
|
optional :foo
|
20
23
|
end
|
21
24
|
|
@@ -26,7 +29,7 @@ describe React::Validator do
|
|
26
29
|
|
27
30
|
describe "Type validation" do
|
28
31
|
it "should check if passed value with wrong type" do
|
29
|
-
validator = React::Validator.build do
|
32
|
+
validator = React::Validator.new(Foo).build do
|
30
33
|
requires :foo, type: String
|
31
34
|
end
|
32
35
|
|
@@ -36,7 +39,7 @@ describe React::Validator do
|
|
36
39
|
|
37
40
|
it "should check if passed value with wrong custom type" do
|
38
41
|
stub_const 'Bar', Class.new
|
39
|
-
validator = React::Validator.build do
|
42
|
+
validator = React::Validator.new(Foo).build do
|
40
43
|
requires :foo, type: Bar
|
41
44
|
end
|
42
45
|
|
@@ -45,7 +48,7 @@ describe React::Validator do
|
|
45
48
|
end
|
46
49
|
|
47
50
|
it 'coerces native JS prop types to opal objects' do
|
48
|
-
validator = React::Validator.build do
|
51
|
+
validator = React::Validator.new(Foo).build do
|
49
52
|
requires :foo, type: `{ x: 1 }`
|
50
53
|
end
|
51
54
|
|
@@ -54,7 +57,7 @@ describe React::Validator do
|
|
54
57
|
end
|
55
58
|
|
56
59
|
it 'coerces native JS values to opal objects' do
|
57
|
-
validator = React::Validator.build do
|
60
|
+
validator = React::Validator.new(Foo).build do
|
58
61
|
requires :foo, type: Array[Fixnum]
|
59
62
|
end
|
60
63
|
|
@@ -63,7 +66,7 @@ describe React::Validator do
|
|
63
66
|
end
|
64
67
|
|
65
68
|
it "should support Array[Class] validation" do
|
66
|
-
validator = React::Validator.build do
|
69
|
+
validator = React::Validator.new(Foo).build do
|
67
70
|
requires :foo, type: Array[Hash]
|
68
71
|
end
|
69
72
|
|
@@ -80,7 +83,7 @@ describe React::Validator do
|
|
80
83
|
|
81
84
|
describe "Limited values" do
|
82
85
|
it "should check if passed value is not one of the specified values" do
|
83
|
-
validator = React::Validator.build do
|
86
|
+
validator = React::Validator.new(Foo).build do
|
84
87
|
requires :foo, values: [4,5,6]
|
85
88
|
end
|
86
89
|
|
@@ -93,7 +96,7 @@ describe React::Validator do
|
|
93
96
|
describe '#undefined_props' do
|
94
97
|
let(:props) { { foo: 'foo', bar: 'bar', biz: 'biz', baz: 'baz' } }
|
95
98
|
let(:validator) do
|
96
|
-
React::Validator.build do
|
99
|
+
React::Validator.new(Foo).build do
|
97
100
|
requires :foo
|
98
101
|
optional :bar
|
99
102
|
end
|
@@ -112,7 +115,7 @@ describe React::Validator do
|
|
112
115
|
|
113
116
|
describe "default_props" do
|
114
117
|
it "should return specified default values" do
|
115
|
-
validator = React::Validator.build do
|
118
|
+
validator = React::Validator.new(Foo).build do
|
116
119
|
requires :foo, default: 10
|
117
120
|
requires :bar
|
118
121
|
optional :lorem, default: 20
|
@@ -12,6 +12,14 @@ RSpec.describe ReactiveRuby::ComponentLoader do
|
|
12
12
|
let(:context) { ExecJS.compile(GLOBAL_WRAPPER + js) }
|
13
13
|
let(:v8_context) { context.instance_variable_get(:@v8_context) }
|
14
14
|
|
15
|
+
describe '.new' do
|
16
|
+
it 'raises a meaningful exception when initialized without a context' do
|
17
|
+
expect {
|
18
|
+
described_class.new(nil)
|
19
|
+
}.to raise_error(/Could not obtain ExecJS runtime context/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
15
23
|
describe '#load' do
|
16
24
|
it 'loads given asset file into context' do
|
17
25
|
loader = described_class.new(v8_context)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reactive-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.32
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Chang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opal
|
@@ -262,6 +262,7 @@ files:
|
|
262
262
|
- lib/react/callbacks.rb
|
263
263
|
- lib/react/component.rb
|
264
264
|
- lib/react/component/api.rb
|
265
|
+
- lib/react/component/base.rb
|
265
266
|
- lib/react/component/class_methods.rb
|
266
267
|
- lib/react/element.rb
|
267
268
|
- lib/react/event.rb
|
@@ -292,11 +293,13 @@ files:
|
|
292
293
|
- spec/controller_helper_spec.rb
|
293
294
|
- spec/index.html.erb
|
294
295
|
- spec/react/callbacks_spec.rb
|
296
|
+
- spec/react/component_base_spec.rb
|
295
297
|
- spec/react/component_spec.rb
|
296
298
|
- spec/react/dsl_spec.rb
|
297
299
|
- spec/react/element_spec.rb
|
298
300
|
- spec/react/event_spec.rb
|
299
301
|
- spec/react/native_library_spec.rb
|
302
|
+
- spec/react/observable_spec.rb
|
300
303
|
- spec/react/param_declaration_spec.rb
|
301
304
|
- spec/react/react_spec.rb
|
302
305
|
- spec/react/react_state_spec.rb
|
@@ -338,11 +341,13 @@ test_files:
|
|
338
341
|
- spec/controller_helper_spec.rb
|
339
342
|
- spec/index.html.erb
|
340
343
|
- spec/react/callbacks_spec.rb
|
344
|
+
- spec/react/component_base_spec.rb
|
341
345
|
- spec/react/component_spec.rb
|
342
346
|
- spec/react/dsl_spec.rb
|
343
347
|
- spec/react/element_spec.rb
|
344
348
|
- spec/react/event_spec.rb
|
345
349
|
- spec/react/native_library_spec.rb
|
350
|
+
- spec/react/observable_spec.rb
|
346
351
|
- spec/react/param_declaration_spec.rb
|
347
352
|
- spec/react/react_spec.rb
|
348
353
|
- spec/react/react_state_spec.rb
|