diff --git a/.gitignore b/.gitignore index a359e8362..7168f97c4 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,9 @@ diff.py Vagrantfile *.sublime* coverage/ -.env \ No newline at end of file +.env# React on Rails +npm-debug.log +node_modules + +# Generated js bundles +/app/assets/webpack/* diff --git a/.rubocop.yml b/.rubocop.yml index fba1c4a28..9be356cf2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,9 +14,11 @@ AllCops: # 2015/01/04 - skipping Capistrano deploy.rb file for now, remove the # following line once #1074 is resolved - 'config/deploy.rb' + - 'config/initializers/react_on_rails.rb' - 'lib/**/*.erb' - 'script/**/*' - 'vendor/**/*' + - 'node_modules/**/*' # disable the documentation check at the start of classes / modules Documentation: Enabled: false diff --git a/.travis.yml b/.travis.yml index 501b6a199..a39bb8df1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,7 @@ before_script: - cp config/secrets.yml.example config/secrets.yml - bundle exec rake db:create - bundle exec rake db:schema:load + - npm run build:test script: # JS linters, uncomment once code passes diff --git a/Gemfile b/Gemfile index 2f88b0ec6..323ba593f 100644 --- a/Gemfile +++ b/Gemfile @@ -46,6 +46,8 @@ gem 'select2-rails', '~> 4.0.2' gem 'kaminari', '~> 0.16.3' gem 'draper', '~> 2.1.0' gem 'inline_svg', '~> 0.8.0' +gem 'react_on_rails', '~> 6.0.1' +gem 'active_model_serializers', '~> 0.10.0' # forms / formatting gem 'simple_form', '~> 3.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index a422d2ce5..8961831ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,6 +20,10 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) + active_model_serializers (0.10.0) + actionpack (>= 4.0) + activemodel (>= 4.0) + railties (>= 4.0) activejob (4.2.6) activesupport (= 4.2.6) globalid (>= 0.3.0) @@ -104,6 +108,7 @@ GEM execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.2) + connection_pool (2.2.0) daemons (1.2.3) database_cleaner (1.5.3) debug_inspector (0.0.2) @@ -144,6 +149,8 @@ GEM ffi (1.9.10) font-awesome-rails (4.6.3.0) railties (>= 3.2, < 5.1) + foreman (0.82.0) + thor (~> 0.19.1) formatador (0.2.5) fullcalendar-rails (2.6.1.0) jquery-rails (>= 4.0.5, < 5.0.0) @@ -339,6 +346,13 @@ GEM rb-fsevent (0.9.7) rb-inotify (0.9.7) ffi (>= 0.5.0) + react_on_rails (6.0.1) + addressable + connection_pool + execjs (~> 2.5) + foreman + rails (>= 3.2) + rainbow (~> 2.1) redcarpet (3.3.4) ref (2.0.0) remotipart (1.2.1) @@ -445,6 +459,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers (~> 0.10.0) awesome_print (~> 1.6.1) aws-sdk (< 2.0) bootstrap-sass (~> 3.3.6) @@ -501,6 +516,7 @@ DEPENDENCIES rails_12factor (~> 0.0.3) rails_admin (~> 0.8.1) rake (~> 11.1.2) + react_on_rails (~> 6.0.1) redcarpet (~> 3.3.4) rspec-rails (~> 3.4.2) rubocop (~> 0.40.0) diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 000000000..8ac16351b --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +web: rails s +client: sh -c 'rm app/assets/webpack/* || true && cd client && npm run build:development' diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 924cb380d..282d82fe7 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,3 +1,5 @@ +//= require webpack-bundle + //= require jquery //= require jquery_ujs //= require datatables.min.js diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 69bb2dacf..a562bb44a 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,7 @@ # rubocop:disable ClassLength class UsersController < ApplicationController + include ReactOnRails::Controller + load_and_authorize_resource layout 'application_with_sidebar', only: [:show, :edit] @@ -12,6 +14,7 @@ class UsersController < ApplicationController only: [:show, :edit, :update, :destroy, :ban, :unban] before_action :check_cas_auth, only: [:show, :new, :create, :quick_create, :edit, :update] + before_action :props, only: [:show] include Autocomplete include Calendarable @@ -168,29 +171,10 @@ def edit @can_edit_username = can? :edit_username, User end - def update # rubocop:disable CyclomaticComplexity, PerceivedComplexity - @edit_title_text = (current_user == @user) ? 'Profile' : 'User' - par = user_params - # use :update_with_password when we're not using CAS and you're editing - # your own profile - if @cas_auth || ((can? :manage, User) && (@user.id != current_user.id)) - method = :update_attributes - # delete the current_password key from the params hash just in case it's - # present (and :update_attributes will throw an error) - par.delete('current_password') - else - method = :update_with_password - # make sure we update the username as well - par[:username] = par[:email] - end - if @user.send(method, par) - # sign in the user if you've edited yourself since you have a new - # password, otherwise don't - sign_in @user, bypass: true if @user.id == current_user.id - flash[:notice] = 'Successfully updated user.' - redirect_to user_path(@user) - else - render :edit + def update + respond_to do |format| + format.html { html_update } + format.json { js_update } end end @@ -243,6 +227,37 @@ def find # rubocop:disable CyclomaticComplexity, PerceivedComplexity private + def html_update # rubocop:disable CyclomaticComplexity, PerceivedComplexity + @edit_title_text = (current_user == @user) ? 'Profile' : 'User' + par = user_params + # use :update_with_password when we're not using CAS and you're editing + # your own profile + if @cas_auth || ((can? :manage, User) && (@user.id != current_user.id)) + method = :update_attributes + # delete the current_password key from the params hash just in case it's + # present (and :update_attributes will throw an error) + par.delete('current_password') + else + method = :update_with_password + # make sure we update the username as well + par[:username] = par[:email] + end + if @user.send(method, par) + # sign in the user if you've edited yourself since you have a new + # password, otherwise don't + sign_in @user, bypass: true if @user.id == current_user.id + flash[:notice] = 'Successfully updated user.' + redirect_to user_path(@user) + else + render :edit + end + end + + def js_update + flash[:error] unless @user.update(user_params) + render nothing: true + end + def user_params permitted_attributes = [:first_name, :last_name, :nickname, :phone, :email, :affiliation, :terms_of_service_accepted, @@ -273,4 +288,11 @@ def generate_calendar_resource def calendar_name_method :equipment_model end + + # React on Rails / Redux methods + + def props + @props = ActiveModelSerializers::SerializableResource + .new(@user, scope: current_user, scope_name: :current_user) + end end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb new file mode 100644 index 000000000..2bcb6a8c7 --- /dev/null +++ b/app/serializers/user_serializer.rb @@ -0,0 +1,16 @@ +class UserSerializer < ActiveModel::Serializer + attributes :id, :email, :first_name, :last_name, :nickname, :phone, + :affiliation, :terms_of_service_accepted, :role, + :reservation_counts + + def reservation_counts + res = object.reservations + counts = { checked_out: res.checked_out.count, + overdue: res.overdue.count, + future: res.reserved.count, + past: res.returned.count, + past_overdue: res.returned_overdue.count } + counts[:missed] = res.missed.count unless AppConfig.check(:res_exp_time) + counts + end +end diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 5ad8d466c..1efff0e63 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -25,50 +25,7 @@ -
- -
-
- <% if @cas_auth %> -
Username
-
<%= @user.username %>
- <% end %> - -
First Name
-
<%= @user.first_name %>
- -
Last Name
-
<%= @user.last_name %>
- -
Nickname
-
<%= !@user.nickname.blank? ? @user.nickname : "(none)" %>
- -
Phone
-
<%= !@user.phone.blank? ? @user.phone : "(none)" %>
- -
Email
-
<%= mail_to @user.email, @user.email %>
- -
Affiliation
-
<%= @user.affiliation %>
-
-
- -
- - <%# @show_equipment has reservation status (:overdue, ...) as key, full - reservations in question as value %> - <% @show_equipment.each do |status, reservations| %> - <%= content_tag :div, :id => "#{status}_count", :class => 'col-md-6' do %> - <%= content_tag :h4, status.to_s.titleize %> - <%= content_tag :div, :class => "#{status}_num" do %> - <%= content_tag :i, nil, :class => stats_icons(status) %> - <%= reservations.size %> - <% end %> - <% end %> - <% end %> -
-
+<%= react_component('UserProfile', props: @props) %> <%# TODO: Make this prettier %> <% unless @user.requirements.empty? %> diff --git a/client/.babelrc b/client/.babelrc new file mode 100644 index 000000000..9b7d435ad --- /dev/null +++ b/client/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "stage-0", "react"] +} diff --git a/client/REACT_ON_RAILS_CLIENT_README.md b/client/REACT_ON_RAILS_CLIENT_README.md new file mode 100644 index 000000000..642244d66 --- /dev/null +++ b/client/REACT_ON_RAILS_CLIENT_README.md @@ -0,0 +1,9 @@ +Client folder generated by the React on Rails gem. + +See documentation [at github.com/shakacode/react_on_rails](https://github.com/shakacode/react_on_rails) for details on how it is organized. + +If you need additional help, please consider: + +* [Our ShakaCode Forum for React on Rails](https://forum.shakacode.com/c/rails/reactonrails). +* Joining our Slack discussion room by [email us a bit about you and your project](mailto:contact@shakacode.com). +* [Hiring us](https://forum.shakacode.com/c/rails/reactonrails) for coaching and custom web application development for your project. diff --git a/client/app/reducers.jsx b/client/app/reducers.jsx new file mode 100644 index 000000000..e0190045c --- /dev/null +++ b/client/app/reducers.jsx @@ -0,0 +1,12 @@ +// This file is our manifest of all reducers for the app. +import userReducer from './user/userReducer'; +import { $$initialState as $$userState } from './user/userReducer'; + +export default { + $$userStore: userReducer, +}; + +export const initialStates = { + $$userState, +}; + diff --git a/client/app/registration.jsx b/client/app/registration.jsx new file mode 100644 index 000000000..e5ac4fcd0 --- /dev/null +++ b/client/app/registration.jsx @@ -0,0 +1,7 @@ +import ReactOnRails from 'react-on-rails'; + +import UserProfile from './user/UserProfile'; + +ReactOnRails.register({ + UserProfile, +}); diff --git a/client/app/user/UserProfile.jsx b/client/app/user/UserProfile.jsx new file mode 100644 index 000000000..0f204d8f2 --- /dev/null +++ b/client/app/user/UserProfile.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { createStore } from 'redux'; +import { Provider } from 'react-redux'; +import User from './userContainer'; +import userReducer from './userReducer'; + + +export default (props, _railsContext) => { + const store = createStore(userReducer, { user: props }); + const reactComponent = ( + + + + ); + return reactComponent; +}; + diff --git a/client/app/user/editableTable.jsx b/client/app/user/editableTable.jsx new file mode 100644 index 000000000..ae51c338a --- /dev/null +++ b/client/app/user/editableTable.jsx @@ -0,0 +1,97 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; + +const Form = ({ user, onSave, id }) => { + let firstName, lastName, nickname, phone, email, affiliation, tos; + + return ( +
+
{ + e.preventDefault(); + onSave({ + first_name: firstName.value, + last_name: lastName.value, + nickname: nickname.value, + phone: phone.value, + email: email.value, + affiliation: affiliation.value, + terms_of_service_accepted: tos.value, + }) + }}> +
+ +
+ { firstName = node } }/> +
+
+
+ +
+ { lastName = node } }/> +
+
+
+ +
+ { nickname = node } }/> +
+
+
+ +
+ { phone = node } }/> +
+
+
+ +
+ { email = node } }/> +
+
+
+ +
+ { affiliation = node } }/> +
+
+
+
+
+ +
+
+
+
+
+ ); +} + +const mapStateToProps = (state) => ({ + user: state.user, +}); + +const mapDispatchToProps = (dispatch) => { + return { + onSave(changes) { dispatch({ type: 'UPDATE_USER', changes: changes }) }, + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(Form) diff --git a/client/app/user/reservations.jsx b/client/app/user/reservations.jsx new file mode 100644 index 000000000..49ea28386 --- /dev/null +++ b/client/app/user/reservations.jsx @@ -0,0 +1,30 @@ +import React from 'react'; + +const Item = ({ title, num, icon }) => { + return ( +
+

{title}

+
+ {num} +
+
+ ); +} + +const Reservations = ({ counts }) => { + const missed = counts.hasOwnProperty('missed') + ? + : null; + return ( +
+ + + + + + {missed} +
+ ); +} + +export default Reservations diff --git a/client/app/user/table.jsx b/client/app/user/table.jsx new file mode 100644 index 000000000..237e1cd56 --- /dev/null +++ b/client/app/user/table.jsx @@ -0,0 +1,34 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; + +const Item = ({ title, text }) => { + return ( +
+ +
+

{text}

+
+
+ ); +} + +const Table = ({ user }) => { + const nickname = user.nickname === '' ? '(none)' : `${user.nickname}`; + const phone = user.phone === '' ? '(none)' : `${user.phone}`; + return ( +
+ + + + + + + ); +} + +const mapStateToProps = (state) => ({ + user: state.user +}); + +export default connect(mapStateToProps)(Table) + diff --git a/client/app/user/userContainer.jsx b/client/app/user/userContainer.jsx new file mode 100644 index 000000000..a3613d23c --- /dev/null +++ b/client/app/user/userContainer.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import EditableTable from './editableTable'; +import Table from './table'; +import Reservations from './reservations'; + +const UserInfo = ({ user, canEdit, editing, onEditClick }) => { + const table = editing ? : ; + const save = editing + ? + : null; + const color = editing ? 'default' : 'primary'; + const text = editing ? 'Cancel' : 'Edit'; + return ( +
+
+
+
+ {table} +
+
+
+ {save} + +
+
+
+
+
+ +
+
+ ); +} + +const mapStateToProps = (state) => ({ + editing: state.editMode, + user: state.user, +}); + +const mapDispatchToProps = (dispatch) => ({ + onEditClick: () => { dispatch({ type: 'TOGGLE_EDIT_MODE' }) }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(UserInfo) diff --git a/client/app/user/userReducer.jsx b/client/app/user/userReducer.jsx new file mode 100644 index 000000000..6581e95fd --- /dev/null +++ b/client/app/user/userReducer.jsx @@ -0,0 +1,39 @@ +import React, { PropTypes } from 'react'; + +const initialState = { + editMode: false, + user: null, +} + +export default function userReducer(state = initialState, action) { + switch (action.type) { + case 'SET_USER': + return { + ...state, + user: action.user, + }; + case 'TOGGLE_EDIT_MODE': + return { + ...state, + editMode: !state.editMode, + }; + case 'UPDATE_USER': + const user = state.user; + const changes = action.changes + if (state.editMode) { + $.ajax({ + type: 'PUT', + url: `/users/${user.id}`, + data: { user: changes }, + dataType: 'json', + }); + } + return { + ...state, + editMode: !state.editMode, + user: { ...user, ...changes } + }; + default: + return state; + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 000000000..75693d887 --- /dev/null +++ b/client/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-webpack-rails-tutorial", + "version": "0.0.1", + "engines": { + "node": "5.10.0", + "npm": "3.5.0" + }, + "scripts": { + "build:test": "webpack --config webpack.config.js", + "build:production": "NODE_ENV=production webpack --config webpack.config.js", + "build:development": "webpack -w --config webpack.config.js" + }, + "cacheDirectories": ["node_modules", "client/node_modules"], + "dependencies": { + "babel": "^6.5.2", + "babel-cli": "^6.6.5", + "babel-core": "^6.7.4", + "babel-loader": "^6.2.4", + "babel-runtime": "^6.6.1", + "babel-polyfill": "^6.7.4", + "babel-preset-es2015": "^6.6.0", + "babel-preset-react": "^6.5.0", + "babel-preset-stage-0": "^6.5.0", + "es5-shim": "^4.5.7", + "expose-loader": "^0.7.1", + "immutable": "^3.7.6", + "imports-loader": "^0.6.5", + "mirror-creator": "1.1.0", + "react": "^0.14.8 || ^15.0.0", + "react-dom": "^0.14.8 || ^15.0.0", + "react-on-rails": "6.0.1", + "react-redux": "^4.4.1", + "redux": "^3.3.1", + "redux-promise": "^0.5.3", + "redux-thunk": "^2.0.1", + "webpack": "^1.12.14" + }, + "devDependencies": { + } +} diff --git a/client/webpack.config.js b/client/webpack.config.js new file mode 100644 index 000000000..57ec939c3 --- /dev/null +++ b/client/webpack.config.js @@ -0,0 +1,58 @@ +const webpack = require('webpack'); +const path = require('path'); + +const devBuild = process.env.NODE_ENV !== 'production'; +const nodeEnv = devBuild ? 'development' : 'production'; + +config = { + entry: [ + 'es5-shim/es5-shim', + 'es5-shim/es5-sham', + 'babel-polyfill', + './app/registration', + ], + + output: { + filename: 'webpack-bundle.js', + path: '../app/assets/webpack', + }, + + resolve: { + extensions: ['', '.js', '.jsx'], + alias: { + react: path.resolve('./node_modules/react'), + 'react-dom': path.resolve('./node_modules/react-dom'), + }, + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(nodeEnv), + }, + }), + ], + module: { + loaders: [ + { + test: require.resolve('react'), + loader: 'imports?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham', + }, + { + test: /\.jsx?$/, loader: 'babel-loader', + exclude: /node_modules/, + }, + ], + }, +}; + +module.exports = config; + +if (devBuild) { + console.log('Webpack dev build for Rails'); // eslint-disable-line no-console + module.exports.devtool = 'eval-source-map'; +} else { + config.plugins.push( + new webpack.optimize.DedupePlugin() + ); + console.log('Webpack production build for Rails'); // eslint-disable-line no-console +} diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 68e54f5d5..4d2da8831 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -10,3 +10,14 @@ # application.js, application.css, and all non-JS/CSS in app/assets folder are # already added. Rails.application.config.assets.precompile += %w( print.css ) +# Add client/assets/ folders to asset pipeline's search path. +# If you do not want to move existing images and fonts from your Rails app +# you could also consider creating symlinks there that point to the original +# rails directories. In that case, you would not add these paths here. +# If you have a different server bundle file than your client bundle, you'll +# need to add it here, like this: +# Rails.application.config.assets.precompile += %w( server-bundle.js ) + +# Add folder with webpack generated assets to assets.paths +Rails.application.config.assets.paths << Rails.root.join('app', 'assets', + 'webpack') diff --git a/config/initializers/react_on_rails.rb b/config/initializers/react_on_rails.rb new file mode 100644 index 000000000..1619a278a --- /dev/null +++ b/config/initializers/react_on_rails.rb @@ -0,0 +1,80 @@ +# Shown below are the defaults for configuration +ReactOnRails.configure do |config| + # Client bundles are configured in application.js + + # Directory where your generated assets go. All generated assets must go to the same directory. + # Configure this in your webpack config files. This relative to your Rails root directory. + config.generated_assets_dir = File.join(%w(app assets webpack)) + + # Define the files we need to check for webpack compilation when running tests. + config.webpack_generated_files = %w( webpack-bundle.js ) + + # This is the file used for server rendering of React when using `(prerender: true)` + # If you are never using server rendering, you may set this to "". + # If you are using the same file for client and server rendering, having this set probably does + # not affect performance. + config.server_bundle_js_file = "webpack-bundle.js" + + # If you are using the ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) + # with rspec then this controls what npm command is run + # to automatically refresh your webpack assets on every test run. + config.npm_build_test_command = "npm run build:test" + + # This configures the script to run to build the production assets by webpack. Set this to nil + # if you don't want react_on_rails building this file for you. + config.npm_build_production_command = "npm run build:production" + + ################################################################################ + # CLIENT RENDERING OPTIONS + # Below options can be overriden by passing options to the react_on_rails + # `render_component` view helper method. + ################################################################################ + # default is false + config.prerender = false + + # default is true for development, off otherwise + config.trace = Rails.env.development? + + ################################################################################ + # SERVER RENDERING OPTIONS + ################################################################################ + # If set to true, this forces Rails to reload the server bundle if it is modified + config.development_mode = Rails.env.development? + + # For server rendering. This can be set to false so that server side messages are discarded. + # Default is true. Be cautious about turning this off. + config.replay_console = true + + # Default is true. Logs server rendering messages to Rails.logger.info + config.logging_on_server = true + + config.raise_on_prerender_error = false # change to true to raise exception on server if the JS code throws + + # Server rendering only (not for render_component helper) + # You can configure your pool of JS virtual machines and specify where it should load code: + # On MRI, use `therubyracer` for the best performance + # (see [discussion](https://github.com/reactjs/react-rails/pull/290)) + # On MRI, you'll get a deadlock with `pool_size` > 1 + # If you're using JRuby, you can increase `pool_size` to have real multi-threaded rendering. + config.server_renderer_pool_size = 1 # increase if you're on JRuby + config.server_renderer_timeout = 20 # seconds + + ################################################################################ + # MISCELLANEOUS OPTIONS + ################################################################################ + + # Default is false, enable if your content security policy doesn't include `style-src: 'unsafe-inline'` + config.skip_display_none = false + + # The server render method - either ExecJS or NodeJS + config.server_render_method = "ExecJS" + + # Client js uses assets not digested by rails. + # For any asset matching this regex, non-digested symlink will be created + # To disable symlinks set this parameter to nil. + config.symlink_non_digested_assets_regex = /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg)/ + + ActiveSupport.on_load(:action_view) do + include ReactOnRailsHelper + end +end diff --git a/package.json b/package.json new file mode 100644 index 000000000..a07191333 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "react-webpack-rails-tutorial", + "version": "0.0.1", + "engines": { + "node": "5.10.0", + "npm": "3.5.0" + }, + "scripts": { + "postinstall": "cd client && npm install", + "rails-server": "foreman start -f Procfile.dev", + "test": "rspec" + } +} diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4b7930bbf..e43a6330e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,10 @@ ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| + # Ensure that if we are running js tests, we are using latest webpack assets + # This will use the defaults of :js and :server_rendering meta tags + ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) + # ## Mock Framework # # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: