require 'spec_helper'

describe 'React::Component', js: true do

  it 'defines component spec methods' do
    on_client do
      class Foo
        include React::Component
        def initialize(native = nil)
        end

        def render
          React.create_element('div')
        end
      end
    end
    # class methods
    expect_evaluate_ruby("Foo.respond_to?(:initial_state)").to be_truthy
    expect_evaluate_ruby("Foo.respond_to?(:default_props)").to be_truthy
    expect_evaluate_ruby("Foo.respond_to?(:prop_types)").to be_truthy
    # instance_methods
    expect_evaluate_ruby("Foo.new.respond_to?(:component_will_mount)").to be_truthy
    expect_evaluate_ruby("Foo.new.respond_to?(:component_did_mount)").to be_truthy
    expect_evaluate_ruby("Foo.new.respond_to?(:component_will_receive_props)").to be_truthy
    expect_evaluate_ruby("Foo.new.respond_to?(:should_component_update?)").to be_truthy
    expect_evaluate_ruby("Foo.new.respond_to?(:component_will_update)").to be_truthy
    expect_evaluate_ruby("Foo.new.respond_to?(:component_did_update)").to be_truthy
    expect_evaluate_ruby("Foo.new.respond_to?(:component_will_unmount)").to be_truthy
  end

  describe 'Life Cycle' do
    before(:each) do
      on_client do
        class Foo
          include React::Component
          def self.call_history
            @call_history ||= []
          end
          def render
            React.create_element('div') { 'lorem' }
          end
        end
      end
    end

    it 'invokes `before_mount` registered methods when `componentWillMount()`' do
      mount 'Foo' do
        Foo.class_eval do
          before_mount :bar, :bar2
          def bar; self.class.call_history << "bar"; end
          def bar2; self.class.call_history << "bar2"; end
        end
      end
      expect_evaluate_ruby("Foo.call_history").to eq(["bar", "bar2"])
    end

    it 'invokes `after_mount` registered methods when `componentDidMount()`' do
      mount 'Foo' do
        Foo.class_eval do
          after_mount :bar3, :bar4
          def bar3; self.class.call_history << "bar3"; end
          def bar4; self.class.call_history << "bar4"; end
        end
      end
      expect_evaluate_ruby("Foo.call_history").to eq(["bar3", "bar4"])
    end

    it 'allows multiple class declared life cycle hooker' do
      evaluate_ruby do
        Foo.class_eval do
          before_mount :bar
          def bar; self.class.call_history << "bar"; end
        end

        class FooBar
          include React::Component
          after_mount :bar2
          def self.call_history
            @call_history ||= []
          end
          def bar2; self.class.call_history << "bar2"; end
          def render
            React.create_element('div') { 'lorem' }
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        instance = React::Test::Utils.render_component_into_document(FooBar)
      end
      expect_evaluate_ruby("Foo.call_history").to eq(["bar"])
      expect_evaluate_ruby("FooBar.call_history").to eq(["bar2"])
    end

    it 'allows block for life cycle callback' do
      expect_evaluate_ruby do
        Foo.class_eval do
          before_mount do
            set_state({ foo: "bar" })
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        instance.state[:foo]
      end.to eq('bar')
    end

    it 'invokes :after_error when componentDidCatch' do
      client_option raise_on_js_errors: :off
      mount 'Foo' do
        class ErrorFoo
          include Hyperloop::Component::Mixin
          param :just
          def render
            raise 'ErrorFoo Error'
          end
        end
        Foo.class_eval do
          def self.get_error
            @@error
          end
          
          def self.get_info
            @@info
          end

          def render
            DIV { ErrorFoo(just: :a_param) }
          end

          after_error do |error, info|
            @@error = error.message
            @@info = info[:componentStack]
          end
        end
      end
      expect_evaluate_ruby('Foo.get_error').to eq('ErrorFoo Error')
      expect_evaluate_ruby('Foo.get_info').to eq("\n    in ErrorFoo\n    in div\n    in Foo\n    in React::TopLevelRailsComponent")
    end
  end

  describe 'New style setter & getter' do
    before(:each) do
      on_client do
        class Foo
          include React::Component
          def render
            div { state.foo }
          end
        end
      end
    end

    it 'implicitly will create a state variable when first written' do
      mount 'Foo' do
        Foo.class_eval do
          before_mount do
            state.foo! 'bar'
          end
        end
      end
      # this was a 'have_xpath' check, but these are totally unreliable in capybara with webdrivers
      # leading to false positives and negatives
      # this simple check for string inclusion makes this checks reliable
      expect(page.body[-35..-19]).to include("<div>bar</div>")
    end

    it 'allows kernal method names like "format" to be used as state variable names' do
      mount 'Foo' do
        Foo.class_eval do
          before_mount do
            state.format! 'yes'
            state.foo! state.format
          end
        end
      end
      expect(page.body[-35..-19]).to include("<div>yes</div>")
    end

    it 'returns an observer with the bang method and no arguments' do
      mount 'Foo' do
        Foo.class_eval do
          before_mount do
            state.foo!(state.baz!.class.name)
          end
        end
      end
      expect(page.body[-50..-19]).to include("<div>React::Observable</div>")
    end

    it 'returns the current value of a state when written' do
      mount 'Foo' do
        Foo.class_eval do
          before_mount do
            state.baz! 'bar'
            state.foo!(state.baz!('pow'))
          end
        end
      end
      expect(page.body[-35..-19]).to include("<div>bar</div>")
    end

    it 'can access an explicitly defined state`' do
      mount 'Foo' do
        Foo.class_eval do
          define_state foo: :bar
        end
      end
      expect(page.body[-35..-19]).to include("<div>bar</div>")
    end
  end

  describe 'State setter & getter' do
    before(:each) do
      on_client do
        class Foo
          include React::Component
          def render
            React.create_element('div') { 'lorem' }
          end
        end
      end
    end

    it 'defines setter using `define_state`' do
      expect_evaluate_ruby do
        Foo.class_eval do
          define_state :foo
          before_mount :set_up
          def set_up
            mutate.foo 'bar'
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        instance.state.foo
      end.to eq('bar')
    end

    it 'defines init state by passing a block to `define_state`' do
      expect_evaluate_ruby do
        element_to_render = React.create_element(Foo)
        Foo.class_eval do
          define_state(:foo) { 10 }
        end
        dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
        instance = React.render(element_to_render, dom_el)
        instance.state.foo
      end.to eq(10)
    end

    it 'defines getter using `define_state`' do
      expect_evaluate_ruby do
        Foo.class_eval do
          define_state(:foo) { 10 }
          before_mount :bump
          def bump
            mutate.foo(state.foo + 20)
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        instance.state.foo
      end.to eq(30)
    end

    it 'defines multiple state accessors by passing array to `define_state`' do
      expect_evaluate_ruby do
        Foo.class_eval do
          define_state :foo, :foo2
          before_mount :set_up
          def set_up
            mutate.foo 10
            mutate.foo2 20
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        [ instance.state.foo, instance.state.foo2 ]
      end.to eq([10, 20])
    end

    it 'invokes `define_state` multiple times to define states' do
      expect_evaluate_ruby do
        Foo.class_eval do
          define_state(:foo) { 30 }
          define_state(:foo2) { 40 }
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        [ instance.state.foo, instance.state.foo2 ]
      end.to eq([30, 40])
    end

    it 'can initialize multiple state variables with a block' do
      expect_evaluate_ruby do
        Foo.class_eval do
          define_state(:foo, :foo2) { 30 }
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        [ instance.state.foo, instance.state.foo2 ]
      end.to eq([30, 30])
    end

    it 'can mix multiple state variables with initializers and a block' do
      expect_evaluate_ruby do
        Foo.class_eval do
          define_state(:x, :y, foo: 1, bar: 2) {3}
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        [ instance.state.x, instance.state.y, instance.state.foo, instance.state.bar ]
      end.to eq([3, 3, 1, 2])
    end

    it 'gets state in render method' do
      mount 'Foo' do
        Foo.class_eval do
          define_state(:foo) { 10 }
          def render
            React.create_element('div') { state.foo }
          end
        end
      end
      expect(page.body[-35..-19]).to include("<div>10</div>")
    end

    it 'supports original `setState` as `set_state` method' do
      expect_evaluate_ruby do
        Foo.class_eval do
          before_mount do
            self.set_state(foo: 'bar')
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        instance.state[:foo]
      end.to eq('bar')
    end

    it '`set_state!` method works and doesnt replace other state' do
      # this test changed because the function replaceState is gone in react
      expect_evaluate_ruby do
        Foo.class_eval do
          before_mount do
            set_state(foo: 'bar')
            set_state!(bar: 'lorem')
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        [ instance.state[:foo], instance.state[:bar] ]
      end.to eq(['bar', 'lorem'])
    end

    it 'supports original `state` method' do
      mount 'Foo' do
        Foo.class_eval do
          before_mount do
            self.set_state(foo: 'bar')
          end

          def render
            div { self.state[:foo] }
          end
        end
      end
      expect(page.body[-35..-19]).to include("<div>bar</div>")
    end

    it 'transforms state getter to Ruby object' do
      mount 'Foo' do
        Foo.class_eval do
          define_state :foo

          before_mount do
            mutate.foo [{a: "Hello"}]
          end

          def render
            div { state.foo[0][:a] }
          end
        end
      end
      expect(page.body[-40..-19]).to include("<div>Hello</div>")
    end

    it 'sets initial state with default value in constructor in @native object state property' do
      mount 'StateFoo' do
        class StateFoo
          include Hyperloop::Component::Mixin
          state bar: 25

          def initialize(native)
            super(native)
            @@initial_state = @native.JS[:state].JS[:bar]
          end

          def self.initial_state
            @@initial_state ||= 0
          end

          def render
            React.create_element('div') { 'lorem' }
          end
        end
      end
      expect_evaluate_ruby('StateFoo.initial_state').to eq(25)
    end

    it 'doesnt cause extra render when setting initial state' do
      mount 'StateFoo' do
        class StateFoo
          include Hyperloop::Component::Mixin
          state bar: 25

          def self.render_count
            @@render_count ||= 0
          end
          def self.incr_render_count
            @@render_count ||= 0
            @@render_count += 1
          end

          def render
            StateFoo.incr_render_count
            React.create_element('div') { 'lorem' }
          end
        end
      end
      expect_evaluate_ruby('StateFoo.render_count').to eq(1)
    end

    it 'doesnt cause extra render when setting state in :before_mount' do
      mount 'StateFoo' do
        class StateFoo
          include Hyperloop::Component::Mixin

          def self.render_count
            @@render_count ||= 0
          end
          def self.incr_render_count
            @@render_count ||= 0
            @@render_count += 1
          end

          before_mount do
            mutate.bar 50
          end

          def render
            StateFoo.incr_render_count
            React.create_element('div') { 'lorem' }
          end
        end
      end
      expect_evaluate_ruby('StateFoo.render_count').to eq(1)
    end


    it 'doesnt cause extra render when setting state in :before_receive_props' do
      mount 'Foo' do
        class StateFoo
          include Hyperloop::Component::Mixin

          param :drinks

          def self.render_count
            @@render_count ||= 0
          end
          def self.incr_render_count
            @@render_count ||= 0
            @@render_count += 1
          end

          before_receive_props do |new_params|
            mutate.bar 50
          end

          def render
            StateFoo.incr_render_count
            React.create_element('div') { 'lorem' }
          end
        end

        Foo.class_eval do
          define_state :foo

          before_mount do
            state.foo 25
          end

          def render
            div { StateFoo(drinks: state.foo) }
          end

          after_mount do
            mutate.foo 50
          end
        end
      end
      expect_evaluate_ruby('StateFoo.render_count').to eq(2)
    end
  end

  describe 'Props' do
    describe 'this.props could be accessed through `params` method' do
      before do
        on_client do
          class Foo
            include React::Component
          end
        end
      end

      it 'reads from parent passed properties through `params`' do
        mount 'Foo', prop: 'foobar' do
          Foo.class_eval do
            param :prop
            def render
              React.create_element('div') { params[:prop] }
            end
          end
        end
        expect(page.body[-40..-19]).to include("<div>foobar</div>")
      end

      it 'accesses nested params as orignal Ruby object' do
        mount 'Foo', prop: [{foo: 10}] do
          Foo.class_eval do
            param :prop
            def render
              React.create_element('div') { params[:prop][0][:foo] }
            end
          end
        end
        expect(page.body[-35..-19]).to include("<div>10</div>")
      end
    end

    describe 'Props Updating', v13_only: true do
      before do
        on_client do
          class Foo
            include React::Component
          end
        end
      end

      it '`setProps` as method `set_props` is no longer supported' do
        expect_evaluate_ruby do
          Foo.class_eval do
            param :foo
            def render
              React.create_element('div') { params[:foo] }
            end
          end
          instance = React::Test::Utils.render_component_into_document(Foo, foo: 10)
          begin
            instance.set_props(foo: 20)
          rescue
            'got risen'
          end
        end.to eq('got risen')
      end

      it 'original `replaceProps` as method `set_props!` is no longer supported' do
        expect_evaluate_ruby do
          Foo.class_eval do
            param :foo
            def render
              React.create_element('div') { params[:foo] ? 'exist' : 'null' }
            end
          end
          instance = React::Test::Utils.render_component_into_document(Foo, foo: 10)
          begin
            instance.set_props!(bar: 20)
          rescue
            'got risen'
          end
        end.to eq('got risen')
      end
    end

    describe 'Prop validation' do
      before do
        on_client do
          class Foo
            include Hyperloop::Component::Mixin
          end
        end
      end

      it 'specifies validation rules using `params` class method' do
        expect_evaluate_ruby do
          Foo.class_eval do
            params do
              requires :foo, type: String
              optional :bar
            end
          end
          Foo.prop_types
        end.to have_key('_componentValidator')
      end

      it 'logs error in warning if validation failed' do
        evaluate_ruby do
          class Lorem; end
          Foo.class_eval do
            params do
              requires :foo
              requires :lorem, type: Lorem
              optional :bar, type: String
            end

            def render; div; end
          end
          React::Test::Utils.render_component_into_document(Foo, bar: 10, lorem: Lorem.new)
        end
        expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
          .to match(/Warning: Failed prop( type|Type): In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` could not be converted to String/)
      end

      it 'should not log anything if validation pass' do
        evaluate_ruby do
          class Lorem; end
          Foo.class_eval do
            params do
              requires :foo
              requires :lorem, type: Lorem
              optional :bar, type: String
            end

            def render; div; end
          end
          React::Test::Utils.render_component_into_document(Foo, foo: 10, bar: '10', lorem: Lorem.new)
        end
        expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n")).to_not match(/prop/)
      end
    end

    describe 'Default props' do
      it 'sets default props using validation helper' do
        on_client do
          class Foo
            include React::Component
            params do
              optional :foo, default: 'foo'
              optional :bar, default: 'bar'
            end

            def render
              div { params[:foo] + '-' + params[:bar]}
            end
          end
        end
        mount 'Foo'
        expect(page.body[-40..-19]).to include("<div>foo-bar</div>")
        mount 'Foo', foo: 'lorem'
        expect(page.body[-40..-19]).to include("<div>lorem-bar</div>")
      end
    end
  end

  describe 'Anonymous Component' do
    it "will not generate spurious warning messages" do
      evaluate_ruby do
        foo = Class.new(React::Component::Base)
        foo.class_eval do
          def render; "hello" end
        end

        React::Test::Utils.render_component_into_document(foo)
      end
      expect(page.driver.browser.manage.logs.get(:browser)
        .reject { |entry| entry.to_s.include?("Deprecated feature") }
        .map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n").size)
        .to eq(0)
    end
  end

  describe 'Render Error Handling' do
    it "will generate a message if render returns something other than an Element or a String" do
      mount 'Foo' do
        class Foo < React::Component::Base
          def render; Hash.new; end
        end
      end
      expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
          .to match(/Instead the Hash \{\} was returned/)
    end
    it "will generate a message if render returns a Component class" do
      mount 'Foo' do
        class Foo < React::Component::Base
          def render; Foo; end
        end
      end
      expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
        .to match(/Did you mean Foo()/)
    end
    it "will generate a message if more than 1 element is generated" do
      mount 'Foo' do
        class Foo < React::Component::Base
          def render; "hello".span; "goodby".span; end
        end
      end
      expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
        .to match(/Instead 2 elements were generated/)
    end
    it "will generate a message if the element generated is not the element returned" do
      mount 'Foo' do
        class Foo < React::Component::Base
          def render; "hello".span; "goodby".span.delete; end
        end
      end
      expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
        .to match(/A different element was returned than was generated within the DSL/)
    end
  end

  describe 'Event handling' do
    before do
      on_client do
        class Foo
          include React::Component
        end
      end
    end

    it 'works in render method' do
      expect_evaluate_ruby do
        Foo.class_eval do
          define_state(:clicked) { false }

          def render
            React.create_element('div').on(:click) do
              mutate.clicked true
            end
          end
        end
        instance = React::Test::Utils.render_component_into_document(Foo)
        React::Test::Utils.simulate_click(instance)
        instance.state.clicked
      end.to eq(true)
    end

    it 'invokes handler on `this.props` using emit' do
      on_client do
        Foo.class_eval do
          param :on_foo_fubmit, type: Proc
          after_mount :setup

          def setup
            self.emit(:foo_submit, 'bar')
          end

          def render
            React.create_element('div')
          end
        end
      end
      evaluate_ruby do
        element = React.create_element(Foo).on(:foo_submit) { 'bar' }
        React::Test::Utils.render_into_document(element)
      end
      expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
        .to_not match(/Exception raised/)
    end

    it 'invokes handler with multiple params using emit' do
      on_client do
        Foo.class_eval do
          param :on_foo_invoked, type: Proc
          after_mount :setup

          def setup
            self.emit(:foo_invoked, [1,2,3], 'bar')
          end

          def render
            React.create_element('div')
          end
        end
      end

      evaluate_ruby do
        element = React.create_element(Foo).on(:foo_invoked) { return [1,2,3], 'bar' }
        React::Test::Utils.render_into_document(element)
      end
      expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
        .to_not match(/Exception raised/)
    end
  end

  describe '#render' do
    it 'supports element building helpers' do
      on_client do
        class Foo
          include React::Component
          param :foo
          def render
            div do
              span { params[:foo] }
            end
          end
        end

        class Bar
          include React::Component
          def render
            div do
              React::RenderingContext.render(Foo, foo: 'astring')
            end
          end
        end
      end
      evaluate_ruby do
        React::Test::Utils.render_component_into_document(Bar)
      end
      expect(page.body[-80..-19]).to include("<div><div><span>astring</span></div></div>")
    end

    it 'builds single node in top-level render without providing a block' do
      mount 'Foo' do
        class Foo
          include React::Component

          def render
            div
          end
        end
      end
      expect(page.body).to include('<div data-react-class="React.TopLevelRailsComponent" data-react-props="{&quot;render_params&quot;:{},&quot;component_name&quot;:&quot;Foo&quot;,&quot;controller&quot;:&quot;ReactTest&quot;}"><div></div></div>')
    end

    it 'redefines `p` to make method missing work' do
      mount 'Foo' do
        class Foo
          include React::Component

          def render
            div {
              p(class_name: 'foo')
              p
              div { 'lorem ipsum' }
              p(id: '10')
            }
          end
        end
      end
      expect(page.body).to include('<div><p class="foo"></p><p></p><div>lorem ipsum</div><p id="10"></p></div>')
    end

    it 'only overrides `p` in render context' do
      mount 'Foo' do
        
        class Foo
          include React::Component

          def self.result
            @@result ||= 'ooopsy'
          end

          def self.result_two
            @@result_two ||= 'ooopsy'
          end

          before_mount do
            @@result = p 'first'
          end

          after_mount do
            @@result_two = p 'second'
          end

          def render
            p do
              'third'
            end
          end
        end
      end
      expect_evaluate_ruby('Kernel.p "first"').to eq('first')
      expect_evaluate_ruby('p "second"').to eq('second')
      expect_evaluate_ruby('Foo.result').to eq('first')
      expect_evaluate_ruby('Foo.result_two').to eq('second')
      expect(page.body[-40..-10]).to include("<p>third</p>")
      expect(page.body[-40..-10]).not_to include("<p>first</p>")
    end
  end

  describe 'new react 15/16 custom isMounted implementation' do
    it 'returns true if after mounted' do
      expect_evaluate_ruby do
        class Foo
          include React::Component

          def render
            React.create_element('div')
          end
        end

        component = React::Test::Utils.render_component_into_document(Foo)
        component.mounted?
      end.to eq(true)
    end
  end

  describe '.params_changed?' do

    before(:each) do
      on_client do
        class Foo < React::Component::Base
          def needs_update?(next_params, next_state)
            next_params.changed?
          end
        end
      end
    end

    it "returns false if new and old params are the same" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        @foo.instance_eval { @native.JS[:props] = JS.call(:eval, 'function bla(){return {value1: 1, value2: 2};}bla();') }
        @foo.should_component_update?({ value2: 2, value1: 1 }, {})
      end.to be_falsy
    end

    it "returns true if new and old params are have different values" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        @foo.instance_eval { @native.JS[:props] = JS.call(:eval, 'function bla(){return {value1: 1, value2: 2};}bla();') }
        @foo.should_component_update?({value2: 2, value1: 2}, {})
      end.to be_truthy
    end

    it "returns true if new and old params are have different keys" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        @foo.instance_eval { @native.JS[:props] = JS.call(:eval, 'function bla(){return {value1: 1, value2: 2};}bla();') }
        @foo.should_component_update?({value2: 2, value1: 1, value3: 3}, {})
      end.to be_truthy
    end
  end

  describe '#should_component_update?' do

    before(:each) do
      on_client do
        class Foo < React::Component::Base
          def needs_update?(next_params, next_state)
            next_state.changed?
          end
        end

        EMPTIES = [`{}`, `undefined`, `null`, `false`]
      end
    end

    it "returns false if both new and old states are empty" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        return_values = []
        EMPTIES.each do |empty1|
          EMPTIES.each do |empty2|
            @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return #{empty1};}bla();") }
            return_values << @foo.should_component_update?({}, Hash.new(empty2))
          end
        end
        return_values
      end.to all( be_falsy )
    end

    it "returns true if old state is empty, but new state is not" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        return_values = []
        EMPTIES.each do |empty|
          @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return #{empty};}bla();") }
          return_values << @foo.should_component_update?({}, {foo: 12})
        end
        return_values
      end.to all( be_truthy )
    end

    it "returns true if new state is empty, but old state is not" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        return_values = []
        EMPTIES.each do |empty|
          @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {foo: 12};}bla();") }
          return_values << @foo.should_component_update?({}, Hash.new(empty))
        end
        return_values
      end.to all( be_truthy )
    end

    it "returns true if new state and old state have different time stamps" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        return_values = []
        EMPTIES.each do |empty|
          @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'***_state_updated_at-***': 12};}bla();") }
          return_values << @foo.should_component_update?({}, {'***_state_updated_at-***' => 13})
        end
        return_values
      end.to all ( be_truthy )
    end

    it "returns false if new state and old state have the same time stamps" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        return_values = []
        EMPTIES.each do |empty|
          @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'***_state_updated_at-***': 12};}bla();") }
          return_values << @foo.should_component_update?({}, {'***_state_updated_at-***' => 12})
        end
        return_values
      end.to all( be_falsy )
    end

    it "returns true if new state without timestamp is different from old state" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        return_values = []
        EMPTIES.each do |empty|
          @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'my_state': 12};}bla();") }
          return_values << @foo.should_component_update?({}, {'my-state' => 13})
        end
        return_values
      end.to all ( be_truthy )
    end

    it "returns false if new state without timestamp is the same as old state" do
      expect_evaluate_ruby do
        @foo = Foo.new(nil)
        return_values = []
        EMPTIES.each do |empty|
          @foo.instance_eval { @native.JS[:state] = JS.call(:eval, "function bla(){return {'my_state': 12};}bla();") }
          return_values << @foo.should_component_update?({}, {'my_state' => 12})
        end
        return_values
      end.to all( be_falsy )
    end
  end

  describe '#children' do
    before(:each) do
      on_client do
        class Foo
          include React::Component
          def render
            React.create_element('div') { 'lorem' }
          end
        end
      end
    end

    it 'returns React::Children collection with child elements' do
      evaluate_ruby do
        ele = React.create_element(Foo) {
          [React.create_element('a'), React.create_element('li')]
        }
        instance = React::Test::Utils.render_into_document(ele)

        CHILDREN = instance.children
      end
      expect_evaluate_ruby("CHILDREN.class.name").to eq('React::Children')
      expect_evaluate_ruby("CHILDREN.count").to eq(2)
      expect_evaluate_ruby("CHILDREN.map(&:element_type)").to eq(['a', 'li'])
    end

    it 'returns an empty Enumerator if there are no children' do
      evaluate_ruby do
        ele = React.create_element(Foo)
        instance = React::Test::Utils.render_into_document(ele)
        NODES = instance.children.each
      end
      expect_evaluate_ruby("NODES.size").to eq(0)
      expect_evaluate_ruby("NODES.count").to eq(0)
    end
  end
end