reactive-ruby 0.7.25 → 0.7.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ require 'action_view'
2
+ require 'react-rails'
3
+ require 'reactive-ruby/server_rendering/contextual_renderer'
4
+ require 'reactive-ruby/rails/component_mount'
5
+ require 'reactive-ruby/rails/railtie'
6
+ require 'reactive-ruby/rails/controller_helper'
7
+ require 'reactive-ruby/component_loader'
@@ -0,0 +1,44 @@
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
+ options = context_initializer_options(options, name) if options[:prerender]
12
+ props = serialized_props(props, name, controller)
13
+ super(top_level_name, props, options, &block) + footers
14
+ end
15
+
16
+ private
17
+
18
+ def context_initializer_options(options, name)
19
+ options[:prerender] = {options[:prerender] => true} unless options[:prerender].is_a? Hash
20
+ existing_context_initializer = options[:prerender][:context_initializer]
21
+
22
+ options[:prerender][:context_initializer] = lambda do |ctx|
23
+ React::IsomorphicHelpers.load_context(ctx, controller, name)
24
+ existing_context_initializer.call ctx if existing_context_initializer
25
+ end
26
+
27
+ options
28
+ end
29
+
30
+ def serialized_props(props, name, controller)
31
+ { render_params: props, component_name: name,
32
+ controller: controller.class.name.gsub(/Controller$/,"") }.react_serializer
33
+ end
34
+
35
+ def top_level_name
36
+ 'React.TopLevelRailsComponent'
37
+ end
38
+
39
+ def footers
40
+ React::IsomorphicHelpers.prerender_footers #if options[:prerender]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ require 'action_controller'
2
+
3
+ module ReactiveRuby
4
+ module Rails
5
+ class ActionController::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[0].is_a? Hash) ? args[0] : {}
9
+ render inline: "<%= react_component @component_name, @render_params, { prerender: !params[:no_prerender] } %>", layout: 'application'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
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 =
8
+ ReactiveRuby::ServerRendering::ContextualRenderer
9
+ app.config.react.view_helper_implementation =
10
+ ReactiveRuby::Rails::ComponentMount
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,33 @@
1
+ module ReactiveRuby
2
+ module ServerRendering
3
+ class ContextualRenderer < React::ServerRendering::SprocketsRenderer
4
+ def initialize(options = {})
5
+ super(options)
6
+ ComponentLoader.new(v8_context).load
7
+ end
8
+
9
+ def render(component_name, props, prerender_options)
10
+ if prerender_options.is_a? Hash
11
+ if v8_runtime? and prerender_options[:context_initializer]
12
+ raise React::ServerRendering::PrerenderError.new(component_name, props, "you must use 'therubyracer' with the prerender[:context] option") unless v8_runtime?
13
+ else
14
+ prerender_options[:context_initializer].call v8_context
15
+ prerender_options = prerender_options[:static] ? :static : true
16
+ end
17
+ end
18
+
19
+ super(component_name, props, prerender_options)
20
+ end
21
+
22
+ private
23
+
24
+ def v8_runtime?
25
+ ExecJS.runtime.name == "(V8)"
26
+ end
27
+
28
+ def v8_context
29
+ @v8_context ||= @context.instance_variable_get("@v8_context")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module React
2
- VERSION = "0.7.25"
2
+ VERSION = "0.7.26"
3
3
  end
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.add_development_dependency 'opal-rails'
26
26
  s.add_development_dependency 'rake'
27
27
  s.add_development_dependency 'rspec-rails'
28
+ s.add_development_dependency 'timecop'
28
29
  s.add_development_dependency 'opal-rspec'
29
30
  s.add_development_dependency 'sinatra'
