react_on_rails 16.2.0.beta.3 → 16.2.0.beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -5
- data/CLAUDE.md +59 -0
- data/CONTRIBUTING.md +49 -1
- data/Gemfile.development_dependencies +1 -1
- data/Gemfile.lock +25 -10
- data/SWITCHING_CI_CONFIGS.md +55 -6
- data/Steepfile +51 -0
- data/bin/ci-rerun-failures +68 -22
- data/bin/ci-run-failed-specs +26 -2
- data/bin/ci-switch-config +262 -34
- data/bin/lefthook/check-trailing-newlines +2 -12
- data/bin/lefthook/eslint-lint +0 -10
- data/bin/lefthook/prettier-format +0 -10
- data/bin/lefthook/ruby-autofix +3 -6
- data/knip.ts +35 -9
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +32 -52
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +5 -1
- data/lib/react_on_rails/configuration.rb +56 -12
- data/lib/react_on_rails/controller.rb +3 -3
- data/lib/react_on_rails/dev/server_manager.rb +11 -4
- data/lib/react_on_rails/doctor.rb +249 -2
- data/lib/react_on_rails/helper.rb +12 -3
- data/lib/react_on_rails/pro_helper.rb +2 -44
- data/lib/react_on_rails/react_component/render_options.rb +7 -7
- data/lib/react_on_rails/utils.rb +40 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/react_on_rails_pro/CHANGELOG.md +142 -29
- data/react_on_rails_pro/CONTRIBUTING.md +2 -13
- data/react_on_rails_pro/Gemfile.development_dependencies +1 -0
- data/react_on_rails_pro/Gemfile.lock +24 -3
- data/react_on_rails_pro/README.md +559 -38
- data/react_on_rails_pro/docs/code-splitting-loadable-components.md +1 -1
- data/react_on_rails_pro/docs/contributors-info/releasing.md +2 -2
- data/react_on_rails_pro/docs/installation.md +129 -109
- data/react_on_rails_pro/docs/node-renderer/basics.md +29 -22
- data/react_on_rails_pro/docs/node-renderer/error-reporting-and-tracing.md +8 -8
- data/react_on_rails_pro/docs/node-renderer/js-configuration.md +25 -23
- data/react_on_rails_pro/docs/node-renderer/troubleshooting.md +2 -0
- data/react_on_rails_pro/docs/updating.md +209 -15
- data/react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb +58 -4
- data/react_on_rails_pro/lib/react_on_rails_pro/configuration.rb +17 -3
- data/react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb +9 -9
- data/react_on_rails_pro/lib/react_on_rails_pro/request.rb +41 -25
- data/react_on_rails_pro/lib/react_on_rails_pro/stream_request.rb +27 -7
- data/react_on_rails_pro/lib/react_on_rails_pro/utils.rb +3 -3
- data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
- data/react_on_rails_pro/package-scripts.yml +1 -1
- data/react_on_rails_pro/package.json +5 -8
- data/react_on_rails_pro/packages/node-renderer/src/integrations/api.ts +1 -1
- data/react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts +39 -17
- data/react_on_rails_pro/packages/node-renderer/src/master.ts +15 -4
- data/react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts +44 -5
- data/react_on_rails_pro/packages/node-renderer/src/shared/utils.ts +4 -2
- data/react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts +49 -0
- data/react_on_rails_pro/packages/node-renderer/src/worker/vm.ts +3 -3
- data/react_on_rails_pro/packages/node-renderer/src/worker.ts +5 -2
- data/react_on_rails_pro/packages/node-renderer/tests/helper.ts +8 -8
- data/react_on_rails_pro/packages/node-renderer/tests/testingNodeRendererConfigs.js +1 -1
- data/react_on_rails_pro/packages/node-renderer/tests/worker.test.ts +19 -19
- data/react_on_rails_pro/rakelib/public_key_management.rake +6 -5
- data/react_on_rails_pro/rakelib/rbs.rake +47 -0
- data/react_on_rails_pro/react_on_rails_pro.gemspec +1 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/cache.rbs +13 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/configuration.rbs +100 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/error.rbs +4 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/utils.rbs +7 -0
- data/react_on_rails_pro/sig/react_on_rails_pro.rbs +5 -0
- data/react_on_rails_pro/spec/dummy/Gemfile.lock +23 -3
- data/react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb +3 -3
- data/react_on_rails_pro/spec/dummy/bin/dev +4 -8
- data/react_on_rails_pro/spec/dummy/client/node-renderer.js +4 -4
- data/react_on_rails_pro/spec/dummy/config/environments/production.rb +1 -1
- data/react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb +28 -12
- data/react_on_rails_pro/spec/dummy/config.ru +1 -1
- data/react_on_rails_pro/spec/dummy/package.json +2 -2
- data/react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb +40 -11
- data/react_on_rails_pro/spec/dummy/spec/rails_helper.rb +1 -1
- data/react_on_rails_pro/spec/dummy/spec/requests/renderer_console_logging_spec.rb +5 -5
- data/react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb +15 -10
- data/react_on_rails_pro/spec/dummy/spec/system/renderer_integration_spec.rb +3 -3
- data/react_on_rails_pro/spec/dummy/yarn.lock +4 -4
- data/react_on_rails_pro/spec/execjs-compatible-dummy/config/environments/production.rb +1 -1
- data/react_on_rails_pro/spec/execjs-compatible-dummy/config/initializers/react_on_rails.rb +16 -43
- data/react_on_rails_pro/spec/react_on_rails_pro/assets_precompile_spec.rb +15 -18
- data/react_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb +5 -3
- data/react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb +27 -12
- data/react_on_rails_pro/spec/react_on_rails_pro/request_spec.rb +0 -27
- data/react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/stream_decorator_spec.rb +89 -0
- data/react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb +144 -0
- data/react_on_rails_pro/spec/react_on_rails_pro/support/caching.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/support/mock_block_helper.rb +4 -2
- data/sig/react_on_rails/controller.rbs +1 -1
- data/sig/react_on_rails/error.rbs +4 -0
- data/sig/react_on_rails/helper.rbs +2 -2
- data/sig/react_on_rails/json_parse_error.rbs +10 -0
- data/sig/react_on_rails/prerender_error.rbs +21 -0
- data/sig/react_on_rails/smart_error.rbs +28 -0
- data/sig/react_on_rails.rbs +3 -24
- metadata +14 -4
- data/lib/react_on_rails/pro_utils.rb +0 -37
- data/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TestingStreamableComponent.jsx +0 -15
|
@@ -1228,10 +1228,6 @@
|
|
|
1228
1228
|
dependencies:
|
|
1229
1229
|
"@sentry/types" "7.120.0"
|
|
1230
1230
|
|
|
1231
|
-
"@shakacode-tools/react-on-rails-pro-node-renderer@link:.yalc/@shakacode-tools/react-on-rails-pro-node-renderer":
|
|
1232
|
-
version "0.0.0"
|
|
1233
|
-
uid ""
|
|
1234
|
-
|
|
1235
1231
|
"@shakacode/use-ssr-computation.macro@^1.2.4":
|
|
1236
1232
|
version "1.2.4"
|
|
1237
1233
|
resolved "https://registry.yarnpkg.com/@shakacode/use-ssr-computation.macro/-/use-ssr-computation.macro-1.2.4.tgz#b247d683e3133126dbdb42060c26e1c34a8b625d"
|
|
@@ -5461,6 +5457,10 @@ react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0:
|
|
|
5461
5457
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
|
5462
5458
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
|
5463
5459
|
|
|
5460
|
+
"react-on-rails-pro-node-renderer@link:.yalc/react-on-rails-pro-node-renderer":
|
|
5461
|
+
version "0.0.0"
|
|
5462
|
+
uid ""
|
|
5463
|
+
|
|
5464
5464
|
"react-on-rails-pro@link:.yalc/react-on-rails-pro":
|
|
5465
5465
|
version "0.0.0"
|
|
5466
5466
|
uid ""
|
|
@@ -55,7 +55,7 @@ Rails.application.configure do
|
|
|
55
55
|
|
|
56
56
|
# Log to STDOUT by default
|
|
57
57
|
config.logger = ActiveSupport::Logger.new($stdout)
|
|
58
|
-
.tap { |logger| logger.formatter =
|
|
58
|
+
.tap { |logger| logger.formatter = Logger::Formatter.new }
|
|
59
59
|
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }
|
|
60
60
|
|
|
61
61
|
# Prepend all log lines with the following tags.
|
|
@@ -1,58 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# for
|
|
3
|
+
# ⚠️ TEST CONFIGURATION - Do not copy directly for production apps
|
|
4
|
+
# This is the ExecJS-compatible dummy app for testing legacy webpacker compatibility.
|
|
5
|
+
# See docs/api-reference/configuration.md for production configuration guidance.
|
|
5
6
|
|
|
6
7
|
ReactOnRails.configure do |config|
|
|
7
|
-
# This configures the script to run to build the production assets by webpack. Set this to nil
|
|
8
|
-
# if you don't want react_on_rails building this file for you.
|
|
9
|
-
# If nil, then the standard shakacode/webpacker assets:precompile will run
|
|
10
|
-
# config.build_production_command = nil
|
|
11
|
-
|
|
12
|
-
################################################################################
|
|
13
8
|
################################################################################
|
|
14
|
-
#
|
|
15
|
-
# Below options are used with the use of this test helper:
|
|
16
|
-
# ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
|
|
9
|
+
# Essential Configuration
|
|
17
10
|
################################################################################
|
|
11
|
+
# Configure server bundle for server-side rendering
|
|
12
|
+
config.server_bundle_js_file = "server-bundle.js"
|
|
18
13
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
|
|
22
|
-
#
|
|
23
|
-
# with rspec then this controls what yarn command is run
|
|
24
|
-
# to automatically refresh your webpack assets on every test run.
|
|
25
|
-
#
|
|
26
|
-
# Alternately, you can remove the `ReactOnRails::TestHelper.configure_rspec_to_compile_assets`
|
|
27
|
-
# and set the config/webpacker.yml option for test to true.
|
|
14
|
+
# Test configuration
|
|
28
15
|
config.build_test_command = "RAILS_ENV=test bin/webpacker"
|
|
29
16
|
|
|
30
17
|
################################################################################
|
|
18
|
+
# File System Based Component Registry (Optional - Disabled for this test)
|
|
31
19
|
################################################################################
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
# Note, there is only one server bundle, unlike JavaScript where you want to minimize the size
|
|
37
|
-
# of the JS sent to the client. For the server rendering, React on Rails creates a pool of
|
|
38
|
-
# JavaScript execution instances which should handle any component requested.
|
|
39
|
-
#
|
|
40
|
-
# While you may configure this to be the same as your client bundle file, this file is typically
|
|
41
|
-
# different. You should have ONE server bundle which can create all of your server rendered
|
|
42
|
-
# React components.
|
|
43
|
-
#
|
|
44
|
-
config.server_bundle_js_file = "server-bundle.js"
|
|
20
|
+
# Uncomment to enable automatic component registration:
|
|
21
|
+
# config.components_subdirectory = "ror_components"
|
|
22
|
+
# config.auto_load_bundle = true
|
|
23
|
+
config.auto_load_bundle = false
|
|
45
24
|
|
|
46
25
|
################################################################################
|
|
26
|
+
# Advanced Configuration
|
|
47
27
|
################################################################################
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
#
|
|
51
|
-
# for use in the Rails views. The default is nil, you can enable the feature by updating it in the next line.
|
|
52
|
-
# config.components_subdirectory = "ror_components"
|
|
53
|
-
#
|
|
54
|
-
# For automated component registry, `render_component` view helper method tries to load bundle for component from
|
|
55
|
-
# generated directory. default is false, you can pass option at the time of individual usage or update the default
|
|
56
|
-
# in the following line
|
|
57
|
-
config.auto_load_bundle = false
|
|
28
|
+
# Most options have sensible defaults. For advanced configuration including
|
|
29
|
+
# component loading strategies, server bundle security, and more, see:
|
|
30
|
+
# https://github.com/shakacode/react_on_rails/blob/master/docs/api-reference/configuration.md
|
|
58
31
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "spec_helper"
|
|
4
4
|
require_relative "../../lib/react_on_rails_pro/assets_precompile"
|
|
5
5
|
|
|
6
6
|
describe ReactOnRailsPro::AssetsPrecompile do
|
|
@@ -46,8 +46,6 @@ describe ReactOnRailsPro::AssetsPrecompile do
|
|
|
46
46
|
|
|
47
47
|
ror_pro_config = instance_double(ReactOnRailsPro::Configuration)
|
|
48
48
|
|
|
49
|
-
allow(ror_pro_config).to receive(:dependency_globs).and_return([expected_parameters.last])
|
|
50
|
-
|
|
51
49
|
adapter = Module.new do
|
|
52
50
|
def self.cache_keys
|
|
53
51
|
%w[a b]
|
|
@@ -58,7 +56,8 @@ describe ReactOnRailsPro::AssetsPrecompile do
|
|
|
58
56
|
end
|
|
59
57
|
end
|
|
60
58
|
|
|
61
|
-
allow(ror_pro_config).to
|
|
59
|
+
allow(ror_pro_config).to receive_messages(dependency_globs: [expected_parameters.last],
|
|
60
|
+
remote_bundle_cache_adapter: adapter)
|
|
62
61
|
|
|
63
62
|
stub_const("ReactOnRailsPro::VERSION", "2.2.0")
|
|
64
63
|
|
|
@@ -136,11 +135,10 @@ describe ReactOnRailsPro::AssetsPrecompile do
|
|
|
136
135
|
it "calls build_bundles & cache_bundles if cached bundles can't be fetched" do
|
|
137
136
|
instance = described_class.instance
|
|
138
137
|
|
|
139
|
-
allow(instance).to receive(:fetch_and_unzip_cached_bundles).and_return(false)
|
|
140
138
|
expect(instance).to receive(:fetch_and_unzip_cached_bundles).once
|
|
141
|
-
allow(instance).to receive(:build_bundles).and_return(nil)
|
|
142
139
|
expect(instance).to receive(:build_bundles).once
|
|
143
|
-
allow(instance).to
|
|
140
|
+
allow(instance).to receive_messages(fetch_and_unzip_cached_bundles: false, build_bundles: nil,
|
|
141
|
+
cache_bundles: nil)
|
|
144
142
|
expect(instance).to receive(:cache_bundles).once
|
|
145
143
|
|
|
146
144
|
instance.build_or_fetch_bundles
|
|
@@ -179,9 +177,9 @@ describe ReactOnRailsPro::AssetsPrecompile do
|
|
|
179
177
|
unique_variable = { unique_key: "a unique value" }
|
|
180
178
|
|
|
181
179
|
instance = described_class.instance
|
|
182
|
-
allow(instance).to
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
allow(instance).to receive_messages(remote_bundle_cache_adapter: adapter_double,
|
|
181
|
+
zipped_bundles_filename: unique_variable,
|
|
182
|
+
zipped_bundles_filepath: "zipped_bundles_filepath")
|
|
185
183
|
|
|
186
184
|
allow(File).to receive(:binwrite).and_return(true)
|
|
187
185
|
expect(File).to receive(:binwrite).once
|
|
@@ -197,8 +195,7 @@ describe ReactOnRailsPro::AssetsPrecompile do
|
|
|
197
195
|
allow(File).to receive(:exist?).and_return(false)
|
|
198
196
|
|
|
199
197
|
instance = described_class.instance
|
|
200
|
-
allow(instance).to
|
|
201
|
-
allow(instance).to receive(:zipped_bundles_filepath).and_return("a")
|
|
198
|
+
allow(instance).to receive_messages(fetch_bundles: false, zipped_bundles_filepath: "a")
|
|
202
199
|
|
|
203
200
|
expect(instance.fetch_and_unzip_cached_bundles).to be(false)
|
|
204
201
|
end
|
|
@@ -256,10 +253,10 @@ describe ReactOnRailsPro::AssetsPrecompile do
|
|
|
256
253
|
zipped_bundles_filepath = Pathname.new(Dir.tmpdir).join("foobar")
|
|
257
254
|
|
|
258
255
|
instance = described_class.instance
|
|
259
|
-
allow(instance).to
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
256
|
+
allow(instance).to receive_messages(remote_bundle_cache_adapter: adapter_double,
|
|
257
|
+
zipped_bundles_filename: "zipped_bundles_filename",
|
|
258
|
+
zipped_bundles_filepath: zipped_bundles_filepath,
|
|
259
|
+
remove_extra_files_cache_dir: nil)
|
|
263
260
|
|
|
264
261
|
expect(instance.cache_bundles).to be_truthy
|
|
265
262
|
|
|
@@ -289,8 +286,8 @@ describe ReactOnRailsPro::AssetsPrecompile do
|
|
|
289
286
|
|
|
290
287
|
instance = described_class.instance
|
|
291
288
|
|
|
292
|
-
allow(instance).to
|
|
293
|
-
|
|
289
|
+
allow(instance).to receive_messages(remote_bundle_cache_adapter: adapter,
|
|
290
|
+
extra_files_path: Pathname.new(Dir.pwd).join("extra_files_cache_dir"))
|
|
294
291
|
copied_gemfile_path = Pathname.new(Dir.pwd).join("extra_files_cache_dir", "Gemfile")
|
|
295
292
|
copied_assets_precompile_path = Pathname.new(Dir.pwd).join("extra_files_cache_dir",
|
|
296
293
|
"lib---react_on_rails_pro---assets_precompile.rb")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "spec_helper"
|
|
4
4
|
|
|
5
|
-
module ReactOnRailsPro
|
|
5
|
+
module ReactOnRailsPro # rubocop:disable Metrics/ModuleLength
|
|
6
6
|
RSpec.describe Configuration do
|
|
7
7
|
after do
|
|
8
8
|
ReactOnRailsPro.instance_variable_set(:@configuration, nil)
|
|
@@ -205,7 +205,8 @@ module ReactOnRailsPro
|
|
|
205
205
|
|
|
206
206
|
expect(ReactOnRailsPro.configuration.rsc_bundle_js_file).to eq("rsc-bundle.js")
|
|
207
207
|
expect(ReactOnRailsPro.configuration.react_client_manifest_file).to eq("react-client-manifest.json")
|
|
208
|
-
expect(ReactOnRailsPro.configuration.react_server_client_manifest_file)
|
|
208
|
+
expect(ReactOnRailsPro.configuration.react_server_client_manifest_file)
|
|
209
|
+
.to eq("react-server-client-manifest.json")
|
|
209
210
|
end
|
|
210
211
|
|
|
211
212
|
it "allows setting rsc_bundle_js_file" do
|
|
@@ -229,7 +230,8 @@ module ReactOnRailsPro
|
|
|
229
230
|
config.react_server_client_manifest_file = "custom-server-client-manifest.json"
|
|
230
231
|
end
|
|
231
232
|
|
|
232
|
-
expect(ReactOnRailsPro.configuration.react_server_client_manifest_file)
|
|
233
|
+
expect(ReactOnRailsPro.configuration.react_server_client_manifest_file)
|
|
234
|
+
.to eq("custom-server-client-manifest.json")
|
|
233
235
|
end
|
|
234
236
|
|
|
235
237
|
it "allows nil values for RSC configuration options" do
|
|
@@ -75,26 +75,30 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
75
75
|
ENV["REACT_ON_RAILS_PRO_LICENSE"] = expired_token
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
context "in development/test environment" do
|
|
78
|
+
context "when in development/test environment" do
|
|
79
79
|
before do
|
|
80
80
|
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("development"))
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
it "raises error immediately" do
|
|
84
|
-
expect
|
|
84
|
+
expect do
|
|
85
|
+
described_class.validated_license_data!
|
|
86
|
+
end.to raise_error(ReactOnRailsPro::Error, /License has expired/)
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
it "includes FREE license information in error message" do
|
|
88
|
-
expect
|
|
90
|
+
expect do
|
|
91
|
+
described_class.validated_license_data!
|
|
92
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
89
93
|
end
|
|
90
94
|
end
|
|
91
95
|
|
|
92
|
-
context "in production environment" do
|
|
96
|
+
context "when in production environment" do
|
|
93
97
|
before do
|
|
94
98
|
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
|
|
95
99
|
end
|
|
96
100
|
|
|
97
|
-
context "
|
|
101
|
+
context "with grace period (expired < 1 month ago)" do
|
|
98
102
|
let(:expired_within_grace) do
|
|
99
103
|
{
|
|
100
104
|
sub: "test@example.com",
|
|
@@ -113,7 +117,8 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
it "logs warning with grace period remaining" do
|
|
116
|
-
expect(mock_logger).to receive(:error)
|
|
120
|
+
expect(mock_logger).to receive(:error)
|
|
121
|
+
.with(/WARNING:.*License has expired.*Grace period:.*day\(s\) remaining/)
|
|
117
122
|
described_class.validated_license_data!
|
|
118
123
|
end
|
|
119
124
|
|
|
@@ -123,7 +128,7 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
123
128
|
end
|
|
124
129
|
end
|
|
125
130
|
|
|
126
|
-
context "outside grace period (expired > 1 month ago)" do
|
|
131
|
+
context "when outside grace period (expired > 1 month ago)" do
|
|
127
132
|
let(:expired_outside_grace) do
|
|
128
133
|
{
|
|
129
134
|
sub: "test@example.com",
|
|
@@ -138,11 +143,15 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
138
143
|
end
|
|
139
144
|
|
|
140
145
|
it "raises error" do
|
|
141
|
-
expect
|
|
146
|
+
expect do
|
|
147
|
+
described_class.validated_license_data!
|
|
148
|
+
end.to raise_error(ReactOnRailsPro::Error, /License has expired/)
|
|
142
149
|
end
|
|
143
150
|
|
|
144
151
|
it "includes FREE license information in error message" do
|
|
145
|
-
expect
|
|
152
|
+
expect do
|
|
153
|
+
described_class.validated_license_data!
|
|
154
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
146
155
|
end
|
|
147
156
|
end
|
|
148
157
|
end
|
|
@@ -168,7 +177,9 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
168
177
|
end
|
|
169
178
|
|
|
170
179
|
it "includes FREE license information in error message" do
|
|
171
|
-
expect
|
|
180
|
+
expect do
|
|
181
|
+
described_class.validated_license_data!
|
|
182
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
172
183
|
end
|
|
173
184
|
end
|
|
174
185
|
|
|
@@ -180,11 +191,15 @@ RSpec.describe ReactOnRailsPro::LicenseValidator do
|
|
|
180
191
|
end
|
|
181
192
|
|
|
182
193
|
it "raises error" do
|
|
183
|
-
expect
|
|
194
|
+
expect do
|
|
195
|
+
described_class.validated_license_data!
|
|
196
|
+
end.to raise_error(ReactOnRailsPro::Error, /Invalid license signature/)
|
|
184
197
|
end
|
|
185
198
|
|
|
186
199
|
it "includes FREE license information in error message" do
|
|
187
|
-
expect
|
|
200
|
+
expect do
|
|
201
|
+
described_class.validated_license_data!
|
|
202
|
+
end.to raise_error(ReactOnRailsPro::Error, /FREE evaluation license/)
|
|
188
203
|
end
|
|
189
204
|
end
|
|
190
205
|
|
|
@@ -194,32 +194,5 @@ describe ReactOnRailsPro::Request do
|
|
|
194
194
|
expect(mocked_block).not_to have_received(:call)
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
|
-
|
|
198
|
-
it "does not use HTTPx retries plugin for streaming requests to prevent body duplication" do
|
|
199
|
-
# This test verifies the fix for https://github.com/shakacode/react_on_rails/issues/1895
|
|
200
|
-
# When streaming requests encounter connection errors mid-transmission, HTTPx retries
|
|
201
|
-
# would cause body duplication because partial chunks are already sent to the client.
|
|
202
|
-
# The StreamRequest class handles retries properly by starting fresh requests.
|
|
203
|
-
|
|
204
|
-
# Reset connections to ensure we're using a fresh connection
|
|
205
|
-
described_class.reset_connection
|
|
206
|
-
|
|
207
|
-
# Trigger a streaming request
|
|
208
|
-
mock_streaming_response(render_full_url, 200) do |yielder|
|
|
209
|
-
yielder.call("Test chunk\n")
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
stream = described_class.render_code_as_stream("/render", "console.log('test');", is_rsc_payload: false)
|
|
213
|
-
chunks = []
|
|
214
|
-
stream.each_chunk { |chunk| chunks << chunk }
|
|
215
|
-
|
|
216
|
-
# Verify that the streaming request completed successfully
|
|
217
|
-
expect(chunks).to eq(["Test chunk"])
|
|
218
|
-
|
|
219
|
-
# Verify that the connection_without_retries was created
|
|
220
|
-
# by checking that a connection was created with retries disabled
|
|
221
|
-
connection_without_retries = described_class.send(:connection_without_retries)
|
|
222
|
-
expect(connection_without_retries).to be_a(HTTPX::Session)
|
|
223
|
-
end
|
|
224
197
|
end
|
|
225
198
|
end
|
|
@@ -15,7 +15,7 @@ require "rails"
|
|
|
15
15
|
require "rails/test_help"
|
|
16
16
|
Rails.backtrace_cleaner.remove_silencers!
|
|
17
17
|
|
|
18
|
-
require_relative "
|
|
18
|
+
require_relative "simplecov_helper"
|
|
19
19
|
# prevent Test::Unit's AutoRunner from executing during RSpec's rake task
|
|
20
20
|
Test::Unit.run = true if defined?(Test::Unit) && Test::Unit.respond_to?(:run=)
|
|
21
21
|
|
|
@@ -62,4 +62,93 @@ RSpec.describe ReactOnRailsPro::StreamDecorator do
|
|
|
62
62
|
expect(chunks.last).to end_with("-end")
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
describe "#rescue" do
|
|
67
|
+
it "catches the error happens inside the component" do
|
|
68
|
+
allow(mock_component).to receive(:each_chunk).and_raise(StandardError.new("Fake Error"))
|
|
69
|
+
mocked_block = mock_block
|
|
70
|
+
|
|
71
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
72
|
+
chunks = []
|
|
73
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
74
|
+
|
|
75
|
+
expect(mocked_block).to have_received(:call) do |error|
|
|
76
|
+
expect(error).to be_a(StandardError)
|
|
77
|
+
expect(error.message).to eq("Fake Error")
|
|
78
|
+
end
|
|
79
|
+
expect(chunks).to eq([])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "catches the error happens inside subsequent component calls" do
|
|
83
|
+
allow(mock_component).to receive(:each_chunk).and_yield("Chunk1").and_raise(ArgumentError.new("Fake Error"))
|
|
84
|
+
mocked_block = mock_block
|
|
85
|
+
|
|
86
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
87
|
+
chunks = []
|
|
88
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
89
|
+
|
|
90
|
+
expect(mocked_block).to have_received(:call) do |error|
|
|
91
|
+
expect(chunks).to eq(["Chunk1"])
|
|
92
|
+
expect(error).to be_a(ArgumentError)
|
|
93
|
+
expect(error.message).to eq("Fake Error")
|
|
94
|
+
end
|
|
95
|
+
expect(chunks).to eq(["Chunk1"])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "can yield values to the stream" do
|
|
99
|
+
allow(mock_component).to receive(:each_chunk).and_yield("Chunk1").and_raise(ArgumentError.new("Fake Error"))
|
|
100
|
+
mocked_block = mock_block
|
|
101
|
+
|
|
102
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
103
|
+
chunks = []
|
|
104
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
105
|
+
|
|
106
|
+
expect(mocked_block).to have_received(:call) do |error, &inner_block|
|
|
107
|
+
expect(chunks).to eq(["Chunk1"])
|
|
108
|
+
expect(error).to be_a(ArgumentError)
|
|
109
|
+
expect(error.message).to eq("Fake Error")
|
|
110
|
+
|
|
111
|
+
inner_block.call "Chunk from rescue block"
|
|
112
|
+
inner_block.call "Chunk2 from rescue block"
|
|
113
|
+
end
|
|
114
|
+
expect(chunks).to eq(["Chunk1", "Chunk from rescue block", "Chunk2 from rescue block"])
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "can convert the error into another error" do
|
|
118
|
+
allow(mock_component).to receive(:each_chunk).and_raise(StandardError.new("Fake Error"))
|
|
119
|
+
mocked_block = mock_block do |error|
|
|
120
|
+
expect(error).to be_a(StandardError)
|
|
121
|
+
expect(error.message).to eq("Fake Error")
|
|
122
|
+
raise ArgumentError, "Another Error"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
stream_decorator.rescue(&mocked_block.block)
|
|
126
|
+
chunks = []
|
|
127
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.to raise_error(ArgumentError, "Another Error")
|
|
128
|
+
expect(chunks).to eq([])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "chains multiple rescue blocks" do
|
|
132
|
+
allow(mock_component).to receive(:each_chunk).and_yield("Chunk1").and_raise(StandardError.new("Fake Error"))
|
|
133
|
+
fist_rescue_block = mock_block do |error, &block|
|
|
134
|
+
expect(error).to be_a(StandardError)
|
|
135
|
+
expect(error.message).to eq("Fake Error")
|
|
136
|
+
block.call "Chunk from first rescue block"
|
|
137
|
+
raise ArgumentError, "Another Error"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
second_rescue_block = mock_block do |error, &block|
|
|
141
|
+
expect(error).to be_a(ArgumentError)
|
|
142
|
+
expect(error.message).to eq("Another Error")
|
|
143
|
+
block.call "Chunk from second rescue block"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
stream_decorator.rescue(&fist_rescue_block.block)
|
|
147
|
+
stream_decorator.rescue(&second_rescue_block.block)
|
|
148
|
+
chunks = []
|
|
149
|
+
expect { stream_decorator.each_chunk { |chunk| chunks << chunk } }.not_to raise_error
|
|
150
|
+
|
|
151
|
+
expect(chunks).to eq(["Chunk1", "Chunk from first rescue block", "Chunk from second rescue block"])
|
|
152
|
+
end
|
|
153
|
+
end
|
|
65
154
|
end
|
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "async"
|
|
4
|
+
require "async/queue"
|
|
3
5
|
require_relative "spec_helper"
|
|
4
6
|
|
|
7
|
+
class StreamController
|
|
8
|
+
include ReactOnRailsPro::Stream
|
|
9
|
+
|
|
10
|
+
attr_reader :response
|
|
11
|
+
|
|
12
|
+
def initialize(component_queues:, initial_response: "TEMPLATE")
|
|
13
|
+
@component_queues = component_queues
|
|
14
|
+
@initial_response = initial_response
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render_to_string(**_opts)
|
|
18
|
+
@rorp_rendering_fibers = @component_queues.map do |queue|
|
|
19
|
+
Fiber.new do
|
|
20
|
+
loop do
|
|
21
|
+
chunk = queue.dequeue
|
|
22
|
+
break if chunk.nil?
|
|
23
|
+
|
|
24
|
+
Fiber.yield chunk
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@initial_response
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
5
33
|
RSpec.describe "Streaming API" do
|
|
6
34
|
let(:origin) { "http://api.example.com" }
|
|
7
35
|
let(:path) { "/stream" }
|
|
@@ -342,4 +370,120 @@ RSpec.describe "Streaming API" do
|
|
|
342
370
|
expect(mocked_block).to have_received(:call).with("First chunk")
|
|
343
371
|
end
|
|
344
372
|
end
|
|
373
|
+
|
|
374
|
+
describe "Component streaming concurrency" do
|
|
375
|
+
def run_stream(controller, template: "ignored")
|
|
376
|
+
Sync do |parent|
|
|
377
|
+
parent.async { controller.stream_view_containing_react_components(template: template) }
|
|
378
|
+
yield(parent)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def setup_stream_test(component_count: 2)
|
|
383
|
+
component_queues = Array.new(component_count) { Async::Queue.new }
|
|
384
|
+
controller = StreamController.new(component_queues: component_queues)
|
|
385
|
+
|
|
386
|
+
mocked_response = instance_double(ActionController::Live::Response)
|
|
387
|
+
mocked_stream = instance_double(ActionController::Live::Buffer)
|
|
388
|
+
allow(mocked_response).to receive(:stream).and_return(mocked_stream)
|
|
389
|
+
allow(mocked_stream).to receive(:write)
|
|
390
|
+
allow(mocked_stream).to receive(:close)
|
|
391
|
+
allow(controller).to receive(:response).and_return(mocked_response)
|
|
392
|
+
|
|
393
|
+
[component_queues, controller, mocked_stream]
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
it "streams components concurrently" do
|
|
397
|
+
queues, controller, stream = setup_stream_test
|
|
398
|
+
|
|
399
|
+
run_stream(controller) do |_parent|
|
|
400
|
+
queues[1].enqueue("B1")
|
|
401
|
+
sleep 0.05
|
|
402
|
+
expect(stream).to have_received(:write).with("B1")
|
|
403
|
+
|
|
404
|
+
queues[0].enqueue("A1")
|
|
405
|
+
sleep 0.05
|
|
406
|
+
expect(stream).to have_received(:write).with("A1")
|
|
407
|
+
|
|
408
|
+
queues[1].enqueue("B2")
|
|
409
|
+
queues[1].close
|
|
410
|
+
sleep 0.05
|
|
411
|
+
|
|
412
|
+
queues[0].enqueue("A2")
|
|
413
|
+
queues[0].close
|
|
414
|
+
sleep 0.1
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
it "maintains per-component ordering" do
|
|
419
|
+
queues, controller, stream = setup_stream_test
|
|
420
|
+
|
|
421
|
+
run_stream(controller) do |_parent|
|
|
422
|
+
queues[0].enqueue("X1")
|
|
423
|
+
queues[0].enqueue("X2")
|
|
424
|
+
queues[0].enqueue("X3")
|
|
425
|
+
queues[0].close
|
|
426
|
+
|
|
427
|
+
queues[1].enqueue("Y1")
|
|
428
|
+
queues[1].enqueue("Y2")
|
|
429
|
+
queues[1].close
|
|
430
|
+
|
|
431
|
+
sleep 0.2
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Verify all chunks were written
|
|
435
|
+
expect(stream).to have_received(:write).with("X1")
|
|
436
|
+
expect(stream).to have_received(:write).with("X2")
|
|
437
|
+
expect(stream).to have_received(:write).with("X3")
|
|
438
|
+
expect(stream).to have_received(:write).with("Y1")
|
|
439
|
+
expect(stream).to have_received(:write).with("Y2")
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
it "handles empty component list" do
|
|
443
|
+
_queues, controller, stream = setup_stream_test(component_count: 0)
|
|
444
|
+
|
|
445
|
+
run_stream(controller) do |_parent|
|
|
446
|
+
sleep 0.1
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
expect(stream).to have_received(:write).with("TEMPLATE")
|
|
450
|
+
expect(stream).to have_received(:close)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
it "handles single component" do
|
|
454
|
+
queues, controller, stream = setup_stream_test(component_count: 1)
|
|
455
|
+
|
|
456
|
+
run_stream(controller) do |_parent|
|
|
457
|
+
queues[0].enqueue("Single1")
|
|
458
|
+
queues[0].enqueue("Single2")
|
|
459
|
+
queues[0].close
|
|
460
|
+
|
|
461
|
+
sleep 0.1
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
expect(stream).to have_received(:write).with("Single1")
|
|
465
|
+
expect(stream).to have_received(:write).with("Single2")
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
it "applies backpressure with slow writer" do
|
|
469
|
+
queues, controller, stream = setup_stream_test(component_count: 1)
|
|
470
|
+
|
|
471
|
+
write_timestamps = []
|
|
472
|
+
allow(stream).to receive(:write) do |_data|
|
|
473
|
+
write_timestamps << Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
474
|
+
sleep 0.05
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
run_stream(controller) do |_parent|
|
|
478
|
+
5.times { |i| queues[0].enqueue("Chunk#{i}") }
|
|
479
|
+
queues[0].close
|
|
480
|
+
|
|
481
|
+
sleep 1
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
expect(write_timestamps.length).to be >= 2
|
|
485
|
+
gaps = write_timestamps.each_cons(2).map { |a, b| b - a }
|
|
486
|
+
expect(gaps.all? { |gap| gap >= 0.04 }).to be true
|
|
487
|
+
end
|
|
488
|
+
end
|
|
345
489
|
end
|
|
@@ -4,7 +4,7 @@ RSpec.configure do |config|
|
|
|
4
4
|
config.before(:each, :caching) do
|
|
5
5
|
cache_store = ActiveSupport::Cache::MemoryStore.new
|
|
6
6
|
allow(controller).to receive(:cache_store).and_return(cache_store) if defined?(controller) && controller
|
|
7
|
-
allow(
|
|
7
|
+
allow(Rails).to receive(:cache).and_return(cache_store)
|
|
8
8
|
ReactOnRailsPro::Cache.instance_variable_set(:@serializer_checksum, nil)
|
|
9
9
|
Rails.cache.clear
|
|
10
10
|
end
|
|
@@ -9,9 +9,11 @@ module MockBlockHelper
|
|
|
9
9
|
# mocked_block = mock_block
|
|
10
10
|
# testing_method_taking_block(&mocked_block.block)
|
|
11
11
|
# expect(mocked_block).to have_received(:call).with(1, 2, 3)
|
|
12
|
-
def mock_block(
|
|
12
|
+
def mock_block(&block)
|
|
13
13
|
double("BlockMock").tap do |mock| # rubocop:disable RSpec/VerifiedDoubles
|
|
14
|
-
allow(mock).to receive(:call)
|
|
14
|
+
allow(mock).to receive(:call) do |*args, &inner_block|
|
|
15
|
+
block&.call(*args, &inner_block)
|
|
16
|
+
end
|
|
15
17
|
def mock.block
|
|
16
18
|
method(:call).to_proc
|
|
17
19
|
end
|