Skip to content

Commit d81bb5e

Browse files
Add comprehensive tests for Phase 3 RSC utility methods
Add tests for the three RSC utility methods moved to Pro gem: - rsc_support_enabled?: Tests enabled/disabled states - rsc_bundle_js_file_path: Tests delegation to bundle_js_file_path, caching behavior in development vs production - react_client_manifest_file_path: Tests packer integration with dev server running/not running, caching behavior - react_server_client_manifest_file_path: Tests delegation, caching behavior, error handling when manifest is nil Total: ~210 lines of tests ensuring all RSC utility methods work correctly in the Pro gem with proper caching and error handling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent e346593 commit d81bb5e

File tree

2 files changed

+241
-0
lines changed

2 files changed

+241
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(git commit:*)",
5+
"Bash(yarn build:*)",
6+
"Bash(bundle exec rake:*)",
7+
"Bash(gh pr view:*)",
8+
"Bash(gh run list:*)",
9+
"Bash(gh pr checks:*)",
10+
"Bash(gh run view:*)",
11+
"Bash(bundle exec rubocop:*)",
12+
"Bash(gh pr comment 1854 --body \"$(cat <<''EOF''\n## Closing this PR - Change in Plan\n\nAfter discussion with @justin808, we''ve decided on a different approach:\n\n**New Plan:**\n- Keep the immediate hydration Ruby code in the MIT gem (move from `lib/react_on_rails/pro/` to regular `lib/react_on_rails/`)\n- Make it disabled by default when Pro gem is not installed\n- Automatically enable it when Pro gem is present\n- The actual functionality remains in the Pro npm package, so users can''t hack it by editing Ruby code\n\n**Benefits:**\n- Marketing visibility: All users see the Pro feature exists\n- Professional presentation: Non-intrusive way to inform users about Pro features\n- No security risk: Core logic stays in Pro npm package\n- Clean separation: Ruby code in MIT, functionality in Pro packages\n\n**Next Steps:**\n- Will open a new, simpler PR that just moves `lib/react_on_rails/pro/` code to MIT license\n- Update LICENSE.md accordingly\n- Update documentation\n\nThis PR did valuable work in establishing the data enhancement pattern and will serve as reference, but the simpler approach is more aligned with business goals.\nEOF\n)\")",
13+
"Bash(gh pr close:*)",
14+
"Bash(git checkout:*)",
15+
"Bash(git pull:*)",
16+
"Bash(git fetch:*)",
17+
"Bash(bundle exec rspec:*)",
18+
"Bash(git add:*)",
19+
"Bash(git push:*)",
20+
"Bash(git revert:*)",
21+
"Bash(git rm:*)",
22+
"Bash(gh pr list:*)",
23+
"Bash(gh issue create --title \"Migrate React on Rails Pro CI from CircleCI to GitHub Actions\" --body \"$(cat <<''EOF''\n## Summary\n\nMigrate the React on Rails Pro package CI/CD pipeline from CircleCI to GitHub Actions to consolidate all testing infrastructure in one platform.\n\n## Background\n\nCurrently:\n- **Main package** (react-on-rails): Uses GitHub Actions (`.github/workflows/*.yml`)\n- **Pro package** (react-on-rails-pro): Uses CircleCI (`.circleci/config.yml`)\n\nThis creates maintenance overhead and splits CI visibility across two platforms.\n\n## Migration Plan\n\n### CircleCI Jobs to Migrate (10 jobs total):\n\n1. **lint-js-and-ruby** - Linting for Pro package (JS, Ruby, formatting, TypeScript)\n2. **install-package-node-packages** - Install Pro package node modules (caching job)\n3. **install-dummy-app-node-packages** - Install dummy app node modules (caching job)\n4. **install-package-ruby-gems** - Install Pro package Ruby gems (caching job)\n5. **install-dummy-app-ruby-gems** - Install dummy app Ruby gems (caching job)\n6. **build-dummy-app-webpack-test-bundles** - Build webpack test bundles\n7. **package-js-tests** - Jest unit tests for Pro package\n8. **rspec-package-specs** - RSpec tests for Pro package\n9. **rspec-dummy-app-node-renderer** - RSpec integration tests with Node renderer (includes test splitting)\n10. **dummy-app-node-renderer-e2-tests** - Playwright E2E tests (requires Redis service)\n\n### New GitHub Actions Workflows (3 files):\n\n1. **`.github/workflows/pro-lint.yml`** - Linting workflow\n2. **`.github/workflows/pro-package-tests.yml`** - Unit tests workflow\n3. **`.github/workflows/pro-integration-tests.yml`** - Integration & E2E tests workflow\n\n### Key Implementation Details:\n\n- **Test Parallelization**: Use GitHub Actions matrix strategy with sharding for RSpec tests (replaces CircleCI test splitting)\n- **Redis Service**: Use GitHub Actions service containers for E2E tests\n- **Caching**: Migrate to `actions/cache@v4` with similar key patterns\n- **Ruby/Node Versions**: Ruby 3.3.7, Node 20/22\n- **Browser Tests**: Chrome installation for Capybara/Playwright tests\n- **Working Directory**: All commands run from `react_on_rails_pro/`\n- **Artifacts**: Store test results, screenshots, and logs\n\n### Migration Strategy:\n\n✅ **Phase 1**: Create GitHub Actions workflows (keep CircleCI running in parallel)\n✅ **Phase 2**: Verify all tests pass and artifacts are stored correctly \n✅ **Phase 3**: Monitor both systems for 1-2 weeks\n✅ **Phase 4**: Remove CircleCI config once GitHub Actions is proven stable\n\n### Technical Feasibility:\n\n✅ All CircleCI features can be replicated in GitHub Actions\n✅ Test splitting: Achievable with matrix strategy + RSpec sharding\n✅ Redis service: Native support via service containers\n✅ Background processes: Works identically in both platforms\n✅ Caching: Full support with similar patterns\n✅ Artifacts & test results: Full support\n\n**No blockers identified** - migration is straightforward and low-risk.\n\n## Benefits\n\n- ✅ Unified CI platform (all workflows in GitHub Actions)\n- ✅ Better visibility (all CI results in one place)\n- ✅ Reduced maintenance overhead\n- ✅ Cost consolidation\n- ✅ Consistent workflow patterns across main and pro packages\n\n## Related\n\n- Main package workflows: `.github/workflows/main.yml`, `lint-js-and-ruby.yml`, etc.\n- CircleCI config: `.circleci/config.yml`\nEOF\n)\")",
24+
"Bash(gh api:*)"
25+
],
26+
"deny": [],
27+
"ask": []
28+
}
29+
}