30
31
  s.add_development_dependency 'sqlite3' # For Test Rails App
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ReactiveRuby::ComponentLoader do
4
+ GLOBAL_WRAPPER = <<-JS
5
+ var global = global || this;
6
+ var self = self || this;
7
+ var window = window || this;
8
+ JS
9
+
10
+ let(:js) { ::Rails.application.assets['components'].to_s }
11
+ let(:context) { ExecJS.compile(GLOBAL_WRAPPER + js) }
12
+ let(:v8_context) { context.instance_variable_get(:@v8_context) }
13
+
14
+ describe '#load' do
15
+ it 'loads given asset file into context' do
16
+ loader = described_class.new(v8_context)
17
+
18
+ expect {
19
+ loader.load
20
+ }.to change { !!v8_context.eval('Opal.React') }.from(false).to(true)
21
+ end
22
+
23
+ it 'is truthy upon successful load' do
24
+ loader = described_class.new(v8_context)
25
+ expect(loader.load).to be_truthy
26
+ end
27
+
28
+ it 'fails silently returning false' do
29
+ loader = described_class.new(v8_context)
30
+ expect(loader.load('foo')).to be_falsey
31
+ end
32
+ end
33
+
34
+ describe '#load!' do
35
+ it 'is truthy upon successful load' do
36
+ loader = described_class.new(v8_context)
37
+ expect(loader.load!).to be_truthy
38
+ end
39
+
40
+ it 'raises an expection if loading fails' do
41
+ loader = described_class.new(v8_context)
42
+ expect { loader.load!('foo') }.to raise_error(/No react\.rb components/)
43
+ end
44
+ end
45
+
46
+ describe '#loaded?' do
47
+ it 'is truthy if components file is already loaded' do
48
+ loader = described_class.new(v8_context)
49
+ loader.load
50
+ expect(loader).to be_loaded
51
+ end
52
+
53
+ it 'is false if components file is not loaded' do
54
+ loader = described_class.new(v8_context)
55
+ expect(loader).to_not be_loaded
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe React::IsomorphicHelpers do
4
+ describe 'code execution context', :ruby do
5
+ let(:klass) { Class.send(:include, described_class) }
6
+ describe 'module class methods' do
7
+ it { is_expected.to_not be_on_opal_server }
8
+ it { is_expected.to_not be_on_opal_client }
9
+ end
10
+
11
+ describe 'included class methods' do
12
+ subject { klass }
13
+ it { is_expected.to_not be_on_opal_server }
14
+ it { is_expected.to_not be_on_opal_client }
15
+ end
16
+
17
+ describe 'included instance methods' do
18
+ subject { klass.new }
19
+ it { is_expected.to_not be_on_opal_server }
20
+ it { is_expected.to_not be_on_opal_client }
21
+ end
22
+ end
23
+
24
+ describe 'load_context', :ruby do
25
+ let(:v8_context) { TestV8Context.new }
26
+ let(:controller) { double('controller') }
27
+ let(:name) { double('name') }
28
+
29
+ it 'creates a context and sets a controller' do
30
+ context = described_class.load_context(v8_context, controller, name)
31
+ expect(context.controller).to eq(controller)
32
+ end
33
+
34
+ it 'creates a context and sets a unique_id' do
35
+ Timecop.freeze do
36
+ stamp = Time.now.to_i
37
+ context = described_class.load_context(v8_context, controller, name)
38
+ expect(context.unique_id).to eq("#{ controller.object_id }-#{ stamp }")
39
+ end
40
+ end
41
+ end
42
+
43
+ describe React::IsomorphicHelpers::Context do
44
+ class TestV8Context < Hash
45
+ def eval(args)
46
+ true
47
+ end
48
+ end
49
+
50
+ # Need to decouple/dry up this...
51
+ def test_context(files = nil)
52
+ js = ReactiveRuby::ServerRendering::ContextualRenderer::CONSOLE_POLYFILL.dup
53
+ js << Opal::Builder.build('opal').to_s
54
+ Array(files).each do |filename|
55
+ js << ::Rails.application.assets[filename].to_s
56
+ end
57
+ js = "#{React::ServerRendering::ExecJSRenderer::GLOBAL_WRAPPER}#{js}"
58
+ ctx = ExecJS.compile(js)
59
+ ctx = ctx.instance_variable_get("@v8_context")
60
+ end
61
+
62
+ def react_context
63
+ test_context('components')
64
+ end
65
+
66
+ let(:v8_context) { TestV8Context.new }
67
+ let(:controller) { double('controller') }
68
+ let(:name) { double('name') }
69
+ before do
70
+ described_class.instance_variable_set :@before_first_mount_blocks, nil
71
+ end
72
+
73
+ describe '#initialize' do
74
+ it "sets the given V8 context's ServerSideIsomorphicMethods to itself" do
75
+ context = described_class.new('unique-id', v8_context, controller, name)
76
+ expect(v8_context['ServerSideIsomorphicMethods']).to eq(context)
77
+ end
78
+
79
+ it 'calls before mount callbacks' do
80
+ string = instance_double(String)
81
+ described_class.register_before_first_mount_block do
82
+ string.inspect
83
+ end
84
+ expect(string).to receive(:inspect).once
85
+ context = described_class.new('unique-id', v8_context, controller, name)
86
+ end
87
+ end
88
+
89
+ describe '#eval' do
90
+ it 'delegates to given context' do
91
+ context = described_class.new('unique-id', v8_context, controller, name)
92
+ js = 'true;'
93
+ expect(v8_context).to receive(:eval).with(js).once
94
+ context.eval(js)
95
+ end
96
+ end
97
+
98
+ describe '#send_to_opal' do
99
+ let(:opal_code) { Opal::Builder.new.build_str(ruby_code, __FILE__) }
100
+ let(:ruby_code) { %Q[
101
+ module React::IsomorphicHelpers
102
+ def self.greet(name)
103
+ "Hello, #\{name}!"
104
+ end
105
+
106
+ def self.valediction
107
+ 'Goodbye'
108
+ end
109
+ end
110
+ ]}
111
+
112
+ it 'raises an error when react cannot be loaded' do
113
+ context = described_class.new('unique-id', v8_context, controller, name)
114
+ context.instance_variable_set(:@ctx, test_context)
115
+ expect {
116
+ context.send_to_opal(:foo)
117
+ }.to raise_error(/No react.rb components found/)
118
+ end
119
+
120
+ it 'executes method with args inside opal rubyracer context' do
121
+ ctx = react_context
122
+ context = described_class.new('unique-id', ctx, controller, name)
123
+ ctx.eval(opal_code)
124
+ result = context.send_to_opal(:greet, 'world')
125
+ expect(result).to eq('Hello, world!')
126
+ end
127
+
128
+ it 'executes the method inside opal rubyracer context' do
129
+ ctx = react_context
130
+ context = described_class.new('unique-id', ctx, controller, name)
131
+ ctx.eval(opal_code)
132
+ result = context.send_to_opal(:valediction)
133
+ expect(result).to eq('Goodbye')
134
+ end
135
+ end
136
+ end
137
+ end
@@ -1,12 +1,26 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe React::Rails::ViewHelper, type: :helper do
3
+ RSpec.describe ReactiveRuby::Rails::ComponentMount do
4
+ let(:helper) { described_class.new }
5
+
6
+ before do
7
+ env = double
8
+ allow(env).to receive(:request).and_return(
9
+ { 'controller' => ActionView::TestCase::TestController.new })
10
+ helper.setup(env)
11
+ end
12
+
4
13
  describe '#react_component' do
