reactive-ruby 0.7.3
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/.gitignore +30 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +19 -0
- data/README.md +303 -0
- data/config.ru +15 -0
- data/example/examples/Gemfile +7 -0
- data/example/examples/Gemfile.lock +45 -0
- data/example/examples/config.ru +44 -0
- data/example/examples/hello.js.rb +43 -0
- data/example/react-tutorial/Gemfile +7 -0
- data/example/react-tutorial/Gemfile.lock +49 -0
- data/example/react-tutorial/README.md +8 -0
- data/example/react-tutorial/_comments.json +14 -0
- data/example/react-tutorial/config.ru +63 -0
- data/example/react-tutorial/example.js.rb +290 -0
- data/example/react-tutorial/public/base.css +62 -0
- data/example/todos/Gemfile +11 -0
- data/example/todos/Gemfile.lock +84 -0
- data/example/todos/README.md +37 -0
- data/example/todos/Rakefile +8 -0
- data/example/todos/app/application.rb +22 -0
- data/example/todos/app/components/app.react.rb +61 -0
- data/example/todos/app/components/footer.react.rb +31 -0
- data/example/todos/app/components/todo_item.react.rb +46 -0
- data/example/todos/app/components/todo_list.react.rb +25 -0
- data/example/todos/app/models/todo.rb +19 -0
- data/example/todos/config.ru +14 -0
- data/example/todos/index.html.haml +16 -0
- data/example/todos/spec/todo_spec.rb +28 -0
- data/example/todos/vendor/base.css +410 -0
- data/example/todos/vendor/bg.png +0 -0
- data/example/todos/vendor/jquery.js +4 -0
- data/lib/rails-helpers/react_component.rb +32 -0
- data/lib/reactive-ruby.rb +23 -0
- data/lib/reactive-ruby/api.rb +177 -0
- data/lib/reactive-ruby/callbacks.rb +35 -0
- data/lib/reactive-ruby/component.rb +411 -0
- data/lib/reactive-ruby/element.rb +87 -0
- data/lib/reactive-ruby/event.rb +76 -0
- data/lib/reactive-ruby/ext/hash.rb +9 -0
- data/lib/reactive-ruby/ext/string.rb +8 -0
- data/lib/reactive-ruby/isomorphic_helpers.rb +223 -0
- data/lib/reactive-ruby/observable.rb +33 -0
- data/lib/reactive-ruby/rendering_context.rb +91 -0
- data/lib/reactive-ruby/serializers.rb +15 -0
- data/lib/reactive-ruby/state.rb +90 -0
- data/lib/reactive-ruby/top_level.rb +53 -0
- data/lib/reactive-ruby/validator.rb +83 -0
- data/lib/reactive-ruby/version.rb +3 -0
- data/logo1.png +0 -0
- data/logo2.png +0 -0
- data/logo3.png +0 -0
- data/reactive-ruby.gemspec +25 -0
- data/spec/callbacks_spec.rb +107 -0
- data/spec/component_spec.rb +597 -0
- data/spec/element_spec.rb +60 -0
- data/spec/event_spec.rb +22 -0
- data/spec/react_spec.rb +209 -0
- data/spec/reactjs/index.html.erb +11 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/tutorial/tutorial_spec.rb +37 -0
- data/spec/validator_spec.rb +79 -0
- data/vendor/active_support/core_ext/array/extract_options.rb +29 -0
- data/vendor/active_support/core_ext/class/attribute.rb +127 -0
- data/vendor/active_support/core_ext/kernel/singleton_class.rb +13 -0
- data/vendor/active_support/core_ext/module/remove_method.rb +11 -0
- metadata +205 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe React::Element do
|
4
|
+
it "should bridge `type` of native React.Element attributes" do
|
5
|
+
element = React.create_element('div')
|
6
|
+
expect(element.element_type).to eq("div")
|
7
|
+
end
|
8
|
+
|
9
|
+
async "should be renderable" do
|
10
|
+
element = React.create_element('span')
|
11
|
+
div = `document.createElement("div")`
|
12
|
+
React.render(element, div) do
|
13
|
+
run_async {
|
14
|
+
expect(`div.children[0].tagName`).to eq("SPAN")
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "Event subscription" do
|
20
|
+
it "should be subscribable through `on(:event_name)` method" do
|
21
|
+
expect { |b|
|
22
|
+
element = React.create_element("div").on(:click, &b)
|
23
|
+
instance = renderElementToDocument(element)
|
24
|
+
simulateEvent(:click, instance)
|
25
|
+
}.to yield_with_args(React::Event)
|
26
|
+
|
27
|
+
expect { |b|
|
28
|
+
element = React.create_element("div").on(:key_down, &b)
|
29
|
+
instance = renderElementToDocument(element)
|
30
|
+
simulateEvent(:keyDown, instance, {key: "Enter"})
|
31
|
+
}.to yield_control
|
32
|
+
|
33
|
+
expect { |b|
|
34
|
+
element = React.create_element("form").on(:submit, &b)
|
35
|
+
instance = renderElementToDocument(element)
|
36
|
+
simulateEvent(:submit, instance, {})
|
37
|
+
}.to yield_control
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return self for `on` method" do
|
41
|
+
element = React.create_element("div")
|
42
|
+
expect(element.on(:click){}).to eq(element)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "Children" do
|
47
|
+
it "should return a Enumerable" do
|
48
|
+
ele = React.create_element('div') { [React.create_element('a'), React.create_element('li')] }
|
49
|
+
nodes = ele.children.map {|ele| ele.element_type }
|
50
|
+
expect(nodes).to eq(["a", "li"])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return a Enumerator when not providing a block" do
|
54
|
+
ele = React.create_element('div') { [React.create_element('a'), React.create_element('li')] }
|
55
|
+
nodes = ele.children.each
|
56
|
+
expect(nodes).to be_a(Enumerator)
|
57
|
+
expect(nodes.size).to eq(2)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/event_spec.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe React::Event do
|
4
|
+
it "should bridge attributes of native SyntheticEvent (see http://facebook.github.io/react/docs/events.html#syntheticevent)" do
|
5
|
+
element = React.create_element('div').on(:click) do |event|
|
6
|
+
expect(event.bubbles).to eq(`#{event.to_n}.bubbles`)
|
7
|
+
expect(event.cancelable).to eq(`#{event.to_n}.cancelable`)
|
8
|
+
expect(event.current_target).to eq(`#{event.to_n}.currentTarget`)
|
9
|
+
expect(event.default_prevented).to eq(`#{event.to_n}.defaultPrevented`)
|
10
|
+
expect(event.event_phase).to eq(`#{event.to_n}.eventPhase`)
|
11
|
+
expect(event.is_trusted?).to eq(`#{event.to_n}.isTrusted`)
|
12
|
+
expect(event.native_event).to eq(`#{event.to_n}.nativeEvent`)
|
13
|
+
expect(event.target).to eq(`#{event.to_n}.target`)
|
14
|
+
expect(event.timestamp).to eq(`#{event.to_n}.timeStamp`)
|
15
|
+
expect(event.event_type).to eq(`#{event.to_n}.type`)
|
16
|
+
expect(event).to respond_to(:prevent_default)
|
17
|
+
expect(event).to respond_to(:stop_propagation)
|
18
|
+
end
|
19
|
+
instance = renderElementToDocument(element)
|
20
|
+
simulateEvent(:click, instance)
|
21
|
+
end
|
22
|
+
end
|
data/spec/react_spec.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe React do
|
4
|
+
after(:each) do
|
5
|
+
React::API.clear_component_class_cache
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "is_valid_element" do
|
9
|
+
it "should return true if passed a valid element" do
|
10
|
+
element = React::Element.new(`React.createElement('div')`)
|
11
|
+
expect(React.is_valid_element(element)).to eq(true)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return false is passed a non React element" do
|
15
|
+
element = React::Element.new(`{}`)
|
16
|
+
expect(React.is_valid_element(element)).to eq(false)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "create_element" do
|
21
|
+
it "should create a valid element with only tag" do
|
22
|
+
element = React.create_element('div')
|
23
|
+
expect(React.is_valid_element(element)).to eq(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with block" do
|
27
|
+
it "should create a valid element with text as only child when block yield String" do
|
28
|
+
element = React.create_element('div') { "lorem ipsum" }
|
29
|
+
expect(React.is_valid_element(element)).to eq(true)
|
30
|
+
expect(element.props.children).to eq("lorem ipsum")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should create a valid element with children as array when block yield Array of element" do
|
34
|
+
element = React.create_element('div') do
|
35
|
+
[React.create_element('span'), React.create_element('span'), React.create_element('span')]
|
36
|
+
end
|
37
|
+
expect(React.is_valid_element(element)).to eq(true)
|
38
|
+
expect(element.props.children.length).to eq(3)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should render element with children as array when block yield Array of element" do
|
42
|
+
element = React.create_element('div') do
|
43
|
+
[React.create_element('span'), React.create_element('span'), React.create_element('span')]
|
44
|
+
end
|
45
|
+
instance = renderElementToDocument(element)
|
46
|
+
expect(instance.getDOMNode.childNodes.length).to eq(3)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
describe "custom element" do
|
50
|
+
before do
|
51
|
+
stub_const 'Foo', Class.new
|
52
|
+
Foo.class_eval do
|
53
|
+
def render
|
54
|
+
React.create_element("div") { "lorem" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should render element with only one children correctly" do
|
60
|
+
element = React.create_element(Foo) { React.create_element('span') }
|
61
|
+
instance = renderElementToDocument(element)
|
62
|
+
expect(instance.props.children).not_to be_a(Array)
|
63
|
+
expect(instance.props.children.type).to eq("span")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should render element with more than one children correctly" do
|
67
|
+
element = React.create_element(Foo) { [React.create_element('span'), React.create_element('span')] }
|
68
|
+
instance = renderElementToDocument(element)
|
69
|
+
expect(instance.props.children).to be_a(Array)
|
70
|
+
expect(instance.props.children.length).to eq(2)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should create a valid element provided class defined `render`" do
|
74
|
+
element = React.create_element(Foo)
|
75
|
+
expect(React.is_valid_element(element)).to eq(true)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should allow creating with properties" do
|
79
|
+
element = React.create_element(Foo, foo: "bar")
|
80
|
+
expect(element.props.foo).to eq("bar")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should raise error if provided class doesn't defined `render`" do
|
84
|
+
expect { React.create_element(Array) }.to raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should use the same instance for the same ReactComponent" do
|
88
|
+
Foo.class_eval do
|
89
|
+
attr_accessor :a
|
90
|
+
def initialize(n)
|
91
|
+
self.a = 10
|
92
|
+
end
|
93
|
+
|
94
|
+
def component_will_mount
|
95
|
+
self.a = 20
|
96
|
+
end
|
97
|
+
|
98
|
+
def render
|
99
|
+
React.create_element("div") { self.a.to_s }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
expect(React.render_to_static_markup(React.create_element(Foo))).to eq("<div>20</div>")
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should match the instance cycle to ReactComponent life cycle" do
|
107
|
+
`var count = 0;`
|
108
|
+
|
109
|
+
Foo.class_eval do
|
110
|
+
def initialize
|
111
|
+
`count = count + 1;`
|
112
|
+
end
|
113
|
+
def render
|
114
|
+
React.create_element("div")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
renderToDocument(Foo)
|
119
|
+
renderToDocument(Foo)
|
120
|
+
|
121
|
+
expect(`count`).to eq(2)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "create element with properties" do
|
126
|
+
it "should enforce snake-cased property name" do
|
127
|
+
element = React.create_element("div", class_name: "foo")
|
128
|
+
expect(element.props.className).to eq("foo")
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should allow custom property" do
|
132
|
+
element = React.create_element("div", foo: "bar")
|
133
|
+
expect(element.props.foo).to eq("bar")
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should not camel-case custom property" do
|
137
|
+
element = React.create_element("div", foo_bar: "foo")
|
138
|
+
expect(element.props.foo_bar).to eq("foo")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "class_name helpers (React.addons.classSet)" do
|
143
|
+
it "should transform Hash provided to `class_name` props as string" do
|
144
|
+
classes = {foo: true, bar: false, lorem: true}
|
145
|
+
element = React.create_element("div", class_name: classes)
|
146
|
+
|
147
|
+
expect(element.props.className).to eq("foo lorem")
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should not alter behavior when passing a string" do
|
151
|
+
element = React.create_element("div", class_name: "foo bar")
|
152
|
+
|
153
|
+
expect(element.props.className).to eq("foo bar")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
describe "render" do
|
160
|
+
async "should render element to DOM" do
|
161
|
+
div = `document.createElement("div")`
|
162
|
+
React.render(React.create_element('span') { "lorem" }, div) do
|
163
|
+
run_async {
|
164
|
+
expect(`div.children[0].tagName`).to eq("SPAN")
|
165
|
+
expect(`div.textContent`).to eq("lorem")
|
166
|
+
}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should work without providing a block" do
|
171
|
+
div = `document.createElement("div")`
|
172
|
+
React.render(React.create_element('span') { "lorem" }, div)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should return a React::Component::API compatible object" do
|
176
|
+
div = `document.createElement("div")`
|
177
|
+
component = React.render(React.create_element('span') { "lorem" }, div)
|
178
|
+
React::Component::API.public_instance_methods(true).each do |method_name|
|
179
|
+
expect(component).to respond_to(method_name)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
pending "should return nil to prevent abstraction leakage" do
|
184
|
+
div = `document.createElement("div")`
|
185
|
+
expect {
|
186
|
+
React.render(React.create_element('span') { "lorem" }, div)
|
187
|
+
}.to be_nil
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "render_to_string" do
|
192
|
+
it "should render a React.Element to string" do
|
193
|
+
ele = React.create_element('span') { "lorem" }
|
194
|
+
expect(React.render_to_string(ele)).to be_kind_of(String)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "unmount_component_at_node" do
|
199
|
+
async "should unmount component at node" do
|
200
|
+
div = `document.createElement("div")`
|
201
|
+
React.render(React.create_element('span') { "lorem" }, div ) do
|
202
|
+
run_async {
|
203
|
+
expect(React.unmount_component_at_node(div)).to eq(true)
|
204
|
+
}
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'react'
|
2
|
+
|
3
|
+
module ReactTestHelpers
|
4
|
+
`var ReactTestUtils = React.addons.TestUtils`
|
5
|
+
|
6
|
+
def renderToDocument(type, options = {})
|
7
|
+
element = React.create_element(type, options)
|
8
|
+
return renderElementToDocument(element)
|
9
|
+
end
|
10
|
+
|
11
|
+
def renderElementToDocument(element)
|
12
|
+
instance = Native(`ReactTestUtils.renderIntoDocument(#{element.to_n})`)
|
13
|
+
instance.class.include(React::Component::API)
|
14
|
+
return instance
|
15
|
+
end
|
16
|
+
|
17
|
+
def simulateEvent(event, element, params = {})
|
18
|
+
simulator = Native(`ReactTestUtils.Simulate`)
|
19
|
+
simulator[event.to_s].call(`#{element.to_n}.getDOMNode()`, params)
|
20
|
+
end
|
21
|
+
|
22
|
+
def isElementOfType(element, type)
|
23
|
+
`React.addons.TestUtils.isElementOfType(#{element.to_n}, #{type.cached_component_class})`
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec.configure do |config|
|
28
|
+
config.include ReactTestHelpers
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class HelloMessage
|
4
|
+
include React::Component
|
5
|
+
def render
|
6
|
+
div { "Hello World!" }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "An Example from the react.rb doc" do
|
11
|
+
|
12
|
+
it "produces the correct result" do
|
13
|
+
expect(React.render_to_static_markup(React.create_element(HelloMessage))).to eq('<div>Hello World!</div>')
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class HelloMessage2
|
19
|
+
include React::Component
|
20
|
+
define_state(:user_name) { '@catmando' }
|
21
|
+
def render
|
22
|
+
div { "Hello #{user_name}" }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "Adding state to a component (second tutorial example)" do
|
27
|
+
|
28
|
+
it "produces the correct result" do
|
29
|
+
expect(React.render_to_static_markup(React.create_element(HelloMessage2))).to eq('<div>Hello @catmando</div>')
|
30
|
+
end
|
31
|
+
|
32
|
+
it "renders to the document" do
|
33
|
+
React.render(React.create_element(HelloMessage2), `document.getElementById('render_here')`)
|
34
|
+
expect(`document.getElementById('render_here').innerHTML`) =~ 'Hello @catmando'
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe React::Validator do
|
4
|
+
describe "validate" do
|
5
|
+
describe "Presence validation" do
|
6
|
+
it "should check if required props provided" do
|
7
|
+
validator = React::Validator.build do
|
8
|
+
requires :foo
|
9
|
+
requires :bar
|
10
|
+
end
|
11
|
+
|
12
|
+
expect(validator.validate({})).to eq(["Required prop `foo` was not specified", "Required prop `bar` was not specified"])
|
13
|
+
expect(validator.validate({foo: 1, bar: 3})).to eq([])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should check if passed non specified prop" do
|
17
|
+
validator = React::Validator.build do
|
18
|
+
optional :foo
|
19
|
+
end
|
20
|
+
|
21
|
+
expect(validator.validate({bar: 10})).to eq(["Provided prop `bar` not specified in spec"])
|
22
|
+
expect(validator.validate({foo: 10})).to eq([])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "Type validation" do
|
27
|
+
it "should check if passed value with wrong type" do
|
28
|
+
validator = React::Validator.build do
|
29
|
+
requires :foo, type: String
|
30
|
+
end
|
31
|
+
|
32
|
+
expect(validator.validate({foo: 10})).to eq(["Provided prop `foo` was not the specified type `String`"])
|
33
|
+
expect(validator.validate({foo: "10"})).to eq([])
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should check if passed value with wrong custom type" do
|
37
|
+
stub_const 'Bar', Class.new
|
38
|
+
validator = React::Validator.build do
|
39
|
+
requires :foo, type: Bar
|
40
|
+
end
|
41
|
+
|
42
|
+
expect(validator.validate({foo: 10})).to eq(["Provided prop `foo` was not the specified type `Bar`"])
|
43
|
+
expect(validator.validate({foo: Bar.new})).to eq([])
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should support Array[Class] validation" do
|
47
|
+
validator = React::Validator.build do
|
48
|
+
requires :foo, type: Array[Hash]
|
49
|
+
end
|
50
|
+
|
51
|
+
expect(validator.validate({foo: [1,'2',3]})).to eq(["Provided prop `foo` was not an Array of the specified type `Hash`"])
|
52
|
+
expect(validator.validate({foo: [{},{},{}]})).to eq([])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "Limited values" do
|
57
|
+
it "should check if passed value is not one of the specified values" do
|
58
|
+
validator = React::Validator.build do
|
59
|
+
requires :foo, values: [4,5,6]
|
60
|
+
end
|
61
|
+
|
62
|
+
expect(validator.validate({foo: 3})).to eq(["Value `3` for prop `foo` is not an allowed value"])
|
63
|
+
expect(validator.validate({foo: 4})).to eq([])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "default_props" do
|
69
|
+
it "should return specified default values" do
|
70
|
+
validator = React::Validator.build do
|
71
|
+
requires :foo, default: 10
|
72
|
+
requires :bar
|
73
|
+
optional :lorem, default: 20
|
74
|
+
end
|
75
|
+
|
76
|
+
expect(validator.default_props).to eq({foo: 10, lorem: 20})
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|