require "spec_helper" describe React::Component do after(:each) do React::API.clear_component_class_cache end it "should define component spec methods" do stub_const 'Foo', Class.new Foo.class_eval do include React::Component def render React.create_element("div") end end # Class Methods expect(Foo).to respond_to("initial_state") expect(Foo).to respond_to("default_props") expect(Foo).to respond_to("prop_types") # Instance method expect(Foo.new).to respond_to("component_will_mount") expect(Foo.new).to respond_to("component_did_mount") expect(Foo.new).to respond_to("component_will_receive_props") expect(Foo.new).to respond_to("should_component_update?") expect(Foo.new).to respond_to("component_will_update") expect(Foo.new).to respond_to("component_did_update") expect(Foo.new).to respond_to("component_will_unmount") end describe "Life Cycle" do before(:each) do stub_const 'Foo', Class.new Foo.class_eval do include React::Component def render React.create_element("div") { "lorem" } end end end it "should invoke `before_mount` registered methods when `componentWillMount()`" do Foo.class_eval do before_mount :bar, :bar2 def bar; end def bar2; end end expect_any_instance_of(Foo).to receive(:bar) expect_any_instance_of(Foo).to receive(:bar2) renderToDocument(Foo) end it "should invoke `after_mount` registered methods when `componentDidMount()`" do Foo.class_eval do after_mount :bar3, :bar4 def bar3; end def bar4; end end expect_any_instance_of(Foo).to receive(:bar3) expect_any_instance_of(Foo).to receive(:bar4) renderToDocument(Foo) end it "should allow multiple class declared life cycle hooker" do stub_const 'FooBar', Class.new Foo.class_eval do before_mount :bar def bar; end end FooBar.class_eval do include React::Component after_mount :bar2 def bar2; end def render React.create_element("div") { "lorem" } end end expect_any_instance_of(Foo).to receive(:bar) renderToDocument(Foo) end it "should allow block for life cycle callback" do Foo.class_eval do define_state(:foo) before_mount do self.foo = "bar" end end element = renderToDocument(Foo) expect(element.state.foo).to be("bar") end end describe "State setter & getter" do before(:each) do stub_const 'Foo', Class.new Foo.class_eval do include React::Component def render React.create_element("div") { "lorem" } end end end it "should define setter using `define_state`" do Foo.class_eval do define_state :foo before_mount :set_up def set_up self.foo = "bar" end end element = renderToDocument(Foo) expect(element.state.foo).to be("bar") end it "should define init state by passing a block to `define_state`" do Foo.class_eval do define_state(:foo) { 10 } end element = renderToDocument(Foo) expect(element.state.foo).to be(10) end it "should define getter using `define_state`" do Foo.class_eval do define_state(:foo) { 10 } before_mount :bump def bump self.foo = self.foo + 20 end end element = renderToDocument(Foo) expect(element.state.foo).to be(30) end it "should define multiple state accessor by passing symols array to `define_state`" do Foo.class_eval do define_state :foo, :foo2 before_mount :set_up def set_up self.foo = 10 self.foo2 = 20 end end element = renderToDocument(Foo) expect(element.state.foo).to be(10) expect(element.state.foo2).to be(20) end it "should invoke `define_state` multiple times to define states" do Foo.class_eval do define_state(:foo) { 30 } define_state(:foo2) { 40 } end element = renderToDocument(Foo) expect(element.state.foo).to be(30) expect(element.state.foo2).to be(40) end it "should raise error if multiple states and block given at the same time" do expect { Foo.class_eval do define_state(:foo, :foo2) { 30 } end }.to raise_error end it "should get state in render method" do Foo.class_eval do define_state(:foo) { 10 } def render React.create_element("div") { self.foo } end end element = renderToDocument(Foo) expect(element.getDOMNode.textContent).to eq("10") end it "should support original `setState` as `set_state` method" do Foo.class_eval do before_mount do self.set_state(foo: "bar") end end element = renderToDocument(Foo) expect(element.state.foo).to be("bar") end it "should support original `replaceState` as `set_state!` method" do Foo.class_eval do before_mount do self.set_state(foo: "bar") self.set_state!(bar: "lorem") end end element = renderToDocument(Foo) expect(element.state.foo).to be_nil expect(element.state.bar).to eq("lorem") end it "should support originl `state` method" do Foo.class_eval do before_mount do self.set_state(foo: "bar") end def render div { self.state[:foo] } end end expect(React.render_to_static_markup(React.create_element(Foo))).to eq("
bar
") end it "should transform state getter to Ruby object" do Foo.class_eval do define_state :foo before_mount do self.foo = [{a: 10}] end def render div { self.foo[0][:a] } end end expect(React.render_to_static_markup(React.create_element(Foo))).to eq("
10
") end end describe "Props" do describe "this.props could be accessed through `params` method" do before do stub_const 'Foo', Class.new Foo.class_eval do include React::Component end end it "should read from parent passed properties through `params`" do Foo.class_eval do def render React.create_element("div") { params[:prop] } end end element = renderToDocument(Foo, prop: "foobar") expect(element.getDOMNode.textContent).to eq("foobar") end it "should access nested params as orignal Ruby object" do Foo.class_eval do def render React.create_element("div") { params[:prop][0][:foo] } end end element = renderToDocument(Foo, prop: [{foo: 10}]) expect(element.getDOMNode.textContent).to eq("10") end end describe "Props Updating" do before do stub_const 'Foo', Class.new Foo.class_eval do include React::Component end end it "should support original `setProps` as method `set_props`" do Foo.class_eval do def render React.create_element("div") { params[:foo] } end end element = renderToDocument(Foo, {foo: 10}) element.set_props(foo: 20) expect(element.dom_node.innerHTML).to eq('20') end it "should support original `replaceProps` as method `set_props!`" do Foo.class_eval do def render React.create_element("div") { params[:foo] ? "exist" : "null" } end end element = renderToDocument(Foo, {foo: 10}) element.set_props!(bar: 20) expect(element.dom_node.innerHTML).to eq('null') end end describe "Prop validation" do before do stub_const 'Foo', Class.new Foo.class_eval do include React::Component end end it "should specify validation rules using `params` class method" do Foo.class_eval do params do requires :foo, type: String optional :bar end end expect(Foo.prop_types).to have_key(:_componentValidator) end it "should log error in warning if validation failed" do stub_const 'Lorem', Class.new Foo.class_eval do params do requires :foo requires :lorem, type: Lorem optional :bar, type: String end def render; div; end end %x{ var log = []; var org_console = window.console; window.console = {warn: function(str){log.push(str)}} } renderToDocument(Foo, bar: 10, lorem: Lorem.new) `window.console = org_console;` expect(`log`).to eq(["Warning: Failed propType: In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` was not the specified type `String`"]) end it "should not log anything if validation pass" do stub_const 'Lorem', Class.new Foo.class_eval do params do requires :foo requires :lorem, type: Lorem optional :bar, type: String end def render; div; end end %x{ var log = []; var org_console = window.console; window.console = {warn: function(str){log.push(str)}} } renderToDocument(Foo, foo: 10, bar: "10", lorem: Lorem.new) `window.console = org_console;` expect(`log`).to eq([]) end end describe "Default props" do it "should set default props using validation helper" do stub_const 'Foo', Class.new Foo.class_eval do include React::Component params do optional :foo, default: "foo" optional :bar, default: "bar" end def render div { params[:foo] + "-" + params[:bar]} end end expect(React.render_to_static_markup(React.create_element(Foo, foo: "lorem"))).to eq("
lorem-bar
") expect(React.render_to_static_markup(React.create_element(Foo))).to eq("
foo-bar
") end end end describe "Event handling" do before do stub_const 'Foo', Class.new Foo.class_eval do include React::Component end end it "should work in render method" do Foo.class_eval do define_state(:clicked) { false } def render React.create_element("div").on(:click) do self.clicked = true end end end element = React.create_element(Foo) instance = renderElementToDocument(element) simulateEvent(:click, instance) expect(instance.state.clicked).to eq(true) end it "should invoke handler on `this.props` using emit" do Foo.class_eval do after_mount :setup def setup self.emit(:foo_submit, "bar") end def render React.create_element("div") end end expect { |b| element = React.create_element(Foo).on(:foo_submit, &b) renderElementToDocument(element) }.to yield_with_args("bar") end it "should invoke handler with multiple params using emit" do Foo.class_eval do after_mount :setup def setup self.emit(:foo_invoked, [1,2,3], "bar") end def render React.create_element("div") end end expect { |b| element = React.create_element(Foo).on(:foo_invoked, &b) renderElementToDocument(element) }.to yield_with_args([1,2,3], "bar") end end describe "Refs" do before do stub_const 'Foo', Class.new Foo.class_eval do include React::Component end end it "should correctly assign refs" do Foo.class_eval do def render React.create_element("input", type: :text, ref: :field) end end element = renderToDocument(Foo) expect(element.refs.field).not_to be_nil end it "should access refs through `refs` method" do Foo.class_eval do def render React.create_element("input", type: :text, ref: :field).on(:click) do refs[:field].value = "some_stuff" end end end element = renderToDocument(Foo) simulateEvent(:click, element) expect(element.refs.field.value).to eq("some_stuff") end end describe "Render" do it "should support element building helpers" do stub_const 'Foo', Class.new Foo.class_eval do include React::Component def render div do span { params[:foo] } end end end stub_const 'Bar', Class.new Bar.class_eval do include React::Component def render div do present Foo, foo: "astring" end end end expect(React.render_to_static_markup(React.create_element(Bar))).to eq("
astring
") end it "should build single node in top-level render without providing a block" do stub_const 'Foo', Class.new Foo.class_eval do include React::Component def render div end end element = React.create_element(Foo) expect(React.render_to_static_markup(element)).to eq("
") end it "should redefine `p` to make method missing work" do stub_const 'Foo', Class.new Foo.class_eval do include React::Component def render p(class_name: "foo") do p div { "lorem ipsum" } p(id: "10") end end end element = React.create_element(Foo) expect(React.render_to_static_markup(element)).to eq("

lorem ipsum

") end it "should only override `p` in render context" do stub_const 'Foo', Class.new Foo.class_eval do include React::Component before_mount do p "first" end after_mount do p "second" end def render div end end expect(Kernel).to receive(:p).with("first") expect(Kernel).to receive(:p).with("second") renderToDocument(Foo) end end describe "isMounted()" do it "should return true if after mounted" do stub_const 'Foo', Class.new Foo.class_eval do include React::Component def render React.create_element("div") end end component = renderToDocument(Foo) expect(component.mounted?).to eq(true) end end end