hyper-react 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +27 -0
- data/.gitignore +36 -0
- data/.rubocop.yml +1159 -0
- data/.travis.yml +29 -0
- data/Appraisals +20 -0
- data/CHANGELOG.md +93 -0
- data/Gemfile +6 -0
- data/LICENSE +19 -0
- data/README.md +121 -0
- data/Rakefile +33 -0
- data/UPGRADING.md +24 -0
- data/component-name-lookup.md +145 -0
- data/config.ru +25 -0
- data/gemfiles/opal_0.8_react_13.gemfile +13 -0
- data/gemfiles/opal_0.8_react_14.gemfile +13 -0
- data/gemfiles/opal_0.8_react_15.gemfile +13 -0
- data/gemfiles/opal_0.9_react_13.gemfile +13 -0
- data/gemfiles/opal_0.9_react_14.gemfile +13 -0
- data/gemfiles/opal_0.9_react_15.gemfile +13 -0
- data/hyper-react.gemspec +43 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb +4 -0
- data/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb +2 -0
- data/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb +6 -0
- data/lib/generators/reactive_ruby/test_app/templates/script/rails +5 -0
- data/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb +13 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb +11 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb +14 -0
- data/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb +0 -0
- data/lib/generators/reactive_ruby/test_app/test_app_generator.rb +109 -0
- data/lib/hyper-react.rb +52 -0
- data/lib/rails-helpers/top_level_rails_component.rb +54 -0
- data/lib/react-sources/react-server.js +2 -0
- data/lib/react/api.rb +162 -0
- data/lib/react/callbacks.rb +42 -0
- data/lib/react/children.rb +30 -0
- data/lib/react/component.rb +139 -0
- data/lib/react/component/api.rb +50 -0
- data/lib/react/component/base.rb +9 -0
- data/lib/react/component/class_methods.rb +214 -0
- data/lib/react/component/dsl_instance_methods.rb +27 -0
- data/lib/react/component/params.rb +6 -0
- data/lib/react/component/props_wrapper.rb +83 -0
- data/lib/react/component/should_component_update.rb +98 -0
- data/lib/react/component/tags.rb +144 -0
- data/lib/react/element.rb +168 -0
- data/lib/react/event.rb +76 -0
- data/lib/react/ext/hash.rb +9 -0
- data/lib/react/ext/string.rb +8 -0
- data/lib/react/hash.rb +13 -0
- data/lib/react/native_library.rb +92 -0
- data/lib/react/object.rb +15 -0
- data/lib/react/observable.rb +29 -0
- data/lib/react/react-source.rb +9 -0
- data/lib/react/rendering_context.rb +142 -0
- data/lib/react/state.rb +190 -0
- data/lib/react/test.rb +16 -0
- data/lib/react/test/dsl.rb +17 -0
- data/lib/react/test/matchers/render_html_matcher.rb +49 -0
- data/lib/react/test/rspec.rb +15 -0
- data/lib/react/test/session.rb +46 -0
- data/lib/react/top_level.rb +132 -0
- data/lib/react/validator.rb +136 -0
- data/lib/reactive-ruby/component_loader.rb +49 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +197 -0
- data/lib/reactive-ruby/rails.rb +7 -0
- data/lib/reactive-ruby/rails/component_mount.rb +46 -0
- data/lib/reactive-ruby/rails/controller_helper.rb +15 -0
- data/lib/reactive-ruby/rails/railtie.rb +14 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/server_rendering/contextual_renderer.rb +42 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/lib/reactrb/auto-import.rb +32 -0
- data/lib/reactrb/deep-compare.rb +24 -0
- data/lib/reactrb/new-event-name-convention.rb +11 -0
- data/lib/sources/react-latest.js +21169 -0
- data/lib/sources/react-v13.js +21645 -0
- data/lib/sources/react-v14.js +20821 -0
- data/lib/sources/react-v15.js +21170 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/path_release_steps.md +9 -0
- data/spec/controller_helper_spec.rb +34 -0
- data/spec/index.html.erb +10 -0
- data/spec/react/callbacks_spec.rb +106 -0
- data/spec/react/children_spec.rb +76 -0
- data/spec/react/component/base_spec.rb +32 -0
- data/spec/react/component_spec.rb +872 -0
- data/spec/react/dsl_spec.rb +296 -0
- data/spec/react/element_spec.rb +136 -0
- data/spec/react/event_spec.rb +24 -0
- data/spec/react/native_library_spec.rb +344 -0
- data/spec/react/observable_spec.rb +7 -0
- data/spec/react/opal_jquery_extensions_spec.rb +66 -0
- data/spec/react/param_declaration_spec.rb +258 -0
- data/spec/react/react_spec.rb +209 -0
- data/spec/react/state_spec.rb +55 -0
- data/spec/react/test/dsl_spec.rb +43 -0
- data/spec/react/test/matchers/render_html_matcher_spec.rb +83 -0
- data/spec/react/test/rspec_spec.rb +62 -0
- data/spec/react/test/session_spec.rb +100 -0
- data/spec/react/test/utils_spec.rb +45 -0
- data/spec/react/top_level_component_spec.rb +96 -0
- data/spec/react/tutorial/tutorial_spec.rb +36 -0
- data/spec/react/validator_spec.rb +124 -0
- data/spec/reactive-ruby/component_loader_spec.rb +71 -0
- data/spec/reactive-ruby/isomorphic_helpers_spec.rb +155 -0
- data/spec/reactive-ruby/rails/asset_pipeline_spec.rb +10 -0
- data/spec/reactive-ruby/rails/component_mount_spec.rb +66 -0
- data/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb +35 -0
- data/spec/spec_helper.rb +115 -0
- data/spec/support/react/spec_helpers.rb +64 -0
- data/spec/vendor/es5-shim.min.js +6 -0
- data/spec/vendor/jquery-2.2.4.min.js +4 -0
- metadata +387 -0
data/logo1.png
ADDED
|
Binary file
|
data/logo2.png
ADDED
|
Binary file
|
data/logo3.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
For example assuming you are releasing fix to 0.8.18
|
|
3
|
+
|
|
4
|
+
1. Checkout 0-8-stable
|
|
5
|
+
2. Update tests, fix the bug and commit the changes.
|
|
6
|
+
3. Build & Release to RubyGems (Remember the version in version.rb should already be 0.8.19)
|
|
7
|
+
4. Create a tag 'v0.8.19' pointing to that commit.
|
|
8
|
+
5. Bump the version in 0-8-stable to 0.8.20 so it will be ready for the next patch level release.
|
|
9
|
+
6. Commit the version bump, and do a `git push --tags` so the new tag goes up
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if ruby?
|
|
4
|
+
class TestController < ActionController::Base; end
|
|
5
|
+
|
|
6
|
+
RSpec.describe TestController, type: :controller do
|
|
7
|
+
render_views
|
|
8
|
+
|
|
9
|
+
describe '#render_component' do
|
|
10
|
+
controller do
|
|
11
|
+
|
|
12
|
+
layout "test_layout"
|
|
13
|
+
|
|
14
|
+
def index
|
|
15
|
+
render_component
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def new
|
|
19
|
+
render_component "Index", {}, layout: :explicit_layout
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'renders with the default layout' do
|
|
24
|
+
get :index, no_prerender: true
|
|
25
|
+
expect(response).to render_template(layout: :test_layout)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "renders with a specified layout" do
|
|
29
|
+
get :new, no_prerender: true
|
|
30
|
+
expect(response).to render_template(layout: :explicit_layout)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/spec/index.html.erb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if opal?
|
|
4
|
+
describe React::Callbacks do
|
|
5
|
+
it 'defines callback' do
|
|
6
|
+
stub_const 'Foo', Class.new
|
|
7
|
+
Foo.class_eval do
|
|
8
|
+
include React::Callbacks
|
|
9
|
+
define_callback :before_dinner
|
|
10
|
+
before_dinner :wash_hand
|
|
11
|
+
|
|
12
|
+
def wash_hand
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
expect_any_instance_of(Foo).to receive(:wash_hand)
|
|
17
|
+
Foo.new.run_callback(:before_dinner)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'defines multiple callbacks' do
|
|
21
|
+
stub_const 'Foo', Class.new
|
|
22
|
+
Foo.class_eval do
|
|
23
|
+
include React::Callbacks
|
|
24
|
+
define_callback :before_dinner
|
|
25
|
+
|
|
26
|
+
before_dinner :wash_hand, :turn_of_laptop
|
|
27
|
+
|
|
28
|
+
def wash_hand;end
|
|
29
|
+
|
|
30
|
+
def turn_of_laptop;end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
expect_any_instance_of(Foo).to receive(:wash_hand)
|
|
34
|
+
expect_any_instance_of(Foo).to receive(:turn_of_laptop)
|
|
35
|
+
Foo.new.run_callback(:before_dinner)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'defines block callback' do
|
|
39
|
+
stub_const 'Foo', Class.new
|
|
40
|
+
Foo.class_eval do
|
|
41
|
+
include React::Callbacks
|
|
42
|
+
attr_accessor :a
|
|
43
|
+
attr_accessor :b
|
|
44
|
+
|
|
45
|
+
define_callback :before_dinner
|
|
46
|
+
|
|
47
|
+
before_dinner do
|
|
48
|
+
self.a = 10
|
|
49
|
+
end
|
|
50
|
+
before_dinner do
|
|
51
|
+
self.b = 20
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
foo = Foo.new
|
|
56
|
+
foo.run_callback(:before_dinner)
|
|
57
|
+
expect(foo.a).to eq(10)
|
|
58
|
+
expect(foo.b).to eq(20)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'defines multiple callback group' do
|
|
62
|
+
stub_const 'Foo', Class.new
|
|
63
|
+
Foo.class_eval do
|
|
64
|
+
include React::Callbacks
|
|
65
|
+
define_callback :before_dinner
|
|
66
|
+
define_callback :after_dinner
|
|
67
|
+
attr_accessor :a
|
|
68
|
+
|
|
69
|
+
before_dinner do
|
|
70
|
+
self.a = 10
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
foo = Foo.new
|
|
75
|
+
foo.run_callback(:before_dinner)
|
|
76
|
+
foo.run_callback(:after_dinner)
|
|
77
|
+
|
|
78
|
+
expect(foo.a).to eq(10)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'receives args as callback' do
|
|
82
|
+
stub_const 'Foo', Class.new
|
|
83
|
+
Foo.class_eval do
|
|
84
|
+
include React::Callbacks
|
|
85
|
+
define_callback :before_dinner
|
|
86
|
+
define_callback :after_dinner
|
|
87
|
+
|
|
88
|
+
attr_accessor :lorem
|
|
89
|
+
|
|
90
|
+
before_dinner do |a, b|
|
|
91
|
+
self.lorem = "#{a}-#{b}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
after_dinner :eat_ice_cream
|
|
95
|
+
def eat_ice_cream(a,b,c); end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
expect_any_instance_of(Foo).to receive(:eat_ice_cream).with(4,5,6)
|
|
99
|
+
|
|
100
|
+
foo = Foo.new
|
|
101
|
+
foo.run_callback(:before_dinner, 1, 2)
|
|
102
|
+
foo.run_callback(:after_dinner, 4, 5, 6)
|
|
103
|
+
expect(foo.lorem).to eq('1-2')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if opal?
|
|
4
|
+
describe React::Children do
|
|
5
|
+
let(:component) {
|
|
6
|
+
Class.new do
|
|
7
|
+
include React::Component
|
|
8
|
+
def render
|
|
9
|
+
div { 'lorem' }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
}
|
|
13
|
+
let(:childs) { [React.create_element('a'), React.create_element('li')] }
|
|
14
|
+
let(:element) { React.create_element(component) { childs } }
|
|
15
|
+
let(:children) { described_class.new(`#{element.to_n}.props.children`) }
|
|
16
|
+
|
|
17
|
+
before(:each) do
|
|
18
|
+
renderElementToDocument(element)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'is enumerable' do
|
|
22
|
+
nodes = children.map { |elem| elem.element_type }
|
|
23
|
+
expect(nodes).to eq(['a', 'li'])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'returns an Enumerator when not providing a block' do
|
|
27
|
+
nodes = children.each
|
|
28
|
+
expect(nodes).to be_a(Enumerator)
|
|
29
|
+
expect(nodes.size).to eq(2)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe '#each' do
|
|
33
|
+
it 'returns an array of elements' do
|
|
34
|
+
nodes = children.each { |elem| elem.element_type }
|
|
35
|
+
expect(nodes).to be_a(Array)
|
|
36
|
+
expect(nodes.map(&:class)).to eq(childs.map(&:class))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe '#length' do
|
|
41
|
+
it 'returns the number of child elements' do
|
|
42
|
+
expect(children.length).to eq(2)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'with single child element' do
|
|
47
|
+
let(:childs) { [React.create_element('a')] }
|
|
48
|
+
|
|
49
|
+
it 'is enumerable containing single element' do
|
|
50
|
+
nodes = children.map { |elem| elem.element_type }
|
|
51
|
+
expect(nodes).to eq(['a'])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe '#length' do
|
|
55
|
+
it 'returns the number of child elements' do
|
|
56
|
+
expect(children.length).to eq(1)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe 'with no child element' do
|
|
62
|
+
let(:element) { React.create_element(component) }
|
|
63
|
+
|
|
64
|
+
it 'is enumerable containing no elements' do
|
|
65
|
+
nodes = children.map { |elem| elem.element_type }
|
|
66
|
+
expect(nodes).to eq([])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#length' do
|
|
70
|
+
it 'returns the number of child elements' do
|
|
71
|
+
expect(children.length).to eq(0)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if opal?
|
|
4
|
+
RSpec.describe React::Component::Base, type: :component do
|
|
5
|
+
after(:each) do
|
|
6
|
+
React::API.clear_component_class_cache
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'can be inherited to create a component class' do
|
|
10
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
11
|
+
Foo.class_eval do
|
|
12
|
+
before_mount do
|
|
13
|
+
@instance_data = ["working"]
|
|
14
|
+
end
|
|
15
|
+
def render
|
|
16
|
+
@instance_data.first
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
stub_const 'Bar', Class.new(Foo)
|
|
20
|
+
Bar.class_eval do
|
|
21
|
+
before_mount do
|
|
22
|
+
@instance_data << "well"
|
|
23
|
+
end
|
|
24
|
+
def render
|
|
25
|
+
@instance_data.join(" ")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
expect(Foo).to render("<span>working</span>")
|
|
29
|
+
expect(Bar).to render("<span>working well</span>")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if opal?
|
|
4
|
+
describe React::Component, type: :component do
|
|
5
|
+
after(:each) do
|
|
6
|
+
React::API.clear_component_class_cache
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'defines component spec methods' do
|
|
10
|
+
stub_const 'Foo', Class.new
|
|
11
|
+
Foo.class_eval do
|
|
12
|
+
include React::Component
|
|
13
|
+
def render
|
|
14
|
+
React.create_element('div')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Class Methods
|
|
19
|
+
expect(Foo).to respond_to('initial_state')
|
|
20
|
+
expect(Foo).to respond_to('default_props')
|
|
21
|
+
expect(Foo).to respond_to('prop_types')
|
|
22
|
+
|
|
23
|
+
# Instance method
|
|
24
|
+
expect(Foo.new).to respond_to('component_will_mount')
|
|
25
|
+
expect(Foo.new).to respond_to('component_did_mount')
|
|
26
|
+
expect(Foo.new).to respond_to('component_will_receive_props')
|
|
27
|
+
expect(Foo.new).to respond_to('should_component_update?')
|
|
28
|
+
expect(Foo.new).to respond_to('component_will_update')
|
|
29
|
+
expect(Foo.new).to respond_to('component_did_update')
|
|
30
|
+
expect(Foo.new).to respond_to('component_will_unmount')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe 'Life Cycle' do
|
|
34
|
+
before(:each) do
|
|
35
|
+
stub_const 'Foo', Class.new
|
|
36
|
+
Foo.class_eval do
|
|
37
|
+
include React::Component
|
|
38
|
+
def render
|
|
39
|
+
React.create_element('div') { 'lorem' }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'invokes `before_mount` registered methods when `componentWillMount()`' do
|
|
45
|
+
Foo.class_eval do
|
|
46
|
+
before_mount :bar, :bar2
|
|
47
|
+
def bar; end
|
|
48
|
+
def bar2; end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
expect_any_instance_of(Foo).to receive(:bar)
|
|
52
|
+
expect_any_instance_of(Foo).to receive(:bar2)
|
|
53
|
+
|
|
54
|
+
renderToDocument(Foo)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'invokes `after_mount` registered methods when `componentDidMount()`' do
|
|
58
|
+
Foo.class_eval do
|
|
59
|
+
after_mount :bar3, :bar4
|
|
60
|
+
def bar3; end
|
|
61
|
+
def bar4; end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
expect_any_instance_of(Foo).to receive(:bar3)
|
|
65
|
+
expect_any_instance_of(Foo).to receive(:bar4)
|
|
66
|
+
|
|
67
|
+
renderToDocument(Foo)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'allows multiple class declared life cycle hooker' do
|
|
71
|
+
stub_const 'FooBar', Class.new
|
|
72
|
+
Foo.class_eval do
|
|
73
|
+
before_mount :bar
|
|
74
|
+
def bar; end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
FooBar.class_eval do
|
|
78
|
+
include React::Component
|
|
79
|
+
after_mount :bar2
|
|
80
|
+
def bar2; end
|
|
81
|
+
def render
|
|
82
|
+
React.create_element('div') { 'lorem' }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
expect_any_instance_of(Foo).to receive(:bar)
|
|
87
|
+
|
|
88
|
+
renderToDocument(Foo)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'allows block for life cycle callback' do
|
|
92
|
+
Foo.class_eval do
|
|
93
|
+
export_state(:foo)
|
|
94
|
+
|
|
95
|
+
before_mount do
|
|
96
|
+
foo! 'bar'
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
element = renderToDocument(Foo)
|
|
101
|
+
expect(Foo.foo).to be('bar')
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe 'New style setter & getter' do
|
|
106
|
+
before(:each) do
|
|
107
|
+
stub_const 'Foo', Class.new
|
|
108
|
+
Foo.class_eval do
|
|
109
|
+
include React::Component
|
|
110
|
+
def render
|
|
111
|
+
div { state.foo }
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'implicitly will create a state variable when first written' do
|
|
117
|
+
Foo.class_eval do
|
|
118
|
+
before_mount do
|
|
119
|
+
state.foo! 'bar'
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
expect(Foo).to render('<div>bar</div>')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'allows kernal method names like "format" to be used as state variable names' do
|
|
127
|
+
Foo.class_eval do
|
|
128
|
+
before_mount do
|
|
129
|
+
state.format! 'yes'
|
|
130
|
+
state.foo! state.format
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
expect(Foo).to render('<div>yes</div>')
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'returns an observer with the bang method and no arguments' do
|
|
138
|
+
Foo.class_eval do
|
|
139
|
+
before_mount do
|
|
140
|
+
state.foo!(state.baz!.class.name)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
expect(Foo).to render('<div>React::Observable</div>')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'returns the current value of a state when written' do
|
|
148
|
+
Foo.class_eval do
|
|
149
|
+
before_mount do
|
|
150
|
+
state.baz! 'bar'
|
|
151
|
+
state.foo!(state.baz!('pow'))
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
expect(Foo).to render('<div>bar</div>')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'can access an explicitly defined state`' do
|
|
159
|
+
Foo.class_eval do
|
|
160
|
+
define_state foo: :bar
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
expect(Foo).to render('<div>bar</div>')
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe 'State setter & getter' do
|
|
169
|
+
before(:each) do
|
|
170
|
+
stub_const 'Foo', Class.new
|
|
171
|
+
Foo.class_eval do
|
|
172
|
+
include React::Component
|
|
173
|
+
def render
|
|
174
|
+
React.create_element('div') { 'lorem' }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'defines setter using `define_state`' do
|
|
180
|
+
Foo.class_eval do
|
|
181
|
+
define_state :foo
|
|
182
|
+
before_mount :set_up
|
|
183
|
+
def set_up
|
|
184
|
+
self.foo = 'bar'
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
element = renderToDocument(Foo)
|
|
189
|
+
expect(element.state.foo).to be('bar')
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it 'defines init state by passing a block to `define_state`' do
|
|
193
|
+
Foo.class_eval do
|
|
194
|
+
define_state(:foo) { 10 }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
element = renderToDocument(Foo)
|
|
198
|
+
expect(element.state.foo).to be(10)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it 'defines getter using `define_state`' do
|
|
202
|
+
Foo.class_eval do
|
|
203
|
+
define_state(:foo) { 10 }
|
|
204
|
+
before_mount :bump
|
|
205
|
+
def bump
|
|
206
|
+
self.foo = self.foo + 20
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
element = renderToDocument(Foo)
|
|
211
|
+
expect(element.state.foo).to be(30)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'defines multiple state accessors by passing array to `define_state`' do
|
|
215
|
+
Foo.class_eval do
|
|
216
|
+
define_state :foo, :foo2
|
|
217
|
+
before_mount :set_up
|
|
218
|
+
def set_up
|
|
219
|
+
self.foo = 10
|
|
220
|
+
self.foo2 = 20
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
element = renderToDocument(Foo)
|
|
225
|
+
expect(element.state.foo).to be(10)
|
|
226
|
+
expect(element.state.foo2).to be(20)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'invokes `define_state` multiple times to define states' do
|
|
230
|
+
Foo.class_eval do
|
|
231
|
+
define_state(:foo) { 30 }
|
|
232
|
+
define_state(:foo2) { 40 }
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
element = renderToDocument(Foo)
|
|
236
|
+
expect(element.state.foo).to be(30)
|
|
237
|
+
expect(element.state.foo2).to be(40)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'can initialize multiple state variables with a block' do
|
|
241
|
+
Foo.class_eval do
|
|
242
|
+
define_state(:foo, :foo2) { 30 }
|
|
243
|
+
end
|
|
244
|
+
element = renderToDocument(Foo)
|
|
245
|
+
expect(element.state.foo).to be(30)
|
|
246
|
+
expect(element.state.foo2).to be(30)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'can mix multiple state variables with initializers and a block' do
|
|
250
|
+
Foo.class_eval do
|
|
251
|
+
define_state(:x, :y, foo: 1, bar: 2) {3}
|
|
252
|
+
end
|
|
253
|
+
element = renderToDocument(Foo)
|
|
254
|
+
expect(element.state.x).to be(3)
|
|
255
|
+
expect(element.state.y).to be(3)
|
|
256
|
+
expect(element.state.foo).to be(1)
|
|
257
|
+
expect(element.state.bar).to be(2)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'gets state in render method' do
|
|
261
|
+
Foo.class_eval do
|
|
262
|
+
define_state(:foo) { 10 }
|
|
263
|
+
def render
|
|
264
|
+
React.create_element('div') { self.foo }
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
element = renderToDocument(Foo)
|
|
269
|
+
expect(Element[element].text).to eq('10')
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it 'supports original `setState` as `set_state` method' do
|
|
273
|
+
Foo.class_eval do
|
|
274
|
+
before_mount do
|
|
275
|
+
self.set_state(foo: 'bar')
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
element = renderToDocument(Foo)
|
|
280
|
+
expect(element.state.foo).to be('bar')
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
it 'supports original `replaceState` as `set_state!` method' do
|
|
284
|
+
Foo.class_eval do
|
|
285
|
+
before_mount do
|
|
286
|
+
self.set_state(foo: 'bar')
|
|
287
|
+
self.set_state!(bar: 'lorem')
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
element = renderToDocument(Foo)
|
|
292
|
+
expect(element.state.foo).to be_nil
|
|
293
|
+
expect(element.state.bar).to eq('lorem')
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'supports original `state` method' do
|
|
297
|
+
Foo.class_eval do
|
|
298
|
+
before_mount do
|
|
299
|
+
self.set_state(foo: 'bar')
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def render
|
|
303
|
+
div { self.state[:foo] }
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
expect(Foo).to render('<div>bar</div>')
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'transforms state getter to Ruby object' do
|
|
311
|
+
Foo.class_eval do
|
|
312
|
+
define_state :foo
|
|
313
|
+
|
|
314
|
+
before_mount do
|
|
315
|
+
self.foo = [{a: "Hello"}]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def render
|
|
319
|
+
div { self.foo[0][:a] }
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
expect(Foo).to render('<div>Hello</div>')
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
describe 'Props' do
|
|
328
|
+
describe 'this.props could be accessed through `params` method' do
|
|
329
|
+
before do
|
|
330
|
+
stub_const 'Foo', Class.new
|
|
331
|
+
Foo.class_eval do
|
|
332
|
+
include React::Component
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
it 'reads from parent passed properties through `params`' do
|
|
337
|
+
Foo.class_eval do
|
|
338
|
+
def render
|
|
339
|
+
React.create_element('div') { params[:prop] }
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
element = renderToDocument(Foo, prop: 'foobar')
|
|
344
|
+
expect(Element[element].text).to eq('foobar')
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it 'accesses nested params as orignal Ruby object' do
|
|
348
|
+
Foo.class_eval do
|
|
349
|
+
def render
|
|
350
|
+
React.create_element('div') { params[:prop][0][:foo] }
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
element = renderToDocument(Foo, prop: [{foo: 10}])
|
|
355
|
+
expect(Element[element].text).to eq('10')
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
describe 'Props Updating', v13_only: true do
|
|
360
|
+
before do
|
|
361
|
+
stub_const 'Foo', Class.new
|
|
362
|
+
Foo.class_eval do
|
|
363
|
+
include React::Component
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
it 'supports original `setProps` as method `set_props`' do
|
|
368
|
+
Foo.class_eval do
|
|
369
|
+
def render
|
|
370
|
+
React.create_element('div') { params[:foo] }
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
element = renderToDocument(Foo, {foo: 10})
|
|
375
|
+
element.set_props(foo: 20)
|
|
376
|
+
expect(`#{element.dom_node}.innerHTML`).to eq('20')
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it 'supports original `replaceProps` as method `set_props!`' do
|
|
380
|
+
Foo.class_eval do
|
|
381
|
+
def render
|
|
382
|
+
React.create_element('div') { params[:foo] ? 'exist' : 'null' }
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
element = renderToDocument(Foo, {foo: 10})
|
|
387
|
+
element.set_props!(bar: 20)
|
|
388
|
+
expect(element.getDOMNode.innerHTML).to eq('null')
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
describe 'Prop validation' do
|
|
393
|
+
before do
|
|
394
|
+
stub_const 'Foo', Class.new
|
|
395
|
+
Foo.class_eval do
|
|
396
|
+
include React::Component
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it 'specifies validation rules using `params` class method' do
|
|
401
|
+
Foo.class_eval do
|
|
402
|
+
params do
|
|
403
|
+
requires :foo, type: String
|
|
404
|
+
optional :bar
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
expect(Foo.prop_types).to have_key(:_componentValidator)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
it 'logs error in warning if validation failed' do
|
|
412
|
+
stub_const 'Lorem', Class.new
|
|
413
|
+
Foo.class_eval do
|
|
414
|
+
params do
|
|
415
|
+
requires :foo
|
|
416
|
+
requires :lorem, type: Lorem
|
|
417
|
+
optional :bar, type: String
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def render; div; end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
%x{
|
|
424
|
+
var log = [];
|
|
425
|
+
var org_warn_console = window.console.warn;
|
|
426
|
+
var org_error_console = window.console.error;
|
|
427
|
+
window.console.warn = window.console.error = function(str){log.push(str)}
|
|
428
|
+
}
|
|
429
|
+
renderToDocument(Foo, bar: 10, lorem: Lorem.new)
|
|
430
|
+
`window.console.warn = org_warn_console; window.console.error = org_error_console;`
|
|
431
|
+
expect(`log[0]`).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/)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
it 'should not log anything if validation pass' do
|
|
435
|
+
stub_const 'Lorem', Class.new
|
|
436
|
+
Foo.class_eval do
|
|
437
|
+
params do
|
|
438
|
+
requires :foo
|
|
439
|
+
requires :lorem, type: Lorem
|
|
440
|
+
optional :bar, type: String
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def render; div; end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
%x{
|
|
447
|
+
var log = [];
|
|
448
|
+
var org_warn_console = window.console.warn;
|
|
449
|
+
var org_error_console = window.console.error
|
|
450
|
+
window.console.warn = window.console.error = function(str){log.push(str)}
|
|
451
|
+
}
|
|
452
|
+
renderToDocument(Foo, foo: 10, bar: '10', lorem: Lorem.new)
|
|
453
|
+
`window.console.warn = org_warn_console; window.console.error = org_error_console;`
|
|
454
|
+
expect(`log`).to eq([])
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
describe 'Default props' do
|
|
459
|
+
it 'sets default props using validation helper' do
|
|
460
|
+
stub_const 'Foo', Class.new
|
|
461
|
+
Foo.class_eval do
|
|
462
|
+
include React::Component
|
|
463
|
+
params do
|
|
464
|
+
optional :foo, default: 'foo'
|
|
465
|
+
optional :bar, default: 'bar'
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def render
|
|
469
|
+
div { params[:foo] + '-' + params[:bar]}
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
expect(Foo).to render('<div>lorem-bar</div>').with_params(foo: 'lorem')
|
|
474
|
+
expect(Foo).to render('<div>foo-bar</div>')
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
describe 'Anonymous Component' do
|
|
480
|
+
it "will not generate spurious warning messages" do
|
|
481
|
+
foo = Class.new(React::Component::Base)
|
|
482
|
+
foo.class_eval do
|
|
483
|
+
def render; "hello" end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
%x{
|
|
487
|
+
var log = [];
|
|
488
|
+
var org_warn_console = window.console.warn;
|
|
489
|
+
var org_error_console = window.console.error
|
|
490
|
+
window.console.warn = window.console.error = function(str){log.push(str)}
|
|
491
|
+
}
|
|
492
|
+
renderToDocument(foo)
|
|
493
|
+
`window.console.warn = org_warn_console; window.console.error = org_error_console;`
|
|
494
|
+
expect(`log`).to eq([])
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
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
|
+
|
|
550
|
+
describe 'Event handling' do
|
|
551
|
+
before do
|
|
552
|
+
stub_const 'Foo', Class.new
|
|
553
|
+
Foo.class_eval do
|
|
554
|
+
include React::Component
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
it 'works in render method' do
|
|
559
|
+
Foo.class_eval do
|
|
560
|
+
define_state(:clicked) { false }
|
|
561
|
+
|
|
562
|
+
def render
|
|
563
|
+
React.create_element('div').on(:click) do
|
|
564
|
+
self.clicked = true
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
element = React.create_element(Foo)
|
|
570
|
+
instance = renderElementToDocument(element)
|
|
571
|
+
simulateEvent(:click, instance)
|
|
572
|
+
expect(instance.state.clicked).to eq(true)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
it 'invokes handler on `this.props` using emit' do
|
|
576
|
+
Foo.class_eval do
|
|
577
|
+
after_mount :setup
|
|
578
|
+
|
|
579
|
+
def setup
|
|
580
|
+
self.emit(:foo_submit, 'bar')
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def render
|
|
584
|
+
React.create_element('div')
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
expect { |b|
|
|
589
|
+
element = React.create_element(Foo).on(:foo_submit, &b)
|
|
590
|
+
renderElementToDocument(element)
|
|
591
|
+
}.to yield_with_args('bar')
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
it 'invokes handler with multiple params using emit' do
|
|
595
|
+
Foo.class_eval do
|
|
596
|
+
after_mount :setup
|
|
597
|
+
|
|
598
|
+
def setup
|
|
599
|
+
self.emit(:foo_invoked, [1,2,3], 'bar')
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def render
|
|
603
|
+
React.create_element('div')
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
expect { |b|
|
|
608
|
+
element = React.create_element(Foo).on(:foo_invoked, &b)
|
|
609
|
+
renderElementToDocument(element)
|
|
610
|
+
}.to yield_with_args([1,2,3], 'bar')
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
describe '#refs' do
|
|
615
|
+
before do
|
|
616
|
+
stub_const 'Foo', Class.new
|
|
617
|
+
Foo.class_eval do
|
|
618
|
+
include React::Component
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
it 'correctly assigns refs' do
|
|
623
|
+
Foo.class_eval do
|
|
624
|
+
def render
|
|
625
|
+
React.create_element('input', type: :text, ref: :field)
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
element = renderToDocument(Foo)
|
|
630
|
+
expect(element.refs.field).not_to be_nil
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
it 'accesses refs through `refs` method' do
|
|
634
|
+
Foo.class_eval do
|
|
635
|
+
def render
|
|
636
|
+
React.create_element('input', type: :text, ref: :field).on(:click) do
|
|
637
|
+
refs[:field].value = 'some_stuff'
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
element = renderToDocument(Foo)
|
|
643
|
+
simulateEvent(:click, element)
|
|
644
|
+
|
|
645
|
+
expect(element.refs.field.value).to eq('some_stuff')
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
it "allows access the actual DOM node" do
|
|
649
|
+
Foo.class_eval do
|
|
650
|
+
after_mount do
|
|
651
|
+
dom = refs[:my_div].dom_node
|
|
652
|
+
`dom.innerHTML = 'Modified'`
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
def render
|
|
656
|
+
React.create_element('div', ref: :my_div) { "Original Content" }
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
instance = renderToDocument(Foo)
|
|
661
|
+
expect(`#{instance.dom_node}.innerHTML`).to eq('Modified')
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
describe '#render' do
|
|
666
|
+
it 'supports element building helpers' do
|
|
667
|
+
stub_const 'Foo', Class.new
|
|
668
|
+
Foo.class_eval do
|
|
669
|
+
include React::Component
|
|
670
|
+
|
|
671
|
+
def render
|
|
672
|
+
div do
|
|
673
|
+
span { params[:foo] }
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
stub_const 'Bar', Class.new
|
|
679
|
+
Bar.class_eval do
|
|
680
|
+
include React::Component
|
|
681
|
+
def render
|
|
682
|
+
div do
|
|
683
|
+
present Foo, foo: 'astring'
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
expect(Bar).to render('<div><div><span>astring</span></div></div>')
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
it 'builds single node in top-level render without providing a block' do
|
|
692
|
+
stub_const 'Foo', Class.new
|
|
693
|
+
Foo.class_eval do
|
|
694
|
+
include React::Component
|
|
695
|
+
|
|
696
|
+
def render
|
|
697
|
+
div
|
|
698
|
+
end
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
expect(Foo).to render('<div></div>')
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
it 'redefines `p` to make method missing work' do
|
|
705
|
+
stub_const 'Foo', Class.new
|
|
706
|
+
Foo.class_eval do
|
|
707
|
+
include React::Component
|
|
708
|
+
|
|
709
|
+
def render
|
|
710
|
+
p(class_name: 'foo') do
|
|
711
|
+
p
|
|
712
|
+
div { 'lorem ipsum' }
|
|
713
|
+
p(id: '10')
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
markup = '<p class="foo"><p></p><div>lorem ipsum</div><p id="10"></p></p>'
|
|
719
|
+
expect(Foo).to render(markup)
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
it 'only overrides `p` in render context' do
|
|
723
|
+
stub_const 'Foo', Class.new
|
|
724
|
+
Foo.class_eval do
|
|
725
|
+
include React::Component
|
|
726
|
+
|
|
727
|
+
before_mount do
|
|
728
|
+
p 'first'
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
after_mount do
|
|
732
|
+
p 'second'
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
def render
|
|
736
|
+
div
|
|
737
|
+
end
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
expect(Kernel).to receive(:p).with('first')
|
|
741
|
+
expect(Kernel).to receive(:p).with('second')
|
|
742
|
+
renderToDocument(Foo)
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
describe 'isMounted()' do
|
|
747
|
+
it 'returns true if after mounted' do
|
|
748
|
+
stub_const 'Foo', Class.new
|
|
749
|
+
Foo.class_eval do
|
|
750
|
+
include React::Component
|
|
751
|
+
|
|
752
|
+
def render
|
|
753
|
+
React.create_element('div')
|
|
754
|
+
end
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
component = renderToDocument(Foo)
|
|
758
|
+
expect(component.mounted?).to eq(true)
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
describe '.params_changed?' do
|
|
763
|
+
|
|
764
|
+
before(:each) do
|
|
765
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
766
|
+
Foo.define_method :needs_update? do |next_params, next_state|
|
|
767
|
+
next_params.changed?
|
|
768
|
+
end
|
|
769
|
+
@foo = Foo.new
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
it "returns false if new and old params are the same" do
|
|
773
|
+
@foo.instance_variable_set("@native", `{props: {value1: 1, value2: 2}}`)
|
|
774
|
+
expect(@foo.should_component_update?(`{value2: 2, value1: 1}`, `null`)).to be_falsy
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
it "returns true if new and old params are have different values" do
|
|
778
|
+
@foo.instance_variable_set("@native", `{props: {value1: 1, value2: 2}}`)
|
|
779
|
+
expect(@foo.should_component_update?(`{value2: 2, value1: 2}`, `null`)).to be_truthy
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
it "returns true if new and old params are have different keys" do
|
|
783
|
+
@foo.instance_variable_set("@native", `{props: {value1: 1, value2: 2}}`)
|
|
784
|
+
expect(@foo.should_component_update?(`{value2: 2, value1: 1, value3: 3}`, `null`)).to be_truthy
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
describe '#state_changed?' do
|
|
789
|
+
|
|
790
|
+
empties = [`{}`, `undefined`, `null`, `false`]
|
|
791
|
+
|
|
792
|
+
before(:each) do
|
|
793
|
+
stub_const 'Foo', Class.new(React::Component::Base)
|
|
794
|
+
Foo.define_method :needs_update? do |next_params, next_state|
|
|
795
|
+
next_state.changed?
|
|
796
|
+
end
|
|
797
|
+
@foo = Foo.new
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
it "returns false if both new and old states are empty" do
|
|
801
|
+
empties.each do |empty1|
|
|
802
|
+
empties.each do |empty2|
|
|
803
|
+
@foo.instance_variable_set("@native", `{state: #{empty1}}`)
|
|
804
|
+
expect(@foo.should_component_update?(`{}`, empty2)).to be_falsy
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
it "returns true if old state is empty, but new state is not" do
|
|
810
|
+
empties.each do |empty|
|
|
811
|
+
@foo.instance_variable_set("@native", `{state: #{empty}}`)
|
|
812
|
+
expect(@foo.should_component_update?(`{}`, `{foo: 12}`)).to be_truthy
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
it "returns true if new state is empty, but old state is not" do
|
|
817
|
+
empties.each do |empty|
|
|
818
|
+
@foo.instance_variable_set("@native", `{state: {foo: 12}}`)
|
|
819
|
+
expect(@foo.should_component_update?(`{}`, empty)).to be_truthy
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
it "returns true if new state and old state have different time stamps" do
|
|
824
|
+
@foo.instance_variable_set("@native", `{state: {'***_state_updated_at-***': 12}}`)
|
|
825
|
+
expect(@foo.should_component_update?(`{}`, `{'***_state_updated_at-***': 13}`)).to be_truthy
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
it "returns false if new state and old state have the same time stamps" do
|
|
829
|
+
@foo.instance_variable_set("@native", `{state: {'***_state_updated_at-***': 12}}`)
|
|
830
|
+
expect(@foo.should_component_update?(`{}`, `{'***_state_updated_at-***': 12}`)).to be_falsy
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
end
|
|
834
|
+
|
|
835
|
+
describe '#children' do
|
|
836
|
+
before(:each) do
|
|
837
|
+
stub_const 'Foo', Class.new
|
|
838
|
+
Foo.class_eval do
|
|
839
|
+
include React::Component
|
|
840
|
+
export_state :the_children
|
|
841
|
+
before_mount do
|
|
842
|
+
the_children! children
|
|
843
|
+
end
|
|
844
|
+
def render
|
|
845
|
+
React.create_element('div') { 'lorem' }
|
|
846
|
+
end
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
it 'returns React::Children collection with child elements' do
|
|
851
|
+
ele = React.create_element(Foo) {
|
|
852
|
+
[React.create_element('a'), React.create_element('li')]
|
|
853
|
+
}
|
|
854
|
+
renderElementToDocument(ele)
|
|
855
|
+
|
|
856
|
+
children = Foo.the_children
|
|
857
|
+
|
|
858
|
+
expect(children).to be_a(React::Children)
|
|
859
|
+
expect(children.count).to eq(2)
|
|
860
|
+
expect(children.map(&:element_type)).to eq(['a', 'li'])
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
it 'returns an empty Enumerator if there are no children' do
|
|
864
|
+
ele = React.create_element(Foo)
|
|
865
|
+
renderElementToDocument(ele)
|
|
866
|
+
nodes = Foo.the_children.each
|
|
867
|
+
expect(nodes.size).to eq(0)
|
|
868
|
+
expect(nodes.count).to eq(0)
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
end
|