react_on_rails_pro/spec/react_on_rails_pro/utils_spec.rb

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,218 @@ module ReactOnRailsPro
222222
it { expect(printable_cache_key).to eq("1_2_3_4_5") }
223223
end
224224

225+
describe ".rsc_support_enabled?" do
226+
context "when RSC support is enabled" do
227+
before do
228+
allow(ReactOnRailsPro.configuration).to receive(:enable_rsc_support).and_return(true)
229+
described_class.instance_variable_set(:@rsc_support_enabled, nil)
230+
end
231+
232+
it "returns true" do
233+
expect(described_class.rsc_support_enabled?).to be(true)
234+
end
235+
end
236+
237+
context "when RSC support is disabled" do
238+
before do
239+
allow(ReactOnRailsPro.configuration).to receive(:enable_rsc_support).and_return(false)
240+
described_class.instance_variable_set(:@rsc_support_enabled, nil)
241+
end
242+
243+
it "returns false" do
244+
expect(described_class.rsc_support_enabled?).to be(false)
245+
end
246+
end
247+
end
248+
249+
describe ".rsc_bundle_js_file_path" do
250+
before do
251+
described_class.instance_variable_set(:@rsc_bundle_path, nil)
252+
allow(ReactOnRailsPro.configuration).to receive(:rsc_bundle_js_file).and_return("rsc-bundle.js")
253+
end
254+
255+
after do
256+
described_class.instance_variable_set(:@rsc_bundle_path, nil)
257+
end
258+
259+
it "calls bundle_js_file_path with the rsc_bundle_js_file name" do
260+
allow(ReactOnRails::Utils).to receive(:bundle_js_file_path).with("rsc-bundle.js")
261+
.and_return("/some/path/rsc-bundle.js")
262+
263+
result = described_class.rsc_bundle_js_file_path
264+
265+
expect(ReactOnRails::Utils).to have_received(:bundle_js_file_path).with("rsc-bundle.js")
266+
expect(result).to eq("/some/path/rsc-bundle.js")
267+
end
268+
269+
it "caches the path when not in development" do
270+
allow(Rails.env).to receive(:development?).and_return(false)
271+
allow(ReactOnRails::Utils).to receive(:bundle_js_file_path).with("rsc-bundle.js")
272+
.and_return("/some/path/rsc-bundle.js")
273+
274+
result1 = described_class.rsc_bundle_js_file_path
275+
result2 = described_class.rsc_bundle_js_file_path
276+
277+
expect(ReactOnRails::Utils).to have_received(:bundle_js_file_path).once.with("rsc-bundle.js")
278+
expect(result1).to eq("/some/path/rsc-bundle.js")
279+
expect(result2).to eq("/some/path/rsc-bundle.js")
280+
end
281+
282+
it "does not cache the path in development" do
283+
allow(Rails.env).to receive(:development?).and_return(true)
284+
allow(ReactOnRails::Utils).to receive(:bundle_js_file_path).with("rsc-bundle.js")
285+
.and_return("/some/path/rsc-bundle.js")
286+
287+
result1 = described_class.rsc_bundle_js_file_path
288+
result2 = described_class.rsc_bundle_js_file_path
289+
290+
expect(ReactOnRails::Utils).to have_received(:bundle_js_file_path).twice.with("rsc-bundle.js")
291+
expect(result1).to eq("/some/path/rsc-bundle.js")
292+
expect(result2).to eq("/some/path/rsc-bundle.js")
293+
end
294+
end
295+
296+
describe ".react_client_manifest_file_path" do
297+
before do
298+
described_class.instance_variable_set(:@react_client_manifest_path, nil)
299+
allow(ReactOnRailsPro.configuration).to receive(:react_client_manifest_file)
300+
.and_return("react-client-manifest.json")
301+
end
302+
303+
after do
304+
described_class.instance_variable_set(:@react_client_manifest_path, nil)
305+
end
306+
307+
context "when using packer" do
308+
let(:public_output_path) { "/path/to/public/webpack/dev" }
309+
310+
before do
311+
allow(::Shakapacker).to receive_message_chain("config.public_output_path")
312+
.and_return(Pathname.new(public_output_path))
313+
allow(::Shakapacker).to receive_message_chain("config.public_path")
314+
.and_return(Pathname.new("/path/to/public"))
315+
end
316+
317+
context "when dev server is running" do
318+
before do
319+
allow(::Shakapacker).to receive(:dev_server).and_return(
320+
instance_double(
321+
::Shakapacker::DevServer,
322+
running?: true,
323+
protocol: "http",
324+
host_with_port: "localhost:3035"
325+
)
326+
)
327+
end
328+
329+
it "returns manifest URL with dev server path" do
330+
expected_url = "http://localhost:3035/webpack/dev/react-client-manifest.json"
331+
expect(described_class.react_client_manifest_file_path).to eq(expected_url)
332+
end
333+
end
334+
335+
context "when dev server is not running" do
336+
before do
337+
allow(::Shakapacker).to receive_message_chain("dev_server.running?")
338+
.and_return(false)
339+
end
340+
341+
it "returns file path to the manifest" do
342+
expected_path = File.join(public_output_path, "react-client-manifest.json")
343+
expect(described_class.react_client_manifest_file_path).to eq(expected_path)
344+
end
345+
end
346+
end
347+
348+
it "caches the path when not in development" do
349+
allow(Rails.env).to receive(:development?).and_return(false)
350+
allow(ReactOnRails::PackerUtils).to receive(:asset_uri_from_packer)
351+
.with("react-client-manifest.json")
352+
.and_return("/some/path/react-client-manifest.json")
353+
354+
result1 = described_class.react_client_manifest_file_path
355+
result2 = described_class.react_client_manifest_file_path
356+
357+
expect(ReactOnRails::PackerUtils).to have_received(:asset_uri_from_packer).once
358+
expect(result1).to eq("/some/path/react-client-manifest.json")
359+
expect(result2).to eq("/some/path/react-client-manifest.json")
360+
end
361+
362+
it "does not cache the path in development" do
363+
allow(Rails.env).to receive(:development?).and_return(true)
364+
allow(ReactOnRails::PackerUtils).to receive(:asset_uri_from_packer)
365+
.with("react-client-manifest.json")
366+
.and_return("/some/path/react-client-manifest.json")
367+
368+
result1 = described_class.react_client_manifest_file_path
369+
result2 = described_class.react_client_manifest_file_path
370+
371+
expect(ReactOnRails::PackerUtils).to have_received(:asset_uri_from_packer).twice
372+
expect(result1).to eq("/some/path/react-client-manifest.json")
373+
expect(result2).to eq("/some/path/react-client-manifest.json")
374+
end
375+
end
376+
377+
describe ".react_server_client_manifest_file_path" do
378+
let(:asset_name) { "react-server-client-manifest.json" }
379+
380+
before do
381+
described_class.instance_variable_set(:@react_server_manifest_path, nil)
382+
allow(ReactOnRailsPro.configuration).to receive(:react_server_client_manifest_file).and_return(asset_name)
383+
allow(Rails.env).to receive(:development?).and_return(false)
384+
end
385+
386+
after do
387+
described_class.instance_variable_set(:@react_server_manifest_path, nil)
388+
end
389+
390+
it "calls bundle_js_file_path with the correct asset name and returns its value" do
391+
allow(ReactOnRails::Utils).to receive(:bundle_js_file_path).with(asset_name)
392+
.and_return("/some/path/#{asset_name}")
393+
394+
result = described_class.react_server_client_manifest_file_path
395+
396+
expect(ReactOnRails::Utils).to have_received(:bundle_js_file_path).with(asset_name)
397+
expect(result).to eq("/some/path/#{asset_name}")
398+
end
399+
400+
it "caches the path when not in development" do
401+
allow(ReactOnRails::Utils).to receive(:bundle_js_file_path).with(asset_name)
402+
.and_return("/some/path/#{asset_name}")
403+
404+
result1 = described_class.react_server_client_manifest_file_path
405+
result2 = described_class.react_server_client_manifest_file_path
406+
407+
expect(ReactOnRails::Utils).to have_received(:bundle_js_file_path).once.with(asset_name)
408+
expect(result1).to eq("/some/path/#{asset_name}")
409+
expect(result2).to eq("/some/path/#{asset_name}")
410+
end
411+
412+
it "does not cache the path in development" do
413+
allow(Rails.env).to receive(:development?).and_return(true)
414+
allow(ReactOnRails::Utils).to receive(:bundle_js_file_path).with(asset_name)
415+
.and_return("/some/path/#{asset_name}")
416+
417+
result1 = described_class.react_server_client_manifest_file_path
418+
result2 = described_class.react_server_client_manifest_file_path
419+
420+
expect(ReactOnRails::Utils).to have_received(:bundle_js_file_path).twice.with(asset_name)
421+
expect(result1).to eq("/some/path/#{asset_name}")
422+
expect(result2).to eq("/some/path/#{asset_name}")
423+
end
424+
425+
context "when manifest file name is nil" do
426+
before do
427+
allow(ReactOnRailsPro.configuration).to receive(:react_server_client_manifest_file).and_return(nil)
428+
end
429+
430+
it "raises an error" do
431+
expect { described_class.react_server_client_manifest_file_path }
432+
.to raise_error(ReactOnRailsPro::Error, /react_server_client_manifest_file is nil/)
433+
end
434+
end
435+
end
436+
225437
describe ".pro_attribution_comment" do
226438
context "when license is valid and not in grace period" do
227439
before do

0 commit comments

Comments
 (0)