├── .codeclimate.yml
├── .gitignore
├── .rubocop.yml
├── .travis.yml
├── Appraisals
├── CHANGELOG.md
├── DOCS.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── UPGRADING.md
├── component-name-lookup.md
├── dciy.toml
├── hyper-react.gemspec
├── lib
├── generators
│ └── reactive_ruby
│ │ └── test_app
│ │ ├── templates
│ │ ├── assets
│ │ │ └── javascripts
│ │ │ │ ├── components.rb
│ │ │ │ ├── server_rendering.js
│ │ │ │ └── test_application.rb
│ │ ├── boot.rb.erb
│ │ ├── script
│ │ │ └── rails
│ │ ├── test_application.rb.erb
│ │ └── views
│ │ │ ├── components
│ │ │ ├── hello_world.rb
│ │ │ └── todo.rb
│ │ │ └── layouts
│ │ │ └── test_layout.html.erb
│ │ └── test_app_generator.rb
├── hyper-react.rb
├── rails-helpers
│ └── top_level_rails_component.rb
├── react
│ ├── api.rb
│ ├── callbacks.rb
│ ├── children.rb
│ ├── component.rb
│ ├── component
│ │ ├── api.rb
│ │ ├── base.rb
│ │ ├── class_methods.rb
│ │ ├── dsl_instance_methods.rb
│ │ ├── params.rb
│ │ ├── props_wrapper.rb
│ │ ├── should_component_update.rb
│ │ └── tags.rb
│ ├── config.rb
│ ├── element.rb
│ ├── event.rb
│ ├── ext
│ │ ├── hash.rb
│ │ ├── opal-jquery
│ │ │ └── element.rb
│ │ └── string.rb
│ ├── native_library.rb
│ ├── object.rb
│ ├── react-source-browser.rb
│ ├── react-source-server.rb
│ ├── react-source.rb
│ ├── ref_callback.rb
│ ├── rendering_context.rb
│ ├── server.rb
│ ├── state_wrapper.rb
│ ├── test.rb
│ ├── test
│ │ ├── dsl.rb
│ │ ├── matchers
│ │ │ └── render_html_matcher.rb
│ │ ├── rspec.rb
│ │ ├── session.rb
│ │ └── utils.rb
│ ├── to_key.rb
│ ├── top_level.rb
│ ├── top_level_render.rb
│ └── validator.rb
├── reactive-ruby
│ ├── component_loader.rb
│ ├── isomorphic_helpers.rb
│ ├── rails.rb
│ ├── rails
│ │ ├── component_mount.rb
│ │ ├── controller_helper.rb
│ │ └── railtie.rb
│ ├── serializers.rb
│ ├── server_rendering
│ │ ├── contextual_renderer.rb
│ │ └── hyper_asset_container.rb
│ └── version.rb
└── reactrb
│ └── auto-import.rb
├── logo1.png
├── logo2.png
├── logo3.png
├── path_release_steps.md
└── spec
├── controller_helper_spec.rb
├── index.html.erb
├── react
├── builtin_tags_spec.rb
├── callbacks_spec.rb
├── children_spec.rb
├── component
│ └── base_spec.rb
├── component_spec.rb
├── dsl_spec.rb
├── element_spec.rb
├── event_spec.rb
├── native_library_spec.rb
├── observable_spec.rb
├── opal_jquery_extensions_spec.rb
├── param_declaration_spec.rb
├── react_spec.rb
├── refs_callback_spec.rb
├── server_spec.rb
├── state_spec.rb
├── test
│ ├── dsl_spec.rb
│ ├── matchers
│ │ └── render_html_matcher_spec.rb
│ ├── rspec_spec.rb
│ ├── session_spec.rb
│ └── utils_spec.rb
├── to_key_spec.rb
├── top_level_component_spec.rb
├── tutorial
│ └── tutorial_spec.rb
└── validator_spec.rb
├── reactive-ruby
├── component_loader_spec.rb
├── isomorphic_helpers_spec.rb
├── rails
│ ├── asset_pipeline_spec.rb
│ └── component_mount_spec.rb
└── server_rendering
│ └── contextual_renderer_spec.rb
├── spec_helper.rb
├── test_app
├── README.md
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ ├── javascripts
│ │ │ ├── application.rb
│ │ │ ├── cable.js
│ │ │ ├── channels
│ │ │ │ └── .keep
│ │ │ └── server_rendering.js
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ └── concerns
│ │ │ └── .keep
│ ├── helpers
│ │ └── application_helper.rb
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ ├── models
│ │ ├── application_record.rb
│ │ └── concerns
│ │ │ └── .keep
│ └── views
│ │ ├── components.rb
│ │ ├── components
│ │ ├── hello_world.rb
│ │ └── todo.rb
│ │ └── layouts
│ │ ├── application.html.erb
│ │ ├── explicit_layout.html.erb
│ │ ├── mailer.html.erb
│ │ ├── mailer.text.erb
│ │ └── test_layout.html.erb
├── bin
│ ├── bundle
│ ├── rails
│ ├── rake
│ ├── setup
│ ├── update
│ └── yarn
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ ├── secrets.yml
│ └── spring.rb
├── db
│ ├── schema.rb
│ └── seeds.rb
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
├── package.json
└── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ └── favicon.ico
└── vendor
├── es5-shim.min.js
└── jquery-2.2.4.min.js
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | duplication:
4 | enabled: true
5 | config:
6 | languages:
7 | - ruby
8 | - javascript
9 | - python
10 | - php
11 | fixme:
12 | enabled: true
13 | rubocop:
14 | enabled: true
15 | ratings:
16 | paths:
17 | - "**.inc"
18 | - "**.js"
19 | - "**.jsx"
20 | - "**.module"
21 | - "**.php"
22 | - "**.py"
23 | - "**.rb"
24 | exclude_paths:
25 | - example/**/*
26 | - lib/sources/**/*
27 | - spec/
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
27 | node_modules
28 |
29 | # Ignore bundler config.
30 | .bundle
31 |
32 | spec/test_app/tmp
33 | spec/test_app/db
34 |
35 | /gemfiles/*.lock
36 | /tmp
37 |
38 | # ignore gem
39 | *.gem
40 |
41 | # ignore IDE files
42 | .idea
43 | .vscode
44 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | language: ruby
3 | cache: bundler
4 | rvm:
5 | - 2.4.4
6 | - 2.5.1
7 | - ruby-head
8 | env:
9 | - DRIVER=google-chrome TZ=Europe/Berlin
10 | matrix:
11 | fast_finish: true
12 | allow_failures:
13 | - rvm: ruby-head
14 | before_install:
15 | - if [[ "$DRIVER" == "google-chrome" ]]; then wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -; fi
16 | - if [[ "$DRIVER" == "google-chrome" ]]; then echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list; fi
17 | - if [[ "$DRIVER" == "google-chrome" ]]; then sudo apt-get update -qq && sudo apt-get install -qq -y google-chrome-stable; fi
18 | - gem install bundler
19 | before_script:
20 | - cd spec/test_app
21 | - bundle install --jobs=3 --retry=3
22 | - bundle exec rails db:setup
23 | - cd ../../
24 | - if [[ "$DRIVER" == "google-chrome" ]]; then bundle exec chromedriver-update; fi
25 | - if [[ "$DRIVER" == "google-chrome" ]]; then ls -lR ~/.chromedriver-helper/; fi
26 | - if [[ "$DRIVER" == "google-chrome" ]]; then bundle exec chromedriver --version; fi
27 | - if [[ "$DRIVER" == "google-chrome" ]]; then google-chrome --version; fi
28 | - if [[ "$DRIVER" == "google-chrome" ]]; then which google-chrome; fi
29 | script: bundle exec rspec
30 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | opal_versions = ['0.8', '0.9', '0.10']
2 | react_versions_map = {
3 | '13' => '~> 1.3.3',
4 | '14' => '~> 1.6.2',
5 | '15' => '~> 1.10.0'
6 | }
7 | opal_rails_versions_map = {
8 | '0.8' => '~> 0.8.1',
9 | '0.9' => '~> 0.9.0',
10 | '0.10' => '~> 0.9.0',
11 | }
12 |
13 | opal_versions.each do |opal_v|
14 | react_versions_map.each do |react_v, react_rails_v|
15 | appraise "opal-#{opal_v}-react-#{react_v}" do
16 | ruby ">= 1.9.3"
17 | gem 'opal', "~> #{opal_v}.0"
18 | gem 'opal-rails', opal_rails_versions_map[opal_v]
19 | gem 'react-rails', react_rails_v, require: false
20 | end
21 | end
22 | end
23 |
24 |
25 | appraise "opal-master-react-15" do
26 | ruby '>= 2.0.0'
27 | gem 'opal', git: 'https://github.com/opal/opal.git'
28 | gem "opal-sprockets", git: 'https://github.com/opal/opal-sprockets.git'
29 | gem 'opal-rails', '~> 0.9.4'
30 | gem 'react-rails', '~> 2.4.0', require: false
31 | end
32 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file starting with v0.8.6.
4 | This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5 |
6 | Changes are grouped as follows:
7 | - **Added** for new features.
8 | - **Changed** for changes in existing functionality.
9 | - **Deprecated** for once-stable features to be removed in upcoming releases.
10 | - **Removed** for deprecated features removed in this release.
11 | - **Fixed** for any bug fixes.
12 | - **Security** to invite users to upgrade in case of vulnerabilities.
13 |
14 |
20 |
21 | ## [0.12.0] - Unreleased
22 |
23 | ### Added
24 |
25 | - `React::Server` is provided as a module wrapping the original `ReactDOMServer` API, require `react/server` to use it. (#186)
26 | - `React::Config` is introduced, `environment` is the only config option provided for now. See [#204](https://github.com/ruby-hyperloop/hyper-react/issues/204) for usage details.
27 |
28 | ### Changed
29 |
30 | - State syntax is now consistent with Hyperloop::Store, old syntax is deprecated. (#209, #97)
31 |
32 | ### Deprecated
33 |
34 | - Current ref callback behavior is deprecated. Require `"react/ref_callback"` to get the updated behavior. (#188)
35 | - `React.render_to_string` & `React.render_to_static_markup` is deprecated, use `React::Server.render_to_string` & `React::Server.render_to_static_markup` instead. (#186)
36 | - `react/react-source` is deprecated, use `react/react-source-browser` or `react/react-source-server` instead. For most usecase, `react/react-source-browser` is sufficient. If you are using the built-in server side rendering feature, the actual `ReactDOMServer` is already provided by the `react-rails` gem. Therefore, unless you are building a custom server side rendering mechanism, it's not suggested to use `react/react-source-server` in browser code. (#186)
37 |
38 | ### Removed
39 |
40 | - `react-latest` & `react-v1x` is removed. Use `react/react-source-browser` or `react/react-source-server` instead.
41 | - Support for Ruby < 2.0 is removed. (#201)
42 |
43 | ### Fixed
44 |
45 | - [NativeLibrary] Passing native JS object as props will raise exception. (#195)
46 | - Returns better error message if result of rendering block is not suitable (#207)
47 | - Batch all state changes and execute *after* rendering cycle (#206, #178) (Code is now moved to Hyper::Store)
48 | You can revert to the old behavior by defining the `React::State::ALWAYS_UPDATE_STATE_AFTER_RENDER = false`
49 | - Memory Leak in render context fixed (#192)
50 |
51 |
52 | ## [0.11.0] - 2016-12-13
53 |
54 | ### Changed
55 |
56 | - The whole opal-activesuppport is not loaded by default now. This gave us about 18% size reduction on the built file. If your code rely on any of the module which is not required by hyper-react, you need to require it yourself. (#135)
57 |
58 | ### Deprecated
59 |
60 | - Current `React.render` behavior is deprecated. Require `"react/top_level_render"` to get the updated behavior. (#187)
61 | - `React.is_valid_element` is deprecated in favor of `React.is_valid_element?`.
62 | - `expect(component).to render('
')` is now deprecated in favor of `expect(component).to render_static_html('')`, which is much clearer.
63 |
64 | ### Fixed
65 |
66 | - `ReferenceError: window is not defined` error in prerender context with react-rails v1.10.0. (#196)
67 | - State might not be updated using `React::Observable` from a param. (#175)
68 | - Arity checking failed for `_react_param_conversion` & `React::Element#initialize` (#167)
69 |
70 |
71 | ## [0.10.0] - 2016-10-30
72 |
73 | ### Changed
74 |
75 | - This gem is now renamed to `hyper-react`, see [UPGRADING](UPGRADING.md) for details.
76 |
77 | ### Fixed
78 |
79 | - ReactJS functional stateless component could not be imported from `NativeLibrary`. Note that functional component is only supported in React v14+. (#162)
80 | - Prerender log got accumulated between reqeusts. (#176)
81 |
82 | ## [0.9.0] - 2016-10-19
83 |
84 | ### Added
85 |
86 | - `react/react-source` is the suggested way to include ReactJS sources now. Simply require `react/react-source` immediately before the `require "reactrb"` in your Opal code will make it work.
87 |
88 | ### Deprecated
89 |
90 | - `react-latest` & `react-v1x` is deprecated. Use `react/react-source` instead.
91 |
92 | ### Removed
93 |
94 | - `opal-browser` is removed from runtime dependency. (#133) You will have to add `gem 'opal-browser'` to your gemfile (recommended) or remove all references to opal-browser from your manifest files.
95 |
96 | ### Fixed
97 |
98 | - `$window#on` in `opal-jquery` is broken. (#166)
99 | - `Element#render` trigger unnecessary re-mounts when called multiple times. (#170)
100 | - Gets rid of react warnings about updating state during render (#155)
101 | - Multiple HAML classes (i.e. div.foo.bar) was not working (regression introduced in 0.8.8)
102 | - Don't send nil (null) to form components as the value string (#157)
103 | - Process `params` (props) correctly when using `Element#on` or `Element#render` (#158)
104 | - Deprecate shallow param compare (#156)
105 |
106 |
107 | ## [0.8.8] - 2016-07-13
108 |
109 | ### Added
110 |
111 | - More helpful error messages on render failures (#152)
112 | - `Element#on('')` subscribes to `my_event_name` (#153)
113 |
114 | ### Changed
115 |
116 | - `Element#on(:event)` subscribes to `on_event` for reactrb components and `onEvent` for native components. (#153)
117 |
118 | ### Deprecated
119 |
120 | - `Element#on(:event)` subscription to `_onEvent` is deprecated. Once you have changed params named `_on...` to `on_...` you can `require 'reactrb/new-event-name-convention.rb'` to avoid spurious react warning messages. (#153)
121 |
122 |
123 | ### Fixed
124 |
125 | - The `Element['#container'].render...` method generates a spurious react error (#154)
126 |
127 |
128 |
129 |
130 | ## [0.8.7] - 2016-07-08
131 |
132 |
133 | ### Fixed
134 |
135 | - Opal 0.10.x compatibility
136 |
137 |
138 | ## [0.8.6] - 2016-06-30
139 |
140 |
141 | ### Fixed
142 |
143 | - Method missing within a component was being reported as `incorrect const name` (#151)
144 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master"
3 | gem 'hyperloop-config', git: 'https://github.com/ruby-hyperloop/hyperloop-config.git', branch: 'edge'
4 | gem 'puma', '~> 3.11.0' # As of adding, version 3.12.0 isn't working so we are locking
5 | gemspec
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Yi-Cheng Chang (http://github.com/zetachang)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
31 |
32 | ## Hyper-React GEM is part of Hyperloop GEMS family
33 |
34 | Hyper-react GEM comes with the Hyperloop GEM.
35 |
36 | But if you want to install it separately, please install the [Hyper-component GEM](https://github.com/ruby-hyperloop/hyper-component).
37 |
38 | ## Community
39 |
40 | #### Getting Help
41 | Please **do not post** usage questions to GitHub Issues. For these types of questions use our [Gitter chatroom](https://gitter.im/ruby-hyperloop/chat) or [StackOverflow](http://stackoverflow.com/questions/tagged/hyperloop).
42 |
43 | #### Submitting Bugs and Enhancements
44 | [GitHub Issues](https://github.com/ruby-hyperloop/hyperloop/issues) is for suggesting enhancements and reporting bugs. Before submiting a bug make sure you do the following:
45 | * Check out our [contributing guide](https://github.com/ruby-hyperloop/hyperloop/blob/master/CONTRIBUTING.md) for info on our release cycle.
46 |
47 | ## License
48 |
49 | Hyperloop is released under the [MIT License](http://www.opensource.org/licenses/MIT).
50 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # require 'bundler'
2 | # Bundler.require
3 | # Bundler::GemHelper.install_tasks
4 | #
5 | # # Store the BUNDLE_GEMFILE env, since rake or rspec seems to clean it
6 | # # while invoking task.
7 | # ENV['REAL_BUNDLE_GEMFILE'] = ENV['BUNDLE_GEMFILE']
8 | #
9 | # require 'rspec/core/rake_task'
10 | # require 'opal/rspec/rake_task'
11 | #
12 | # RSpec::Core::RakeTask.new('ruby:rspec')
13 | #
14 | # task :test do
15 | # Rake::Task['ruby:rspec'].invoke
16 | # end
17 | #
18 | # require 'generators/reactive_ruby/test_app/test_app_generator'
19 | # desc "Generates a dummy app for testing"
20 | # task :test_app do
21 | # ReactiveRuby::TestAppGenerator.start
22 | # puts "Setting up test app database..."
23 | # system("bundle exec rake db:drop db:create db:migrate > #{File::NULL}")
24 | # end
25 | #
26 | # task :test_prepare do
27 | # system("./dciy_prepare.sh")
28 | # end
29 | #
30 | # task default: [ :test ]
31 |
32 | require "bundler/gem_tasks"
33 | require "rspec/core/rake_task"
34 |
35 | RSpec::Core::RakeTask.new(:spec)
36 |
37 | namespace :spec do
38 | task :prepare do
39 | sh %{bundle update}
40 | sh %{cd spec/test_app; bundle update}
41 | end
42 | end
43 |
44 | task :default => :spec
45 |
--------------------------------------------------------------------------------
/UPGRADING.md:
--------------------------------------------------------------------------------
1 | ## Upgrading to hyper-react from Reactrb
2 |
3 | Follow these steps to upgrade:
4 |
5 | 1. Replace `reactrb` with `hyper-react` both in **Gemfile** and any `require`s in your code.
6 | 2. To include the React.js source, the suggested way is to add `require 'react/react-source'` before `require 'hyper-react'`. This will use the copy of React.js source from `react-rails` gem.
7 |
8 | ## Upgrading to Reactrb
9 |
10 | The original gem `react.rb` was superceeded by `reactive-ruby`, which has had over 15,000 downloads. This name has now been superceeded by `reactrb` (see #144 for detailed discussion on why.)
11 |
12 | Going forward the name `reactrb` will be used consistently as the organization name, the gem name, the domain name, the twitter handle, etc.
13 |
14 | The first initial version of `reactrb` is 0.8.x.
15 |
16 | It is very unlikely that there will be any more releases of the `reactive-ruby` gem, so users should upgrade to `reactrb`.
17 |
18 | There are no syntactic or semantic breaking changes between `reactrb` v 0.8.x and
19 | previous versions, however the `reactrb` gem does *not* include the react-js source as previous versions did. This allows you to pick the react js source compatible with other gems and react js components you may be using.
20 |
21 | Follow these steps to upgrade:
22 |
23 | 1. Replace `reactive-ruby` with `reactrb` both in **Gemfile** and any `require`s in your code.
24 | 2. To include the React.js source, the suggested way is to add `require 'react/react-source'` before `require 'reactrb'`. This will use the copy of React.js source from `react-rails` gem.
25 |
--------------------------------------------------------------------------------
/component-name-lookup.md:
--------------------------------------------------------------------------------
1 | #### Notes on how component names are looked up
2 |
3 | Given:
4 |
5 | ```ruby
6 |
7 | class Blat < React::Component::Base
8 |
9 | render do
10 | Bar()
11 | Foo::Bar()
12 | end
13 |
14 | end
15 |
16 | class Bar < React::Component::Base
17 | end
18 |
19 | module Foo
20 |
21 | class Bar < React::Component::Base
22 |
23 | render do
24 | Blat()
25 | Baz()
26 | end
27 | end
28 |
29 | class Baz < React::Component::Base
30 | end
31 |
32 | end
33 | ```
34 |
35 | The problem is that method lookup is different than constant lookup. We can prove it by running this code:
36 |
37 | ```ruby
38 | def try_it(test, &block)
39 | puts "trying #{test}"
40 | result = yield
41 | puts "success#{': '+result.to_s if result}"
42 | rescue Exception => e
43 | puts "failed: #{e}"
44 | ensure
45 | puts "---------------------------------"
46 | end
47 |
48 | module Boom
49 |
50 | Bar = 12
51 |
52 | def self.Bar
53 | puts " Boom::Bar says hi"
54 | end
55 |
56 | class Baz
57 | def doit
58 | try_it("Bar()") { Bar() }
59 | try_it("Boom::Bar()") {Boom::Bar()}
60 | try_it("Bar") { Bar }
61 | try_it("Boom::Bar") { Boom::Bar }
62 | end
63 | end
64 | end
65 |
66 |
67 |
68 | Boom::Baz.new.doit
69 | ```
70 |
71 | which prints:
72 |
73 | ```text
74 | trying Bar()
75 | failed: Bar: undefined method `Bar' for #
76 | ---------------------------------
77 | trying Boom::Bar()
78 | Boom::Bar says hi
79 | success
80 | ---------------------------------
81 | trying Bar
82 | success: 12
83 | ---------------------------------
84 | trying Boom::Bar
85 | success: 12
86 | ---------------------------------
87 | ```
88 |
89 | [try-it](http://opalrb.org/try/?code:def%20try_it(test%2C%20%26block)%0A%20%20puts%20%22trying%20%23%7Btest%7D%22%0A%20%20result%20%3D%20yield%0A%20%20puts%20%22success%23%7B%27%3A%20%27%2Bresult.to_s%20if%20result%7D%22%0Arescue%20Exception%20%3D%3E%20e%0A%20%20puts%20%22failed%3A%20%23%7Be%7D%22%0Aensure%0A%20%20puts%20%22---------------------------------%22%0Aend%0A%0Amodule%20Boom%0A%20%20%0A%20%20Bar%20%3D%2012%0A%20%20%0A%20%20def%20self.Bar%0A%20%20%20%20puts%20%22%20%20%20Boom%3A%3ABar%20says%20hi%22%0A%20%20end%0A%0A%20%20class%20Baz%0A%20%20%20%20def%20doit%0A%20%20%20%20%20%20try_it(%22Bar()%22)%20%7B%20Bar()%20%7D%0A%20%20%20%20%20%20try_it(%22Boom%3A%3ABar()%22)%20%7BBoom%3A%3ABar()%7D%0A%20%20%20%20%20%20try_it(%22Bar%22)%20%7B%20Bar%20%7D%0A%20%20%20%20%20%20try_it(%22Boom%3A%3ABar%22)%20%7B%20Boom%3A%3ABar%20%7D%0A%20%20%20%20end%0A%20%20end%0Aend%0A%20%20%0A%0A%0ABoom%3A%3ABaz.new.doit)
90 |
91 |
92 | What we need to do is:
93 |
94 | 1. when defining a component class `Foo`, also define in the same scope that Foo is being defined a method `self.Foo` that will accept Foo's params and child block, and render it.
95 |
96 | 2. As long as a name is qualified with at least one scope (i.e. `ModName::Foo()`) everything will work out, but if we say just `Foo()` then the only way I believe out of this is to handle it via method_missing, and let method_missing do a const_get on the method_name (which will return the class) and then render that component.
97 |
98 | #### details
99 |
100 | To define `self.Foo` in the same scope level as the class `Foo`, we need code like this:
101 |
102 | ```ruby
103 | def register_component_dsl_method(component)
104 | split_name = component.name && component.name.split('::')
105 | return unless split_name && split_name.length > 2
106 | component_name = split_name.last
107 | parent = split_name.inject([Module]) { |nesting, next_const| nesting + [nesting.last.const_get(next_const)] }[-2]
108 | class << parent
109 | define_method component_name do |*args, &block|
110 | React::RenderingContext.render(name, *args, &block)
111 | end
112 | define_method "#{component_name}_as_node" do |*args, &block|
113 | React::Component.deprecation_warning("..._as_node is deprecated. Render component and then use the .node method instead")
114 | send(component_name, *args, &block).node
115 | end
116 | end
117 | end
118 |
119 | module React
120 | module Component
121 | def self.included(base)
122 | ...
123 | register_component_dsl_method(base.name)
124 | end
125 | end
126 | end
127 | ```
128 |
129 | The component's method_missing function will look like this:
130 |
131 | ```ruby
132 | def method_missing(name, *args, &block)
133 | if name =~ /_as_node$/
134 | React::Component.deprecation_warning("..._as_node is deprecated. Render component and then use the .node method instead")
135 | method_missing(name.gsub(/_as_node$/,""), *args, &block).node
136 | else
137 | component = const_get name if defined? name
138 | React::RenderingContext.render(nil, component, *args, &block)
139 | end
140 | end
141 | ```
142 |
143 | ### other related issues
144 |
145 | The Kernel#p method conflicts with the
tag. However the p method can be invoked on any object so we are going to go ahead and use it, and deprecate the para method.
146 |
--------------------------------------------------------------------------------
/dciy.toml:
--------------------------------------------------------------------------------
1 | [dciy.commands]
2 | prepare = ["rake spec:prepare"]
3 | cibuild = ["bundle exec rake"]
4 |
--------------------------------------------------------------------------------
/hyper-react.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path('../lib/', __FILE__)
3 | require 'reactive-ruby/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'hyper-react'
7 | spec.version = React::VERSION
8 |
9 | spec.authors = ['David Chang', 'Adam Jahn', 'Mitch VanDuyn', 'Jan Biedermann']
10 | spec.email = ['mitch@catprint.com', 'jan@kursator.com']
11 | spec.homepage = 'http://ruby-hyperloop.org'
12 | spec.summary = 'Opal Ruby wrapper of React.js library.'
13 | spec.license = 'MIT'
14 | spec.description = 'Write React UI components in pure Ruby.'
15 | # spec.metadata = {
16 | # "homepage_uri" => 'http://ruby-hyperloop.org',
17 | # "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component'
18 | # }
19 |
20 | spec.files = `git ls-files`.split("\n").reject { |f| f.match(%r{^(gemfiles|spec)/}) }
21 | spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22 | spec.require_paths = ['lib']
23 |
24 | spec.add_dependency 'hyper-store', React::VERSION
25 | spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
26 | spec.add_dependency 'opal-activesupport', '~> 0.3.1'
27 | spec.add_dependency 'hyperloop-config', React::VERSION
28 | spec.add_dependency 'mini_racer', '~> 0.1.15'
29 | # https://github.com/discourse/mini_racer/issues/92
30 | spec.add_dependency 'libv8', '~> 6.3.0'
31 | spec.add_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
32 |
33 | spec.add_development_dependency 'bundler', '~> 1.16.0'
34 | spec.add_development_dependency 'chromedriver-helper'
35 | spec.add_development_dependency 'hyper-spec', React::VERSION
36 | spec.add_development_dependency 'jquery-rails'
37 | spec.add_development_dependency 'listen'
38 | spec.add_development_dependency 'mime-types'
39 | spec.add_development_dependency 'nokogiri'
40 | spec.add_development_dependency 'opal-jquery'
41 | spec.add_development_dependency 'opal-rails', '~> 0.9.4'
42 | spec.add_development_dependency 'opal-rspec'
43 | spec.add_development_dependency 'puma'
44 | spec.add_development_dependency 'pry'
45 | spec.add_development_dependency 'rails', '>= 4.0.0'
46 | spec.add_development_dependency 'rails-controller-testing'
47 | spec.add_development_dependency 'rake'
48 | spec.add_development_dependency 'rspec-rails'
49 | spec.add_development_dependency 'rubocop', '~> 0.51.0'
50 | spec.add_development_dependency 'sqlite3'
51 | spec.add_development_dependency 'timecop', '~> 0.8.1'
52 | end
53 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/components.rb:
--------------------------------------------------------------------------------
1 | require 'opal'
2 | require 'hyper-react'
3 | require_tree './components'
4 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/server_rendering.js:
--------------------------------------------------------------------------------
1 | //= require 'react-server'
2 | //= require 'react_ujs'
3 | //= require 'components'
4 |
5 | Opal.load('components')
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/assets/javascripts/test_application.rb:
--------------------------------------------------------------------------------
1 | require 'components'
2 | require 'react_ujs'
3 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/boot.rb.erb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | gemfile = File.expand_path("<%= gemfile_path %>", __FILE__)
3 |
4 | ENV['BUNDLE_GEMFILE'] = gemfile
5 | require 'bundler'
6 | Bundler.setup
7 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | APP_PATH = File.expand_path('../../config/application', __FILE__)
4 | require File.expand_path('../../config/boot', __FILE__)
5 | require 'rails/commands'
6 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/test_application.rb.erb:
--------------------------------------------------------------------------------
1 | <% if defined? application_definition %>
2 | require 'rails/all'
3 | require File.expand_path('../boot', __FILE__)
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups(assets: %w(development test)))
8 |
9 | require 'opal-rails'
10 | require 'hyper-react'
11 |
12 | <%= application_definition %>
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/views/components/hello_world.rb:
--------------------------------------------------------------------------------
1 | module Components
2 | class HelloWorld
3 | include React::Component
4 |
5 | def render
6 | div do
7 | "Hello, World!".span
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/views/components/todo.rb:
--------------------------------------------------------------------------------
1 | module Components
2 | class Todo
3 | include React::Component
4 | export_component
5 |
6 | params do
7 | requires :todo
8 | end
9 |
10 | def render
11 | li { "#{params[:todo]}" }
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/lib/generators/reactive_ruby/test_app/templates/views/layouts/test_layout.html.erb
--------------------------------------------------------------------------------
/lib/generators/reactive_ruby/test_app/test_app_generator.rb:
--------------------------------------------------------------------------------
1 | require 'rails/generators/rails/app/app_generator'
2 |
3 | module ReactiveRuby
4 | class TestAppGenerator < ::Rails::Generators::Base
5 | def self.source_paths
6 | paths = self.superclass.source_paths
7 | paths << File.expand_path('../templates', __FILE__)
8 | paths.flatten
9 | end
10 |
11 | def remove_existing_app
12 | remove_dir(test_app_path) if File.directory?(test_app_path)
13 | end
14 |
15 | def generate_test_app
16 | opts = options.dup
17 | opts[:database] = 'sqlite3' if opts[:database].blank?
18 | opts[:force] = true
19 | opts[:skip_bundle] = true
20 |
21 | puts "Generating Test Rails Application..."
22 | invoke ::Rails::Generators::AppGenerator,
23 | [ File.expand_path(test_app_path, destination_root) ], opts
24 | end
25 |
26 | def configure_test_app
27 | template 'boot.rb.erb', "#{test_app_path}/config/boot.rb", force: true
28 | template 'test_application.rb.erb', "#{test_app_path}/config/application.rb", force: true
29 | template 'assets/javascripts/test_application.rb',
30 | "#{test_app_path}/app/assets/javascripts/application.rb", force: true
31 | template 'assets/javascripts/server_rendering.js',
32 | "#{test_app_path}/app/assets/javascripts/server_rendering.js", force: true
33 | template 'assets/javascripts/components.rb',
34 | "#{test_app_path}/app/views/components.rb", force: true
35 | template 'views/components/hello_world.rb',
36 | "#{test_app_path}/app/views/components/hello_world.rb", force: true
37 | template 'views/components/todo.rb',
38 | "#{test_app_path}/app/views/components/todo.rb", force: true
39 | template 'views/layouts/test_layout.html.erb',
40 | "#{test_app_path}/app/views/layouts/test_layout.html.erb", force: true
41 | template 'views/layouts/test_layout.html.erb',
42 | "#{test_app_path}/app/views/layouts/explicit_layout.html.erb", force: true
43 | end
44 |
45 | def clean_superfluous_files
46 | inside test_app_path do
47 | remove_file '.gitignore'
48 | remove_file 'doc'
49 | remove_file 'Gemfile'
50 | remove_file 'lib/tasks'
51 | remove_file 'app/assets/images/rails.png'
52 | remove_file 'app/assets/javascripts/application.js'
53 | remove_file 'public/index.html'
54 | remove_file 'public/robots.txt'
55 | remove_file 'README.rdoc'
56 | remove_file 'test'
57 | remove_file 'vendor'
58 | remove_file 'spec'
59 | end
60 | end
61 |
62 | def configure_opal_rspec
63 | inject_into_file "#{test_app_path}/config/application.rb",
64 | after: /class Application < Rails::Application/, verbose: true do
65 | %Q[
66 | config.opal.method_missing = true
67 | config.opal.optimized_operators = true
68 | config.opal.arity_check = false
69 | config.opal.const_missing = true
70 | config.opal.dynamic_require_severity = :ignore
71 | config.opal.enable_specs = true
72 | config.opal.spec_location = 'spec-opal'
73 | config.hyperloop.auto_config = false
74 |
75 | config.react.server_renderer_options = {
76 | files: ["server_rendering.js"]
77 | }
78 | config.react.server_renderer_directories = ["/app/assets/javascripts"]
79 | ]
80 | end
81 | end
82 |
83 | protected
84 |
85 | def application_definition
86 | @application_definition ||= begin
87 | test_application_contents
88 | end
89 | end
90 | alias :store_application_definition! :application_definition
91 |
92 | private
93 |
94 | def test_app_path
95 | 'spec/test_app'
96 | end
97 |
98 | def test_application_path
99 | File.expand_path("#{test_app_path}/config/application.rb",
100 | destination_root)
101 | end
102 |
103 | def test_application_contents
104 | return unless File.exists?(test_application_path) && !options[:pretend]
105 | contents = File.read(test_application_path)
106 | contents[(contents.index("module #{module_name}"))..-1]
107 | end
108 |
109 | def module_name
110 | 'TestApp'
111 | end
112 |
113 | def gemfile_path
114 | '../../../../Gemfile'
115 | end
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/lib/hyper-react.rb:
--------------------------------------------------------------------------------
1 | require 'hyperloop-config'
2 | Hyperloop.import 'hyper-store'
3 | Hyperloop.js_import 'react/react-source-browser', client_only: true, defines: ['ReactDOM', 'React']
4 | Hyperloop.js_import 'react/react-source-server', server_only: true, defines: 'React'
5 | Hyperloop.import 'browser/delay', client_only: true
6 | Hyperloop.import 'hyper-react'
7 | Hyperloop.js_import 'react_ujs', defines: 'ReactRailsUJS'
8 |
9 | if RUBY_ENGINE == 'opal'
10 | module Hyperloop
11 | class Component
12 | end
13 | end
14 | require 'native'
15 | require 'react/observable'
16 | require 'react/validator'
17 | require 'react/element'
18 | require 'react/api'
19 | require 'react/component'
20 | require 'react/component/dsl_instance_methods'
21 | require 'react/component/should_component_update'
22 | require 'react/component/tags'
23 | require 'react/component/base'
24 | require 'react/event'
25 | require 'react/rendering_context'
26 | require 'react/state'
27 | require 'react/object'
28 | require 'react/to_key'
29 | require 'react/ext/opal-jquery/element'
30 | require 'reactive-ruby/isomorphic_helpers'
31 | require 'react/top_level'
32 | require 'react/top_level_render'
33 | require 'rails-helpers/top_level_rails_component'
34 | require 'reactive-ruby/version'
35 | module Hyperloop
36 | class Component
37 | def self.inherited(child)
38 | child.include(Mixin)
39 | end
40 | end
41 | end
42 | React::Component.deprecation_warning(
43 | 'component.rb',
44 | "Requiring 'hyper-react' is deprecated. Use gem 'hyper-component', and require 'hyper-component' instead."
45 | ) unless defined? Hyperloop::Component::VERSION
46 | else
47 | require 'opal'
48 |
49 | require 'hyper-store'
50 | require 'opal-activesupport'
51 | require 'reactive-ruby/version'
52 | require 'reactive-ruby/rails' if defined?(Rails)
53 | require 'reactive-ruby/isomorphic_helpers'
54 | require 'reactive-ruby/serializers'
55 |
56 | Opal.append_path File.expand_path('../', __FILE__).untaint
57 | require 'react/react-source'
58 | end
59 |
--------------------------------------------------------------------------------
/lib/rails-helpers/top_level_rails_component.rb:
--------------------------------------------------------------------------------
1 | module React
2 | class TopLevelRailsComponent
3 | include Hyperloop::Component::Mixin
4 |
5 | def self.search_path
6 | @search_path ||= [Object]
7 | end
8 |
9 | export_component
10 |
11 | param :component_name
12 | param :controller
13 | param :render_params
14 |
15 | backtrace :off
16 |
17 | def render
18 | paths_searched = []
19 | component = nil
20 | if params.component_name.start_with?('::')
21 | # if absolute path of component is given, look it up and fail if not found
22 | paths_searched << params.component_name
23 | component = begin
24 | Object.const_get(params.component_name)
25 | rescue NameError
26 | nil
27 | end
28 | else
29 | # if relative path is given, look it up like this
30 | # 1) we check each path + controller-name + component-name
31 | # 2) if we can't find it there we check each path + component-name
32 | # if we can't find it we just try const_get
33 | # so (assuming controller name is Home)
34 | # ::Foo::Bar will only resolve to some component named ::Foo::Bar
35 | # but Foo::Bar will check (in this order) ::Home::Foo::Bar, ::Components::Home::Foo::Bar, ::Foo::Bar, ::Components::Foo::Bar
36 | self.class.search_path.each do |scope|
37 | paths_searched << "#{scope.name}::#{params.controller}::#{params.component_name}"
38 | component = begin
39 | scope.const_get(params.controller, false).const_get(params.component_name, false)
40 | rescue NameError
41 | nil
42 | end
43 | break if component != nil
44 | end
45 | unless component
46 | self.class.search_path.each do |scope|
47 | paths_searched << "#{scope.name}::#{params.component_name}"
48 | component = begin
49 | scope.const_get(params.component_name, false)
50 | rescue NameError
51 | nil
52 | end
53 | break if component != nil
54 | end
55 | end
56 | end
57 | return React::RenderingContext.render(component, params.render_params) if component && component.method_defined?(:render)
58 | raise "Could not find component class '#{params.component_name}' for params.controller '#{params.controller}' in any component directory. Tried [#{paths_searched.join(", ")}]"
59 | end
60 | end
61 | end
62 |
63 | class Module
64 | def add_to_react_search_path(replace_search_path = nil)
65 | if replace_search_path
66 | React::TopLevelRailsComponent.search_path = [self]
67 | elsif !React::TopLevelRailsComponent.search_path.include? self
68 | React::TopLevelRailsComponent.search_path << self
69 | end
70 | end
71 | end
72 |
73 | module Components
74 | add_to_react_search_path
75 | end
76 |
--------------------------------------------------------------------------------
/lib/react/callbacks.rb:
--------------------------------------------------------------------------------
1 | require 'hyperloop-config'
2 |
3 | module React
4 | module Callbacks
5 | def self.included(base)
6 | base.extend(ClassMethods)
7 | end
8 |
9 | def run_callback(name, *args)
10 | self.class.callbacks_for(name).each do |callback|
11 | if callback.is_a?(Proc)
12 | instance_exec(*args, &callback)
13 | else
14 | send(callback, *args)
15 | end
16 | end
17 | end
18 |
19 | module ClassMethods
20 | def define_callback(callback_name)
21 | wrapper_name = "_#{callback_name}_callbacks"
22 | define_singleton_method(wrapper_name) do
23 | Hyperloop::Context.set_var(self, "@#{wrapper_name}", force: true) { [] }
24 | end
25 | define_singleton_method(callback_name) do |*args, &block|
26 | send(wrapper_name).concat(args)
27 | send(wrapper_name).push(block) if block_given?
28 | end
29 | end
30 |
31 | def callbacks_for(callback_name)
32 | wrapper_name = "_#{callback_name}_callbacks"
33 | if superclass.respond_to? :callbacks_for
34 | superclass.callbacks_for(callback_name)
35 | else
36 | []
37 | end + send(wrapper_name)
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/react/children.rb:
--------------------------------------------------------------------------------
1 | module React
2 | class Children
3 | include Enumerable
4 |
5 | def initialize(children)
6 | @children = children
7 | end
8 |
9 | def render
10 | each(&:render)
11 | end
12 |
13 | def to_proc
14 | -> () { render }
15 | end
16 |
17 | def each(&block)
18 | return to_enum(__callee__) { length } unless block_given?
19 | return [] unless length > 0
20 | collection = []
21 | %x{
22 | React.Children.forEach(#{@children}, function(context){
23 | #{
24 | element = React::Element.new(`context`)
25 | block.call(element)
26 | collection << element
27 | }
28 | })
29 | }
30 | collection
31 | end
32 |
33 | def length
34 | @length ||= `React.Children.count(#{@children})`
35 | end
36 | alias_method :size, :length
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/react/component/api.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Component
3 | module API
4 | def dom_node
5 | `ReactDOM.findDOMNode(#{self}.native)` # react >= v0.15.0
6 | end
7 |
8 | def mounted?
9 | `(#{self}.is_mounted === undefined) ? false : #{self}.is_mounted`
10 | end
11 |
12 | def force_update!
13 | `#{self}.native.forceUpdate()`
14 | self
15 | end
16 |
17 | def set_props(prop, &block)
18 | raise "set_props: setProps() is no longer supported by react"
19 | end
20 | alias :set_props! :set_props
21 |
22 | def set_state(state, &block)
23 | set_or_replace_state_or_prop(state, 'setState', &block)
24 | end
25 |
26 | def set_state!(state, &block)
27 | set_or_replace_state_or_prop(state, 'setState', &block)
28 | `#{self}.native.forceUpdate()`
29 | end
30 |
31 | private
32 |
33 | def set_or_replace_state_or_prop(state_or_prop, method, &block)
34 | raise "No native ReactComponent associated" unless @native
35 | `var state_prop_n = #{state_or_prop.shallow_to_n}`
36 | # the state object is initalized when the ruby component is instantiated
37 | # this is detected by self.native.__opalInstanceInitializedState
38 | # which is set in the native component constructor in react/api.rb
39 | # the setState update callback is not called when initalizing initial state
40 | if block
41 | %x{
42 | if (#{@native}.__opalInstanceInitializedState === true) {
43 | #{@native}[method](state_prop_n, function(){
44 | block.$call();
45 | });
46 | } else {
47 | for (var sp in state_prop_n) {
48 | if (state_prop_n.hasOwnProperty(sp)) {
49 | #{@native}.state[sp] = state_prop_n[sp];
50 | }
51 | }
52 | }
53 | }
54 | else
55 | %x{
56 | if (#{@native}.__opalInstanceInitializedState === true) {
57 | #{@native}[method](state_prop_n);
58 | } else {
59 | for (var sp in state_prop_n) {
60 | if (state_prop_n.hasOwnProperty(sp)) {
61 | #{@native}.state[sp] = state_prop_n[sp];
62 | }
63 | }
64 | }
65 | }
66 | end
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/lib/react/component/base.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Component
3 | class Base
4 | def self.inherited(child)
5 | # note this is turned off during old style testing: See the spec_helper
6 | unless child.to_s == "React::Component::HyperTestDummy"
7 | React::Component.deprecation_warning child, "The class name React::Component::Base has been deprecated. Use Hyperloop::Component instead."
8 | end
9 | child.include(ComponentNoNotice)
10 | end
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/react/component/dsl_instance_methods.rb:
--------------------------------------------------------------------------------
1 | require "react/children"
2 |
3 | module React
4 | module Component
5 | module DslInstanceMethods
6 | def children
7 | Children.new(`#{@native}.props.children`)
8 | end
9 |
10 | def params
11 | @params ||= self.class.props_wrapper.new(self)
12 | end
13 |
14 | def props
15 | Hash.new(`#{@native}.props`)
16 | end
17 |
18 | def refs
19 | Hash.new(`#{@native}.refs`)
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/react/component/params.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Component
3 | module Params
4 | end
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/react/component/props_wrapper.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Component
3 |
4 | class PropsWrapper
5 | attr_reader :component
6 |
7 | def self.define_param(name, param_type)
8 | if param_type == Observable
9 | define_method("#{name}") do
10 | value_for(name)
11 | end
12 | define_method("#{name}!") do |*args|
13 | current_value = value_for(name)
14 | if args.count > 0
15 | props[name].call args[0]
16 | current_value
17 | else
18 | # rescue in case we in middle of render... What happens during a
19 | # render that causes exception?
20 | # Where does `dont_update_state` come from?
21 | props[name].call current_value unless @dont_update_state rescue nil
22 | props[name]
23 | end
24 | end
25 | elsif param_type == Proc
26 | define_method("#{name}") do |*args, &block|
27 | props[name].call(*args, &block) if props[name]
28 | end
29 | else
30 | define_method("#{name}") do
31 | fetch_from_cache(name) do
32 | if param_type.respond_to? :_react_param_conversion
33 | param_type._react_param_conversion props[name], nil
34 | elsif param_type.is_a?(Array) &&
35 | param_type[0].respond_to?(:_react_param_conversion)
36 | props[name].collect do |param|
37 | param_type[0]._react_param_conversion param, nil
38 | end
39 | else
40 | props[name]
41 | end
42 | end
43 | end
44 | end
45 | end
46 |
47 | def self.define_all_others(name)
48 | define_method("#{name}") do
49 | @_all_others_cache ||= yield(props)
50 | end
51 | end
52 |
53 |
54 | def initialize(component)
55 | @component = component
56 | end
57 |
58 | def [](prop)
59 | props[prop]
60 | end
61 |
62 |
63 | def _reset_all_others_cache
64 | @_all_others_cache = nil
65 | end
66 |
67 | private
68 |
69 | def fetch_from_cache(name)
70 | last, value = cache[name]
71 | return value if last.equal?(props[name])
72 | yield.tap do |value|
73 | cache[name] = [props[name], value]
74 | end
75 | end
76 |
77 | def cache
78 | @cache ||= Hash.new { |h, k| h[k] = [] }
79 | end
80 |
81 | def props
82 | component.props
83 | end
84 |
85 | def value_for(name)
86 | self[name].instance_variable_get("@value") if self[name]
87 | end
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/lib/react/component/should_component_update.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Component
3 | #
4 | # React assumes all components should update, unless a component explicitly overrides
5 | # the shouldComponentUpdate method. Reactrb does an explicit check doing a shallow
6 | # compare of params, and using a timestamp to determine if state has changed.
7 |
8 | # If needed components can provide their own #needs_update? method which will be
9 | # passed the next params and state opal hashes.
10 |
11 | # Attached to these hashes is a #changed? method that returns whether the hash contains
12 | # changes as calculated by the base mechanism. This way implementations of #needs_update?
13 | # can use the base comparison mechanism as needed.
14 |
15 | # For example
16 | # def needs_update?(next_params, next_state)
17 | # # use a special comparison method
18 | # return false if next_state.changed? || next_params.changed?
19 | # # do some other special checks
20 | # end
21 |
22 | # Note that beginning in 0.9 we will use standard ruby compare on all params further reducing
23 | # the need for needs_update?
24 | #
25 | module ShouldComponentUpdate
26 | def should_component_update?(next_props, next_state)
27 | State.set_state_context_to(self, false) do
28 | # rubocop:disable Style/DoubleNegation # we must return true/false to js land
29 | if respond_to?(:needs_update?)
30 | !!call_needs_update(next_props, next_state)
31 | else
32 | (props_changed?(next_props) || native_state_changed?(next_state))
33 | end
34 | # rubocop:enable Style/DoubleNegation
35 | end
36 | end
37 |
38 | # create opal hashes for next params and state, and attach
39 | # the changed? method to each hash
40 |
41 | def call_needs_update(next_params, next_state)
42 | component = self
43 | next_params.define_singleton_method(:changed?) do
44 | component.props_changed?(self)
45 | end
46 | next_state.define_singleton_method(:changed?) do
47 | component.native_state_changed?(next_state)
48 | end
49 | needs_update?(next_params, next_state)
50 | end
51 |
52 | # Whenever state changes, reactrb updates a timestamp on the state object.
53 | # We can rapidly check for state changes comparing the incoming state time_stamp
54 | # with the current time stamp.
55 |
56 | # we receive a Opal Ruby Hash here, always, so the Hash is either empty or filled
57 | # Hash is converted to native object
58 | # if the Hash was empty, the Object has no keys
59 |
60 | # Different versions of react treat empty state differently, so we first
61 | # convert anything that looks like an empty state to "false" for consistency.
62 |
63 | # Then we test if one state is empty and the other is not, then we return false.
64 | # Then we test if both states are empty we return true.
65 | # If either state does not have a time stamp then we have to assume a change.
66 | # Otherwise we check time stamps
67 |
68 | # rubocop:disable Metrics/MethodLength # for effeciency we want this to be one method
69 | def native_state_changed?(next_state_hash)
70 | # next_state = next_state_hash.to_n
71 | # %x{
72 | # var current_state = #{@native}.state
73 | # var normalized_next_state =
74 | # !next_state || Object.keys(next_state).length === 0 ? false : next_state
75 | # var normalized_current_state =
76 | # !current_state || Object.keys(current_state).length === 0 ? false : current_state
77 | # if (!normalized_current_state != !normalized_next_state) return(true)
78 | # if (!normalized_current_state && !normalized_next_state) return(false)
79 | # if (!normalized_current_state['***_state_updated_at-***'] &&
80 | # !normalized_next_state['***_state_updated_at-***']) return(false)
81 | # if (!normalized_current_state['***_state_updated_at-***'] ||
82 | # !normalized_next_state['***_state_updated_at-***']) return(true)
83 | # return (normalized_current_state['***_state_updated_at-***'] !=
84 | # normalized_next_state['***_state_updated_at-***'])
85 | # }
86 | state_hash = Hash.new(`#{@native}.state`)
87 | next_state_hash != state_hash
88 | end
89 | # rubocop:enable Metrics/MethodLength
90 |
91 | # Do a shallow compare on the two hashes. Starting in 0.9 we will do a deep compare. ???
92 |
93 | def props_changed?(next_props)
94 | props = Hash.new(`#{@native}.props`)
95 | next_props != props
96 | end
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/lib/react/component/tags.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Component
3 | # contains the name of all HTML tags, and the mechanism to register a component
4 | # class as a new tag
5 | module Tags
6 | HTML_TAGS = %w(a abbr address area article aside audio b base bdi bdo big blockquote body br
7 | button canvas caption cite code col colgroup data datalist dd del details dfn
8 | dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5
9 | h6 head header hr html i iframe img input ins kbd keygen label legend li link
10 | main map mark menu menuitem meta meter nav noscript object ol optgroup option
11 | output p param picture pre progress q rp rt ruby s samp script section select
12 | small source span strong style sub summary sup table tbody td textarea tfoot th
13 | thead time title tr track u ul var video wbr) +
14 | # The SVG Tags
15 | %w(circle clipPath defs ellipse g line linearGradient mask path pattern polygon polyline
16 | radialGradient rect stop svg text tspan)
17 |
18 | # the present method is retained as a legacy behavior
19 | def present(component, *params, &children)
20 | React::RenderingContext.render(component, *params, &children)
21 | end
22 |
23 | # define each predefined tag (upcase) as an instance method and a constant
24 | # deprecated: define each predefined tag (downcase) as the alias of the instance method
25 |
26 | HTML_TAGS.each do |tag|
27 |
28 | define_method(tag.upcase) do |*params, &children|
29 | React::RenderingContext.render(tag, *params, &children)
30 | end
31 |
32 | const_set tag.upcase, tag
33 |
34 | # deprecated: remove
35 | if tag == 'p'
36 | define_method(tag) do |*params, &children|
37 | if children || params.count == 0 || (params.count == 1 && params.first.is_a?(Hash))
38 | React::RenderingContext.render(tag, *params, &children)
39 | else
40 | Kernel.p(*params)
41 | end
42 | end
43 | else
44 | alias_method tag, tag.upcase
45 | end
46 | # end of deprecated code
47 | end
48 |
49 | # this is used for haml style (i.e. DIV.foo.bar) class tags which is deprecated
50 | def self.html_tag_class_for(tag)
51 | downcased_tag = tag.downcase
52 | if tag =~ /[A-Z]+/ && HTML_TAGS.include?(downcased_tag)
53 | Object.const_set tag, React.create_element(downcased_tag)
54 | end
55 | end
56 |
57 | # use method_missing to look up component names in the form of "Foo(..)"
58 | # where there is no preceeding scope.
59 |
60 | def method_missing(name, *params, &children)
61 | component = find_component(name)
62 | return React::RenderingContext.render(component, *params, &children) if component
63 | Object.method_missing(name, *params, &children)
64 | end
65 |
66 | # install methods with the same name as the component in the parent class/module
67 | # thus component names in the form Foo::Bar(...) will work
68 |
69 | class << self
70 | def included(component)
71 | name, parent = find_name_and_parent(component)
72 | tag_names_module = Module.new do
73 | define_method name do |*params, &children|
74 | React::RenderingContext.render(component, *params, &children)
75 | end
76 | # handle deprecated _as_node style
77 | define_method "#{name}_as_node" do |*params, &children|
78 | React::RenderingContext.build_only(component, *params, &children)
79 | end
80 | end
81 | parent.extend(tag_names_module)
82 | end
83 |
84 | private
85 |
86 | def find_name_and_parent(component)
87 | split_name = component.name && component.name.split('::')
88 | if split_name && split_name.length > 1
89 | [split_name.last, split_name.inject([Module]) { |a, e| a + [a.last.const_get(e)] }[-2]]
90 | end
91 | end
92 | end
93 |
94 | private
95 |
96 | def find_component(name)
97 | component = lookup_const(name)
98 | if component && !component.method_defined?(:render)
99 | raise "#{name} does not appear to be a react component."
100 | end
101 | component
102 | end
103 |
104 | def lookup_const(name)
105 | return nil unless name =~ /^[A-Z]/
106 | #html_tag = React::Component::Tags.html_tag_class(name)
107 | #return html_tag if html_tag
108 | scopes = self.class.name.to_s.split('::').inject([Module]) do |nesting, next_const|
109 | nesting + [nesting.last.const_get(next_const)]
110 | end.reverse
111 | scope = scopes.detect { |s| s.const_defined?(name) }
112 | scope.const_get(name) if scope
113 | end
114 | end
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/lib/react/config.rb:
--------------------------------------------------------------------------------
1 | if RUBY_ENGINE != 'opal'
2 | module Hyperloop
3 | define_setting :prerendering, :off
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/lib/react/element.rb:
--------------------------------------------------------------------------------
1 | require 'react/ext/string'
2 |
3 | module React
4 | #
5 | # Wraps the React Native element class
6 | #
7 | # adds the #on method to add event handlers to the element
8 | #
9 | # adds the #render method to place elements in the DOM and
10 | # #delete (alias/deprecated #as_node) method to remove elements from the DOM
11 | #
12 | # handles the haml style class notation so that
13 | # div.bar.blat becomes div(class: "bar blat")
14 | # by using method missing
15 | #
16 | class Element
17 | include Native
18 |
19 | alias_native :element_type, :type
20 | alias_native :props, :props
21 |
22 | attr_reader :type
23 | attr_reader :properties
24 | attr_reader :block
25 |
26 | attr_accessor :waiting_on_resources
27 |
28 | def initialize(native_element, type = nil, properties = {}, block = nil)
29 | @type = type
30 | @properties = (`typeof #{properties} === 'undefined'` ? nil : properties) || {}
31 | @block = block
32 | @native = native_element
33 | end
34 |
35 | # Attach event handlers.
36 |
37 | def on(*event_names, &block)
38 | event_names.each { |event_name| merge_event_prop!(event_name, &block) }
39 | @native = `React.cloneElement(#{@native}, #{@properties.shallow_to_n})`
40 | self
41 | end
42 |
43 | # Render element into DOM in the current rendering context.
44 | # Used for elements that are not yet in DOM, i.e. they are provided as children
45 | # or they have been explicitly removed from the rendering context using the delete method.
46 |
47 | def render(props = {}, &new_block)
48 | if props.empty?
49 | React::RenderingContext.render(self)
50 | else
51 | props = API.convert_props(props)
52 | React::RenderingContext.render(
53 | Element.new(`React.cloneElement(#{@native}, #{props.shallow_to_n})`,
54 | type, @properties.merge(props), block),
55 | )
56 | end
57 | end
58 |
59 | # Delete (remove) element from rendering context, the element may later be added back in
60 | # using the render method.
61 |
62 | def delete
63 | React::RenderingContext.delete(self)
64 | end
65 | # Deprecated version of delete method
66 | alias as_node delete
67 |
68 | # Any other method applied to an element will be treated as class name (haml style) thus
69 | # div.foo.bar(id: :fred) is the same as saying div(class: "foo bar", id: :fred)
70 | #
71 | # single underscores become dashes, and double underscores become a single underscore
72 | #
73 | # params may be provide to each class (but typically only to the last for easy reading.)
74 |
75 | def method_missing(class_name, args = {}, &new_block)
76 | return dup.render.method_missing(class_name, args, &new_block) unless rendered?
77 | React::RenderingContext.replace(
78 | self,
79 | RenderingContext.build do
80 | RenderingContext.render(type, build_new_properties(class_name, args), &new_block)
81 | end
82 | )
83 | end
84 |
85 | def rendered?
86 | React::RenderingContext.rendered? self
87 | end
88 |
89 | def self.haml_class_name(class_name)
90 | class_name.gsub(/__|_/, '__' => '_', '_' => '-')
91 | end
92 |
93 | private
94 |
95 | def build_new_properties(class_name, args)
96 | class_name = self.class.haml_class_name(class_name)
97 | new_props = @properties.dup
98 | new_props[:className] = "\
99 | #{class_name} #{new_props[:className]} #{args.delete(:class)} #{args.delete(:className)}\
100 | ".split(' ').uniq.join(' ')
101 | new_props.merge! args
102 | end
103 |
104 | # built in events, events going to native components, and events going to reactrb
105 |
106 | # built in events will have their event param translated to the Event wrapper
107 | # and the name will camelcased and have on prefixed, so :click becomes onClick.
108 | #
109 | # events emitting from native components are assumed to have the same camel case and
110 | # on prefixed.
111 | #
112 | # events emitting from reactrb components will just have on_ prefixed. So
113 | # :play_button_pushed attaches to the :on_play_button_pushed param
114 | #
115 | # in all cases the default name convention can be overriden by wrapping in <...> brackets.
116 | # So on("") will attach to the "MyEvent" param.
117 |
118 | def merge_event_prop!(event_name, &block)
119 | if event_name =~ /^<(.+)>$/
120 | merge_component_event_prop! event_name.gsub(/^<(.+)>$/, '\1'), &block
121 | elsif React::Event::BUILT_IN_EVENTS.include?(name = "on#{event_name.event_camelize}")
122 | merge_built_in_event_prop! name, &block
123 | elsif @type.instance_variable_get('@native_import')
124 | merge_component_event_prop! name, &block
125 | else
126 | merge_component_event_prop! "on_#{event_name}", &block
127 | end
128 | end
129 |
130 | def merge_built_in_event_prop!(prop_name)
131 | @properties.merge!(
132 | prop_name => %x{
133 | function(){
134 | var react_event = arguments[0];
135 | var all_args;
136 | var other_args;
137 | if (arguments.length > 1) {
138 | all_args = Array.prototype.slice.call(arguments);
139 | other_args = all_args.slice(1, arguments.length);
140 | return #{yield(React::Event.new(`react_event`), *(`other_args`))};
141 | } else {
142 | return #{yield(React::Event.new(`react_event`))};
143 | }
144 | }
145 | }
146 | )
147 | end
148 |
149 | def merge_component_event_prop!(prop_name)
150 | @properties.merge!(
151 | prop_name => %x{
152 | function(){
153 | return #{yield(*Array(`arguments`))}
154 | }
155 | }
156 | )
157 | end
158 | end
159 | end
160 |
--------------------------------------------------------------------------------
/lib/react/event.rb:
--------------------------------------------------------------------------------
1 | module React
2 | class Event
3 | include Native
4 | alias_native :bubbles, :bubbles
5 | alias_native :cancelable, :cancelable
6 | alias_native :current_target, :currentTarget
7 | alias_native :default_prevented, :defaultPrevented
8 | alias_native :event_phase, :eventPhase
9 | alias_native :is_trusted?, :isTrusted
10 | alias_native :native_event, :nativeEvent
11 | alias_native :target, :target
12 | alias_native :timestamp, :timeStamp
13 | alias_native :event_type, :type
14 | alias_native :prevent_default, :preventDefault
15 | alias_native :stop_propagation, :stopPropagation
16 | # Clipboard
17 | alias_native :clipboard_data, :clipboardData
18 | # Keyboard
19 | alias_native :alt_key, :altKey
20 | alias_native :char_code, :charCode
21 | alias_native :ctrl_key, :ctrlKey
22 | alias_native :get_modifier_state, :getModifierState
23 | alias_native :key, :key
24 | alias_native :key_code, :keyCode
25 | alias_native :locale, :locale
26 | alias_native :location, :location
27 | alias_native :meta_key, :metaKey
28 | alias_native :repeat, :repeat
29 | alias_native :shift_key, :shiftKey
30 | alias_native :which, :which
31 | # Focus
32 | alias_native :related_target, :relatedTarget
33 | # Mouse
34 | # aliased above: alias_native :alt_key, :altKey
35 | alias_native :button, :button
36 | alias_native :buttons, :buttons
37 | alias_native :client_x, :clientX
38 | alias_native :client_y, :clientY
39 | # aliased above: alias_native :ctrl_key, :ctrlKey
40 | alias_native :get_modifier_state, :getModifierState
41 | # aliased above: alias_native :meta_key, :metaKey
42 | alias_native :page_x, :pageX
43 | alias_native :page_y, :pageY
44 | # aliased above: alias_native :related_target, :relatedTarget
45 | alias_native :screen_x, :screen_x
46 | alias_native :screen_y, :screen_y
47 | # aliased above: alias_native :shift_key, :shift_key
48 | # Touch
49 | # aliased above: alias_native :alt_key, :altKey
50 | alias_native :changed_touches, :changedTouches
51 | # aliased above: alias_native :ctrl_key, :ctrlKey
52 | # aliased above: alias_native :get_modifier_state, :getModifierState
53 | # aliased above: alias_native :meta_key, :metaKey
54 | # aliased above: alias_native :shift_key, :shiftKey
55 | alias_native :target_touches, :targetTouches
56 | alias_native :touches, :touches
57 | # UI
58 | alias_native :detail, :detail
59 | alias_native :view, :view
60 | # Wheel
61 | alias_native :delta_mode, :deltaMode
62 | alias_native :delta_x, :deltaX
63 | alias_native :delta_y, :deltaY
64 | alias_native :delta_z, :deltaZ
65 |
66 | BUILT_IN_EVENTS = %w{onCopy onCut onPaste onKeyDown onKeyPress onKeyUp
67 | onFocus onBlur onChange onInput onSubmit onClick onContextMenu onDoubleClick onDrag
68 | onDragEnd onDragEnter onDragExit onDragLeave onDragOver onDragStart onDrop
69 | onMouseDown onMouseEnter onMouseLeave onMouseMove onMouseOut onMouseOver
70 | onMouseUp onSelect onTouchCancel onTouchEnd onTouchMove onTouchStart onScroll onWheel}
71 |
72 | def initialize(native_event)
73 | @native = native_event
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/react/ext/hash.rb:
--------------------------------------------------------------------------------
1 | class Hash
2 | def shallow_to_n
3 | hash = `{}`
4 | self.each do |key, value|
5 | `hash[#{key}] = #{value}`
6 | end
7 | hash
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/react/ext/opal-jquery/element.rb:
--------------------------------------------------------------------------------
1 | Element.instance_eval do
2 | def self.find(selector)
3 | selector = begin
4 | selector.dom_node
5 | rescue
6 | selector
7 | end if `#{selector}.$dom_node !== undefined`
8 | `$(#{selector})`
9 | end
10 |
11 | def self.[](selector)
12 | find(selector)
13 | end
14 |
15 | define_method :render do |container = nil, params = {}, &block|
16 | if `#{self.to_n}._reactrb_component_class === undefined`
17 | `#{self.to_n}._reactrb_component_class = #{Class.new(Hyperloop::Component)}`
18 | end
19 | klass = `#{self.to_n}._reactrb_component_class`
20 | klass.class_eval do
21 | render(container, params, &block)
22 | end
23 |
24 | React.render(React.create_element(`#{self.to_n}._reactrb_component_class`), self)
25 | end
26 |
27 | # mount_components is useful for dynamically generated page segments for example
28 | # see react-rails documentation for more details
29 |
30 | %x{
31 | $.fn.mount_components = function() {
32 | this.each(function(e) { ReactRailsUJS.mountComponents(e[0]) })
33 | return this;
34 | }
35 | }
36 | Element.expose :mount_components
37 | end if Object.const_defined?('Element')
38 |
--------------------------------------------------------------------------------
/lib/react/ext/string.rb:
--------------------------------------------------------------------------------
1 | class String
2 | def event_camelize
3 | `return #{self}.replace(/(^|_)([^_]+)/g, function(match, pre, word, index) {
4 | var capitalize = true;
5 | return capitalize ? word.substr(0,1).toUpperCase()+word.substr(1) : word;
6 | })`
7 | end
8 | end
--------------------------------------------------------------------------------
/lib/react/native_library.rb:
--------------------------------------------------------------------------------
1 | module React
2 | # NativeLibrary handles importing JS libraries. Importing native components is handled
3 | # by the React::Base. It also provides several methods used by auto-import.rb
4 |
5 | # A NativeLibrary is simply a wrapper that holds the name of the native js library.
6 | # It responds to const_missing and method_missing by looking up objects within the js library.
7 | # If the object is a react component it is wrapped by a reactrb component class, otherwise
8 | # a nested NativeLibrary is returned.
9 |
10 | # Two macros are provided: imports (for naming the native library) and renames which allows
11 | # the members of a library to be given different names within the ruby name space.
12 |
13 | # Public methods used by auto-import.rb are import_const_from_native and find_and_render_component
14 | class NativeLibrary
15 | class << self
16 | def imports(native_name)
17 | @native_prefix = "#{native_name}."
18 | self
19 | end
20 |
21 | def rename(rename_list)
22 | # rename_list is a hash in the form: native_name => ruby_name, native_name => ruby_name
23 | rename_list.each do |js_name, ruby_name|
24 | native_name = lookup_native_name(js_name)
25 | if lookup_native_name(js_name)
26 | create_component_wrapper(self, native_name, ruby_name) ||
27 | create_library_wrapper(self, native_name, ruby_name)
28 | else
29 | raise "class #{name} < React::NativeLibrary could not import #{js_name}. "\
30 | "Native value #{scope_native_name(js_name)} is undefined."
31 | end
32 | end
33 | end
34 |
35 | def import_const_from_native(klass, const_name, create_library)
36 | native_name = lookup_native_name(const_name) ||
37 | lookup_native_name(const_name[0].downcase + const_name[1..-1])
38 | native_name && (
39 | create_component_wrapper(klass, native_name, const_name) || (
40 | create_library &&
41 | create_library_wrapper(klass, native_name, const_name)))
42 | end
43 |
44 | def const_missing(const_name)
45 | import_const_from_native(self, const_name, true) || super
46 | end
47 |
48 | def method_missing(method, *args, &block)
49 | component_class = const_get(method) if const_defined?(method, false)
50 | component_class ||= import_const_from_native(self, method, false)
51 | raise 'could not import a react component named: '\
52 | "#{scope_native_name method}" unless component_class
53 | React::RenderingContext.render(component_class, *args, &block)
54 | end
55 |
56 | private
57 |
58 | def lookup_native_name(js_name)
59 | native_name = scope_native_name(js_name)
60 | `eval(#{native_name}) !== undefined && native_name`
61 | # rubocop:disable Lint/RescueException # that is what eval raises in Opal >= 0.10.
62 | rescue Exception
63 | nil
64 | # rubocop:enable Lint/RescueException
65 | end
66 |
67 | def scope_native_name(js_name)
68 | "#{@native_prefix}#{js_name}"
69 | end
70 |
71 | def create_component_wrapper(klass, native_name, ruby_name)
72 | if React::API.native_react_component?(native_name)
73 | new_klass = klass.const_set ruby_name, Class.new
74 | new_klass.class_eval do
75 | include Hyperloop::Component::Mixin
76 | imports native_name
77 | end
78 | new_klass
79 | end
80 | end
81 |
82 | def create_library_wrapper(klass, native_name, ruby_name)
83 | klass.const_set ruby_name, Class.new(React::NativeLibrary).imports(native_name)
84 | end
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/lib/react/object.rb:
--------------------------------------------------------------------------------
1 | # Lazy load HTML tag constants in the form DIV or A
2 | # This is needed to allow for a HAML expression like this DIV.my_class
3 | class Object
4 | class << self
5 | alias _reactrb_tag_original_const_missing const_missing
6 |
7 | def const_missing(const_name)
8 | # Opal uses const_missing to initially define things,
9 | # so we always call the original, and respond to the exception
10 | _reactrb_tag_original_const_missing(const_name)
11 | rescue StandardError => e
12 | React::Component::Tags.html_tag_class_for(const_name) || raise(e)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/react/react-source-browser.rb:
--------------------------------------------------------------------------------
1 | if RUBY_ENGINE == 'opal'
2 | require 'react.js'
3 | end
4 |
--------------------------------------------------------------------------------
/lib/react/react-source-server.rb:
--------------------------------------------------------------------------------
1 | if RUBY_ENGINE == 'opal'
2 | require 'react-server.js'
3 | end
4 |
--------------------------------------------------------------------------------
/lib/react/react-source.rb:
--------------------------------------------------------------------------------
1 | if RUBY_ENGINE == 'opal'
2 | %x{
3 | var ms = [
4 | "Warning: `react/react-source` is deprecated, ",
5 | "use `react/react-source-browser` or `react/react-source-server` instead."
6 | ]
7 | console.error(ms.join(""));
8 | }
9 | require 'react.js'
10 | require "react-server.js"
11 | else
12 | require "react/config"
13 | require "react/rails/asset_variant"
14 | variant = Hyperloop.env.production? ? 'production' : 'development'
15 | react_directory = React::Rails::AssetVariant.new({environment: variant}).react_directory
16 | Opal.append_path react_directory.untaint
17 | end
18 |
--------------------------------------------------------------------------------
/lib/react/ref_callback.rb:
--------------------------------------------------------------------------------
1 | require 'react/native_library'
2 |
3 | module React
4 | module RefsCallbackExtension
5 | end
6 |
7 | class API
8 | class << self
9 | alias :orig_convert_props :convert_props
10 | end
11 |
12 | def self.convert_props(properties)
13 | props = self.orig_convert_props(properties)
14 | props.map do |key, value|
15 | if key == "ref" && value.is_a?(Proc)
16 | new_proc = Proc.new do |native_inst|
17 | if `#{native_inst} !== null && #{native_inst}.__opalInstance !== undefined && #{native_inst}.__opalInstance !== null`
18 | value.call(`#{native_inst}.__opalInstance`)
19 | elsif `#{native_inst} !== null && ReactDOM.findDOMNode !== undefined && #{native_inst}.nodeType === undefined`
20 | value.call(`ReactDOM.findDOMNode(#{native_inst})`) # react >= v0.15.`)
21 | else
22 | value.call(native_inst)
23 | end
24 | end
25 | props[key] = new_proc
26 | end
27 | end
28 | props
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/lib/react/rendering_context.rb:
--------------------------------------------------------------------------------
1 | module React
2 | class RenderingContext
3 | class << self
4 | attr_accessor :waiting_on_resources
5 |
6 | def render(name, *args, &block)
7 | was_outer_most = !@not_outer_most
8 | @not_outer_most = true
9 | remove_nodes_from_args(args)
10 | @buffer ||= [] unless @buffer
11 | if block
12 | element = build do
13 | saved_waiting_on_resources = waiting_on_resources
14 | self.waiting_on_resources = nil
15 | run_child_block(name.nil?, &block)
16 | if name
17 | buffer = @buffer.dup
18 | React::API.create_element(name, *args) { buffer }.tap do |element|
19 | element.waiting_on_resources = saved_waiting_on_resources || !!buffer.detect { |e| e.waiting_on_resources if e.respond_to?(:waiting_on_resources) }
20 | element.waiting_on_resources ||= waiting_on_resources if buffer.last.is_a?(String)
21 | end
22 | elsif @buffer.last.is_a? React::Element
23 | @buffer.last.tap { |element| element.waiting_on_resources ||= saved_waiting_on_resources }
24 | else
25 | buffer_s = @buffer.last.to_s
26 | React::RenderingContext.render(:span) { buffer_s }.tap { |element| element.waiting_on_resources = saved_waiting_on_resources }
27 | end
28 | end
29 | elsif name.is_a? React::Element
30 | element = name
31 | else
32 | element = React::API.create_element(name, *args)
33 | element.waiting_on_resources = waiting_on_resources
34 | end
35 | @buffer << element
36 | self.waiting_on_resources = nil
37 | element
38 | ensure
39 | @not_outer_most = @buffer = nil if was_outer_most
40 | end
41 |
42 | def build
43 | current = @buffer
44 | @buffer = []
45 | return_val = yield @buffer
46 | @buffer = current
47 | return_val
48 | end
49 |
50 | def delete(element)
51 | @buffer.delete(element)
52 | element
53 | end
54 | alias as_node delete
55 |
56 | def rendered?(element)
57 | @buffer.include? element
58 | end
59 |
60 | def replace(e1, e2)
61 | @buffer[@buffer.index(e1)] = e2
62 | end
63 |
64 | def remove_nodes_from_args(args)
65 | args[0].each do |key, value|
66 | begin
67 | value.delete if value.is_a?(Element) # deletes Element from buffer
68 | rescue Exception
69 | end
70 | end if args[0] && args[0].is_a?(Hash)
71 | end
72 |
73 | # run_child_block gathers the element(s) generated by a child block.
74 | # for example when rendering this div: div { "hello".span; "goodby".span }
75 | # two child Elements will be generated.
76 | #
77 | # the final value of the block should either be
78 | # 1 an object that responds to :acts_as_string?
79 | # 2 a string,
80 | # 3 an element that is NOT yet pushed on the rendering buffer
81 | # 4 or the last element pushed on the buffer
82 | #
83 | # in case 1 we render a span
84 | # in case 2 we automatically push the string onto the buffer
85 | # in case 3 we also push the Element onto the buffer IF the buffer is empty
86 | # case 4 requires no special processing
87 | #
88 | # Once we have taken care of these special cases we do a check IF we are in an
89 | # outer rendering scope. In this case react only allows us to generate 1 Element
90 | # so we insure that is the case, and also check to make sure that element in the buffer
91 | # is the element returned
92 |
93 | def run_child_block(is_outer_scope)
94 | result = yield
95 | if result.respond_to?(:acts_as_string?) && result.acts_as_string?
96 | # hyper-mesh DummyValues respond to acts_as_string, and must
97 | # be converted to spans INSIDE the parent, otherwise the waiting_on_resources
98 | # flag will get set in the wrong context
99 | React::RenderingContext.render(:span) { result.to_s }
100 | elsif result.is_a?(String) || (result.is_a?(React::Element) && @buffer.empty?)
101 | @buffer << result
102 | end
103 | raise_render_error(result) if is_outer_scope && @buffer != [result]
104 | end
105 |
106 | # heurestically raise a meaningful error based on the situation
107 |
108 | def raise_render_error(result)
109 | improper_render 'A different element was returned than was generated within the DSL.',
110 | 'Possibly improper use of Element#delete.' if @buffer.count == 1
111 | improper_render "Instead #{@buffer.count} elements were generated.",
112 | 'Do you want to wrap your elements in a div?' if @buffer.count > 1
113 | improper_render "Instead the component #{result} was returned.",
114 | "Did you mean #{result}()?" if result.try :reactrb_component?
115 | improper_render "Instead the #{result.class} #{result} was returned.",
116 | 'You may need to convert this to a string.'
117 | end
118 |
119 | def improper_render(message, solution)
120 | raise "a component's render method must generate and return exactly 1 element or a string.\n"\
121 | " #{message} #{solution}"
122 | end
123 | end
124 | end
125 | end
126 |
127 | class Object
128 | [:span, :td, :th, :while_loading].each do |tag|
129 | define_method(tag) do |*args, &block|
130 | args.unshift(tag)
131 | return send(*args, &block) if is_a? React::Component
132 | React::RenderingContext.render(*args) { to_s }
133 | end
134 | end
135 |
136 | def para(*args, &block)
137 | args.unshift(:p)
138 | return send(*args, &block) if is_a? React::Component
139 | React::RenderingContext.render(*args) { to_s }
140 | end
141 |
142 | def br
143 | return send(:br) if is_a? React::Component
144 | React::RenderingContext.render(:span) do
145 | React::RenderingContext.render(to_s)
146 | React::RenderingContext.render(:br)
147 | end
148 | end
149 | end
150 |
--------------------------------------------------------------------------------
/lib/react/server.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Server
3 | def self.render_to_string(element)
4 | if !(`typeof ReactDOMServer === 'undefined'`)
5 | React::RenderingContext.build { `ReactDOMServer.renderToString(#{element.to_n})` } # v0.15+
6 | else
7 | raise "renderToString is not defined. In React >= v15 you must import it with ReactDOMServer"
8 | end
9 | end
10 |
11 | def self.render_to_static_markup(element)
12 | if !(`typeof ReactDOMServer === 'undefined'`)
13 | React::RenderingContext.build { `ReactDOMServer.renderToStaticMarkup(#{element.to_n})` } # v0.15+
14 | else
15 | raise "renderToStaticMarkup is not defined. In React >= v15 you must import it with ReactDOMServer"
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/react/state_wrapper.rb:
--------------------------------------------------------------------------------
1 | module HyperStore
2 | class StateWrapper < BaseStoreClass # < BasicObject
3 |
4 | def [](state)
5 | `#{__from__.instance_variable_get('@native')}.state[#{state}] || #{nil}`
6 | end
7 |
8 | def []=(state, new_value)
9 | `#{__from__.instance_variable_get('@native')}.state[#{state}] = new_value`
10 | end
11 |
12 | alias pre_component_method_missing method_missing
13 |
14 | def method_missing(method, *args)
15 | if method.end_with?('!') && __from__.respond_to?(:deprecation_warning)
16 | __from__.deprecation_warning("The mutator 'state.#{method}' has been deprecated. Use 'mutate.#{method.sub(/\!$/,'')}' instead.")
17 | __from__.mutate.__send__(method.chop, *args)
18 | else
19 | pre_component_method_missing(method, *args)
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/react/test.rb:
--------------------------------------------------------------------------------
1 | require 'react/test/session'
2 | require 'react/test/dsl'
3 |
4 | module React
5 | module Test
6 | class << self
7 | def current_session
8 | @current_session ||= Session.new
9 | end
10 |
11 | def reset_session!
12 | @current_session = nil
13 | end
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/react/test/dsl.rb:
--------------------------------------------------------------------------------
1 | require 'react/test'
2 |
3 | module React
4 | module Test
5 | module DSL
6 | def component
7 | React::Test.current_session
8 | end
9 |
10 | Session::DSL_METHODS.each do |method|
11 | define_method method do |*args, &block|
12 | component.public_send(method, *args, &block)
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/react/test/matchers/render_html_matcher.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Test
3 | module Matchers
4 | class RenderHTMLMatcher
5 | def initialize(expected)
6 | @expected = expected
7 | @params = {}
8 | end
9 |
10 | def with_params(params)
11 | @params = params
12 | self
13 | end
14 |
15 | def matches?(component)
16 | @component = component
17 | @actual = render_to_html
18 | @expected == @actual
19 | end
20 |
21 | def failure_message
22 | failure_string
23 | end
24 |
25 | def failure_message_when_negated
26 | failure_string(:negative)
27 | end
28 |
29 | alias negative_failure_message failure_message_when_negated
30 |
31 | private
32 |
33 | def render_to_html
34 | element = React.create_element(@component, @params)
35 | React::Server.render_to_static_markup(element)
36 | end
37 |
38 | def failure_string(negative = false)
39 | str = "expected '#{@component.name}' with params '#{@params}' to "
40 | str = str + "not " if negative
41 | str = str + "render '#{@expected}', but '#{@actual}' was rendered."
42 | str
43 | end
44 | end
45 |
46 | def render_static_html(*args)
47 | RenderHTMLMatcher.new(*args)
48 | end
49 |
50 | def render(*args)
51 | %x{ console.error("Warning: `render` matcher is deprecated in favor of `render_static_html`."); }
52 | RenderHTMLMatcher.new(*args)
53 | end
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/react/test/rspec.rb:
--------------------------------------------------------------------------------
1 | require 'react/test/dsl'
2 | require 'react/test/matchers/render_html_matcher'
3 |
4 | RSpec.configure do |config|
5 | config.include React::Test::DSL, type: :component
6 | config.include React::Test::Matchers, type: :component
7 |
8 | config.after do
9 | React::Test.reset_session!
10 | end
11 |
12 | config.before do
13 | # nothing yet
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/react/test/session.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Test
3 | class Session
4 | DSL_METHODS = %i[mount instance update_params html].freeze
5 |
6 | def mount(component_klass, params = {})
7 | @element = React.create_element(component_klass, params)
8 | instance
9 | end
10 |
11 | def instance
12 | unless @instance
13 | @container = `document.createElement('div')`
14 | @instance = React.render(@element, @container)
15 | end
16 | @instance
17 | end
18 |
19 | def update_params(params, &block)
20 | cloned_element = React::Element.new(`React.cloneElement(#{@element.to_n}, #{params.to_n})`)
21 | React.render(cloned_element, @container, &block)
22 | nil
23 | end
24 |
25 | def html
26 | html = `#@container.innerHTML`
27 | %x{
28 | var REGEX_REMOVE_ROOT_IDS = /\s?data-reactroot="[^"]*"/g;
29 | var REGEX_REMOVE_IDS = /\s?data-reactid="[^"]+"/g;
30 | html = html.replace(REGEX_REMOVE_ROOT_IDS, '');
31 | html = html.replace(REGEX_REMOVE_IDS, '');
32 | }
33 | return html
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/react/test/utils.rb:
--------------------------------------------------------------------------------
1 | module React
2 | module Test
3 | class Utils
4 | def self.render_component_into_document(component, args = {})
5 | element = React.create_element(component, args)
6 | render_into_document(element)
7 | end
8 |
9 | def self.render_into_document(element)
10 | raise "You should pass a valid React::Element" unless React.is_valid_element?(element)
11 | dom_el = `document.body.querySelector('div[data-react-class="React.TopLevelRailsComponent"]').appendChild(document.createElement('div'))`
12 | React.render(element, dom_el)
13 | end
14 |
15 | def self.simulate_click(element)
16 | # element must be a component or a dom node or a element
17 | el = if `typeof element.nodeType !== "undefined"`
18 | element
19 | elsif element.is_a? React::Component
20 | element.dom_node
21 | elsif element.is_a? React::Element
22 | `ReactDOM.findDOMNode(#{element.to_n}.native)`
23 | else
24 | element
25 | end
26 | %x{
27 | var evob = new MouseEvent('click', {
28 | view: window,
29 | bubbles: true,
30 | cancelable: true
31 | });
32 | el.dispatchEvent(evob);
33 | }
34 | end
35 |
36 | def self.simulate_keydown(element, key_name = "Enter")
37 | # element must be a component or a dom node or a element
38 | el = if `typeof element.nodeType !== "undefined"`
39 | element
40 | elsif element.is_a? React::Component
41 | element.dom_node
42 | elsif element.is_a? React::Element
43 | `ReactDOM.findDOMNode(#{element.to_n}.native)`
44 | else
45 | element
46 | end
47 | %x{
48 | var evob = new KeyboardEvent('keydown', { key: key_name, bubbles: true, cancelable: true });
49 | el.dispatchEvent(evob);
50 | }
51 | end
52 |
53 | def self.simulate_submit(element)
54 | # element must be a component or a dom node or a element
55 | el = if `typeof element.nodeType !== "undefined"`
56 | element
57 | elsif element.is_a? React::Component
58 | element.dom_node
59 | elsif element.is_a? React::Element
60 | `ReactDOM.findDOMNode(#{element.to_n}.native)`
61 | else
62 | element
63 | end
64 | %x{
65 | var evob = new Event('submit', { bubbles: true, cancelable: true });
66 | el.dispatchEvent(evob);
67 | }
68 | end
69 | end
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/react/to_key.rb:
--------------------------------------------------------------------------------
1 | # to_key method returns a suitable unique id that can be used as
2 | # a react `key`. Other classes may override to_key as needed
3 | # for example hyper_mesh returns the object id of the internal
4 | # backing record.
5 | #
6 | # to_key is automatically called on objects passed as keys for
7 | # example Foo(key: my_object) results in Foo(key: my_object.to_key)
8 | class Object
9 | def to_key
10 | object_id
11 | end
12 | end
13 |
14 | # for Number to_key can just be the number itself
15 | class Number
16 | def to_key
17 | self
18 | end
19 | end
20 |
21 | # for Boolean to_key can be true or false
22 | class Boolean
23 | def to_key
24 | self
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/react/top_level.rb:
--------------------------------------------------------------------------------
1 | require "native"
2 | require 'active_support/core_ext/object/try'
3 | require 'react/component/tags'
4 | require 'react/component/base'
5 |
6 | module React
7 |
8 | ATTRIBUTES = %w(accept acceptCharset accessKey action allowFullScreen allowTransparency alt
9 | async autoComplete autoPlay cellPadding cellSpacing charSet checked classID
10 | className cols colSpan content contentEditable contextMenu controls coords
11 | crossOrigin data dateTime defer dir disabled download draggable encType form
12 | formAction formEncType formMethod formNoValidate formTarget frameBorder height
13 | hidden href hrefLang htmlFor httpEquiv icon id label lang list loop manifest
14 | marginHeight marginWidth max maxLength media mediaGroup method min multiple
15 | muted name noValidate open pattern placeholder poster preload radioGroup
16 | readOnly rel required role rows rowSpan sandbox scope scrolling seamless
17 | selected shape size sizes span spellCheck src srcDoc srcSet start step style
18 | tabIndex target title type useMap value width wmode dangerouslySetInnerHTML) +
19 | #SVG ATTRIBUTES
20 | %w(clipPath cx cy d dx dy fill fillOpacity fontFamily
21 | fontSize fx fy gradientTransform gradientUnits markerEnd
22 | markerMid markerStart offset opacity patternContentUnits
23 | patternUnits points preserveAspectRatio r rx ry spreadMethod
24 | stopColor stopOpacity stroke strokeDasharray strokeLinecap
25 | strokeOpacity strokeWidth textAnchor transform version
26 | viewBox x1 x2 x xlinkActuate xlinkArcrole xlinkHref xlinkRole
27 | xlinkShow xlinkTitle xlinkType xmlBase xmlLang xmlSpace y1 y2 y)
28 | HASH_ATTRIBUTES = %w(data aria)
29 | HTML_TAGS = React::Component::Tags::HTML_TAGS
30 |
31 | def self.html_tag?(name)
32 | tags = HTML_TAGS
33 | %x{
34 | for(var i = 0; i < tags.length; i++) {
35 | if(tags[i] === name)
36 | return true;
37 | }
38 | return false;
39 | }
40 | end
41 |
42 | def self.html_attr?(name)
43 | attrs = ATTRIBUTES
44 | %x{
45 | for(var i = 0; i < attrs.length; i++) {
46 | if(attrs[i] === name)
47 | return true;
48 | }
49 | return false;
50 | }
51 | end
52 |
53 | def self.create_element(type, properties = {}, &block)
54 | React::API.create_element(type, properties, &block)
55 | end
56 |
57 | def self.render(element, container)
58 | %x{
59 | console.error(
60 | "Warning: Using deprecated behavior of `React.render`,",
61 | "require \"react/top_level_render\" to get the correct behavior."
62 | );
63 | }
64 | container = `container.$$class ? container[0] : container`
65 | if !(`typeof ReactDOM === 'undefined'`)
66 | component = Native(`ReactDOM.render(#{element.to_n}, container, function(){#{yield if block_given?}})`) # v0.15+
67 | else
68 | raise "render is not defined. In React >= v15 you must import it with ReactDOM"
69 | end
70 |
71 | component.class.include(React::Component::API)
72 | component
73 | end
74 |
75 | def self.is_valid_element(element)
76 | %x{ console.error("Warning: `is_valid_element` is deprecated in favor of `is_valid_element?`."); }
77 | element.kind_of?(React::Element) && `React.isValidElement(#{element.to_n})`
78 | end
79 |
80 | def self.is_valid_element?(element)
81 | element.kind_of?(React::Element) && `React.isValidElement(#{element.to_n})`
82 | end
83 |
84 | def self.render_to_string(element)
85 | %x{ console.error("Warning: `React.render_to_string` is deprecated in favor of `React::Server.render_to_string`."); }
86 | if !(`typeof ReactDOMServer === 'undefined'`)
87 | React::RenderingContext.build { `ReactDOMServer.renderToString(#{element.to_n})` } # v0.15+
88 | else
89 | raise "renderToString is not defined. In React >= v15 you must import it with ReactDOMServer"
90 | end
91 | end
92 |
93 | def self.render_to_static_markup(element)
94 | %x{ console.error("Warning: `React.render_to_static_markup` is deprecated in favor of `React::Server.render_to_static_markup`."); }
95 | if !(`typeof ReactDOMServer === 'undefined'`)
96 | React::RenderingContext.build { `ReactDOMServer.renderToStaticMarkup(#{element.to_n})` } # v0.15+
97 | else
98 | raise "renderToStaticMarkup is not defined. In React >= v15 you must import it with ReactDOMServer"
99 | end
100 | end
101 |
102 | def self.unmount_component_at_node(node)
103 | if !(`typeof ReactDOM === 'undefined'`)
104 | `ReactDOM.unmountComponentAtNode(node.$$class ? node[0] : node)` # v0.15+
105 | else
106 | raise "unmountComponentAtNode is not defined. In React >= v15 you must import it with ReactDOM"
107 | end
108 | end
109 |
110 | end
111 |
--------------------------------------------------------------------------------
/lib/react/top_level_render.rb:
--------------------------------------------------------------------------------
1 | module React
2 | def self.render(element, container)
3 | raise "ReactDOM.render is not defined. In React >= v15 you must import it with ReactDOM" if (`typeof ReactDOM === 'undefined'`)
4 |
5 | container = `container.$$class ? container[0] : container`
6 |
7 | if block_given?
8 | cb = %x{
9 | function(){
10 | setTimeout(function(){
11 | #{yield}
12 | }, 0)
13 | }
14 | }
15 | native = `ReactDOM.render(#{element.to_n}, container, cb)`
16 | else
17 | native = `ReactDOM.render(#{element.to_n}, container)`
18 | end
19 |
20 | if `#{native}.__opalInstance !== undefined && #{native}.__opalInstance !== null`
21 | `#{native}.__opalInstance`
22 | elsif `ReactDOM.findDOMNode !== undefined && #{native}.nodeType === undefined`
23 | `ReactDOM.findDOMNode(#{native})`
24 | else
25 | native
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/react/validator.rb:
--------------------------------------------------------------------------------
1 | module React
2 | class Validator
3 | attr_accessor :errors
4 | attr_reader :props_wrapper
5 | private :errors, :props_wrapper
6 |
7 | def initialize(props_wrapper = Class.new(Component::PropsWrapper))
8 | @props_wrapper = props_wrapper
9 | end
10 |
11 | def self.build(&block)
12 | self.new.build(&block)
13 | end
14 |
15 | def build(&block)
16 | instance_eval(&block)
17 | self
18 | end
19 |
20 | def requires(name, options = {})
21 | options[:required] = true
22 | define_rule(name, options)
23 | end
24 |
25 | def optional(name, options = {})
26 | options[:required] = false
27 | define_rule(name, options)
28 | end
29 |
30 | def all_other_params(name)
31 | @allow_undefined_props = true
32 | props_wrapper.define_all_others(name) { |props| props.reject { |name, value| rules[name] } }
33 | end
34 |
35 | def validate(props)
36 | self.errors = []
37 | validate_undefined(props) unless allow_undefined_props?
38 | props = coerce_native_hash_values(defined_props(props))
39 | validate_required(props)
40 | props.each do |name, value|
41 | validate_types(name, value)
42 | validate_allowed(name, value)
43 | end
44 | errors
45 | end
46 |
47 | def default_props
48 | rules
49 | .select {|key, value| value.keys.include?("default") }
50 | .inject({}) {|memo, (k,v)| memo[k] = v[:default]; memo}
51 | end
52 |
53 | private
54 |
55 | def defined_props(props)
56 | props.select { |name| rules.keys.include?(name) }
57 | end
58 |
59 | def allow_undefined_props?
60 | !!@allow_undefined_props
61 | end
62 |
63 | def rules
64 | @rules ||= { children: { required: false } }
65 | end
66 |
67 | def define_rule(name, options = {})
68 | rules[name] = coerce_native_hash_values(options)
69 | props_wrapper.define_param(name, options[:type])
70 | end
71 |
72 | def errors
73 | @errors ||= []
74 | end
75 |
76 | def validate_types(prop_name, value)
77 | return unless klass = rules[prop_name][:type]
78 | if !klass.is_a?(Array)
79 | allow_nil = !!rules[prop_name][:allow_nil]
80 | type_check("`#{prop_name}`", value, klass, allow_nil)
81 | elsif klass.length > 0
82 | validate_value_array(prop_name, value)
83 | else
84 | allow_nil = !!rules[prop_name][:allow_nil]
85 | type_check("`#{prop_name}`", value, Array, allow_nil)
86 | end
87 | end
88 |
89 | def type_check(prop_name, value, klass, allow_nil)
90 | return if allow_nil && value.nil?
91 | return if value.is_a?(klass)
92 | return if klass.respond_to?(:_react_param_conversion) &&
93 | klass._react_param_conversion(value, :validate_only)
94 | errors << "Provided prop #{prop_name} could not be converted to #{klass}"
95 | end
96 |
97 | def validate_allowed(prop_name, value)
98 | return unless values = rules[prop_name][:values]
99 | return if values.include?(value)
100 | errors << "Value `#{value}` for prop `#{prop_name}` is not an allowed value"
101 | end
102 |
103 | def validate_required(props)
104 | (rules.keys - props.keys).each do |name|
105 | next unless rules[name][:required]
106 | errors << "Required prop `#{name}` was not specified"
107 | end
108 | end
109 |
110 | def validate_undefined(props)
111 | (props.keys - rules.keys).each do |prop_name|
112 | errors << "Provided prop `#{prop_name}` not specified in spec"
113 | end
114 | end
115 |
116 | def validate_value_array(name, value)
117 | klass = rules[name][:type]
118 | allow_nil = !!rules[name][:allow_nil]
119 | value.each_with_index do |item, index|
120 | type_check("`#{name}`[#{index}]", Native(item), klass[0], allow_nil)
121 | end
122 | rescue NoMethodError
123 | errors << "Provided prop `#{name}` was not an Array"
124 | end
125 |
126 | def coerce_native_hash_values(hash)
127 | hash.each do |key, value|
128 | hash[key] = Native(value)
129 | end
130 | end
131 | end
132 | end
133 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/component_loader.rb:
--------------------------------------------------------------------------------
1 | module ReactiveRuby
2 | class ComponentLoader
3 | attr_reader :v8_context
4 | private :v8_context
5 |
6 | def initialize(v8_context)
7 | unless v8_context
8 | raise ArgumentError.new('Could not obtain ExecJS runtime context')
9 | end
10 | @v8_context = v8_context
11 | end
12 |
13 | def load(file = components)
14 | return true if loaded?
15 | !!v8_context.eval(opal(file))
16 | end
17 |
18 | def load!(file = components)
19 | return true if loaded?
20 | self.load(file)
21 | ensure
22 | raise "No HyperReact components found in #{components}" unless loaded?
23 | end
24 |
25 | def loaded?
26 | !!v8_context.eval('Opal.React !== undefined')
27 | rescue ::ExecJS::Error
28 | false
29 | end
30 |
31 | private
32 |
33 | def components
34 | opts = ::Rails.configuration.react.server_renderer_options
35 | return opts[:files].first.gsub(/.js$/,'') if opts && opts[:files]
36 | 'components'
37 | end
38 |
39 | def opal(file)
40 | Opal::Sprockets.load_asset(file)
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/rails.rb:
--------------------------------------------------------------------------------
1 | require 'action_view'
2 | require 'react-rails'
3 | require 'reactive-ruby/server_rendering/hyper_asset_container'
4 | require 'reactive-ruby/server_rendering/contextual_renderer'
5 | require 'reactive-ruby/rails/component_mount'
6 | require 'reactive-ruby/rails/railtie'
7 | require 'reactive-ruby/rails/controller_helper'
8 | require 'reactive-ruby/component_loader'
9 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/rails/component_mount.rb:
--------------------------------------------------------------------------------
1 | module ReactiveRuby
2 | module Rails
3 | class ComponentMount < React::Rails::ComponentMount
4 | attr_accessor :controller
5 |
6 | def setup(controller)
7 | self.controller = controller
8 | end
9 |
10 | def react_component(name, props = {}, options = {}, &block)
11 | if options[:prerender] || [:on, 'on', true].include?(Hyperloop.prerendering)
12 | options = context_initializer_options(options, name)
13 | end
14 | props = serialized_props(props, name, controller)
15 | result = super(top_level_name, props, options, &block).gsub("\n","")
16 | result = result.gsub(/()<\/div>$/,'\1').html_safe
17 | result + footers
18 | end
19 |
20 | private
21 |
22 | def context_initializer_options(options, name)
23 | options[:prerender] = {options[:prerender] => true} unless options[:prerender].is_a? Hash
24 | existing_context_initializer = options[:prerender][:context_initializer]
25 |
26 | options[:prerender][:context_initializer] = lambda do |ctx|
27 | React::IsomorphicHelpers.load_context(ctx, controller, name)
28 | existing_context_initializer.call(ctx) if existing_context_initializer
29 | end
30 |
31 | options
32 | end
33 |
34 | def serialized_props(props, name, controller)
35 | { render_params: props, component_name: name,
36 | controller: controller.class.name.gsub(/Controller$/,"") }.react_serializer
37 | end
38 |
39 | def top_level_name
40 | 'React.TopLevelRailsComponent'
41 | end
42 |
43 | def footers
44 | React::IsomorphicHelpers.prerender_footers(controller)
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/rails/controller_helper.rb:
--------------------------------------------------------------------------------
1 | require 'action_controller'
2 |
3 | module ActionController
4 | # adds render_component helper to ActionControllers
5 | class Base
6 | def render_component(*args)
7 | @component_name = (args[0].is_a? Hash) || args.empty? ? params[:action].camelize : args.shift
8 | @render_params = args.shift || {}
9 | options = args[0] || {}
10 | render inline: '<%= react_component @component_name, @render_params %>',
11 | layout: options.key?(:layout) ? options[:layout].to_s : :default
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/rails/railtie.rb:
--------------------------------------------------------------------------------
1 | module ReactiveRuby
2 | module Rails
3 | class Railtie < ::Rails::Railtie
4 | config.before_configuration do |app|
5 | app.config.assets.enabled = true
6 | app.config.assets.paths << ::Rails.root.join('app', 'views').to_s
7 | app.config.react.server_renderer = ReactiveRuby::ServerRendering::ContextualRenderer
8 | app.config.react.view_helper_implementation = ReactiveRuby::Rails::ComponentMount
9 | ReactiveRuby::ServerRendering::ContextualRenderer.asset_container_class = ReactiveRuby::ServerRendering::HyperAssetContainer
10 | end
11 | config.after_initialize do
12 | class ::HyperloopController < ::ApplicationController
13 | def action_missing(name)
14 | render_component
15 | end
16 | end
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/serializers.rb:
--------------------------------------------------------------------------------
1 | [FalseClass, Float, Integer, NilClass, String, Symbol, Time, TrueClass].each do |klass|
2 | klass.send(:define_method, :react_serializer) do
3 | as_json
4 | end
5 | end
6 |
7 | if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4.0')
8 | [Bignum, Fixnum].each do |klass|
9 | klass.send(:define_method, :react_serializer) do
10 | as_json
11 | end
12 | end
13 | end
14 |
15 | BigDecimal.send(:define_method, :react_serializer) { as_json } rescue nil
16 |
17 | Array.send(:define_method, :react_serializer) do
18 | self.collect { |e| e.react_serializer }.as_json
19 | end
20 |
21 | Hash.send(:define_method, :react_serializer) do
22 | Hash[*self.collect { |key, value| [key, value.react_serializer] }.flatten(1)].as_json
23 | end
24 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/server_rendering/contextual_renderer.rb:
--------------------------------------------------------------------------------
1 | module ReactiveRuby
2 | module ServerRendering
3 | def self.context_instance_name
4 | '@context'
5 | end
6 |
7 | def self.context_instance_for(context)
8 | context.instance_variable_get(context_instance_name)
9 | end
10 |
11 | class ContextualRenderer < React::ServerRendering::BundleRenderer
12 | def initialize(options = {})
13 | super(options)
14 | ComponentLoader.new(v8_context).load
15 | end
16 |
17 | def before_render(*args)
18 | # the base class clears the log history... we don't want that as it is taken
19 | # care of in IsomorphicHelpers.load_context
20 | end
21 |
22 | def render(component_name, props, prerender_options)
23 | if prerender_options.is_a?(Hash)
24 | if !v8_runtime? && prerender_options[:context_initializer]
25 | raise React::ServerRendering::PrerenderError.new(component_name, props, "you must use 'mini_racer' with the prerender[:context] option") unless v8_runtime?
26 | else
27 | prerender_options[:context_initializer].call v8_context
28 | prerender_options = prerender_options[:static] ? :static : true
29 | end
30 | end
31 |
32 | super(component_name, props, prerender_options)
33 | end
34 |
35 | private
36 |
37 | def v8_runtime?
38 | ExecJS.runtime.name == 'mini_racer (V8)'
39 | end
40 |
41 | def v8_context
42 | @v8_context ||= ReactiveRuby::ServerRendering.context_instance_for(@context)
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/reactive-ruby/server_rendering/hyper_asset_container.rb:
--------------------------------------------------------------------------------
1 | require 'react/server_rendering/environment_container'
2 | require 'react/server_rendering/manifest_container'
3 | require 'react/server_rendering/webpacker_manifest_container'
4 |
5 | module ReactiveRuby
6 | module ServerRendering
7 | class HyperTestAssetContainer
8 | def find_asset(logical_path)
9 | ::Rails.cache.read(logical_path)
10 | end
11 | end
12 |
13 | class HyperAssetContainer
14 | def initialize
15 | @ass_containers = []
16 | if assets_precompiled?
17 | @ass_containers << React::ServerRendering::ManifestContainer.new if React::ServerRendering::ManifestContainer.compatible?
18 | else
19 | @ass_containers << React::ServerRendering::EnvironmentContainer.new if ::Rails.application.assets
20 | end
21 | if React::ServerRendering::WebpackerManifestContainer.compatible?
22 | @ass_containers << React::ServerRendering::WebpackerManifestContainer.new
23 | end
24 | @ass_containers << HyperTestAssetContainer.new if ::Rails.env.test?
25 | end
26 |
27 | def find_asset(logical_path)
28 | @ass_containers.each do |ass|
29 | begin
30 | asset = ass.find_asset(logical_path)
31 | return asset if asset && asset != ''
32 | rescue
33 | next # no asset found, try the next container
34 | end
35 | end
36 | raise "No asset found for #{logical_path}, tried: #{@ass_containers.map { |c| c.class.name }.join(', ')}"
37 | end
38 |
39 | private
40 |
41 | def assets_precompiled?
42 | !::Rails.application.config.assets.compile
43 | end
44 | end
45 | end
46 | end
--------------------------------------------------------------------------------
/lib/reactive-ruby/version.rb:
--------------------------------------------------------------------------------
1 | module React
2 | VERSION = '1.0.0.lap28'
3 | end
4 |
--------------------------------------------------------------------------------
/lib/reactrb/auto-import.rb:
--------------------------------------------------------------------------------
1 | # rubocop:disable Style/FileName
2 | # require 'reactrb/auto-import' to automatically
3 | # import JS libraries and components when they are detected
4 | if RUBY_ENGINE == 'opal'
5 | # modifies const and method_missing so that they will attempt
6 | # to auto import native libraries and components using React::NativeLibrary
7 | class Object
8 | class << self
9 | alias _reactrb_original_const_missing const_missing
10 | alias _reactrb_original_method_missing method_missing
11 |
12 | def const_missing(const_name)
13 | # Opal uses const_missing to initially define things,
14 | # so we always call the original, and respond to the exception
15 | _reactrb_original_const_missing(const_name)
16 | rescue StandardError => e
17 | React::NativeLibrary.import_const_from_native(Object, const_name, true) || raise(e)
18 | end
19 |
20 | def method_missing(method, *args, &block)
21 | component_class = React::NativeLibrary.import_const_from_native(self, method, false)
22 | _reactrb_original_method_missing(method, *args, &block) unless component_class
23 | React::RenderingContext.render(component_class, *args, &block)
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/logo1.png
--------------------------------------------------------------------------------
/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/logo2.png
--------------------------------------------------------------------------------
/logo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/logo3.png
--------------------------------------------------------------------------------
/path_release_steps.md:
--------------------------------------------------------------------------------
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
10 |
--------------------------------------------------------------------------------
/spec/controller_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | require 'rails-controller-testing'
4 | Rails::Controller::Testing.install
5 |
6 | class TestController < ActionController::Base; end
7 |
8 | RSpec.describe TestController, type: :controller do
9 | render_views
10 |
11 | describe '#render_component' do
12 | controller do
13 |
14 | layout "test_layout"
15 |
16 | def index
17 | render_component
18 | end
19 |
20 | def new
21 | render_component "Index", {}, layout: :explicit_layout
22 | end
23 | end
24 |
25 | it 'renders with the default layout' do
26 | get :index
27 | expect(response).to render_template(layout: :test_layout)
28 | end
29 |
30 | it "renders with a specified layout" do
31 | get :new
32 | expect(response).to render_template(layout: :explicit_layout)
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/spec/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%= javascript_include_tag 'vendor/es5-shim.min' %>
8 | <%= javascript_include_tag 'vendor/jquery-2.2.4.min' %>
9 | <%= javascript_include_tag @server.main %>
10 |
11 |
12 |
--------------------------------------------------------------------------------
/spec/react/builtin_tags_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'redefining builtin tags', js: true do
4 | it "built in tags can be redefined" do
5 | mount 'Foo' do
6 | React::Component::Tags.remove_method :DIV
7 | React::Component::Tags.send(:remove_const, :DIV)
8 |
9 | class React::Component::Tags::DIV < Hyperloop::Component
10 | others :opts
11 | render do
12 | present :div, params.opts, data: {render_time: Time.now}, &children
13 | end
14 | end
15 |
16 | class Foo < Hyperloop::Component
17 | render(DIV, id: :tp) do
18 | "hello"
19 | end
20 | end
21 | end
22 | expect(Time.parse(find('#tp')['data-render-time'])).to be <= Time.now
23 | expect(page).to have_content('hello')
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/react/callbacks_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'React::Callbacks', js: true do
4 | it 'defines callback' do
5 | on_client do
6 | class Foo
7 | include React::Callbacks
8 | define_callback :before_dinner
9 | before_dinner :wash_hands
10 |
11 | def wash_hands;end
12 | end
13 | end
14 |
15 | expect_evaluate_ruby do
16 | instance = Foo.new
17 | [ instance.respond_to?(:wash_hands), instance.run_callback(:before_dinner) ]
18 | end.to eq([true, ["wash_hands"]])
19 | end
20 |
21 | it 'defines multiple callbacks' do
22 | on_client do
23 | class Foo
24 | include React::Callbacks
25 | define_callback :before_dinner
26 | before_dinner :wash_hands, :turn_off_laptop
27 |
28 | def wash_hands;end
29 | def turn_off_laptop;end
30 | end
31 | end
32 | expect_evaluate_ruby do
33 | instance = Foo.new
34 | [ instance.respond_to?(:wash_hands),
35 | instance.respond_to?(:turn_off_laptop),
36 | instance.run_callback(:before_dinner) ]
37 | end.to eq([true, true, ["wash_hands", "turn_off_laptop" ]])
38 | end
39 |
40 | context 'using Hyperloop::Context.reset!' do
41 | #after(:all) do
42 | # Hyperloop::Context.instance_variable_set(:@context, nil)
43 | #end
44 | it 'clears callbacks on Hyperloop::Context.reset!' do
45 | on_client do
46 | Hyperloop::Context.reset!
47 |
48 | class Foo
49 | include React::Callbacks
50 | define_callback :before_dinner
51 |
52 | before_dinner :wash_hands, :turn_off_laptop
53 |
54 | def wash_hands;end
55 |
56 | def turn_off_laptop;end
57 | end
58 | end
59 | expect_evaluate_ruby do
60 | instance = Foo.new
61 |
62 | Hyperloop::Context.reset!
63 |
64 | Foo.class_eval do
65 | before_dinner :wash_hands
66 | end
67 |
68 | instance.run_callback(:before_dinner)
69 | end.to eq(["wash_hands"])
70 | end
71 | end
72 |
73 | it 'defines block callback' do
74 | on_client do
75 | class Foo
76 | include React::Callbacks
77 | attr_accessor :a
78 | attr_accessor :b
79 |
80 | define_callback :before_dinner
81 |
82 | before_dinner do
83 | self.a = 10
84 | end
85 | before_dinner do
86 | self.b = 20
87 | end
88 | end
89 | end
90 | expect_evaluate_ruby do
91 | foo = Foo.new
92 | foo.run_callback(:before_dinner)
93 | [ foo.a, foo.b ]
94 | end.to eq([10, 20])
95 | end
96 |
97 | it 'defines multiple callback group' do
98 | on_client do
99 | class Foo
100 | include React::Callbacks
101 | define_callback :before_dinner
102 | define_callback :after_dinner
103 | attr_accessor :a
104 |
105 | before_dinner do
106 | self.a = 10
107 | end
108 | end
109 | end
110 | expect_evaluate_ruby do
111 | foo = Foo.new
112 | foo.run_callback(:before_dinner)
113 | foo.run_callback(:after_dinner)
114 | foo.a
115 | end.to eq(10)
116 | end
117 |
118 | it 'receives args as callback' do
119 | on_client do
120 | class Foo
121 | include React::Callbacks
122 | define_callback :before_dinner
123 | define_callback :after_dinner
124 |
125 | attr_accessor :lorem
126 |
127 | before_dinner do |a, b|
128 | self.lorem = "#{a}-#{b}"
129 | end
130 |
131 | after_dinner :eat_ice_cream
132 | def eat_ice_cream(a,b,c); end
133 | end
134 | end
135 | expect_evaluate_ruby do
136 | foo = Foo.new
137 | foo.run_callback(:before_dinner, 1, 2)
138 | res1 = foo.run_callback(:after_dinner, 4, 5, 6)
139 | [res1, foo.lorem]
140 | end.to eq([["eat_ice_cream"], '1-2'])
141 | end
142 | end
143 |
--------------------------------------------------------------------------------
/spec/react/children_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'React::Children', js: true do
4 | describe 'with multiple child elements' do
5 | before :each do
6 | on_client do
7 | class InitTest
8 | def self.get_children
9 | component = Class.new do
10 | include React::Component
11 | def render
12 | div { 'lorem' }
13 | end
14 | end
15 | childs = [ React.create_element('a'), React.create_element('li') ]
16 | element = React.create_element(component) { childs }
17 | el_children = element.to_n.JS[:props].JS[:children]
18 | children = React::Children.new(el_children)
19 | dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
20 | React.render(element, dom_el)
21 | children
22 | end
23 | end
24 | end
25 | end
26 |
27 | it 'is enumerable' do
28 | expect_evaluate_ruby do
29 | InitTest.get_children.map { |elem| elem.element_type }
30 | end.to eq(['a', 'li'])
31 | end
32 |
33 | it 'returns an Enumerator when not providing a block' do
34 | expect_evaluate_ruby do
35 | nodes = InitTest.get_children.each
36 | [nodes.class.name, nodes.size]
37 | end.to eq(["Enumerator", 2])
38 | end
39 |
40 | describe '#each' do
41 | it 'returns an array of elements' do
42 | expect_evaluate_ruby do
43 | nodes = InitTest.get_children.each { |elem| elem.element_type }
44 | [nodes.class.name, nodes.map(&:class)]
45 | end.to eq(["Array", ["React::Element", "React::Element"]])
46 | end
47 | end
48 |
49 | describe '#length' do
50 | it 'returns the number of child elements' do
51 | expect_evaluate_ruby do
52 | InitTest.get_children.length
53 | end.to eq(2)
54 | end
55 | end
56 | end
57 |
58 | describe 'with single child element' do
59 | before :each do
60 | on_client do
61 | class InitTest
62 | def self.get_children
63 | component = Class.new do
64 | include React::Component
65 | def render
66 | div { 'lorem' }
67 | end
68 | end
69 | childs = [ React.create_element('a') ]
70 | element = React.create_element(component) { childs }
71 | el_children = element.to_n.JS[:props].JS[:children]
72 | children = React::Children.new(el_children)
73 | dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
74 | React.render(element, dom_el)
75 | children
76 | end
77 | end
78 | end
79 | end
80 |
81 | it 'is enumerable containing single element' do
82 | expect_evaluate_ruby do
83 | InitTest.get_children.map { |elem| elem.element_type }
84 | end.to eq(["a"])
85 | end
86 |
87 | describe '#length' do
88 | it 'returns the number of child elements' do
89 | expect_evaluate_ruby do
90 | InitTest.get_children.length
91 | end.to eq(1)
92 | end
93 | end
94 | end
95 |
96 | describe 'with no child element' do
97 | before :each do
98 | on_client do
99 | class InitTest
100 | def self.get_children
101 | component = Class.new do
102 | include React::Component
103 | def render
104 | div { 'lorem' }
105 | end
106 | end
107 | element = React.create_element(component)
108 | el_children = element.to_n.JS[:props].JS[:children]
109 | children = React::Children.new(el_children)
110 | dom_el = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
111 | React.render(element, dom_el)
112 | children
113 | end
114 | end
115 | end
116 | end
117 |
118 | it 'is enumerable containing no elements' do
119 | expect_evaluate_ruby do
120 | InitTest.get_children.map { |elem| elem.element_type }
121 | end.to eq([])
122 | end
123 |
124 | describe '#length' do
125 | it 'returns the number of child elements' do
126 | expect_evaluate_ruby do
127 | InitTest.get_children.length
128 | end.to eq(0)
129 | end
130 | end
131 | end
132 |
133 | describe 'other methods' do
134 | it 'responds to to_proc' do
135 | mount 'Children' do
136 | class ChildTester < Hyperloop::Component
137 | render do
138 | DIV(id: :tp, &children)
139 | end
140 | end
141 | class Children < Hyperloop::Component
142 | render do
143 | ChildTester { "one".span; "two".span; "three".span }
144 | end
145 | end
146 | end
147 | expect(page).to have_content('one')
148 | expect(page).to have_content('two')
149 | expect(page).to have_content('three')
150 | end
151 | it 'responds to render' do
152 | mount 'Children' do
153 | class ChildTester < Hyperloop::Component
154 | render do
155 | DIV(id: :tp) { children.render }
156 | end
157 | end
158 | class Children < Hyperloop::Component
159 | render do
160 | ChildTester { "one".span; "two".span; "three".span }
161 | end
162 | end
163 | end
164 | expect(page).to have_content('one')
165 | expect(page).to have_content('two')
166 | expect(page).to have_content('three')
167 | end
168 | end
169 | end
170 |
--------------------------------------------------------------------------------
/spec/react/component/base_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'React::Component::Base', js: true do
4 |
5 | before :each do
6 | on_client do
7 | class Foo < React::Component::Base
8 | before_mount do
9 | @instance_data = ["working"]
10 | end
11 | def render
12 | @instance_data.first
13 | end
14 | end
15 | end
16 | end
17 |
18 | it 'can create a simple component class' do
19 | mount 'Foo'
20 | expect(page.body[-50..-19]).to match(/working<\/span>/)
21 | end
22 |
23 | it 'can create a simple component class that can be inherited to create another component class' do
24 | mount 'Bar' do
25 | class Bar < Foo
26 | before_mount do
27 | @instance_data << "well"
28 | end
29 | def render
30 | @instance_data.join(" ")
31 | end
32 | end
33 | end
34 | expect(page.body[-50..-19]).to match(/working well<\/span>/)
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/spec/react/element_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe 'React::Element', js: true do
4 | it 'bridges `type` of native React.Element attributes' do
5 | expect_evaluate_ruby do
6 | element = React.create_element('div')
7 | element.element_type
8 | end.to eq("div")
9 | end
10 |
11 | it 'is renderable' do
12 | expect_evaluate_ruby do
13 | element = React.create_element('span')
14 | div = JS.call(:eval, 'document.createElement("div")')
15 | React.render(element, div)
16 | div.JS[:children].JS[0].JS[:tagName]
17 | end.to eq("SPAN")
18 | end
19 |
20 | describe "Event Subscription" do
21 | it "keeps the original params" do
22 | client_option render_on: :both
23 | mount 'Foo' do
24 | class Foo
25 | include React::Component
26 | def render
27 | INPUT(value: nil, type: 'text').on(:change) {}
28 | end
29 | end
30 | end
31 | expect(page.body[-285..-233]).to match(//)
32 | end
33 | end
34 |
35 | describe 'Component Event Subscription' do
36 |
37 | it 'will subscribe to a component event param' do
38 | evaluate_ruby do
39 | class Foo < React::Component::Base
40 | param :on_event, type: Proc, default: nil, allow_nil: true
41 | def render
42 | params.on_event
43 | end
44 | end
45 | React::Test::Utils.render_into_document(React.create_element(Foo).on(:event) {'works!'})
46 | end
47 | expect(page.body[-50..-19]).to include('works!')
48 | end
49 |
50 | it 'will subscribe to multiple component event params' do
51 | evaluate_ruby do
52 | class Foo < React::Component::Base
53 | param :on_event1, type: Proc, default: nil, allow_nil: true
54 | param :on_event2, type: Proc, default: nil, allow_nil: true
55 | def render
56 | params.on_event1+params.on_event2
57 | end
58 | end
59 | React::Test::Utils.render_into_document(React.create_element(Foo).on(:event1, :event2) {'works!'})
60 | end
61 | expect(page.body[-60..-19]).to include('works!works!')
62 | end
63 |
64 | it 'will subscribe to a native components event param' do
65 |
66 | evaluate_ruby do
67 | "this makes sure everything is loaded"
68 | end
69 | page.execute_script('window.NativeComponent = class extends React.Component {
70 | constructor(props) {
71 | super(props);
72 | this.displayName = "HelloMessage";
73 | }
74 | render() { return React.createElement("span", null, this.props.onEvent()); }
75 | }')
76 | evaluate_ruby do
77 | class Foo < React::Component::Base
78 | imports "NativeComponent"
79 | end
80 | React::Test::Utils.render_into_document(React.create_element(Foo).on(:event) {'works!'})
81 | true
82 | end
83 | expect(page.body[-60..-19]).to include('works!')
84 | end
85 |
86 | it 'will subscribe to a component event param with a non-default name' do
87 |
88 | evaluate_ruby do
89 | class Foo < React::Component::Base
90 | param :my_event, type: Proc, default: nil, allow_nil: true
91 | def render
92 | params.my_event
93 | end
94 | end
95 | React::Test::Utils.render_into_document(React.create_element(Foo).on("") {'works!'})
96 | end
97 | expect(page.body[-60..-19]).to include('works!')
98 | end
99 | end
100 |
101 | describe 'Builtin Event subscription' do
102 | it 'is subscribable through `on(:event_name)` method' do
103 | expect_evaluate_ruby do
104 | element = React.create_element("div").on(:click) { |event| RESULT_C = 'clicked' if event.is_a? React::Event }
105 | dom_node = React::Test::Utils.render_into_document(element)
106 | React::Test::Utils.simulate_click(dom_node)
107 | RESULT_C rescue 'not clicked'
108 | end.to eq('clicked')
109 |
110 | expect_evaluate_ruby do
111 | element = React.create_element("div").on(:key_down) { |event| RESULT_P = 'pressed' if event.is_a? React::Event }
112 | dom_node = React::Test::Utils.render_into_document(element)
113 | React::Test::Utils.simulate_keydown(dom_node, "Enter")
114 | RESULT_P rescue 'not pressed'
115 | end.to eq('pressed')
116 |
117 | expect_evaluate_ruby do
118 | element = React.create_element("form").on(:submit) { |event| RESULT_S = 'submitted' if event.is_a? React::Event }
119 | dom_node = React::Test::Utils.render_into_document(element)
120 | React::Test::Utils.simulate_submit(dom_node)
121 | RESULT_S rescue 'not submitted'
122 | end.to eq('submitted')
123 | end
124 |
125 | it 'returns self for `on` method' do
126 | expect_evaluate_ruby do
127 | element = React.create_element("div")
128 | element.on(:click){} == element
129 | end.to be_truthy
130 | end
131 | end
132 | end
133 |
--------------------------------------------------------------------------------
/spec/react/event_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe 'React::Event', js: true do
4 | it "should bridge attributes of native SyntheticEvent (see http://facebook.github.io/react/docs/events.html#syntheticevent)" do
5 | expect_evaluate_ruby do
6 | results = {}
7 | element = React.create_element('div').on(:click) do |event|
8 | results[:bubbles] = event.bubbles == event.to_n.JS[:bubbles]
9 | results[:cancelable] = event.cancelable == event.to_n.JS[:cancelable]
10 | results[:current_target] = event.current_target == event.to_n.JS[:currentTarget]
11 | results[:default_prevented] = event.default_prevented == event.to_n.JS[:defaultPrevented]
12 | results[:event_phase] = event.event_phase == event.to_n.JS[:eventPhase]
13 | results[:is_trusted?] = event.is_trusted? == event.to_n.JS[:isTrusted]
14 | results[:native_event] = event.native_event == event.to_n.JS[:nativeEvent]
15 | results[:target] = event.target == event.to_n.JS[:target]
16 | results[:timestamp] = event.timestamp == event.to_n.JS[:timeStamp]
17 | results[:event_type] = event.event_type == event.to_n.JS[:type]
18 | results[:prevent_default] = event.respond_to?(:prevent_default)
19 | results[:stop_propagation] = event.respond_to?(:stop_propagation)
20 | end
21 | dom_node = React::Test::Utils.render_into_document(element)
22 | React::Test::Utils.simulate_click(dom_node)
23 | results
24 | end.to eq({
25 | 'bubbles' => true,
26 | 'cancelable' => true,
27 | 'current_target' => true,
28 | 'default_prevented' => true,
29 | 'event_phase' => true,
30 | 'is_trusted?' => true,
31 | 'native_event' => true,
32 | 'target' => true,
33 | 'timestamp' => true,
34 | 'event_type' => true,
35 | 'prevent_default' => true,
36 | 'stop_propagation' => true
37 | })
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/react/observable_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'React::Observable', js: true do
4 | it "allows to set value on Observable" do
5 | mount 'Foo' do
6 | class Zoo
7 | include React::Component
8 | param :foo, type: React::Observable
9 | before_mount do
10 | params.foo! 4
11 | end
12 |
13 | def render
14 | nil
15 | end
16 | end
17 |
18 | class Foo
19 | include React::Component
20 |
21 | def render
22 | div do
23 | Zoo(foo: state.foo! )
24 | span { state.foo.to_s }
25 | end
26 | end
27 | end
28 | end
29 | expect(page.body[-60..-19]).to include('4')
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/spec/react/opal_jquery_extensions_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'opal-jquery extensions', js: true do
4 | describe 'Element' do
5 | xit 'will reuse the wrapper component class for the same Element' do
6 | evaluate_ruby do
7 | class Foo < React::Component::Base
8 | param :name
9 | def render
10 | "hello #{params.name}"
11 | end
12 |
13 | def self.rec_cnt
14 | @@rec_cnt ||= 0
15 | end
16 | before_unmount do
17 | @@rec_cnt ||= 0
18 | @@rec_cnt += 1
19 | end
20 | end
21 | end
22 | expect_evaluate_ruby do
23 | test_div = Element.new(:div)
24 | test_div.render { Foo(name: 'fred') }
25 | test_div.render { Foo(name: 'freddy') }
26 | [ Element[test_div].find('span').html, Foo.rec_cnt]
27 | end.to eq(['hello freddy', 0])
28 | end
29 |
30 | it 'renders a top level component using render with a block' do
31 | expect_evaluate_ruby do
32 | class Foo < React::Component::Base
33 | param :name
34 | def render
35 | "hello #{params.name}"
36 | end
37 | end
38 | test_div = Element.new(:div)
39 | test_div.render { Foo(name: 'fred') }
40 | Element[test_div].find('span').html
41 | end.to eq('hello fred')
42 | end
43 |
44 | it 'renders a top level component using render with a container and params ' do
45 | expect_evaluate_ruby do
46 | test_div = Element.new(:div)
47 | test_div.render(:span, id: :render_test_span) { 'hello' }
48 | Element[test_div].find('#render_test_span').html
49 | end.to eq('hello')
50 | end
51 |
52 | it 'will find the DOM node given a react element' do
53 | expect_evaluate_ruby do
54 | class Foo < React::Component::Base
55 | def render
56 | div { 'hello' }
57 | end
58 | end
59 | Element[React::Test::Utils.render_component_into_document(Foo)].html
60 | end.to eq('hello')
61 | end
62 |
63 | it "accepts plain js object as selector" do
64 | evaluate_ruby do
65 | Element[JS.call(:eval, "(function () { return window; })();")]
66 | end
67 | expect(page.driver.browser.manage.logs.get(:browser).map { |m| m.message.gsub(/\\n/, "\n") }.to_a.join("\n"))
68 | .not_to match(/Exception|Error/)
69 | end
70 |
71 | it "can dynamically mount components" do
72 | on_client do
73 | class DynoMount < Hyperloop::Component
74 | render(DIV) { 'I got rendered' }
75 | end
76 | end
77 | mount 'MountPoint' do
78 | class MountPoint < Hyperloop::Component
79 | render(DIV) do
80 | # simulate what react-rails render_component output
81 | DIV(
82 | 'data-react-class' => 'React.TopLevelRailsComponent',
83 | 'data-react-props' => '{"render_params": {}, "component_name": "DynoMount", "controller": ""}'
84 | )
85 | end
86 | end
87 | end
88 | evaluate_ruby do
89 | Element['body'].mount_components
90 | end
91 | expect(page).to have_content('I got rendered')
92 | end
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/spec/react/refs_callback_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Refs callback', js: true do
4 | before do
5 | on_client do
6 | class Foo
7 | include React::Component
8 | def self.bar
9 | @@bar
10 | end
11 | def self.bar=(club)
12 | @@bar = club
13 | end
14 | end
15 | end
16 | end
17 |
18 | it "is invoked with the actual Ruby instance" do
19 | expect_evaluate_ruby do
20 | class Bar
21 | include React::Component
22 | def render
23 | React.create_element('div')
24 | end
25 | end
26 |
27 | Foo.class_eval do
28 | def my_bar=(bars)
29 | Foo.bar = bars
30 | end
31 |
32 | def render
33 | React.create_element(Bar, ref: method(:my_bar=).to_proc)
34 | end
35 | end
36 |
37 | element = React.create_element(Foo)
38 | React::Test::Utils.render_into_document(element)
39 | begin
40 | "#{Foo.bar.class.name}"
41 | rescue
42 | "Club"
43 | end
44 | end.to eq('Bar')
45 | end
46 |
47 | it "is invoked with the actual DOM node" do
48 | # client_option raise_on_js_errors: :off
49 | expect_evaluate_ruby do
50 | Foo.class_eval do
51 | def my_div=(div)
52 | Foo.bar = div
53 | end
54 |
55 | def render
56 | React.create_element('div', ref: method(:my_div=).to_proc)
57 | end
58 | end
59 |
60 | element = React.create_element(Foo)
61 | React::Test::Utils.render_into_document(element)
62 | "#{Foo.bar.JS['nodeType']}" # avoids json serialisation errors by using "#{}"
63 | end.to eq("1")
64 | end
65 |
66 | it "works, even when the component is unmounted" do
67 | # was a bug, on unmount react calls the ref method with null instead of a dom node
68 | # callback failed then
69 | # ref is called two times, once on mount with dom_node, once on unmount with null
70 | mount "Foo" do
71 | class Unmountable < Hyperloop::Component
72 | render do
73 | DIV { "This is a Component" }
74 | end
75 | end
76 | Foo.class_eval do
77 | def ref_rec(dom_node)
78 | @@rec_cnt ||= 0
79 | @@rec_cnt += 1
80 | end
81 | def self.rec_cnt
82 | @@rec_cnt
83 | end
84 |
85 | after_mount { mutate.unmount true }
86 |
87 | render do
88 | Unmountable(ref: method(:ref_rec).to_proc) unless state.unmount
89 | end
90 | end
91 | end
92 | expect_evaluate_ruby('Foo.rec_cnt').to eq(2)
93 | end
94 | end
95 |
--------------------------------------------------------------------------------
/spec/react/server_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe 'React::Server', js: true do
4 |
5 | describe "render_to_string" do
6 | it "should render a React.Element to string" do
7 | client_option render_on: :both
8 | expect_evaluate_ruby do
9 | ele = React.create_element('span') { "lorem" }
10 | React::Server.render_to_string(ele).class.name
11 | end.to eq("String")
12 | end
13 | end
14 |
15 | describe "render_to_static_markup" do
16 | it "should render a React.Element to static markup" do
17 | client_option render_on: :both
18 | expect_evaluate_ruby do
19 | ele = React.create_element('span') { "lorem" }
20 | React::Server.render_to_static_markup(ele)
21 | end.to eq("lorem")
22 | end
23 | end
24 | end
25 |
26 |
--------------------------------------------------------------------------------
/spec/react/state_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'React::State', js: true do
4 | it "can create dynamically initialized exported states" do
5 | expect_evaluate_ruby do
6 | class Foo
7 | include React::Component
8 | export_state(:foo) { 'bar' }
9 | end
10 | Hyperloop::Application::Boot.run
11 | Foo.foo
12 | end.to eq('bar')
13 | end
14 |
15 | # these will all require async operations and testing to see if things get
16 | # re-rendered see spec_helper the "render" test method
17 |
18 | # if Foo.foo is used during rendering then when Foo.foo changes we will
19 | # rerender
20 | it "sets up observers when exported states are read"
21 |
22 | # React::State.set_state(object, attribute, value) +
23 | # React::State.get_state(object, attribute)
24 | it "can be accessed outside of react using get/set_state"
25 |
26 | it 'ignores state updates during rendering' do
27 | client_option render_on: :both
28 | evaluate_ruby do
29 | class StateTest < React::Component::Base
30 | export_state :boom
31 | before_mount do
32 | # force boom to be on the observing list during the current rendering cycle
33 | StateTest.boom! !StateTest.boom
34 | # this is automatically called by after_mount / after_update, but we don't want
35 | # to have to setup a complicated async test, so we just force it now.
36 | # if we don't do this, then updating boom will have no effect on the first render
37 | React::State.update_states_to_observe
38 | end
39 | def render
40 | (StateTest.boom ? "Boom" : "No Boom").tap { StateTest.boom! !StateTest.boom }
41 | end
42 | end
43 | MARKUP = React::Server.render_to_static_markup(React.create_element(StateTest))
44 | end
45 | expect_evaluate_ruby("MARKUP").to eq('Boom')
46 | expect_evaluate_ruby("StateTest.boom").to be_falsy
47 | expect(page.driver.browser.manage.logs.get(:browser).reject { |entry|
48 | entry_s = entry.to_s
49 | entry_s.include?("Deprecated feature") || entry_s.include?("Mount() on the server. This is a no-op.")
50 | }.size).to eq(0)
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/react/test/dsl_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | if RUBY_ENGINE == 'opal'
4 | RSpec.describe React::Test::DSL do
5 | describe 'the DSL' do
6 | let(:session) { Class.new { include React::Test::DSL }.new }
7 |
8 | before do
9 | React::Test.reset_session!
10 |
11 | stub_const 'Greeter', Class.new
12 | Greeter.class_eval do
13 | include React::Component
14 |
15 | params do
16 | optional :message
17 | optional :from
18 | end
19 |
20 | def render
21 | span { "Hello #{params.message}" }
22 | end
23 | end
24 | end
25 |
26 | it 'is possible to include it in another class' do
27 | session.mount(Greeter)
28 | expect(session.instance).to be_a(Greeter)
29 | end
30 |
31 | it "should provide a 'component' shortcut for more expressive tests" do
32 | session.component.mount(Greeter)
33 | expect(session.component.instance).to be_a(Greeter)
34 | end
35 |
36 | React::Test::Session::DSL_METHODS.each do |method|
37 | it "responds to all DSL method: #{method}" do
38 | expect(session).to respond_to(method)
39 | end
40 | end
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/spec/react/test/matchers/render_html_matcher_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | if RUBY_ENGINE == 'opal'
4 | describe React::Test::Matchers::RenderHTMLMatcher do
5 | let(:component) {
6 | Class.new do
7 | include React::Component
8 | params do
9 | optional :string
10 | end
11 | def render
12 | div do
13 | span { params.string } if params.string
14 | 'lorem'
15 | end
16 | end
17 | end
18 | }
19 | let(:expected) { '
lorem
' }
20 | let(:matcher) { described_class.new(expected) }
21 |
22 | describe '#matches?' do
23 | it 'is truthy when rendered component html equals expected html' do
24 | expect(matcher.matches?(component)).to be_truthy
25 | end
26 |
27 | it 'is falsey when rendered component html does not equal expected html' do
28 | matcher = described_class.new('foo')
29 | expect(matcher.matches?(component)).to be_falsey
30 | end
31 | end
32 |
33 | describe '#with_params' do
34 | let(:expected) { '
strlorem
' }
35 |
36 | it 'renders the component with the given params' do
37 | matcher.with_params(string: 'str')
38 | expect(matcher.matches?(component)).to be_truthy
39 | end
40 | end
41 |
42 | describe '#failure_message' do
43 | let(:expected) { '
strlorem
' }
44 |
45 | it 'includes the name of the component' do
46 | stub_const 'Foo', component
47 | matcher.matches?(Foo)
48 | expect(matcher.failure_message).to match(/expected 'Foo'/)
49 | end
50 |
51 | it 'includes the params hash' do
52 | matcher.with_params(string: 'bar')
53 | matcher.matches?(component)
54 | expect(matcher.failure_message).to match(/with params '{"string"=>"bar"}'/)
55 | end
56 |
57 | it 'includes the expected html value' do
58 | matcher.matches?(component)
59 | expect(matcher.failure_message).to match(/to render '#{expected}'/)
60 | end
61 |
62 | it 'includes the actual html value' do
63 | actual = '
lorem<\/div>'
64 | matcher.matches?(component)
65 | expect(matcher.failure_message).to match(/, but '#{actual}' was rendered/)
66 | end
67 |
68 | it 'does not include "to not render"' do
69 | matcher.matches?(component)
70 | expect(matcher.failure_message).to_not match(/to not render/)
71 | end
72 | end
73 |
74 | describe '#negative_failure_message' do
75 | let(:expected) { '
strlorem
' }
76 |
77 | it 'includes "to not render"' do
78 | matcher.matches?(component)
79 | expect(matcher.negative_failure_message).to match(/to not render/)
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/spec/react/test/rspec_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | if RUBY_ENGINE == 'opal'
4 | RSpec.describe 'react/test/rspec', type: :component do
5 | before do
6 | stub_const 'Greeter', Class.new
7 | Greeter.class_eval do
8 | include React::Component
9 | params do
10 | optional :message
11 | optional :from
12 | end
13 |
14 | def render
15 | span { "Hello #{params.message}" }
16 | end
17 | end
18 | end
19 |
20 | it 'should include react/test in rspec' do
21 | comp = mount(Greeter)
22 | expect(component.instance).to eq(comp)
23 | end
24 |
25 | it 'includes rspec matchers' do
26 | expect(Greeter).to render_static_html(
27 | 'Hello world'
28 | ).with_params(message: 'world')
29 | end
30 |
31 | describe 'resetting the session' do
32 | it 'creates an instance of the mounted component in one example' do
33 | mount(Greeter)
34 | end
35 |
36 | it '...then is not availalbe in the next' do
37 | expect { component.instance }.to raise_error
38 | end
39 | end
40 | end
41 |
42 | RSpec.describe 'react/test/rspec', type: :other do
43 | before do
44 | stub_const 'Greeter', Class.new
45 | Greeter.class_eval do
46 | include React::Component
47 | params do
48 | optional :message
49 | optional :from
50 | end
51 |
52 | def render
53 | span { "Hello #{params.message}" }
54 | end
55 | end
56 | end
57 |
58 | it 'should not include react/test in rspec' do
59 | expect { mount(Greeter) }.to raise_error
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/spec/react/test/session_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | if RUBY_ENGINE == 'opal'
4 | RSpec.describe React::Test::Session do
5 | subject { described_class.new }
6 | before do
7 | stub_const 'Greeter', Class.new
8 | Greeter.class_eval do
9 | include React::Component
10 |
11 | params do
12 | optional :message
13 | optional :from
14 | end
15 |
16 | def render
17 | span { "Hello #{params.message}" }
18 | end
19 | end
20 | end
21 |
22 | describe '#mount' do
23 | it 'returns an instance of the mounted component' do
24 | expect(subject.mount(Greeter)).to be_a(Greeter)
25 | end
26 |
27 | it 'actualy mounts the component' do
28 | expect(subject.mount(Greeter)).to be_mounted
29 | end
30 |
31 | it 'optionaly passes params to the component' do
32 | instance = subject.mount(Greeter, message: 'world')
33 | expect(instance.params.message).to eq('world')
34 | end
35 | end
36 |
37 | describe '#instance' do
38 | it 'returns the instance of the mounted component' do
39 | instance = subject.mount(Greeter)
40 | expect(subject.instance).to eq(instance)
41 | end
42 | end
43 |
44 | describe '#html' do
45 | it 'returns the component rendered to static html' do
46 | subject.mount(Greeter, message: 'world')
47 | expect(subject.html).to eq('Hello world')
48 | end
49 |
50 | async 'returns the updated static html' do
51 | subject.mount(Greeter)
52 | subject.update_params(message: 'moon') do
53 | run_async {
54 | expect(subject.html).to eq('Hello moon')
55 | }
56 | end
57 | end
58 | end
59 |
60 | describe '#update_params' do
61 | it 'sends new params to the component' do
62 | instance = subject.mount(Greeter, message: 'world')
63 | subject.update_params(message: 'moon')
64 | expect(instance.params.message).to eq('moon')
65 | end
66 |
67 | it 'leaves unspecified params in tact' do
68 | instance = subject.mount(Greeter, message: 'world', from: 'outerspace')
69 | subject.update_params(message: 'moon')
70 | expect(instance.params.from).to eq('outerspace')
71 | end
72 |
73 | it 'causes the component to render' do
74 | instance = subject.mount(Greeter, message: 'world')
75 | expect(instance).to receive(:render)
76 | subject.update_params(message: 'moon')
77 | end
78 | end
79 |
80 | describe 'instance#force_update!' do
81 | it 'causes the component to render' do
82 | instance = subject.mount(Greeter)
83 | expect(instance).to receive(:render)
84 | subject.instance.force_update!
85 | end
86 | end
87 | end
88 | end
89 |
--------------------------------------------------------------------------------
/spec/react/test/utils_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | if RUBY_ENGINE == 'opal'
4 | RSpec.describe React::Test::Utils do
5 | it 'simulates' do
6 | stub_const 'Foo', Class.new
7 | Foo.class_eval do
8 | include React::Component
9 |
10 | def render
11 | div { 'Click Me' }.on(:click) { |e| click(e) }
12 | end
13 | end
14 |
15 | instance = React::Test::Utils.render_into_document(React.create_element(Foo))
16 | expect(instance).to receive(:click)
17 | described_class.simulate(:click, instance.dom_node)
18 | end
19 |
20 | describe "render_into_document" do
21 | it "works with native element" do
22 | expect {
23 | described_class.render_into_document(React.create_element('div'))
24 | }.to_not raise_error
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/react/to_key_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'to_key helper', js: true do
4 | it "has added 'to_key' method to Object and each key is different" do
5 | expect_evaluate_ruby do
6 | Object.new.to_key != Object.new.to_key
7 | end.to be_truthy
8 | end
9 |
10 | it "to_key return 'self' for String objects" do
11 | expect_evaluate_ruby do
12 | debugger
13 | "hello".to_key == "hello"
14 | end.to be_truthy
15 | end
16 |
17 | it "to_key return 'self' for Number objects" do
18 | expect_evaluate_ruby do
19 | 12.to_key == 12
20 | end.to be_truthy
21 | end
22 |
23 | it "to_key return 'self' for Boolean objects" do
24 | expect_evaluate_ruby do
25 | true.to_key == true && false.to_key == false
26 | end.to be_truthy
27 | end
28 |
29 | it "will use the use the to_key method to get the react key" do
30 | mount "TestComponent" do
31 | class MyTestClass
32 | attr_reader :to_key_called
33 | def to_key
34 | @to_key_called = true
35 | super
36 | end
37 | end
38 | class TestComponent < Hyperloop::Component
39 | before_mount { @test_object = MyTestClass.new }
40 | render do
41 | DIV(key: @test_object) { TestComponent2(test_object: @test_object) }
42 | end
43 | end
44 | class TestComponent2 < Hyperloop::Component
45 | param :test_object
46 | render do
47 | "to key was called!" if params.test_object.to_key_called
48 | end
49 | end
50 | end
51 | expect(page).to have_content('to key was called!')
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/spec/react/top_level_component_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 |
4 | describe 'React::TopLevelRailsComponent', js: true do
5 | before :each do
6 | on_client do
7 | module Components
8 | module Controller
9 | class Component1
10 | include React::Component
11 | def render
12 | self.class.name.to_s
13 | end
14 | end
15 | end
16 |
17 | class Component1
18 | include React::Component
19 | def render
20 | self.class.name.to_s
21 | end
22 | end
23 |
24 | class Component2
25 | include React::Component
26 | def render
27 | self.class.name.to_s
28 | end
29 | end
30 | end
31 |
32 | module Controller
33 | class SomeOtherClass # see issue #80
34 | end
35 | end
36 |
37 | class Component1
38 | include React::Component
39 | def render
40 | self.class.name.to_s
41 | end
42 | end
43 |
44 | def render_top_level(controller, component_name)
45 | params = {
46 | controller: controller,
47 | component_name: component_name,
48 | render_params: {}
49 | }
50 | component = React::Test::Utils.render_component_into_document(React::TopLevelRailsComponent, params)
51 | component.dom_node.JS[:outerHTML]
52 | end
53 | end
54 | end
55 |
56 | it 'uses the controller name to lookup a component' do
57 | expect_evaluate_ruby('render_top_level("Controller", "Component1")').to eq('Components::Controller::Component1')
58 | end
59 |
60 | it 'can find the name without matching the controller' do
61 | expect_evaluate_ruby('render_top_level("Controller", "Component2")').to eq('Components::Component2')
62 | end
63 |
64 | it 'will find the outer most matching component' do
65 | expect_evaluate_ruby('render_top_level("OtherController", "Component1")').to eq('Component1')
66 | end
67 |
68 | it 'can find the correct component when the name is fully qualified' do
69 | expect_evaluate_ruby('render_top_level("Controller", "::Components::Component1")').to eq('Components::Component1')
70 | end
71 |
72 | describe '.html_tag?' do
73 | it 'is truthy for valid html tags' do
74 | expect_evaluate_ruby('React.html_tag?("a")').to be_truthy
75 | expect_evaluate_ruby('React.html_tag?("div")').to be_truthy
76 | end
77 |
78 | it 'is truthy for valid svg tags' do
79 | expect_evaluate_ruby('React.html_tag?("svg")').to be_truthy
80 | expect_evaluate_ruby('React.html_tag?("circle")').to be_truthy
81 | end
82 |
83 | it 'is falsey for invalid tags' do
84 | expect_evaluate_ruby('React.html_tag?("tagizzle")').to be_falsey
85 | end
86 | end
87 |
88 | describe '.html_attr?' do
89 | it 'is truthy for valid html attributes' do
90 | expect_evaluate_ruby('React.html_attr?("id")').to be_truthy
91 | expect_evaluate_ruby('React.html_attr?("data")').to be_truthy
92 | end
93 |
94 | it 'is truthy for valid svg attributes' do
95 | expect_evaluate_ruby('React.html_attr?("cx")').to be_truthy
96 | expect_evaluate_ruby('React.html_attr?("strokeWidth")').to be_truthy
97 | end
98 |
99 | it 'is falsey for invalid attributes' do
100 | expect_evaluate_ruby('React.html_tag?("attrizzle")').to be_falsey
101 | end
102 | end
103 | end
104 |
--------------------------------------------------------------------------------
/spec/react/tutorial/tutorial_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'An Example from the react.rb doc', js: true do
4 | it 'produces the correct result' do
5 | mount 'HelloMessage' do
6 | class HelloMessage
7 | include React::Component
8 | def render
9 | div { "Hello World!" }
10 | end
11 | end
12 | end
13 | expect(page).to have_xpath('//div', text: 'Hello World!')
14 | end
15 | end
16 |
17 | describe 'Adding state to a component (second tutorial example)', js: true do
18 | before :each do
19 | on_client do
20 | class HelloMessage2
21 | include React::Component
22 | define_state(:user_name) { '@catmando' }
23 | def render
24 | div { "Hello #{state.user_name}" }
25 | end
26 | end
27 | end
28 | end
29 |
30 | it "produces the correct result" do
31 | mount 'HelloMessage2'
32 | expect(page).to have_xpath('//div', text: 'Hello @catmando')
33 | end
34 |
35 | it 'renders to the document' do
36 | evaluate_ruby do
37 | ele = JS.call(:eval, "document.body.appendChild(document.createElement('div'))")
38 | React.render(React.create_element(HelloMessage2), ele)
39 | end
40 | expect(page).to have_xpath('//div', text: 'Hello @catmando')
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/react/validator_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe 'React::Validator', js: true do
4 | describe '#validate' do
5 | describe "Presence validation" do
6 | it "should check if required props provided" do
7 | evaluate_ruby do
8 | VALIDATOR = React::Validator.new.build do
9 | requires :foo
10 | requires :bar
11 | end
12 | end
13 | expect_evaluate_ruby('VALIDATOR.validate({})').to eq(["Required prop `foo` was not specified", "Required prop `bar` was not specified"])
14 | expect_evaluate_ruby('VALIDATOR.validate({foo: 1, bar: 3})').to eq([])
15 | end
16 |
17 | it "should check if passed non specified prop" do
18 | evaluate_ruby do
19 | VALIDATOR = React::Validator.new.build do
20 | optional :foo
21 | end
22 | end
23 | expect_evaluate_ruby('VALIDATOR.validate({bar: 10})').to eq(["Provided prop `bar` not specified in spec"])
24 | expect_evaluate_ruby('VALIDATOR.validate({foo: 10})').to eq([])
25 | end
26 | end
27 |
28 | describe "Type validation" do
29 | it "should check if passed value with wrong type" do
30 | evaluate_ruby do
31 | VALIDATOR = React::Validator.new.build do
32 | requires :foo, type: String
33 | end
34 | end
35 | expect_evaluate_ruby('VALIDATOR.validate({foo: 10})').to eq(["Provided prop `foo` could not be converted to String"])
36 | expect_evaluate_ruby('VALIDATOR.validate({foo: "10"})').to eq([])
37 | end
38 |
39 | it "should check if passed value with wrong custom type" do
40 | evaluate_ruby do
41 | class Bar; end
42 | VALIDATOR = React::Validator.new.build do
43 | requires :foo, type: Bar
44 | end
45 | end
46 | expect_evaluate_ruby('VALIDATOR.validate({foo: 10})').to eq(["Provided prop `foo` could not be converted to Bar"])
47 | expect_evaluate_ruby('VALIDATOR.validate({foo: Bar.new})').to eq([])
48 | end
49 |
50 | it 'coerces native JS prop types to opal objects' do
51 | evaluate_ruby do
52 | VALIDATOR = React::Validator.new.build do
53 | requires :foo, type: JS.call(:eval, "(function () { return { x: 1 }; })();")
54 | end
55 | end
56 | expect_evaluate_ruby('VALIDATOR.validate({foo: `{ x: 1 }`})').to eq(["Provided prop `foo` could not be converted to [object Object]"])
57 | end
58 |
59 | it 'coerces native JS values to opal objects' do
60 | evaluate_ruby do
61 | VALIDATOR = React::Validator.new.build do
62 | requires :foo, type: Array[Integer]
63 | end
64 | end
65 | expect_evaluate_ruby('VALIDATOR.validate({foo: `[ { x: 1 } ]`})').to eq(["Provided prop `foo`[0] could not be converted to #{Integer.name}"])
66 | end
67 |
68 | it "should support Array[Class] validation" do
69 | evaluate_ruby do
70 | VALIDATOR = React::Validator.new.build do
71 | requires :foo, type: Array[Hash]
72 | end
73 | end
74 | expect_evaluate_ruby('VALIDATOR.validate({foo: [1,"2",3]})').to eq(
75 | [
76 | "Provided prop `foo`[0] could not be converted to Hash",
77 | "Provided prop `foo`[1] could not be converted to Hash",
78 | "Provided prop `foo`[2] could not be converted to Hash"
79 | ]
80 | )
81 | expect_evaluate_ruby('VALIDATOR.validate({foo: [{},{},{}]})').to eq([])
82 | end
83 | end
84 |
85 | describe "Limited values" do
86 | it "should check if passed value is not one of the specified values" do
87 | evaluate_ruby do
88 | VALIDATOR = React::Validator.new.build do
89 | requires :foo, values: [4,5,6]
90 | end
91 | end
92 | expect_evaluate_ruby('VALIDATOR.validate({foo: 3})').to eq(["Value `3` for prop `foo` is not an allowed value"])
93 | expect_evaluate_ruby('VALIDATOR.validate({foo: 4})').to eq([])
94 | end
95 | end
96 | end
97 |
98 | it 'collects other params into a hash' do
99 | evaluate_ruby do
100 | PROPS = { foo: 'foo', bar: 'bar', biz: 'biz', baz: 'baz' }
101 | VALIDATOR = React::Validator.new.build do
102 | requires :foo
103 | optional :bar
104 | all_other_params :baz
105 | end
106 | class Dummy
107 | def props
108 | PROPS
109 | end
110 | end
111 | end
112 | expect_evaluate_ruby('VALIDATOR.validate(PROPS)').to eq([])
113 | expect_evaluate_ruby('VALIDATOR.props_wrapper.new(Dummy.new).baz').to eq({ "biz" => 'biz', "baz" => 'baz' })
114 | end
115 |
116 | describe "default_props" do
117 | it "should return specified default values" do
118 | evaluate_ruby do
119 | VALIDATOR = React::Validator.new.build do
120 | requires :foo, default: 10
121 | requires :bar
122 | optional :lorem, default: 20
123 | end
124 | end
125 | expect_evaluate_ruby('VALIDATOR.default_props').to eq({"foo" => 10, "lorem" => 20})
126 | end
127 | end
128 | end
129 |
--------------------------------------------------------------------------------
/spec/reactive-ruby/component_loader_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe ReactiveRuby::ComponentLoader do
4 | GLOBAL_WRAPPER = <<-JS
5 | #{React::ServerRendering::ExecJSRenderer::GLOBAL_WRAPPER}
6 | var console = {
7 | warn: function(s) { }
8 | };
9 | JS
10 |
11 | let(:js) do
12 | if ::Rails.application.assets['react-server.js']
13 | react_source = ::Rails.application.assets['react-server.js']
14 | else
15 | react_source = ::Rails.application.assets['react.js']
16 | end
17 | ::Rails.application.assets['components'].to_s + react_source.to_s
18 | end
19 | let(:context) { ExecJS.compile(GLOBAL_WRAPPER + js) }
20 | let(:v8_context) { ReactiveRuby::ServerRendering.context_instance_for(context) }
21 |
22 | describe '.new' do
23 | it 'raises a meaningful exception when initialized without a context' do
24 | expect {
25 | described_class.new(nil)
26 | }.to raise_error(/Could not obtain ExecJS runtime context/)
27 | end
28 | end
29 |
30 | describe '#load' do
31 | xit 'loads given asset file into context' do
32 | loader = described_class.new(v8_context)
33 |
34 | expect {
35 | loader.load('components')
36 | }.to change { !!v8_context.eval('Opal.React !== undefined') }.from(false).to(true)
37 | end
38 |
39 | xit 'is truthy upon successful load' do
40 | loader = described_class.new(v8_context)
41 | expect(loader.load('components')).to be_truthy
42 | end
43 |
44 | xit 'fails silently returning false' do
45 | loader = described_class.new(v8_context)
46 | expect(loader.load('foo')).to be_falsey
47 | end
48 | end
49 |
50 | describe '#load!' do
51 | xit 'is truthy upon successful load' do
52 | loader = described_class.new(v8_context)
53 | expect(loader.load!('components')).to be_truthy
54 | end
55 |
56 | xit 'raises an expection if loading fails' do
57 | loader = described_class.new(v8_context)
58 | expect { loader.load!('foo') }.to raise_error(/No HyperReact components/)
59 | end
60 | end
61 |
62 | describe '#loaded?' do
63 | xit 'is truthy if components file is already loaded' do
64 | loader = described_class.new(v8_context)
65 | loader.load('components')
66 | expect(loader).to be_loaded
67 | end
68 |
69 | xit 'is false if components file is not loaded' do
70 | loader = described_class.new(v8_context)
71 | expect(loader).to_not be_loaded
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/spec/reactive-ruby/isomorphic_helpers_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe React::IsomorphicHelpers do
4 | describe 'code execution context' do
5 | let(:klass) { Class.send(:include, described_class) }
6 |
7 | describe 'module class methods', :opal do
8 | it { expect(described_class).to_not be_on_opal_server }
9 | it { expect(described_class).to be_on_opal_client }
10 | end
11 |
12 | describe 'included class methods', :opal do
13 | it { expect(klass).to_not be_on_opal_server }
14 | it { expect(klass).to be_on_opal_client }
15 | end
16 |
17 | describe 'included instance methods', :opal do
18 | it { expect(klass.new).to_not be_on_opal_server }
19 | it { expect(klass.new).to be_on_opal_client }
20 | end
21 |
22 | describe 'module class methods', :ruby do
23 | it { is_expected.to_not be_on_opal_server }
24 | it { is_expected.to_not be_on_opal_client }
25 | end
26 |
27 | describe 'included class methods', :ruby do
28 | subject { klass }
29 | it { is_expected.to_not be_on_opal_server }
30 | it { is_expected.to_not be_on_opal_client }
31 | end
32 |
33 | describe 'included instance methods', :ruby do
34 | subject { klass.new }
35 | it { is_expected.to_not be_on_opal_server }
36 | it { is_expected.to_not be_on_opal_client }
37 | end
38 | end
39 |
40 | describe 'load_context', :ruby do
41 | let(:v8_context) { TestV8Context.new }
42 | let(:controller) { double('controller') }
43 | let(:name) { double('name') }
44 |
45 | it 'creates a context and sets a controller' do
46 | context = described_class.load_context(v8_context, controller, name)
47 | expect(context.controller).to eq(controller)
48 | end
49 |
50 | it 'creates a context and sets a unique_id', js: true do
51 | # this tests loads the prerender context and somehow trys evaluate_ruby, works only with above js: true
52 | # TODO this is triggered by TimeCop for some reason
53 | Timecop.freeze do
54 | stamp = Time.now.to_i
55 | context = described_class.load_context(v8_context, controller, name)
56 | expect(context.unique_id).to eq("#{ controller.object_id }-#{ stamp }")
57 | end
58 | end
59 | end
60 |
61 | describe React::IsomorphicHelpers::Context do
62 | class TestV8Context < Hash
63 | def eval(args)
64 | true
65 | end
66 | def attach(*args)
67 | true
68 | end
69 | end
70 |
71 | # Need to decouple/dry up this...
72 | def test_context(files = nil)
73 | js = ReactiveRuby::ServerRendering::ContextualRenderer::CONSOLE_POLYFILL.dup
74 | js << Opal::Builder.build('opal').to_s
75 | Array(files).each do |filename|
76 | js << ::Rails.application.assets[filename].to_s
77 | end
78 | js = "#{React::ServerRendering::ExecJSRenderer::GLOBAL_WRAPPER}#{js}"
79 | ctx = ExecJS.compile(js)
80 | ctx = ReactiveRuby::ServerRendering.context_instance_for(ctx)
81 | end
82 |
83 | def react_context
84 | if ::Rails.application.assets['react-server.js']
85 | test_context(['server_rendering.js', 'react-server.js'])
86 | else
87 | test_context(['components', 'react.js'])
88 | end
89 | end
90 |
91 | let(:v8_context) { TestV8Context.new }
92 | let(:controller) { double('controller') }
93 | let(:name) { double('name') }
94 | before do
95 | described_class.instance_variable_set :@before_first_mount_blocks, nil
96 | end
97 |
98 | describe '#initialize' do
99 | it 'calls before mount callbacks' do
100 | string = instance_double(String)
101 | described_class.register_before_first_mount_block do
102 | string.inspect
103 | end
104 | expect(string).to receive(:inspect).once
105 | context = described_class.new('unique-id', v8_context, controller, name)
106 | end
107 | end
108 |
109 | describe '#eval' do
110 | it 'delegates to given context' do
111 | context = described_class.new('unique-id', v8_context, controller, name)
112 | js = 'true;'
113 | expect(v8_context).to receive(:eval).with(js).once
114 | context.eval(js)
115 | end
116 | end
117 |
118 | describe '#send_to_opal' do
119 | let(:opal_code) { Opal::Builder.new.build_str(ruby_code, __FILE__).to_s }
120 | let(:ruby_code) { %Q[
121 | module React::IsomorphicHelpers
122 | def self.greet(name)
123 | "Hello, " + name + "!"
124 | end
125 |
126 | def self.valediction
127 | 'Goodbye'
128 | end
129 | end
130 | ]}
131 |
132 | it 'raises an error when react cannot be loaded' do
133 | context = described_class.new('unique-id', v8_context, controller, name)
134 | context.instance_variable_set(:@ctx, test_context)
135 | expect {
136 | context.send_to_opal(:foo)
137 | }.to raise_error(/No HyperReact components found/)
138 | end
139 |
140 | it 'executes method with args inside opal rubyracer context' do
141 | ctx = react_context
142 | context = described_class.new('unique-id', ctx, controller, name)
143 | context.eval(opal_code)
144 | result = context.send_to_opal(:greet, 'world')
145 | expect(result).to eq('Hello, world!')
146 | end
147 |
148 | it 'executes the method inside opal rubyracer context' do
149 | ctx = react_context
150 | context = described_class.new('unique-id', ctx, controller, name)
151 | context.eval(opal_code)
152 | result = context.send_to_opal(:valediction)
153 | expect(result).to eq('Goodbye')
154 | end
155 | end
156 | end
157 | end
158 |
--------------------------------------------------------------------------------
/spec/reactive-ruby/rails/asset_pipeline_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # this spec makes trouble, becasue if the assets wont get deleted, the app will use
4 | # the precompiled assets suring testing, whcih interferes with dynamically created code
5 | describe 'test_app generator' do
6 | xit "does not interfere with asset precompilation" do
7 | cmd = "cd spec/test_app; BUNDLE_GEMFILE=#{ENV['REAL_BUNDLE_GEMFILE']} bundle exec rails assets:precompile"
8 | expect(system(cmd)).to be_truthy
9 | end
10 | end
11 |
12 | describe 'assets:clobber' do
13 | xit "remove precompiled assets so tests use recent assets" do
14 | cmd = "cd spec/test_app; BUNDLE_GEMFILE=#{ENV['REAL_BUNDLE_GEMFILE']} bundle exec rails assets:clobber"
15 | expect(system(cmd)).to be_truthy
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/reactive-ruby/rails/component_mount_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | RSpec.describe ReactiveRuby::Rails::ComponentMount do
4 | let(:helper) { described_class.new }
5 |
6 | before do
7 | helper.setup(ActionView::TestCase::TestController.new)
8 | end
9 |
10 | describe '#react_component' do
11 | it 'renders a div' do
12 | html = helper.react_component('Components::HelloWorld')
13 | expect(html).to match(/<\/div>/)
14 | end
15 |
16 | it 'accepts a pre-render option' do
17 | html = helper.react_component('Components::HelloWorld', {}, prerender: true)
18 | expect(html).to match(/Hello, World!<\/span><\/div>/)
19 | end
20 |
21 | it 'sets data-react-class to React.TopLevelRailsComponent' do
22 | html = helper.react_component('Components::HelloWorld')
23 | top_level_class = 'React.TopLevelRailsComponent'
24 | expect(attr_value(html, 'data-react-class')).to eq(top_level_class)
25 | end
26 |
27 | it 'sets component_name in data-react-props hash' do
28 | html = helper.react_component('Components::HelloWorld')
29 | props = react_props_for(html)
30 |
31 | expect(props['component_name']).to eq('Components::HelloWorld')
32 | end
33 |
34 | it 'sets render_params in data-react-props hash' do
35 | html = helper.react_component('Components::HelloWorld', {'foo' => 'bar'})
36 | props = react_props_for(html)
37 |
38 | expect(props['render_params']).to include({ 'foo' => 'bar' })
39 | end
40 |
41 | it 'sets controller in data-react-props hash' do
42 | html = helper.react_component('Components::HelloWorld')
43 | props = react_props_for(html)
44 |
45 | expect(props['controller']).to eq('ActionView::TestCase::Test')
46 | end
47 |
48 | it 'passes additional options through as html attributes' do
49 | html = helper.react_component('Components::HelloWorld', {},
50 | { 'foo-bar' => 'biz-baz' })
51 |
52 | expect(attr_value(html, 'foo-bar')).to eq('biz-baz')
53 | end
54 | end
55 |
56 | def attr_value(html, attr)
57 | matches = html.match(/#{attr}=["']((?:.(?!["']\s+(?:\S+)=|[>"']))+.)["']?/)
58 | matches.captures.first
59 | end
60 |
61 | def react_props_for(html)
62 | JSON.parse(CGI.unescapeHTML("#{attr_value(html, 'data-react-props')}"))
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/spec/reactive-ruby/server_rendering/contextual_renderer_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | RSpec.describe ReactiveRuby::ServerRendering::ContextualRenderer do
4 | let(:renderer) { described_class.new({}) }
5 | let(:init) { Proc.new {} }
6 | let(:options) { { context_initializer: init } }
7 |
8 | describe '#render' do
9 | it 'pre-renders HTML' do
10 | result = renderer.render('Components.Todo',
11 | { todo: 'finish reactive-ruby' },
12 | options)
13 | expect(result).to match(/finish reactive-ruby<\/li>/)
14 | # react 16 does not generate checksum
15 | # expect(result).to match(/data-react-checksum/)
16 | expect(result).to match(/data-reactroot/)
17 | end
18 |
19 | it 'accepts props as a string' do
20 | result = renderer.render('Components.Todo',
21 | { todo: 'finish reactive-ruby' }.to_json,
22 | options)
23 | expect(result).to match(/finish reactive-ruby<\/li>/)
24 | # react 16 does not generate checksum
25 | # expect(result).to match(/data-react-checksum/)
26 | expect(result).to match(/data-reactroot/)
27 | end
28 |
29 | it 'pre-renders static content' do
30 | result = renderer.render('Components.Todo',
31 | { todo: 'finish reactive-ruby' },
32 | :static)
33 | expect(result).to match(/finish reactive-ruby<\/li>/)
34 | # react 16 does not generate checksum
35 | # expect(result).to_not match(/data-react-checksum/)
36 | expect(result).to_not match(/data-reactroot/)
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= 'test'
2 |
3 | require 'opal'
4 | require 'opal-rspec'
5 | require 'opal-jquery'
6 |
7 | begin
8 | require File.expand_path('../test_app/config/environment', __FILE__)
9 | rescue LoadError
10 | puts 'Could not load test application. Please ensure you have run `bundle exec rake test_app`'
11 | end
12 | require 'rspec/rails'
13 | require 'hyper-spec'
14 | require 'pry'
15 | require 'opal-browser'
16 | require 'timecop'
17 |
18 | RSpec.configure do |config|
19 | config.color = true
20 | config.fail_fast = ENV['FAIL_FAST'] || false
21 | config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures")
22 | config.infer_spec_type_from_file_location!
23 | config.mock_with :rspec
24 | config.raise_errors_for_deprecations!
25 |
26 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
27 | # examples within a transaction, comment the following line or assign false
28 | # instead of true.
29 | config.use_transactional_fixtures = true
30 |
31 | config.before :each do
32 | Rails.cache.clear
33 | end
34 |
35 | config.filter_run_including focus: true
36 | config.filter_run_excluding opal: true
37 | config.run_all_when_everything_filtered = true
38 |
39 | # Fail tests on JavaScript errors in Chrome Headless
40 | class JavaScriptError < StandardError; end
41 |
42 | config.after(:each, js: true) do |spec|
43 | logs = page.driver.browser.manage.logs.get(:browser)
44 | errors = logs.select { |e| e.level == "SEVERE" && e.message.present? }
45 | .map { |m| m.message.gsub(/\\n/, "\n") }.to_a
46 | if client_options[:deprecation_warnings] == :on
47 | warnings = logs.select { |e| e.level == "WARNING" && e.message.present? }
48 | .map { |m| m.message.gsub(/\\n/, "\n") }.to_a
49 | puts "\033[0;33;1m\nJavascript client console warnings:\n\n" + warnings.join("\n\n") + "\033[0;30;21m" if warnings.present?
50 | end
51 | unless client_options[:raise_on_js_errors] == :off
52 | raise JavaScriptError, errors.join("\n\n") if errors.present?
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/test_app/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
--------------------------------------------------------------------------------
/spec/test_app/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/test_app/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/spec/test_app/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/app/assets/images/.keep
--------------------------------------------------------------------------------
/spec/test_app/app/assets/javascripts/application.rb:
--------------------------------------------------------------------------------
1 | require 'jquery'
2 | require 'react.js'
3 | require 'react-server.js'
4 | require 'react_ujs'
5 | require 'opal'
6 | require 'opal-jquery'
7 | require 'components'
8 |
--------------------------------------------------------------------------------
/spec/test_app/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/spec/test_app/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/spec/test_app/app/assets/javascripts/server_rendering.js:
--------------------------------------------------------------------------------
1 | //= require 'react-server'
2 | //= require 'react_ujs'
3 | //= require 'opal'
4 | //= require 'components'
5 | Opal.load('components');
--------------------------------------------------------------------------------
/spec/test_app/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
6 | * vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/spec/test_app/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/test_app/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/test_app/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 | end
4 |
--------------------------------------------------------------------------------
/spec/test_app/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/spec/test_app/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/spec/test_app/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/spec/test_app/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/spec/test_app/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/spec/test_app/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/app/models/concerns/.keep
--------------------------------------------------------------------------------
/spec/test_app/app/views/components.rb:
--------------------------------------------------------------------------------
1 | require 'hyper-react'
2 | if React::IsomorphicHelpers.on_opal_client?
3 | require 'browser'
4 | require 'browser/delay'
5 | end
6 | require 'react/server'
7 | require 'react/test/utils'
8 | require 'reactrb/auto-import'
9 | require 'js'
10 |
11 | require_tree './components'
12 |
--------------------------------------------------------------------------------
/spec/test_app/app/views/components/hello_world.rb:
--------------------------------------------------------------------------------
1 | module Components
2 | class HelloWorld
3 | include React::Component
4 |
5 | def render
6 | div do
7 | "Hello, World!".span
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/test_app/app/views/components/todo.rb:
--------------------------------------------------------------------------------
1 | module Components
2 | class Todo
3 | include React::Component
4 | export_component
5 |
6 | params do
7 | requires :todo
8 | end
9 |
10 | def render
11 | li { "#{params[:todo]}" }
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/test_app/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TestApp
5 | <%= csrf_meta_tags %>
6 |
7 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
8 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
9 |
10 |
11 |
12 | <%= yield %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/test_app/app/views/layouts/explicit_layout.html.erb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/app/views/layouts/explicit_layout.html.erb
--------------------------------------------------------------------------------
/spec/test_app/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/test_app/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/spec/test_app/app/views/layouts/test_layout.html.erb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/app/views/layouts/test_layout.html.erb
--------------------------------------------------------------------------------
/spec/test_app/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/test_app/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/test_app/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/test_app/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | # Install JavaScript dependencies if using Yarn
22 | # system('bin/yarn')
23 |
24 |
25 | # puts "\n== Copying sample files =="
26 | # unless File.exist?('config/database.yml')
27 | # cp 'config/database.yml.sample', 'config/database.yml'
28 | # end
29 |
30 | puts "\n== Preparing database =="
31 | system! 'bin/rails db:setup'
32 |
33 | puts "\n== Removing old logs and tempfiles =="
34 | system! 'bin/rails log:clear tmp:clear'
35 |
36 | puts "\n== Restarting application server =="
37 | system! 'bin/rails restart'
38 | end
39 |
--------------------------------------------------------------------------------
/spec/test_app/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/spec/test_app/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | VENDOR_PATH = File.expand_path('..', __dir__)
3 | Dir.chdir(VENDOR_PATH) do
4 | begin
5 | exec "yarnpkg #{ARGV.join(" ")}"
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/test_app/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/spec/test_app/config/application.rb:
--------------------------------------------------------------------------------
1 |
2 | require 'rails/all'
3 | require File.expand_path('../boot', __FILE__)
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups(assets: %w(development test)))
8 | require 'jquery-rails'
9 | require 'opal'
10 | require 'opal-jquery'
11 | require 'opal-browser'
12 | require 'opal-rails'
13 | require 'react-rails'
14 | require 'hyper-store'
15 | require 'hyper-react'
16 | require 'hyper-spec'
17 |
18 | module TestApp
19 | class Application < Rails::Application
20 | config.opal.method_missing = true
21 | config.opal.optimized_operators = true
22 | config.opal.arity_check = false
23 | config.opal.const_missing = true
24 | config.opal.dynamic_require_severity = :ignore
25 | config.opal.enable_specs = true
26 | config.opal.spec_location = 'spec-opal'
27 | config.hyperloop.auto_config = false
28 |
29 | config.assets.cache_store = :null_store
30 |
31 | config.react.server_renderer_options = {
32 | files: ["server_rendering.js"]
33 | }
34 | config.react.server_renderer_directories = ["/app/assets/javascripts"]
35 |
36 | # Initialize configuration defaults for originally generated Rails version.
37 | config.load_defaults 5.1
38 |
39 | # Settings in config/environments/* take precedence over those specified here.
40 | # Application configuration should go into files in config/initializers
41 | # -- all .rb files in that directory are automatically loaded.
42 | end
43 | end
44 |
45 |
46 |
--------------------------------------------------------------------------------
/spec/test_app/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | gemfile = File.expand_path("../../../../Gemfile", __FILE__)
3 |
4 | ENV['BUNDLE_GEMFILE'] = gemfile
5 | require 'bundler'
6 | Bundler.setup
7 |
--------------------------------------------------------------------------------
/spec/test_app/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 | channel_prefix: test_app_production
11 |
--------------------------------------------------------------------------------
/spec/test_app/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/spec/test_app/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/test_app/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join('tmp/caching-dev.txt').exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 |
34 | # Print deprecation notices to the Rails logger.
35 | config.active_support.deprecation = :log
36 |
37 | # Raise an error on page load if there are pending migrations.
38 | config.active_record.migration_error = :page_load
39 |
40 | # Debug mode disables concatenation and preprocessing of assets.
41 | # This option may cause significant delays in view rendering with a large
42 | # number of complex assets.
43 | config.assets.debug = true
44 |
45 | # Suppress logger output for asset requests.
46 | config.assets.quiet = true
47 |
48 | # Raises error for missing translations
49 | # config.action_view.raise_on_missing_translations = true
50 |
51 | # Use an evented file watcher to asynchronously detect changes in source code,
52 | # routes, locales, etc. This feature depends on the listen gem.
53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
54 | end
55 |
--------------------------------------------------------------------------------
/spec/test_app/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
19 | # `config/secrets.yml.key`.
20 | config.read_encrypted_secrets = true
21 |
22 | # Disable serving static files from the `/public` folder by default since
23 | # Apache or NGINX already handles this.
24 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
25 |
26 | # Compress JavaScripts and CSS.
27 | config.assets.js_compressor = :uglifier
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
34 |
35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
36 | # config.action_controller.asset_host = 'http://assets.example.com'
37 |
38 | # Specifies the header that your server uses for sending files.
39 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
41 |
42 | # Mount Action Cable outside main process or domain
43 | # config.action_cable.mount_path = nil
44 | # config.action_cable.url = 'wss://example.com/cable'
45 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
46 |
47 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
48 | # config.force_ssl = true
49 |
50 | # Use the lowest log level to ensure availability of diagnostic information
51 | # when problems arise.
52 | config.log_level = :debug
53 |
54 | # Prepend all log lines with the following tags.
55 | config.log_tags = [ :request_id ]
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Use a real queuing backend for Active Job (and separate queues per environment)
61 | # config.active_job.queue_adapter = :resque
62 | # config.active_job.queue_name_prefix = "test_app_#{Rails.env}"
63 | config.action_mailer.perform_caching = false
64 |
65 | # Ignore bad email addresses and do not raise email delivery errors.
66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
67 | # config.action_mailer.raise_delivery_errors = false
68 |
69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
70 | # the I18n.default_locale when a translation cannot be found).
71 | config.i18n.fallbacks = true
72 |
73 | # Send deprecation notices to registered listeners.
74 | config.active_support.deprecation = :notify
75 |
76 | # Use default logging formatter so that PID and timestamp are not suppressed.
77 | config.log_formatter = ::Logger::Formatter.new
78 |
79 | # Use a different logger for distributed setups.
80 | # require 'syslog/logger'
81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
82 |
83 | if ENV["RAILS_LOG_TO_STDOUT"].present?
84 | logger = ActiveSupport::Logger.new(STDOUT)
85 | logger.formatter = config.log_formatter
86 | config.logger = ActiveSupport::TaggedLogging.new(logger)
87 | end
88 |
89 | # Do not dump schema after migrations.
90 | config.active_record.dump_schema_after_migration = false
91 | end
92 |
--------------------------------------------------------------------------------
/spec/test_app/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
36 |
37 | # Print deprecation notices to the stderr.
38 | config.active_support.deprecation = :stderr
39 |
40 | # Raises error for missing translations
41 | # config.action_view.raise_on_missing_translations = true
42 | end
43 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | Rails.application.config.assets.precompile += %w(time_cop.js)
15 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/spec/test_app/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/test_app/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/spec/test_app/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # If you are preloading your application and using Active Record, it's
36 | # recommended that you close any connections to the database before workers
37 | # are forked to prevent connection leakage.
38 | #
39 | # before_fork do
40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
41 | # end
42 |
43 | # The code in the `on_worker_boot` will be called if you are using
44 | # clustered mode by specifying a number of `workers`. After each worker
45 | # process is booted, this block will be run. If you are using the `preload_app!`
46 | # option, you will want to use this block to reconnect to any threads
47 | # or connections that may have been created at application boot, as Ruby
48 | # cannot share connections between processes.
49 | #
50 | # on_worker_boot do
51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
52 | # end
53 | #
54 |
55 | # Allow puma to be restarted by `rails restart` command.
56 | plugin :tmp_restart
57 |
--------------------------------------------------------------------------------
/spec/test_app/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
3 | end
4 |
--------------------------------------------------------------------------------
/spec/test_app/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | # Shared secrets are available across all environments.
14 |
15 | # shared:
16 | # api_key: a1B2c3D4e5F6
17 |
18 | # Environmental secrets are only available for that specific environment.
19 |
20 | development:
21 | secret_key_base: 44614022137481567d758b39609607efaa37be4f81c7c562d874eccc298088c602cc3ce22006e75b63ffb2b766d3f180a7857bdf779b911aabffc7a6afeeac27
22 |
23 | test:
24 | secret_key_base: b6f22d4ce913d3053fbdab42e1cf8f9bf6d22af1ff131b6d57246d690abdd29b1d799809fbe2c0b0e6bdd7d7c7e6974083467f9ff31b97e41f0e44034a0e7f72
25 |
26 | # Do not keep production secrets in the unencrypted secrets file.
27 | # Instead, either read values from the environment.
28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets
29 | # and move the `production:` environment over there.
30 |
31 | production:
32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
33 |
--------------------------------------------------------------------------------
/spec/test_app/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/spec/test_app/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 0) do
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/spec/test_app/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/spec/test_app/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/test_app/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruby-hyperloop/hyper-react/579326fd76ee09c38742b4c26160ae2106581671/spec/test_app/log/.keep
--------------------------------------------------------------------------------
/spec/test_app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test_app",
3 | "private": true,
4 | "dependencies": {}
5 | }
6 |
--------------------------------------------------------------------------------
/spec/test_app/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.