ruby_wasm_ui 0.8.2 → 0.9.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +0 -2
  3. data/Makefile +53 -0
  4. data/README.md +22 -4
  5. data/examples/.gitignore +3 -0
  6. data/examples/Gemfile.lock +15 -17
  7. data/examples/src/index.html +21 -0
  8. data/examples/src/index.rb +26 -0
  9. data/exe/ruby-wasm-ui +6 -0
  10. data/lib/ruby_wasm_ui/cli/command/base.rb +174 -0
  11. data/lib/ruby_wasm_ui/cli/command/dev.rb +206 -0
  12. data/lib/ruby_wasm_ui/cli/command/pack.rb +36 -0
  13. data/lib/ruby_wasm_ui/cli/command/rebuild.rb +38 -0
  14. data/lib/ruby_wasm_ui/cli/command/setup.rb +130 -0
  15. data/lib/ruby_wasm_ui/cli/command.rb +48 -0
  16. data/lib/ruby_wasm_ui/version.rb +1 -1
  17. data/lib/ruby_wasm_ui.rb +8 -8
  18. data/package-lock.json +2 -2
  19. data/package.json +1 -1
  20. data/packages/npm-packages/runtime/package-lock.json +2 -2
  21. data/packages/npm-packages/runtime/package.json +3 -3
  22. data/packages/npm-packages/runtime/rollup.config.mjs +68 -10
  23. data/spec/ruby_wasm_ui/cli/command/base_spec.rb +358 -0
  24. data/spec/ruby_wasm_ui/cli/command/dev_spec.rb +412 -0
  25. data/spec/ruby_wasm_ui/cli/command/pack_spec.rb +127 -0
  26. data/spec/ruby_wasm_ui/cli/command/rebuild_spec.rb +95 -0
  27. data/spec/ruby_wasm_ui/cli/command/setup_spec.rb +186 -0
  28. data/spec/ruby_wasm_ui/cli/command_spec.rb +118 -0
  29. data/{packages/npm-packages/runtime/spec → spec}/spec_helper.rb +1 -1
  30. metadata +96 -38
  31. data/packages/npm-packages/runtime/Gemfile +0 -3
  32. data/packages/npm-packages/runtime/Gemfile.lock +0 -26
  33. /data/lib/ruby_wasm_ui/{app.rb → runtime/app.rb} +0 -0
  34. /data/lib/ruby_wasm_ui/{component.rb → runtime/component.rb} +0 -0
  35. /data/lib/ruby_wasm_ui/{dispatcher.rb → runtime/dispatcher.rb} +0 -0
  36. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/attributes.rb +0 -0
  37. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/destroy_dom.rb +0 -0
  38. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/events.rb +0 -0
  39. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/mount_dom.rb +0 -0
  40. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/patch_dom.rb +0 -0
  41. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/scheduler.rb +0 -0
  42. /data/lib/ruby_wasm_ui/{dom.rb → runtime/dom.rb} +0 -0
  43. /data/lib/ruby_wasm_ui/{nodes_equal.rb → runtime/nodes_equal.rb} +0 -0
  44. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_conditional_group.rb +0 -0
  45. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_for_group.rb +0 -0
  46. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_vdom.rb +0 -0
  47. /data/lib/ruby_wasm_ui/{template → runtime/template}/parser.rb +0 -0
  48. /data/lib/ruby_wasm_ui/{template.rb → runtime/template.rb} +0 -0
  49. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/arrays.rb +0 -0
  50. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/objects.rb +0 -0
  51. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/props.rb +0 -0
  52. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/strings.rb +0 -0
  53. /data/lib/ruby_wasm_ui/{utils.rb → runtime/utils.rb} +0 -0
  54. /data/lib/ruby_wasm_ui/{vdom.rb → runtime/vdom.rb} +0 -0
  55. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/component_spec.rb +0 -0
  56. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/dom/scheduler_spec.rb +0 -0
  57. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/nodes_equal_spec.rb +0 -0
  58. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_conditional_group_spec.rb +0 -0
  59. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_for_group_spec.rb +0 -0
  60. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_vdom_spec.rb +0 -0
  61. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/parser_spec.rb +0 -0
  62. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/arrays_spec.rb +0 -0
  63. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/objects_spec.rb +0 -0
  64. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/props_spec.rb +0 -0
  65. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/strings_spec.rb +0 -0
