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

Commit b094f19

Browse files
committed
closes #152
1 parent d556cff commit b094f19

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
@@ -496,6 +496,58 @@ def render; "hello" end
496496
end
497497
end
498498

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

0 commit comments

Comments
 (0)