5
14
  it 'renders a div' do
6
15
  html = helper.react_component('Components::HelloWorld')
7
16
  expect(html).to match(/<div.*><\/div>/)
8
17
  end
9
18
 
19
+ it 'accepts a pre-render option' do
20
+ html = helper.react_component('Components::HelloWorld', {}, prerender: true)
21
+ expect(html).to match(/<div.*><span.*>Hello, World!<\/span><\/div>/)
22
+ end
23
+
10
24
  it 'sets data-react-class to React.TopLevelRailsComponent' do
11
25
  html = helper.react_component('Components::HelloWorld')
12
26
  top_level_class = 'React.TopLevelRailsComponent'
@@ -0,0 +1,33 @@
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(/<li.*>finish reactive-ruby<\/li>/)
14
+ expect(result).to match(/data-react-checksum/)
15
+ end
16
+
17
+ it 'accepts props as a string' do
18
+ result = renderer.render('Components.Todo',
19
+ { todo: 'finish reactive-ruby' }.to_json,
20
+ options)
21
+ expect(result).to match(/<li.*>finish reactive-ruby<\/li>/)
22
+ expect(result).to match(/data-react-checksum/)
23
+ end
24
+
25
+ it 'pre-renders static content' do
26
+ result = renderer.render('Components.Todo',
27
+ { todo: 'finish reactive-ruby' },
28
+ :static)
29
+ expect(result).to match(/<li.*>finish reactive-ruby<\/li>/)
30
+ expect(result).to_not match(/data-react-checksum/)
31
+ end
32
+ end
33
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,29 +1,52 @@
1
1
  ENV["RAILS_ENV"] ||= 'test'
2
2
 
3
- begin
4
- require File.expand_path('../test_app/config/environment', __FILE__)
5
- rescue LoadError
6
- puts 'Could not load test application. Please ensure you have run `bundle exec rake test_app`'
3
+ require 'opal'
4
+ require 'opal-rspec'
5
+
6
+ def opal?
7
+ RUBY_ENGINE == 'opal'
8
+ end
9
+
10
+ def ruby?
11
+ !opal?
12
+ end
13
+
14
+ if RUBY_ENGINE == 'opal'
15
+ RSpec.configure do |config|
16
+ config.filter_run_excluding :ruby
17
+ end
7
18
  end
8
19
 
9
- require 'rspec/rails'
20
+ if RUBY_ENGINE != 'opal'
21
+ begin
22
+ require File.expand_path('../test_app/config/environment', __FILE__)
23
+ rescue LoadError
24
+ puts 'Could not load test application. Please ensure you have run `bundle exec rake test_app`'
25
+ end
26
+ require 'rspec/rails'
27
+ require 'timecop'
28
+
29
+ Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
10
30
 
11
- Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
31
+ RSpec.configure do |config|
32
+ config.color = true
33
+ config.fail_fast = ENV['FAIL_FAST'] || false
34
+ config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures")
35
+ config.infer_spec_type_from_file_location!
36
+ config.mock_with :rspec
37
+ config.raise_errors_for_deprecations!
12
38
 
13
- RSpec.configure do |config|
14
- config.color = true
15
- config.fail_fast = ENV['FAIL_FAST'] || false
16
- config.fixture_path = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures")
17
- config.infer_spec_type_from_file_location!
18
- config.mock_with :rspec
19
- config.raise_errors_for_deprecations!
39
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
40
+ # examples within a transaction, comment the following line or assign false
41
+ # instead of true.
42
+ config.use_transactional_fixtures = true
20
43
 
21
- # If you're not using ActiveRecord, or you'd prefer not to run each of your
22
- # examples within a transaction, comment the following line or assign false
23
- # instead of true.
24
- config.use_transactional_fixtures = true
44
+ config.before :each do
45
+ Rails.cache.clear
46
+ end
25
47
 
26
- config.before :each do
27
- Rails.cache.clear
48
+ config.filter_run_including focus: true
49
+ config.filter_run_excluding opal: true
50
+ config.run_all_when_everything_filtered = true
28
51
  end
29
52
  end