require 'spec_helper'
if opal?
describe React::Component do
after(:each) do
React::API.clear_component_class_cache
end
it 'defines 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 'invokes `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 'invokes `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 'allows 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 'allows block for life cycle callback' do
Foo.class_eval do
export_state(:foo)
before_mount do
foo! 'bar'
end
end
element = renderToDocument(Foo)
expect(Foo.foo).to be('bar')
end
end
describe 'New style setter & getter' do
before(:each) do
stub_const 'Foo', Class.new
Foo.class_eval do
include React::Component
def render
div { state.foo }
end
end
end
it 'implicitly will create a state variable when first written' do
Foo.class_eval do
before_mount do
state.foo! 'bar'
end
end
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('
bar
')
end
it 'allows kernal method names like "format" to be used as state variable names' do
Foo.class_eval do
before_mount do
state.format! 'yes'
state.foo! state.format
end
end
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('yes
')
end
it 'returns an observer with the bang method and no arguments' do
Foo.class_eval do
before_mount do
state.foo!(state.baz!.class.name)
end
end
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('React::Observable
')
end
it 'returns the current value of a state when written' do
Foo.class_eval do
before_mount do
state.baz! 'bar'
state.foo!(state.baz!('pow'))
end
end
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('bar
')
end
it 'can access an explicitly defined state`' do
Foo.class_eval do
define_state foo: :bar
end
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('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 'defines 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 'defines 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 'defines 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 'defines multiple state accessors by passing 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 'invokes `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 'can initialize multiple state variables with a block' do
Foo.class_eval do
define_state(:foo, :foo2) { 30 }
end
element = renderToDocument(Foo)
expect(element.state.foo).to be(30)
expect(element.state.foo2).to be(30)
end
it 'can mix multiple state variables with initializers and a block' do
Foo.class_eval do
define_state(:x, :y, foo: 1, bar: 2) {3}
end
element = renderToDocument(Foo)
expect(element.state.x).to be(3)
expect(element.state.y).to be(3)
expect(element.state.foo).to be(1)
expect(element.state.bar).to be(2)
end
it 'gets 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[element].text).to eq('10')
end
it 'supports 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 'supports 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 'supports original `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 'transforms state getter to Ruby object' do
Foo.class_eval do
define_state :foo
before_mount do
self.foo = [{a: "Hello"}]
end
def render
div { self.foo[0][:a] }
end
end
expect(React.render_to_static_markup(React.create_element(Foo))).to eq('Hello
')
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 'reads 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[element].text).to eq('foobar')
end
it 'accesses 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[element].text).to eq('10')
end
end
# deprecated as of React 14
# describe 'Props Updating' do
# before do
# stub_const 'Foo', Class.new
# Foo.class_eval do
# include React::Component
# end
# end
#
# it 'supports 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 'supports 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.getDOMNode.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 'specifies 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 'logs 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_warn_console = window.console.warn;
var org_error_console = window.console.error;
window.console.warn = window.console.error = function(str){log.push(str)}
}
renderToDocument(Foo, bar: 10, lorem: Lorem.new)
`window.console.warn = org_warn_console; window.console.error = org_error_console;`
expect(`log`).to eq(["Warning: Failed propType: 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
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_warn_console = window.console.warn;
var org_error_console = window.console.error
window.console.warn = window.console.error = function(str){log.push(str)}
}
renderToDocument(Foo, foo: 10, bar: '10', lorem: Lorem.new)
`window.console.warn = org_warn_console; window.console.error = org_error_console;`
expect(`log`).to eq([])
end
end
describe 'Default props' do
it 'sets 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 'works 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 'invokes 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 'invokes 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 'correctly assigns 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 'accesses 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 'supports 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('')
end
it 'builds 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
end
describe 'isMounted()' do
it 'returns 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
describe '#children' do
before(:each) do
stub_const 'Foo', Class.new
Foo.class_eval do
include React::Component
export_state :the_children
before_mount do
the_children! children
end
def render
React.create_element('div') { 'lorem' }
end
end
end
it 'returns an Enumerable' do
ele = React.create_element(Foo) {
[React.create_element('a'), React.create_element('li')]
}
renderElementToDocument(ele)
nodes = Foo.the_children.map { |ele| ele.element_type }
expect(nodes).to eq(['a', 'li'])
end
it 'returns an Enumerator when not providing a block' do
ele = React.create_element(Foo) {
[React.create_element('a'), React.create_element('li')]
}
renderElementToDocument(ele)
nodes = Foo.the_children.each
expect(nodes).to be_a(Enumerator)
expect(nodes.size).to eq(2)
end
it 'returns an empty Enumerator if there are no children' do
ele = React.create_element(Foo)
renderElementToDocument(ele)
nodes = Foo.the_children.each
expect(nodes.size).to eq(nil)
expect(nodes.count).to eq(0)
end
end
end
end