@@ -0,0 +1,412 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+
7
+ RSpec.describe RubyWasmUi::Cli::Command::Dev do
8
+ let(:dev_instance) { described_class.new }
9
+ let(:temp_dir) { Dir.mktmpdir }
10
+ let(:original_dir) { Dir.pwd }
11
+
12
+ around do |example|
13
+ begin
14
+ Dir.chdir(temp_dir) do
15
+ example.run
16
+ end
17
+ ensure
18
+ # 確実に元のディレクトリに戻る
19
+ begin
20
+ Dir.chdir(original_dir) if Dir.exist?(original_dir)
21
+ rescue => e
22
+ # ディレクトリが存在しない場合は無視
23
+ end
24
+ FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
25
+ end
26
+ end
27
+
28
+ describe '.description' do
29
+ it 'returns the description' do
30
+ expect(described_class.description).to eq('Start development server with file watching and auto-build')
31
+ end
32
+ end
33
+
34
+ describe '#run' do
35
+ let(:watcher_thread) { instance_double(Thread, kill: nil) }
36
+
37
+ before do
38
+ allow(dev_instance).to receive(:build).and_return(true)
39
+ allow(dev_instance).to receive(:start_file_watcher)
40
+ allow(dev_instance).to receive(:start_server)
41
+ allow(Thread).to receive(:new).and_return(watcher_thread)
42
+ end
43
+
44
+ context 'when src directory does not exist' do
45
+ it 'outputs error message and exits' do
46
+ expect { dev_instance.run([]) }.to output(
47
+ /src directory not found. Please run 'ruby-wasm-ui setup' first./
48
+ ).to_stdout.and raise_error(SystemExit)
49
+ end
50
+
51
+ it 'exits with status 1' do
52
+ expect { dev_instance.run([]) }.to raise_error(SystemExit) do |error|
53
+ expect(error.status).to eq(1)
54
+ end
55
+ end
56
+ end
57
+
58
+ context 'when src directory exists' do
59
+ before do
60
+ FileUtils.mkdir_p('src')
61
+ end
62
+
63
+ it 'outputs starting message' do
64
+ expect { dev_instance.run([]) }.to output(
65
+ /Starting development server/
66
+ ).to_stdout
67
+ end
68
+
69
+ it 'performs initial build' do
70
+ expect(dev_instance).to receive(:build).and_return(true)
71
+ dev_instance.run([])
72
+ end
73
+
74
+ it 'outputs initial build completion message' do
75
+ expect { dev_instance.run([]) }.to output(
76
+ /Initial build completed/
77
+ ).to_stdout
78
+ end
79
+
80
+ it 'starts file watcher' do
81
+ file_watcher_called = false
82
+ # Allow Thread.new to actually create threads so start_file_watcher gets called
83
+ allow(Thread).to receive(:new).and_call_original
84
+ allow(dev_instance).to receive(:start_file_watcher) do
85
+ file_watcher_called = true
86
+ end
87
+ # Mock start_server to block briefly to allow thread to execute
88
+ allow(dev_instance).to receive(:start_server) do
89
+ sleep 0.1 # Give thread time to execute
90
+ end
91
+
92
+ dev_instance.run([])
93
+
94
+ expect(file_watcher_called).to be true
95
+ end
96
+
97
+ it 'starts development server' do
98
+ allow(dev_instance).to receive(:start_file_watcher).and_return(nil)
99
+ expect(dev_instance).to receive(:start_server)
100
+ dev_instance.run([])
101
+ end
102
+
103
+ context 'when build fails' do
104
+ before do
105
+ allow(dev_instance).to receive(:build).and_return(false)
106
+ end
107
+
108
+ it 'still starts the server' do
109
+ expect(dev_instance).to receive(:start_server)
110
+ dev_instance.run([])
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ describe '#build' do
117
+ before do
118
+ FileUtils.mkdir_p('src')
119
+ end
120
+
121
+ context 'when command succeeds' do
122
+ before do
123
+ allow(dev_instance).to receive(:run_command).and_return(true)
124
+ end
125
+
126
+ it 'executes rbwasm pack command via run_command' do
127
+ expect(dev_instance).to receive(:run_command).with(
128
+ 'bundle exec rbwasm pack ruby.wasm --dir ./src::./src -o src.wasm',
129
+ exit_on_error: false
130
+ )
131
+ dev_instance.send(:build)
132
+ end
133
+
134
+ it 'outputs build message' do
135
+ expect { dev_instance.send(:build) }.to output(
136
+ /Building: bundle exec rbwasm pack/
137
+ ).to_stdout
138
+ end
139
+
140
+ it 'outputs success message' do
141
+ expect { dev_instance.send(:build) }.to output(
142
+ /Build completed/
143
+ ).to_stdout
144
+ end
145
+
146
+ it 'returns true' do
147
+ result = dev_instance.send(:build)
148
+ expect(result).to be true
149
+ end
150
+ end
151
+
152
+ context 'when command fails' do
153
+ before do
154
+ allow(dev_instance).to receive(:run_command).and_return(false)
155
+ end
156
+
157
+ it 'outputs error message' do
158
+ expect { dev_instance.send(:build) }.to output(
159
+ /Build failed/
160
+ ).to_stdout
161
+ end
162
+
163
+ it 'returns false' do
164
+ result = dev_instance.send(:build)
165
+ expect(result).to be false
166
+ end
167
+ end
168
+ end
169
+
170
+ describe '#start_file_watcher' do
171
+ before do
172
+ FileUtils.mkdir_p('src')
173
+ @listener = instance_double(Listen::Listener, start: nil, stop: nil)
174
+ allow(Listen).to receive(:to).and_return(@listener)
175
+ end
176
+
177
+ it 'starts listening to src directory' do
178
+ expect(Listen).to receive(:to).with('src')
179
+ dev_instance.send(:start_file_watcher)
180
+ end
181
+
182
+ it 'starts the listener' do
183
+ expect(@listener).to receive(:start)
184
+ dev_instance.send(:start_file_watcher)
185
+ end
186
+
187
+ context 'when file changes occur' do
188
+ let(:callback_container) { {} }
189
+
190
+ before do
191
+ allow(Listen).to receive(:to) do |_dir, &block|
192
+ callback_container[:callback] = block
193
+ @listener
194
+ end
195
+ dev_instance.instance_variable_set(:@build_lock, Mutex.new)
196
+ dev_instance.instance_variable_set(:@build_queue, Queue.new)
197
+ allow(dev_instance).to receive(:build)
198
+ allow(dev_instance).to receive(:log_info)
199
+ end
200
+
201
+ it 'triggers rebuild on file change' do
202
+ dev_instance.send(:start_file_watcher)
203
+ callback = callback_container[:callback]
204
+ expect(callback).not_to be_nil
205
+
206
+ # Allow Thread.new to create actual threads for the debounce logic
207
+ allow(Thread).to receive(:new).and_call_original
208
+
209
+ callback.call(['src/app.rb'], [], [])
210
+ sleep 0.6 # Wait for debounce
211
+ expect(dev_instance).to have_received(:build)
212
+ end
213
+
214
+ it 'ignores temporary files' do
215
+ dev_instance.send(:start_file_watcher)
216
+ callback = callback_container[:callback]
217
+ expect(callback).not_to be_nil
218
+
219
+ # Allow Thread.new to create actual threads for the debounce logic
220
+ allow(Thread).to receive(:new).and_call_original
221
+
222
+ callback.call(['src/app.rb.swp'], [], [])
223
+ sleep 0.6
224
+ expect(dev_instance).not_to have_received(:build)
225
+ end
226
+ end
227
+
228
+ context 'when listener raises an error' do
229
+ before do
230
+ allow(Listen).to receive(:to).and_raise(StandardError.new('Listener error'))
231
+ end
232
+
233
+ it 'outputs error message' do
234
+ expect { dev_instance.send(:start_file_watcher) }.to output(
235
+ /File watcher error: Listener error/
236
+ ).to_stdout
237
+ end
238
+ end
239
+ end
240
+
241
+ describe '#start_server' do
242
+ let(:puma_handler_class) do
243
+ Class.new do
244
+ def self.run(app, options = {})
245
+ # Blocking call - will be mocked
246
+ end
247
+ end
248
+ end
249
+
250
+ before do
251
+ FileUtils.mkdir_p('src')
252
+ end
253
+
254
+ context 'when Puma handler is available' do
255
+ before do
256
+ stub_const('Rack::Handler::Puma', puma_handler_class)
257
+ # Mock require to prevent LoadError for rack/handler/puma
258
+ allow(dev_instance).to receive(:require).with('rack/handler/puma').and_return(true)
259
+ # Prevent browser from opening in all tests by default
260
+ allow(dev_instance).to receive(:open_browser)
261
+ # Prevent Thread.new from creating actual threads by default
262
+ allow(Thread).to receive(:new).and_return(instance_double(Thread))
263
+ end
264
+
265
+ it 'outputs server start message' do
266
+ allow(Rack::Handler::Puma).to receive(:run)
267
+ expect { dev_instance.send(:start_server) }.to output(
268
+ a_string_including('Starting development server on http://localhost:8080')
269
+ ).to_stdout
270
+ end
271
+
272
+ it 'outputs instructions message' do
273
+ allow(Rack::Handler::Puma).to receive(:run)
274
+ expect { dev_instance.send(:start_server) }.to output(
275
+ a_string_including('Press Ctrl+C to stop')
276
+ ).to_stdout
277
+ end
278
+
279
+ it 'starts Puma server' do
280
+ expect(Rack::Handler::Puma).to receive(:run).with(
281
+ anything,
282
+ Port: 8080,
283
+ Host: '0.0.0.0'
284
+ )
285
+ dev_instance.send(:start_server)
286
+ end
287
+
288
+ it 'outputs handler info message' do
289
+ allow(Rack::Handler::Puma).to receive(:run)
290
+ expect { dev_instance.send(:start_server) }.to output(
291
+ a_string_including('Using handler: puma')
292
+ ).to_stdout
293
+ end
294
+
295
+ it 'opens browser after server starts' do
296
+ allow(Rack::Handler::Puma).to receive(:run)
297
+ browser_thread_called = false
298
+
299
+ allow(Thread).to receive(:new) do |&block|
300
+ if block
301
+ browser_thread_called = true
302
+ # Execute block immediately for testing (without sleep)
303
+ allow(Kernel).to receive(:sleep)
304
+ block.call
305
+ end
306
+ instance_double(Thread)
307
+ end
308
+
309
+ expect(dev_instance).to receive(:open_browser).with('http://localhost:8080/src/index.html').and_return(nil)
310
+
311
+ dev_instance.send(:start_server)
312
+
313
+ expect(browser_thread_called).to be true
314
+ end
315
+ end
316
+
317
+ context 'when Puma handler is not available' do
318
+ before do
319
+ allow(dev_instance).to receive(:require).with('rack/handler/puma').and_raise(LoadError.new('cannot load such file'))
320
+ end
321
+
322
+ it 'outputs error message' do
323
+ expect { dev_instance.send(:start_server) }.to output(
324
+ /Puma handler not available/
325
+ ).to_stdout.and raise_error(SystemExit)
326
+ end
327
+
328
+ it 'exits with status 1' do
329
+ expect { dev_instance.send(:start_server) }.to raise_error(SystemExit) do |error|
330
+ expect(error.status).to eq(1)
331
+ end
332
+ end
333
+ end
334
+
335
+ context 'when server raises an error' do
336
+ before do
337
+ stub_const('Rack::Handler::Puma', puma_handler_class)
338
+ allow(dev_instance).to receive(:require).with('rack/handler/puma').and_return(true)
339
+ allow(Rack::Handler::Puma).to receive(:run).and_raise(StandardError.new('Server error'))
340
+ end
341
+
342
+ it 'outputs error message and exits' do
343
+ expect { dev_instance.send(:start_server) }.to output(
344
+ /Server error: Server error/
345
+ ).to_stdout.and raise_error(SystemExit)
346
+ end
347
+ end
348
+ end
349
+
350
+ describe '#open_browser' do
351
+ before do
352
+ FileUtils.mkdir_p('src')
353
+ end
354
+
355
+ context 'on macOS' do
356
+ before do
357
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return('darwin')
358
+ end
359
+
360
+ it 'calls open command' do
361
+ expect(dev_instance).to receive(:system).with('open', 'http://localhost:8080/src/index.html').and_return(true)
362
+ dev_instance.send(:open_browser, 'http://localhost:8080/src/index.html')
363
+ end
364
+
365
+ context 'when system call fails' do
366
+ before do
367
+ allow(dev_instance).to receive(:system).and_raise(StandardError.new('Command failed'))
368
+ end
369
+
370
+ it 'outputs fallback message' do
371
+ expect { dev_instance.send(:open_browser, 'http://localhost:8080/src/index.html') }.to output(
372
+ /Please open http:\/\/localhost:8080\/src\/index.html in your browser/
373
+ ).to_stdout
374
+ end
375
+ end
376
+ end
377
+
378
+ context 'on Linux' do
379
+ before do
380
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return('linux')
381
+ end
382
+
383
+ it 'calls xdg-open command' do
384
+ expect(dev_instance).to receive(:system).with('xdg-open', 'http://localhost:8080/src/index.html').and_return(true)
385
+ dev_instance.send(:open_browser, 'http://localhost:8080/src/index.html')
386
+ end
387
+ end
388
+
389
+ context 'on Windows' do
390
+ before do
391
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return('mswin')
392
+ end
393
+
394
+ it 'calls start command' do
395
+ expect(dev_instance).to receive(:system).with('start', 'http://localhost:8080/src/index.html').and_return(true)
396
+ dev_instance.send(:open_browser, 'http://localhost:8080/src/index.html')
397
+ end
398
+ end
399
+
400
+ context 'on unknown OS' do
401
+ before do
402
+ allow(RbConfig::CONFIG).to receive(:[]).with('host_os').and_return('unknown')
403
+ end
404
+
405
+ it 'outputs manual instruction' do
406
+ expect { dev_instance.send(:open_browser, 'http://localhost:8080/src/index.html') }.to output(
407
+ /Please open http:\/\/localhost:8080\/src\/index.html in your browser/
408
+ ).to_stdout
409
+ end
410
+ end
411
+ end
412
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+
7
+ RSpec.describe RubyWasmUi::Cli::Command::Pack do
8
+ let(:pack_instance) { described_class.new }
9
+ let(:temp_dir) { Dir.mktmpdir }
10
+ let(:original_dir) { Dir.pwd }
11
+
12
+ around do |example|
13
+ Dir.chdir(temp_dir) do
14
+ example.run
15
+ end
16
+ ensure
17
+ Dir.chdir(original_dir)
18
+ FileUtils.rm_rf(temp_dir) if Dir.exist?(temp_dir)
19
+ end
20
+
21
+ describe '.description' do
22
+ it 'returns the description' do
23
+ expect(described_class.description).to eq('Pack WASM file by packing Ruby source files')
24
+ end
25
+ end
26
+
27
+ describe '#run' do
28
+ context 'when src directory does not exist' do
29
+ it 'outputs error message and exits' do
30
+ expect { pack_instance.run([]) }.to output(
31
+ /src directory not found. Please run 'ruby-wasm-ui setup' first./
32
+ ).to_stdout.and raise_error(SystemExit)
33
+ end
34
+
35
+ it 'exits with status 1' do
36
+ expect { pack_instance.run([]) }.to raise_error(SystemExit) do |error|
37
+ expect(error.status).to eq(1)
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'when src directory exists but ruby.wasm does not exist' do
43
+ before do
44
+ FileUtils.mkdir_p('src')
45
+ end
46
+
47
+ it 'outputs error message and exits' do
48
+ expect { pack_instance.run([]) }.to output(
49
+ /ruby.wasm not found. Please run 'ruby-wasm-ui setup' first./
50
+ ).to_stdout.and raise_error(SystemExit)
51
+ end
52
+
53
+ it 'exits with status 1' do
54
+ expect { pack_instance.run([]) }.to raise_error(SystemExit) do |error|
55
+ expect(error.status).to eq(1)
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'when src directory and ruby.wasm exist' do
61
+ before do
62
+ FileUtils.mkdir_p('src')
63
+ FileUtils.touch('ruby.wasm')
64
+ allow(pack_instance).to receive(:pack)
65
+ end
66
+
67
+ it 'outputs packing message' do
68
+ expect { pack_instance.run([]) }.to output(
69
+ /Packing WASM file/
70
+ ).to_stdout
71
+ end
72
+
73
+ it 'calls pack method' do
74
+ expect(pack_instance).to receive(:pack)
75
+ pack_instance.run([])
76
+ end
77
+ end
78
+ end
79
+
80
+ describe '#pack' do
81
+ before do
82
+ FileUtils.mkdir_p('src')
83
+ FileUtils.touch('ruby.wasm')
84
+ end
85
+
86
+ context 'when command succeeds' do
87
+ before do
88
+ allow(pack_instance).to receive(:run_command).and_return(true)
89
+ end
90
+
91
+ it 'executes rbwasm pack command via run_command' do
92
+ expect(pack_instance).to receive(:run_command).with(
93
+ 'bundle exec rbwasm pack ruby.wasm --dir ./src::./src -o src.wasm'
94
+ )
95
+ pack_instance.send(:pack)
96
+ end
97
+
98
+ it 'outputs pack message' do
99
+ expect { pack_instance.send(:pack) }.to output(
100
+ /Packing: bundle exec rbwasm pack/
101
+ ).to_stdout
102
+ end
103
+
104
+ it 'outputs success message' do
105
+ expect { pack_instance.send(:pack) }.to output(
106
+ /Pack completed/
107
+ ).to_stdout
108
+ end
109
+ end
110
+
111
+ context 'when command fails' do
112
+ before do
113
+ allow(pack_instance).to receive(:run_command).and_raise(SystemExit.new(1))
114
+ end
115
+
116
+ it 'outputs error message and exits' do
117
+ expect { pack_instance.send(:pack) }.to raise_error(SystemExit)
118
+ end
119
+
120
+ it 'exits with status 1' do
121
+ expect { pack_instance.send(:pack) }.to raise_error(SystemExit) do |error|
122
+ expect(error.status).to eq(1)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RubyWasmUi::Cli::Command::Rebuild do
6
+ let(:rebuild_instance) { described_class.new }
7
+
8
+ describe '.description' do
9
+ it 'returns the description' do
10
+ expect(described_class.description).to eq('Rebuild Ruby WASM file (useful when gems are added)')
11
+ end
12
+ end
13
+
14
+ describe '#run' do
15
+ before do
16
+ allow(rebuild_instance).to receive(:check_ruby_version).and_return(RUBY_VERSION.split('.')[0..1].join('.'))
17
+ allow(rebuild_instance).to receive(:configure_excluded_gems)
18
+ allow(rebuild_instance).to receive(:build_ruby_wasm)
19
+ end
20
+
21
+ it 'outputs rebuild message' do
22
+ expect { rebuild_instance.run([]) }.to output(
23
+ /Rebuilding Ruby WASM/
24
+ ).to_stdout
25
+ end
26
+
27
+ it 'checks Ruby version' do
28
+ expect(rebuild_instance).to receive(:check_ruby_version)
29
+ rebuild_instance.run([])
30
+ end
31
+
32
+ it 'configures excluded gems' do
33
+ expect(rebuild_instance).to receive(:configure_excluded_gems)
34
+ rebuild_instance.run([])
35
+ end
36
+
37
+ it 'executes rbwasm build command' do
38
+ ruby_version = RUBY_VERSION.split('.')[0..1].join('.')
39
+ expect(rebuild_instance).to receive(:build_ruby_wasm).with(ruby_version)
40
+ rebuild_instance.run([])
41
+ end
42
+
43
+ it 'outputs completion message' do
44
+ expect { rebuild_instance.run([]) }.to output(
45
+ /Rebuild completed successfully!/
46
+ ).to_stdout
47
+ end
48
+
49
+ it 'outputs progress messages' do
50
+ expect { rebuild_instance.run([]) }.to output(
51
+ /Configuring excluded gems/
52
+ ).to_stdout
53
+ expect { rebuild_instance.run([]) }.to output(
54
+ /Building Ruby WASM/
55
+ ).to_stdout
56
+ end
57
+
58
+ it 'does not update .gitignore' do
59
+ expect(rebuild_instance).not_to respond_to(:update_gitignore)
60
+ rebuild_instance.run([])
61
+ end
62
+
63
+ it 'does not create initial files' do
64
+ expect(rebuild_instance).not_to respond_to(:create_initial_files)
65
+ rebuild_instance.run([])
66
+ end
67
+
68
+ context 'when Ruby version check fails' do
69
+ before do
70
+ allow(rebuild_instance).to receive(:check_ruby_version).and_raise(SystemExit.new(1))
71
+ allow(Kernel).to receive(:exit)
72
+ end
73
+
74
+ it 'does not execute subsequent steps' do
75
+ expect(rebuild_instance).not_to receive(:configure_excluded_gems)
76
+ expect(rebuild_instance).not_to receive(:build_ruby_wasm)
77
+ expect { rebuild_instance.run([]) }.to raise_error(SystemExit)
78
+ end
79
+ end
80
+
81
+ context 'when Ruby version is 3.2' do
82
+ before do
83
+ stub_const('RUBY_VERSION', '3.2.0')
84
+ allow(rebuild_instance).to receive(:check_ruby_version).and_return('3.2')
85
+ allow(rebuild_instance).to receive(:configure_excluded_gems)
86
+ allow(rebuild_instance).to receive(:build_ruby_wasm)
87
+ end
88
+
89
+ it 'executes rbwasm build command with correct version' do
90
+ expect(rebuild_instance).to receive(:build_ruby_wasm).with('3.2')
91
+ rebuild_instance.run([])
92
+ end
93
+ end
94
+ end
95
+ end