ruby_spriter 0.6.7.1 → 0.7.0.1
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/.rspec +3 -3
- data/CHANGELOG.md +1035 -524
- data/Gemfile +17 -17
- data/LICENSE +21 -21
- data/README.md +183 -950
- data/bin/ruby_spriter +20 -20
- data/lib/ruby_spriter/background_sampler.rb +140 -0
- data/lib/ruby_spriter/batch_processor.rb +268 -214
- data/lib/ruby_spriter/cell_cleanup_config.rb +21 -0
- data/lib/ruby_spriter/cell_cleanup_gimp_script.rb +123 -0
- data/lib/ruby_spriter/cell_cleanup_processor.rb +230 -0
- data/lib/ruby_spriter/cli.rb +676 -612
- data/lib/ruby_spriter/compression_manager.rb +101 -101
- data/lib/ruby_spriter/consolidator.rb +179 -179
- data/lib/ruby_spriter/dependency_checker.rb +224 -224
- data/lib/ruby_spriter/ghost_edge_cleaner.rb +164 -0
- data/lib/ruby_spriter/gimp_processor.rb +1188 -1058
- data/lib/ruby_spriter/metadata_manager.rb +117 -116
- data/lib/ruby_spriter/platform.rb +137 -137
- data/lib/ruby_spriter/processor.rb +1230 -891
- data/lib/ruby_spriter/smoke_detector.rb +223 -0
- data/lib/ruby_spriter/threshold_stepper.rb +227 -0
- data/lib/ruby_spriter/utils/file_helper.rb +82 -82
- data/lib/ruby_spriter/utils/image_helper.rb +16 -0
- data/lib/ruby_spriter/utils/output_formatter.rb +65 -65
- data/lib/ruby_spriter/utils/path_helper.rb +59 -59
- data/lib/ruby_spriter/utils/spritesheet_splitter.rb +86 -86
- data/lib/ruby_spriter/version.rb +6 -7
- data/lib/ruby_spriter/video_processor.rb +357 -139
- data/lib/ruby_spriter.rb +38 -34
- data/ruby_spriter.gemspec +44 -42
- data/spec/fixtures/centered_sprite_with_inner_bg.png +0 -0
- data/spec/fixtures/centered_sprite_with_inner_bg_inner_removed.png +0 -0
- data/spec/fixtures/centered_sprite_with_inner_bg_threshold_stepped.png +0 -0
- data/spec/fixtures/complex_background_sprite.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing.mp4 +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet-nobg-global.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431-nobg-global.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185027_431.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738-nobg-global.png +0 -0
- data/spec/fixtures/death - bloodborne rekconing_spritesheet_20251110_185125_738.png +0 -0
- data/spec/fixtures/has_inner_bg.png +0 -0
- data/spec/fixtures/has_small_inner_bg.png +0 -0
- data/spec/fixtures/smoke_effect_sprite.png +0 -0
- data/spec/fixtures/spritesheet_with_metadata.png +0 -0
- data/spec/fixtures/test_sprite.png +0 -0
- data/spec/fixtures/test_sprite_smoke_processed.png +0 -0
- data/spec/fixtures/test_video_spritesheet.png +0 -0
- data/spec/fixtures/transparent_bg_sprite.png +0 -0
- data/spec/fixtures/walk_north_sprite-sheet.png +0 -0
- data/spec/integration/no_fuzzy_mode_spec.rb +100 -0
- data/spec/ruby_spriter/batch_processor_spec.rb +509 -200
- data/spec/ruby_spriter/cli_spec.rb +2026 -1892
- data/spec/ruby_spriter/compression_manager_spec.rb +157 -157
- data/spec/ruby_spriter/consolidator_spec.rb +538 -538
- data/spec/ruby_spriter/gimp_processor_spec.rb +523 -425
- data/spec/ruby_spriter/platform_spec.rb +92 -92
- data/spec/ruby_spriter/processor_spec.rb +911 -735
- data/spec/ruby_spriter/utils/file_helper_spec.rb +150 -150
- data/spec/ruby_spriter/utils/path_helper_spec.rb +78 -78
- data/spec/ruby_spriter/utils/spritesheet_splitter_spec.rb +104 -104
- data/spec/ruby_spriter/video_processor_spec.rb +346 -29
- data/spec/spec_helper.rb +41 -41
- data/spec/tmp/cli_test_output.png +0 -0
- data/spec/tmp/cli_test_output_20251105_114647_395.png +0 -0
- data/spec/tmp/combined_test.png +0 -0
- data/spec/tmp/compat_test.png +0 -0
- data/spec/tmp/compat_test_20251030_174233_361.png +0 -0
- data/spec/tmp/final_all_features.png +0 -0
- data/spec/tmp/final_test_all_features.png +0 -0
- data/spec/tmp/full_pipeline_test.png +0 -0
- data/spec/tmp/inner_test.png +0 -0
- data/spec/tmp/integration_test.png +0 -0
- data/spec/tmp/validation_test.png +0 -0
- data/spec/unit/background_sampler_spec.rb +132 -0
- data/spec/unit/cell_cleanup_config_spec.rb +32 -0
- data/spec/unit/cell_cleanup_gimp_script_spec.rb +59 -0
- data/spec/unit/cell_cleanup_processor_spec.rb +261 -0
- data/spec/unit/ghost_edge_cleaner_spec.rb +223 -0
- data/spec/unit/gimp_processor_single_point_selection_spec.rb +81 -0
- data/spec/unit/smoke_detector_spec.rb +246 -0
- data/spec/unit/threshold_stepper_spec.rb +195 -0
- metadata +56 -10
|
@@ -1,1892 +1,2026 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe RubySpriter::CLI do
|
|
6
|
-
describe 'Other Options' do
|
|
7
|
-
describe '--keep-temp flag' do
|
|
8
|
-
it 'sets keep_temp option to true' do
|
|
9
|
-
# Mock the Processor to capture the options
|
|
10
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
11
|
-
allow(processor_double).to receive(:run)
|
|
12
|
-
|
|
13
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
14
|
-
expect(options[:keep_temp]).to eq(true)
|
|
15
|
-
processor_double
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Parse with --keep-temp and a valid input to avoid validation errors
|
|
19
|
-
described_class.start(['--video', 'test.mp4', '--keep-temp'])
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
describe '--debug flag' do
|
|
24
|
-
it 'sets both debug and keep_temp options to true' do
|
|
25
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
26
|
-
allow(processor_double).to receive(:run)
|
|
27
|
-
|
|
28
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
29
|
-
expect(options[:debug]).to eq(true)
|
|
30
|
-
expect(options[:keep_temp]).to eq(true)
|
|
31
|
-
processor_double
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
described_class.start(['--video', 'test.mp4', '--debug'])
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
describe '--help flag' do
|
|
39
|
-
it 'outputs help text and exits' do
|
|
40
|
-
expect do
|
|
41
|
-
expect { described_class.start(['--help']) }.to output(/Usage: ruby_spriter/).to_stdout
|
|
42
|
-
end.to raise_error(SystemExit)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
it 'displays Other Options section' do
|
|
46
|
-
expect do
|
|
47
|
-
expect { described_class.start(['--help']) }.to output(/Other Options:/).to_stdout
|
|
48
|
-
end.to raise_error(SystemExit)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it 'lists --keep-temp in help output' do
|
|
52
|
-
expect do
|
|
53
|
-
expect { described_class.start(['--help']) }.to output(/--keep-temp/).to_stdout
|
|
54
|
-
end.to raise_error(SystemExit)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
it 'lists --debug in help output' do
|
|
58
|
-
expect do
|
|
59
|
-
expect { described_class.start(['--help']) }.to output(/--debug/).to_stdout
|
|
60
|
-
end.to raise_error(SystemExit)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
it 'lists --version in help output' do
|
|
64
|
-
expect do
|
|
65
|
-
expect { described_class.start(['--help']) }.to output(/--version/).to_stdout
|
|
66
|
-
end.to raise_error(SystemExit)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it 'lists --check-dependencies in help output' do
|
|
70
|
-
expect do
|
|
71
|
-
expect { described_class.start(['--help']) }.to output(/--check-dependencies/).to_stdout
|
|
72
|
-
end.to raise_error(SystemExit)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it 'supports short form -h' do
|
|
76
|
-
expect do
|
|
77
|
-
expect { described_class.start(['-h']) }.to output(/Usage: ruby_spriter/).to_stdout
|
|
78
|
-
end.to raise_error(SystemExit)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
it 'shows mode-specific help hints' do
|
|
82
|
-
output = StringIO.new
|
|
83
|
-
$stdout = output
|
|
84
|
-
|
|
85
|
-
begin
|
|
86
|
-
described_class.start(['--help'])
|
|
87
|
-
rescue SystemExit
|
|
88
|
-
# Expected
|
|
89
|
-
ensure
|
|
90
|
-
$stdout = STDOUT
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
expect(output.string).to include('Get mode-specific help:')
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
describe '--version flag' do
|
|
98
|
-
it 'outputs version information and exits' do
|
|
99
|
-
expect do
|
|
100
|
-
expect { described_class.start(['--version']) }
|
|
101
|
-
.to output(/Ruby Spriter v#{RubySpriter::VERSION}/).to_stdout
|
|
102
|
-
end.to raise_error(SystemExit)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
it 'displays platform information' do
|
|
106
|
-
expect do
|
|
107
|
-
expect { described_class.start(['--version']) }
|
|
108
|
-
.to output(/Platform:/).to_stdout
|
|
109
|
-
end.to raise_error(SystemExit)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
it 'displays date information' do
|
|
113
|
-
expect do
|
|
114
|
-
expect { described_class.start(['--version']) }
|
|
115
|
-
.to output(/Date: #{RubySpriter::VERSION_DATE}/).to_stdout
|
|
116
|
-
end.to raise_error(SystemExit)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
describe '--check-dependencies flag' do
|
|
121
|
-
it 'sets check_dependencies option to true' do
|
|
122
|
-
# Mock DependencyChecker to avoid actually checking dependencies
|
|
123
|
-
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
124
|
-
allow(checker_double).to receive(:print_report)
|
|
125
|
-
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
126
|
-
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
127
|
-
|
|
128
|
-
expect do
|
|
129
|
-
described_class.start(['--check-dependencies'])
|
|
130
|
-
end.to raise_error(SystemExit) { |error|
|
|
131
|
-
expect(error.status).to eq(0)
|
|
132
|
-
}
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
it 'exits with 0 when all dependencies are satisfied' do
|
|
136
|
-
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
137
|
-
allow(checker_double).to receive(:print_report)
|
|
138
|
-
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
139
|
-
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
140
|
-
|
|
141
|
-
expect do
|
|
142
|
-
described_class.start(['--check-dependencies'])
|
|
143
|
-
end.to raise_error(SystemExit) { |error|
|
|
144
|
-
expect(error.status).to eq(0)
|
|
145
|
-
}
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
it 'exits with 1 when dependencies are missing' do
|
|
149
|
-
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
150
|
-
allow(checker_double).to receive(:print_report)
|
|
151
|
-
allow(checker_double).to receive(:all_satisfied?).and_return(false)
|
|
152
|
-
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
153
|
-
|
|
154
|
-
expect do
|
|
155
|
-
described_class.start(['--check-dependencies'])
|
|
156
|
-
end.to raise_error(SystemExit) { |error|
|
|
157
|
-
expect(error.status).to eq(1)
|
|
158
|
-
}
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
it 'calls DependencyChecker with verbose: true' do
|
|
162
|
-
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
163
|
-
allow(checker_double).to receive(:print_report)
|
|
164
|
-
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
165
|
-
|
|
166
|
-
expect(RubySpriter::DependencyChecker).to receive(:new).with(verbose: true).and_return(checker_double)
|
|
167
|
-
|
|
168
|
-
expect do
|
|
169
|
-
described_class.start(['--check-dependencies'])
|
|
170
|
-
end.to raise_error(SystemExit)
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
describe '--overwrite flag' do
|
|
175
|
-
it 'sets overwrite option to true' do
|
|
176
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
177
|
-
allow(processor_double).to receive(:run)
|
|
178
|
-
|
|
179
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
180
|
-
expect(options[:overwrite]).to eq(true)
|
|
181
|
-
processor_double
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
described_class.start(['--video', 'test.mp4', '--overwrite'])
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
it 'defaults to false when not specified' do
|
|
188
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
189
|
-
allow(processor_double).to receive(:run)
|
|
190
|
-
|
|
191
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
192
|
-
expect(options[:overwrite]).to be_nil
|
|
193
|
-
processor_double
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
described_class.start(['--video', 'test.mp4'])
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
describe '--image flag' do
|
|
202
|
-
let(:fixture_with_meta) { File.join(__dir__, '..', 'fixtures', 'spritesheet_with_metadata.png') }
|
|
203
|
-
let(:fixture_without_meta) { File.join(__dir__, '..', 'fixtures', 'image_without_metadata.png') }
|
|
204
|
-
|
|
205
|
-
describe 'argument parsing' do
|
|
206
|
-
it 'sets image option with --image flag' do
|
|
207
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
208
|
-
allow(processor_double).to receive(:run)
|
|
209
|
-
|
|
210
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
211
|
-
expect(options[:image]).to eq(fixture_with_meta)
|
|
212
|
-
processor_double
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
described_class.start(['--image', fixture_with_meta])
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
it 'supports short form -i flag' do
|
|
219
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
220
|
-
allow(processor_double).to receive(:run)
|
|
221
|
-
|
|
222
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
223
|
-
expect(options[:image]).to eq(fixture_without_meta)
|
|
224
|
-
processor_double
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
described_class.start(['-i', fixture_without_meta])
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
it 'accepts file path with spaces' do
|
|
231
|
-
# Create a temp file with spaces in the name for this test
|
|
232
|
-
temp_file = File.join(@test_dir, 'file with spaces.png')
|
|
233
|
-
FileUtils.cp(fixture_with_meta, temp_file)
|
|
234
|
-
|
|
235
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
236
|
-
allow(processor_double).to receive(:run)
|
|
237
|
-
|
|
238
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
239
|
-
expect(options[:image]).to eq(temp_file)
|
|
240
|
-
processor_double
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
described_class.start(['--image', temp_file])
|
|
244
|
-
end
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
describe 'mutual exclusivity with other input modes' do
|
|
248
|
-
it 'cannot be used with --video' do
|
|
249
|
-
expect do
|
|
250
|
-
described_class.start(['--video', 'test.mp4', '--image', fixture_with_meta])
|
|
251
|
-
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
it 'cannot be used with --consolidate' do
|
|
255
|
-
expect do
|
|
256
|
-
described_class.start(['--consolidate', 'a.png,b.png', '--image', fixture_with_meta])
|
|
257
|
-
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
it 'cannot be used with --verify' do
|
|
261
|
-
expect do
|
|
262
|
-
described_class.start(['--verify', fixture_with_meta, '--image', fixture_without_meta])
|
|
263
|
-
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
it 'can be used alone without error' do
|
|
267
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
268
|
-
allow(processor_double).to receive(:run)
|
|
269
|
-
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
270
|
-
|
|
271
|
-
expect do
|
|
272
|
-
described_class.start(['--image', fixture_with_meta])
|
|
273
|
-
end.not_to raise_error
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
describe 'file validation' do
|
|
278
|
-
describe 'file existence' do
|
|
279
|
-
it 'raises error for non-existent file' do
|
|
280
|
-
expect do
|
|
281
|
-
described_class.start(['--image', 'nonexistent.png'])
|
|
282
|
-
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
it 'accepts existing PNG file with metadata' do
|
|
286
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
287
|
-
allow(processor_double).to receive(:run)
|
|
288
|
-
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
289
|
-
|
|
290
|
-
expect(File.exist?(fixture_with_meta)).to be true
|
|
291
|
-
expect do
|
|
292
|
-
described_class.start(['--image', fixture_with_meta])
|
|
293
|
-
end.not_to raise_error
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
it 'accepts existing PNG file without metadata' do
|
|
297
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
298
|
-
allow(processor_double).to receive(:run)
|
|
299
|
-
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
300
|
-
|
|
301
|
-
expect(File.exist?(fixture_without_meta)).to be true
|
|
302
|
-
expect do
|
|
303
|
-
described_class.start(['--image', fixture_without_meta])
|
|
304
|
-
end.not_to raise_error
|
|
305
|
-
end
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
describe 'file extension validation' do
|
|
309
|
-
it 'accepts .png extension' do
|
|
310
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
311
|
-
allow(processor_double).to receive(:run)
|
|
312
|
-
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
313
|
-
|
|
314
|
-
expect(File.extname(fixture_with_meta)).to eq('.png')
|
|
315
|
-
expect do
|
|
316
|
-
described_class.start(['--image', fixture_with_meta])
|
|
317
|
-
end.not_to raise_error
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
it 'accepts .PNG extension (case insensitive)' do
|
|
321
|
-
# Create a temp file with uppercase extension
|
|
322
|
-
temp_file = File.join(@test_dir, 'test.PNG')
|
|
323
|
-
FileUtils.cp(fixture_with_meta, temp_file)
|
|
324
|
-
|
|
325
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
326
|
-
allow(processor_double).to receive(:run)
|
|
327
|
-
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
328
|
-
|
|
329
|
-
expect do
|
|
330
|
-
described_class.start(['--image', temp_file])
|
|
331
|
-
end.not_to raise_error
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
it 'rejects .jpg extension' do
|
|
335
|
-
# Create a fake .jpg file (doesn't need to be valid JPG for this test)
|
|
336
|
-
temp_file = File.join(@test_dir, 'test.jpg')
|
|
337
|
-
FileUtils.touch(temp_file)
|
|
338
|
-
|
|
339
|
-
expect do
|
|
340
|
-
described_class.start(['--image', temp_file])
|
|
341
|
-
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.jpg/)
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
it 'rejects .jpeg extension' do
|
|
345
|
-
temp_file = File.join(@test_dir, 'test.jpeg')
|
|
346
|
-
FileUtils.touch(temp_file)
|
|
347
|
-
|
|
348
|
-
expect do
|
|
349
|
-
described_class.start(['--image', temp_file])
|
|
350
|
-
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.jpeg/)
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
it 'rejects .gif extension' do
|
|
354
|
-
temp_file = File.join(@test_dir, 'test.gif')
|
|
355
|
-
FileUtils.touch(temp_file)
|
|
356
|
-
|
|
357
|
-
expect do
|
|
358
|
-
described_class.start(['--image', temp_file])
|
|
359
|
-
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.gif/)
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
it 'rejects .bmp extension' do
|
|
363
|
-
temp_file = File.join(@test_dir, 'test.bmp')
|
|
364
|
-
FileUtils.touch(temp_file)
|
|
365
|
-
|
|
366
|
-
expect do
|
|
367
|
-
described_class.start(['--image', temp_file])
|
|
368
|
-
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.bmp/)
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
it 'rejects file with no extension' do
|
|
372
|
-
temp_file = File.join(@test_dir, 'testfile')
|
|
373
|
-
FileUtils.touch(temp_file)
|
|
374
|
-
|
|
375
|
-
expect do
|
|
376
|
-
described_class.start(['--image', temp_file])
|
|
377
|
-
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file/)
|
|
378
|
-
end
|
|
379
|
-
end
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
describe 'integration with processing options' do
|
|
383
|
-
it 'works with --scale option' do
|
|
384
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
385
|
-
allow(processor_double).to receive(:run)
|
|
386
|
-
|
|
387
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
388
|
-
expect(options[:image]).to eq(fixture_with_meta)
|
|
389
|
-
expect(options[:scale_percent]).to eq(50)
|
|
390
|
-
processor_double
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
described_class.start(['--image', fixture_with_meta, '--scale', '50'])
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
it 'works with --remove-bg option' do
|
|
397
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
398
|
-
allow(processor_double).to receive(:run)
|
|
399
|
-
|
|
400
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
401
|
-
expect(options[:image]).to eq(fixture_with_meta)
|
|
402
|
-
expect(options[:remove_bg]).to eq(true)
|
|
403
|
-
processor_double
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
described_class.start(['--image', fixture_with_meta, '--remove-bg'])
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
it 'works with --sharpen option' do
|
|
410
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
411
|
-
allow(processor_double).to receive(:run)
|
|
412
|
-
|
|
413
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
414
|
-
expect(options[:image]).to eq(fixture_without_meta)
|
|
415
|
-
expect(options[:sharpen]).to eq(true)
|
|
416
|
-
processor_double
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
described_class.start(['--image', fixture_without_meta, '--sharpen'])
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
it 'works with --interpolation option' do
|
|
423
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
424
|
-
allow(processor_double).to receive(:run)
|
|
425
|
-
|
|
426
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
427
|
-
expect(options[:image]).to eq(fixture_with_meta)
|
|
428
|
-
expect(options[:scale_interpolation]).to eq('nohalo')
|
|
429
|
-
processor_double
|
|
430
|
-
end
|
|
431
|
-
|
|
432
|
-
described_class.start(['--image', fixture_with_meta, '--interpolation', 'nohalo'])
|
|
433
|
-
end
|
|
434
|
-
|
|
435
|
-
it 'works with multiple processing options combined' do
|
|
436
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
437
|
-
allow(processor_double).to receive(:run)
|
|
438
|
-
|
|
439
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
440
|
-
expect(options[:image]).to eq(fixture_without_meta)
|
|
441
|
-
expect(options[:scale_percent]).to eq(50)
|
|
442
|
-
expect(options[:remove_bg]).to eq(true)
|
|
443
|
-
expect(options[:sharpen]).to eq(true)
|
|
444
|
-
expect(options[:scale_interpolation]).to eq('lohalo')
|
|
445
|
-
processor_double
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
described_class.start([
|
|
449
|
-
'--image', fixture_without_meta,
|
|
450
|
-
'--scale', '50',
|
|
451
|
-
'--remove-bg',
|
|
452
|
-
'--sharpen',
|
|
453
|
-
'--interpolation', 'lohalo'
|
|
454
|
-
])
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
it 'works with --output option' do
|
|
458
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
459
|
-
allow(processor_double).to receive(:run)
|
|
460
|
-
|
|
461
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
462
|
-
expect(options[:image]).to eq(fixture_with_meta)
|
|
463
|
-
expect(options[:output]).to eq('custom_output.png')
|
|
464
|
-
processor_double
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
described_class.start(['--image', fixture_with_meta, '--output', 'custom_output.png'])
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
it 'works with --overwrite option' do
|
|
471
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
472
|
-
allow(processor_double).to receive(:run)
|
|
473
|
-
|
|
474
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
475
|
-
expect(options[:image]).to eq(fixture_with_meta)
|
|
476
|
-
expect(options[:remove_bg]).to eq(true)
|
|
477
|
-
expect(options[:overwrite]).to eq(true)
|
|
478
|
-
processor_double
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
described_class.start(['--image', fixture_with_meta, '--remove-bg', '--overwrite'])
|
|
482
|
-
end
|
|
483
|
-
|
|
484
|
-
it 'works with --overwrite and --output options combined' do
|
|
485
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
486
|
-
allow(processor_double).to receive(:run)
|
|
487
|
-
|
|
488
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
489
|
-
expect(options[:image]).to eq(fixture_with_meta)
|
|
490
|
-
expect(options[:scale_percent]).to eq(50)
|
|
491
|
-
expect(options[:output]).to eq('custom.png')
|
|
492
|
-
expect(options[:overwrite]).to eq(true)
|
|
493
|
-
processor_double
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
described_class.start(['--image', fixture_with_meta, '--scale', '50', '--output', 'custom.png', '--overwrite'])
|
|
497
|
-
end
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
describe 'output filename behavior with processing' do
|
|
501
|
-
it 'generates unique filename by default when processing without --output' do
|
|
502
|
-
# Mock all dependencies
|
|
503
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
504
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
505
|
-
|
|
506
|
-
# Mock GimpProcessor to return a processed file
|
|
507
|
-
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
508
|
-
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
509
|
-
allow(gimp_double).to receive(:process).and_return('input-nobg-fuzzy_20251023_123456_789.png')
|
|
510
|
-
|
|
511
|
-
processor = RubySpriter::Processor.new(
|
|
512
|
-
image: fixture_with_meta,
|
|
513
|
-
remove_bg: true,
|
|
514
|
-
overwrite: false
|
|
515
|
-
)
|
|
516
|
-
|
|
517
|
-
allow(processor).to receive(:check_dependencies!)
|
|
518
|
-
allow(processor).to receive(:setup_temp_directory)
|
|
519
|
-
allow(processor).to receive(:cleanup)
|
|
520
|
-
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
521
|
-
|
|
522
|
-
result = nil
|
|
523
|
-
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
524
|
-
|
|
525
|
-
# Should return the uniquely-named file from GIMP processing
|
|
526
|
-
expect(result[:output_file]).to match(/-nobg-fuzzy.*\.png$/)
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
it 'overwrites output file when --overwrite is specified' do
|
|
530
|
-
# Mock all dependencies
|
|
531
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
532
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
533
|
-
|
|
534
|
-
# Mock GimpProcessor - with overwrite:true, it should return same filename
|
|
535
|
-
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
536
|
-
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
537
|
-
allow(gimp_double).to receive(:process).and_return('input-scaled-50pct.png')
|
|
538
|
-
|
|
539
|
-
processor = RubySpriter::Processor.new(
|
|
540
|
-
image: fixture_with_meta,
|
|
541
|
-
scale_percent: 50,
|
|
542
|
-
overwrite: true
|
|
543
|
-
)
|
|
544
|
-
|
|
545
|
-
allow(processor).to receive(:check_dependencies!)
|
|
546
|
-
allow(processor).to receive(:setup_temp_directory)
|
|
547
|
-
allow(processor).to receive(:cleanup)
|
|
548
|
-
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
549
|
-
|
|
550
|
-
result = nil
|
|
551
|
-
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
552
|
-
|
|
553
|
-
# Should return the base filename (no timestamp)
|
|
554
|
-
expect(result[:output_file]).to eq('input-scaled-50pct.png')
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
it 'generates unique output filename when --output is used without --overwrite' do
|
|
558
|
-
# Mock all dependencies
|
|
559
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
560
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
561
|
-
|
|
562
|
-
# Mock ensure_unique_output to verify it's called correctly
|
|
563
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
564
|
-
expect(path).to eq('custom_output.png')
|
|
565
|
-
expect(overwrite).to eq(false)
|
|
566
|
-
'custom_output_20251023_123456_789.png'
|
|
567
|
-
end
|
|
568
|
-
|
|
569
|
-
# Mock GimpProcessor
|
|
570
|
-
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
571
|
-
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
572
|
-
allow(gimp_double).to receive(:process).and_return('temp-processed.png')
|
|
573
|
-
|
|
574
|
-
# Mock file operations
|
|
575
|
-
allow(FileUtils).to receive(:cp)
|
|
576
|
-
|
|
577
|
-
processor = RubySpriter::Processor.new(
|
|
578
|
-
image: fixture_with_meta,
|
|
579
|
-
remove_bg: true,
|
|
580
|
-
output: 'custom_output.png',
|
|
581
|
-
overwrite: false
|
|
582
|
-
)
|
|
583
|
-
|
|
584
|
-
allow(processor).to receive(:check_dependencies!)
|
|
585
|
-
allow(processor).to receive(:setup_temp_directory)
|
|
586
|
-
allow(processor).to receive(:cleanup)
|
|
587
|
-
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
588
|
-
|
|
589
|
-
result = nil
|
|
590
|
-
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
591
|
-
|
|
592
|
-
# Should return unique filename
|
|
593
|
-
expect(result[:output_file]).to match(/custom_output_\d{8}_\d{6}_\d{3}\.png$/)
|
|
594
|
-
end
|
|
595
|
-
|
|
596
|
-
it 'uses exact output filename when --output and --overwrite are both specified' do
|
|
597
|
-
# Mock all dependencies
|
|
598
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
599
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
600
|
-
|
|
601
|
-
# Mock ensure_unique_output to verify it's called with overwrite:true
|
|
602
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
603
|
-
expect(path).to eq('exact_output.png')
|
|
604
|
-
expect(overwrite).to eq(true)
|
|
605
|
-
'exact_output.png'
|
|
606
|
-
end
|
|
607
|
-
|
|
608
|
-
# Mock GimpProcessor
|
|
609
|
-
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
610
|
-
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
611
|
-
allow(gimp_double).to receive(:process).and_return('temp-processed.png')
|
|
612
|
-
|
|
613
|
-
# Mock file operations
|
|
614
|
-
allow(FileUtils).to receive(:cp)
|
|
615
|
-
|
|
616
|
-
processor = RubySpriter::Processor.new(
|
|
617
|
-
image: fixture_with_meta,
|
|
618
|
-
scale_percent: 50,
|
|
619
|
-
output: 'exact_output.png',
|
|
620
|
-
overwrite: true
|
|
621
|
-
)
|
|
622
|
-
|
|
623
|
-
allow(processor).to receive(:check_dependencies!)
|
|
624
|
-
allow(processor).to receive(:setup_temp_directory)
|
|
625
|
-
allow(processor).to receive(:cleanup)
|
|
626
|
-
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
627
|
-
|
|
628
|
-
result = nil
|
|
629
|
-
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
630
|
-
|
|
631
|
-
# Should return exact filename (no timestamp)
|
|
632
|
-
expect(result[:output_file]).to eq('exact_output.png')
|
|
633
|
-
end
|
|
634
|
-
|
|
635
|
-
it 'generates unique filename when using --sharpen alone without --output' do
|
|
636
|
-
# Mock all dependencies
|
|
637
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
638
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
639
|
-
|
|
640
|
-
# Mock GimpProcessor to return a sharpened file
|
|
641
|
-
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
642
|
-
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
643
|
-
allow(gimp_double).to receive(:process).and_return('input-sharpened_20251023_123456_789.png')
|
|
644
|
-
|
|
645
|
-
processor = RubySpriter::Processor.new(
|
|
646
|
-
image: fixture_with_meta,
|
|
647
|
-
sharpen: true,
|
|
648
|
-
overwrite: false
|
|
649
|
-
)
|
|
650
|
-
|
|
651
|
-
allow(processor).to receive(:check_dependencies!)
|
|
652
|
-
allow(processor).to receive(:setup_temp_directory)
|
|
653
|
-
allow(processor).to receive(:cleanup)
|
|
654
|
-
|
|
655
|
-
result = nil
|
|
656
|
-
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
657
|
-
|
|
658
|
-
# Should return the uniquely-named sharpened file
|
|
659
|
-
expect(result[:output_file]).to match(/-sharpened.*\.png$/)
|
|
660
|
-
end
|
|
661
|
-
|
|
662
|
-
it 'overwrites sharpened file when --sharpen with --overwrite' do
|
|
663
|
-
# Mock all dependencies
|
|
664
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
665
|
-
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
666
|
-
|
|
667
|
-
# Mock GimpProcessor - with overwrite:true, should return base filename
|
|
668
|
-
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
669
|
-
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
670
|
-
allow(gimp_double).to receive(:process).and_return('input-sharpened.png')
|
|
671
|
-
|
|
672
|
-
processor = RubySpriter::Processor.new(
|
|
673
|
-
image: fixture_with_meta,
|
|
674
|
-
sharpen: true,
|
|
675
|
-
overwrite: true
|
|
676
|
-
)
|
|
677
|
-
|
|
678
|
-
allow(processor).to receive(:check_dependencies!)
|
|
679
|
-
allow(processor).to receive(:setup_temp_directory)
|
|
680
|
-
allow(processor).to receive(:cleanup)
|
|
681
|
-
|
|
682
|
-
result = nil
|
|
683
|
-
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
684
|
-
|
|
685
|
-
# Should return the base filename (no timestamp)
|
|
686
|
-
expect(result[:output_file]).to eq('input-sharpened.png')
|
|
687
|
-
end
|
|
688
|
-
end
|
|
689
|
-
end
|
|
690
|
-
|
|
691
|
-
describe '--video flag' do
|
|
692
|
-
let(:fixture_video) { File.join(__dir__, '..', 'fixtures', 'test_video.mp4') }
|
|
693
|
-
|
|
694
|
-
describe 'context-sensitive help' do
|
|
695
|
-
it 'shows video mode help with --help' do
|
|
696
|
-
output = StringIO.new
|
|
697
|
-
$stdout = output
|
|
698
|
-
|
|
699
|
-
begin
|
|
700
|
-
described_class.start(['--video', '--help'])
|
|
701
|
-
rescue SystemExit
|
|
702
|
-
# Expected
|
|
703
|
-
ensure
|
|
704
|
-
$stdout = STDOUT
|
|
705
|
-
end
|
|
706
|
-
|
|
707
|
-
expect(output.string).to include('Video Mode')
|
|
708
|
-
end
|
|
709
|
-
|
|
710
|
-
it 'shows parent-child option hierarchy in video mode help' do
|
|
711
|
-
output = StringIO.new
|
|
712
|
-
$stdout = output
|
|
713
|
-
|
|
714
|
-
begin
|
|
715
|
-
described_class.start(['--video', '--help'])
|
|
716
|
-
rescue SystemExit
|
|
717
|
-
# Expected
|
|
718
|
-
ensure
|
|
719
|
-
$stdout = STDOUT
|
|
720
|
-
end
|
|
721
|
-
|
|
722
|
-
# Check for parent options
|
|
723
|
-
expect(output.string).to include('-s, --scale PERCENT')
|
|
724
|
-
expect(output.string).to include('-r, --remove-bg')
|
|
725
|
-
|
|
726
|
-
# Check for child options with hierarchy marker
|
|
727
|
-
expect(output.string).to include('└─ Interpolation:')
|
|
728
|
-
expect(output.string).to include('└─ Sharpen radius')
|
|
729
|
-
expect(output.string).to include('└─ Use fuzzy select')
|
|
730
|
-
expect(output.string).to include('└─ Feather radius')
|
|
731
|
-
expect(output.string).to include('└─ Grow selection')
|
|
732
|
-
|
|
733
|
-
# Check that --order mentions BOTH requirement
|
|
734
|
-
expect(output.string).to match(/order.*BOTH.*--scale.*AND.*--remove-bg/i)
|
|
735
|
-
end
|
|
736
|
-
|
|
737
|
-
it 'shows --sharpen as standalone option in video mode help' do
|
|
738
|
-
output = StringIO.new
|
|
739
|
-
$stdout = output
|
|
740
|
-
|
|
741
|
-
begin
|
|
742
|
-
described_class.start(['--video', '--help'])
|
|
743
|
-
rescue SystemExit
|
|
744
|
-
# Expected
|
|
745
|
-
ensure
|
|
746
|
-
$stdout = STDOUT
|
|
747
|
-
end
|
|
748
|
-
|
|
749
|
-
# --sharpen should be a standalone parent option (not indented under --scale)
|
|
750
|
-
expect(output.string).to match(/^ --sharpen\s+Apply unsharp mask/)
|
|
751
|
-
|
|
752
|
-
# --sharpen modifiers should be children under --sharpen
|
|
753
|
-
expect(output.string).to include('└─ Sharpen radius')
|
|
754
|
-
expect(output.string).to include('└─ Sharpen gain')
|
|
755
|
-
expect(output.string).to include('└─ Sharpen threshold')
|
|
756
|
-
|
|
757
|
-
# --interpolation should ONLY be under --scale (not under --sharpen)
|
|
758
|
-
lines = output.string.lines
|
|
759
|
-
sharpen_line_idx = lines.index { |l| l.include?('--sharpen') && l.include?('Apply unsharp mask') }
|
|
760
|
-
scale_line_idx = lines.index { |l| l.include?('--scale PERCENT') }
|
|
761
|
-
interpolation_line_idx = lines.index { |l| l.include?('└─ Interpolation') }
|
|
762
|
-
|
|
763
|
-
# Interpolation should come after scale, not after sharpen
|
|
764
|
-
expect(interpolation_line_idx).to be > scale_line_idx
|
|
765
|
-
expect(interpolation_line_idx).to be < sharpen_line_idx
|
|
766
|
-
end
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
it 'shows
|
|
784
|
-
output = StringIO.new
|
|
785
|
-
$stdout = output
|
|
786
|
-
|
|
787
|
-
begin
|
|
788
|
-
described_class.start(['--
|
|
789
|
-
rescue SystemExit
|
|
790
|
-
# Expected
|
|
791
|
-
ensure
|
|
792
|
-
$stdout = STDOUT
|
|
793
|
-
end
|
|
794
|
-
|
|
795
|
-
expect(output.string).to include('
|
|
796
|
-
end
|
|
797
|
-
|
|
798
|
-
it 'shows
|
|
799
|
-
output = StringIO.new
|
|
800
|
-
$stdout = output
|
|
801
|
-
|
|
802
|
-
begin
|
|
803
|
-
described_class.start(['--
|
|
804
|
-
rescue SystemExit
|
|
805
|
-
# Expected
|
|
806
|
-
ensure
|
|
807
|
-
$stdout = STDOUT
|
|
808
|
-
end
|
|
809
|
-
|
|
810
|
-
expect(output.string).to include('
|
|
811
|
-
end
|
|
812
|
-
|
|
813
|
-
it 'shows
|
|
814
|
-
output = StringIO.new
|
|
815
|
-
$stdout = output
|
|
816
|
-
|
|
817
|
-
begin
|
|
818
|
-
described_class.start(['--
|
|
819
|
-
rescue SystemExit
|
|
820
|
-
# Expected
|
|
821
|
-
ensure
|
|
822
|
-
$stdout = STDOUT
|
|
823
|
-
end
|
|
824
|
-
|
|
825
|
-
expect(output.string).to include('
|
|
826
|
-
end
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
allow(
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
it '
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
end
|
|
883
|
-
|
|
884
|
-
it '
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
allow(
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
end
|
|
899
|
-
end
|
|
900
|
-
|
|
901
|
-
describe '
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
it '
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
expect(options[:
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
expect(options[:
|
|
1133
|
-
expect(options[:sharpen]).to eq(true)
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
allow(
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
expect(options[:
|
|
1185
|
-
expect(options[:
|
|
1186
|
-
processor_double
|
|
1187
|
-
end
|
|
1188
|
-
|
|
1189
|
-
described_class.start(['--video', fixture_video, '--
|
|
1190
|
-
end
|
|
1191
|
-
|
|
1192
|
-
it 'works with --
|
|
1193
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
1194
|
-
allow(processor_double).to receive(:run)
|
|
1195
|
-
|
|
1196
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1197
|
-
expect(options[:video]).to eq(fixture_video)
|
|
1198
|
-
expect(options[:
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
it 'works with --preset
|
|
1208
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
1209
|
-
allow(processor_double).to receive(:run)
|
|
1210
|
-
|
|
1211
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1212
|
-
expect(options[:video]).to eq(fixture_video)
|
|
1213
|
-
expect(options[:columns]).to eq(
|
|
1214
|
-
expect(options[:frame_count]).to eq(
|
|
1215
|
-
expect(options[:max_width]).to eq(
|
|
1216
|
-
processor_double
|
|
1217
|
-
end
|
|
1218
|
-
|
|
1219
|
-
described_class.start(['--video', fixture_video, '--preset', '
|
|
1220
|
-
end
|
|
1221
|
-
|
|
1222
|
-
it 'works with --preset
|
|
1223
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
1224
|
-
allow(processor_double).to receive(:run)
|
|
1225
|
-
|
|
1226
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1227
|
-
expect(options[:video]).to eq(fixture_video)
|
|
1228
|
-
expect(options[:columns]).to eq(
|
|
1229
|
-
expect(options[:frame_count]).to eq(
|
|
1230
|
-
expect(options[:max_width]).to eq(
|
|
1231
|
-
processor_double
|
|
1232
|
-
end
|
|
1233
|
-
|
|
1234
|
-
described_class.start(['--video', fixture_video, '--preset', '
|
|
1235
|
-
end
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
1283
|
-
allow(processor_double).to receive(:run)
|
|
1284
|
-
|
|
1285
|
-
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1286
|
-
expect(options[:consolidate]).to eq([
|
|
1287
|
-
processor_double
|
|
1288
|
-
end
|
|
1289
|
-
|
|
1290
|
-
described_class.start(['--consolidate', "#{
|
|
1291
|
-
end
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
1313
|
-
allow(processor_double).to receive(:run)
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
end
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
expect do
|
|
1337
|
-
described_class.start(['--
|
|
1338
|
-
end.
|
|
1339
|
-
end
|
|
1340
|
-
|
|
1341
|
-
it '
|
|
1342
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
1343
|
-
allow(processor_double).to receive(:run)
|
|
1344
|
-
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1345
|
-
|
|
1346
|
-
expect do
|
|
1347
|
-
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1348
|
-
end.not_to raise_error
|
|
1349
|
-
end
|
|
1350
|
-
end
|
|
1351
|
-
|
|
1352
|
-
describe '
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
processor_double = instance_double(RubySpriter::Processor)
|
|
1404
|
-
allow(processor_double).to receive(:run)
|
|
1405
|
-
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1406
|
-
|
|
1407
|
-
expect
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
expect do
|
|
1447
|
-
described_class.start(['--consolidate', "#{spritesheet_4x2},#{
|
|
1448
|
-
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file/)
|
|
1449
|
-
end
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
allow(
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
allow(
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
expect(options[:
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
expect(options[:
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
consolidate
|
|
1565
|
-
overwrite
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
consolidator_double
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
)
|
|
1599
|
-
|
|
1600
|
-
allow(processor).to receive(:
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
end
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
expect(options[:
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
it '
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
expect(
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
allow(
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
allow(
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
allow(
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
allow(
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe RubySpriter::CLI do
|
|
6
|
+
describe 'Other Options' do
|
|
7
|
+
describe '--keep-temp flag' do
|
|
8
|
+
it 'sets keep_temp option to true' do
|
|
9
|
+
# Mock the Processor to capture the options
|
|
10
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
11
|
+
allow(processor_double).to receive(:run)
|
|
12
|
+
|
|
13
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
14
|
+
expect(options[:keep_temp]).to eq(true)
|
|
15
|
+
processor_double
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Parse with --keep-temp and a valid input to avoid validation errors
|
|
19
|
+
described_class.start(['--video', 'test.mp4', '--keep-temp'])
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '--debug flag' do
|
|
24
|
+
it 'sets both debug and keep_temp options to true' do
|
|
25
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
26
|
+
allow(processor_double).to receive(:run)
|
|
27
|
+
|
|
28
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
29
|
+
expect(options[:debug]).to eq(true)
|
|
30
|
+
expect(options[:keep_temp]).to eq(true)
|
|
31
|
+
processor_double
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
described_class.start(['--video', 'test.mp4', '--debug'])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '--help flag' do
|
|
39
|
+
it 'outputs help text and exits' do
|
|
40
|
+
expect do
|
|
41
|
+
expect { described_class.start(['--help']) }.to output(/Usage: ruby_spriter/).to_stdout
|
|
42
|
+
end.to raise_error(SystemExit)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'displays Other Options section' do
|
|
46
|
+
expect do
|
|
47
|
+
expect { described_class.start(['--help']) }.to output(/Other Options:/).to_stdout
|
|
48
|
+
end.to raise_error(SystemExit)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'lists --keep-temp in help output' do
|
|
52
|
+
expect do
|
|
53
|
+
expect { described_class.start(['--help']) }.to output(/--keep-temp/).to_stdout
|
|
54
|
+
end.to raise_error(SystemExit)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'lists --debug in help output' do
|
|
58
|
+
expect do
|
|
59
|
+
expect { described_class.start(['--help']) }.to output(/--debug/).to_stdout
|
|
60
|
+
end.to raise_error(SystemExit)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'lists --version in help output' do
|
|
64
|
+
expect do
|
|
65
|
+
expect { described_class.start(['--help']) }.to output(/--version/).to_stdout
|
|
66
|
+
end.to raise_error(SystemExit)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'lists --check-dependencies in help output' do
|
|
70
|
+
expect do
|
|
71
|
+
expect { described_class.start(['--help']) }.to output(/--check-dependencies/).to_stdout
|
|
72
|
+
end.to raise_error(SystemExit)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'supports short form -h' do
|
|
76
|
+
expect do
|
|
77
|
+
expect { described_class.start(['-h']) }.to output(/Usage: ruby_spriter/).to_stdout
|
|
78
|
+
end.to raise_error(SystemExit)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'shows mode-specific help hints' do
|
|
82
|
+
output = StringIO.new
|
|
83
|
+
$stdout = output
|
|
84
|
+
|
|
85
|
+
begin
|
|
86
|
+
described_class.start(['--help'])
|
|
87
|
+
rescue SystemExit
|
|
88
|
+
# Expected
|
|
89
|
+
ensure
|
|
90
|
+
$stdout = STDOUT
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
expect(output.string).to include('Get mode-specific help:')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe '--version flag' do
|
|
98
|
+
it 'outputs version information and exits' do
|
|
99
|
+
expect do
|
|
100
|
+
expect { described_class.start(['--version']) }
|
|
101
|
+
.to output(/Ruby Spriter v#{RubySpriter::VERSION}/).to_stdout
|
|
102
|
+
end.to raise_error(SystemExit)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'displays platform information' do
|
|
106
|
+
expect do
|
|
107
|
+
expect { described_class.start(['--version']) }
|
|
108
|
+
.to output(/Platform:/).to_stdout
|
|
109
|
+
end.to raise_error(SystemExit)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'displays date information' do
|
|
113
|
+
expect do
|
|
114
|
+
expect { described_class.start(['--version']) }
|
|
115
|
+
.to output(/Date: #{RubySpriter::VERSION_DATE}/).to_stdout
|
|
116
|
+
end.to raise_error(SystemExit)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe '--check-dependencies flag' do
|
|
121
|
+
it 'sets check_dependencies option to true' do
|
|
122
|
+
# Mock DependencyChecker to avoid actually checking dependencies
|
|
123
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
124
|
+
allow(checker_double).to receive(:print_report)
|
|
125
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
126
|
+
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
127
|
+
|
|
128
|
+
expect do
|
|
129
|
+
described_class.start(['--check-dependencies'])
|
|
130
|
+
end.to raise_error(SystemExit) { |error|
|
|
131
|
+
expect(error.status).to eq(0)
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'exits with 0 when all dependencies are satisfied' do
|
|
136
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
137
|
+
allow(checker_double).to receive(:print_report)
|
|
138
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
139
|
+
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
140
|
+
|
|
141
|
+
expect do
|
|
142
|
+
described_class.start(['--check-dependencies'])
|
|
143
|
+
end.to raise_error(SystemExit) { |error|
|
|
144
|
+
expect(error.status).to eq(0)
|
|
145
|
+
}
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'exits with 1 when dependencies are missing' do
|
|
149
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
150
|
+
allow(checker_double).to receive(:print_report)
|
|
151
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(false)
|
|
152
|
+
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
153
|
+
|
|
154
|
+
expect do
|
|
155
|
+
described_class.start(['--check-dependencies'])
|
|
156
|
+
end.to raise_error(SystemExit) { |error|
|
|
157
|
+
expect(error.status).to eq(1)
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'calls DependencyChecker with verbose: true' do
|
|
162
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
163
|
+
allow(checker_double).to receive(:print_report)
|
|
164
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
165
|
+
|
|
166
|
+
expect(RubySpriter::DependencyChecker).to receive(:new).with(verbose: true).and_return(checker_double)
|
|
167
|
+
|
|
168
|
+
expect do
|
|
169
|
+
described_class.start(['--check-dependencies'])
|
|
170
|
+
end.to raise_error(SystemExit)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe '--overwrite flag' do
|
|
175
|
+
it 'sets overwrite option to true' do
|
|
176
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
177
|
+
allow(processor_double).to receive(:run)
|
|
178
|
+
|
|
179
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
180
|
+
expect(options[:overwrite]).to eq(true)
|
|
181
|
+
processor_double
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
described_class.start(['--video', 'test.mp4', '--overwrite'])
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'defaults to false when not specified' do
|
|
188
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
189
|
+
allow(processor_double).to receive(:run)
|
|
190
|
+
|
|
191
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
192
|
+
expect(options[:overwrite]).to be_nil
|
|
193
|
+
processor_double
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
described_class.start(['--video', 'test.mp4'])
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
describe '--image flag' do
|
|
202
|
+
let(:fixture_with_meta) { File.join(__dir__, '..', 'fixtures', 'spritesheet_with_metadata.png') }
|
|
203
|
+
let(:fixture_without_meta) { File.join(__dir__, '..', 'fixtures', 'image_without_metadata.png') }
|
|
204
|
+
|
|
205
|
+
describe 'argument parsing' do
|
|
206
|
+
it 'sets image option with --image flag' do
|
|
207
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
208
|
+
allow(processor_double).to receive(:run)
|
|
209
|
+
|
|
210
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
211
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
212
|
+
processor_double
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
described_class.start(['--image', fixture_with_meta])
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'supports short form -i flag' do
|
|
219
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
220
|
+
allow(processor_double).to receive(:run)
|
|
221
|
+
|
|
222
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
223
|
+
expect(options[:image]).to eq(fixture_without_meta)
|
|
224
|
+
processor_double
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
described_class.start(['-i', fixture_without_meta])
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'accepts file path with spaces' do
|
|
231
|
+
# Create a temp file with spaces in the name for this test
|
|
232
|
+
temp_file = File.join(@test_dir, 'file with spaces.png')
|
|
233
|
+
FileUtils.cp(fixture_with_meta, temp_file)
|
|
234
|
+
|
|
235
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
236
|
+
allow(processor_double).to receive(:run)
|
|
237
|
+
|
|
238
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
239
|
+
expect(options[:image]).to eq(temp_file)
|
|
240
|
+
processor_double
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
described_class.start(['--image', temp_file])
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
describe 'mutual exclusivity with other input modes' do
|
|
248
|
+
it 'cannot be used with --video' do
|
|
249
|
+
expect do
|
|
250
|
+
described_class.start(['--video', 'test.mp4', '--image', fixture_with_meta])
|
|
251
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'cannot be used with --consolidate' do
|
|
255
|
+
expect do
|
|
256
|
+
described_class.start(['--consolidate', 'a.png,b.png', '--image', fixture_with_meta])
|
|
257
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'cannot be used with --verify' do
|
|
261
|
+
expect do
|
|
262
|
+
described_class.start(['--verify', fixture_with_meta, '--image', fixture_without_meta])
|
|
263
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it 'can be used alone without error' do
|
|
267
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
268
|
+
allow(processor_double).to receive(:run)
|
|
269
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
270
|
+
|
|
271
|
+
expect do
|
|
272
|
+
described_class.start(['--image', fixture_with_meta])
|
|
273
|
+
end.not_to raise_error
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
describe 'file validation' do
|
|
278
|
+
describe 'file existence' do
|
|
279
|
+
it 'raises error for non-existent file' do
|
|
280
|
+
expect do
|
|
281
|
+
described_class.start(['--image', 'nonexistent.png'])
|
|
282
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'accepts existing PNG file with metadata' do
|
|
286
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
287
|
+
allow(processor_double).to receive(:run)
|
|
288
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
289
|
+
|
|
290
|
+
expect(File.exist?(fixture_with_meta)).to be true
|
|
291
|
+
expect do
|
|
292
|
+
described_class.start(['--image', fixture_with_meta])
|
|
293
|
+
end.not_to raise_error
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'accepts existing PNG file without metadata' do
|
|
297
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
298
|
+
allow(processor_double).to receive(:run)
|
|
299
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
300
|
+
|
|
301
|
+
expect(File.exist?(fixture_without_meta)).to be true
|
|
302
|
+
expect do
|
|
303
|
+
described_class.start(['--image', fixture_without_meta])
|
|
304
|
+
end.not_to raise_error
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
describe 'file extension validation' do
|
|
309
|
+
it 'accepts .png extension' do
|
|
310
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
311
|
+
allow(processor_double).to receive(:run)
|
|
312
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
313
|
+
|
|
314
|
+
expect(File.extname(fixture_with_meta)).to eq('.png')
|
|
315
|
+
expect do
|
|
316
|
+
described_class.start(['--image', fixture_with_meta])
|
|
317
|
+
end.not_to raise_error
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it 'accepts .PNG extension (case insensitive)' do
|
|
321
|
+
# Create a temp file with uppercase extension
|
|
322
|
+
temp_file = File.join(@test_dir, 'test.PNG')
|
|
323
|
+
FileUtils.cp(fixture_with_meta, temp_file)
|
|
324
|
+
|
|
325
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
326
|
+
allow(processor_double).to receive(:run)
|
|
327
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
328
|
+
|
|
329
|
+
expect do
|
|
330
|
+
described_class.start(['--image', temp_file])
|
|
331
|
+
end.not_to raise_error
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it 'rejects .jpg extension' do
|
|
335
|
+
# Create a fake .jpg file (doesn't need to be valid JPG for this test)
|
|
336
|
+
temp_file = File.join(@test_dir, 'test.jpg')
|
|
337
|
+
FileUtils.touch(temp_file)
|
|
338
|
+
|
|
339
|
+
expect do
|
|
340
|
+
described_class.start(['--image', temp_file])
|
|
341
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.jpg/)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it 'rejects .jpeg extension' do
|
|
345
|
+
temp_file = File.join(@test_dir, 'test.jpeg')
|
|
346
|
+
FileUtils.touch(temp_file)
|
|
347
|
+
|
|
348
|
+
expect do
|
|
349
|
+
described_class.start(['--image', temp_file])
|
|
350
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.jpeg/)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it 'rejects .gif extension' do
|
|
354
|
+
temp_file = File.join(@test_dir, 'test.gif')
|
|
355
|
+
FileUtils.touch(temp_file)
|
|
356
|
+
|
|
357
|
+
expect do
|
|
358
|
+
described_class.start(['--image', temp_file])
|
|
359
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.gif/)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'rejects .bmp extension' do
|
|
363
|
+
temp_file = File.join(@test_dir, 'test.bmp')
|
|
364
|
+
FileUtils.touch(temp_file)
|
|
365
|
+
|
|
366
|
+
expect do
|
|
367
|
+
described_class.start(['--image', temp_file])
|
|
368
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.bmp/)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
it 'rejects file with no extension' do
|
|
372
|
+
temp_file = File.join(@test_dir, 'testfile')
|
|
373
|
+
FileUtils.touch(temp_file)
|
|
374
|
+
|
|
375
|
+
expect do
|
|
376
|
+
described_class.start(['--image', temp_file])
|
|
377
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file/)
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
describe 'integration with processing options' do
|
|
383
|
+
it 'works with --scale option' do
|
|
384
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
385
|
+
allow(processor_double).to receive(:run)
|
|
386
|
+
|
|
387
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
388
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
389
|
+
expect(options[:scale_percent]).to eq(50)
|
|
390
|
+
processor_double
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
described_class.start(['--image', fixture_with_meta, '--scale', '50'])
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
it 'works with --remove-bg option' do
|
|
397
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
398
|
+
allow(processor_double).to receive(:run)
|
|
399
|
+
|
|
400
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
401
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
402
|
+
expect(options[:remove_bg]).to eq(true)
|
|
403
|
+
processor_double
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
described_class.start(['--image', fixture_with_meta, '--remove-bg'])
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
it 'works with --sharpen option' do
|
|
410
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
411
|
+
allow(processor_double).to receive(:run)
|
|
412
|
+
|
|
413
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
414
|
+
expect(options[:image]).to eq(fixture_without_meta)
|
|
415
|
+
expect(options[:sharpen]).to eq(true)
|
|
416
|
+
processor_double
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
described_class.start(['--image', fixture_without_meta, '--sharpen'])
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
it 'works with --interpolation option' do
|
|
423
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
424
|
+
allow(processor_double).to receive(:run)
|
|
425
|
+
|
|
426
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
427
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
428
|
+
expect(options[:scale_interpolation]).to eq('nohalo')
|
|
429
|
+
processor_double
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
described_class.start(['--image', fixture_with_meta, '--interpolation', 'nohalo'])
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
it 'works with multiple processing options combined' do
|
|
436
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
437
|
+
allow(processor_double).to receive(:run)
|
|
438
|
+
|
|
439
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
440
|
+
expect(options[:image]).to eq(fixture_without_meta)
|
|
441
|
+
expect(options[:scale_percent]).to eq(50)
|
|
442
|
+
expect(options[:remove_bg]).to eq(true)
|
|
443
|
+
expect(options[:sharpen]).to eq(true)
|
|
444
|
+
expect(options[:scale_interpolation]).to eq('lohalo')
|
|
445
|
+
processor_double
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
described_class.start([
|
|
449
|
+
'--image', fixture_without_meta,
|
|
450
|
+
'--scale', '50',
|
|
451
|
+
'--remove-bg',
|
|
452
|
+
'--sharpen',
|
|
453
|
+
'--interpolation', 'lohalo'
|
|
454
|
+
])
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
it 'works with --output option' do
|
|
458
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
459
|
+
allow(processor_double).to receive(:run)
|
|
460
|
+
|
|
461
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
462
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
463
|
+
expect(options[:output]).to eq('custom_output.png')
|
|
464
|
+
processor_double
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
described_class.start(['--image', fixture_with_meta, '--output', 'custom_output.png'])
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
it 'works with --overwrite option' do
|
|
471
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
472
|
+
allow(processor_double).to receive(:run)
|
|
473
|
+
|
|
474
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
475
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
476
|
+
expect(options[:remove_bg]).to eq(true)
|
|
477
|
+
expect(options[:overwrite]).to eq(true)
|
|
478
|
+
processor_double
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
described_class.start(['--image', fixture_with_meta, '--remove-bg', '--overwrite'])
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
it 'works with --overwrite and --output options combined' do
|
|
485
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
486
|
+
allow(processor_double).to receive(:run)
|
|
487
|
+
|
|
488
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
489
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
490
|
+
expect(options[:scale_percent]).to eq(50)
|
|
491
|
+
expect(options[:output]).to eq('custom.png')
|
|
492
|
+
expect(options[:overwrite]).to eq(true)
|
|
493
|
+
processor_double
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
described_class.start(['--image', fixture_with_meta, '--scale', '50', '--output', 'custom.png', '--overwrite'])
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
describe 'output filename behavior with processing' do
|
|
501
|
+
it 'generates unique filename by default when processing without --output' do
|
|
502
|
+
# Mock all dependencies
|
|
503
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
504
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
505
|
+
|
|
506
|
+
# Mock GimpProcessor to return a processed file
|
|
507
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
508
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
509
|
+
allow(gimp_double).to receive(:process).and_return('input-nobg-fuzzy_20251023_123456_789.png')
|
|
510
|
+
|
|
511
|
+
processor = RubySpriter::Processor.new(
|
|
512
|
+
image: fixture_with_meta,
|
|
513
|
+
remove_bg: true,
|
|
514
|
+
overwrite: false
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
allow(processor).to receive(:check_dependencies!)
|
|
518
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
519
|
+
allow(processor).to receive(:cleanup)
|
|
520
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
521
|
+
|
|
522
|
+
result = nil
|
|
523
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
524
|
+
|
|
525
|
+
# Should return the uniquely-named file from GIMP processing
|
|
526
|
+
expect(result[:output_file]).to match(/-nobg-fuzzy.*\.png$/)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
it 'overwrites output file when --overwrite is specified' do
|
|
530
|
+
# Mock all dependencies
|
|
531
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
532
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
533
|
+
|
|
534
|
+
# Mock GimpProcessor - with overwrite:true, it should return same filename
|
|
535
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
536
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
537
|
+
allow(gimp_double).to receive(:process).and_return('input-scaled-50pct.png')
|
|
538
|
+
|
|
539
|
+
processor = RubySpriter::Processor.new(
|
|
540
|
+
image: fixture_with_meta,
|
|
541
|
+
scale_percent: 50,
|
|
542
|
+
overwrite: true
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
allow(processor).to receive(:check_dependencies!)
|
|
546
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
547
|
+
allow(processor).to receive(:cleanup)
|
|
548
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
549
|
+
|
|
550
|
+
result = nil
|
|
551
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
552
|
+
|
|
553
|
+
# Should return the base filename (no timestamp)
|
|
554
|
+
expect(result[:output_file]).to eq('input-scaled-50pct.png')
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
it 'generates unique output filename when --output is used without --overwrite' do
|
|
558
|
+
# Mock all dependencies
|
|
559
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
560
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
561
|
+
|
|
562
|
+
# Mock ensure_unique_output to verify it's called correctly
|
|
563
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
564
|
+
expect(path).to eq('custom_output.png')
|
|
565
|
+
expect(overwrite).to eq(false)
|
|
566
|
+
'custom_output_20251023_123456_789.png'
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Mock GimpProcessor
|
|
570
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
571
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
572
|
+
allow(gimp_double).to receive(:process).and_return('temp-processed.png')
|
|
573
|
+
|
|
574
|
+
# Mock file operations
|
|
575
|
+
allow(FileUtils).to receive(:cp)
|
|
576
|
+
|
|
577
|
+
processor = RubySpriter::Processor.new(
|
|
578
|
+
image: fixture_with_meta,
|
|
579
|
+
remove_bg: true,
|
|
580
|
+
output: 'custom_output.png',
|
|
581
|
+
overwrite: false
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
allow(processor).to receive(:check_dependencies!)
|
|
585
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
586
|
+
allow(processor).to receive(:cleanup)
|
|
587
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
588
|
+
|
|
589
|
+
result = nil
|
|
590
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
591
|
+
|
|
592
|
+
# Should return unique filename
|
|
593
|
+
expect(result[:output_file]).to match(/custom_output_\d{8}_\d{6}_\d{3}\.png$/)
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
it 'uses exact output filename when --output and --overwrite are both specified' do
|
|
597
|
+
# Mock all dependencies
|
|
598
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
599
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
600
|
+
|
|
601
|
+
# Mock ensure_unique_output to verify it's called with overwrite:true
|
|
602
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
603
|
+
expect(path).to eq('exact_output.png')
|
|
604
|
+
expect(overwrite).to eq(true)
|
|
605
|
+
'exact_output.png'
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Mock GimpProcessor
|
|
609
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
610
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
611
|
+
allow(gimp_double).to receive(:process).and_return('temp-processed.png')
|
|
612
|
+
|
|
613
|
+
# Mock file operations
|
|
614
|
+
allow(FileUtils).to receive(:cp)
|
|
615
|
+
|
|
616
|
+
processor = RubySpriter::Processor.new(
|
|
617
|
+
image: fixture_with_meta,
|
|
618
|
+
scale_percent: 50,
|
|
619
|
+
output: 'exact_output.png',
|
|
620
|
+
overwrite: true
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
allow(processor).to receive(:check_dependencies!)
|
|
624
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
625
|
+
allow(processor).to receive(:cleanup)
|
|
626
|
+
allow(processor).to receive(:gimp_path).and_return('/usr/bin/gimp')
|
|
627
|
+
|
|
628
|
+
result = nil
|
|
629
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
630
|
+
|
|
631
|
+
# Should return exact filename (no timestamp)
|
|
632
|
+
expect(result[:output_file]).to eq('exact_output.png')
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
it 'generates unique filename when using --sharpen alone without --output' do
|
|
636
|
+
# Mock all dependencies
|
|
637
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
638
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
639
|
+
|
|
640
|
+
# Mock GimpProcessor to return a sharpened file
|
|
641
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
642
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
643
|
+
allow(gimp_double).to receive(:process).and_return('input-sharpened_20251023_123456_789.png')
|
|
644
|
+
|
|
645
|
+
processor = RubySpriter::Processor.new(
|
|
646
|
+
image: fixture_with_meta,
|
|
647
|
+
sharpen: true,
|
|
648
|
+
overwrite: false
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
allow(processor).to receive(:check_dependencies!)
|
|
652
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
653
|
+
allow(processor).to receive(:cleanup)
|
|
654
|
+
|
|
655
|
+
result = nil
|
|
656
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
657
|
+
|
|
658
|
+
# Should return the uniquely-named sharpened file
|
|
659
|
+
expect(result[:output_file]).to match(/-sharpened.*\.png$/)
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
it 'overwrites sharpened file when --sharpen with --overwrite' do
|
|
663
|
+
# Mock all dependencies
|
|
664
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
665
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_readable!)
|
|
666
|
+
|
|
667
|
+
# Mock GimpProcessor - with overwrite:true, should return base filename
|
|
668
|
+
gimp_double = instance_double(RubySpriter::GimpProcessor)
|
|
669
|
+
allow(RubySpriter::GimpProcessor).to receive(:new).and_return(gimp_double)
|
|
670
|
+
allow(gimp_double).to receive(:process).and_return('input-sharpened.png')
|
|
671
|
+
|
|
672
|
+
processor = RubySpriter::Processor.new(
|
|
673
|
+
image: fixture_with_meta,
|
|
674
|
+
sharpen: true,
|
|
675
|
+
overwrite: true
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
allow(processor).to receive(:check_dependencies!)
|
|
679
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
680
|
+
allow(processor).to receive(:cleanup)
|
|
681
|
+
|
|
682
|
+
result = nil
|
|
683
|
+
expect { result = processor.run }.to output(/SUCCESS/).to_stdout
|
|
684
|
+
|
|
685
|
+
# Should return the base filename (no timestamp)
|
|
686
|
+
expect(result[:output_file]).to eq('input-sharpened.png')
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
describe '--video flag' do
|
|
692
|
+
let(:fixture_video) { File.join(__dir__, '..', 'fixtures', 'test_video.mp4') }
|
|
693
|
+
|
|
694
|
+
describe 'context-sensitive help' do
|
|
695
|
+
it 'shows video mode help with --help' do
|
|
696
|
+
output = StringIO.new
|
|
697
|
+
$stdout = output
|
|
698
|
+
|
|
699
|
+
begin
|
|
700
|
+
described_class.start(['--video', '--help'])
|
|
701
|
+
rescue SystemExit
|
|
702
|
+
# Expected
|
|
703
|
+
ensure
|
|
704
|
+
$stdout = STDOUT
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
expect(output.string).to include('Video Mode')
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
it 'shows parent-child option hierarchy in video mode help' do
|
|
711
|
+
output = StringIO.new
|
|
712
|
+
$stdout = output
|
|
713
|
+
|
|
714
|
+
begin
|
|
715
|
+
described_class.start(['--video', '--help'])
|
|
716
|
+
rescue SystemExit
|
|
717
|
+
# Expected
|
|
718
|
+
ensure
|
|
719
|
+
$stdout = STDOUT
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# Check for parent options
|
|
723
|
+
expect(output.string).to include('-s, --scale PERCENT')
|
|
724
|
+
expect(output.string).to include('-r, --remove-bg')
|
|
725
|
+
|
|
726
|
+
# Check for child options with hierarchy marker
|
|
727
|
+
expect(output.string).to include('└─ Interpolation:')
|
|
728
|
+
expect(output.string).to include('└─ Sharpen radius')
|
|
729
|
+
expect(output.string).to include('└─ Use fuzzy select')
|
|
730
|
+
expect(output.string).to include('└─ Feather radius')
|
|
731
|
+
expect(output.string).to include('└─ Grow selection')
|
|
732
|
+
|
|
733
|
+
# Check that --order mentions BOTH requirement
|
|
734
|
+
expect(output.string).to match(/order.*BOTH.*--scale.*AND.*--remove-bg/i)
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
it 'shows --sharpen as standalone option in video mode help' do
|
|
738
|
+
output = StringIO.new
|
|
739
|
+
$stdout = output
|
|
740
|
+
|
|
741
|
+
begin
|
|
742
|
+
described_class.start(['--video', '--help'])
|
|
743
|
+
rescue SystemExit
|
|
744
|
+
# Expected
|
|
745
|
+
ensure
|
|
746
|
+
$stdout = STDOUT
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
# --sharpen should be a standalone parent option (not indented under --scale)
|
|
750
|
+
expect(output.string).to match(/^ --sharpen\s+Apply unsharp mask/)
|
|
751
|
+
|
|
752
|
+
# --sharpen modifiers should be children under --sharpen
|
|
753
|
+
expect(output.string).to include('└─ Sharpen radius')
|
|
754
|
+
expect(output.string).to include('└─ Sharpen gain')
|
|
755
|
+
expect(output.string).to include('└─ Sharpen threshold')
|
|
756
|
+
|
|
757
|
+
# --interpolation should ONLY be under --scale (not under --sharpen)
|
|
758
|
+
lines = output.string.lines
|
|
759
|
+
sharpen_line_idx = lines.index { |l| l.include?('--sharpen') && l.include?('Apply unsharp mask') }
|
|
760
|
+
scale_line_idx = lines.index { |l| l.include?('--scale PERCENT') }
|
|
761
|
+
interpolation_line_idx = lines.index { |l| l.include?('└─ Interpolation') }
|
|
762
|
+
|
|
763
|
+
# Interpolation should come after scale, not after sharpen
|
|
764
|
+
expect(interpolation_line_idx).to be > scale_line_idx
|
|
765
|
+
expect(interpolation_line_idx).to be < sharpen_line_idx
|
|
766
|
+
end
|
|
767
|
+
it 'includes --by-frame flag in video mode help' do
|
|
768
|
+
output = StringIO.new
|
|
769
|
+
$stdout = output
|
|
770
|
+
|
|
771
|
+
begin
|
|
772
|
+
described_class.start(['--video', '--help'])
|
|
773
|
+
rescue SystemExit
|
|
774
|
+
# Expected
|
|
775
|
+
ensure
|
|
776
|
+
$stdout = STDOUT
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
expect(output.string).to include('--by-frame')
|
|
780
|
+
expect(output.string).to include('Remove background from each frame individually')
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
it 'shows image mode help with --help' do
|
|
784
|
+
output = StringIO.new
|
|
785
|
+
$stdout = output
|
|
786
|
+
|
|
787
|
+
begin
|
|
788
|
+
described_class.start(['--image', '--help'])
|
|
789
|
+
rescue SystemExit
|
|
790
|
+
# Expected
|
|
791
|
+
ensure
|
|
792
|
+
$stdout = STDOUT
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
expect(output.string).to include('Image Mode')
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
it 'shows consolidate mode help with --help' do
|
|
799
|
+
output = StringIO.new
|
|
800
|
+
$stdout = output
|
|
801
|
+
|
|
802
|
+
begin
|
|
803
|
+
described_class.start(['--consolidate', '--help'])
|
|
804
|
+
rescue SystemExit
|
|
805
|
+
# Expected
|
|
806
|
+
ensure
|
|
807
|
+
$stdout = STDOUT
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
expect(output.string).to include('Consolidate Mode')
|
|
811
|
+
end
|
|
812
|
+
|
|
813
|
+
it 'shows batch mode help with --help' do
|
|
814
|
+
output = StringIO.new
|
|
815
|
+
$stdout = output
|
|
816
|
+
|
|
817
|
+
begin
|
|
818
|
+
described_class.start(['--batch', '--help'])
|
|
819
|
+
rescue SystemExit
|
|
820
|
+
# Expected
|
|
821
|
+
ensure
|
|
822
|
+
$stdout = STDOUT
|
|
823
|
+
end
|
|
824
|
+
|
|
825
|
+
expect(output.string).to include('Batch Mode')
|
|
826
|
+
end
|
|
827
|
+
it 'includes --by-frame flag in batch mode help' do
|
|
828
|
+
output = StringIO.new
|
|
829
|
+
$stdout = output
|
|
830
|
+
|
|
831
|
+
begin
|
|
832
|
+
described_class.start(['--batch', '--help'])
|
|
833
|
+
rescue SystemExit
|
|
834
|
+
# Expected
|
|
835
|
+
ensure
|
|
836
|
+
$stdout = STDOUT
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
expect(output.string).to include('--by-frame')
|
|
840
|
+
expect(output.string).to include('Remove background from each frame individually')
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
it 'shows split mode help with --help' do
|
|
844
|
+
output = StringIO.new
|
|
845
|
+
$stdout = output
|
|
846
|
+
|
|
847
|
+
begin
|
|
848
|
+
described_class.start(['--split', '--help'])
|
|
849
|
+
rescue SystemExit
|
|
850
|
+
# Expected
|
|
851
|
+
ensure
|
|
852
|
+
$stdout = STDOUT
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
expect(output.string).to include('Split Mode')
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
describe 'argument parsing' do
|
|
860
|
+
it 'sets video option with --video flag' do
|
|
861
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
862
|
+
allow(processor_double).to receive(:run)
|
|
863
|
+
|
|
864
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
865
|
+
expect(options[:video]).to eq(fixture_video)
|
|
866
|
+
processor_double
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
described_class.start(['--video', fixture_video])
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
it 'supports short form -v flag' do
|
|
873
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
874
|
+
allow(processor_double).to receive(:run)
|
|
875
|
+
|
|
876
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
877
|
+
expect(options[:video]).to eq(fixture_video)
|
|
878
|
+
processor_double
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
described_class.start(['-v', fixture_video])
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
it 'accepts file path with spaces' do
|
|
885
|
+
# Create a temp file with spaces in the name for this test
|
|
886
|
+
temp_file = File.join(@test_dir, 'video with spaces.mp4')
|
|
887
|
+
FileUtils.cp(fixture_video, temp_file)
|
|
888
|
+
|
|
889
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
890
|
+
allow(processor_double).to receive(:run)
|
|
891
|
+
|
|
892
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
893
|
+
expect(options[:video]).to eq(temp_file)
|
|
894
|
+
processor_double
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
described_class.start(['--video', temp_file])
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
describe 'mutual exclusivity with other input modes' do
|
|
902
|
+
it 'cannot be used with --image' do
|
|
903
|
+
expect do
|
|
904
|
+
described_class.start(['--video', fixture_video, '--image', 'test.png'])
|
|
905
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
it 'cannot be used with --consolidate' do
|
|
909
|
+
expect do
|
|
910
|
+
described_class.start(['--video', fixture_video, '--consolidate', 'a.png,b.png'])
|
|
911
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
it 'cannot be used with --verify' do
|
|
915
|
+
expect do
|
|
916
|
+
described_class.start(['--video', fixture_video, '--verify', 'test.png'])
|
|
917
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
it 'can be used alone without error' do
|
|
921
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
922
|
+
allow(processor_double).to receive(:run)
|
|
923
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
924
|
+
|
|
925
|
+
expect do
|
|
926
|
+
described_class.start(['--video', fixture_video])
|
|
927
|
+
end.not_to raise_error
|
|
928
|
+
end
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
describe 'file validation' do
|
|
932
|
+
describe 'file existence' do
|
|
933
|
+
it 'raises error for non-existent file' do
|
|
934
|
+
expect do
|
|
935
|
+
described_class.start(['--video', 'nonexistent.mp4'])
|
|
936
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
it 'accepts existing MP4 file' do
|
|
940
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
941
|
+
allow(processor_double).to receive(:run)
|
|
942
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
943
|
+
|
|
944
|
+
expect(File.exist?(fixture_video)).to be true
|
|
945
|
+
expect do
|
|
946
|
+
described_class.start(['--video', fixture_video])
|
|
947
|
+
end.not_to raise_error
|
|
948
|
+
end
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
describe 'file extension validation' do
|
|
952
|
+
it 'accepts .mp4 extension' do
|
|
953
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
954
|
+
allow(processor_double).to receive(:run)
|
|
955
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
956
|
+
|
|
957
|
+
expect(File.extname(fixture_video)).to eq('.mp4')
|
|
958
|
+
expect do
|
|
959
|
+
described_class.start(['--video', fixture_video])
|
|
960
|
+
end.not_to raise_error
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
it 'accepts .MP4 extension (case insensitive)' do
|
|
964
|
+
# Create a temp file with uppercase extension
|
|
965
|
+
temp_file = File.join(@test_dir, 'test.MP4')
|
|
966
|
+
FileUtils.cp(fixture_video, temp_file)
|
|
967
|
+
|
|
968
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
969
|
+
allow(processor_double).to receive(:run)
|
|
970
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
971
|
+
|
|
972
|
+
expect do
|
|
973
|
+
described_class.start(['--video', temp_file])
|
|
974
|
+
end.not_to raise_error
|
|
975
|
+
end
|
|
976
|
+
|
|
977
|
+
it 'rejects .avi extension' do
|
|
978
|
+
temp_file = File.join(@test_dir, 'test.avi')
|
|
979
|
+
FileUtils.touch(temp_file)
|
|
980
|
+
|
|
981
|
+
expect do
|
|
982
|
+
described_class.start(['--video', temp_file])
|
|
983
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.avi/)
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
it 'rejects .mov extension' do
|
|
987
|
+
temp_file = File.join(@test_dir, 'test.mov')
|
|
988
|
+
FileUtils.touch(temp_file)
|
|
989
|
+
|
|
990
|
+
expect do
|
|
991
|
+
described_class.start(['--video', temp_file])
|
|
992
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.mov/)
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
it 'rejects .mkv extension' do
|
|
996
|
+
temp_file = File.join(@test_dir, 'test.mkv')
|
|
997
|
+
FileUtils.touch(temp_file)
|
|
998
|
+
|
|
999
|
+
expect do
|
|
1000
|
+
described_class.start(['--video', temp_file])
|
|
1001
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.mkv/)
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
it 'rejects .wmv extension' do
|
|
1005
|
+
temp_file = File.join(@test_dir, 'test.wmv')
|
|
1006
|
+
FileUtils.touch(temp_file)
|
|
1007
|
+
|
|
1008
|
+
expect do
|
|
1009
|
+
described_class.start(['--video', temp_file])
|
|
1010
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.wmv/)
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
it 'rejects file with no extension' do
|
|
1014
|
+
temp_file = File.join(@test_dir, 'videofile')
|
|
1015
|
+
FileUtils.touch(temp_file)
|
|
1016
|
+
|
|
1017
|
+
expect do
|
|
1018
|
+
described_class.start(['--video', temp_file])
|
|
1019
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file/)
|
|
1020
|
+
end
|
|
1021
|
+
end
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
describe 'integration with video-specific options' do
|
|
1025
|
+
it 'works with --frames option' do
|
|
1026
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1027
|
+
allow(processor_double).to receive(:run)
|
|
1028
|
+
|
|
1029
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1030
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1031
|
+
expect(options[:frame_count]).to eq(32)
|
|
1032
|
+
processor_double
|
|
1033
|
+
end
|
|
1034
|
+
|
|
1035
|
+
described_class.start(['--video', fixture_video, '--frames', '32'])
|
|
1036
|
+
end
|
|
1037
|
+
|
|
1038
|
+
it 'works with --columns option' do
|
|
1039
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1040
|
+
allow(processor_double).to receive(:run)
|
|
1041
|
+
|
|
1042
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1043
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1044
|
+
expect(options[:columns]).to eq(8)
|
|
1045
|
+
processor_double
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
described_class.start(['--video', fixture_video, '--columns', '8'])
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
it 'works with --width option' do
|
|
1052
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1053
|
+
allow(processor_double).to receive(:run)
|
|
1054
|
+
|
|
1055
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1056
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1057
|
+
expect(options[:max_width]).to eq(640)
|
|
1058
|
+
processor_double
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
described_class.start(['--video', fixture_video, '--width', '640'])
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
it 'works with --background option' do
|
|
1065
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1066
|
+
allow(processor_double).to receive(:run)
|
|
1067
|
+
|
|
1068
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1069
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1070
|
+
expect(options[:bg_color]).to eq('white')
|
|
1071
|
+
processor_double
|
|
1072
|
+
end
|
|
1073
|
+
|
|
1074
|
+
described_class.start(['--video', fixture_video, '--background', 'white'])
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
it 'works with multiple video options combined' do
|
|
1078
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1079
|
+
allow(processor_double).to receive(:run)
|
|
1080
|
+
|
|
1081
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1082
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1083
|
+
expect(options[:frame_count]).to eq(64)
|
|
1084
|
+
expect(options[:columns]).to eq(8)
|
|
1085
|
+
expect(options[:max_width]).to eq(480)
|
|
1086
|
+
expect(options[:bg_color]).to eq('white')
|
|
1087
|
+
processor_double
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
described_class.start([
|
|
1091
|
+
'--video', fixture_video,
|
|
1092
|
+
'--frames', '64',
|
|
1093
|
+
'--columns', '8',
|
|
1094
|
+
'--width', '480',
|
|
1095
|
+
'--background', 'white'
|
|
1096
|
+
])
|
|
1097
|
+
end
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
describe 'integration with processing options' do
|
|
1101
|
+
it 'works with --scale option' do
|
|
1102
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1103
|
+
allow(processor_double).to receive(:run)
|
|
1104
|
+
|
|
1105
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1106
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1107
|
+
expect(options[:scale_percent]).to eq(50)
|
|
1108
|
+
processor_double
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
described_class.start(['--video', fixture_video, '--scale', '50'])
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
it 'works with --remove-bg option' do
|
|
1115
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1116
|
+
allow(processor_double).to receive(:run)
|
|
1117
|
+
|
|
1118
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1119
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1120
|
+
expect(options[:remove_bg]).to eq(true)
|
|
1121
|
+
processor_double
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
described_class.start(['--video', fixture_video, '--remove-bg'])
|
|
1125
|
+
end
|
|
1126
|
+
|
|
1127
|
+
it 'works with --sharpen option' do
|
|
1128
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1129
|
+
allow(processor_double).to receive(:run)
|
|
1130
|
+
|
|
1131
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1132
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1133
|
+
expect(options[:sharpen]).to eq(true)
|
|
1134
|
+
processor_double
|
|
1135
|
+
end
|
|
1136
|
+
|
|
1137
|
+
described_class.start(['--video', fixture_video, '--sharpen'])
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1140
|
+
it 'works with --interpolation option' do
|
|
1141
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1142
|
+
allow(processor_double).to receive(:run)
|
|
1143
|
+
|
|
1144
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1145
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1146
|
+
expect(options[:scale_interpolation]).to eq('lohalo')
|
|
1147
|
+
processor_double
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
described_class.start(['--video', fixture_video, '--interpolation', 'lohalo'])
|
|
1151
|
+
end
|
|
1152
|
+
|
|
1153
|
+
it 'works with all options combined' do
|
|
1154
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1155
|
+
allow(processor_double).to receive(:run)
|
|
1156
|
+
|
|
1157
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1158
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1159
|
+
expect(options[:frame_count]).to eq(32)
|
|
1160
|
+
expect(options[:columns]).to eq(8)
|
|
1161
|
+
expect(options[:scale_percent]).to eq(50)
|
|
1162
|
+
expect(options[:remove_bg]).to eq(true)
|
|
1163
|
+
expect(options[:sharpen]).to eq(true)
|
|
1164
|
+
expect(options[:scale_interpolation]).to eq('nohalo')
|
|
1165
|
+
processor_double
|
|
1166
|
+
end
|
|
1167
|
+
|
|
1168
|
+
described_class.start([
|
|
1169
|
+
'--video', fixture_video,
|
|
1170
|
+
'--frames', '32',
|
|
1171
|
+
'--columns', '8',
|
|
1172
|
+
'--scale', '50',
|
|
1173
|
+
'--remove-bg',
|
|
1174
|
+
'--sharpen',
|
|
1175
|
+
'--interpolation', 'nohalo'
|
|
1176
|
+
])
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
it 'works with --output option' do
|
|
1180
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1181
|
+
allow(processor_double).to receive(:run)
|
|
1182
|
+
|
|
1183
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1184
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1185
|
+
expect(options[:output]).to eq('custom_spritesheet.png')
|
|
1186
|
+
processor_double
|
|
1187
|
+
end
|
|
1188
|
+
|
|
1189
|
+
described_class.start(['--video', fixture_video, '--output', 'custom_spritesheet.png'])
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1192
|
+
it 'works with --save-frames option' do
|
|
1193
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1194
|
+
allow(processor_double).to receive(:run)
|
|
1195
|
+
|
|
1196
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1197
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1198
|
+
expect(options[:save_frames]).to eq(true)
|
|
1199
|
+
processor_double
|
|
1200
|
+
end
|
|
1201
|
+
|
|
1202
|
+
described_class.start(['--video', fixture_video, '--save-frames'])
|
|
1203
|
+
end
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1206
|
+
describe 'preset configurations' do
|
|
1207
|
+
it 'works with --preset thumbnail' do
|
|
1208
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1209
|
+
allow(processor_double).to receive(:run)
|
|
1210
|
+
|
|
1211
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1212
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1213
|
+
expect(options[:columns]).to eq(3)
|
|
1214
|
+
expect(options[:frame_count]).to eq(9)
|
|
1215
|
+
expect(options[:max_width]).to eq(240)
|
|
1216
|
+
processor_double
|
|
1217
|
+
end
|
|
1218
|
+
|
|
1219
|
+
described_class.start(['--video', fixture_video, '--preset', 'thumbnail'])
|
|
1220
|
+
end
|
|
1221
|
+
|
|
1222
|
+
it 'works with --preset preview' do
|
|
1223
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1224
|
+
allow(processor_double).to receive(:run)
|
|
1225
|
+
|
|
1226
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1227
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1228
|
+
expect(options[:columns]).to eq(4)
|
|
1229
|
+
expect(options[:frame_count]).to eq(16)
|
|
1230
|
+
expect(options[:max_width]).to eq(400)
|
|
1231
|
+
processor_double
|
|
1232
|
+
end
|
|
1233
|
+
|
|
1234
|
+
described_class.start(['--video', fixture_video, '--preset', 'preview'])
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
it 'works with --preset detailed' do
|
|
1238
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1239
|
+
allow(processor_double).to receive(:run)
|
|
1240
|
+
|
|
1241
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1242
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1243
|
+
expect(options[:columns]).to eq(10)
|
|
1244
|
+
expect(options[:frame_count]).to eq(50)
|
|
1245
|
+
expect(options[:max_width]).to eq(320)
|
|
1246
|
+
processor_double
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
described_class.start(['--video', fixture_video, '--preset', 'detailed'])
|
|
1250
|
+
end
|
|
1251
|
+
|
|
1252
|
+
it 'works with --preset contact' do
|
|
1253
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1254
|
+
allow(processor_double).to receive(:run)
|
|
1255
|
+
|
|
1256
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1257
|
+
expect(options[:video]).to eq(fixture_video)
|
|
1258
|
+
expect(options[:columns]).to eq(8)
|
|
1259
|
+
expect(options[:frame_count]).to eq(64)
|
|
1260
|
+
expect(options[:max_width]).to eq(160)
|
|
1261
|
+
processor_double
|
|
1262
|
+
end
|
|
1263
|
+
|
|
1264
|
+
described_class.start(['--video', fixture_video, '--preset', 'contact'])
|
|
1265
|
+
end
|
|
1266
|
+
end
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
describe '--consolidate flag' do
|
|
1270
|
+
# Real spritesheets generated from test_video.mp4 using --video
|
|
1271
|
+
# These demonstrate the actual workflow: --video creates spritesheets, --consolidate combines them
|
|
1272
|
+
let(:spritesheet_4x2) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x2.png') } # 2 cols, 2 rows, 4 frames
|
|
1273
|
+
let(:spritesheet_6x2) { File.join(__dir__, '..', 'fixtures', 'spritesheet_6x2.png') } # 2 cols, 3 rows, 6 frames
|
|
1274
|
+
let(:spritesheet_4x4) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x4.png') } # 4 cols, 1 row, 4 frames (different columns)
|
|
1275
|
+
|
|
1276
|
+
# Generic PNG fixtures for edge case testing
|
|
1277
|
+
let(:fixture_with_meta) { File.join(__dir__, '..', 'fixtures', 'spritesheet_with_metadata.png') }
|
|
1278
|
+
let(:fixture_without_meta) { File.join(__dir__, '..', 'fixtures', 'image_without_metadata.png') }
|
|
1279
|
+
|
|
1280
|
+
describe 'argument parsing' do
|
|
1281
|
+
it 'accepts comma-separated list of files' do
|
|
1282
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1283
|
+
allow(processor_double).to receive(:run)
|
|
1284
|
+
|
|
1285
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1286
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1287
|
+
processor_double
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1291
|
+
end
|
|
1292
|
+
|
|
1293
|
+
it 'accepts three or more files' do
|
|
1294
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1295
|
+
allow(processor_double).to receive(:run)
|
|
1296
|
+
|
|
1297
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1298
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2, spritesheet_4x4])
|
|
1299
|
+
processor_double
|
|
1300
|
+
end
|
|
1301
|
+
|
|
1302
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2},#{spritesheet_4x4}"])
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
it 'accepts file paths with spaces' do
|
|
1306
|
+
# Create temp files with spaces in names
|
|
1307
|
+
temp_file1 = File.join(@test_dir, 'file with spaces 1.png')
|
|
1308
|
+
temp_file2 = File.join(@test_dir, 'file with spaces 2.png')
|
|
1309
|
+
FileUtils.cp(spritesheet_4x2, temp_file1)
|
|
1310
|
+
FileUtils.cp(spritesheet_6x2, temp_file2)
|
|
1311
|
+
|
|
1312
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1313
|
+
allow(processor_double).to receive(:run)
|
|
1314
|
+
|
|
1315
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1316
|
+
expect(options[:consolidate]).to eq([temp_file1, temp_file2])
|
|
1317
|
+
processor_double
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1320
|
+
described_class.start(['--consolidate', "#{temp_file1},#{temp_file2}"])
|
|
1321
|
+
end
|
|
1322
|
+
end
|
|
1323
|
+
|
|
1324
|
+
describe 'minimum file count validation' do
|
|
1325
|
+
it 'requires at least 2 files' do
|
|
1326
|
+
expect do
|
|
1327
|
+
described_class.start(['--consolidate', spritesheet_4x2])
|
|
1328
|
+
end.to raise_error(RubySpriter::ValidationError, /requires at least 2 files/)
|
|
1329
|
+
end
|
|
1330
|
+
|
|
1331
|
+
it 'accepts exactly 2 files' do
|
|
1332
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1333
|
+
allow(processor_double).to receive(:run)
|
|
1334
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1335
|
+
|
|
1336
|
+
expect do
|
|
1337
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1338
|
+
end.not_to raise_error
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
it 'accepts more than 2 files' do
|
|
1342
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1343
|
+
allow(processor_double).to receive(:run)
|
|
1344
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1345
|
+
|
|
1346
|
+
expect do
|
|
1347
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2},#{spritesheet_4x4}"])
|
|
1348
|
+
end.not_to raise_error
|
|
1349
|
+
end
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1352
|
+
describe 'mutual exclusivity with other input modes' do
|
|
1353
|
+
it 'cannot be used with --video' do
|
|
1354
|
+
expect do
|
|
1355
|
+
described_class.start(['--video', 'test.mp4', '--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1356
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
1357
|
+
end
|
|
1358
|
+
|
|
1359
|
+
it 'cannot be used with --image' do
|
|
1360
|
+
expect do
|
|
1361
|
+
described_class.start(['--image', spritesheet_4x2, '--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1362
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
it 'cannot be used with --verify' do
|
|
1366
|
+
expect do
|
|
1367
|
+
described_class.start(['--verify', spritesheet_4x2, '--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1368
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
1369
|
+
end
|
|
1370
|
+
|
|
1371
|
+
it 'can be used alone without error' do
|
|
1372
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1373
|
+
allow(processor_double).to receive(:run)
|
|
1374
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1375
|
+
|
|
1376
|
+
expect do
|
|
1377
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1378
|
+
end.not_to raise_error
|
|
1379
|
+
end
|
|
1380
|
+
end
|
|
1381
|
+
|
|
1382
|
+
describe 'file validation' do
|
|
1383
|
+
describe 'file existence' do
|
|
1384
|
+
it 'raises error if first file does not exist' do
|
|
1385
|
+
expect do
|
|
1386
|
+
described_class.start(['--consolidate', "nonexistent1.png,#{spritesheet_4x2}"])
|
|
1387
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
1388
|
+
end
|
|
1389
|
+
|
|
1390
|
+
it 'raises error if second file does not exist' do
|
|
1391
|
+
expect do
|
|
1392
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},nonexistent2.png"])
|
|
1393
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
it 'raises error if any file in list does not exist' do
|
|
1397
|
+
expect do
|
|
1398
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},nonexistent.png,#{spritesheet_6x2}"])
|
|
1399
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
1400
|
+
end
|
|
1401
|
+
|
|
1402
|
+
it 'accepts all existing spritesheet files' do
|
|
1403
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1404
|
+
allow(processor_double).to receive(:run)
|
|
1405
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1406
|
+
|
|
1407
|
+
expect(File.exist?(spritesheet_4x2)).to be true
|
|
1408
|
+
expect(File.exist?(spritesheet_6x2)).to be true
|
|
1409
|
+
|
|
1410
|
+
expect do
|
|
1411
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1412
|
+
end.not_to raise_error
|
|
1413
|
+
end
|
|
1414
|
+
end
|
|
1415
|
+
|
|
1416
|
+
describe 'file extension validation' do
|
|
1417
|
+
it 'accepts all .png spritesheet files' do
|
|
1418
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1419
|
+
allow(processor_double).to receive(:run)
|
|
1420
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1421
|
+
|
|
1422
|
+
expect do
|
|
1423
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
1424
|
+
end.not_to raise_error
|
|
1425
|
+
end
|
|
1426
|
+
|
|
1427
|
+
it 'accepts .PNG extension (case insensitive)' do
|
|
1428
|
+
temp_file1 = File.join(@test_dir, 'test1.PNG')
|
|
1429
|
+
temp_file2 = File.join(@test_dir, 'test2.PNG')
|
|
1430
|
+
FileUtils.cp(spritesheet_4x2, temp_file1)
|
|
1431
|
+
FileUtils.cp(spritesheet_6x2, temp_file2)
|
|
1432
|
+
|
|
1433
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1434
|
+
allow(processor_double).to receive(:run)
|
|
1435
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
1436
|
+
|
|
1437
|
+
expect do
|
|
1438
|
+
described_class.start(['--consolidate', "#{temp_file1},#{temp_file2}"])
|
|
1439
|
+
end.not_to raise_error
|
|
1440
|
+
end
|
|
1441
|
+
|
|
1442
|
+
it 'rejects files with .jpg extension' do
|
|
1443
|
+
temp_file = File.join(@test_dir, 'test.jpg')
|
|
1444
|
+
FileUtils.touch(temp_file)
|
|
1445
|
+
|
|
1446
|
+
expect do
|
|
1447
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file}"])
|
|
1448
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file, got: \.jpg/)
|
|
1449
|
+
end
|
|
1450
|
+
|
|
1451
|
+
it 'rejects files with .mp4 extension' do
|
|
1452
|
+
temp_file = File.join(@test_dir, 'test.mp4')
|
|
1453
|
+
FileUtils.touch(temp_file)
|
|
1454
|
+
|
|
1455
|
+
expect do
|
|
1456
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file}"])
|
|
1457
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file, got: \.mp4/)
|
|
1458
|
+
end
|
|
1459
|
+
|
|
1460
|
+
it 'rejects files with no extension' do
|
|
1461
|
+
temp_file = File.join(@test_dir, 'noextension')
|
|
1462
|
+
FileUtils.touch(temp_file)
|
|
1463
|
+
|
|
1464
|
+
expect do
|
|
1465
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file}"])
|
|
1466
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file/)
|
|
1467
|
+
end
|
|
1468
|
+
|
|
1469
|
+
it 'validates all files in the list' do
|
|
1470
|
+
temp_file1 = File.join(@test_dir, 'test1.jpg')
|
|
1471
|
+
temp_file2 = File.join(@test_dir, 'test2.gif')
|
|
1472
|
+
FileUtils.touch(temp_file1)
|
|
1473
|
+
FileUtils.touch(temp_file2)
|
|
1474
|
+
|
|
1475
|
+
# Should fail on the first non-PNG file
|
|
1476
|
+
expect do
|
|
1477
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file1},#{temp_file2}"])
|
|
1478
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file/)
|
|
1479
|
+
end
|
|
1480
|
+
end
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
describe 'consolidation-specific options' do
|
|
1484
|
+
it 'works with --validate-columns flag (default true)' do
|
|
1485
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1486
|
+
allow(processor_double).to receive(:run)
|
|
1487
|
+
|
|
1488
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1489
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1490
|
+
expect(options[:validate_columns]).to eq(true)
|
|
1491
|
+
processor_double
|
|
1492
|
+
end
|
|
1493
|
+
|
|
1494
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--validate-columns'])
|
|
1495
|
+
end
|
|
1496
|
+
|
|
1497
|
+
it 'works with --no-validate-columns flag' do
|
|
1498
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1499
|
+
allow(processor_double).to receive(:run)
|
|
1500
|
+
|
|
1501
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1502
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1503
|
+
expect(options[:validate_columns]).to eq(false)
|
|
1504
|
+
processor_double
|
|
1505
|
+
end
|
|
1506
|
+
|
|
1507
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--no-validate-columns'])
|
|
1508
|
+
end
|
|
1509
|
+
end
|
|
1510
|
+
|
|
1511
|
+
describe 'integration with other options' do
|
|
1512
|
+
it 'works with --output option' do
|
|
1513
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1514
|
+
allow(processor_double).to receive(:run)
|
|
1515
|
+
|
|
1516
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1517
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1518
|
+
expect(options[:output]).to eq('consolidated_output.png')
|
|
1519
|
+
processor_double
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1522
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--output', 'consolidated_output.png'])
|
|
1523
|
+
end
|
|
1524
|
+
|
|
1525
|
+
it 'works with --debug option' do
|
|
1526
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1527
|
+
allow(processor_double).to receive(:run)
|
|
1528
|
+
|
|
1529
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1530
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1531
|
+
expect(options[:debug]).to eq(true)
|
|
1532
|
+
expect(options[:keep_temp]).to eq(true)
|
|
1533
|
+
processor_double
|
|
1534
|
+
end
|
|
1535
|
+
|
|
1536
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--debug'])
|
|
1537
|
+
end
|
|
1538
|
+
|
|
1539
|
+
it 'works with multiple options combined' do
|
|
1540
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1541
|
+
allow(processor_double).to receive(:run)
|
|
1542
|
+
|
|
1543
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1544
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1545
|
+
expect(options[:validate_columns]).to eq(false)
|
|
1546
|
+
expect(options[:output]).to eq('combined.png')
|
|
1547
|
+
expect(options[:debug]).to eq(true)
|
|
1548
|
+
processor_double
|
|
1549
|
+
end
|
|
1550
|
+
|
|
1551
|
+
described_class.start([
|
|
1552
|
+
'--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}",
|
|
1553
|
+
'--no-validate-columns',
|
|
1554
|
+
'--output', 'combined.png',
|
|
1555
|
+
'--debug'
|
|
1556
|
+
])
|
|
1557
|
+
end
|
|
1558
|
+
|
|
1559
|
+
it 'works with --overwrite option' do
|
|
1560
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1561
|
+
allow(processor_double).to receive(:run)
|
|
1562
|
+
|
|
1563
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1564
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1565
|
+
expect(options[:overwrite]).to eq(true)
|
|
1566
|
+
processor_double
|
|
1567
|
+
end
|
|
1568
|
+
|
|
1569
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--overwrite'])
|
|
1570
|
+
end
|
|
1571
|
+
end
|
|
1572
|
+
|
|
1573
|
+
describe 'default output filename behavior' do
|
|
1574
|
+
it 'generates consolidated_spritesheet.png when no --output specified' do
|
|
1575
|
+
# Mock all the dependencies
|
|
1576
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
1577
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
1578
|
+
expect(path).to eq('consolidated_spritesheet.png')
|
|
1579
|
+
expect(overwrite).to eq(false)
|
|
1580
|
+
'consolidated_spritesheet.png'
|
|
1581
|
+
end
|
|
1582
|
+
|
|
1583
|
+
consolidator_double = instance_double(RubySpriter::Consolidator)
|
|
1584
|
+
allow(RubySpriter::Consolidator).to receive(:new).and_return(consolidator_double)
|
|
1585
|
+
allow(consolidator_double).to receive(:consolidate).and_return({
|
|
1586
|
+
output_file: 'consolidated_spritesheet.png',
|
|
1587
|
+
columns: 2,
|
|
1588
|
+
rows: 4,
|
|
1589
|
+
frames: 8
|
|
1590
|
+
})
|
|
1591
|
+
|
|
1592
|
+
processor = RubySpriter::Processor.new(
|
|
1593
|
+
consolidate_mode: true,
|
|
1594
|
+
consolidate: [spritesheet_4x2, spritesheet_6x2],
|
|
1595
|
+
overwrite: false
|
|
1596
|
+
)
|
|
1597
|
+
|
|
1598
|
+
allow(processor).to receive(:check_dependencies!)
|
|
1599
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
1600
|
+
allow(processor).to receive(:cleanup)
|
|
1601
|
+
|
|
1602
|
+
# Capture output to suppress console messages
|
|
1603
|
+
expect { processor.run }.to output(/SUCCESS/).to_stdout
|
|
1604
|
+
end
|
|
1605
|
+
|
|
1606
|
+
it 'respects --overwrite flag with default filename' do
|
|
1607
|
+
# Mock all the dependencies
|
|
1608
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:validate_exists!)
|
|
1609
|
+
allow(RubySpriter::Utils::FileHelper).to receive(:ensure_unique_output) do |path, overwrite:|
|
|
1610
|
+
expect(path).to eq('consolidated_spritesheet.png')
|
|
1611
|
+
expect(overwrite).to eq(true)
|
|
1612
|
+
'consolidated_spritesheet.png'
|
|
1613
|
+
end
|
|
1614
|
+
|
|
1615
|
+
consolidator_double = instance_double(RubySpriter::Consolidator)
|
|
1616
|
+
allow(RubySpriter::Consolidator).to receive(:new).and_return(consolidator_double)
|
|
1617
|
+
allow(consolidator_double).to receive(:consolidate).and_return({
|
|
1618
|
+
output_file: 'consolidated_spritesheet.png',
|
|
1619
|
+
columns: 2,
|
|
1620
|
+
rows: 4,
|
|
1621
|
+
frames: 8
|
|
1622
|
+
})
|
|
1623
|
+
|
|
1624
|
+
processor = RubySpriter::Processor.new(
|
|
1625
|
+
consolidate_mode: true,
|
|
1626
|
+
consolidate: [spritesheet_4x2, spritesheet_6x2],
|
|
1627
|
+
overwrite: true
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
allow(processor).to receive(:check_dependencies!)
|
|
1631
|
+
allow(processor).to receive(:setup_temp_directory)
|
|
1632
|
+
allow(processor).to receive(:cleanup)
|
|
1633
|
+
|
|
1634
|
+
# Capture output to suppress console messages
|
|
1635
|
+
expect { processor.run }.to output(/SUCCESS/).to_stdout
|
|
1636
|
+
end
|
|
1637
|
+
end
|
|
1638
|
+
|
|
1639
|
+
describe 'directory-based consolidation' do
|
|
1640
|
+
let(:test_dir) { File.join(@test_dir, 'consolidate_dir') }
|
|
1641
|
+
|
|
1642
|
+
before do
|
|
1643
|
+
FileUtils.mkdir_p(test_dir)
|
|
1644
|
+
# Copy fixture spritesheets to test directory
|
|
1645
|
+
FileUtils.cp(spritesheet_4x2, File.join(test_dir, 'sprite1.png'))
|
|
1646
|
+
FileUtils.cp(spritesheet_6x2, File.join(test_dir, 'sprite2.png'))
|
|
1647
|
+
end
|
|
1648
|
+
|
|
1649
|
+
it 'accepts --dir option with --consolidate' do
|
|
1650
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1651
|
+
allow(processor_double).to receive(:run)
|
|
1652
|
+
|
|
1653
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1654
|
+
expect(options[:consolidate]).to be_nil
|
|
1655
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1656
|
+
processor_double
|
|
1657
|
+
end
|
|
1658
|
+
|
|
1659
|
+
described_class.start(['--consolidate', '--dir', test_dir])
|
|
1660
|
+
end
|
|
1661
|
+
|
|
1662
|
+
it 'validates directory exists' do
|
|
1663
|
+
expect do
|
|
1664
|
+
described_class.start(['--consolidate', '--dir', 'nonexistent_directory'])
|
|
1665
|
+
end.to raise_error(RubySpriter::ValidationError, /Directory not found/)
|
|
1666
|
+
end
|
|
1667
|
+
|
|
1668
|
+
it 'cannot use --dir with comma-separated file list' do
|
|
1669
|
+
expect do
|
|
1670
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--dir', test_dir])
|
|
1671
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use --dir with comma-separated file list/)
|
|
1672
|
+
end
|
|
1673
|
+
|
|
1674
|
+
it 'works with --output option' do
|
|
1675
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1676
|
+
allow(processor_double).to receive(:run)
|
|
1677
|
+
|
|
1678
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1679
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1680
|
+
expect(options[:output]).to eq('custom_output.png')
|
|
1681
|
+
processor_double
|
|
1682
|
+
end
|
|
1683
|
+
|
|
1684
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--output', 'custom_output.png'])
|
|
1685
|
+
end
|
|
1686
|
+
|
|
1687
|
+
it 'works with --outputdir option' do
|
|
1688
|
+
output_dir = File.join(@test_dir, 'output')
|
|
1689
|
+
FileUtils.mkdir_p(output_dir)
|
|
1690
|
+
|
|
1691
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1692
|
+
allow(processor_double).to receive(:run)
|
|
1693
|
+
|
|
1694
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1695
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1696
|
+
expect(options[:outputdir]).to eq(output_dir)
|
|
1697
|
+
processor_double
|
|
1698
|
+
end
|
|
1699
|
+
|
|
1700
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--outputdir', output_dir])
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1703
|
+
it 'works with --overwrite option' do
|
|
1704
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1705
|
+
allow(processor_double).to receive(:run)
|
|
1706
|
+
|
|
1707
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1708
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1709
|
+
expect(options[:overwrite]).to eq(true)
|
|
1710
|
+
processor_double
|
|
1711
|
+
end
|
|
1712
|
+
|
|
1713
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--overwrite'])
|
|
1714
|
+
end
|
|
1715
|
+
|
|
1716
|
+
it 'works with --max-compress option' do
|
|
1717
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1718
|
+
allow(processor_double).to receive(:run)
|
|
1719
|
+
|
|
1720
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1721
|
+
expect(options[:dir]).to eq(test_dir)
|
|
1722
|
+
expect(options[:max_compress]).to eq(true)
|
|
1723
|
+
processor_double
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
described_class.start(['--consolidate', '--dir', test_dir, '--max-compress'])
|
|
1727
|
+
end
|
|
1728
|
+
end
|
|
1729
|
+
end
|
|
1730
|
+
|
|
1731
|
+
describe 'error handling' do
|
|
1732
|
+
describe 'invalid option' do
|
|
1733
|
+
it 'displays error message for invalid option' do
|
|
1734
|
+
expect do
|
|
1735
|
+
expect { described_class.start(['--invalid-option']) }
|
|
1736
|
+
.to output(/Error:.*invalid/).to_stdout
|
|
1737
|
+
end.to raise_error(SystemExit) { |error|
|
|
1738
|
+
expect(error.status).to eq(1)
|
|
1739
|
+
}
|
|
1740
|
+
end
|
|
1741
|
+
|
|
1742
|
+
it 'suggests using --help' do
|
|
1743
|
+
expect do
|
|
1744
|
+
expect { described_class.start(['--invalid-option']) }
|
|
1745
|
+
.to output(/Use --help for usage information/).to_stdout
|
|
1746
|
+
end.to raise_error(SystemExit)
|
|
1747
|
+
end
|
|
1748
|
+
end
|
|
1749
|
+
|
|
1750
|
+
describe '--split option' do
|
|
1751
|
+
it 'parses split option with R:C format' do
|
|
1752
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1753
|
+
allow(processor_double).to receive(:run)
|
|
1754
|
+
|
|
1755
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1756
|
+
expect(options[:split]).to eq('4:4')
|
|
1757
|
+
processor_double
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
described_class.start(['--image', 'test.png', '--split', '4:4'])
|
|
1761
|
+
end
|
|
1762
|
+
end
|
|
1763
|
+
|
|
1764
|
+
describe '--override-md option' do
|
|
1765
|
+
it 'sets override_md option to true' do
|
|
1766
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1767
|
+
allow(processor_double).to receive(:run)
|
|
1768
|
+
|
|
1769
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1770
|
+
expect(options[:override_md]).to eq(true)
|
|
1771
|
+
processor_double
|
|
1772
|
+
end
|
|
1773
|
+
|
|
1774
|
+
described_class.start(['--image', 'test.png', '--split', '4:4', '--override-md'])
|
|
1775
|
+
end
|
|
1776
|
+
end
|
|
1777
|
+
|
|
1778
|
+
describe '--extract option' do
|
|
1779
|
+
it 'parses extract option with comma-separated frame numbers' do
|
|
1780
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1781
|
+
allow(processor_double).to receive(:run)
|
|
1782
|
+
|
|
1783
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1784
|
+
expect(options[:extract]).to eq('1,2,4,5,8')
|
|
1785
|
+
processor_double
|
|
1786
|
+
end
|
|
1787
|
+
|
|
1788
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,4,5,8'])
|
|
1789
|
+
end
|
|
1790
|
+
|
|
1791
|
+
it 'allows duplicate frame numbers' do
|
|
1792
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1793
|
+
allow(processor_double).to receive(:run)
|
|
1794
|
+
|
|
1795
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1796
|
+
expect(options[:extract]).to eq('1,1,2,2,3,3')
|
|
1797
|
+
processor_double
|
|
1798
|
+
end
|
|
1799
|
+
|
|
1800
|
+
described_class.start(['--image', 'test.png', '--extract', '1,1,2,2,3,3'])
|
|
1801
|
+
end
|
|
1802
|
+
|
|
1803
|
+
it 'cannot be used with --split' do
|
|
1804
|
+
expect do
|
|
1805
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--split', '4:4'])
|
|
1806
|
+
end.to raise_error(RubySpriter::ValidationError, /--extract and --split are mutually exclusive/)
|
|
1807
|
+
end
|
|
1808
|
+
end
|
|
1809
|
+
|
|
1810
|
+
describe '--columns option' do
|
|
1811
|
+
it 'parses columns option for extraction grid' do
|
|
1812
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1813
|
+
allow(processor_double).to receive(:run)
|
|
1814
|
+
|
|
1815
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1816
|
+
expect(options[:columns]).to eq(3)
|
|
1817
|
+
processor_double
|
|
1818
|
+
end
|
|
1819
|
+
|
|
1820
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--columns', '3'])
|
|
1821
|
+
end
|
|
1822
|
+
|
|
1823
|
+
it 'works without --extract (for future use)' do
|
|
1824
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1825
|
+
allow(processor_double).to receive(:run)
|
|
1826
|
+
|
|
1827
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1828
|
+
expect(options[:columns]).to eq(5)
|
|
1829
|
+
processor_double
|
|
1830
|
+
end
|
|
1831
|
+
|
|
1832
|
+
described_class.start(['--image', 'test.png', '--columns', '5'])
|
|
1833
|
+
end
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1836
|
+
describe '--save-frames option' do
|
|
1837
|
+
it 'sets save_frames option to true' do
|
|
1838
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1839
|
+
allow(processor_double).to receive(:run)
|
|
1840
|
+
|
|
1841
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1842
|
+
expect(options[:save_frames]).to eq(true)
|
|
1843
|
+
processor_double
|
|
1844
|
+
end
|
|
1845
|
+
|
|
1846
|
+
described_class.start(['--image', 'test.png', '--extract', '1,2,3', '--save-frames'])
|
|
1847
|
+
end
|
|
1848
|
+
|
|
1849
|
+
it 'can be used without --extract' do
|
|
1850
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1851
|
+
allow(processor_double).to receive(:run)
|
|
1852
|
+
|
|
1853
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1854
|
+
expect(options[:save_frames]).to eq(true)
|
|
1855
|
+
processor_double
|
|
1856
|
+
end
|
|
1857
|
+
|
|
1858
|
+
described_class.start(['--image', 'test.png', '--split', '4:4', '--save-frames'])
|
|
1859
|
+
end
|
|
1860
|
+
end
|
|
1861
|
+
|
|
1862
|
+
describe '--add-meta option' do
|
|
1863
|
+
it 'parses add-meta option with R:C format' do
|
|
1864
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1865
|
+
allow(processor_double).to receive(:run)
|
|
1866
|
+
|
|
1867
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1868
|
+
expect(options[:add_meta]).to eq('4:4')
|
|
1869
|
+
processor_double
|
|
1870
|
+
end
|
|
1871
|
+
|
|
1872
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4'])
|
|
1873
|
+
end
|
|
1874
|
+
|
|
1875
|
+
it 'cannot be combined with --scale' do
|
|
1876
|
+
expect do
|
|
1877
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--scale', '50'])
|
|
1878
|
+
end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
|
|
1879
|
+
end
|
|
1880
|
+
|
|
1881
|
+
it 'cannot be combined with --remove-bg' do
|
|
1882
|
+
expect do
|
|
1883
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--remove-bg'])
|
|
1884
|
+
end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
|
|
1885
|
+
end
|
|
1886
|
+
|
|
1887
|
+
it 'cannot be combined with --sharpen' do
|
|
1888
|
+
expect do
|
|
1889
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--sharpen'])
|
|
1890
|
+
end.to raise_error(RubySpriter::ValidationError, /--add-meta cannot be combined with processing options/)
|
|
1891
|
+
end
|
|
1892
|
+
end
|
|
1893
|
+
|
|
1894
|
+
describe '--overwrite-meta option' do
|
|
1895
|
+
it 'sets overwrite_meta option to true' do
|
|
1896
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1897
|
+
allow(processor_double).to receive(:run)
|
|
1898
|
+
|
|
1899
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1900
|
+
expect(options[:overwrite_meta]).to eq(true)
|
|
1901
|
+
processor_double
|
|
1902
|
+
end
|
|
1903
|
+
|
|
1904
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--overwrite-meta'])
|
|
1905
|
+
end
|
|
1906
|
+
end
|
|
1907
|
+
|
|
1908
|
+
describe '--frames option for partial grids' do
|
|
1909
|
+
it 'parses frames option with integer value' do
|
|
1910
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1911
|
+
allow(processor_double).to receive(:run)
|
|
1912
|
+
|
|
1913
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1914
|
+
expect(options[:frame_count]).to eq(14)
|
|
1915
|
+
processor_double
|
|
1916
|
+
end
|
|
1917
|
+
|
|
1918
|
+
described_class.start(['--image', 'test.png', '--add-meta', '4:4', '--frames', '14'])
|
|
1919
|
+
end
|
|
1920
|
+
end
|
|
1921
|
+
|
|
1922
|
+
describe '--by-frame flag validation' do
|
|
1923
|
+
context 'when --by-frame is used without --video or --batch' do
|
|
1924
|
+
it 'raises ValidationError when used with --image' do
|
|
1925
|
+
expect {
|
|
1926
|
+
described_class.start(['--image', 'sprite.png', '--remove-bg', '--by-frame'])
|
|
1927
|
+
}.to raise_error(RubySpriter::ValidationError, /--by-frame requires --video or --batch/)
|
|
1928
|
+
end
|
|
1929
|
+
|
|
1930
|
+
it 'raises ValidationError when used alone with --remove-bg' do
|
|
1931
|
+
expect {
|
|
1932
|
+
described_class.start(['--remove-bg', '--by-frame'])
|
|
1933
|
+
}.to raise_error(RubySpriter::ValidationError, /--by-frame requires --video or --batch/)
|
|
1934
|
+
end
|
|
1935
|
+
end
|
|
1936
|
+
|
|
1937
|
+
context 'when --by-frame is used without --remove-bg' do
|
|
1938
|
+
it 'raises ValidationError' do
|
|
1939
|
+
expect {
|
|
1940
|
+
described_class.start(['--video', 'input.mp4', '--by-frame'])
|
|
1941
|
+
}.to raise_error(RubySpriter::ValidationError, /--by-frame requires --remove-bg/)
|
|
1942
|
+
end
|
|
1943
|
+
end
|
|
1944
|
+
|
|
1945
|
+
context 'when --by-frame is used correctly' do
|
|
1946
|
+
it 'accepts --by-frame with --video and --remove-bg' do
|
|
1947
|
+
# Mock the entire Processor to prevent file validation
|
|
1948
|
+
mock_processor = instance_double(RubySpriter::Processor)
|
|
1949
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(mock_processor)
|
|
1950
|
+
allow(mock_processor).to receive(:run)
|
|
1951
|
+
|
|
1952
|
+
expect {
|
|
1953
|
+
described_class.start(['--video', 'input.mp4', '--remove-bg', '--by-frame'])
|
|
1954
|
+
}.not_to raise_error
|
|
1955
|
+
end
|
|
1956
|
+
|
|
1957
|
+
it 'accepts --by-frame with --batch and --remove-bg' do
|
|
1958
|
+
# Mock the entire Processor to prevent dependency checking
|
|
1959
|
+
mock_processor = instance_double(RubySpriter::Processor)
|
|
1960
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(mock_processor)
|
|
1961
|
+
allow(mock_processor).to receive(:run)
|
|
1962
|
+
|
|
1963
|
+
expect {
|
|
1964
|
+
described_class.start(['--batch', '--dir', 'videos/', '--remove-bg', '--by-frame'])
|
|
1965
|
+
}.not_to raise_error
|
|
1966
|
+
end
|
|
1967
|
+
end
|
|
1968
|
+
end
|
|
1969
|
+
|
|
1970
|
+
describe '--cleanup-cells flag validation' do
|
|
1971
|
+
it 'requires --remove-bg flag' do
|
|
1972
|
+
expect do
|
|
1973
|
+
described_class.start(['--video', 'test.mp4', '--cleanup-cells'])
|
|
1974
|
+
end.to raise_error(RubySpriter::ValidationError, /requires --remove-bg/)
|
|
1975
|
+
end
|
|
1976
|
+
|
|
1977
|
+
it 'cannot be used with --by-frame' do
|
|
1978
|
+
expect do
|
|
1979
|
+
described_class.start(['--video', 'test.mp4', '--remove-bg', '--by-frame', '--cleanup-cells'])
|
|
1980
|
+
end.to raise_error(RubySpriter::ValidationError, /cannot be used with --by-frame/)
|
|
1981
|
+
end
|
|
1982
|
+
|
|
1983
|
+
it 'requires video or batch mode' do
|
|
1984
|
+
# Create a temporary image file for testing
|
|
1985
|
+
temp_dir = Dir.mktmpdir
|
|
1986
|
+
temp_file = File.join(temp_dir, 'test.png')
|
|
1987
|
+
FileUtils.touch(temp_file)
|
|
1988
|
+
|
|
1989
|
+
begin
|
|
1990
|
+
expect do
|
|
1991
|
+
described_class.start(['--image', temp_file, '--remove-bg', '--cleanup-cells'])
|
|
1992
|
+
end.to raise_error(RubySpriter::ValidationError, /requires --video or --batch/)
|
|
1993
|
+
ensure
|
|
1994
|
+
FileUtils.rm_rf(temp_dir)
|
|
1995
|
+
end
|
|
1996
|
+
end
|
|
1997
|
+
|
|
1998
|
+
it 'validates cell-cleanup-threshold range (too low)' do
|
|
1999
|
+
expect do
|
|
2000
|
+
described_class.start(['--video', 'test.mp4', '--remove-bg', '--cleanup-cells', '--cell-cleanup-threshold', '0.5'])
|
|
2001
|
+
end.to raise_error(RubySpriter::ValidationError, /between 1.0 and 50.0/)
|
|
2002
|
+
end
|
|
2003
|
+
|
|
2004
|
+
it 'validates cell-cleanup-threshold range (too high)' do
|
|
2005
|
+
expect do
|
|
2006
|
+
described_class.start(['--video', 'test.mp4', '--remove-bg', '--cleanup-cells', '--cell-cleanup-threshold', '55.0'])
|
|
2007
|
+
end.to raise_error(RubySpriter::ValidationError, /between 1.0 and 50.0/)
|
|
2008
|
+
end
|
|
2009
|
+
|
|
2010
|
+
it 'accepts valid configuration' do
|
|
2011
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
2012
|
+
allow(processor_double).to receive(:run)
|
|
2013
|
+
|
|
2014
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
2015
|
+
expect(options[:cleanup_cells]).to be true
|
|
2016
|
+
expect(options[:cell_cleanup_threshold]).to eq(20.0)
|
|
2017
|
+
processor_double
|
|
2018
|
+
end
|
|
2019
|
+
|
|
2020
|
+
expect do
|
|
2021
|
+
described_class.start(['--video', 'test.mp4', '--remove-bg', '--cleanup-cells', '--cell-cleanup-threshold', '20.0'])
|
|
2022
|
+
end.not_to raise_error
|
|
2023
|
+
end
|
|
2024
|
+
end
|
|
2025
|
+
end
|
|
2026
|
+
end
|