diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2797df..7b827c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## react.js v0.13
+* Add `React::Testing` helpers
+
## 0.3.0
* Depends on `sprockets < 3` thus source map can still work
* Remove sprockets-es6 from dependency
diff --git a/Gemfile b/Gemfile
index d926697..08edb90 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,2 +1,4 @@
source 'https://rubygems.org'
-gemspec
\ No newline at end of file
+gem 'opal', '0.8.0.rc1'
+gem 'opal-rspec', github: 'opal/opal-rspec'
+gemspec
diff --git a/Gemfile.lock b/Gemfile.lock
index fe04ccd..d1b82e6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,11 +1,19 @@
+GIT
+ remote: git://github.com/opal/opal-rspec.git
+ revision: e2290d56ff5147e5a2b96258ef5bdc2b54a9e6af
+ specs:
+ opal-rspec (0.4.2)
+ opal (>= 0.7.0, < 0.9)
+
PATH
remote: .
specs:
react.rb (0.3.0)
- opal (~> 0.6.0)
+ opal (>= 0.7.0, < 0.9)
opal-activesupport (~> 0)
react-jsx (~> 0.8.0)
- sprockets (>= 2.2.3, < 3.0.0)
+ react-source (~> 0.13)
+ sprockets (~> 3.1)
therubyracer (~> 0)
GEM
@@ -13,19 +21,18 @@ GEM
specs:
execjs (2.5.2)
hike (1.2.3)
- json (1.8.2)
+ json (1.8.3)
libv8 (3.16.14.7)
- multi_json (1.11.0)
- opal (0.6.3)
- source_map
- sprockets
+ opal (0.8.0.rc1)
+ hike (~> 1.2)
+ sourcemap (~> 0.1.0)
+ sprockets (~> 3.1)
+ tilt (>= 1.4)
opal-activesupport (0.1.0)
opal (>= 0.5.0, < 1.0.0)
opal-jquery (0.2.0)
opal (>= 0.5.0, < 1.0.0)
- opal-rspec (0.3.0.beta3)
- opal (>= 0.6.0, < 1.0.0)
- rack (1.6.0)
+ rack (1.6.1)
rack-protection (1.5.3)
rack
rake (10.4.2)
@@ -33,31 +40,27 @@ GEM
execjs (>= 2.0.2)
json (>= 1.8.0)
react-source (>= 0.4.1)
- react-source (0.12.2)
+ react-source (0.13.3)
ref (1.0.5)
sinatra (1.4.6)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
- source_map (3.0.1)
- json
- sprockets (2.12.3)
- hike (~> 1.2)
- multi_json (~> 1.0)
+ sourcemap (0.1.1)
+ sprockets (3.2.0)
rack (~> 1.0)
- tilt (~> 1.1, != 1.3.0)
therubyracer (0.12.2)
libv8 (~> 3.16.14.0)
ref
- tilt (1.4.1)
+ tilt (2.0.1)
PLATFORMS
ruby
DEPENDENCIES
+ opal (= 0.8.0.rc1)
opal-jquery (~> 0)
- opal-rspec (~> 0.3.0.beta3)
+ opal-rspec!
rake (~> 10)
- react-source (~> 0.12.0)
react.rb!
sinatra (~> 1)
diff --git a/README.md b/README.md
index 1bb5c61..b297442 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,27 @@ React.render(React.create_element('h1'){ "Hello World!" }, `document.body`)
For integration with server (Sinatra, etc), see setup of [TodoMVC](example/todos) or the [official docs](http://opalrb.org/docs/) of Opal.
+## React.js Dependency
+
+React.js v0.13 is required to use react.rb, you can access the pre-bundled source through `Opal::React.bundled_path` directory, example below shows setup for a basic rack app.
+
+```ruby
+#config.ru
+run Opal::Server.new {|s|
+ s.append_path './'
+ s.append_path Opal::React.bundled_path
+ s.main = 'example'
+ s.debug = true
+ s.index_path = "index.html.erb"
+}
+```
+
+```erb
+
+<%= javascript_include_tag "react" %>
+<%= javascript_include_tag "example" %>
+```
+
## Usage
### A Simple Component
@@ -46,12 +67,12 @@ puts React.render_to_static_markup(React.create_element(HelloMessage))
### More complicated one
-To hook into native ReactComponent life cycle, the native `this` will be passed to the class's initializer. And all corresponding life cycle methods (`componentDidMount`, etc) will be invoked on the instance using the snake-case method name.
+To hook into native ReactComponent life cycle, the props will be passed as the first argument to the class's initializer. And all corresponding life cycle methods (`componentDidMount`, etc) will be invoked on the instance using the snake-case method name.
```ruby
class HelloMessage
- def initialize(native)
- @native = Native(native)
+ def initialize(props)
+ puts props
end
def component_will_mount
@@ -59,12 +80,13 @@ class HelloMessage
end
def render
- React.create_element("div") { "Hello #{@native[:props][:name]}!" }
+ React.create_element("div") { "Hello #{self.props[:name]}!" }
end
end
puts React.render_to_static_markup(React.create_element(HelloMessage, name: 'John'))
+# => {"name"=>"John"}
# => will_mount!
# => '
Hello John!
'
```
diff --git a/Rakefile b/Rakefile
index d4c0f54..7845cae 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,10 +3,9 @@ Bundler.require
Bundler::GemHelper.install_tasks
require 'opal/rspec/rake_task'
-require "react/source"
Opal::RSpec::RakeTask.new(:default) do |s|
- s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons"))
+ s.append_path Opal::React.bundled_path
s.append_path 'spec/vendor'
s.index_path = 'spec/reactjs/index.html.erb'
-end
\ No newline at end of file
+end
diff --git a/config.ru b/config.ru
index 1a647c1..ab9515e 100644
--- a/config.ru
+++ b/config.ru
@@ -2,7 +2,6 @@ require 'bundler'
Bundler.require
require "opal-rspec"
-require "react/source"
Opal.append_path File.expand_path('../spec', __FILE__)
@@ -10,7 +9,7 @@ run Opal::Server.new { |s|
s.main = 'opal/rspec/sprockets_runner'
s.append_path 'spec'
s.append_path 'spec/vendor'
- s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
+ s.append_path Opal::React.bundled_path
s.debug = true
s.index_path = 'spec/reactjs/index.html.erb'
}
diff --git a/examples/todos/app/application.rb b/examples/todos/app/application.rb
index 6985307..296a165 100755
--- a/examples/todos/app/application.rb
+++ b/examples/todos/app/application.rb
@@ -14,7 +14,8 @@
Vienna::Router.new.tap do |router|
router.route('/:filter') do |params|
- component.set_props(filter: params[:filter].empty? ? "all" : params[:filter])
+ element = React.create_element(TodoAppView, filter: params[:filter].empty? ? "all" : params[:filter])
+ component = React.render(element, `document.getElementById('todoapp')`)
end
end.update
diff --git a/examples/todos/app/components/app.react.rb b/examples/todos/app/components/app.react.rb
index 09fd045..baa8766 100644
--- a/examples/todos/app/components/app.react.rb
+++ b/examples/todos/app/components/app.react.rb
@@ -17,6 +17,7 @@ class TodoAppView
Todo.on(:create) { Todo.adapter.sync_models(Todo); reload_current_filter }
Todo.on(:update) { Todo.adapter.sync_models(Todo); reload_current_filter }
Todo.on(:destroy) { Todo.adapter.sync_models(Todo); reload_current_filter }
+ reload_current_filter
end
before_receive_props do |next_props|
diff --git a/lib/opal/react.rb b/lib/opal/react.rb
new file mode 100644
index 0000000..9adf9ba
--- /dev/null
+++ b/lib/opal/react.rb
@@ -0,0 +1,8 @@
+require 'opal'
+require "opal-activesupport"
+
+require "opal/react/jsx_support"
+require "opal/react/source"
+
+Opal.append_path File.expand_path('../../../opal', __FILE__).untaint
+Opal.append_path File.expand_path('../../../vendor', __FILE__).untaint
diff --git a/lib/opal/react/jsx_support.rb b/lib/opal/react/jsx_support.rb
new file mode 100644
index 0000000..51efec9
--- /dev/null
+++ b/lib/opal/react/jsx_support.rb
@@ -0,0 +1,22 @@
+require 'react/jsx'
+require 'tilt'
+require "sprockets"
+
+module Opal
+ module React
+ module JSX
+ class Template < Tilt::Template
+ self.default_mime_type = 'application/javascript'
+
+ def prepare
+ end
+
+ def evaluate(scope, locals, &block)
+ @output ||= ::React::JSX.compile(data)
+ end
+ end
+ end
+ end
+end
+
+Sprockets.register_engine '.jsx', Opal::React::JSX::Template
diff --git a/lib/opal/react/source.rb b/lib/opal/react/source.rb
new file mode 100644
index 0000000..12db113
--- /dev/null
+++ b/lib/opal/react/source.rb
@@ -0,0 +1,9 @@
+require "react/source"
+
+module Opal
+ module React
+ def self.bundled_path
+ File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
+ end
+ end
+end
diff --git a/lib/opal/react/version.rb b/lib/opal/react/version.rb
new file mode 100644
index 0000000..a95415a
--- /dev/null
+++ b/lib/opal/react/version.rb
@@ -0,0 +1,5 @@
+module Opal
+ module React
+ VERSION = "0.3.0"
+ end
+end
diff --git a/lib/react.rb b/lib/react.rb
index f99e731..cc5f398 100644
--- a/lib/react.rb
+++ b/lib/react.rb
@@ -1,18 +1,18 @@
-if RUBY_ENGINE == 'opal'
- require "react/top_level"
- require "react/component"
- require "react/element"
- require "react/event"
- require "react/version"
- require "react/api"
- require "react/validator"
-else
- require "tilt"
- require "opal"
- require "react/version"
- require "opal-activesupport"
- require "react/ext/jsx_support"
-
- Opal.append_path File.expand_path('../', __FILE__).untaint
- Opal.append_path File.expand_path('../../vendor', __FILE__).untaint
-end
+# if RUBY_ENGINE == 'opal'
+# require "react/top_level"
+# require "react/component"
+# require "react/element"
+# require "react/event"
+# require "react/version"
+# require "react/component_factory"
+# require "react/validator"
+# else
+# require "tilt"
+# require "opal"
+# require "react/version"
+# require "opal-activesupport"
+# require "react/ext/jsx_support"
+require "opal/react"
+# Opal.append_path File.expand_path('../', __FILE__).untaint
+# Opal.append_path File.expand_path('../../vendor', __FILE__).untaint
+#end
diff --git a/lib/react/api.rb b/lib/react/api.rb
deleted file mode 100644
index ffbc5d7..0000000
--- a/lib/react/api.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-module React
- class API
- @@component_classes = {}
-
- def self.create_element(type, properties = {}, &block)
- params = []
-
- # Component Spec or Nomral DOM
- if `(typeof type === 'function')`
- params << type
- elsif type.kind_of?(Class)
- raise "Provided class should define `render` method" if !(type.method_defined? :render)
- params << self.native_component_class(type)
- else
- raise "#{type} not implemented" unless HTML_TAGS.include?(type)
- params << type
- end
-
- # Passed in properties
- props = {}
- properties.map do |key, value|
- if key == "class_name" && value.is_a?(Hash)
- props[lower_camelize(key)] = value.inject([]) {|ary, (k,v)| v ? ary.push(k) : ary}.join(" ")
- else
- props[React::ATTRIBUTES.include?(lower_camelize(key)) ? lower_camelize(key) : key] = value
- end
- end
- params << props.shallow_to_n
-
- # Children Nodes
- if block_given?
- children = [yield].flatten.each do |ele|
- params << ele.to_n
- end
- end
-
- return `React.createElement.apply(null, #{params})`
- end
-
- def self.clear_component_class_cache
- @@component_classes = {}
- end
-
- def self.native_component_class(type)
- @@component_classes[type.to_s] ||= %x{
- React.createClass({
- propTypes: #{type.respond_to?(:prop_types) ? type.prop_types.to_n : `{}`},
- getDefaultProps: function(){
- return #{type.respond_to?(:default_props) ? type.default_props.to_n : `{}`};
- },
- getInitialState: function(){
- return #{type.respond_to?(:initial_state) ? type.initial_state.to_n : `{}`};
- },
- componentWillMount: function() {
- var instance = this._getOpalInstance.apply(this);
- return #{`instance`.component_will_mount if type.method_defined? :component_will_mount};
- },
- componentDidMount: function() {
- var instance = this._getOpalInstance.apply(this);
- return #{`instance`.component_did_mount if type.method_defined? :component_did_mount};
- },
- componentWillReceiveProps: function(next_props) {
- var instance = this._getOpalInstance.apply(this);
- return #{`instance`.component_will_receive_props(`next_props`) if type.method_defined? :component_will_receive_props};
- },
- shouldComponentUpdate: function(next_props, next_state) {
- var instance = this._getOpalInstance.apply(this);
- return #{`instance`.should_component_update?(`next_props`, `next_state`) if type.method_defined? :should_component_update?};
- },
- componentWillUpdate: function(next_props, next_state) {
- var instance = this._getOpalInstance.apply(this);
- return #{`instance`.component_will_update(`next_props`, `next_state`) if type.method_defined? :component_will_update};
- },
- componentDidUpdate: function(prev_props, prev_state) {
- var instance = this._getOpalInstance.apply(this);
- return #{`instance`.component_did_update(`prev_props`, `prev_state`) if type.method_defined? :component_did_update};
- },
- componentWillUnmount: function() {
- var instance = this._getOpalInstance.apply(this);
- return #{`instance`.component_will_unmount if type.method_defined? :component_will_unmount};
- },
- _getOpalInstance: function() {
- if (this.__opalInstance == undefined) {
- var instance = #{type.new(`this`)};
- } else {
- var instance = this.__opalInstance;
- }
- this.__opalInstance = instance;
- return instance;
- },
- displayName: #{type.to_s},
- render: function() {
- var instance = this._getOpalInstance.apply(this);
- return instance.$render();
- }
- })
- }
- end
-
- private
-
- def self.lower_camelize(snake_cased_word)
- words = snake_cased_word.split("_")
- result = [words.first]
- result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] })
- result.join("")
- end
- end
-end
diff --git a/lib/react/ext/jsx_support.rb b/lib/react/ext/jsx_support.rb
deleted file mode 100644
index 7734f5a..0000000
--- a/lib/react/ext/jsx_support.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'react/jsx'
-require 'tilt'
-require "sprockets"
-
-module React
- module JSX
-
- class Template < Tilt::Template
- self.default_mime_type = 'application/javascript'
-
- def prepare
- end
-
- def evaluate(scope, locals, &block)
- @output ||= React::JSX.compile(data)
- end
- end
- end
-end
-
-Sprockets.register_engine '.jsx', React::JSX::Template
diff --git a/lib/react/ext/string.rb b/lib/react/ext/string.rb
deleted file mode 100644
index 4f979c1..0000000
--- a/lib/react/ext/string.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class String
- def event_camelize
- `#{self}.replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
- var capitalize = true;
- return capitalize ? word.substr(0,1).toUpperCase()+word.substr(1) : word;
- })`
- end
-end
\ No newline at end of file
diff --git a/lib/react/version.rb b/lib/react/version.rb
deleted file mode 100644
index 50ba0cd..0000000
--- a/lib/react/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module React
- VERSION = "0.3.0"
-end
diff --git a/opal/react.rb b/opal/react.rb
new file mode 100644
index 0000000..2f30dbb
--- /dev/null
+++ b/opal/react.rb
@@ -0,0 +1,6 @@
+require "react/top_level"
+require "react/component"
+require "react/element"
+require "react/event"
+require "react/component_factory"
+require "react/validator"
diff --git a/lib/react/callbacks.rb b/opal/react/callbacks.rb
similarity index 100%
rename from lib/react/callbacks.rb
rename to opal/react/callbacks.rb
diff --git a/lib/react/component.rb b/opal/react/component.rb
similarity index 69%
rename from lib/react/component.rb
rename to opal/react/component.rb
index fc52d8a..31b692e 100644
--- a/lib/react/component.rb
+++ b/opal/react/component.rb
@@ -1,7 +1,9 @@
-require "./ext/string"
+require "react/ext/string"
require 'active_support/core_ext/class/attribute'
require 'react/callbacks'
require "react/ext/hash"
+require "react/component/api"
+require "react/element_children_handle"
module React
module Component
@@ -20,25 +22,15 @@ def self.included(base)
base.extend(ClassMethods)
end
- def initialize(native_element)
- @native = native_element
- end
-
def params
- Hash.new(`#{@native}.props`)
- end
-
- def refs
- Hash.new(`#{@native}.refs`)
- end
-
- def state
- raise "No native ReactComponent associated" unless @native
- Hash.new(`#{@native}.state`)
+ Hash.new(`#{self}.props`).inject({}) do |memo, (k,v)|
+ memo[k.underscore] = v
+ memo
+ end
end
def emit(event_name, *args)
- self.params["_on#{event_name.to_s.event_camelize}"].call(*args)
+ self.params["on_#{event_name.to_s}"].call(*args)
end
def component_will_mount
@@ -89,22 +81,39 @@ def method_missing(name, *args, &block)
if name == "_p_tag"
name = "p"
end
-
- @buffer = [] unless @buffer
+
+ unless @buffer
+ puts "first render for #{name}"
+ @buffer = []
+ @root_element = true
+ else
+ @root_element = false
+ end
+
if block
current = @buffer
+ is_root = @root_element
@buffer = []
result = block.call
element = React.create_element(name, *args) { @buffer.count == 0 ? result : @buffer }
@buffer = current
+ @root_element = is_root
else
element = React.create_element(name, *args)
end
@buffer << element
- element
+
+ if @root_element
+ element
+ else
+ React::ElementChildrenHandle.new(@buffer, @buffer.length - 1)
+ end
+ end
+
+ def to_n
+ self
end
-
module ClassMethods
def prop_types
@@ -146,12 +155,10 @@ def define_state(*states)
states.each do |name|
# getter
define_method("#{name}") do
- return unless @native
self.state[name]
end
# setter
define_method("#{name}=") do |new_state|
- return unless @native
hash = {}
hash[name] = new_state
self.set_state(hash)
@@ -161,49 +168,5 @@ def define_state(*states)
end
end
end
-
- module API
- include Native
-
- alias_native :dom_node, :getDOMNode
- alias_native :mounted?, :isMounted
- alias_native :force_update!, :forceUpdate
-
- def set_props(prop, &block)
- raise "No native ReactComponent associated" unless @native
- %x{
- #{@native}.setProps(#{prop.shallow_to_n}, function(){
- #{block.call if block}
- });
- }
- end
-
- def set_props!(prop, &block)
- raise "No native ReactComponent associated" unless @native
- %x{
- #{@native}.replaceProps(#{prop.shallow_to_n}, function(){
- #{block.call if block}
- });
- }
- end
-
- def set_state(state, &block)
- raise "No native ReactComponent associated" unless @native
- %x{
- #{@native}.setState(#{state.shallow_to_n}, function(){
- #{block.call if block}
- });
- }
- end
-
- def set_state!(state, &block)
- raise "No native ReactComponent associated" unless @native
- %x{
- #{@native}.replaceState(#{state.shallow_to_n}, function(){
- #{block.call if block}
- });
- }
- end
- end
end
end
diff --git a/opal/react/component/api.rb b/opal/react/component/api.rb
new file mode 100644
index 0000000..3c726c9
--- /dev/null
+++ b/opal/react/component/api.rb
@@ -0,0 +1,44 @@
+module React
+ module Component
+ module API
+ def state
+ Hash.new(`#{self}.state`)
+ end
+
+ def props
+ Hash.new(`#{self}.props`)
+ end
+
+ def force_update!
+ `#{self}.forceUpdate()`
+ end
+
+ def set_state(state, &block)
+ %x{
+ #{self}.setState(#{state.shallow_to_n}, function(){
+ #{block.call if block}
+ });
+ }
+ end
+
+ def refs
+ hash = {}
+
+ %x{
+ var refs = self.refs;
+ for (var property in refs) {
+ if (refs.hasOwnProperty(property)) {
+ #{hash[`property`] = `refs[property]`}
+ }
+ }
+ }
+
+ hash
+ end
+
+ def dom_node
+ raise "`dom_node` is deprecated in favor of `React.find_dom_node`"
+ end
+ end
+ end
+end
diff --git a/opal/react/component_factory.rb b/opal/react/component_factory.rb
new file mode 100644
index 0000000..394698a
--- /dev/null
+++ b/opal/react/component_factory.rb
@@ -0,0 +1,73 @@
+module React
+ class ComponentFactory
+ @@component_classes = {}
+
+ def self.clear_component_class_cache
+ @@component_classes = {}
+ end
+
+ def self.native_component_class(klass)
+ klass.class_eval do
+ include(React::Component::API)
+ # In Opal 0.8, native_alias fails if the method isn't there but we don't want to force all of these to be implemented
+ optional_native_alias = lambda do |js, ruby|
+ not_there = `!(#{self}.$$proto['$' + #{ruby}])`
+ native_alias js, ruby unless not_there
+ end
+ optional_native_alias[:componentWillMount, :component_will_mount]
+ optional_native_alias[:componentDidMount, :component_did_mount]
+ optional_native_alias[:componentWillReceiveProps, :component_will_receive_props]
+ optional_native_alias[:shouldComponentUpdate, :should_component_update?]
+ optional_native_alias[:componentWillUpdate, :component_will_update]
+ optional_native_alias[:componentDidUpdate, :component_did_update]
+ optional_native_alias[:componentWillUnmount, :component_will_unmount]
+ native_alias :render, :render
+ end
+ %x{
+ if (!Object.assign) {
+ Object.defineProperty(Object, 'assign', {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: function(target, firstSource) {
+ 'use strict';
+ if (target === undefined || target === null) {
+ throw new TypeError('Cannot convert first argument to object');
+ }
+
+ var to = Object(target);
+ for (var i = 1; i < arguments.length; i++) {
+ var nextSource = arguments[i];
+ if (nextSource === undefined || nextSource === null) {
+ continue;
+ }
+
+ var keysArray = Object.keys(Object(nextSource));
+ for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+ var nextKey = keysArray[nextIndex];
+ var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
+ if (desc !== undefined && desc.enumerable) {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ return to;
+ }
+ });
+ }
+ function ctor(props){
+ this.constructor = ctor;
+ this.state = #{klass.respond_to?(:initial_state) ? klass.initial_state.to_n : `{}`};
+ React.Component.apply(this, arguments);
+ #{klass}.$$alloc.prototype.$initialize.call(this, Opal.Hash.$new(props));
+ };
+ ctor.prototype = klass.$$proto;
+ ctor.displayName = #{klass.to_s};
+ Object.assign(ctor.prototype, React.Component.prototype);
+ ctor.propTypes = #{klass.respond_to?(:prop_types) ? klass.prop_types.to_n : `{}`};
+ ctor.defaultProps = #{klass.respond_to?(:default_props) ? klass.default_props.to_n : `{}`};
+ }
+ @@component_classes[klass.to_s] ||= `ctor`
+ end
+ end
+end
diff --git a/lib/react/element.rb b/opal/react/element.rb
similarity index 81%
rename from lib/react/element.rb
rename to opal/react/element.rb
index 9d87e09..bd167c2 100644
--- a/lib/react/element.rb
+++ b/opal/react/element.rb
@@ -1,7 +1,34 @@
-require "./ext/string"
+require "react/ext/string"
module React
class Element < `(function(){var r = React;var f = function(){};var c = r.createClass({render:function(){return null;}});f.prototype = Object.getPrototypeOf(r.createElement(c));return f;})()`
+ def self.attach_event_callback(element, event_name)
+ name = event_name.to_s.event_camelize
+
+ prop_key = "on#{name}"
+
+ if React::Event::BUILT_IN_EVENTS.include?(prop_key)
+ callback = %x{
+ function(event){
+ #{yield React::Event.new(`event`)}
+ }
+ }
+ else
+ callback = %x{
+ function(){
+ #{yield *Array(`arguments`)}
+ }
+ }
+ end
+
+ new_prop = `{}`
+ `new_prop[prop_key] = callback`
+
+ new_element = `React.cloneElement(#{element}, #{new_prop})`
+
+ return new_element
+ end
+
def self.new
raise "use React.create_element instead"
end
@@ -22,29 +49,8 @@ def ref
Native(`self.ref`)
end
- def on(event_name)
- name = event_name.to_s.event_camelize
-
-
- if React::Event::BUILT_IN_EVENTS.include?("on#{name}")
- prop_key = "on#{name}"
- callback = %x{
- function(event){
- #{yield React::Event.new(`event`)}
- }
- }
- else
- prop_key = "_on#{name}"
- callback = %x{
- function(){
- #{yield *Array(`arguments`)}
- }
- }
- end
-
- `self.props[#{prop_key}] = #{callback}`
-
- self
+ def on(event_name, &block)
+ self.class.attach_event_callback(self, event_name, &block)
end
def children
diff --git a/opal/react/element_children_handle.rb b/opal/react/element_children_handle.rb
new file mode 100644
index 0000000..bcdcc78
--- /dev/null
+++ b/opal/react/element_children_handle.rb
@@ -0,0 +1,17 @@
+require "react/element"
+
+module React
+ class ElementChildrenHandle
+ def initialize(children, index)
+ @children = children
+ @index = index
+ end
+
+ def on(event_name, &block)
+ old_element = @children[@index]
+ new_element = React::Element.attach_event_callback(old_element, event_name, &block)
+ @children[@index] = new_element
+ self
+ end
+ end
+end
diff --git a/lib/react/event.rb b/opal/react/event.rb
similarity index 100%
rename from lib/react/event.rb
rename to opal/react/event.rb
diff --git a/lib/react/ext/hash.rb b/opal/react/ext/hash.rb
similarity index 100%
rename from lib/react/ext/hash.rb
rename to opal/react/ext/hash.rb
diff --git a/opal/react/ext/string.rb b/opal/react/ext/string.rb
new file mode 100644
index 0000000..728331e
--- /dev/null
+++ b/opal/react/ext/string.rb
@@ -0,0 +1,24 @@
+class String
+ def event_camelize
+ `#{self}.replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
+ var capitalize = true;
+ return capitalize ? word.substr(0,1).toUpperCase()+word.substr(1) : word;
+ })`
+ end
+
+ # 'class_name' => 'className'
+ def lower_camelize
+ # TODO Could be implemented more efficiently
+ words = self.split("_")
+ result = [words.first]
+ result.concat(words[1..-1].map {|word| word[0].upcase + word[1..-1] })
+ result.join("")
+ end
+
+ # 'className' => 'class_name'
+ def underscore
+ return `self.replace(/[A-Z]/g, function(){
+ return '_' + arguments[0].toLowerCase();
+ });`
+ end
+end
diff --git a/opal/react/testing.rb b/opal/react/testing.rb
new file mode 100644
index 0000000..3915922
--- /dev/null
+++ b/opal/react/testing.rb
@@ -0,0 +1,14 @@
+module React
+ module Testing
+ `var ReactTestUtils = React.addons.TestUtils`
+
+ def simulate_event(event_name, dom_element, event_data = {})
+ simulator = Native(`ReactTestUtils.Simulate`)
+ simulator[event_name].call(dom_element, event_data)
+ end
+
+ def render_to_document(element)
+ `ReactTestUtils.renderIntoDocument(element)`
+ end
+ end
+end
diff --git a/lib/react/top_level.rb b/opal/react/top_level.rb
similarity index 50%
rename from lib/react/top_level.rb
rename to opal/react/top_level.rb
index 1154f9c..45b5e8b 100644
--- a/lib/react/top_level.rb
+++ b/opal/react/top_level.rb
@@ -1,5 +1,6 @@
require "native"
require 'active_support'
+require "react/ext/string"
module React
HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
@@ -10,26 +11,44 @@ module React
output p param picture pre progress q rp rt ruby s samp script section select
small source span strong style sub summary sup table tbody td textarea tfoot th
thead time title tr track u ul var video wbr)
- ATTRIBUTES = %w(accept acceptCharset accessKey action allowFullScreen allowTransparency alt
- async autoComplete autoPlay cellPadding cellSpacing charSet checked classID
- className cols colSpan content contentEditable contextMenu controls coords
- crossOrigin data dateTime defer dir disabled download draggable encType form
- formAction formEncType formMethod formNoValidate formTarget frameBorder height
- hidden href hrefLang htmlFor httpEquiv icon id label lang list loop manifest
- marginHeight marginWidth max maxLength media mediaGroup method min multiple
- muted name noValidate open pattern placeholder poster preload radioGroup
- readOnly rel required role rows rowSpan sandbox scope scrolling seamless
- selected shape size sizes span spellCheck src srcDoc srcSet start step style
- tabIndex target title type useMap value width wmode dangerouslySetInnerHTML)
def self.create_element(type, properties = {}, &block)
- React::API.create_element(type, properties, &block)
+ params = []
+
+ # Component Spec or Nomral DOM
+ if `(typeof type === 'function')`
+ params << type
+ elsif type.kind_of?(Class)
+ raise "Provided class should define `render` method" if !(type.method_defined? :render)
+ params << React::ComponentFactory.native_component_class(type)
+ else
+ raise "#{type} not implemented" unless HTML_TAGS.include?(type)
+ params << type
+ end
+
+ # Passed in properties
+ props = {}
+ properties.map do |key, value|
+ if key == "class_name" && value.is_a?(Hash)
+ props[key.lower_camelize] = value.inject([]) {|ary, (k,v)| v ? ary.push(k) : ary}.join(" ")
+ else
+ props[key.lower_camelize] = value
+ end
+ end
+ params << props.shallow_to_n
+
+ # Children Nodes
+ if block_given?
+ children = [yield].flatten.each do |ele|
+ params << ele
+ end
+ end
+
+ return `React.createElement.apply(null, #{params})`
end
def self.render(element, container)
- component = Native(`React.render(#{element}, container, function(){#{yield if block_given?}})`)
- component.class.include(React::Component::API)
- component
+ `React.render(#{element}, container, function(){#{yield if block_given?}})`
end
def self.is_valid_element(element)
@@ -50,7 +69,11 @@ def self.unmount_component_at_node(node)
def self.expose_native_class(*args)
args.each do |klass|
- `window[#{klass.to_s}] = #{React::API.native_component_class(klass)}`
+ `window[#{klass.to_s}] = #{React::ComponentFactory.native_component_class(klass)}`
end
end
+
+ def self.find_dom_node(component)
+ `React.findDOMNode(component)`
+ end
end
diff --git a/lib/react/validator.rb b/opal/react/validator.rb
similarity index 100%
rename from lib/react/validator.rb
rename to opal/react/validator.rb
diff --git a/react.rb.gemspec b/react.rb.gemspec
index 5b349ce..1f36ace 100644
--- a/react.rb.gemspec
+++ b/react.rb.gemspec
@@ -1,9 +1,9 @@
# -*- encoding: utf-8 -*-
-require File.expand_path('../lib/react/version', __FILE__)
+require File.expand_path('../lib/opal/react/version', __FILE__)
Gem::Specification.new do |s|
s.name = 'react.rb'
- s.version = React::VERSION
+ s.version = Opal::React::VERSION
s.author = 'David Chang'
s.email = 'zeta11235813@gmail.com'
s.homepage = 'https://github.com/zetachang/react.rb'
@@ -16,13 +16,13 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.require_paths = ['lib', 'vendor']
- s.add_runtime_dependency 'opal', '~> 0.6.0'
+ s.add_runtime_dependency 'opal', ['>= 0.7.0', '< 0.9']
s.add_runtime_dependency 'opal-activesupport', '~> 0'
s.add_runtime_dependency 'therubyracer', '~> 0'
s.add_runtime_dependency 'react-jsx', '~> 0.8.0'
- s.add_runtime_dependency 'sprockets', '>= 2.2.3', '< 3.0.0'
- s.add_development_dependency 'react-source', '~> 0.12.0'
- s.add_development_dependency 'opal-rspec', '~> 0.3.0.beta3'
+ s.add_runtime_dependency 'sprockets', '~> 3.1'
+ s.add_runtime_dependency 'react-source', '~> 0.13'
+
s.add_development_dependency 'sinatra', '~> 1'
s.add_development_dependency 'opal-jquery', '~> 0'
s.add_development_dependency 'rake', '~> 10'
diff --git a/spec/component_factory_spec.rb b/spec/component_factory_spec.rb
new file mode 100644
index 0000000..71b1085
--- /dev/null
+++ b/spec/component_factory_spec.rb
@@ -0,0 +1,28 @@
+require "spec_helper"
+
+describe React::ComponentFactory do
+ describe "native_component_class" do
+ it "should bridge the defined life cycle methods" do
+ stub_const 'Foo', Class.new
+ Foo.class_eval do
+ def component_will_mount; end
+ def component_did_mount; end
+ def component_will_receive_props; end
+ def should_component_update?; end
+ def component_will_update; end
+ def component_did_update; end
+ def component_will_unmount; end
+ end
+
+ ctor = React::ComponentFactory.native_component_class(Foo)
+ instance = `new ctor`
+ expect(`instance.$component_will_mount`).to be(`instance.componentWillMount`)
+ expect(`instance.$component_did_mount`).to be(`instance.componentDidMount`)
+ expect(`instance.$component_will_receive_props`).to be(`instance.componentWillReceiveProps`)
+ expect(`instance["$should_component_update?"]`).to be(`instance.shouldComponentUpdate`)
+ expect(`instance.$component_will_update`).to be(`instance.componentWillUpdate`)
+ expect(`instance.$component_did_update`).to be(`instance.componentDidUpdate`)
+ expect(`instance.$component_will_unmount`).to be(`instance.componentWillUnmount`)
+ end
+ end
+end
diff --git a/spec/component_spec.rb b/spec/component_spec.rb
index 96fa5c9..3dcec79 100644
--- a/spec/component_spec.rb
+++ b/spec/component_spec.rb
@@ -1,10 +1,6 @@
require "spec_helper"
describe React::Component do
- after(:each) do
- React::API.clear_component_class_cache
- end
-
it "should define component spec methods" do
stub_const 'Foo', Class.new
Foo.class_eval do
@@ -50,7 +46,7 @@ def bar2; end
expect_any_instance_of(Foo).to receive(:bar)
expect_any_instance_of(Foo).to receive(:bar2)
- renderToDocument(Foo)
+ render_to_document(React.create_element(Foo))
end
it "should invoke `after_mount` registered methods when `componentDidMount()`" do
@@ -63,7 +59,7 @@ def bar4; end
expect_any_instance_of(Foo).to receive(:bar3)
expect_any_instance_of(Foo).to receive(:bar4)
- renderToDocument(Foo)
+ render_to_document(React.create_element(Foo))
end
it "should allow multiple class declared life cycle hooker" do
@@ -84,7 +80,7 @@ def render
expect_any_instance_of(Foo).to receive(:bar)
- renderToDocument(Foo)
+ render_to_document(React.create_element(Foo))
end
it "should allow block for life cycle callback" do
@@ -96,8 +92,8 @@ def render
end
end
- element = renderToDocument(Foo)
- expect(element.state.foo).to be("bar")
+ instance = render_to_document(React.create_element(Foo))
+ expect(instance.state[:foo]).to be("bar")
end
end
@@ -121,8 +117,8 @@ def set_up
end
end
- element = renderToDocument(Foo)
- expect(element.state.foo).to be("bar")
+ instance = render_to_document(React.create_element(Foo))
+ expect(instance.state[:foo]).to be("bar")
end
it "should define init state by passing a block to `define_state`" do
@@ -130,8 +126,8 @@ def set_up
define_state(:foo) { 10 }
end
- element = renderToDocument(Foo)
- expect(element.state.foo).to be(10)
+ instance = render_to_document(React.create_element(Foo))
+ expect(instance.state[:foo]).to be(10)
end
it "should define getter using `define_state`" do
@@ -143,8 +139,8 @@ def bump
end
end
- element = renderToDocument(Foo)
- expect(element.state.foo).to be(30)
+ instance = render_to_document(React.create_element(Foo))
+ expect(instance.state[:foo]).to be(30)
end
it "should define multiple state accessor by passing symols array to `define_state`" do
@@ -157,9 +153,9 @@ def set_up
end
end
- element = renderToDocument(Foo)
- expect(element.state.foo).to be(10)
- expect(element.state.foo2).to be(20)
+ instance = render_to_document(React.create_element(Foo))
+ expect(instance.state[:foo]).to be(10)
+ expect(instance.state[:foo2]).to be(20)
end
it "should invoke `define_state` multiple times to define states" do
@@ -168,9 +164,9 @@ def set_up
define_state(:foo2) { 40 }
end
- element = renderToDocument(Foo)
- expect(element.state.foo).to be(30)
- expect(element.state.foo2).to be(40)
+ instance = render_to_document(React.create_element(Foo))
+ expect(instance.state[:foo]).to be(30)
+ expect(instance.state[:foo2]).to be(40)
end
it "should raise error if multiple states and block given at the same time" do
@@ -189,8 +185,8 @@ def render
end
end
- element = renderToDocument(Foo)
- expect(element.getDOMNode.textContent).to eq("10")
+ instance = render_to_document(React.create_element(Foo))
+ expect(`#{React.find_dom_node(instance)}.textContent`).to eq("10")
end
it "should support original `setState` as `set_state` method" do
@@ -200,21 +196,8 @@ def render
end
end
- element = renderToDocument(Foo)
- expect(element.state.foo).to be("bar")
- end
-
- it "should support original `replaceState` as `set_state!` method" do
- Foo.class_eval do
- before_mount do
- self.set_state(foo: "bar")
- self.set_state!(bar: "lorem")
- end
- end
-
- element = renderToDocument(Foo)
- expect(element.state.foo).to be_nil
- expect(element.state.bar).to eq("lorem")
+ instance = render_to_document(React.create_element(Foo))
+ expect(instance.state[:foo]).to be("bar")
end
it "should support originl `state` method" do
@@ -264,8 +247,8 @@ def render
end
end
- element = renderToDocument(Foo, prop: "foobar")
- expect(element.getDOMNode.textContent).to eq("foobar")
+ instance = render_to_document(React.create_element(Foo, prop: "foobar"))
+ expect(`#{React.find_dom_node(instance)}.textContent`).to eq("foobar")
end
it "should access nested params as orignal Ruby object" do
@@ -275,41 +258,8 @@ def render
end
end
- element = renderToDocument(Foo, prop: [{foo: 10}])
- expect(element.getDOMNode.textContent).to eq("10")
- end
- end
-
- describe "Props Updating" do
- before do
- stub_const 'Foo', Class.new
- Foo.class_eval do
- include React::Component
- end
- end
-
- it "should support original `setProps` as method `set_props`" do
- Foo.class_eval do
- def render
- React.create_element("div") { params[:foo] }
- end
- end
-
- element = renderToDocument(Foo, {foo: 10})
- element.set_props(foo: 20)
- expect(element.dom_node.innerHTML).to eq('20')
- end
-
- it "should support original `replaceProps` as method `set_props!`" do
- Foo.class_eval do
- def render
- React.create_element("div") { params[:foo] ? "exist" : "null" }
- end
- end
-
- element = renderToDocument(Foo, {foo: 10})
- element.set_props!(bar: 20)
- expect(element.dom_node.innerHTML).to eq('null')
+ instance = render_to_document(React.create_element(Foo, prop: [{foo: 10}]))
+ expect(`#{React.find_dom_node(instance)}.textContent`).to eq("10")
end
end
@@ -349,9 +299,14 @@ def render; div; end
var org_console = window.console;
window.console = {warn: function(str){log.push(str)}}
}
- renderToDocument(Foo, bar: 10, lorem: Lorem.new)
- `window.console = org_console;`
- expect(`log`).to eq(["Warning: In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` was not the specified type `String`"])
+
+ begin
+ render_to_document(React.create_element(Foo, bar: 10, lorem: Lorem.new))
+
+ expect(`log`).to eq(["Warning: Failed propType: In component `Foo`\nRequired prop `foo` was not specified\nProvided prop `bar` was not the specified type `String`"])
+ ensure
+ `window.console = org_console;`
+ end
end
it "should not log anything if validation pass" do
@@ -371,9 +326,12 @@ def render; div; end
var org_console = window.console;
window.console = {warn: function(str){log.push(str)}}
}
- renderToDocument(Foo, foo: 10, bar: "10", lorem: Lorem.new)
- `window.console = org_console;`
- expect(`log`).to eq([])
+ begin
+ render_to_document(React.create_element(Foo, foo: 10, bar: "10", lorem: Lorem.new))
+ expect(`log`).to eq([])
+ ensure
+ `window.console = org_console;`
+ end
end
end
@@ -417,10 +375,9 @@ def render
end
end
- element = React.create_element(Foo)
- instance = renderElementToDocument(element)
- simulateEvent(:click, instance)
- expect(instance.state.clicked).to eq(true)
+ instance = render_to_document(React.create_element(Foo))
+ simulate_event(:click, React.find_dom_node(instance))
+ expect(instance.state[:clicked]).to eq(true)
end
it "should invoke handler on `this.props` using emit" do
@@ -438,7 +395,7 @@ def render
expect { |b|
element = React.create_element(Foo).on(:foo_submit, &b)
- renderElementToDocument(element)
+ render_to_document(element)
}.to yield_with_args("bar")
end
@@ -457,7 +414,7 @@ def render
expect { |b|
element = React.create_element(Foo).on(:foo_invoked, &b)
- renderElementToDocument(element)
+ render_to_document(element)
}.to yield_with_args([1,2,3], "bar")
end
end
@@ -477,23 +434,24 @@ def render
end
end
- element = renderToDocument(Foo)
- expect(element.refs.field).not_to be_nil
+ instance = render_to_document(React.create_element(Foo))
+ expect(`#{React.find_dom_node(instance.refs[:field])}.tagName`).to eq('INPUT')
end
it "should access refs through `refs` method" do
Foo.class_eval do
def render
React.create_element("input", type: :text, ref: :field).on(:click) do
- refs[:field].value = "some_stuff"
+ input_field = Native(React.find_dom_node(refs[:field]))
+ input_field.value = "some_stuff"
end
end
end
- element = renderToDocument(Foo)
- simulateEvent(:click, element)
+ instance = render_to_document(React.create_element(Foo))
+ simulate_event(:click, React.find_dom_node(instance))
- expect(element.refs.field.value).to eq("some_stuff")
+ expect(`#{React.find_dom_node(instance.refs[:field])}.value`).to eq("some_stuff")
end
end
@@ -575,23 +533,38 @@ def render
expect(Kernel).to receive(:p).with("first")
expect(Kernel).to receive(:p).with("second")
- renderToDocument(Foo)
+ render_to_document(React.create_element(Foo))
end
- end
-
- describe "isMounted()" do
- it "should return true if after mounted" do
+
+ it "should return React::Element for root element" do
stub_const 'Foo', Class.new
Foo.class_eval do
include React::Component
def render
- React.create_element("div")
+ div
end
end
+
+ expect(Foo.new.render).to be_a(React::Element)
+ end
+
+ it "should return React::ElementChildrenHandle for inner children" do
+ 'var inner;'
+
+ stub_const 'Foo', Class.new
+ Foo.class_eval do
+ include React::Component
- component = renderToDocument(Foo)
- expect(component.mounted?).to eq(true)
+ def render
+ div do
+ `inner = #{div {'lorem'}}`
+ end
+ end
+ end
+
+ expect(Foo.new.render).to be_a(React::Element)
+ expect(`inner`).to be_a(React::ElementChildrenHandle)
end
end
end
diff --git a/spec/element_spec.rb b/spec/element_spec.rb
index 6934842..9a7ce91 100644
--- a/spec/element_spec.rb
+++ b/spec/element_spec.rb
@@ -4,6 +4,7 @@
it "should be toll-free bridged to React.Element" do
element = React.create_element('div')
expect(`React.isValidElement(#{element})`).to eq(true)
+ expect(element.kind_of?(React::Element)).to eq(true)
end
describe "#new" do
@@ -54,26 +55,29 @@
it "should be subscribable through `on(:event_name)` method" do
expect { |b|
element = React.create_element("div").on(:click, &b)
- instance = renderElementToDocument(element)
- simulateEvent(:click, instance)
+ instance = render_to_document(element)
+ simulate_event(:click, React.find_dom_node(instance))
}.to yield_with_args(React::Event)
expect { |b|
element = React.create_element("div").on(:key_down, &b)
- instance = renderElementToDocument(element)
- simulateEvent(:keyDown, instance, {key: "Enter"})
+ instance = render_to_document(element)
+ simulate_event(:keyDown, React.find_dom_node(instance), {key: "Enter"})
}.to yield_control
expect { |b|
element = React.create_element("form").on(:submit, &b)
- instance = renderElementToDocument(element)
- simulateEvent(:submit, instance, {})
+ instance = render_to_document(element)
+ simulate_event(:submit, React.find_dom_node(instance), {})
}.to yield_control
end
- it "should return self for `on` method" do
- element = React.create_element("div")
- expect(element.on(:click){}).to eq(element)
+ it "should return the copied ReactElement for `on` method" do
+ element = React.create_element("div", {foo: "bar"})
+
+ new_element = element.on(:click){}
+ expect(new_element).to be_a(React::Element)
+ expect(new_element.props[:foo]).to eq('bar')
end
end
diff --git a/spec/event_spec.rb b/spec/event_spec.rb
index 40f3c7f..11a14e3 100644
--- a/spec/event_spec.rb
+++ b/spec/event_spec.rb
@@ -16,7 +16,7 @@
expect(event).to respond_to(:prevent_default)
expect(event).to respond_to(:stop_propagation)
end
- instance = renderElementToDocument(element)
- simulateEvent(:click, instance)
+ instance = render_to_document(element)
+ simulate_event(:click, React.find_dom_node(instance))
end
-end
\ No newline at end of file
+end
diff --git a/spec/react_spec.rb b/spec/react_spec.rb
index a0a7151..a5547cb 100644
--- a/spec/react_spec.rb
+++ b/spec/react_spec.rb
@@ -1,10 +1,6 @@
require "spec_helper"
describe React do
- after(:each) do
- React::API.clear_component_class_cache
- end
-
describe "is_valid_element" do
it "should return true if passed a valid element" do
element = `React.createElement('div')`
@@ -47,14 +43,6 @@
expect(React.is_valid_element(element)).to eq(true)
expect(element.children.length).to eq(3)
end
-
- it "should render element with children as array when block yield Array of element" do
- element = React.create_element('div') do
- [React.create_element('span'), React.create_element('span'), React.create_element('span')]
- end
- instance = renderElementToDocument(element)
- expect(instance.getDOMNode.childNodes.length).to eq(3)
- end
end
describe "custom element" do
before do
@@ -123,8 +111,8 @@ def render
end
end
- renderToDocument(Foo)
- renderToDocument(Foo)
+ render_to_document(React.create_element(Foo))
+ render_to_document(React.create_element(Foo))
expect(`count`).to eq(2)
end
@@ -141,9 +129,10 @@ def render
expect(element.props[:foo]).to eq("bar")
end
- it "should not camel-case custom property" do
- element = React.create_element("div", foo_bar: "foo")
- expect(element.props[:foo_bar]).to eq("foo")
+ it "should camel-case all property" do
+ element = React.create_element("div", foo_bar: "foo", class_name: 'fancy')
+ expect(element.props[:fooBar]).to eq("foo")
+ expect(element.props[:className]).to eq("fancy")
end
end
@@ -180,14 +169,6 @@ def render
React.render(React.create_element('span') { "lorem" }, div)
end
- it "should return a React::Component::API compatible object" do
- div = `document.createElement("div")`
- component = React.render(React.create_element('span') { "lorem" }, div)
- React::Component::API.public_instance_methods(true).each do |method_name|
- expect(component).to respond_to(method_name)
- end
- end
-
pending "should return nil to prevent abstraction leakage" do
div = `document.createElement("div")`
expect {
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 2ed31ff..6ed1d37 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,29 +1,9 @@
require 'react'
-
-module ReactTestHelpers
- `var ReactTestUtils = React.addons.TestUtils`
-
- def renderToDocument(type, options = {})
- element = React.create_element(type, options)
- return renderElementToDocument(element)
- end
-
- def renderElementToDocument(element)
- instance = Native(`ReactTestUtils.renderIntoDocument(#{element})`)
- instance.class.include(React::Component::API)
- return instance
- end
-
- def simulateEvent(event, component, params = {})
- simulator = Native(`ReactTestUtils.Simulate`)
- simulator[event.to_s].call(`#{component.to_n}.getDOMNode()`, params)
- end
-
- def isElementOfType(element, type)
- `React.addons.TestUtils.isElementOfType(#{element}, #{type.cached_component_class})`
- end
-end
+require 'react/testing'
RSpec.configure do |config|
- config.include ReactTestHelpers
+ config.include React::Testing
+ config.after :each do
+ React::ComponentFactory.clear_component_class_cache
+ end
end