ruby_spriter 0.6.5
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +217 -0
- data/Gemfile +17 -0
- data/LICENSE +21 -0
- data/README.md +561 -0
- data/bin/ruby_spriter +20 -0
- data/lib/ruby_spriter/cli.rb +249 -0
- data/lib/ruby_spriter/consolidator.rb +146 -0
- data/lib/ruby_spriter/dependency_checker.rb +174 -0
- data/lib/ruby_spriter/gimp_processor.rb +664 -0
- data/lib/ruby_spriter/metadata_manager.rb +116 -0
- data/lib/ruby_spriter/platform.rb +82 -0
- data/lib/ruby_spriter/processor.rb +251 -0
- data/lib/ruby_spriter/utils/file_helper.rb +57 -0
- data/lib/ruby_spriter/utils/output_formatter.rb +65 -0
- data/lib/ruby_spriter/utils/path_helper.rb +59 -0
- data/lib/ruby_spriter/version.rb +7 -0
- data/lib/ruby_spriter/video_processor.rb +139 -0
- data/lib/ruby_spriter.rb +31 -0
- data/ruby_spriter.gemspec +42 -0
- data/spec/fixtures/image_without_metadata.png +0 -0
- data/spec/fixtures/spritesheet_4x2.png +0 -0
- data/spec/fixtures/spritesheet_4x4.png +0 -0
- data/spec/fixtures/spritesheet_6x2.png +0 -0
- data/spec/fixtures/spritesheet_with_metadata.png +0 -0
- data/spec/fixtures/test_video.mp4 +0 -0
- data/spec/ruby_spriter/cli_spec.rb +1142 -0
- data/spec/ruby_spriter/consolidator_spec.rb +375 -0
- data/spec/ruby_spriter/dependency_checker_spec.rb +0 -0
- data/spec/ruby_spriter/gimp_processor_spec.rb +425 -0
- data/spec/ruby_spriter/metadata_manager_spec.rb +0 -0
- data/spec/ruby_spriter/platform_spec.rb +82 -0
- data/spec/ruby_spriter/processor_spec.rb +0 -0
- data/spec/ruby_spriter/utils/file_helper_spec.rb +71 -0
- data/spec/ruby_spriter/utils/output_formatter_spec.rb +0 -0
- data/spec/ruby_spriter/utils/path_helper_spec.rb +78 -0
- data/spec/ruby_spriter/video_processor_spec.rb +0 -0
- data/spec/spec_helper.rb +41 -0
- metadata +88 -0
|
@@ -0,0 +1,1142 @@
|
|
|
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
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '--version flag' do
|
|
83
|
+
it 'outputs version information and exits' do
|
|
84
|
+
expect do
|
|
85
|
+
expect { described_class.start(['--version']) }
|
|
86
|
+
.to output(/Ruby Spriter v#{RubySpriter::VERSION}/).to_stdout
|
|
87
|
+
end.to raise_error(SystemExit)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'displays platform information' do
|
|
91
|
+
expect do
|
|
92
|
+
expect { described_class.start(['--version']) }
|
|
93
|
+
.to output(/Platform:/).to_stdout
|
|
94
|
+
end.to raise_error(SystemExit)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'displays date information' do
|
|
98
|
+
expect do
|
|
99
|
+
expect { described_class.start(['--version']) }
|
|
100
|
+
.to output(/Date: #{RubySpriter::VERSION_DATE}/).to_stdout
|
|
101
|
+
end.to raise_error(SystemExit)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe '--check-dependencies flag' do
|
|
106
|
+
it 'sets check_dependencies option to true' do
|
|
107
|
+
# Mock DependencyChecker to avoid actually checking dependencies
|
|
108
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
109
|
+
allow(checker_double).to receive(:print_report)
|
|
110
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
111
|
+
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
112
|
+
|
|
113
|
+
expect do
|
|
114
|
+
described_class.start(['--check-dependencies'])
|
|
115
|
+
end.to raise_error(SystemExit) { |error|
|
|
116
|
+
expect(error.status).to eq(0)
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'exits with 0 when all dependencies are satisfied' do
|
|
121
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
122
|
+
allow(checker_double).to receive(:print_report)
|
|
123
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
124
|
+
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
125
|
+
|
|
126
|
+
expect do
|
|
127
|
+
described_class.start(['--check-dependencies'])
|
|
128
|
+
end.to raise_error(SystemExit) { |error|
|
|
129
|
+
expect(error.status).to eq(0)
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'exits with 1 when dependencies are missing' do
|
|
134
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
135
|
+
allow(checker_double).to receive(:print_report)
|
|
136
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(false)
|
|
137
|
+
allow(RubySpriter::DependencyChecker).to receive(:new).and_return(checker_double)
|
|
138
|
+
|
|
139
|
+
expect do
|
|
140
|
+
described_class.start(['--check-dependencies'])
|
|
141
|
+
end.to raise_error(SystemExit) { |error|
|
|
142
|
+
expect(error.status).to eq(1)
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'calls DependencyChecker with verbose: true' do
|
|
147
|
+
checker_double = instance_double(RubySpriter::DependencyChecker)
|
|
148
|
+
allow(checker_double).to receive(:print_report)
|
|
149
|
+
allow(checker_double).to receive(:all_satisfied?).and_return(true)
|
|
150
|
+
|
|
151
|
+
expect(RubySpriter::DependencyChecker).to receive(:new).with(verbose: true).and_return(checker_double)
|
|
152
|
+
|
|
153
|
+
expect do
|
|
154
|
+
described_class.start(['--check-dependencies'])
|
|
155
|
+
end.to raise_error(SystemExit)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
describe '--image flag' do
|
|
161
|
+
let(:fixture_with_meta) { File.join(__dir__, '..', 'fixtures', 'spritesheet_with_metadata.png') }
|
|
162
|
+
let(:fixture_without_meta) { File.join(__dir__, '..', 'fixtures', 'image_without_metadata.png') }
|
|
163
|
+
|
|
164
|
+
describe 'argument parsing' do
|
|
165
|
+
it 'sets image option with --image flag' do
|
|
166
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
167
|
+
allow(processor_double).to receive(:run)
|
|
168
|
+
|
|
169
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
170
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
171
|
+
processor_double
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
described_class.start(['--image', fixture_with_meta])
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it 'supports short form -i flag' do
|
|
178
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
179
|
+
allow(processor_double).to receive(:run)
|
|
180
|
+
|
|
181
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
182
|
+
expect(options[:image]).to eq(fixture_without_meta)
|
|
183
|
+
processor_double
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
described_class.start(['-i', fixture_without_meta])
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it 'accepts file path with spaces' do
|
|
190
|
+
# Create a temp file with spaces in the name for this test
|
|
191
|
+
temp_file = File.join(@test_dir, 'file with spaces.png')
|
|
192
|
+
FileUtils.cp(fixture_with_meta, temp_file)
|
|
193
|
+
|
|
194
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
195
|
+
allow(processor_double).to receive(:run)
|
|
196
|
+
|
|
197
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
198
|
+
expect(options[:image]).to eq(temp_file)
|
|
199
|
+
processor_double
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
described_class.start(['--image', temp_file])
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
describe 'mutual exclusivity with other input modes' do
|
|
207
|
+
it 'cannot be used with --video' do
|
|
208
|
+
expect do
|
|
209
|
+
described_class.start(['--video', 'test.mp4', '--image', fixture_with_meta])
|
|
210
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'cannot be used with --consolidate' do
|
|
214
|
+
expect do
|
|
215
|
+
described_class.start(['--consolidate', 'a.png,b.png', '--image', fixture_with_meta])
|
|
216
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it 'cannot be used with --verify' do
|
|
220
|
+
expect do
|
|
221
|
+
described_class.start(['--verify', fixture_with_meta, '--image', fixture_without_meta])
|
|
222
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'can be used alone without error' do
|
|
226
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
227
|
+
allow(processor_double).to receive(:run)
|
|
228
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
229
|
+
|
|
230
|
+
expect do
|
|
231
|
+
described_class.start(['--image', fixture_with_meta])
|
|
232
|
+
end.not_to raise_error
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
describe 'file validation' do
|
|
237
|
+
describe 'file existence' do
|
|
238
|
+
it 'raises error for non-existent file' do
|
|
239
|
+
expect do
|
|
240
|
+
described_class.start(['--image', 'nonexistent.png'])
|
|
241
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'accepts existing PNG file with metadata' do
|
|
245
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
246
|
+
allow(processor_double).to receive(:run)
|
|
247
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
248
|
+
|
|
249
|
+
expect(File.exist?(fixture_with_meta)).to be true
|
|
250
|
+
expect do
|
|
251
|
+
described_class.start(['--image', fixture_with_meta])
|
|
252
|
+
end.not_to raise_error
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'accepts existing PNG file without metadata' do
|
|
256
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
257
|
+
allow(processor_double).to receive(:run)
|
|
258
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
259
|
+
|
|
260
|
+
expect(File.exist?(fixture_without_meta)).to be true
|
|
261
|
+
expect do
|
|
262
|
+
described_class.start(['--image', fixture_without_meta])
|
|
263
|
+
end.not_to raise_error
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
describe 'file extension validation' do
|
|
268
|
+
it 'accepts .png extension' do
|
|
269
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
270
|
+
allow(processor_double).to receive(:run)
|
|
271
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
272
|
+
|
|
273
|
+
expect(File.extname(fixture_with_meta)).to eq('.png')
|
|
274
|
+
expect do
|
|
275
|
+
described_class.start(['--image', fixture_with_meta])
|
|
276
|
+
end.not_to raise_error
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
it 'accepts .PNG extension (case insensitive)' do
|
|
280
|
+
# Create a temp file with uppercase extension
|
|
281
|
+
temp_file = File.join(@test_dir, 'test.PNG')
|
|
282
|
+
FileUtils.cp(fixture_with_meta, temp_file)
|
|
283
|
+
|
|
284
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
285
|
+
allow(processor_double).to receive(:run)
|
|
286
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
287
|
+
|
|
288
|
+
expect do
|
|
289
|
+
described_class.start(['--image', temp_file])
|
|
290
|
+
end.not_to raise_error
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
it 'rejects .jpg extension' do
|
|
294
|
+
# Create a fake .jpg file (doesn't need to be valid JPG for this test)
|
|
295
|
+
temp_file = File.join(@test_dir, 'test.jpg')
|
|
296
|
+
FileUtils.touch(temp_file)
|
|
297
|
+
|
|
298
|
+
expect do
|
|
299
|
+
described_class.start(['--image', temp_file])
|
|
300
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.jpg/)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it 'rejects .jpeg extension' do
|
|
304
|
+
temp_file = File.join(@test_dir, 'test.jpeg')
|
|
305
|
+
FileUtils.touch(temp_file)
|
|
306
|
+
|
|
307
|
+
expect do
|
|
308
|
+
described_class.start(['--image', temp_file])
|
|
309
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.jpeg/)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
it 'rejects .gif extension' do
|
|
313
|
+
temp_file = File.join(@test_dir, 'test.gif')
|
|
314
|
+
FileUtils.touch(temp_file)
|
|
315
|
+
|
|
316
|
+
expect do
|
|
317
|
+
described_class.start(['--image', temp_file])
|
|
318
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.gif/)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it 'rejects .bmp extension' do
|
|
322
|
+
temp_file = File.join(@test_dir, 'test.bmp')
|
|
323
|
+
FileUtils.touch(temp_file)
|
|
324
|
+
|
|
325
|
+
expect do
|
|
326
|
+
described_class.start(['--image', temp_file])
|
|
327
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file, got: \.bmp/)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
it 'rejects file with no extension' do
|
|
331
|
+
temp_file = File.join(@test_dir, 'testfile')
|
|
332
|
+
FileUtils.touch(temp_file)
|
|
333
|
+
|
|
334
|
+
expect do
|
|
335
|
+
described_class.start(['--image', temp_file])
|
|
336
|
+
end.to raise_error(RubySpriter::ValidationError, /--image expects \.png file/)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
describe 'integration with processing options' do
|
|
342
|
+
it 'works with --scale option' do
|
|
343
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
344
|
+
allow(processor_double).to receive(:run)
|
|
345
|
+
|
|
346
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
347
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
348
|
+
expect(options[:scale_percent]).to eq(50)
|
|
349
|
+
processor_double
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
described_class.start(['--image', fixture_with_meta, '--scale', '50'])
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it 'works with --remove-bg option' do
|
|
356
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
357
|
+
allow(processor_double).to receive(:run)
|
|
358
|
+
|
|
359
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
360
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
361
|
+
expect(options[:remove_bg]).to eq(true)
|
|
362
|
+
processor_double
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
described_class.start(['--image', fixture_with_meta, '--remove-bg'])
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
it 'works with --sharpen option' do
|
|
369
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
370
|
+
allow(processor_double).to receive(:run)
|
|
371
|
+
|
|
372
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
373
|
+
expect(options[:image]).to eq(fixture_without_meta)
|
|
374
|
+
expect(options[:sharpen]).to eq(true)
|
|
375
|
+
processor_double
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
described_class.start(['--image', fixture_without_meta, '--sharpen'])
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
it 'works with --interpolation option' do
|
|
382
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
383
|
+
allow(processor_double).to receive(:run)
|
|
384
|
+
|
|
385
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
386
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
387
|
+
expect(options[:scale_interpolation]).to eq('nohalo')
|
|
388
|
+
processor_double
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
described_class.start(['--image', fixture_with_meta, '--interpolation', 'nohalo'])
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
it 'works with multiple processing options combined' do
|
|
395
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
396
|
+
allow(processor_double).to receive(:run)
|
|
397
|
+
|
|
398
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
399
|
+
expect(options[:image]).to eq(fixture_without_meta)
|
|
400
|
+
expect(options[:scale_percent]).to eq(50)
|
|
401
|
+
expect(options[:remove_bg]).to eq(true)
|
|
402
|
+
expect(options[:sharpen]).to eq(true)
|
|
403
|
+
expect(options[:scale_interpolation]).to eq('lohalo')
|
|
404
|
+
processor_double
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
described_class.start([
|
|
408
|
+
'--image', fixture_without_meta,
|
|
409
|
+
'--scale', '50',
|
|
410
|
+
'--remove-bg',
|
|
411
|
+
'--sharpen',
|
|
412
|
+
'--interpolation', 'lohalo'
|
|
413
|
+
])
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
it 'works with --output option' do
|
|
417
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
418
|
+
allow(processor_double).to receive(:run)
|
|
419
|
+
|
|
420
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
421
|
+
expect(options[:image]).to eq(fixture_with_meta)
|
|
422
|
+
expect(options[:output]).to eq('custom_output.png')
|
|
423
|
+
processor_double
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
described_class.start(['--image', fixture_with_meta, '--output', 'custom_output.png'])
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
describe '--video flag' do
|
|
432
|
+
let(:fixture_video) { File.join(__dir__, '..', 'fixtures', 'test_video.mp4') }
|
|
433
|
+
|
|
434
|
+
describe 'argument parsing' do
|
|
435
|
+
it 'sets video option with --video flag' 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[:video]).to eq(fixture_video)
|
|
441
|
+
processor_double
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
described_class.start(['--video', fixture_video])
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
it 'supports short form -v flag' do
|
|
448
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
449
|
+
allow(processor_double).to receive(:run)
|
|
450
|
+
|
|
451
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
452
|
+
expect(options[:video]).to eq(fixture_video)
|
|
453
|
+
processor_double
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
described_class.start(['-v', fixture_video])
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
it 'accepts file path with spaces' do
|
|
460
|
+
# Create a temp file with spaces in the name for this test
|
|
461
|
+
temp_file = File.join(@test_dir, 'video with spaces.mp4')
|
|
462
|
+
FileUtils.cp(fixture_video, temp_file)
|
|
463
|
+
|
|
464
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
465
|
+
allow(processor_double).to receive(:run)
|
|
466
|
+
|
|
467
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
468
|
+
expect(options[:video]).to eq(temp_file)
|
|
469
|
+
processor_double
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
described_class.start(['--video', temp_file])
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
describe 'mutual exclusivity with other input modes' do
|
|
477
|
+
it 'cannot be used with --image' do
|
|
478
|
+
expect do
|
|
479
|
+
described_class.start(['--video', fixture_video, '--image', 'test.png'])
|
|
480
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
it 'cannot be used with --consolidate' do
|
|
484
|
+
expect do
|
|
485
|
+
described_class.start(['--video', fixture_video, '--consolidate', 'a.png,b.png'])
|
|
486
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
it 'cannot be used with --verify' do
|
|
490
|
+
expect do
|
|
491
|
+
described_class.start(['--video', fixture_video, '--verify', 'test.png'])
|
|
492
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
it 'can be used alone without error' do
|
|
496
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
497
|
+
allow(processor_double).to receive(:run)
|
|
498
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
499
|
+
|
|
500
|
+
expect do
|
|
501
|
+
described_class.start(['--video', fixture_video])
|
|
502
|
+
end.not_to raise_error
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
describe 'file validation' do
|
|
507
|
+
describe 'file existence' do
|
|
508
|
+
it 'raises error for non-existent file' do
|
|
509
|
+
expect do
|
|
510
|
+
described_class.start(['--video', 'nonexistent.mp4'])
|
|
511
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
it 'accepts existing MP4 file' do
|
|
515
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
516
|
+
allow(processor_double).to receive(:run)
|
|
517
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
518
|
+
|
|
519
|
+
expect(File.exist?(fixture_video)).to be true
|
|
520
|
+
expect do
|
|
521
|
+
described_class.start(['--video', fixture_video])
|
|
522
|
+
end.not_to raise_error
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
describe 'file extension validation' do
|
|
527
|
+
it 'accepts .mp4 extension' do
|
|
528
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
529
|
+
allow(processor_double).to receive(:run)
|
|
530
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
531
|
+
|
|
532
|
+
expect(File.extname(fixture_video)).to eq('.mp4')
|
|
533
|
+
expect do
|
|
534
|
+
described_class.start(['--video', fixture_video])
|
|
535
|
+
end.not_to raise_error
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
it 'accepts .MP4 extension (case insensitive)' do
|
|
539
|
+
# Create a temp file with uppercase extension
|
|
540
|
+
temp_file = File.join(@test_dir, 'test.MP4')
|
|
541
|
+
FileUtils.cp(fixture_video, temp_file)
|
|
542
|
+
|
|
543
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
544
|
+
allow(processor_double).to receive(:run)
|
|
545
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
546
|
+
|
|
547
|
+
expect do
|
|
548
|
+
described_class.start(['--video', temp_file])
|
|
549
|
+
end.not_to raise_error
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
it 'rejects .avi extension' do
|
|
553
|
+
temp_file = File.join(@test_dir, 'test.avi')
|
|
554
|
+
FileUtils.touch(temp_file)
|
|
555
|
+
|
|
556
|
+
expect do
|
|
557
|
+
described_class.start(['--video', temp_file])
|
|
558
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.avi/)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
it 'rejects .mov extension' do
|
|
562
|
+
temp_file = File.join(@test_dir, 'test.mov')
|
|
563
|
+
FileUtils.touch(temp_file)
|
|
564
|
+
|
|
565
|
+
expect do
|
|
566
|
+
described_class.start(['--video', temp_file])
|
|
567
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.mov/)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
it 'rejects .mkv extension' do
|
|
571
|
+
temp_file = File.join(@test_dir, 'test.mkv')
|
|
572
|
+
FileUtils.touch(temp_file)
|
|
573
|
+
|
|
574
|
+
expect do
|
|
575
|
+
described_class.start(['--video', temp_file])
|
|
576
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.mkv/)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
it 'rejects .wmv extension' do
|
|
580
|
+
temp_file = File.join(@test_dir, 'test.wmv')
|
|
581
|
+
FileUtils.touch(temp_file)
|
|
582
|
+
|
|
583
|
+
expect do
|
|
584
|
+
described_class.start(['--video', temp_file])
|
|
585
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file, got: \.wmv/)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
it 'rejects file with no extension' do
|
|
589
|
+
temp_file = File.join(@test_dir, 'videofile')
|
|
590
|
+
FileUtils.touch(temp_file)
|
|
591
|
+
|
|
592
|
+
expect do
|
|
593
|
+
described_class.start(['--video', temp_file])
|
|
594
|
+
end.to raise_error(RubySpriter::ValidationError, /--video expects \.mp4 file/)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
describe 'integration with video-specific options' do
|
|
600
|
+
it 'works with --frames option' do
|
|
601
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
602
|
+
allow(processor_double).to receive(:run)
|
|
603
|
+
|
|
604
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
605
|
+
expect(options[:video]).to eq(fixture_video)
|
|
606
|
+
expect(options[:frame_count]).to eq(32)
|
|
607
|
+
processor_double
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
described_class.start(['--video', fixture_video, '--frames', '32'])
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
it 'works with --columns option' do
|
|
614
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
615
|
+
allow(processor_double).to receive(:run)
|
|
616
|
+
|
|
617
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
618
|
+
expect(options[:video]).to eq(fixture_video)
|
|
619
|
+
expect(options[:columns]).to eq(8)
|
|
620
|
+
processor_double
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
described_class.start(['--video', fixture_video, '--columns', '8'])
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
it 'works with --width option' do
|
|
627
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
628
|
+
allow(processor_double).to receive(:run)
|
|
629
|
+
|
|
630
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
631
|
+
expect(options[:video]).to eq(fixture_video)
|
|
632
|
+
expect(options[:max_width]).to eq(640)
|
|
633
|
+
processor_double
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
described_class.start(['--video', fixture_video, '--width', '640'])
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
it 'works with --background option' do
|
|
640
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
641
|
+
allow(processor_double).to receive(:run)
|
|
642
|
+
|
|
643
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
644
|
+
expect(options[:video]).to eq(fixture_video)
|
|
645
|
+
expect(options[:bg_color]).to eq('white')
|
|
646
|
+
processor_double
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
described_class.start(['--video', fixture_video, '--background', 'white'])
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
it 'works with multiple video options combined' do
|
|
653
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
654
|
+
allow(processor_double).to receive(:run)
|
|
655
|
+
|
|
656
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
657
|
+
expect(options[:video]).to eq(fixture_video)
|
|
658
|
+
expect(options[:frame_count]).to eq(64)
|
|
659
|
+
expect(options[:columns]).to eq(8)
|
|
660
|
+
expect(options[:max_width]).to eq(480)
|
|
661
|
+
expect(options[:bg_color]).to eq('white')
|
|
662
|
+
processor_double
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
described_class.start([
|
|
666
|
+
'--video', fixture_video,
|
|
667
|
+
'--frames', '64',
|
|
668
|
+
'--columns', '8',
|
|
669
|
+
'--width', '480',
|
|
670
|
+
'--background', 'white'
|
|
671
|
+
])
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
describe 'integration with processing options' do
|
|
676
|
+
it 'works with --scale option' do
|
|
677
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
678
|
+
allow(processor_double).to receive(:run)
|
|
679
|
+
|
|
680
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
681
|
+
expect(options[:video]).to eq(fixture_video)
|
|
682
|
+
expect(options[:scale_percent]).to eq(50)
|
|
683
|
+
processor_double
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
described_class.start(['--video', fixture_video, '--scale', '50'])
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
it 'works with --remove-bg option' do
|
|
690
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
691
|
+
allow(processor_double).to receive(:run)
|
|
692
|
+
|
|
693
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
694
|
+
expect(options[:video]).to eq(fixture_video)
|
|
695
|
+
expect(options[:remove_bg]).to eq(true)
|
|
696
|
+
processor_double
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
described_class.start(['--video', fixture_video, '--remove-bg'])
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
it 'works with --sharpen option' do
|
|
703
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
704
|
+
allow(processor_double).to receive(:run)
|
|
705
|
+
|
|
706
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
707
|
+
expect(options[:video]).to eq(fixture_video)
|
|
708
|
+
expect(options[:sharpen]).to eq(true)
|
|
709
|
+
processor_double
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
described_class.start(['--video', fixture_video, '--sharpen'])
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
it 'works with --interpolation option' do
|
|
716
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
717
|
+
allow(processor_double).to receive(:run)
|
|
718
|
+
|
|
719
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
720
|
+
expect(options[:video]).to eq(fixture_video)
|
|
721
|
+
expect(options[:scale_interpolation]).to eq('lohalo')
|
|
722
|
+
processor_double
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
described_class.start(['--video', fixture_video, '--interpolation', 'lohalo'])
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
it 'works with all options combined' do
|
|
729
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
730
|
+
allow(processor_double).to receive(:run)
|
|
731
|
+
|
|
732
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
733
|
+
expect(options[:video]).to eq(fixture_video)
|
|
734
|
+
expect(options[:frame_count]).to eq(32)
|
|
735
|
+
expect(options[:columns]).to eq(8)
|
|
736
|
+
expect(options[:scale_percent]).to eq(50)
|
|
737
|
+
expect(options[:remove_bg]).to eq(true)
|
|
738
|
+
expect(options[:sharpen]).to eq(true)
|
|
739
|
+
expect(options[:scale_interpolation]).to eq('nohalo')
|
|
740
|
+
processor_double
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
described_class.start([
|
|
744
|
+
'--video', fixture_video,
|
|
745
|
+
'--frames', '32',
|
|
746
|
+
'--columns', '8',
|
|
747
|
+
'--scale', '50',
|
|
748
|
+
'--remove-bg',
|
|
749
|
+
'--sharpen',
|
|
750
|
+
'--interpolation', 'nohalo'
|
|
751
|
+
])
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
it 'works with --output option' do
|
|
755
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
756
|
+
allow(processor_double).to receive(:run)
|
|
757
|
+
|
|
758
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
759
|
+
expect(options[:video]).to eq(fixture_video)
|
|
760
|
+
expect(options[:output]).to eq('custom_spritesheet.png')
|
|
761
|
+
processor_double
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
described_class.start(['--video', fixture_video, '--output', 'custom_spritesheet.png'])
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
describe 'preset configurations' do
|
|
769
|
+
it 'works with --preset thumbnail' do
|
|
770
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
771
|
+
allow(processor_double).to receive(:run)
|
|
772
|
+
|
|
773
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
774
|
+
expect(options[:video]).to eq(fixture_video)
|
|
775
|
+
expect(options[:columns]).to eq(3)
|
|
776
|
+
expect(options[:frame_count]).to eq(9)
|
|
777
|
+
expect(options[:max_width]).to eq(240)
|
|
778
|
+
processor_double
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
described_class.start(['--video', fixture_video, '--preset', 'thumbnail'])
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
it 'works with --preset preview' do
|
|
785
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
786
|
+
allow(processor_double).to receive(:run)
|
|
787
|
+
|
|
788
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
789
|
+
expect(options[:video]).to eq(fixture_video)
|
|
790
|
+
expect(options[:columns]).to eq(4)
|
|
791
|
+
expect(options[:frame_count]).to eq(16)
|
|
792
|
+
expect(options[:max_width]).to eq(400)
|
|
793
|
+
processor_double
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
described_class.start(['--video', fixture_video, '--preset', 'preview'])
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
it 'works with --preset detailed' do
|
|
800
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
801
|
+
allow(processor_double).to receive(:run)
|
|
802
|
+
|
|
803
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
804
|
+
expect(options[:video]).to eq(fixture_video)
|
|
805
|
+
expect(options[:columns]).to eq(10)
|
|
806
|
+
expect(options[:frame_count]).to eq(50)
|
|
807
|
+
expect(options[:max_width]).to eq(320)
|
|
808
|
+
processor_double
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
described_class.start(['--video', fixture_video, '--preset', 'detailed'])
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
it 'works with --preset contact' do
|
|
815
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
816
|
+
allow(processor_double).to receive(:run)
|
|
817
|
+
|
|
818
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
819
|
+
expect(options[:video]).to eq(fixture_video)
|
|
820
|
+
expect(options[:columns]).to eq(8)
|
|
821
|
+
expect(options[:frame_count]).to eq(64)
|
|
822
|
+
expect(options[:max_width]).to eq(160)
|
|
823
|
+
processor_double
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
described_class.start(['--video', fixture_video, '--preset', 'contact'])
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
describe '--consolidate flag' do
|
|
832
|
+
# Real spritesheets generated from test_video.mp4 using --video
|
|
833
|
+
# These demonstrate the actual workflow: --video creates spritesheets, --consolidate combines them
|
|
834
|
+
let(:spritesheet_4x2) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x2.png') } # 2 cols, 2 rows, 4 frames
|
|
835
|
+
let(:spritesheet_6x2) { File.join(__dir__, '..', 'fixtures', 'spritesheet_6x2.png') } # 2 cols, 3 rows, 6 frames
|
|
836
|
+
let(:spritesheet_4x4) { File.join(__dir__, '..', 'fixtures', 'spritesheet_4x4.png') } # 4 cols, 1 row, 4 frames (different columns)
|
|
837
|
+
|
|
838
|
+
# Generic PNG fixtures for edge case testing
|
|
839
|
+
let(:fixture_with_meta) { File.join(__dir__, '..', 'fixtures', 'spritesheet_with_metadata.png') }
|
|
840
|
+
let(:fixture_without_meta) { File.join(__dir__, '..', 'fixtures', 'image_without_metadata.png') }
|
|
841
|
+
|
|
842
|
+
describe 'argument parsing' do
|
|
843
|
+
it 'accepts comma-separated list of files' do
|
|
844
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
845
|
+
allow(processor_double).to receive(:run)
|
|
846
|
+
|
|
847
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
848
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
849
|
+
processor_double
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
it 'accepts three or more files' do
|
|
856
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
857
|
+
allow(processor_double).to receive(:run)
|
|
858
|
+
|
|
859
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
860
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2, spritesheet_4x4])
|
|
861
|
+
processor_double
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2},#{spritesheet_4x4}"])
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
it 'accepts file paths with spaces' do
|
|
868
|
+
# Create temp files with spaces in names
|
|
869
|
+
temp_file1 = File.join(@test_dir, 'file with spaces 1.png')
|
|
870
|
+
temp_file2 = File.join(@test_dir, 'file with spaces 2.png')
|
|
871
|
+
FileUtils.cp(spritesheet_4x2, temp_file1)
|
|
872
|
+
FileUtils.cp(spritesheet_6x2, temp_file2)
|
|
873
|
+
|
|
874
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
875
|
+
allow(processor_double).to receive(:run)
|
|
876
|
+
|
|
877
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
878
|
+
expect(options[:consolidate]).to eq([temp_file1, temp_file2])
|
|
879
|
+
processor_double
|
|
880
|
+
end
|
|
881
|
+
|
|
882
|
+
described_class.start(['--consolidate', "#{temp_file1},#{temp_file2}"])
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
describe 'minimum file count validation' do
|
|
887
|
+
it 'requires at least 2 files' do
|
|
888
|
+
expect do
|
|
889
|
+
described_class.start(['--consolidate', spritesheet_4x2])
|
|
890
|
+
end.to raise_error(RubySpriter::ValidationError, /requires at least 2 files/)
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
it 'accepts exactly 2 files' do
|
|
894
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
895
|
+
allow(processor_double).to receive(:run)
|
|
896
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
897
|
+
|
|
898
|
+
expect do
|
|
899
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
900
|
+
end.not_to raise_error
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
it 'accepts more than 2 files' do
|
|
904
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
905
|
+
allow(processor_double).to receive(:run)
|
|
906
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
907
|
+
|
|
908
|
+
expect do
|
|
909
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2},#{spritesheet_4x4}"])
|
|
910
|
+
end.not_to raise_error
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
|
|
914
|
+
describe 'mutual exclusivity with other input modes' do
|
|
915
|
+
it 'cannot be used with --video' do
|
|
916
|
+
expect do
|
|
917
|
+
described_class.start(['--video', 'test.mp4', '--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
918
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
it 'cannot be used with --image' do
|
|
922
|
+
expect do
|
|
923
|
+
described_class.start(['--image', spritesheet_4x2, '--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
924
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
it 'cannot be used with --verify' do
|
|
928
|
+
expect do
|
|
929
|
+
described_class.start(['--verify', spritesheet_4x2, '--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
930
|
+
end.to raise_error(RubySpriter::ValidationError, /Cannot use multiple input modes/)
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
it 'can be used alone without error' do
|
|
934
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
935
|
+
allow(processor_double).to receive(:run)
|
|
936
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
937
|
+
|
|
938
|
+
expect do
|
|
939
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
940
|
+
end.not_to raise_error
|
|
941
|
+
end
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
describe 'file validation' do
|
|
945
|
+
describe 'file existence' do
|
|
946
|
+
it 'raises error if first file does not exist' do
|
|
947
|
+
expect do
|
|
948
|
+
described_class.start(['--consolidate', "nonexistent1.png,#{spritesheet_4x2}"])
|
|
949
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
it 'raises error if second file does not exist' do
|
|
953
|
+
expect do
|
|
954
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},nonexistent2.png"])
|
|
955
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
it 'raises error if any file in list does not exist' do
|
|
959
|
+
expect do
|
|
960
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},nonexistent.png,#{spritesheet_6x2}"])
|
|
961
|
+
end.to raise_error(RubySpriter::ValidationError, /File not found/)
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
it 'accepts all existing spritesheet files' do
|
|
965
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
966
|
+
allow(processor_double).to receive(:run)
|
|
967
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
968
|
+
|
|
969
|
+
expect(File.exist?(spritesheet_4x2)).to be true
|
|
970
|
+
expect(File.exist?(spritesheet_6x2)).to be true
|
|
971
|
+
|
|
972
|
+
expect do
|
|
973
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
974
|
+
end.not_to raise_error
|
|
975
|
+
end
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
describe 'file extension validation' do
|
|
979
|
+
it 'accepts all .png spritesheet files' do
|
|
980
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
981
|
+
allow(processor_double).to receive(:run)
|
|
982
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
983
|
+
|
|
984
|
+
expect do
|
|
985
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}"])
|
|
986
|
+
end.not_to raise_error
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
it 'accepts .PNG extension (case insensitive)' do
|
|
990
|
+
temp_file1 = File.join(@test_dir, 'test1.PNG')
|
|
991
|
+
temp_file2 = File.join(@test_dir, 'test2.PNG')
|
|
992
|
+
FileUtils.cp(spritesheet_4x2, temp_file1)
|
|
993
|
+
FileUtils.cp(spritesheet_6x2, temp_file2)
|
|
994
|
+
|
|
995
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
996
|
+
allow(processor_double).to receive(:run)
|
|
997
|
+
allow(RubySpriter::Processor).to receive(:new).and_return(processor_double)
|
|
998
|
+
|
|
999
|
+
expect do
|
|
1000
|
+
described_class.start(['--consolidate', "#{temp_file1},#{temp_file2}"])
|
|
1001
|
+
end.not_to raise_error
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
it 'rejects files with .jpg extension' do
|
|
1005
|
+
temp_file = File.join(@test_dir, 'test.jpg')
|
|
1006
|
+
FileUtils.touch(temp_file)
|
|
1007
|
+
|
|
1008
|
+
expect do
|
|
1009
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file}"])
|
|
1010
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file, got: \.jpg/)
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
it 'rejects files with .mp4 extension' do
|
|
1014
|
+
temp_file = File.join(@test_dir, 'test.mp4')
|
|
1015
|
+
FileUtils.touch(temp_file)
|
|
1016
|
+
|
|
1017
|
+
expect do
|
|
1018
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file}"])
|
|
1019
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file, got: \.mp4/)
|
|
1020
|
+
end
|
|
1021
|
+
|
|
1022
|
+
it 'rejects files with no extension' do
|
|
1023
|
+
temp_file = File.join(@test_dir, 'noextension')
|
|
1024
|
+
FileUtils.touch(temp_file)
|
|
1025
|
+
|
|
1026
|
+
expect do
|
|
1027
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file}"])
|
|
1028
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file/)
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
it 'validates all files in the list' do
|
|
1032
|
+
temp_file1 = File.join(@test_dir, 'test1.jpg')
|
|
1033
|
+
temp_file2 = File.join(@test_dir, 'test2.gif')
|
|
1034
|
+
FileUtils.touch(temp_file1)
|
|
1035
|
+
FileUtils.touch(temp_file2)
|
|
1036
|
+
|
|
1037
|
+
# Should fail on the first non-PNG file
|
|
1038
|
+
expect do
|
|
1039
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{temp_file1},#{temp_file2}"])
|
|
1040
|
+
end.to raise_error(RubySpriter::ValidationError, /--consolidate expects \.png file/)
|
|
1041
|
+
end
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
describe 'consolidation-specific options' do
|
|
1046
|
+
it 'works with --validate-columns flag (default true)' do
|
|
1047
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1048
|
+
allow(processor_double).to receive(:run)
|
|
1049
|
+
|
|
1050
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1051
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1052
|
+
expect(options[:validate_columns]).to eq(true)
|
|
1053
|
+
processor_double
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--validate-columns'])
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
it 'works with --no-validate-columns flag' do
|
|
1060
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1061
|
+
allow(processor_double).to receive(:run)
|
|
1062
|
+
|
|
1063
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1064
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1065
|
+
expect(options[:validate_columns]).to eq(false)
|
|
1066
|
+
processor_double
|
|
1067
|
+
end
|
|
1068
|
+
|
|
1069
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--no-validate-columns'])
|
|
1070
|
+
end
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1073
|
+
describe 'integration with other options' do
|
|
1074
|
+
it 'works with --output option' do
|
|
1075
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1076
|
+
allow(processor_double).to receive(:run)
|
|
1077
|
+
|
|
1078
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1079
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1080
|
+
expect(options[:output]).to eq('consolidated_output.png')
|
|
1081
|
+
processor_double
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--output', 'consolidated_output.png'])
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
it 'works with --debug option' do
|
|
1088
|
+
processor_double = instance_double(RubySpriter::Processor)
|
|
1089
|
+
allow(processor_double).to receive(:run)
|
|
1090
|
+
|
|
1091
|
+
allow(RubySpriter::Processor).to receive(:new) do |options|
|
|
1092
|
+
expect(options[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1093
|
+
expect(options[:debug]).to eq(true)
|
|
1094
|
+
expect(options[:keep_temp]).to eq(true)
|
|
1095
|
+
processor_double
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
described_class.start(['--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}", '--debug'])
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
it 'works with multiple options combined' 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[:consolidate]).to eq([spritesheet_4x2, spritesheet_6x2])
|
|
1107
|
+
expect(options[:validate_columns]).to eq(false)
|
|
1108
|
+
expect(options[:output]).to eq('combined.png')
|
|
1109
|
+
expect(options[:debug]).to eq(true)
|
|
1110
|
+
processor_double
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
described_class.start([
|
|
1114
|
+
'--consolidate', "#{spritesheet_4x2},#{spritesheet_6x2}",
|
|
1115
|
+
'--no-validate-columns',
|
|
1116
|
+
'--output', 'combined.png',
|
|
1117
|
+
'--debug'
|
|
1118
|
+
])
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
describe 'error handling' do
|
|
1124
|
+
describe 'invalid option' do
|
|
1125
|
+
it 'displays error message for invalid option' do
|
|
1126
|
+
expect do
|
|
1127
|
+
expect { described_class.start(['--invalid-option']) }
|
|
1128
|
+
.to output(/Error:.*invalid/).to_stdout
|
|
1129
|
+
end.to raise_error(SystemExit) { |error|
|
|
1130
|
+
expect(error.status).to eq(1)
|
|
1131
|
+
}
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
it 'suggests using --help' do
|
|
1135
|
+
expect do
|
|
1136
|
+
expect { described_class.start(['--invalid-option']) }
|
|
1137
|
+
.to output(/Use --help for usage information/).to_stdout
|
|
1138
|
+
end.to raise_error(SystemExit)
|
|
1139
|
+
end
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
end
|