Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

Commit 807efce

Browse files
catmandozetachang
authored andcommitted
closes #152
1 parent c994c1a commit 807efce

File tree

3 files changed

+104
-15
lines changed

3 files changed

+104
-15
lines changed

lib/react/component/class_methods.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ module React
22
module Component
33
# class level methods (macros) for components
44
module ClassMethods
5+
6+
def reactrb_component?
7+
true
8+
end
9+
510
def backtrace(*args)
611
@dont_catch_exceptions = (args[0] == :none)
712
@backtrace_off = @dont_catch_exceptions || (args[0] == :off)

lib/react/rendering_context.rb

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,12 @@ def self.build_only(name, *args, &block)
1313

1414
def self.render(name, *args, &block)
1515
remove_nodes_from_args(args)
16-
@buffer = [] unless @buffer
16+
@buffer ||= [] unless @buffer
1717
if block
1818
element = build do
1919
saved_waiting_on_resources = waiting_on_resources
2020
self.waiting_on_resources = nil
21-
result = block.call
22-
# Todo figure out how children rendering should happen, probably should have special method that pushes children into the buffer
23-
# i.e. render_child/render_children that takes Element/Array[Element] and does the push into the buffer
24-
if !name && ( # !name means called from outer render so we check that it has rendered correctly
25-
(@buffer.count > 1) || # should only render one element
26-
(@buffer.count == 1 && @buffer.last != result) || # it should return that element
27-
(@buffer.count == 0 && !(result.is_a?(String) || (result.respond_to?(:acts_as_string?) && result.acts_as_string?) || result.is_a?(Element))) #for convience we will also convert the return value to a span if its a string
28-
)
29-
raise "a components render method must generate and return exactly 1 element or a string"
30-
end
31-
32-
@buffer << result.to_s if result.is_a? String || (result.respond_to?(:acts_as_string?) && result.acts_as_string?) # For convience we push the last return value on if its a string
33-
@buffer << result if result.is_a?(Element) && @buffer.count == 0
21+
run_child_block(name.nil?, &block)
3422
if name
3523
buffer = @buffer.dup
3624
React.create_element(name, *args) { buffer }.tap do |element|
@@ -44,7 +32,6 @@ def self.render(name, *args, &block)
4432
end
4533
elsif name.is_a? React::Element
4634
element = name
47-
# I BELIEVE WAITING ON RESOURCES SHOULD ALREADY BE SET
4835
else
4936
element = React.create_element(name, *args)
5037
element.waiting_on_resources = waiting_on_resources
@@ -78,6 +65,51 @@ def self.remove_nodes_from_args(args)
7865
value.as_node if value.is_a?(Element) rescue nil
7966
end if args[0] && args[0].is_a?(Hash)
8067
end
68+
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
95+
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
108+
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}"
112+
end
81113
end
82114

83115
class ::Object

spec/react/component_spec.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,58 @@ def render; "hello" end
495495
end
496496
end
497497

498+
describe 'Render Error Handling' do
499+
before(:each) do
500+
%x{
501+
window.test_log = [];
502+
window.org_warn_console = window.console.warn;
503+
window.org_error_console = window.console.error
504+
window.console.warn = window.console.error = function(str){window.test_log.push(str)}
505+
}
506+
end
507+
it "will generate a message if render returns something other than an Element or a String" do
508+
foo = Class.new(React::Component::Base)
509+
foo.class_eval do
510+
def render; Hash.new; end
511+
end
512+
513+
renderToDocument(foo)
514+
`window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
515+
expect(`test_log`.first).to match /Instead the Hash \{\} was returned/
516+
end
517+
it "will generate a message if render returns a Component class" do
518+
stub_const 'Foo', Class.new(React::Component::Base)
519+
foo = Class.new(React::Component::Base)
520+
foo.class_eval do
521+
def render; Foo; end
522+
end
523+
524+
renderToDocument(foo)
525+
`window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
526+
expect(`test_log`.first).to match /Did you mean Foo()/
527+
end
528+
it "will generate a message if more than 1 element is generated" do
529+
foo = Class.new(React::Component::Base)
530+
foo.class_eval do
531+
def render; "hello".span; "goodby".span; end
532+
end
533+
534+
renderToDocument(foo)
535+
`window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
536+
expect(`test_log`.first).to match /Instead 2 elements were generated/
537+
end
538+
it "will generate a message if the element generated is not the element returned" do
539+
foo = Class.new(React::Component::Base)
540+
foo.class_eval do
541+
def render; "hello".span; "goodby".span.delete; end
542+
end
543+
544+
renderToDocument(foo)
545+
`window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
546+
expect(`test_log`.first).to match /A different element was returned than was generated within the DSL/
547+
end
548+
end
549+
498550
describe 'Event handling' do
499551
before do
500552
stub_const 'Foo', Class.new

0 commit comments

Comments
 (0)