react.rb 0.0.1

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +48 -0
  5. data/LICENSE +19 -0
  6. data/README.md +166 -0
  7. data/config.ru +15 -0
  8. data/example/react-tutorial/Gemfile +6 -0
  9. data/example/react-tutorial/Gemfile.lock +45 -0
  10. data/example/react-tutorial/README.md +8 -0
  11. data/example/react-tutorial/_comments.json +6 -0
  12. data/example/react-tutorial/config.ru +55 -0
  13. data/example/react-tutorial/example.rb +104 -0
  14. data/example/react-tutorial/public/base.css +62 -0
  15. data/example/todos/Gemfile +11 -0
  16. data/example/todos/Gemfile.lock +84 -0
  17. data/example/todos/README.md +37 -0
  18. data/example/todos/Rakefile +8 -0
  19. data/example/todos/app/application.rb +22 -0
  20. data/example/todos/app/components/app.react.rb +61 -0
  21. data/example/todos/app/components/footer.react.rb +31 -0
  22. data/example/todos/app/components/todo_item.react.rb +46 -0
  23. data/example/todos/app/components/todo_list.react.rb +25 -0
  24. data/example/todos/app/models/todo.rb +19 -0
  25. data/example/todos/config.ru +14 -0
  26. data/example/todos/index.html.haml +16 -0
  27. data/example/todos/spec/todo_spec.rb +28 -0
  28. data/example/todos/vendor/base.css +410 -0
  29. data/example/todos/vendor/bg.png +0 -0
  30. data/example/todos/vendor/jquery.js +4 -0
  31. data/lib/react.rb +16 -0
  32. data/lib/react/api.rb +95 -0
  33. data/lib/react/callbacks.rb +35 -0
  34. data/lib/react/component.rb +197 -0
  35. data/lib/react/element.rb +63 -0
  36. data/lib/react/event.rb +76 -0
  37. data/lib/react/ext/hash.rb +9 -0
  38. data/lib/react/ext/string.rb +8 -0
  39. data/lib/react/top_level.rb +50 -0
  40. data/lib/react/validator.rb +65 -0
  41. data/lib/react/version.rb +3 -0
  42. data/react.rb.gemspec +24 -0
  43. data/spec/callbacks_spec.rb +107 -0
  44. data/spec/component_spec.rb +556 -0
  45. data/spec/element_spec.rb +60 -0
  46. data/spec/event_spec.rb +22 -0
  47. data/spec/react_spec.rb +168 -0
  48. data/spec/reactjs/index.html.erb +10 -0
  49. data/spec/spec_helper.rb +29 -0
  50. data/spec/validator_spec.rb +79 -0
  51. data/vendor/active_support/core_ext/array/extract_options.rb +29 -0
  52. data/vendor/active_support/core_ext/class/attribute.rb +127 -0
  53. data/vendor/active_support/core_ext/kernel/singleton_class.rb +13 -0
  54. data/vendor/active_support/core_ext/module/remove_method.rb +11 -0
  55. metadata +189 -0
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def shallow_to_n
3
+ hash = `{}`
4
+ self.map do |key, value|
5
+ `hash[#{key}] = #{value}`
6
+ end
7
+ hash
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ class String
2
+ def event_camelize
3
+ `#{self}.replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
4
+ var capitalize = true;
5
+ return capitalize ? word.substr(0,1).toUpperCase()+word.substr(1) : word;
6
+ })`
7
+ end
8
+ end
@@ -0,0 +1,50 @@
1
+ require "native"
2
+ require 'active_support'
3
+
4
+ module React
5
+ HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
6
+ button canvas caption cite code col colgroup data datalist dd del details dfn
7
+ dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
8
+ h6 head header hr html i iframe img input ins kbd keygen label legend li link
9
+ main map mark menu menuitem meta meter nav noscript object ol optgroup option
10
+ output p param picture pre progress q rp rt ruby s samp script section select
11
+ small source span strong style sub summary sup table tbody td textarea tfoot th
12
+ thead time title tr track u ul var video wbr)
13
+ ATTRIBUTES = %w(accept acceptCharset accessKey action allowFullScreen allowTransparency alt
14
+ async autoComplete autoPlay cellPadding cellSpacing charSet checked classID
15
+ className cols colSpan content contentEditable contextMenu controls coords
16
+ crossOrigin data dateTime defer dir disabled download draggable encType form
17
+ formAction formEncType formMethod formNoValidate formTarget frameBorder height
18
+ hidden href hrefLang htmlFor httpEquiv icon id label lang list loop manifest
19
+ marginHeight marginWidth max maxLength media mediaGroup method min multiple
20
+ muted name noValidate open pattern placeholder poster preload radioGroup
21
+ readOnly rel required role rows rowSpan sandbox scope scrolling seamless
22
+ selected shape size sizes span spellCheck src srcDoc srcSet start step style
23
+ tabIndex target title type useMap value width wmode dangerouslySetInnerHTML)
24
+
25
+ def self.create_element(type, properties = {}, &block)
26
+ React::API.create_element(type, properties, &block)
27
+ end
28
+
29
+ def self.render(element, container)
30
+ component = Native(`React.render(#{element.to_n}, container, function(){#{yield if block_given?}})`)
31
+ component.class.include(React::Component::API)
32
+ component
33
+ end
34
+
35
+ def self.is_valid_element(element)
36
+ element.kind_of?(React::Element) && `React.isValidElement(#{element.to_n})`
37
+ end
38
+
39
+ def self.render_to_string(element)
40
+ `React.renderToString(#{element.to_n})`
41
+ end
42
+
43
+ def self.render_to_static_markup(element)
44
+ `React.renderToStaticMarkup(#{element.to_n})`
45
+ end
46
+
47
+ def self.unmount_component_at_node(node)
48
+ `React.unmountComponentAtNode(node)`
49
+ end
50
+ end
@@ -0,0 +1,65 @@
1
+ module React
2
+ class Validator
3
+ def self.build(&block)
4
+ validator = self.new
5
+ validator.instance_eval(&block)
6
+ validator
7
+ end
8
+
9
+ def initialize
10
+ @rules = {}
11
+ end
12
+
13
+ def requires(prop_name, options = {})
14
+ rule = options
15
+ options[:required] = true
16
+ @rules[prop_name] = options
17
+ end
18
+
19
+ def optional(prop_name, options = {})
20
+ rule = options
21
+ options[:required] = false
22
+ @rules[prop_name] = options
23
+ end
24
+
25
+ def validate(props)
26
+ errors = []
27
+ props.keys.each do |prop_name|
28
+ errors << "Provided prop `#{prop_name}` not specified in spec" if @rules[prop_name] == nil
29
+ end
30
+
31
+ props = props.select {|key| @rules.keys.include?(key) }
32
+
33
+ # requires or not
34
+ (@rules.keys - props.keys).each do |prop_name|
35
+ errors << "Required prop `#{prop_name}` was not specified" if @rules[prop_name][:required]
36
+ end
37
+
38
+ # type
39
+ props.each do |prop_name, value|
40
+ if klass = @rules[prop_name][:type]
41
+ if klass.is_a?(Array)
42
+ errors << "Provided prop `#{prop_name}` was not an Array of the specified type `#{klass[0]}`" unless value.all?{ |ele| ele.is_a?(klass[0]) }
43
+ else
44
+ errors << "Provided prop `#{prop_name}` was not the specified type `#{klass}`" unless value.is_a?(klass)
45
+ end
46
+ end
47
+ end
48
+
49
+ # values
50
+ props.each do |prop_name, value|
51
+ if values = @rules[prop_name][:values]
52
+ errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value" unless values.include?(value)
53
+ end
54
+ end
55
+
56
+ errors
57
+ end
58
+
59
+ def default_props
60
+ @rules
61
+ .select {|key, value| value.keys.include?("default") }
62
+ .inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module React
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/react/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'react.rb'
6
+ s.version = React::VERSION
7
+ s.author = 'David Chang'
8
+ s.email = 'zeta11235813@gmail.com'
9
+ s.homepage = 'https://github.com/zetachang/react.rb'
10
+ s.summary = 'Opal Ruby wrapper of React.js library.'
11
+ s.description = "Write reactive UI component with Ruby's elegancy and compiled to run in Javascript."
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.require_paths = ['lib', 'vendor']
17
+
18
+ s.add_runtime_dependency 'opal', '~> 0.6.0'
19
+ s.add_runtime_dependency 'opal-activesupport'
20
+ s.add_development_dependency 'react-source', '~> 0.12'
21
+ s.add_development_dependency 'opal-rspec', '~> 0.3.0.beta3'
22
+ s.add_development_dependency 'sinatra'
23
+ s.add_development_dependency 'opal-jquery'
24
+ end
@@ -0,0 +1,107 @@
1
+ require "spec_helper"
2
+ require "react/callbacks"
3
+
4
+ describe React::Callbacks do
5
+ it "should be able to define callback" do
6
+ stub_const 'Foo', Class.new
7
+ Foo.class_eval do
8
+ include React::Callbacks
9
+ define_callback :before_dinner
10
+
11
+ before_dinner :wash_hand
12
+
13
+ def wash_hand
14
+
15
+ end
16
+ end
17
+
18
+ expect_any_instance_of(Foo).to receive(:wash_hand)
19
+ Foo.new.run_callback(:before_dinner)
20
+ end
21
+
22
+ it "should be able to define multiple callbacks" do
23
+ stub_const 'Foo', Class.new
24
+ Foo.class_eval do
25
+ include React::Callbacks
26
+ define_callback :before_dinner
27
+
28
+ before_dinner :wash_hand, :turn_of_laptop
29
+
30
+ def wash_hand;end
31
+
32
+ def turn_of_laptop;end
33
+ end
34
+
35
+ expect_any_instance_of(Foo).to receive(:wash_hand)
36
+ expect_any_instance_of(Foo).to receive(:turn_of_laptop)
37
+ Foo.new.run_callback(:before_dinner)
38
+ end
39
+
40
+ it "should be able to define block callback" do
41
+ stub_const 'Foo', Class.new
42
+ Foo.class_eval do
43
+ include React::Callbacks
44
+ attr_accessor :a
45
+ attr_accessor :b
46
+
47
+ define_callback :before_dinner
48
+
49
+ before_dinner do
50
+ self.a = 10
51
+ end
52
+ before_dinner do
53
+ self.b = 20
54
+ end
55
+ end
56
+
57
+ foo = Foo.new
58
+ foo.run_callback(:before_dinner)
59
+ expect(foo.a).to eq(10)
60
+ expect(foo.b).to eq(20)
61
+ end
62
+
63
+ it "should be able to define multiple callback group" do
64
+ stub_const 'Foo', Class.new
65
+ Foo.class_eval do
66
+ include React::Callbacks
67
+ define_callback :before_dinner
68
+ define_callback :after_dinner
69
+ attr_accessor :a
70
+
71
+ before_dinner do
72
+ self.a = 10
73
+ end
74
+ end
75
+
76
+ foo = Foo.new
77
+ foo.run_callback(:before_dinner)
78
+ foo.run_callback(:after_dinner)
79
+
80
+ expect(foo.a).to eq(10)
81
+ end
82
+
83
+ it "should be able to receive args as callback" do
84
+ stub_const 'Foo', Class.new
85
+ Foo.class_eval do
86
+ include React::Callbacks
87
+ define_callback :before_dinner
88
+ define_callback :after_dinner
89
+
90
+ attr_accessor :lorem
91
+
92
+ before_dinner do |a, b|
93
+ self.lorem = "#{a}-#{b}"
94
+ end
95
+
96
+ after_dinner :eat_ice_cream
97
+ def eat_ice_cream(a,b,c); end
98
+ end
99
+
100
+ expect_any_instance_of(Foo).to receive(:eat_ice_cream).with(4,5,6)
101
+
102
+ foo = Foo.new
103
+ foo.run_callback(:before_dinner, 1, 2)
104
+ foo.run_callback(:after_dinner, 4, 5, 6)
105
+ expect(foo.lorem).to eq('1-2')
106
+ end
107
+ end
@@ -0,0 +1,556 @@
1
+ require "spec_helper"
2
+
3
+ describe React::Component do
4
+ after(:each) do
5
+ React::API.clear_component_class_cache
6
+ end
7
+
8
+ it "should define component spec methods" do
9
+ stub_const 'Foo', Class.new
10
+ Foo.class_eval do
11
+ include React::Component
12
+ def render
13
+ React.create_element("div")
14
+ end
15
+ end
16
+
17
+ # Class Methods
18
+ expect(Foo).to respond_to("initial_state")
19
+ expect(Foo).to respond_to("default_props")
20
+ expect(Foo).to respond_to("prop_types")
21
+
22
+ # Instance method
23
+ expect(Foo.new).to respond_to("component_will_mount")
24
+ expect(Foo.new).to respond_to("component_did_mount")
25
+ expect(Foo.new).to respond_to("component_will_receive_props")
26
+ expect(Foo.new).to respond_to("should_component_update?")
27
+ expect(Foo.new).to respond_to("component_will_update")
28
+ expect(Foo.new).to respond_to("component_did_update")
29
+ expect(Foo.new).to respond_to("component_will_unmount")
30
+ end
31
+
32
+ describe "Life Cycle" do
33
+ before(:each) do
34
+ stub_const 'Foo', Class.new
35
+ Foo.class_eval do
36
+ include React::Component
37
+ def render
38
+ React.create_element("div") { "lorem" }
39
+ end
40
+ end
41
+ end
42
+
43
+ it "should invoke `before_mount` registered methods when `componentWillMount()`" do
44
+ Foo.class_eval do
45
+ before_mount :bar, :bar2
46
+ def bar; end
47
+ def bar2; end
48
+ end
49
+
50
+ expect_any_instance_of(Foo).to receive(:bar)
51
+ expect_any_instance_of(Foo).to receive(:bar2)
52
+
53
+ renderToDocument(Foo)
54
+ end
55
+
56
+ it "should invoke `after_mount` registered methods when `componentDidMount()`" do
57
+ Foo.class_eval do
58
+ after_mount :bar3, :bar4
59
+ def bar3; end
60
+ def bar4; end
61
+ end
62
+
63
+ expect_any_instance_of(Foo).to receive(:bar3)
64
+ expect_any_instance_of(Foo).to receive(:bar4)
65
+
66
+ renderToDocument(Foo)
67
+ end
68
+
69
+ it "should allow multiple class declared life cycle hooker" do
70
+ stub_const 'FooBar', Class.new
71
+ Foo.class_eval do
72
+ before_mount :bar
73
+ def bar; end
74
+ end
75
+
76
+ FooBar.class_eval do
77
+ include React::Component
78
+ after_mount :bar2
79
+ def bar2; end
80
+ def render
81
+ React.create_element("div") { "lorem" }
82
+ end
83
+ end
84
+
85
+ expect_any_instance_of(Foo).to receive(:bar)
86
+
87
+ renderToDocument(Foo)
88
+ end
89
+
90
+ it "should allow block for life cycle callback" do
91
+ Foo.class_eval do
92
+ define_state(:foo)
93
+
94
+ before_mount do
95
+ self.foo = "bar"
96
+ end
97
+ end
98
+
99
+ element = renderToDocument(Foo)
100
+ expect(element.state.foo).to be("bar")
101
+ end
102
+ end
103
+
104
+ describe "State setter & getter" do
105
+ before(:each) do
106
+ stub_const 'Foo', Class.new
107
+ Foo.class_eval do
108
+ include React::Component
109
+ def render
110
+ React.create_element("div") { "lorem" }
111
+ end
112
+ end
113
+ end
114
+
115
+ it "should define setter using `define_state`" do
116
+ Foo.class_eval do
117
+ define_state :foo
118
+ before_mount :set_up
119
+ def set_up
120
+ self.foo = "bar"
121
+ end
122
+ end
123
+
124
+ element = renderToDocument(Foo)
125
+ expect(element.state.foo).to be("bar")
126
+ end
127
+
128
+ it "should define init state by passing a block to `define_state`" do
129
+ Foo.class_eval do
130
+ define_state(:foo) { 10 }
131
+ end
132
+
133
+ element = renderToDocument(Foo)
134
+ expect(element.state.foo).to be(10)
135
+ end
136
+
137
+ it "should define getter using `define_state`" do
138
+ Foo.class_eval do
139
+ define_state(:foo) { 10 }
140
+ before_mount :bump
141
+ def bump
142
+ self.foo = self.foo + 20
143
+ end
144
+ end
145
+
146
+ element = renderToDocument(Foo)
147
+ expect(element.state.foo).to be(30)
148
+ end
149
+
150
+ it "should define multiple state accessor by passing symols array to `define_state`" do
151
+ Foo.class_eval do
152
+ define_state :foo, :foo2
153
+ before_mount :set_up
154
+ def set_up
155
+ self.foo = 10
156
+ self.foo2 = 20
157
+ end
158
+ end
159
+
160
+ element = renderToDocument(Foo)
161
+ expect(element.state.foo).to be(10)
162
+ expect(element.state.foo2).to be(20)
163
+ end
164
+
165
+ it "should invoke `define_state` multiple times to define states" do
166
+ Foo.class_eval do
167
+ define_state(:foo) { 30 }
168
+ define_state(:foo2) { 40 }
169
+ end
170
+
171
+ element = renderToDocument(Foo)
172
+ expect(element.state.foo).to be(30)
173
+ expect(element.state.foo2).to be(40)
174
+ end
175
+
176
+ it "should raise error if multiple states and block given at the same time" do
177
+ expect {
178
+ Foo.class_eval do
179
+ define_state(:foo, :foo2) { 30 }
180
+ end
181
+ }.to raise_error
182
+ end
183
+
184
+ it "should get state in render method" do
185
+ Foo.class_eval do
186
+ define_state(:foo) { 10 }
187
+ def render
188
+ React.create_element("div") { self.foo }
189
+ end
190
+ end
191
+
192
+ element = renderToDocument(Foo)
193
+ expect(element.getDOMNode.textContent).to eq("10")
194
+ end
195
+
196
+ it "should support original `setState` as `set_state` method" do
197
+ Foo.class_eval do
198
+ before_mount do
199
+ self.set_state(foo: "bar")
200
+ end
201
+ end
202
+
203
+ element = renderToDocument(Foo)
204
+ expect(element.state.foo).to be("bar")
205
+ end
206
+
207
+ it "should support original `replaceState` as `set_state!` method" do
208
+ Foo.class_eval do
209
+ before_mount do
210
+ self.set_state(foo: "bar")
211
+ self.set_state!(bar: "lorem")
212
+ end
213
+ end
214
+
215
+ element = renderToDocument(Foo)
216
+ expect(element.state.foo).to be_nil
217
+ expect(element.state.bar).to eq("lorem")
218
+ end
219
+
220
+ it "should support originl `state` method" do
221
+ Foo.class_eval do
222
+ before_mount do
223
+ self.set_state(foo: "bar")
224
+ end
225
+
226
+ def render
227
+ div { self.state[:foo] }
228
+ end
229
+ end
230
+
231
+ expect(React.render_to_static_markup(React.create_element(Foo))).to eq("<div>bar</div>")
232
+ end
233
+
234
+ it "should transform state getter to Ruby object" do
235
+ Foo.class_eval do
236
+ define_state :foo
237
+
238
+ before_mount do
239
+ self.foo = [{a: 10}]
240
+ end
241
+
242
+ def render
243
+ div { self.foo[0][:a] }
244
+ end
245
+ end
246
+
247
+ expect(React.render_to_static_markup(React.create_element(Foo))).to eq("<div>10</div>")
248
+ end
249
+ end
250
+
251
+ describe "Props" do
252
+ describe "this.props could be accessed through `params` method" do
253
+ before do
254
+ stub_const 'Foo', Class.new
255
+ Foo.class_eval do
256
+ include React::Component
257
+ end
258
+ end
259
+
260
+ it "should read from parent passed properties through `params`" do
261
+ Foo.class_eval do
262
+ def render
263
+ React.create_element("div") { params[:prop] }
264
+ end
265
+ end
266
+
267
+ element = renderToDocument(Foo, prop: "foobar")
268
+ expect(element.getDOMNode.textContent).to eq("foobar")
269
+ end
270
+
271
+ it "should access nested params as orignal Ruby object" do
272
+ Foo.class_eval do
273
+ def render
274
+ React.create_element("div") { params[:prop][0][:foo] }
275
+ end
276
+ end
277
+
278
+ element = renderToDocument(Foo, prop: [{foo: 10}])
279
+ expect(element.getDOMNode.textContent).to eq("10")
280
+ end
281
+ end
282
+
283
+ describe "Props Updating" do
284
+ before do
285
+ stub_const 'Foo', Class.new
286
+ Foo.class_eval do
287
+ include React::Component
288
+ end
289
+ end
290
+
291
+ it "should support original `setProps` as method `set_props`" do
292
+ Foo.class_eval do
293
+ def render
294
+ React.create_element("div") { params[:foo] }
295
+ end
296
+ end
297
+
298
+ element = renderToDocument(Foo, {foo: 10})
299
+ element.set_props(foo: 20)
300
+ expect(element.dom_node.innerHTML).to eq('20')
301
+ end
302
+
303
+ it "should support original `replaceProps` as method `set_props!`" do
304
+ Foo.class_eval do
305
+ def render
306
+ React.create_element("div") { params[:foo] ? "exist" : "null" }
307
+ end
308
+ end
309
+
310
+ element = renderToDocument(Foo, {foo: 10})
311
+ element.set_props!(bar: 20)
312
+ expect(element.dom_node.innerHTML).to eq('null')
313
+ end
314
+ end
315
+
316
+ describe "Prop validation" do
317
+ before do
318
+ stub_const 'Foo', Class.new
319
+ Foo.class_eval do
320
+ include React::Component
321
+ end
322
+ end
323
+
324
+ it "should specify validation rules using `params` class method" do
325
+ Foo.class_eval do
326
+ params do
327
+ requires :foo, type: String
328
+ optional :bar
329
+ end
330
+ end
331
+
332
+ expect(Foo.prop_types).to have_key(:_componentValidator)
333
+ end
334
+
335
+ it "should log error in warning if validation failed" do
336
+ stub_const 'Lorem', Class.new
337
+ Foo.class_eval do
338
+ params do
339
+ requires :foo
340
+ requires :lorem, type: Lorem
341
+ optional :bar, type: String
342
+ end
343
+
344
+ def render; div; end
345
+ end
346
+
347
+ %x{
348
+ var log = [];
349
+ var org_console = window.console;
350
+ window.console = {warn: function(str){log.push(str)}}
351
+ }
352
+ renderToDocument(Foo, bar: 10, lorem: Lorem.new)
353
+ `window.console = org_console;`
354
+ expect(`log`).to eq(["Warning: In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` was not the specified type `String`"])
355
+ end
356
+
357
+ it "should not log anything if validation pass" do
358
+ stub_const 'Lorem', Class.new
359
+ Foo.class_eval do
360
+ params do
361
+ requires :foo
362
+ requires :lorem, type: Lorem
363
+ optional :bar, type: String
364
+ end
365
+
366
+ def render; div; end
367
+ end
368
+
369
+ %x{
370
+ var log = [];
371
+ var org_console = window.console;
372
+ window.console = {warn: function(str){log.push(str)}}
373
+ }
374
+ renderToDocument(Foo, foo: 10, bar: "10", lorem: Lorem.new)
375
+ `window.console = org_console;`
376
+ expect(`log`).to eq([])
377
+ end
378
+ end
379
+
380
+ describe "Default props" do
381
+ it "should set default props using validation helper" do
382
+ stub_const 'Foo', Class.new
383
+ Foo.class_eval do
384
+ include React::Component
385
+ params do
386
+ optional :foo, default: "foo"
387
+ optional :bar, default: "bar"
388
+ end
389
+
390
+ def render
391
+ div { params[:foo] + "-" + params[:bar]}
392
+ end
393
+ end
394
+
395
+ expect(React.render_to_static_markup(React.create_element(Foo, foo: "lorem"))).to eq("<div>lorem-bar</div>")
396
+ expect(React.render_to_static_markup(React.create_element(Foo))).to eq("<div>foo-bar</div>")
397
+ end
398
+ end
399
+ end
400
+
401
+ describe "Event handling" do
402
+ before do
403
+ stub_const 'Foo', Class.new
404
+ Foo.class_eval do
405
+ include React::Component
406
+ end
407
+ end
408
+
409
+ it "should work in render method" do
410
+ Foo.class_eval do
411
+ define_state(:clicked) { false }
412
+
413
+ def render
414
+ React.create_element("div").on(:click) do
415
+ self.clicked = true
416
+ end
417
+ end
418
+ end
419
+
420
+ element = React.create_element(Foo)
421
+ instance = renderElementToDocument(element)
422
+ simulateEvent(:click, instance)
423
+ expect(instance.state.clicked).to eq(true)
424
+ end
425
+
426
+ it "should invoke handler on `this.props` using emit" do
427
+ Foo.class_eval do
428
+ after_mount :setup
429
+
430
+ def setup
431
+ self.emit(:foo_submit, "bar")
432
+ end
433
+
434
+ def render
435
+ React.create_element("div")
436
+ end
437
+ end
438
+
439
+ expect { |b|
440
+ element = React.create_element(Foo).on(:foo_submit, &b)
441
+ renderElementToDocument(element)
442
+ }.to yield_with_args("bar")
443
+ end
444
+
445
+ it "should invoke handler with multiple params using emit" do
446
+ Foo.class_eval do
447
+ after_mount :setup
448
+
449
+ def setup
450
+ self.emit(:foo_invoked, [1,2,3], "bar")
451
+ end
452
+
453
+ def render
454
+ React.create_element("div")
455
+ end
456
+ end
457
+
458
+ expect { |b|
459
+ element = React.create_element(Foo).on(:foo_invoked, &b)
460
+ renderElementToDocument(element)
461
+ }.to yield_with_args([1,2,3], "bar")
462
+ end
463
+ end
464
+
465
+ describe "Refs" do
466
+ before do
467
+ stub_const 'Foo', Class.new
468
+ Foo.class_eval do
469
+ include React::Component
470
+ end
471
+ end
472
+
473
+ it "should correctly assign refs" do
474
+ Foo.class_eval do
475
+ def render
476
+ React.create_element("input", type: :text, ref: :field)
477
+ end
478
+ end
479
+
480
+ element = renderToDocument(Foo)
481
+ expect(element.refs.field).not_to be_nil
482
+ end
483
+
484
+ it "should access refs through `refs` method" do
485
+ Foo.class_eval do
486
+ def render
487
+ React.create_element("input", type: :text, ref: :field).on(:click) do
488
+ refs[:field].value = "some_stuff"
489
+ end
490
+ end
491
+ end
492
+
493
+ element = renderToDocument(Foo)
494
+ simulateEvent(:click, element)
495
+
496
+ expect(element.refs.field.value).to eq("some_stuff")
497
+ end
498
+ end
499
+
500
+ describe "Render" do
501
+ it "should support element building helpers" do
502
+ stub_const 'Foo', Class.new
503
+ Foo.class_eval do
504
+ include React::Component
505
+
506
+ def render
507
+ div do
508
+ span { params[:foo] }
509
+ end
510
+ end
511
+ end
512
+
513
+ stub_const 'Bar', Class.new
514
+ Bar.class_eval do
515
+ include React::Component
516
+ def render
517
+ div do
518
+ present Foo, foo: "astring"
519
+ end
520
+ end
521
+ end
522
+
523
+ expect(React.render_to_static_markup(React.create_element(Bar))).to eq("<div><div><span>astring</span></div></div>")
524
+ end
525
+
526
+ it "should build single node in top-level render without providing a block" do
527
+ stub_const 'Foo', Class.new
528
+ Foo.class_eval do
529
+ include React::Component
530
+
531
+ def render
532
+ div
533
+ end
534
+ end
535
+
536
+ element = React.create_element(Foo)
537
+ expect(React.render_to_static_markup(element)).to eq("<div></div>")
538
+ end
539
+ end
540
+
541
+ describe "isMounted()" do
542
+ it "should return true if after mounted" do
543
+ stub_const 'Foo', Class.new
544
+ Foo.class_eval do
545
+ include React::Component
546
+
547
+ def render
548
+ React.create_element("div")
549
+ end
550
+ end
551
+
552
+ component = renderToDocument(Foo)
553
+ expect(component.mounted?).to eq(true)
554
+ end
555
+ end
556
+ end