react_on_rails 16.1.2 → 16.2.0

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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +85 -0
  4. data/Gemfile.development_dependencies +8 -7
  5. data/Gemfile.lock +158 -119
  6. data/Steepfile +56 -0
  7. data/lib/generators/react_on_rails/base_generator.rb +43 -120
  8. data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/generator_helper.rb +102 -2
  10. data/lib/generators/react_on_rails/install_generator.rb +36 -156
  11. data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
  12. data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
  13. data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
  14. data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
  15. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
  16. data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
  17. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
  18. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
  19. data/lib/react_on_rails/configuration.rb +149 -32
  20. data/lib/react_on_rails/controller.rb +3 -3
  21. data/lib/react_on_rails/dev/pack_generator.rb +168 -2
  22. data/lib/react_on_rails/dev/process_manager.rb +136 -14
  23. data/lib/react_on_rails/dev/server_manager.rb +194 -26
  24. data/lib/react_on_rails/dev/service_checker.rb +200 -0
  25. data/lib/react_on_rails/doctor.rb +341 -12
  26. data/lib/react_on_rails/engine.rb +75 -1
  27. data/lib/react_on_rails/git_utils.rb +3 -1
  28. data/lib/react_on_rails/helper.rb +70 -192
  29. data/lib/react_on_rails/locales/base.rb +17 -5
  30. data/lib/react_on_rails/packer_utils.rb +79 -2
  31. data/lib/react_on_rails/packs_generator.rb +57 -39
  32. data/lib/react_on_rails/prerender_error.rb +74 -17
  33. data/lib/react_on_rails/pro_helper.rb +64 -0
  34. data/lib/react_on_rails/react_component/render_options.rb +7 -7
  35. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
  36. data/lib/react_on_rails/smart_error.rb +326 -0
  37. data/lib/react_on_rails/system_checker.rb +8 -9
  38. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
  39. data/lib/react_on_rails/utils.rb +241 -55
  40. data/lib/react_on_rails/version.rb +1 -1
  41. data/lib/react_on_rails/version_checker.rb +383 -35
  42. data/lib/tasks/generate_packs.rake +12 -6
  43. data/lib/tasks/locale.rake +6 -1
  44. data/rakelib/docker.rake +26 -0
  45. data/rakelib/dummy_apps.rake +30 -0
  46. data/rakelib/example_type.rb +121 -0
  47. data/rakelib/examples_config.yml +52 -0
  48. data/rakelib/lint.rake +52 -0
  49. data/rakelib/node_package.rake +15 -0
  50. data/rakelib/rbs.rake +70 -0
  51. data/rakelib/run_rspec.rake +223 -0
  52. data/rakelib/shakapacker_examples.rake +171 -0
  53. data/rakelib/task_helpers.rb +134 -0
  54. data/rakelib/update_changelog.rake +73 -0
  55. data/react_on_rails.gemspec +4 -3
  56. data/sig/README.md +52 -0
  57. data/sig/react_on_rails/configuration.rbs +96 -0
  58. data/sig/react_on_rails/controller.rbs +15 -0
  59. data/sig/react_on_rails/dev/file_manager.rbs +15 -0
  60. data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
  61. data/sig/react_on_rails/dev/process_manager.rbs +22 -0
  62. data/sig/react_on_rails/dev/server_manager.rbs +39 -0
  63. data/sig/react_on_rails/dev/service_checker.rbs +22 -0
  64. data/sig/react_on_rails/error.rbs +4 -0
  65. data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
  66. data/sig/react_on_rails/git_utils.rbs +8 -0
  67. data/sig/react_on_rails/helper.rbs +65 -0
  68. data/sig/react_on_rails/json_parse_error.rbs +10 -0
  69. data/sig/react_on_rails/locales.rbs +46 -0
  70. data/sig/react_on_rails/packer_utils.rbs +15 -0
  71. data/sig/react_on_rails/prerender_error.rbs +21 -0
  72. data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
  73. data/sig/react_on_rails/smart_error.rbs +28 -0
  74. data/sig/react_on_rails/test_helper.rbs +11 -0
  75. data/sig/react_on_rails/utils.rbs +34 -0
  76. data/sig/react_on_rails/version_checker.rbs +12 -0
  77. data/sig/react_on_rails.rbs +17 -0
  78. metadata +49 -32
  79. data/AI_AGENT_INSTRUCTIONS.md +0 -63
  80. data/CHANGELOG.md +0 -1836
  81. data/CLAUDE.md +0 -135
  82. data/CODING_AGENTS.md +0 -313
  83. data/CONTRIBUTING.md +0 -668
  84. data/Dockerfile_tests +0 -12
  85. data/KUDOS.md +0 -114
  86. data/LICENSE.md +0 -47
  87. data/LICENSES/README.md +0 -14
  88. data/NEWS.md +0 -62
  89. data/PROJECTS.md +0 -63
  90. data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
  91. data/README.md +0 -217
  92. data/SUMMARY.md +0 -88
  93. data/TODO.md +0 -135
  94. data/bin/lefthook/check-trailing-newlines +0 -38
  95. data/bin/lefthook/get-changed-files +0 -26
  96. data/bin/lefthook/prettier-format +0 -26
  97. data/bin/lefthook/ruby-autofix +0 -26
  98. data/bin/lefthook/ruby-lint +0 -27
  99. data/docker-compose.yml +0 -11
  100. data/eslint.config.ts +0 -232
  101. data/knip.ts +0 -114
  102. data/lib/react_on_rails/pro/NOTICE +0 -21
  103. data/lib/react_on_rails/pro/helper.rb +0 -122
  104. data/lib/react_on_rails/pro/utils.rb +0 -53
  105. data/tsconfig.eslint.json +0 -6
  106. data/tsconfig.json +0 -19
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+
5
+ require_relative "task_helpers"
6
+ require_relative File.join(__dir__, "..", "lib", "react_on_rails", "utils")
7
+
8
+ # Defines the ExampleType class, where each object represents a unique type of example
9
+ # app that we can generate.
10
+ module ReactOnRails
11
+ module TaskHelpers
12
+ class ExampleType
13
+ def self.all
14
+ @all ||= { shakapacker_examples: [] }
15
+ end
16
+
17
+ # Supported React versions for compatibility testing
18
+ # Keys are major version strings, values are specific version to pin to (nil = latest)
19
+ REACT_VERSIONS = {
20
+ "16" => "16.14.0",
21
+ "17" => "17.0.2",
22
+ "18" => "18.0.0",
23
+ "19" => nil # nil means use latest (default)
24
+ }.freeze
25
+
26
+ # Supported React major versions (we test with latest patch of each)
27
+ MINIMUM_SUPPORTED_REACT_MAJOR_VERSION = "16"
28
+ LATEST_REACT_MAJOR_VERSION = "19"
29
+
30
+ # Minimum Shakapacker version for compatibility testing
31
+ MINIMUM_SHAKAPACKER_VERSION = "8.2.0"
32
+
33
+ attr_reader :packer_type, :name, :generator_options, :react_version
34
+
35
+ # Returns true if this example uses a pinned (non-latest) React version
36
+ def pinned_react_version?
37
+ !react_version.nil?
38
+ end
39
+
40
+ # Returns the actual React version string to use
41
+ def react_version_string
42
+ return nil unless react_version
43
+
44
+ REACT_VERSIONS[react_version.to_s] || react_version
45
+ end
46
+
47
+ def initialize(packer_type: nil, name: nil, generator_options: nil, react_version: nil)
48
+ @packer_type = packer_type
49
+ @name = name
50
+ @generator_options = generator_options
51
+ @react_version = react_version
52
+
53
+ # Validate react_version is a known version to catch configuration errors early
54
+ if @react_version && !REACT_VERSIONS.key?(@react_version.to_s)
55
+ valid_versions = REACT_VERSIONS.keys.join(", ")
56
+ raise ArgumentError, "Invalid react_version '#{@react_version}' for example '#{name}'. " \
57
+ "Valid versions: #{valid_versions}"
58
+ end
59
+
60
+ self.class.all[packer_type.to_sym] << self
61
+ end
62
+
63
+ def name_pretty
64
+ "#{@name} example app"
65
+ end
66
+
67
+ def dir
68
+ File.join(examples_dir, name)
69
+ end
70
+
71
+ def dir_exist?
72
+ Dir.exist?(dir)
73
+ end
74
+
75
+ def gemfile
76
+ File.join(dir, "Gemfile")
77
+ end
78
+
79
+ # Options we pass when running `rails new` from the command-line.
80
+ attr_writer :rails_options
81
+
82
+ def rails_options
83
+ @rails_options ||= if ReactOnRails::Utils.rails_version_less_than("7.0")
84
+ "--skip-bundle --skip-spring --skip-git --skip-test-unit --skip-active-record -J"
85
+ else
86
+ "--skip-bundle --skip-spring --skip-git --skip-test-unit --skip-active-record -j webpack"
87
+ end
88
+ end
89
+
90
+ %w[gen clobber npm_install build_webpack_bundles].each do |task_type|
91
+ method_name_normal = "#{task_type}_task_name" # ex: `clean_task_name`
92
+ method_name_short = "#{method_name_normal}_short" # ex: `clean_task_name_short`
93
+
94
+ define_method(method_name_normal) { "#{@packer_type}:#{task_type}_#{name}" }
95
+ define_method(method_name_short) { "#{task_type}_#{name}" }
96
+ end
97
+
98
+ def rspec_task_name_short
99
+ "#{packer_type}_#{name}"
100
+ end
101
+
102
+ def rspec_task_name
103
+ "run_rspec:#{rspec_task_name_short}"
104
+ end
105
+
106
+ # Assumes we are inside a rails app's folder and necessary gems have been installed
107
+ def generator_shell_commands
108
+ shell_commands = []
109
+ shell_commands << "rails generate react_on_rails:install #{generator_options} --ignore-warnings --force"
110
+ shell_commands << "rails generate react_on_rails:dev_tests #{generator_options}"
111
+ end
112
+
113
+ private
114
+
115
+ # Defines globs that scoop up all files (including dotfiles) in given directory
116
+ def all_files_in_dir(p_dir)
117
+ [File.join(p_dir, "**", "*"), File.join(p_dir, "**", ".*")]
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,52 @@
1
+ # Example Type Configuration for React on Rails Generator Tests
2
+ #
3
+ # CI Test Coverage:
4
+ # -----------------
5
+ # - Latest CI (all PRs): Runs shakapacker_examples_latest (React 19, Shakapacker 9.x)
6
+ # Examples: basic, basic-server-rendering, redux, redux-server-rendering
7
+ #
8
+ # - Pinned CI (master): Runs shakapacker_examples_pinned (React 16, 17, 18 with Shakapacker 8.2.0)
9
+ # Examples: basic-react16, basic-server-rendering-react16,
10
+ # basic-react17, basic-server-rendering-react17,
11
+ # basic-react18, basic-server-rendering-react18
12
+ #
13
+ # Terminology:
14
+ # - "Latest" = Current React version (19) with latest Shakapacker (9.x)
15
+ # - "Pinned" = Specific older React versions (16, 17, 18) for backward compatibility testing
16
+ #
17
+ # Note: We support React 16+ but test with latest patch of each major version.
18
+
19
+ example_type_data:
20
+ # Latest versions (React 19, Shakapacker 9.x)
21
+ - name: basic
22
+ generator_options: ''
23
+ - name: basic-server-rendering
24
+ generator_options: --example-server-rendering
25
+ - name: redux
26
+ generator_options: --redux
27
+ - name: redux-server-rendering
28
+ generator_options: --redux --example-server-rendering
29
+
30
+ # React 18 compatibility tests (uses Root API introduced in React 18)
31
+ - name: basic-react18
32
+ generator_options: ''
33
+ react_version: '18'
34
+ - name: basic-server-rendering-react18
35
+ generator_options: --example-server-rendering
36
+ react_version: '18'
37
+
38
+ # React 17 compatibility tests (legacy render/hydrate API)
39
+ - name: basic-react17
40
+ generator_options: ''
41
+ react_version: '17'
42
+ - name: basic-server-rendering-react17
43
+ generator_options: --example-server-rendering
44
+ react_version: '17'
45
+
46
+ # React 16 compatibility tests (oldest supported legacy API)
47
+ - name: basic-react16
48
+ generator_options: ''
49
+ react_version: '16'
50
+ - name: basic-server-rendering-react16
51
+ generator_options: --example-server-rendering
52
+ react_version: '16'
data/rakelib/lint.rake ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "task_helpers"
4
+
5
+ namespace :lint do # rubocop:disable Metrics/BlockLength
6
+ include ReactOnRails::TaskHelpers
7
+
8
+ desc "Run Rubocop as shell"
9
+ task :rubocop do
10
+ sh_in_dir(gem_root, "bundle exec rubocop --version", "bundle exec rubocop .")
11
+ end
12
+
13
+ desc "Run stylelint as shell"
14
+ task :scss do
15
+ sh_in_dir(gem_root, stylelint_command)
16
+ end
17
+
18
+ desc "Run eslint as shell"
19
+ task :eslint do
20
+ sh_in_dir(gem_root, "pnpm run eslint --version", "pnpm run eslint .")
21
+ end
22
+
23
+ desc "Run all eslint, rubocop & stylelint linters"
24
+ task lint: %i[eslint rubocop scss] do
25
+ puts "Completed all linting"
26
+ end
27
+
28
+ desc "Auto-fix all linting violations"
29
+ task :autofix do
30
+ sh_in_dir(gem_root, "pnpm run eslint . --fix")
31
+ sh_in_dir(gem_root, "pnpm run prettier --write .")
32
+ sh_in_dir(gem_root, stylelint_fix_command)
33
+ sh_in_dir(gem_root, "bundle exec rubocop -A")
34
+ puts "Completed auto-fixing all linting violations"
35
+ end
36
+
37
+ private
38
+
39
+ def stylelint_command
40
+ "pnpm run stylelint \"spec/dummy/app/assets/stylesheets/**/*.scss\" \"spec/dummy/client/**/*.scss\""
41
+ end
42
+
43
+ def stylelint_fix_command
44
+ "#{stylelint_command} --fix"
45
+ end
46
+ end
47
+
48
+ desc "Runs all linters. Run `rake -D lint` to see all available lint options"
49
+ task lint: ["lint:lint"]
50
+
51
+ desc "Auto-fix all linting violations (eslint --fix, prettier --write, rubocop -A)"
52
+ task autofix: ["lint:autofix"]
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "task_helpers"
4
+
5
+ namespace :node_package do
6
+ include ReactOnRails::TaskHelpers
7
+
8
+ task :build do
9
+ puts "Building Node Package and running 'yalc publish'"
10
+ sh "pnpm run build && pnpm yalc:publish"
11
+ end
12
+ end
13
+
14
+ desc "Prepares node_package by building and symlinking any example/dummy apps present"
15
+ task node_package: "node_package:build"
data/rakelib/rbs.rake ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "timeout"
5
+
6
+ # rubocop:disable Metrics/BlockLength
7
+ namespace :rbs do
8
+ desc "Validate RBS type signatures"
9
+ task :validate do
10
+ require "rbs"
11
+ require "rbs/cli"
12
+
13
+ puts "Validating RBS type signatures..."
14
+
15
+ # Use Open3 for better error handling - captures stdout, stderr, and exit status separately
16
+ # This allows us to distinguish between actual validation errors and warnings
17
+ # Note: Must use bundle exec even though rake runs in bundle context because
18
+ # spawned shell commands via Open3.capture3() do NOT inherit bundle context
19
+ # Wrap in Timeout to prevent hung processes in CI environments (60 second timeout)
20
+ stdout, stderr, status = Timeout.timeout(60) do
21
+ Open3.capture3("bundle exec rbs -I sig validate")
22
+ end
23
+
24
+ if status.success?
25
+ puts "✓ RBS validation passed"
26
+ else
27
+ puts "✗ RBS validation failed"
28
+ puts stdout unless stdout.empty?
29
+ warn stderr unless stderr.empty?
30
+ exit 1
31
+ end
32
+ end
33
+
34
+ desc "Check RBS type signatures (alias for validate)"
35
+ task check: :validate
36
+
37
+ desc "List all RBS files"
38
+ task :list do
39
+ sig_files = Dir.glob("sig/**/*.rbs")
40
+ puts "RBS type signature files:"
41
+ sig_files.each { |f| puts " #{f}" }
42
+ puts "\nTotal: #{sig_files.count} files"
43
+ end
44
+
45
+ desc "Run Steep type checker"
46
+ task :steep do
47
+ puts "Running Steep type checker..."
48
+
49
+ # Use Open3 for better error handling
50
+ # Note: Must use bundle exec even though rake runs in bundle context because
51
+ # spawned shell commands via Open3.capture3() do NOT inherit bundle context
52
+ # Wrap in Timeout to prevent hung processes in CI environments (60 second timeout)
53
+ stdout, stderr, status = Timeout.timeout(60) do
54
+ Open3.capture3("bundle exec steep check")
55
+ end
56
+
57
+ if status.success?
58
+ puts "✓ Steep type checking passed"
59
+ else
60
+ puts "✗ Steep type checking failed"
61
+ puts stdout unless stdout.empty?
62
+ warn stderr unless stderr.empty?
63
+ exit 1
64
+ end
65
+ end
66
+
67
+ desc "Run all RBS checks (validate + steep)"
68
+ task all: %i[validate steep]
69
+ end
70
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "coveralls/rake/task" if ENV["USE_COVERALLS"] == "TRUE"
4
+
5
+ require "pathname"
6
+ require "yaml"
7
+
8
+ require_relative "task_helpers"
9
+ require_relative "example_type"
10
+
11
+ # rubocop:disable Metrics/BlockLength
12
+ namespace :run_rspec do
13
+ include ReactOnRails::TaskHelpers
14
+
15
+ # Loads data from examples_config.yml and instantiates corresponding ExampleType objects
16
+ examples_config_file = File.expand_path("examples_config.yml", __dir__)
17
+ examples_config = symbolize_keys(YAML.safe_load_file(examples_config_file))
18
+ examples_config[:example_type_data].each do |example_type_data|
19
+ ExampleType.new(packer_type: "shakapacker_examples", **symbolize_keys(example_type_data))
20
+ end
21
+
22
+ spec_dummy_dir = File.join(gem_root, "spec", "dummy")
23
+
24
+ # RBS Runtime Type Checking Configuration
25
+ # ========================================
26
+ # Runtime type checking is ENABLED BY DEFAULT when RBS gem is available
27
+ # Use ENV["DISABLE_RBS_RUNTIME_CHECKING"] = "true" to disable
28
+ #
29
+ # Coverage Strategy:
30
+ # - :gem task - Enables checking for ReactOnRails::* (direct gem unit tests)
31
+ # - :dummy tasks - Enables checking (integration tests exercise gem code paths)
32
+ # - :example tasks - No checking (examples are user-facing demo apps)
33
+ #
34
+ # Rationale per Evil Martians best practices:
35
+ # Runtime checking catches type errors in actual execution paths that static
36
+ # analysis might miss. Dummy/integration tests exercise more code paths than
37
+ # unit tests alone, providing comprehensive type safety validation.
38
+ def rbs_runtime_env_vars
39
+ return "" if ENV["DISABLE_RBS_RUNTIME_CHECKING"] == "true"
40
+
41
+ begin
42
+ require "rbs"
43
+ # Preserve existing RUBYOPT flags (e.g., --enable-yjit, --jit, warnings toggles)
44
+ # by appending RBS runtime hook instead of replacing
45
+ existing_rubyopt = ENV.fetch("RUBYOPT", nil)
46
+ rubyopt_parts = ["-rrbs/test/setup", existing_rubyopt].compact.reject(&:empty?)
47
+ "RBS_TEST_TARGET='ReactOnRails::*' RUBYOPT='#{rubyopt_parts.join(' ')}'"
48
+ rescue LoadError
49
+ # RBS not available - silently skip runtime checking
50
+ # This is expected in environments without the rbs gem
51
+ ""
52
+ end
53
+ end
54
+
55
+ desc "Run RSpec for top level only"
56
+ task :gem do
57
+ run_tests_in("",
58
+ rspec_args: File.join("spec", "react_on_rails"),
59
+ env_vars: rbs_runtime_env_vars)
60
+ end
61
+
62
+ desc "Runs dummy rspec with turbolinks"
63
+ task dummy: ["dummy_apps:dummy_app"] do
64
+ run_tests_in(spec_dummy_dir,
65
+ env_vars: rbs_runtime_env_vars)
66
+ end
67
+
68
+ desc "Runs dummy rspec without turbolinks"
69
+ task dummy_no_turbolinks: ["dummy_apps:dummy_app"] do
70
+ # Build env vars array for robustness with complex environment variables
71
+ env_vars_array = []
72
+ env_vars_array << rbs_runtime_env_vars unless rbs_runtime_env_vars.empty?
73
+ env_vars_array << "DISABLE_TURBOLINKS=TRUE"
74
+ env_vars = env_vars_array.join(" ")
75
+ run_tests_in(spec_dummy_dir,
76
+ env_vars: env_vars,
77
+ command_name: "dummy_no_turbolinks")
78
+ end
79
+
80
+ # Dynamically define Rake tasks for each example app found in the examples directory
81
+ ExampleType.all[:shakapacker_examples].each do |example_type|
82
+ puts "Creating #{example_type.rspec_task_name} task"
83
+ desc "Runs RSpec for #{example_type.name_pretty} only"
84
+ task example_type.rspec_task_name_short => example_type.gen_task_name do
85
+ # Use unbundled mode for pinned React version examples to ensure the example app's
86
+ # Gemfile and gem versions are used, not the parent workspace's bundle
87
+ run_tests_in(File.join(examples_dir, example_type.name),
88
+ unbundled: example_type.pinned_react_version?)
89
+ end
90
+ end
91
+
92
+ desc "Runs Rspec for shakapacker example apps only"
93
+ task shakapacker_examples: "shakapacker_examples:gen_all" do
94
+ ExampleType.all[:shakapacker_examples].each { |example_type| Rake::Task[example_type.rspec_task_name].invoke }
95
+ end
96
+
97
+ # Helper methods for filtering examples by React version
98
+ def latest_examples
99
+ ExampleType.all[:shakapacker_examples].reject(&:pinned_react_version?)
100
+ end
101
+
102
+ def react18_examples
103
+ ExampleType.all[:shakapacker_examples].select { |e| e.react_version == "18" }
104
+ end
105
+
106
+ def react17_examples
107
+ ExampleType.all[:shakapacker_examples].select { |e| e.react_version == "17" }
108
+ end
109
+
110
+ def react16_examples
111
+ ExampleType.all[:shakapacker_examples].select { |e| e.react_version == "16" }
112
+ end
113
+
114
+ def pinned_version_examples
115
+ ExampleType.all[:shakapacker_examples].select(&:pinned_react_version?)
116
+ end
117
+
118
+ desc "Runs Rspec for latest version example apps only (React 19, Shakapacker 9.x)"
119
+ task shakapacker_examples_latest: latest_examples.map(&:gen_task_name) do
120
+ latest_examples.each { |example_type| Rake::Task[example_type.rspec_task_name].invoke }
121
+ end
122
+
123
+ desc "Runs Rspec for React 18 example apps only (Shakapacker 8.2.0)"
124
+ task shakapacker_examples_react18: react18_examples.map(&:gen_task_name) do
125
+ react18_examples.each { |example_type| Rake::Task[example_type.rspec_task_name].invoke }
126
+ end
127
+
128
+ desc "Runs Rspec for React 17 example apps only (legacy render API)"
129
+ task shakapacker_examples_react17: react17_examples.map(&:gen_task_name) do
130
+ react17_examples.each { |example_type| Rake::Task[example_type.rspec_task_name].invoke }
131
+ end
132
+
133
+ desc "Runs Rspec for React 16 example apps only (oldest supported legacy API)"
134
+ task shakapacker_examples_react16: react16_examples.map(&:gen_task_name) do
135
+ react16_examples.each { |example_type| Rake::Task[example_type.rspec_task_name].invoke }
136
+ end
137
+
138
+ desc "Runs Rspec for all pinned version example apps (React 16, 17, and 18)"
139
+ task shakapacker_examples_pinned: pinned_version_examples.map(&:gen_task_name) do
140
+ pinned_version_examples.each { |example_type| Rake::Task[example_type.rspec_task_name].invoke }
141
+ end
142
+
143
+ Coveralls::RakeTask.new if ENV["USE_COVERALLS"] == "TRUE"
144
+
145
+ desc "run all tests no examples"
146
+ task all_but_examples: %i[gem dummy_no_turbolinks dummy js_tests] do
147
+ puts "Completed all RSpec tests"
148
+ end
149
+
150
+ desc "run all dummy tests"
151
+ task all_dummy: %i[dummy_no_turbolinks dummy] do
152
+ puts "Completed all RSpec tests"
153
+ end
154
+
155
+ desc "run all tests"
156
+ task :run_rspec, [:packer] => ["all_but_examples"] do
157
+ Rake::Task["run_rspec:#{packer}_examples"].invoke
158
+ puts "Completed all RSpec tests"
159
+ end
160
+ end
161
+ # rubocop:enable Metrics/BlockLength
162
+
163
+ desc "js tests (same as 'pnpm run test')"
164
+ task :js_tests do
165
+ sh "pnpm run test"
166
+ end
167
+
168
+ msg = <<~DESC
169
+ Runs all tests, run `rake -D run_rspec` to see all available test options.
170
+ "rake run_rspec:example_basic" is a good way to run only one generator test.
171
+ DESC
172
+ desc msg
173
+ task run_rspec: ["run_rspec:run_rspec"]
174
+
175
+ def calc_path(dir)
176
+ if dir.is_a?(String)
177
+ if dir.start_with?(File::SEPARATOR)
178
+ Pathname.new(dir)
179
+ else
180
+ Pathname.new(File.join(gem_root, dir))
181
+ end
182
+ else
183
+ dir
184
+ end
185
+ end
186
+
187
+ # Runs rspec in the given directory.
188
+ # If string is passed and it's not absolute, it's converted relative to root of the gem.
189
+ # TEST_ENV_COMMAND_NAME is used to make SimpleCov.command_name unique in order to
190
+ # prevent a name collision. Defaults to the given directory's name.
191
+ # Options:
192
+ # :command_name - name for SimpleCov (default: dir basename)
193
+ # :rspec_args - additional rspec arguments (default: "")
194
+ # :env_vars - additional environment variables (default: "")
195
+ # :unbundled - run with unbundled_sh_in_dir for Bundler isolation (default: false)
196
+ # This is required for pinned version examples because they have different
197
+ # gem versions (e.g., Shakapacker 8.2.0) pinned in their Gemfile than the
198
+ # parent workspace (Shakapacker 9.x). Without bundle isolation, Bundler
199
+ # would inherit the parent's gem resolution and use the wrong versions.
200
+ # Latest version examples don't need this because they use the same versions
201
+ # as the parent workspace.
202
+ def run_tests_in(dir, options = {})
203
+ path = calc_path(dir)
204
+
205
+ command_name = options.fetch(:command_name, path.basename)
206
+ rspec_args = options.fetch(:rspec_args, "")
207
+ unbundled = options.fetch(:unbundled, false)
208
+
209
+ # Build environment variables as an array for proper spacing
210
+ env_tokens = []
211
+ env_tokens << options.fetch(:env_vars, "").strip unless options.fetch(:env_vars, "").strip.empty?
212
+ env_tokens << "TEST_ENV_COMMAND_NAME=\"#{command_name}\""
213
+ env_tokens << "COVERAGE=true" if ENV["USE_COVERALLS"]
214
+
215
+ env_vars = env_tokens.join(" ")
216
+ command = "#{env_vars} bundle exec rspec #{rspec_args}"
217
+
218
+ if unbundled
219
+ unbundled_sh_in_dir(path.realpath, command)
220
+ else
221
+ sh_in_dir(path.realpath, command)
222
+ end
223
+ end