|
6 | 6 | RSpec.describe ReactOnRails::Dev::PackGenerator do |
7 | 7 | describe ".generate" do |
8 | 8 | context "when in Bundler context with Rails available" do |
| 9 | + let(:mock_task) { instance_double(Rake::Task) } |
| 10 | + let(:mock_rails_app) do |
| 11 | + # rubocop:disable RSpec/VerifiedDoubles |
| 12 | + double("Rails.application").tap do |app| |
| 13 | + allow(app).to receive(:load_tasks) |
| 14 | + allow(app).to receive(:respond_to?).with(:load_tasks).and_return(true) |
| 15 | + end |
| 16 | + # rubocop:enable RSpec/VerifiedDoubles |
| 17 | + end |
| 18 | + |
9 | 19 | before do |
| 20 | + # Setup Bundler context |
10 | 21 | stub_const("Bundler", Module.new) |
11 | 22 | allow(ENV).to receive(:[]).and_call_original |
12 | 23 | allow(ENV).to receive(:[]).with("BUNDLE_GEMFILE").and_return("/path/to/Gemfile") |
13 | | - allow(described_class).to receive(:rails_available?).and_return(true) |
| 24 | + |
| 25 | + # Setup Rails availability |
| 26 | + app = mock_rails_app |
| 27 | + rails_module = Module.new do |
| 28 | + define_singleton_method(:application) { app } |
| 29 | + define_singleton_method(:respond_to?) { |method, *| method == :application } |
| 30 | + end |
| 31 | + stub_const("Rails", rails_module) |
| 32 | + |
| 33 | + # Mock Rake::Task at the boundary |
| 34 | + allow(Rake::Task).to receive(:task_defined?).with("react_on_rails:generate_packs").and_return(false) |
| 35 | + allow(Rake::Task).to receive(:[]).with("react_on_rails:generate_packs").and_return(mock_task) |
| 36 | + allow(mock_task).to receive(:reenable) |
| 37 | + allow(mock_task).to receive(:invoke) |
14 | 38 | end |
15 | 39 |
|
16 | 40 | it "runs pack generation successfully in verbose mode using direct rake execution" do |
17 | | - allow(described_class).to receive(:run_rake_task_directly).and_return(true) |
18 | | - |
19 | 41 | expect { described_class.generate(verbose: true) } |
20 | 42 | .to output(/📦 Generating React on Rails packs.../).to_stdout_from_any_process |
21 | | - expect(described_class).to have_received(:run_rake_task_directly) |
| 43 | + |
| 44 | + expect(mock_task).to have_received(:invoke) |
| 45 | + expect(mock_rails_app).to have_received(:load_tasks) |
22 | 46 | end |
23 | 47 |
|
24 | 48 | it "runs pack generation successfully in quiet mode using direct rake execution" do |
25 | | - allow(described_class).to receive(:run_rake_task_directly).and_return(true) |
26 | | - |
27 | 49 | expect { described_class.generate(verbose: false) } |
28 | 50 | .to output(/📦 Generating packs\.\.\. ✅/).to_stdout_from_any_process |
29 | | - expect(described_class).to have_received(:run_rake_task_directly) |
| 51 | + |
| 52 | + expect(mock_task).to have_received(:invoke) |
30 | 53 | end |
31 | 54 |
|
32 | 55 | it "exits with error when pack generation fails" do |
33 | | - allow(described_class).to receive(:run_rake_task_directly).and_return(false) |
| 56 | + allow(mock_task).to receive(:invoke).and_raise(StandardError.new("Task failed")) |
| 57 | + |
| 58 | + # Mock STDERR.puts to capture output |
| 59 | + error_output = [] |
| 60 | + # rubocop:disable Style/GlobalStdStream |
| 61 | + allow(STDERR).to receive(:puts) { |msg| error_output << msg } |
| 62 | + # rubocop:enable Style/GlobalStdStream |
34 | 63 |
|
35 | 64 | expect { described_class.generate(verbose: false) }.to raise_error(SystemExit) |
| 65 | + expect(error_output.join("\n")).to match(/Error generating packs: Task failed/) |
| 66 | + end |
| 67 | + |
| 68 | + it "outputs errors to stderr even in silent mode" do |
| 69 | + allow(mock_task).to receive(:invoke).and_raise(StandardError.new("Silent mode error")) |
| 70 | + |
| 71 | + # Mock STDERR.puts to capture output |
| 72 | + error_output = [] |
| 73 | + # rubocop:disable Style/GlobalStdStream |
| 74 | + allow(STDERR).to receive(:puts) { |msg| error_output << msg } |
| 75 | + # rubocop:enable Style/GlobalStdStream |
| 76 | + |
| 77 | + expect { described_class.generate(verbose: false) }.to raise_error(SystemExit) |
| 78 | + expect(error_output.join("\n")).to match(/Error generating packs: Silent mode error/) |
| 79 | + end |
| 80 | + |
| 81 | + it "includes backtrace in error output when DEBUG env is set" do |
| 82 | + allow(ENV).to receive(:[]).with("DEBUG").and_return("true") |
| 83 | + allow(mock_task).to receive(:invoke).and_raise(StandardError.new("Debug error")) |
| 84 | + |
| 85 | + # Mock STDERR.puts to capture output |
| 86 | + error_output = [] |
| 87 | + # rubocop:disable Style/GlobalStdStream |
| 88 | + allow(STDERR).to receive(:puts) { |msg| error_output << msg } |
| 89 | + # rubocop:enable Style/GlobalStdStream |
| 90 | + |
| 91 | + expect { described_class.generate(verbose: false) }.to raise_error(SystemExit) |
| 92 | + expect(error_output.join("\n")).to match(/Error generating packs: Debug error.*pack_generator_spec\.rb/m) |
| 93 | + end |
| 94 | + |
| 95 | + it "suppresses stdout in silent mode" do |
| 96 | + # Mock task to produce output |
| 97 | + allow(mock_task).to receive(:invoke) do |
| 98 | + puts "This should be suppressed" |
| 99 | + end |
| 100 | + |
| 101 | + expect { described_class.generate(verbose: false) } |
| 102 | + .not_to output(/This should be suppressed/).to_stdout_from_any_process |
36 | 103 | end |
37 | 104 | end |
38 | 105 |
|
39 | 106 | context "when not in Bundler context" do |
40 | 107 | before do |
41 | | - stub_const("Bundler", nil) unless defined?(Bundler) |
42 | | - allow(described_class).to receive(:should_run_directly?).and_return(false) |
| 108 | + # Ensure we're not in Bundler context |
| 109 | + hide_const("Bundler") if defined?(Bundler) |
43 | 110 | end |
44 | 111 |
|
45 | 112 | it "runs pack generation successfully in verbose mode using bundle exec" do |
|
48 | 115 |
|
49 | 116 | expect { described_class.generate(verbose: true) } |
50 | 117 | .to output(/📦 Generating React on Rails packs.../).to_stdout_from_any_process |
| 118 | + |
| 119 | + expect(described_class).to have_received(:system).with(command) |
51 | 120 | end |
52 | 121 |
|
53 | 122 | it "runs pack generation successfully in quiet mode using bundle exec" do |
|
56 | 125 |
|
57 | 126 | expect { described_class.generate(verbose: false) } |
58 | 127 | .to output(/📦 Generating packs\.\.\. ✅/).to_stdout_from_any_process |
| 128 | + |
| 129 | + expect(described_class).to have_received(:system).with(command) |
59 | 130 | end |
60 | 131 |
|
61 | 132 | it "exits with error when pack generation fails" do |
|
65 | 136 | expect { described_class.generate(verbose: false) }.to raise_error(SystemExit) |
66 | 137 | end |
67 | 138 | end |
| 139 | + |
| 140 | + context "when Rails is not available" do |
| 141 | + before do |
| 142 | + stub_const("Bundler", Module.new) |
| 143 | + allow(ENV).to receive(:[]).and_call_original |
| 144 | + allow(ENV).to receive(:[]).with("BUNDLE_GEMFILE").and_return("/path/to/Gemfile") |
| 145 | + |
| 146 | + # Rails not available |
| 147 | + hide_const("Rails") if defined?(Rails) |
| 148 | + end |
| 149 | + |
| 150 | + it "falls back to bundle exec when Rails is not defined" do |
| 151 | + command = "bundle exec rake react_on_rails:generate_packs" |
| 152 | + allow(described_class).to receive(:system).with(command).and_return(true) |
| 153 | + |
| 154 | + expect { described_class.generate(verbose: true) } |
| 155 | + .to output(/📦 Generating React on Rails packs.../).to_stdout_from_any_process |
| 156 | + |
| 157 | + expect(described_class).to have_received(:system).with(command) |
| 158 | + end |
| 159 | + end |
68 | 160 | end |
69 | 161 | end |
0 commit comments